Reapply fast-URI patch.

Review URL: https://codereview.chromium.org/2245533004 .
This commit is contained in:
Lasse R.H. Nielsen 2016-08-17 10:54:24 +02:00
parent 3266e73128
commit afbbbb97cf
9 changed files with 2553 additions and 1200 deletions

View file

@ -165,6 +165,9 @@ Patch release, resolves two issues and improves performance:
### Core library changes
* `dart:core`
* Improved performance when parsing some common URIs.
* Fixed bug in `Uri.resolve` (SDK issue [26804](https://github.com/dart-lang/sdk/issues/26804)).
* `dart:io`
* Adds file locking modes `FileLock.BLOCKING_SHARED` and
`FileLock.BLOCKING_EXCLUSIVE`.

View file

@ -62,23 +62,7 @@ class FastUri implements Uri {
bool get hasFragment => false;
@override
int get hashCode {
// This code is copied from the standard Uri implementation.
// It is important that Uri and FastUri generate compatible hashCodes
// because Uri and FastUri may be used as keys in the same map.
int combine(part, current) {
// The sum is truncated to 30 bits to make sure it fits into a Smi.
return (current * 31 + part.hashCode) & 0x3FFFFFFF;
}
return _hashCode ??= combine(
scheme,
combine(
userInfo,
combine(
host,
combine(port,
combine(path, combine(query, combine(fragment, 1)))))));
}
int get hashCode => _text.hashCode;
@override
bool get hasPort => false;

View file

@ -16,18 +16,20 @@ Uri _unsupportedUriBase() {
_UriBaseClosure _uriBaseClosure = _unsupportedUriBase;
@patch class Uri {
static final bool _isWindowsCached = _isWindowsPlatform;
@patch static bool get _isWindows => _isWindowsCached;
@patch static Uri get base => _uriBaseClosure();
}
@patch class _Uri {
static final bool _isWindowsCached = _isWindowsPlatform;
static bool get _isWindowsPlatform native "Uri_isWindowsPlatform";
@patch static bool get _isWindows => _isWindowsCached;
@patch static String _uriEncode(List<int> canonicalTable,
String text,
Encoding encoding,
bool spaceToPlus) {
String text,
Encoding encoding,
bool spaceToPlus) {
// First check if the text will be changed by encoding.
int i = 0;
if (identical(encoding, UTF8) ||

View file

@ -586,16 +586,18 @@ class NoSuchMethodError {
@patch
class Uri {
@patch
static bool get _isWindows => false;
@patch
static Uri get base {
String uri = Primitives.currentUri();
if (uri != null) return Uri.parse(uri);
throw new UnsupportedError("'Uri.base' is not supported");
}
}
@patch
class _Uri {
@patch
static bool get _isWindows => false;
// Matches a String that _uriEncodes to itself regardless of the kind of
// component. This corresponds to [_unreservedTable], i.e. characters that

File diff suppressed because it is too large Load diff

View file

@ -179,7 +179,7 @@ Future compileAndCheck(String code,
Future compileSources(Map<String, String> sources,
check(MockCompiler compiler)) {
Uri base = new Uri(scheme: 'source');
Uri base = new Uri(scheme: 'source', path: '/');
Uri mainUri = base.resolve('main.dart');
String mainCode = sources['main.dart'];
Expect.isNotNull(mainCode, 'No source code found for "main.dart"');

View file

@ -69,7 +69,7 @@ void main() {
// 2. Some code was refactored, and there are more methods.
// Either situation could be problematic, but in situation 2, it is often
// acceptable to increase [expectedMethodCount] a little.
int expectedMethodCount = 432;
int expectedMethodCount = 466;
Expect.isTrue(
generatedCode.length <= expectedMethodCount,
'Too many compiled methods: '

View file

@ -18,6 +18,13 @@ main() {
testRoundTrip("blåbærgrød", UTF8);
testRoundTrip("blåbærgrød", LATIN1);
testUriEquals("data:,abc?d#e");
testUriEquals("DATA:,ABC?D#E");
testUriEquals("data:,a%20bc?d#e");
testUriEquals("DATA:,A%20BC?D#E");
testUriEquals("data:,a%62c?d#e");
testUriEquals("DATA:,A%42C?D#E");
testUtf8Encoding("\u1000\uffff");
testBytes();
testInvalidCharacters();
@ -250,3 +257,11 @@ expectUriEquals(Uri expect, Uri actual) {
Expect.equals(expect.hasFragment, actual.hasFragment, "hasFragment");
Expect.equals(expect.fragment, actual.fragment, "fragment");
}
void testUriEquals(String uriText) {
var data = UriData.parse(uriText);
var uri = Uri.parse(uriText);
Expect.equals(data.uri, uri);
Expect.equals(data.toString(), uri.data.toString());
Expect.equals(data.toString(), uri.toString());
}

View file

@ -10,6 +10,12 @@ import 'dart:convert';
testUri(String uriText, bool isAbsolute) {
var uri = Uri.parse(uriText);
// Test that parsing a substring works the same as parsing the string.
String wrapper = "://@[]:/%?#";
var embeddedUri = Uri.parse(
"$wrapper$uri$wrapper", wrapper.length, uriText.length + wrapper.length);
Expect.equals(uri, embeddedUri);
Expect.equals(isAbsolute, uri.isAbsolute);
Expect.stringEquals(uriText, uri.toString());
@ -82,7 +88,8 @@ testUriPerRFCs() {
final urisSample = "http://a/b/c/d;p?q";
Uri base = Uri.parse(urisSample);
testResolve(expect, relative) {
Expect.stringEquals(expect, base.resolve(relative).toString());
String name = "$base << $relative";
Expect.stringEquals(expect, base.resolve(relative).toString(), name);
}
// From RFC 3986.
@ -132,19 +139,232 @@ testUriPerRFCs() {
// Additional tests (not from RFC 3986).
testResolve("http://a/b/g;p/h;s", "../g;p/h;s");
base = Uri.parse("s:a/b");
testResolve("s:a/c", "c");
testResolve("s:/c", "../c");
base = Uri.parse("S:a/b");
testResolve("s:a/c", "c");
testResolve("s:/c", "../c");
base = Uri.parse("s:foo");
testResolve("s:bar", "bar");
testResolve("s:bar", "../bar");
base = Uri.parse("S:foo");
testResolve("s:bar", "bar");
testResolve("s:bar", "../bar");
// Special-case (deliberate non-RFC behavior).
base = Uri.parse("foo/bar");
testResolve("foo/baz", "baz");
testResolve("baz", "../baz");
base = Uri.parse("s:/foo");
testResolve("s:/bar", "bar");
testResolve("s:/bar", "../bar");
base = Uri.parse("S:/foo");
testResolve("s:/bar", "bar");
testResolve("s:/bar", "../bar");
// Test non-URI base (no scheme, no authority, relative path).
base = Uri.parse("a/b/c?_#_");
testResolve("a/b/g?q#f", "g?q#f");
testResolve("./", "../..");
testResolve("../", "../../..");
testResolve("a/b/", ".");
testResolve("c", "../../c");
testResolve("c", "../../c"); // Deliberate non-RFC behavior.
base = Uri.parse("../../a/b/c?_#_"); // Initial ".." in base url.
testResolve("../../a/d", "../d");
testResolve("../../../d", "../../../d");
base = Uri.parse("s:a/b");
testResolve("s:/c", "../c");
base = Uri.parse("s://h/p?q#f"); // A simple base.
// Simple references:
testResolve("s2://h2/P?Q#F", "s2://h2/P?Q#F");
testResolve("s://h2/P?Q#F", "//h2/P?Q#F");
testResolve("s://h/P?Q#F", "/P?Q#F");
testResolve("s://h/p?Q#F", "?Q#F");
testResolve("s://h/p?q#F", "#F");
testResolve("s://h/p?q", "");
// Non-simple references:
testResolve("s2://I@h2/P?Q#F%20", "s2://I@h2/P?Q#F%20");
testResolve("s://I@h2/P?Q#F%20", "//I@h2/P?Q#F%20");
testResolve("s://h2/P?Q#F%20", "//h2/P?Q#F%20");
testResolve("s://h/P?Q#F%20", "/P?Q#F%20");
testResolve("s://h/p?Q#F%20", "?Q#F%20");
testResolve("s://h/p?q#F%20", "#F%20");
base = Uri.parse("s://h/p1/p2/p3"); // A simple base with a path.
testResolve("s://h/p1/p2/", ".");
testResolve("s://h/p1/p2/", "./");
testResolve("s://h/p1/", "..");
testResolve("s://h/p1/", "../");
testResolve("s://h/", "../..");
testResolve("s://h/", "../../");
testResolve("s://h/p1/%20", "../%20");
testResolve("s://h/", "../../../..");
testResolve("s://h/", "../../../../");
base = Uri.parse("s://h/p?q#f%20"); // A non-simpe base.
// Simple references:
testResolve("s2://h2/P?Q#F", "s2://h2/P?Q#F");
testResolve("s://h2/P?Q#F", "//h2/P?Q#F");
testResolve("s://h/P?Q#F", "/P?Q#F");
testResolve("s://h/p?Q#F", "?Q#F");
testResolve("s://h/p?q#F", "#F");
testResolve("s://h/p?q", "");
// Non-simple references:
testResolve("s2://I@h2/P?Q#F%20", "s2://I@h2/P?Q#F%20");
testResolve("s://I@h2/P?Q#F%20", "//I@h2/P?Q#F%20");
testResolve("s://h2/P?Q#F%20", "//h2/P?Q#F%20");
testResolve("s://h/P?Q#F%20", "/P?Q#F%20");
testResolve("s://h/p?Q#F%20", "?Q#F%20");
testResolve("s://h/p?q#F%20", "#F%20");
base = Uri.parse("S://h/p1/p2/p3"); // A non-simple base with a path.
testResolve("s://h/p1/p2/", ".");
testResolve("s://h/p1/p2/", "./");
testResolve("s://h/p1/", "..");
testResolve("s://h/p1/", "../");
testResolve("s://h/", "../..");
testResolve("s://h/", "../../");
testResolve("s://h/p1/%20", "../%20");
testResolve("s://h/", "../../../..");
testResolve("s://h/", "../../../../");
base = Uri.parse("../../../"); // A simple relative path.
testResolve("../../../a", "a");
testResolve("../../../../a", "../a");
testResolve("../../../a%20", "a%20");
testResolve("../../../../a%20", "../a%20");
// Tests covering the branches of the merge algorithm in RFC 3986
// with both simple and complex base URIs.
for (var b in ["s://a/pa/pb?q#f", "s://a/pa/pb?q#f%20"]) {
var origBase = Uri.parse(b);
base = origBase;
// if defined(R.scheme) then ...
testResolve("s2://a2/p2?q2#f2", "s2://a2/p2?q2#f2");
// else, if defined(R.authority) then ...
testResolve("s://a2/p2?q2#f2", "//a2/p2?q2#f2");
testResolve("s://a2/?q2#f2", "//a2/../?q2#f2");
testResolve("s://a2?q2#f2", "//a2?q2#f2");
testResolve("s://a2#f2", "//a2#f2");
testResolve("s://a2", "//a2");
// else, if (R.path == "") then ...
// if defined(R.query) then
testResolve("s://a/pa/pb?q2#f2", "?q2#f2");
testResolve("s://a/pa/pb?q2", "?q2");
// else
testResolve("s://a/pa/pb?q#f2", "#f2");
testResolve("s://a/pa/pb?q", "");
// else, if (R.path starts-with "/") then ...
testResolve("s://a/p2?q2#f2", "/p2?q2#f2");
testResolve("s://a/?q2#f2", "/?q2#f2");
testResolve("s://a/#f2", "/#f2");
testResolve("s://a/", "/");
testResolve("s://a/", "/../");
// else ... T.path = merge(Base.path, R.path)
// ... remove-dot-fragments(T.path) ...
// (Cover the merge function and the remove-dot-fragments functions too).
// If base has authority and empty path ...
var emptyPathBase = Uri.parse(b.replaceFirst("/pa/pb", ""));
base = emptyPathBase;
testResolve("s://a/p2?q2#f2", "p2?q2#f2");
testResolve("s://a/p2#f2", "p2#f2");
testResolve("s://a/p2", "p2");
base = origBase;
// otherwise
// (Cover both no authority and non-empty path and both).
var noAuthEmptyPathBase = Uri.parse(b.replaceFirst("//a/pa/pb", ""));
var noAuthAbsPathBase = Uri.parse(b.replaceFirst("//a", ""));
var noAuthRelPathBase = Uri.parse(b.replaceFirst("//a/", ""));
var noAuthRelSinglePathBase = Uri.parse(b.replaceFirst("//a/pa/", ""));
testResolve("s://a/pa/p2?q2#f2", "p2?q2#f2");
testResolve("s://a/pa/p2#f2", "p2#f2");
testResolve("s://a/pa/p2", "p2");
base = noAuthEmptyPathBase;
testResolve("s:p2?q2#f2", "p2?q2#f2");
testResolve("s:p2#f2", "p2#f2");
testResolve("s:p2", "p2");
base = noAuthAbsPathBase;
testResolve("s:/pa/p2?q2#f2", "p2?q2#f2");
testResolve("s:/pa/p2#f2", "p2#f2");
testResolve("s:/pa/p2", "p2");
base = noAuthRelPathBase;
testResolve("s:pa/p2?q2#f2", "p2?q2#f2");
testResolve("s:pa/p2#f2", "p2#f2");
testResolve("s:pa/p2", "p2");
base = noAuthRelSinglePathBase;
testResolve("s:p2?q2#f2", "p2?q2#f2");
testResolve("s:p2#f2", "p2#f2");
testResolve("s:p2", "p2");
// Then remove dot segments.
// A. if input buffer starts with "../" or "./".
// This only happens if base has only a single (may be empty) segment and
// no slash.
base = emptyPathBase;
testResolve("s://a/p2", "../p2");
testResolve("s://a/", "../");
testResolve("s://a/", "..");
testResolve("s://a/p2", "./p2");
testResolve("s://a/", "./");
testResolve("s://a/", ".");
testResolve("s://a/p2", "../../p2");
testResolve("s://a/p2", "../../././p2");
base = noAuthRelSinglePathBase;
testResolve("s:p2", "../p2");
testResolve("s:", "../");
testResolve("s:", "..");
testResolve("s:p2", "./p2");
testResolve("s:", "./");
testResolve("s:", ".");
testResolve("s:p2", "../../p2");
testResolve("s:p2", "../../././p2");
// B. if input buffer starts with "/./" or is "/.". replace with "/".
// (The URI implementation removes the "." path segments when parsing,
// so this case isn't handled by merge).
base = origBase;
testResolve("s://a/pa/p2", "./p2");
// C. if input buffer starts with "/../" or is "/..", replace with "/"
// and remove preceeding segment.
testResolve("s://a/p2", "../p2");
var longPathBase = Uri.parse(b.replaceFirst("/pb", "/pb/pc/pd"));
base = longPathBase;
testResolve("s://a/pa/pb/p2", "../p2");
testResolve("s://a/pa/p2", "../../p2");
testResolve("s://a/p2", "../../../p2");
testResolve("s://a/p2", "../../../../p2");
var noAuthRelLongPathBase =
Uri.parse(b.replaceFirst("//a/pa/pb", "pa/pb/pc/pd"));
base = noAuthRelLongPathBase;
testResolve("s:pa/pb/p2", "../p2");
testResolve("s:pa/p2", "../../p2");
testResolve("s:/p2", "../../../p2");
testResolve("s:/p2", "../../../../p2");
// D. if the input buffer contains only ".." or ".", remove it.
base = noAuthEmptyPathBase;
testResolve("s:", "..");
testResolve("s:", ".");
base = noAuthRelSinglePathBase;
testResolve("s:", "..");
testResolve("s:", ".");
}
}
void testResolvePath(String expected, String path) {
@ -334,6 +554,13 @@ void testNormalization() {
Expect.equals("/zZ${char}zZ", uri.path);
Expect.equals("vV${char}vV", uri.query);
Expect.equals("wW${char}wW", uri.fragment);
uri = Uri.parse("s://yY${escape}yY/zZ${escape}zZ"
"?vV${escape}vV#wW${escape}wW");
Expect.equals("yY${char}yY".toLowerCase(), uri.host);
Expect.equals("/zZ${char}zZ", uri.path);
Expect.equals("vV${char}vV", uri.query);
Expect.equals("wW${char}wW", uri.fragment);
}
// Escapes of reserved characters are kept, but upper-cased.
@ -400,7 +627,11 @@ void testReplace() {
var uris = [
Uri.parse(""),
Uri.parse("a://@:/?#"),
Uri.parse("a://:/?#"), // Parsed as simple URI.
Uri.parse("a://b@c:4/e/f?g#h"),
Uri.parse("a://c:4/e/f?g#h"), // Parsed as simple URI.
Uri.parse("$SCHEMECHAR://$REGNAMECHAR:$DIGIT/$PCHAR/$PCHAR"
"?$QUERYCHAR#$QUERYCHAR"), // Parsed as simple URI.
Uri.parse("$SCHEMECHAR://$USERINFOCHAR@$REGNAMECHAR:$DIGIT/$PCHAR/$PCHAR"
"?$QUERYCHAR#$QUERYCHAR"),
];
@ -470,6 +701,13 @@ void testReplace() {
Expect.equals(2, params.length);
Expect.listEquals(["42", "37"], params["x"]);
Expect.listEquals(["43", "38"], params["y"]);
// Test replacing with empty strings.
uri = Uri.parse("s://a:1/b/c?d#e");
Expect.equals("s://a:1/b/c?d#", uri.replace(fragment: "").toString());
Expect.equals("s://a:1/b/c?#e", uri.replace(query: "").toString());
Expect.equals("s://a:1?d#e", uri.replace(path: "").toString());
Expect.equals("s://:1/b/c?d#e", uri.replace(host: "").toString());
}
main() {
@ -498,6 +736,11 @@ main() {
query: null,
fragment: null).toString());
Expect.stringEquals("file:///", Uri.parse("file:").toString());
Expect.stringEquals("file:///", Uri.parse("file:/").toString());
Expect.stringEquals("file:///", Uri.parse("file:").toString());
Expect.stringEquals("file:///foo", Uri.parse("file:foo").toString());
Expect.stringEquals("file:///foo", Uri.parse("file:/foo").toString());
Expect.stringEquals("file://foo/", Uri.parse("file://foo").toString());
testResolvePath("/a/g", "/a/b/c/./../../g");
testResolvePath("/a/g", "/a/b/c/./../../g");