blob: 2b56ac00003095c547ef53a3d9f48f088646d33d [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 'ssa/tracer.dart' as ssa;
import '../../libraries.dart';
import 'source_file.dart';
class Compiler extends leg.Compiler {
api.ReadStringFromUri provider;
api.DiagnosticHandler handler;
final Uri libraryRoot;
final Uri packageRoot;
List<String> options;
bool mockableLibraryUsed = false;
final Set<String> allowedLibraryCategories;
Compiler(this.provider,
api.CompilerOutputProvider outputProvider,
this.handler,
this.libraryRoot,
this.packageRoot,
List<String> options)
: this.options = options,
this.allowedLibraryCategories = getAllowedLibraryCategories(options),
super(
tracer: new ssa.HTracer(
ssa.GENERATE_SSA_TRACE ? outputProvider('dart', 'cfg') : null),
outputProvider: outputProvider,
enableTypeAssertions: hasOption(options, '--enable-checked-mode'),
enableUserAssertions: hasOption(options, '--enable-checked-mode'),
trustTypeAnnotations:
hasOption(options, '--trust-type-annotations'),
enableMinification: hasOption(options, '--minify'),
enableNativeLiveTypeAnalysis:
!hasOption(options, '--disable-native-live-type-analysis'),
emitJavaScript: !hasOption(options, '--output-type=dart'),
analyzeAllFlag: hasOption(options, '--analyze-all'),
analyzeOnly: hasOption(options, '--analyze-only'),
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: extractSourceMapUri(options),
globalJsName: extractStringOption(
options, '--global-js-name=', r'$'),
terseDiagnostics: hasOption(options, '--terse'),
buildId: extractStringOption(
options, '--build-id=',
"build number could not be determined")) {
if (!libraryRoot.path.endsWith("/")) {
throw new ArgumentError("libraryRoot must end with a /");
}
if (packageRoot != null && !packageRoot.path.endsWith("/")) {
throw new ArgumentError("packageRoot must end with a /");
}
}
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 extractSourceMapUri(List<String> options) {
var option = extractStringOption(options, '--source-map=', 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 = LIBRARIES[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 = LIBRARIES[dartLibraryName];
if (info == null) return null;
if (!info.isDart2jsLibrary) return null;
String path = info.dart2jsPatchPath;
if (path == null) return null;
return "lib/$path";
}
Future<elements.LibraryElement> scanBuiltinLibrary(String path) {
Uri uri = libraryRoot.resolve(lookupLibraryPath(path));
Uri canonicalUri = new Uri(scheme: "dart", path: path);
return libraryLoader.loadLibrary(uri, null, canonicalUri);
}
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(Uri readableUri,
[elements.Element element, tree.Node node]) {
if (!readableUri.isAbsolute) {
internalError('Relative uri $readableUri provided to readScript(Uri)',
node: node);
}
// TODO(johnniwinther): Add [:report(..., {Element element}):] to
// report methods in Compiler.
void reportReadError(exception) {
withCurrentElement(element, () {
reportError(node,
leg.MessageKind.READ_SCRIPT_ERROR,
{'uri': readableUri, 'exception': exception});
});
}
Uri resourceUri = translateUri(readableUri, node);
// 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((String text) {
SourceFile sourceFile = new SourceFile(resourceUri.toString(), text);
// 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, sourceFile);
}).catchError((error) {
reportReadError(error);
return null;
});
}
/**
* Translates a readable URI into a resource URI.
*
* See [LibraryLoader] for terminology on URIs.
*/
Uri translateUri(Uri readableUri, tree.Node node) {
switch (readableUri.scheme) {
case 'package': return translatePackageUri(readableUri, node);
default: return readableUri;
}
}
Uri translateDartUri(elements.LibraryElement importingLibrary,
Uri resolvedUri, tree.Node node) {
LibraryInfo libraryInfo = LIBRARIES[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(Uri uri, tree.Node node) {
if (packageRoot == null) {
reportFatalError(
node, leg.MessageKind.PACKAGE_ROOT_NOT_SET, {'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) {
cumulated += task.timing;
log('${task.name} took ${task.timing}msec');
}
int total = totalCompileTime.elapsedMilliseconds;
log('Total compile-time ${total}msec;'
' unaccounted ${total - cumulated}msec');
return success;
});
}
void reportDiagnostic(leg.SourceSpan span, String message,
api.Diagnostic kind) {
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(span.uri, null), 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 {
handler(uri, begin, end, message, kind);
} catch (ex, s) {
diagnoseCrashInUserCode(
'Uncaught exception in diagnostic handler', ex, s);
rethrow;
}
}
Future callUserProvider(Uri uri) {
try {
return 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));
}
}