blob: be603fa2904b24746741dc370cb3727263ef386d [file] [log] [blame]
// Copyright (c) 2012, 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.
library leg_apiimpl;
import 'dart:async';
import '../compiler.dart' as api;
import 'dart2jslib.dart' as leg;
import 'tree/tree.dart' as tree;
import 'elements/elements.dart' as elements;
import 'package:_internal/libraries.dart' hide LIBRARIES;
import 'package:_internal/libraries.dart' as library_info show LIBRARIES;
import 'io/source_file.dart';
const bool forceIncrementalSupport =
const bool.fromEnvironment('DART2JS_EXPERIMENTAL_INCREMENTAL_SUPPORT');
class Compiler extends leg.Compiler {
api.CompilerInputProvider provider;
api.DiagnosticHandler handler;
final Uri libraryRoot;
final Uri packageRoot;
List<String> options;
Map<String, dynamic> environment;
bool mockableLibraryUsed = false;
final Set<String> allowedLibraryCategories;
leg.GenericTask userHandlerTask;
leg.GenericTask userProviderTask;
Compiler(this.provider,
api.CompilerOutputProvider outputProvider,
this.handler,
this.libraryRoot,
this.packageRoot,
List<String> options,
this.environment)
: this.options = options,
this.allowedLibraryCategories = getAllowedLibraryCategories(options),
super(
outputProvider: outputProvider,
enableTypeAssertions: hasOption(options, '--enable-checked-mode'),
enableUserAssertions: hasOption(options, '--enable-checked-mode'),
trustTypeAnnotations:
hasOption(options, '--trust-type-annotations'),
trustPrimitives:
hasOption(options, '--trust-primitives'),
enableMinification: hasOption(options, '--minify'),
preserveUris: hasOption(options, '--preserve-uris'),
enableNativeLiveTypeAnalysis:
!hasOption(options, '--disable-native-live-type-analysis'),
emitJavaScript: !(hasOption(options, '--output-type=dart') ||
hasOption(options, '--output-type=dart-multi')),
dart2dartMultiFile: hasOption(options, '--output-type=dart-multi'),
generateSourceMap: !hasOption(options, '--no-source-maps'),
analyzeAllFlag: hasOption(options, '--analyze-all'),
analyzeOnly: hasOption(options, '--analyze-only'),
analyzeMain: hasOption(options, '--analyze-main'),
analyzeSignaturesOnly:
hasOption(options, '--analyze-signatures-only'),
strips: extractCsvOption(options, '--force-strip='),
enableConcreteTypeInference:
hasOption(options, '--enable-concrete-type-inference'),
disableTypeInferenceFlag:
hasOption(options, '--disable-type-inference'),
preserveComments: hasOption(options, '--preserve-comments'),
verbose: hasOption(options, '--verbose'),
sourceMapUri: extractUriOption(options, '--source-map='),
outputUri: extractUriOption(options, '--out='),
terseDiagnostics: hasOption(options, '--terse'),
deferredMapUri: extractUriOption(options, '--deferred-map='),
dumpInfo: hasOption(options, '--dump-info'),
buildId: extractStringOption(
options, '--build-id=',
"build number could not be determined"),
showPackageWarnings:
hasOption(options, '--show-package-warnings'),
useContentSecurityPolicy: hasOption(options, '--csp'),
hasIncrementalSupport:
forceIncrementalSupport ||
hasOption(options, '--incremental-support'),
suppressWarnings: hasOption(options, '--suppress-warnings'),
enableExperimentalMirrors:
hasOption(options, '--enable-experimental-mirrors'),
generateCodeWithCompileTimeErrors:
hasOption(options, '--generate-code-with-compile-time-errors'),
allowNativeExtensions:
hasOption(options, '--allow-native-extensions')) {
tasks.addAll([
userHandlerTask = new leg.GenericTask('Diagnostic handler', this),
userProviderTask = new leg.GenericTask('Input provider', this),
]);
if (libraryRoot == null) {
throw new ArgumentError("[libraryRoot] is null.");
}
if (!libraryRoot.path.endsWith("/")) {
throw new ArgumentError("[libraryRoot] must end with a /.");
}
if (packageRoot == null) {
throw new ArgumentError("[packageRoot] is null.");
}
if (!packageRoot.path.endsWith("/")) {
throw new ArgumentError("[packageRoot] must end with a /.");
}
if (!analyzeOnly) {
if (allowNativeExtensions) {
throw new ArgumentError(
"--allow-native-extensions is only supported in combination with "
"--analyze-only");
}
}
}
static String extractStringOption(List<String> options,
String prefix,
String defaultValue) {
for (String option in options) {
if (option.startsWith(prefix)) {
return option.substring(prefix.length);
}
}
return defaultValue;
}
static Uri extractUriOption(List<String> options, String prefix) {
var option = extractStringOption(options, prefix, null);
return (option == null) ? null : Uri.parse(option);
}
// CSV: Comma separated values.
static List<String> extractCsvOption(List<String> options, String prefix) {
for (String option in options) {
if (option.startsWith(prefix)) {
return option.substring(prefix.length).split(',');
}
}
return const <String>[];
}
static Set<String> getAllowedLibraryCategories(List<String> options) {
var result = extractCsvOption(options, '--categories=');
if (result.isEmpty) {
result = ['Client'];
}
result.add('Shared');
result.add('Internal');
return new Set<String>.from(result);
}
static bool hasOption(List<String> options, String option) {
return options.indexOf(option) >= 0;
}
// TODO(johnniwinther): Merge better with [translateDartUri] when
// [scanBuiltinLibrary] is removed.
String lookupLibraryPath(String dartLibraryName) {
LibraryInfo info = lookupLibraryInfo(dartLibraryName);
if (info == null) return null;
if (!info.isDart2jsLibrary) return null;
if (!allowedLibraryCategories.contains(info.category)) return null;
String path = info.dart2jsPath;
if (path == null) {
path = info.path;
}
return "lib/$path";
}
String lookupPatchPath(String dartLibraryName) {
LibraryInfo info = lookupLibraryInfo(dartLibraryName);
if (info == null) return null;
if (!info.isDart2jsLibrary) return null;
String path = info.dart2jsPatchPath;
if (path == null) return null;
return "lib/$path";
}
void log(message) {
handler(null, null, null, message, api.Diagnostic.VERBOSE_INFO);
}
/// See [leg.Compiler.translateResolvedUri].
Uri translateResolvedUri(elements.LibraryElement importingLibrary,
Uri resolvedUri, tree.Node node) {
if (resolvedUri.scheme == 'dart') {
return translateDartUri(importingLibrary, resolvedUri, node);
}
return resolvedUri;
}
/**
* Reads the script designated by [readableUri].
*/
Future<leg.Script> readScript(leg.Spannable node, Uri readableUri) {
if (!readableUri.isAbsolute) {
if (node == null) node = leg.NO_LOCATION_SPANNABLE;
internalError(node,
'Relative uri $readableUri provided to readScript(Uri).');
}
// We need to store the current element since we are reporting read errors
// asynchronously and therefore need to restore the current element for
// [node] to be valid.
elements.Element element = currentElement;
void reportReadError(exception) {
if (element == null || node == null) {
reportError(
new leg.SourceSpan(readableUri, 0, 0),
leg.MessageKind.READ_SELF_ERROR,
{'uri': readableUri, 'exception': exception});
} else {
withCurrentElement(element, () {
reportError(
node,
leg.MessageKind.READ_SCRIPT_ERROR,
{'uri': readableUri, 'exception': exception});
});
}
}
Uri resourceUri = translateUri(node, readableUri);
String resourceUriString = '$resourceUri';
if (resourceUri.scheme == 'dart-ext') {
if (!allowNativeExtensions) {
withCurrentElement(element, () {
reportError(node, leg.MessageKind.DART_EXT_NOT_SUPPORTED);
});
}
return synthesizeScript(node, readableUri);
}
// TODO(johnniwinther): Wrap the result from [provider] in a specialized
// [Future] to ensure that we never execute an asynchronous action without
// setting up the current element of the compiler.
return new Future.sync(() => callUserProvider(resourceUri)).then((data) {
SourceFile sourceFile;
if (data is List<int>) {
sourceFile = new Utf8BytesSourceFile(resourceUriString, data);
} else if (data is String) {
sourceFile = new StringSourceFile(resourceUriString, data);
} else {
String message = "Expected a 'String' or a 'List<int>' from the input "
"provider, but got: ${Error.safeToString(data)}.";
reportReadError(message);
}
// We use [readableUri] as the URI for the script since need to preserve
// the scheme in the script because [Script.uri] is used for resolving
// relative URIs mentioned in the script. See the comment on
// [LibraryLoader] for more details.
return new leg.Script(readableUri, resourceUri, sourceFile);
}).catchError((error) {
reportReadError(error);
return synthesizeScript(node, readableUri);
});
}
Future<leg.Script> synthesizeScript(leg.Spannable node, Uri readableUri) {
Uri resourceUri = translateUri(node, readableUri);
return new Future.value(
new leg.Script(
readableUri, resourceUri,
new StringSourceFile(
'$resourceUri',
"// Synthetic source file generated for '$readableUri'."),
isSynthesized: true));
}
/**
* Translates a readable URI into a resource URI.
*
* See [LibraryLoader] for terminology on URIs.
*/
Uri translateUri(leg.Spannable node, Uri readableUri) {
switch (readableUri.scheme) {
case 'package': return translatePackageUri(node, readableUri);
default: return readableUri;
}
}
Uri translateDartUri(elements.LibraryElement importingLibrary,
Uri resolvedUri, tree.Node node) {
LibraryInfo libraryInfo = lookupLibraryInfo(resolvedUri.path);
String path = lookupLibraryPath(resolvedUri.path);
if (libraryInfo != null &&
libraryInfo.category == "Internal") {
bool allowInternalLibraryAccess = false;
if (importingLibrary != null) {
if (importingLibrary.isPlatformLibrary || importingLibrary.isPatch) {
allowInternalLibraryAccess = true;
} else if (importingLibrary.canonicalUri.path.contains(
'dart/tests/compiler/dart2js_native')) {
allowInternalLibraryAccess = true;
}
}
if (!allowInternalLibraryAccess) {
if (importingLibrary != null) {
reportError(
node,
leg.MessageKind.INTERNAL_LIBRARY_FROM,
{'resolvedUri': resolvedUri,
'importingUri': importingLibrary.canonicalUri});
} else {
reportError(
node,
leg.MessageKind.INTERNAL_LIBRARY,
{'resolvedUri': resolvedUri});
}
}
}
if (path == null) {
reportError(node, leg.MessageKind.LIBRARY_NOT_FOUND,
{'resolvedUri': resolvedUri});
return null;
}
if (resolvedUri.path == 'html' ||
resolvedUri.path == 'io') {
// TODO(ahe): Get rid of mockableLibraryUsed when test.dart
// supports this use case better.
mockableLibraryUsed = true;
}
return libraryRoot.resolve(path);
}
Uri resolvePatchUri(String dartLibraryPath) {
String patchPath = lookupPatchPath(dartLibraryPath);
if (patchPath == null) return null;
return libraryRoot.resolve(patchPath);
}
Uri translatePackageUri(leg.Spannable node, Uri uri) {
return packageRoot.resolve(uri.path);
}
Future<bool> run(Uri uri) {
log('Allowed library categories: $allowedLibraryCategories');
return super.run(uri).then((bool success) {
int cumulated = 0;
for (final task in tasks) {
int elapsed = task.timing;
if (elapsed != 0) {
cumulated += elapsed;
log('${task.name} took ${elapsed}msec');
}
}
int total = totalCompileTime.elapsedMilliseconds;
log('Total compile-time ${total}msec;'
' unaccounted ${total - cumulated}msec');
return success;
});
}
void reportDiagnostic(leg.Spannable node,
leg.Message message,
api.Diagnostic kind) {
leg.SourceSpan span = spanFromSpannable(node);
if (identical(kind, api.Diagnostic.ERROR)
|| identical(kind, api.Diagnostic.CRASH)) {
compilationFailed = true;
}
// [:span.uri:] might be [:null:] in case of a [Script] with no [uri]. For
// instance in the [Types] constructor in typechecker.dart.
if (span == null || span.uri == null) {
callUserHandler(null, null, null, '$message', kind);
} else {
callUserHandler(
translateUri(null, span.uri), span.begin, span.end, '$message', kind);
}
}
bool get isMockCompilation {
return mockableLibraryUsed
&& (options.indexOf('--allow-mock-compilation') != -1);
}
void callUserHandler(Uri uri, int begin, int end,
String message, api.Diagnostic kind) {
try {
userHandlerTask.measure(() {
handler(uri, begin, end, message, kind);
});
} catch (ex, s) {
diagnoseCrashInUserCode(
'Uncaught exception in diagnostic handler', ex, s);
rethrow;
}
}
Future callUserProvider(Uri uri) {
try {
return userProviderTask.measure(() => provider(uri));
} catch (ex, s) {
diagnoseCrashInUserCode('Uncaught exception in input provider', ex, s);
rethrow;
}
}
void diagnoseCrashInUserCode(String message, exception, stackTrace) {
hasCrashed = true;
print('$message: ${tryToString(exception)}');
print(tryToString(stackTrace));
}
fromEnvironment(String name) => environment[name];
LibraryInfo lookupLibraryInfo(String libraryName) {
return library_info.LIBRARIES[libraryName];
}
}