af57d2a0eb
- Remove all dead code associated with flag '--error-on-bad-override' which is currently running only under !FLAG_strong which is not valid anymore - Not removing the flag from flag_list for now as it is used in a number of tests and removing it from all these places would be a big change, it would be a NOP flag - removed some dead code in 'loader.dart' which was invoked from the tag handler Change-Id: I5474b51b4dee7471e80b4081e55b996f1d75612f Reviewed-on: https://dart-review.googlesource.com/c/78382 Reviewed-by: Alexander Markov <alexmarkov@google.com>
1133 lines
34 KiB
Dart
1133 lines
34 KiB
Dart
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
part of vmservice_io;
|
|
|
|
_sanitizeWindowsPath(path) {
|
|
// For Windows we need to massage the paths a bit according to
|
|
// http://blogs.msdn.com/b/ie/archive/2006/12/06/file-uris-in-windows.aspx
|
|
//
|
|
// Convert
|
|
// C:\one\two\three
|
|
// to
|
|
// /C:/one/two/three
|
|
|
|
if (_isWindows == false) {
|
|
// Do nothing when not running Windows.
|
|
return path;
|
|
}
|
|
|
|
var fixedPath = "${path.replaceAll('\\', '/')}";
|
|
|
|
if ((path.length > 2) && (path[1] == ':')) {
|
|
// Path begins with a drive letter.
|
|
return '/$fixedPath';
|
|
}
|
|
|
|
return fixedPath;
|
|
}
|
|
|
|
_trimWindowsPath(path) {
|
|
// Convert /X:/ to X:/.
|
|
if (_isWindows == false) {
|
|
// Do nothing when not running Windows.
|
|
return path;
|
|
}
|
|
if (!path.startsWith('/') || (path.length < 3)) {
|
|
return path;
|
|
}
|
|
// Match '/?:'.
|
|
if ((path[0] == '/') && (path[2] == ':')) {
|
|
// Remove leading '/'.
|
|
return path.substring(1);
|
|
}
|
|
return path;
|
|
}
|
|
|
|
// Ensure we have a trailing slash character.
|
|
_enforceTrailingSlash(uri) {
|
|
if (!uri.endsWith('/')) {
|
|
return '$uri/';
|
|
}
|
|
return uri;
|
|
}
|
|
|
|
class FileRequest {
|
|
final SendPort sp;
|
|
final int tag;
|
|
final Uri uri;
|
|
final Uri resolvedUri;
|
|
final String libraryUrl;
|
|
FileRequest(this.sp, this.tag, this.uri, this.resolvedUri, this.libraryUrl);
|
|
}
|
|
|
|
@pragma("vm:entry-point")
|
|
bool _traceLoading = false;
|
|
@pragma("vm:entry-point")
|
|
bool _deterministic = false;
|
|
|
|
// State associated with the isolate that is used for loading.
|
|
class IsolateLoaderState extends IsolateEmbedderData {
|
|
IsolateLoaderState(this.isolateId);
|
|
|
|
final int isolateId;
|
|
bool _dead = false;
|
|
SendPort sp;
|
|
|
|
void init(String packageRootFlag, String packagesConfigFlag,
|
|
String workingDirectory, String rootScript) {
|
|
if (_dead) {
|
|
return;
|
|
}
|
|
// _workingDirectory must be set first.
|
|
_workingDirectory = new Uri.directory(workingDirectory);
|
|
if (rootScript != null) {
|
|
_rootScript = Uri.parse(rootScript);
|
|
}
|
|
// If the --package-root flag was passed.
|
|
if (packageRootFlag != null) {
|
|
_setPackageRoot(packageRootFlag);
|
|
}
|
|
// If the --packages flag was passed.
|
|
if (packagesConfigFlag != null) {
|
|
_setPackagesConfig(packagesConfigFlag);
|
|
}
|
|
}
|
|
|
|
void updatePackageMap(String packagesConfigFlag) {
|
|
if (packagesConfigFlag == null) {
|
|
return;
|
|
}
|
|
_packageMap = null;
|
|
_setPackagesConfig(packagesConfigFlag);
|
|
}
|
|
|
|
void cleanup() {
|
|
_dead = true;
|
|
if (_packagesPort != null) {
|
|
_packagesPort.close();
|
|
_packagesPort = null;
|
|
}
|
|
}
|
|
|
|
// The working directory when the embedder started.
|
|
Uri _workingDirectory;
|
|
|
|
// The root script's uri.
|
|
Uri _rootScript;
|
|
|
|
// Packages are either resolved looking up in a map or resolved from within a
|
|
// package root.
|
|
bool get _packagesReady =>
|
|
(_packageRoot != null) ||
|
|
(_packageMap != null) ||
|
|
(_packageError != null);
|
|
|
|
// Error string set if there was an error resolving package configuration.
|
|
// For example not finding a .packages file or packages/ directory, malformed
|
|
// .packages file or any other related error.
|
|
String _packageError = null;
|
|
|
|
// The directory to look in to resolve "package:" scheme URIs. By default it
|
|
// is the 'packages' directory right next to the script.
|
|
Uri _packageRoot = null;
|
|
|
|
// The map describing how certain package names are mapped to Uris.
|
|
Uri _packageConfig = null;
|
|
Map<String, Uri> _packageMap = null;
|
|
|
|
// We issue only 16 concurrent calls to File.readAsBytes() to stay within
|
|
// platform-specific resource limits (e.g. max open files). The rest go on
|
|
// _fileRequestQueue and are processed when we can safely issue them.
|
|
static final int _maxFileRequests = _deterministic ? 1 : 16;
|
|
int currentFileRequests = 0;
|
|
final List<FileRequest> _fileRequestQueue = new List<FileRequest>();
|
|
|
|
bool get shouldIssueFileRequest => currentFileRequests < _maxFileRequests;
|
|
void enqueueFileRequest(FileRequest fr) {
|
|
_fileRequestQueue.add(fr);
|
|
}
|
|
|
|
FileRequest dequeueFileRequest() {
|
|
if (_fileRequestQueue.length == 0) {
|
|
return null;
|
|
}
|
|
return _fileRequestQueue.removeAt(0);
|
|
}
|
|
|
|
_setPackageRoot(String packageRoot) {
|
|
packageRoot = _sanitizeWindowsPath(packageRoot);
|
|
if (packageRoot.startsWith('file:') ||
|
|
packageRoot.startsWith('http:') ||
|
|
packageRoot.startsWith('https:')) {
|
|
packageRoot = _enforceTrailingSlash(packageRoot);
|
|
_packageRoot = _workingDirectory.resolve(packageRoot);
|
|
} else {
|
|
packageRoot = _sanitizeWindowsPath(packageRoot);
|
|
packageRoot = _trimWindowsPath(packageRoot);
|
|
_packageRoot =
|
|
_workingDirectory.resolveUri(new Uri.directory(packageRoot));
|
|
}
|
|
}
|
|
|
|
_setPackagesConfig(String packagesParam) {
|
|
var packagesName = _sanitizeWindowsPath(packagesParam);
|
|
var packagesUri = Uri.parse(packagesName);
|
|
if (packagesUri.scheme == '') {
|
|
// Script does not have a scheme, assume that it is a path,
|
|
// resolve it against the working directory.
|
|
packagesUri = _workingDirectory.resolveUri(packagesUri);
|
|
}
|
|
_requestPackagesMap(packagesUri);
|
|
_pendingPackageLoads.add(() {
|
|
// Dummy action.
|
|
});
|
|
}
|
|
|
|
// Handling of access to the package root or package map from user code.
|
|
_triggerPackageResolution(action) {
|
|
if (_packagesReady) {
|
|
// Packages are ready. Execute the action now.
|
|
action();
|
|
} else {
|
|
if (_pendingPackageLoads.isEmpty) {
|
|
// Package resolution has not been setup yet, and this is the first
|
|
// request for package resolution & loading.
|
|
_requestPackagesMap();
|
|
}
|
|
// Register the action for when the package resolution is ready.
|
|
_pendingPackageLoads.add(action);
|
|
}
|
|
}
|
|
|
|
// A list of callbacks which should be invoked after the package map has been
|
|
// loaded.
|
|
List<Function> _pendingPackageLoads = [];
|
|
|
|
// Given a uri with a 'package' scheme, return a Uri that is prefixed with
|
|
// the package root or resolved relative to the package configuration.
|
|
Uri _resolvePackageUri(Uri uri) {
|
|
assert(uri.scheme == "package");
|
|
assert(_packagesReady);
|
|
|
|
if (uri.host.isNotEmpty) {
|
|
var path = '${uri.host}${uri.path}';
|
|
var right = 'package:$path';
|
|
var wrong = 'package://$path';
|
|
|
|
throw "URIs using the 'package:' scheme should look like "
|
|
"'$right', not '$wrong'.";
|
|
}
|
|
|
|
var packageNameEnd = uri.path.indexOf('/');
|
|
if (packageNameEnd == 0) {
|
|
// Package URIs must have a non-empty package name (not start with "/").
|
|
throw "URIS using the 'package:' scheme should look like "
|
|
"'package:packageName${uri.path}', not 'package:${uri.path}'";
|
|
}
|
|
if (_traceLoading) {
|
|
_log('Resolving package with uri path: ${uri.path}');
|
|
}
|
|
var resolvedUri;
|
|
if (_packageError != null) {
|
|
if (_traceLoading) {
|
|
_log("Resolving package with pending resolution error: $_packageError");
|
|
}
|
|
throw _packageError;
|
|
} else if (_packageRoot != null) {
|
|
resolvedUri = _packageRoot.resolve(uri.path);
|
|
} else {
|
|
if (packageNameEnd < 0) {
|
|
// Package URIs must have a path after the package name, even if it's
|
|
// just "/".
|
|
throw "URIS using the 'package:' scheme should look like "
|
|
"'package:${uri.path}/', not 'package:${uri.path}'";
|
|
}
|
|
var packageName = uri.path.substring(0, packageNameEnd);
|
|
var mapping = _packageMap[packageName];
|
|
if (_traceLoading) {
|
|
_log("Mapped '$packageName' package to '$mapping'");
|
|
}
|
|
if (mapping == null) {
|
|
throw "No mapping for '$packageName' package when resolving '$uri'.";
|
|
}
|
|
var path;
|
|
assert(uri.path.length > packageName.length);
|
|
path = uri.path.substring(packageName.length + 1);
|
|
if (_traceLoading) {
|
|
_log("Path to be resolved in package: $path");
|
|
}
|
|
resolvedUri = mapping.resolve(path);
|
|
}
|
|
if (_traceLoading) {
|
|
_log("Resolved '$uri' to '$resolvedUri'.");
|
|
}
|
|
return resolvedUri;
|
|
}
|
|
|
|
RawReceivePort _packagesPort;
|
|
|
|
void _requestPackagesMap([Uri packageConfig]) {
|
|
if (_packagesPort != null) {
|
|
// Already scheduled.
|
|
return;
|
|
}
|
|
// Create a port to receive the packages map on.
|
|
_packagesPort = new RawReceivePort(_handlePackagesReply);
|
|
var sp = _packagesPort.sendPort;
|
|
|
|
if (packageConfig != null) {
|
|
// Explicitly specified .packages path.
|
|
_handlePackagesRequest(sp, _traceLoading, -2, packageConfig);
|
|
} else {
|
|
// Search for .packages starting at the root script.
|
|
_handlePackagesRequest(sp, _traceLoading, -1, _rootScript);
|
|
}
|
|
|
|
if (_traceLoading) {
|
|
_log("Requested packages map for '$_rootScript'.");
|
|
}
|
|
}
|
|
|
|
void _handlePackagesReply(msg) {
|
|
if (_packagesPort == null) {
|
|
return;
|
|
}
|
|
// Make sure to close the _packagePort before any other action.
|
|
_packagesPort.close();
|
|
_packagesPort = null;
|
|
|
|
if (_traceLoading) {
|
|
_log("Got packages reply: $msg");
|
|
}
|
|
if (msg is String) {
|
|
if (_traceLoading) {
|
|
_log("Got failure response on package port: '$msg'");
|
|
}
|
|
// Remember the error message.
|
|
_packageError = msg;
|
|
} else if (msg is List) {
|
|
if (msg.length == 1) {
|
|
if (_traceLoading) {
|
|
_log("Received package root: '${msg[0]}'");
|
|
}
|
|
_packageRoot = Uri.parse(msg[0]);
|
|
} else {
|
|
// First entry contains the location of the loaded .packages file.
|
|
assert((msg.length % 2) == 0);
|
|
assert(msg.length >= 2);
|
|
assert(msg[1] == null);
|
|
_packageConfig = Uri.parse(msg[0]);
|
|
_packageMap = new Map<String, Uri>();
|
|
for (var i = 2; i < msg.length; i += 2) {
|
|
// TODO(iposva): Complain about duplicate entries.
|
|
_packageMap[msg[i]] = Uri.parse(msg[i + 1]);
|
|
}
|
|
if (_traceLoading) {
|
|
_log("Setup package map: $_packageMap");
|
|
}
|
|
}
|
|
} else {
|
|
_packageError = "Bad type of packages reply: ${msg.runtimeType}";
|
|
if (_traceLoading) {
|
|
_log(_packageError);
|
|
}
|
|
}
|
|
|
|
// Resolve all pending package loads now that we know how to resolve them.
|
|
while (_pendingPackageLoads.length > 0) {
|
|
// Order does not matter as we queue all of the requests up right now.
|
|
var req = _pendingPackageLoads.removeLast();
|
|
// Call the registered closure, to handle the delayed action.
|
|
req();
|
|
}
|
|
// Reset the pending package loads to empty. So that we eventually can
|
|
// finish loading.
|
|
_pendingPackageLoads = [];
|
|
}
|
|
}
|
|
|
|
_log(msg) {
|
|
print("% $msg");
|
|
}
|
|
|
|
var _httpClient;
|
|
|
|
// Send a response to the requesting isolate.
|
|
void _sendResourceResponse(SendPort sp, int tag, Uri uri, Uri resolvedUri,
|
|
String libraryUrl, dynamic data) {
|
|
assert((data is List<int>) || (data is String));
|
|
var msg = new List(5);
|
|
if (data is String) {
|
|
// We encountered an error, flip the sign of the tag to indicate that.
|
|
tag = -tag;
|
|
if (libraryUrl == null) {
|
|
data = 'Could not load "$uri": $data';
|
|
} else {
|
|
data = 'Could not import "$uri" from "$libraryUrl": $data';
|
|
}
|
|
}
|
|
msg[0] = tag;
|
|
msg[1] = uri.toString();
|
|
msg[2] = resolvedUri.toString();
|
|
msg[3] = libraryUrl;
|
|
msg[4] = data;
|
|
sp.send(msg);
|
|
}
|
|
|
|
// Send a response to the requesting isolate.
|
|
void _sendExtensionImportResponse(
|
|
SendPort sp, Uri uri, String libraryUrl, String resolvedUri) {
|
|
var msg = new List(5);
|
|
int tag = _Dart_kImportExtension;
|
|
if (resolvedUri == null) {
|
|
// We could not resolve the dart-ext: uri.
|
|
tag = -tag;
|
|
resolvedUri = 'Could not resolve "$uri" from "$libraryUrl"';
|
|
}
|
|
msg[0] = tag;
|
|
msg[1] = uri.toString();
|
|
msg[2] = resolvedUri;
|
|
msg[3] = libraryUrl;
|
|
msg[4] = resolvedUri;
|
|
sp.send(msg);
|
|
}
|
|
|
|
void _loadHttp(
|
|
SendPort sp, int tag, Uri uri, Uri resolvedUri, String libraryUrl) {
|
|
if (_httpClient == null) {
|
|
_httpClient = new HttpClient()..maxConnectionsPerHost = 6;
|
|
}
|
|
_httpClient
|
|
.getUrl(resolvedUri)
|
|
.then((HttpClientRequest request) => request.close())
|
|
.then((HttpClientResponse response) {
|
|
var builder = new BytesBuilder(copy: false);
|
|
response.listen(builder.add, onDone: () {
|
|
if (response.statusCode != 200) {
|
|
var msg = "Failure getting $resolvedUri:\n"
|
|
" ${response.statusCode} ${response.reasonPhrase}";
|
|
_sendResourceResponse(sp, tag, uri, resolvedUri, libraryUrl, msg);
|
|
} else {
|
|
_sendResourceResponse(
|
|
sp, tag, uri, resolvedUri, libraryUrl, builder.takeBytes());
|
|
}
|
|
}, onError: (e) {
|
|
_sendResourceResponse(
|
|
sp, tag, uri, resolvedUri, libraryUrl, e.toString());
|
|
});
|
|
}).catchError((e) {
|
|
_sendResourceResponse(sp, tag, uri, resolvedUri, libraryUrl, e.toString());
|
|
});
|
|
// It's just here to push an event on the event loop so that we invoke the
|
|
// scheduled microtasks.
|
|
Timer.run(() {});
|
|
}
|
|
|
|
void _loadFile(IsolateLoaderState loaderState, SendPort sp, int tag, Uri uri,
|
|
Uri resolvedUri, String libraryUrl) {
|
|
var path = resolvedUri.toFilePath();
|
|
var sourceFile = new File(path);
|
|
sourceFile.readAsBytes().then((data) {
|
|
_sendResourceResponse(sp, tag, uri, resolvedUri, libraryUrl, data);
|
|
}, onError: (e) {
|
|
_sendResourceResponse(sp, tag, uri, resolvedUri, libraryUrl, e.toString());
|
|
}).whenComplete(() {
|
|
loaderState.currentFileRequests--;
|
|
while (loaderState.shouldIssueFileRequest) {
|
|
FileRequest fr = loaderState.dequeueFileRequest();
|
|
if (fr == null) {
|
|
break;
|
|
}
|
|
_loadFile(
|
|
loaderState, fr.sp, fr.tag, fr.uri, fr.resolvedUri, fr.libraryUrl);
|
|
loaderState.currentFileRequests++;
|
|
}
|
|
});
|
|
}
|
|
|
|
void _loadDataUri(
|
|
SendPort sp, int tag, Uri uri, Uri resolvedUri, String libraryUrl) {
|
|
try {
|
|
var mime = uri.data.mimeType;
|
|
if ((mime != "application/dart") && (mime != "text/plain")) {
|
|
throw "MIME-type must be application/dart or text/plain: $mime given.";
|
|
}
|
|
var charset = uri.data.charset;
|
|
if ((charset != "utf-8") && (charset != "US-ASCII")) {
|
|
// The C++ portion of the embedder assumes UTF-8.
|
|
throw "Only utf-8 or US-ASCII encodings are supported: $charset given.";
|
|
}
|
|
_sendResourceResponse(
|
|
sp, tag, uri, resolvedUri, libraryUrl, uri.data.contentAsBytes());
|
|
} catch (e) {
|
|
_sendResourceResponse(sp, tag, uri, resolvedUri, libraryUrl,
|
|
"Invalid data uri ($uri):\n $e");
|
|
}
|
|
}
|
|
|
|
// Loading a package URI needs to first map the package name to a loadable
|
|
// URI.
|
|
_loadPackage(IsolateLoaderState loaderState, SendPort sp, bool traceLoading,
|
|
int tag, Uri uri, Uri resolvedUri, String libraryUrl) {
|
|
if (loaderState._packagesReady) {
|
|
var resolvedUri;
|
|
try {
|
|
resolvedUri = loaderState._resolvePackageUri(uri);
|
|
} catch (e, s) {
|
|
if (traceLoading) {
|
|
_log("Exception ($e) when resolving package URI: $uri");
|
|
}
|
|
// Report error.
|
|
_sendResourceResponse(
|
|
sp, tag, uri, resolvedUri, libraryUrl, e.toString());
|
|
return;
|
|
}
|
|
// Recursively call with the new resolved uri.
|
|
_handleResourceRequest(
|
|
loaderState, sp, traceLoading, tag, uri, resolvedUri, libraryUrl);
|
|
} else {
|
|
if (loaderState._pendingPackageLoads.isEmpty) {
|
|
// Package resolution has not been setup yet, and this is the first
|
|
// request for package resolution & loading.
|
|
loaderState._requestPackagesMap();
|
|
}
|
|
// Register the action of loading this package once the package resolution
|
|
// is ready.
|
|
loaderState._pendingPackageLoads.add(() {
|
|
_handleResourceRequest(
|
|
loaderState, sp, traceLoading, tag, uri, uri, libraryUrl);
|
|
});
|
|
if (traceLoading) {
|
|
_log("Pending package load of '$uri': "
|
|
"${loaderState._pendingPackageLoads.length} pending");
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO(johnmccutchan): This and most other top level functions in this file
|
|
// should be turned into methods on the IsolateLoaderState class.
|
|
_handleResourceRequest(IsolateLoaderState loaderState, SendPort sp,
|
|
bool traceLoading, int tag, Uri uri, Uri resolvedUri, String libraryUrl) {
|
|
if (resolvedUri.scheme == '' || resolvedUri.scheme == 'file') {
|
|
if (loaderState.shouldIssueFileRequest) {
|
|
_loadFile(loaderState, sp, tag, uri, resolvedUri, libraryUrl);
|
|
loaderState.currentFileRequests++;
|
|
} else {
|
|
FileRequest fr = new FileRequest(sp, tag, uri, resolvedUri, libraryUrl);
|
|
loaderState.enqueueFileRequest(fr);
|
|
}
|
|
} else if ((resolvedUri.scheme == 'http') ||
|
|
(resolvedUri.scheme == 'https')) {
|
|
_loadHttp(sp, tag, uri, resolvedUri, libraryUrl);
|
|
} else if ((resolvedUri.scheme == 'data')) {
|
|
_loadDataUri(sp, tag, uri, resolvedUri, libraryUrl);
|
|
} else if ((resolvedUri.scheme == 'package')) {
|
|
_loadPackage(
|
|
loaderState, sp, traceLoading, tag, uri, resolvedUri, libraryUrl);
|
|
} else {
|
|
_sendResourceResponse(
|
|
sp,
|
|
tag,
|
|
uri,
|
|
resolvedUri,
|
|
libraryUrl,
|
|
'Unknown scheme (${resolvedUri.scheme}) for '
|
|
'$resolvedUri');
|
|
}
|
|
}
|
|
|
|
// Handling of packages requests. Finding and parsing of .packages file or
|
|
// packages/ directories.
|
|
const _LF = 0x0A;
|
|
const _CR = 0x0D;
|
|
const _SPACE = 0x20;
|
|
const _HASH = 0x23;
|
|
const _DOT = 0x2E;
|
|
const _COLON = 0x3A;
|
|
const _DEL = 0x7F;
|
|
|
|
const _invalidPackageNameChars = const [
|
|
true, // space
|
|
false, // !
|
|
true, // "
|
|
true, // #
|
|
false, // $
|
|
true, // %
|
|
false, // &
|
|
false, // '
|
|
false, // (
|
|
false, // )
|
|
false, // *
|
|
false, // +
|
|
false, // ,
|
|
false, // -
|
|
false, // .
|
|
true, // /
|
|
false, // 0
|
|
false, // 1
|
|
false, // 2
|
|
false, // 3
|
|
false, // 4
|
|
false, // 5
|
|
false, // 6
|
|
false, // 7
|
|
false, // 8
|
|
false, // 9
|
|
true, // :
|
|
false, // ;
|
|
true, // <
|
|
false, // =
|
|
true, // >
|
|
true, // ?
|
|
false, // @
|
|
false, // A
|
|
false, // B
|
|
false, // C
|
|
false, // D
|
|
false, // E
|
|
false, // F
|
|
false, // G
|
|
false, // H
|
|
false, // I
|
|
false, // J
|
|
false, // K
|
|
false, // L
|
|
false, // M
|
|
false, // N
|
|
false, // O
|
|
false, // P
|
|
false, // Q
|
|
false, // R
|
|
false, // S
|
|
false, // T
|
|
false, // U
|
|
false, // V
|
|
false, // W
|
|
false, // X
|
|
false, // Y
|
|
false, // Z
|
|
true, // [
|
|
true, // \
|
|
true, // ]
|
|
true, // ^
|
|
false, // _
|
|
true, // `
|
|
false, // a
|
|
false, // b
|
|
false, // c
|
|
false, // d
|
|
false, // e
|
|
false, // f
|
|
false, // g
|
|
false, // h
|
|
false, // i
|
|
false, // j
|
|
false, // k
|
|
false, // l
|
|
false, // m
|
|
false, // n
|
|
false, // o
|
|
false, // p
|
|
false, // q
|
|
false, // r
|
|
false, // s
|
|
false, // t
|
|
false, // u
|
|
false, // v
|
|
false, // w
|
|
false, // x
|
|
false, // y
|
|
false, // z
|
|
true, // {
|
|
true, // |
|
|
true, // }
|
|
false, // ~
|
|
true, // DEL
|
|
];
|
|
|
|
_parsePackagesFile(
|
|
SendPort sp, bool traceLoading, Uri packagesFile, List<int> data) {
|
|
// The first entry contains the location of the identified .packages file
|
|
// instead of a mapping.
|
|
var result = [packagesFile.toString(), null];
|
|
var index = 0;
|
|
var len = data.length;
|
|
while (index < len) {
|
|
var start = index;
|
|
var char = data[index];
|
|
if ((char == _CR) || (char == _LF)) {
|
|
// Skipping empty lines.
|
|
index++;
|
|
continue;
|
|
}
|
|
|
|
// Identify split within the line and end of the line.
|
|
var separator = -1;
|
|
var end = len;
|
|
// Verifying validity of package name while scanning the line.
|
|
var nonDot = false;
|
|
var invalidPackageName = false;
|
|
|
|
// Scan to the end of the line or data.
|
|
while (index < len) {
|
|
char = data[index++];
|
|
// If we have not reached the separator yet, determine whether we are
|
|
// scanning legal package name characters.
|
|
if (separator == -1) {
|
|
if ((char == _COLON)) {
|
|
// The first colon on a line is the separator between package name and
|
|
// related URI.
|
|
separator = index - 1;
|
|
} else {
|
|
// Still scanning the package name part. Check for the validity of
|
|
// the characters.
|
|
nonDot = nonDot || (char != _DOT);
|
|
invalidPackageName = invalidPackageName ||
|
|
(char < _SPACE) ||
|
|
(char > _DEL) ||
|
|
_invalidPackageNameChars[char - _SPACE];
|
|
}
|
|
}
|
|
// Identify end of line.
|
|
if ((char == _CR) || (char == _LF)) {
|
|
end = index - 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// No further handling needed for comment lines.
|
|
if (data[start] == _HASH) {
|
|
if (traceLoading) {
|
|
_log("Skipping comment in $packagesFile:\n"
|
|
"${new String.fromCharCodes(data, start, end)}");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Check for a badly formatted line, starting with a ':'.
|
|
if (separator == start) {
|
|
var line = new String.fromCharCodes(data, start, end);
|
|
if (traceLoading) {
|
|
_log("Line starts with ':' in $packagesFile:\n"
|
|
"$line");
|
|
}
|
|
sp.send("Missing package name in $packagesFile:\n"
|
|
"$line");
|
|
return;
|
|
}
|
|
|
|
// Ensure there is a separator on the line.
|
|
if (separator == -1) {
|
|
var line = new String.fromCharCodes(data, start, end);
|
|
if (traceLoading) {
|
|
_log("Line has no ':' in $packagesFile:\n"
|
|
"$line");
|
|
}
|
|
sp.send("Missing ':' separator in $packagesFile:\n"
|
|
"$line");
|
|
return;
|
|
}
|
|
|
|
var packageName = new String.fromCharCodes(data, start, separator);
|
|
|
|
// Check for valid package name.
|
|
if (invalidPackageName || !nonDot) {
|
|
var line = new String.fromCharCodes(data, start, end);
|
|
if (traceLoading) {
|
|
_log("Invalid package name $packageName in $packagesFile");
|
|
}
|
|
sp.send("Invalid package name '$packageName' in $packagesFile:\n"
|
|
"$line");
|
|
return;
|
|
}
|
|
|
|
if (traceLoading) {
|
|
_log("packageName: $packageName");
|
|
}
|
|
var packageUri = new String.fromCharCodes(data, separator + 1, end);
|
|
if (traceLoading) {
|
|
_log("original packageUri: $packageUri");
|
|
}
|
|
// Ensure the package uri ends with a /.
|
|
if (!packageUri.endsWith("/")) {
|
|
packageUri = "$packageUri/";
|
|
}
|
|
packageUri = packagesFile.resolve(packageUri).toString();
|
|
if (traceLoading) {
|
|
_log("mapping: $packageName -> $packageUri");
|
|
}
|
|
result.add(packageName);
|
|
result.add(packageUri);
|
|
}
|
|
|
|
if (traceLoading) {
|
|
_log("Parsed packages file at $packagesFile. Sending:\n$result");
|
|
}
|
|
sp.send(result);
|
|
}
|
|
|
|
_loadPackagesFile(SendPort sp, bool traceLoading, Uri packagesFile) async {
|
|
try {
|
|
var data = await new File.fromUri(packagesFile).readAsBytes();
|
|
if (traceLoading) {
|
|
_log("Loaded packages file from $packagesFile:\n"
|
|
"${new String.fromCharCodes(data)}");
|
|
}
|
|
_parsePackagesFile(sp, traceLoading, packagesFile, data);
|
|
} catch (e, s) {
|
|
if (traceLoading) {
|
|
_log("Error loading packages: $e\n$s");
|
|
}
|
|
sp.send("Uncaught error ($e) loading packages file.");
|
|
}
|
|
}
|
|
|
|
_findPackagesFile(SendPort sp, bool traceLoading, Uri base) async {
|
|
try {
|
|
// Walk up the directory hierarchy to check for the existence of
|
|
// .packages files in parent directories and for the existence of a
|
|
// packages/ directory on the first iteration.
|
|
var dir = new File.fromUri(base).parent;
|
|
var prev = null;
|
|
// Keep searching until we reach the root.
|
|
while ((prev == null) || (prev.path != dir.path)) {
|
|
// Check for the existence of a .packages file and if it exists try to
|
|
// load and parse it.
|
|
var dirUri = dir.uri;
|
|
var packagesFile = dirUri.resolve(".packages");
|
|
if (traceLoading) {
|
|
_log("Checking for $packagesFile file.");
|
|
}
|
|
var exists = await new File.fromUri(packagesFile).exists();
|
|
if (traceLoading) {
|
|
_log("$packagesFile exists: $exists");
|
|
}
|
|
if (exists) {
|
|
_loadPackagesFile(sp, traceLoading, packagesFile);
|
|
return;
|
|
}
|
|
// Move up one level.
|
|
prev = dir;
|
|
dir = dir.parent;
|
|
}
|
|
|
|
// No .packages file was found.
|
|
if (traceLoading) {
|
|
_log("Could not resolve a package location from $base");
|
|
}
|
|
sp.send("Could not resolve a package location for base at $base");
|
|
} catch (e, s) {
|
|
if (traceLoading) {
|
|
_log("Error loading packages: $e\n$s");
|
|
}
|
|
sp.send("Uncaught error ($e) loading packages file.");
|
|
}
|
|
}
|
|
|
|
Future<bool> _loadHttpPackagesFile(
|
|
SendPort sp, bool traceLoading, Uri resource) async {
|
|
try {
|
|
if (_httpClient == null) {
|
|
_httpClient = new HttpClient()..maxConnectionsPerHost = 6;
|
|
}
|
|
if (traceLoading) {
|
|
_log("Fetching packages file from '$resource'.");
|
|
}
|
|
var req = await _httpClient.getUrl(resource);
|
|
var rsp = await req.close();
|
|
var builder = new BytesBuilder(copy: false);
|
|
await for (var bytes in rsp) {
|
|
builder.add(bytes);
|
|
}
|
|
if (rsp.statusCode != 200) {
|
|
if (traceLoading) {
|
|
_log("Got status ${rsp.statusCode} fetching '$resource'.");
|
|
}
|
|
return false;
|
|
}
|
|
var data = builder.takeBytes();
|
|
if (traceLoading) {
|
|
_log("Loaded packages file from '$resource':\n"
|
|
"${new String.fromCharCodes(data)}");
|
|
}
|
|
_parsePackagesFile(sp, traceLoading, resource, data);
|
|
} catch (e, s) {
|
|
if (traceLoading) {
|
|
_log("Error loading packages file from '$resource': $e\n$s");
|
|
}
|
|
sp.send("Uncaught error ($e) loading packages file from '$resource'.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
_loadPackagesData(sp, traceLoading, resource) {
|
|
try {
|
|
var data = resource.data;
|
|
var mime = data.mimeType;
|
|
if (mime != "text/plain") {
|
|
throw "MIME-type must be text/plain: $mime given.";
|
|
}
|
|
var charset = data.charset;
|
|
if ((charset != "utf-8") && (charset != "US-ASCII")) {
|
|
// The C++ portion of the embedder assumes UTF-8.
|
|
throw "Only utf-8 or US-ASCII encodings are supported: $charset given.";
|
|
}
|
|
_parsePackagesFile(sp, traceLoading, resource, data.contentAsBytes());
|
|
} catch (e) {
|
|
sp.send("Uncaught error ($e) loading packages data.");
|
|
}
|
|
}
|
|
|
|
// This code used to exist in a second isolate and so it uses a SendPort to
|
|
// report it's return value. This could be refactored so that it returns it's
|
|
// value and the caller could wait on the future rather than a message on
|
|
// SendPort.
|
|
_handlePackagesRequest(
|
|
SendPort sp, bool traceLoading, int tag, Uri resource) async {
|
|
try {
|
|
if (tag == -1) {
|
|
if (resource.scheme == '' || resource.scheme == 'file') {
|
|
_findPackagesFile(sp, traceLoading, resource);
|
|
} else if ((resource.scheme == 'http') || (resource.scheme == 'https')) {
|
|
// Try to load the .packages file next to the resource.
|
|
var packagesUri = resource.resolve(".packages");
|
|
var exists = await _loadHttpPackagesFile(sp, traceLoading, packagesUri);
|
|
if (!exists) {
|
|
// Loading of the .packages file failed for http/https based scripts
|
|
sp.send([null]);
|
|
}
|
|
} else {
|
|
sp.send("Unsupported scheme used to locate .packages file: "
|
|
"'$resource'.");
|
|
}
|
|
} else if (tag == -2) {
|
|
if (traceLoading) {
|
|
_log("Handling load of packages map: '$resource'.");
|
|
}
|
|
if (resource.scheme == '' || resource.scheme == 'file') {
|
|
var exists = await new File.fromUri(resource).exists();
|
|
if (exists) {
|
|
_loadPackagesFile(sp, traceLoading, resource);
|
|
} else {
|
|
sp.send("Packages file '$resource' not found.");
|
|
}
|
|
} else if ((resource.scheme == 'http') || (resource.scheme == 'https')) {
|
|
var exists = await _loadHttpPackagesFile(sp, traceLoading, resource);
|
|
if (!exists) {
|
|
sp.send("Packages file '$resource' not found.");
|
|
}
|
|
} else if (resource.scheme == 'data') {
|
|
_loadPackagesData(sp, traceLoading, resource);
|
|
} else {
|
|
sp.send("Unknown scheme (${resource.scheme}) for package file at "
|
|
"'$resource'.");
|
|
}
|
|
} else {
|
|
sp.send("Unknown packages request tag: $tag for '$resource'.");
|
|
}
|
|
} catch (e, s) {
|
|
if (traceLoading) {
|
|
_log("Error handling packages request: $e\n$s");
|
|
}
|
|
sp.send("Uncaught error ($e) handling packages request.");
|
|
}
|
|
}
|
|
|
|
// Shutdown all active loaders by sending an error message.
|
|
void shutdownLoaders() {
|
|
String message = 'Service shutdown';
|
|
if (_httpClient != null) {
|
|
_httpClient.close(force: true);
|
|
_httpClient = null;
|
|
}
|
|
isolateEmbedderData.values.toList().forEach((ied) {
|
|
IsolateLoaderState ils = ied;
|
|
ils.cleanup();
|
|
assert(ils.sp != null);
|
|
_sendResourceResponse(ils.sp, 1, null, null, null, message);
|
|
});
|
|
}
|
|
|
|
// See Dart_LibraryTag in dart_api.h
|
|
const _Dart_kCanonicalizeUrl = 0; // Canonicalize the URL.
|
|
const _Dart_kScriptTag = 1; // Load the root script.
|
|
const _Dart_kSourceTag = 2; // Load a part source.
|
|
const _Dart_kImportTag = 3; // Import a library.
|
|
|
|
// Extra requests. Keep these in sync between loader.dart and builtin.dart.
|
|
const _Dart_kInitLoader = 4; // Initialize the loader.
|
|
const _Dart_kResourceLoad = 5; // Resource class support.
|
|
const _Dart_kGetPackageRootUri = 6; // Uri of the packages/ directory.
|
|
const _Dart_kGetPackageConfigUri = 7; // Uri of the .packages file.
|
|
const _Dart_kResolvePackageUri = 8; // Resolve a package: uri.
|
|
|
|
// Extra requests. Keep these in sync between loader.dart and loader.cc.
|
|
const _Dart_kImportExtension = 9; // Import a dart-ext: file.
|
|
const _Dart_kResolveAsFilePath = 10; // Resolve uri to file path.
|
|
|
|
// External entry point for loader requests.
|
|
_processLoadRequest(request) {
|
|
assert(request is List);
|
|
assert(request.length > 4);
|
|
|
|
// Should we trace loading?
|
|
bool traceLoading = request[0];
|
|
|
|
// This is the sending isolate's Dart_GetMainPortId().
|
|
int isolateId = request[1];
|
|
|
|
// The tag describing the operation.
|
|
int tag = request[2];
|
|
|
|
// The send port to send the response on.
|
|
SendPort sp = request[3];
|
|
|
|
// Grab the loader state for the requesting isolate.
|
|
IsolateLoaderState loaderState = isolateEmbedderData[isolateId];
|
|
|
|
// We are either about to initialize the loader, or, we already have.
|
|
assert((tag == _Dart_kInitLoader) || (loaderState != null));
|
|
|
|
// Handle the request specified in the tag.
|
|
switch (tag) {
|
|
case _Dart_kInitLoader:
|
|
{
|
|
String packageRoot = request[4];
|
|
String packagesFile = request[5];
|
|
String workingDirectory = request[6];
|
|
String rootScript = request[7];
|
|
bool isReloading = request[8];
|
|
if (loaderState == null) {
|
|
loaderState = new IsolateLoaderState(isolateId);
|
|
isolateEmbedderData[isolateId] = loaderState;
|
|
loaderState.init(
|
|
packageRoot, packagesFile, workingDirectory, rootScript);
|
|
} else if (isReloading) {
|
|
loaderState.updatePackageMap(packagesFile);
|
|
}
|
|
loaderState.sp = sp;
|
|
assert(isolateEmbedderData[isolateId] == loaderState);
|
|
}
|
|
break;
|
|
case _Dart_kResourceLoad:
|
|
{
|
|
Uri uri = Uri.parse(request[4]);
|
|
_handleResourceRequest(
|
|
loaderState, sp, traceLoading, tag, uri, uri, null);
|
|
}
|
|
break;
|
|
case _Dart_kGetPackageRootUri:
|
|
loaderState._triggerPackageResolution(() {
|
|
// The package root is deprecated and now always returns null.
|
|
sp.send(null);
|
|
});
|
|
break;
|
|
case _Dart_kGetPackageConfigUri:
|
|
loaderState._triggerPackageResolution(() {
|
|
// Respond with the packages config (if any) after package resolution.
|
|
sp.send(loaderState._packageConfig);
|
|
});
|
|
break;
|
|
case _Dart_kResolvePackageUri:
|
|
Uri uri = Uri.parse(request[4]);
|
|
loaderState._triggerPackageResolution(() {
|
|
// Respond with the resolved package uri after package resolution.
|
|
Uri resolvedUri;
|
|
try {
|
|
resolvedUri = loaderState._resolvePackageUri(uri);
|
|
} catch (e, s) {
|
|
if (traceLoading) {
|
|
_log("Exception ($e) when resolving package URI: $uri");
|
|
}
|
|
resolvedUri = null;
|
|
}
|
|
sp.send(resolvedUri);
|
|
});
|
|
break;
|
|
case _Dart_kImportExtension:
|
|
Uri uri = Uri.parse(request[4]);
|
|
String libraryUri = request[5];
|
|
// Strip any filename off of the libraryUri's path.
|
|
int index = libraryUri.lastIndexOf('/');
|
|
var path;
|
|
if (index == -1) {
|
|
path = './';
|
|
} else {
|
|
path = libraryUri.substring(0, index + 1);
|
|
}
|
|
var pathUri = Uri.parse(path);
|
|
switch (pathUri.scheme) {
|
|
case '':
|
|
case 'file':
|
|
_sendExtensionImportResponse(
|
|
sp, uri, libraryUri, pathUri.toFilePath());
|
|
break;
|
|
case 'data':
|
|
case 'http':
|
|
case 'https':
|
|
_sendExtensionImportResponse(sp, uri, libraryUri, pathUri.toString());
|
|
break;
|
|
case 'package':
|
|
// Start package resolution.
|
|
loaderState._triggerPackageResolution(() {
|
|
// Attempt to find the fully resolved uri of [path].
|
|
Uri resolvedUri;
|
|
try {
|
|
resolvedUri = loaderState._resolvePackageUri(pathUri);
|
|
} catch (e, s) {
|
|
if (traceLoading) {
|
|
_log("Exception ($e) when resolving package URI: $uri");
|
|
}
|
|
resolvedUri = null;
|
|
}
|
|
_sendExtensionImportResponse(
|
|
sp, uri, libraryUri, resolvedUri.toString());
|
|
});
|
|
break;
|
|
default:
|
|
if (traceLoading) {
|
|
_log('Unknown scheme (${pathUri.scheme}) in $pathUri.');
|
|
}
|
|
_sendExtensionImportResponse(sp, uri, libraryUri, null);
|
|
break;
|
|
}
|
|
break;
|
|
case _Dart_kResolveAsFilePath:
|
|
loaderState._triggerPackageResolution(() {
|
|
String uri = request[4];
|
|
Uri resolvedUri = Uri.parse(_sanitizeWindowsPath(uri));
|
|
try {
|
|
if (resolvedUri.scheme == 'package') {
|
|
resolvedUri = loaderState._resolvePackageUri(resolvedUri);
|
|
}
|
|
if (resolvedUri.scheme == '' || resolvedUri.scheme == 'file') {
|
|
resolvedUri = loaderState._workingDirectory.resolveUri(resolvedUri);
|
|
var msg = new List(5);
|
|
msg[0] = tag;
|
|
msg[1] = uri;
|
|
msg[2] = resolvedUri.toString();
|
|
msg[3] = null;
|
|
msg[4] = resolvedUri.toFilePath();
|
|
sp.send(msg);
|
|
} else {
|
|
throw "Cannot resolve scheme (${resolvedUri.scheme}) to file path"
|
|
" for $resolvedUri";
|
|
}
|
|
} catch (e) {
|
|
var msg = new List(5);
|
|
msg[0] = -tag;
|
|
msg[1] = uri;
|
|
msg[2] = resolvedUri.toString();
|
|
msg[3] = null;
|
|
msg[4] = e.toString();
|
|
sp.send(msg);
|
|
}
|
|
});
|
|
break;
|
|
default:
|
|
_log('Unknown loader request tag=$tag from $isolateId');
|
|
}
|
|
}
|