OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library leg_apiimpl; | |
6 | |
7 import 'dart:async'; | |
8 | |
9 import '../compiler.dart' as api; | |
10 import 'dart2jslib.dart' as leg; | |
11 import 'tree/tree.dart' as tree; | |
12 import 'elements/elements.dart' as elements; | |
13 import '../../libraries.dart'; | |
14 import 'source_file.dart'; | |
15 | |
16 const bool forceIncrementalSupport = | |
17 const bool.fromEnvironment('DART2JS_EXPERIMENTAL_INCREMENTAL_SUPPORT'); | |
18 | |
19 class Compiler extends leg.Compiler { | |
20 api.CompilerInputProvider provider; | |
21 api.DiagnosticHandler handler; | |
22 final Uri libraryRoot; | |
23 final Uri packageRoot; | |
24 List<String> options; | |
25 Map<String, dynamic> environment; | |
26 bool mockableLibraryUsed = false; | |
27 final Set<String> allowedLibraryCategories; | |
28 | |
29 leg.GenericTask userHandlerTask; | |
30 leg.GenericTask userProviderTask; | |
31 | |
32 Compiler(this.provider, | |
33 api.CompilerOutputProvider outputProvider, | |
34 this.handler, | |
35 this.libraryRoot, | |
36 this.packageRoot, | |
37 List<String> options, | |
38 this.environment) | |
39 : this.options = options, | |
40 this.allowedLibraryCategories = getAllowedLibraryCategories(options), | |
41 super( | |
42 outputProvider: outputProvider, | |
43 enableTypeAssertions: hasOption(options, '--enable-checked-mode'), | |
44 enableUserAssertions: hasOption(options, '--enable-checked-mode'), | |
45 trustTypeAnnotations: | |
46 hasOption(options, '--trust-type-annotations'), | |
47 enableMinification: hasOption(options, '--minify'), | |
48 preserveUris: hasOption(options, '--preserve-uris'), | |
49 enableNativeLiveTypeAnalysis: | |
50 !hasOption(options, '--disable-native-live-type-analysis'), | |
51 emitJavaScript: !(hasOption(options, '--output-type=dart') || | |
52 hasOption(options, '--output-type=dart-multi')), | |
53 dart2dartMultiFile: hasOption(options, '--output-type=dart-multi'), | |
54 generateSourceMap: !hasOption(options, '--no-source-maps'), | |
55 analyzeAllFlag: hasOption(options, '--analyze-all'), | |
56 analyzeOnly: hasOption(options, '--analyze-only'), | |
57 analyzeMain: hasOption(options, '--analyze-main'), | |
58 analyzeSignaturesOnly: | |
59 hasOption(options, '--analyze-signatures-only'), | |
60 strips: extractCsvOption(options, '--force-strip='), | |
61 enableConcreteTypeInference: | |
62 hasOption(options, '--enable-concrete-type-inference'), | |
63 disableTypeInferenceFlag: | |
64 hasOption(options, '--disable-type-inference'), | |
65 preserveComments: hasOption(options, '--preserve-comments'), | |
66 verbose: hasOption(options, '--verbose'), | |
67 sourceMapUri: extractUriOption(options, '--source-map='), | |
68 outputUri: extractUriOption(options, '--out='), | |
69 terseDiagnostics: hasOption(options, '--terse'), | |
70 dumpInfo: hasOption(options, '--dump-info'), | |
71 buildId: extractStringOption( | |
72 options, '--build-id=', | |
73 "build number could not be determined"), | |
74 showPackageWarnings: | |
75 hasOption(options, '--show-package-warnings'), | |
76 useContentSecurityPolicy: hasOption(options, '--csp'), | |
77 hasIncrementalSupport: | |
78 forceIncrementalSupport || | |
79 hasOption(options, '--incremental-support'), | |
80 suppressWarnings: hasOption(options, '--suppress-warnings'), | |
81 enableAsyncAwait: hasOption(options, '--enable-async')) { | |
82 tasks.addAll([ | |
83 userHandlerTask = new leg.GenericTask('Diagnostic handler', this), | |
84 userProviderTask = new leg.GenericTask('Input provider', this), | |
85 ]); | |
86 if (!libraryRoot.path.endsWith("/")) { | |
87 throw new ArgumentError("libraryRoot must end with a /"); | |
88 } | |
89 if (packageRoot != null && !packageRoot.path.endsWith("/")) { | |
90 throw new ArgumentError("packageRoot must end with a /"); | |
91 } | |
92 if (enableAsyncAwait && !analyzeOnly) { | |
93 throw new ArgumentError( | |
94 "--enable-async is currently only supported with --analyze-only"); | |
95 } | |
96 } | |
97 | |
98 static String extractStringOption(List<String> options, | |
99 String prefix, | |
100 String defaultValue) { | |
101 for (String option in options) { | |
102 if (option.startsWith(prefix)) { | |
103 return option.substring(prefix.length); | |
104 } | |
105 } | |
106 return defaultValue; | |
107 } | |
108 | |
109 static Uri extractUriOption(List<String> options, String prefix) { | |
110 var option = extractStringOption(options, prefix, null); | |
111 return (option == null) ? null : Uri.parse(option); | |
112 } | |
113 | |
114 // CSV: Comma separated values. | |
115 static List<String> extractCsvOption(List<String> options, String prefix) { | |
116 for (String option in options) { | |
117 if (option.startsWith(prefix)) { | |
118 return option.substring(prefix.length).split(','); | |
119 } | |
120 } | |
121 return const <String>[]; | |
122 } | |
123 | |
124 static Set<String> getAllowedLibraryCategories(List<String> options) { | |
125 var result = extractCsvOption(options, '--categories='); | |
126 if (result.isEmpty) { | |
127 result = ['Client']; | |
128 } | |
129 result.add('Shared'); | |
130 result.add('Internal'); | |
131 return new Set<String>.from(result); | |
132 } | |
133 | |
134 static bool hasOption(List<String> options, String option) { | |
135 return options.indexOf(option) >= 0; | |
136 } | |
137 | |
138 // TODO(johnniwinther): Merge better with [translateDartUri] when | |
139 // [scanBuiltinLibrary] is removed. | |
140 String lookupLibraryPath(String dartLibraryName) { | |
141 LibraryInfo info = LIBRARIES[dartLibraryName]; | |
142 if (info == null) return null; | |
143 if (!info.isDart2jsLibrary) return null; | |
144 if (!allowedLibraryCategories.contains(info.category)) return null; | |
145 String path = info.dart2jsPath; | |
146 if (path == null) { | |
147 path = info.path; | |
148 } | |
149 return "lib/$path"; | |
150 } | |
151 | |
152 String lookupPatchPath(String dartLibraryName) { | |
153 LibraryInfo info = LIBRARIES[dartLibraryName]; | |
154 if (info == null) return null; | |
155 if (!info.isDart2jsLibrary) return null; | |
156 String path = info.dart2jsPatchPath; | |
157 if (path == null) return null; | |
158 return "lib/$path"; | |
159 } | |
160 | |
161 void log(message) { | |
162 handler(null, null, null, message, api.Diagnostic.VERBOSE_INFO); | |
163 } | |
164 | |
165 /// See [leg.Compiler.translateResolvedUri]. | |
166 Uri translateResolvedUri(elements.LibraryElement importingLibrary, | |
167 Uri resolvedUri, tree.Node node) { | |
168 if (resolvedUri.scheme == 'dart') { | |
169 return translateDartUri(importingLibrary, resolvedUri, node); | |
170 } | |
171 return resolvedUri; | |
172 } | |
173 | |
174 /** | |
175 * Reads the script designated by [readableUri]. | |
176 */ | |
177 Future<leg.Script> readScript(leg.Spannable node, Uri readableUri) { | |
178 if (!readableUri.isAbsolute) { | |
179 if (node == null) node = leg.NO_LOCATION_SPANNABLE; | |
180 internalError(node, | |
181 'Relative uri $readableUri provided to readScript(Uri).'); | |
182 } | |
183 | |
184 // We need to store the current element since we are reporting read errors | |
185 // asynchronously and therefore need to restore the current element for | |
186 // [node] to be valid. | |
187 elements.Element element = currentElement; | |
188 void reportReadError(exception) { | |
189 withCurrentElement(element, () { | |
190 reportError(node, | |
191 leg.MessageKind.READ_SCRIPT_ERROR, | |
192 {'uri': readableUri, 'exception': exception}); | |
193 }); | |
194 } | |
195 | |
196 Uri resourceUri = translateUri(node, readableUri); | |
197 // TODO(johnniwinther): Wrap the result from [provider] in a specialized | |
198 // [Future] to ensure that we never execute an asynchronous action without | |
199 // setting up the current element of the compiler. | |
200 return new Future.sync(() => callUserProvider(resourceUri)).then((data) { | |
201 SourceFile sourceFile; | |
202 String resourceUriString = resourceUri.toString(); | |
203 if (data is List<int>) { | |
204 sourceFile = new Utf8BytesSourceFile(resourceUriString, data); | |
205 } else if (data is String) { | |
206 sourceFile = new StringSourceFile(resourceUriString, data); | |
207 } else { | |
208 String message = "Expected a 'String' or a 'List<int>' from the input " | |
209 "provider, but got: ${Error.safeToString(data)}."; | |
210 reportReadError(message); | |
211 } | |
212 // We use [readableUri] as the URI for the script since need to preserve | |
213 // the scheme in the script because [Script.uri] is used for resolving | |
214 // relative URIs mentioned in the script. See the comment on | |
215 // [LibraryLoader] for more details. | |
216 return new leg.Script(readableUri, resourceUri, sourceFile); | |
217 }).catchError((error) { | |
218 reportReadError(error); | |
219 return null; | |
220 }); | |
221 } | |
222 | |
223 /** | |
224 * Translates a readable URI into a resource URI. | |
225 * | |
226 * See [LibraryLoader] for terminology on URIs. | |
227 */ | |
228 Uri translateUri(leg.Spannable node, Uri readableUri) { | |
229 switch (readableUri.scheme) { | |
230 case 'package': return translatePackageUri(node, readableUri); | |
231 default: return readableUri; | |
232 } | |
233 } | |
234 | |
235 Uri translateDartUri(elements.LibraryElement importingLibrary, | |
236 Uri resolvedUri, tree.Node node) { | |
237 LibraryInfo libraryInfo = LIBRARIES[resolvedUri.path]; | |
238 String path = lookupLibraryPath(resolvedUri.path); | |
239 if (libraryInfo != null && | |
240 libraryInfo.category == "Internal") { | |
241 bool allowInternalLibraryAccess = false; | |
242 if (importingLibrary != null) { | |
243 if (importingLibrary.isPlatformLibrary || importingLibrary.isPatch) { | |
244 allowInternalLibraryAccess = true; | |
245 } else if (importingLibrary.canonicalUri.path.contains( | |
246 'dart/tests/compiler/dart2js_native')) { | |
247 allowInternalLibraryAccess = true; | |
248 } | |
249 } | |
250 if (!allowInternalLibraryAccess) { | |
251 if (importingLibrary != null) { | |
252 reportError( | |
253 node, | |
254 leg.MessageKind.INTERNAL_LIBRARY_FROM, | |
255 {'resolvedUri': resolvedUri, | |
256 'importingUri': importingLibrary.canonicalUri}); | |
257 } else { | |
258 reportError( | |
259 node, | |
260 leg.MessageKind.INTERNAL_LIBRARY, | |
261 {'resolvedUri': resolvedUri}); | |
262 } | |
263 } | |
264 } | |
265 if (path == null) { | |
266 reportError(node, leg.MessageKind.LIBRARY_NOT_FOUND, | |
267 {'resolvedUri': resolvedUri}); | |
268 return null; | |
269 } | |
270 if (resolvedUri.path == 'html' || | |
271 resolvedUri.path == 'io') { | |
272 // TODO(ahe): Get rid of mockableLibraryUsed when test.dart | |
273 // supports this use case better. | |
274 mockableLibraryUsed = true; | |
275 } | |
276 return libraryRoot.resolve(path); | |
277 } | |
278 | |
279 Uri resolvePatchUri(String dartLibraryPath) { | |
280 String patchPath = lookupPatchPath(dartLibraryPath); | |
281 if (patchPath == null) return null; | |
282 return libraryRoot.resolve(patchPath); | |
283 } | |
284 | |
285 Uri translatePackageUri(leg.Spannable node, Uri uri) { | |
286 if (packageRoot == null) { | |
287 reportFatalError( | |
288 node, leg.MessageKind.PACKAGE_ROOT_NOT_SET, {'uri': uri}); | |
289 } | |
290 return packageRoot.resolve(uri.path); | |
291 } | |
292 | |
293 Future<bool> run(Uri uri) { | |
294 log('Allowed library categories: $allowedLibraryCategories'); | |
295 return super.run(uri).then((bool success) { | |
296 int cumulated = 0; | |
297 for (final task in tasks) { | |
298 int elapsed = task.timing; | |
299 if (elapsed != 0) { | |
300 cumulated += elapsed; | |
301 log('${task.name} took ${elapsed}msec'); | |
302 } | |
303 } | |
304 int total = totalCompileTime.elapsedMilliseconds; | |
305 log('Total compile-time ${total}msec;' | |
306 ' unaccounted ${total - cumulated}msec'); | |
307 return success; | |
308 }); | |
309 } | |
310 | |
311 void reportDiagnostic(leg.Spannable node, | |
312 leg.Message message, | |
313 api.Diagnostic kind) { | |
314 leg.SourceSpan span = spanFromSpannable(node); | |
315 if (identical(kind, api.Diagnostic.ERROR) | |
316 || identical(kind, api.Diagnostic.CRASH)) { | |
317 compilationFailed = true; | |
318 } | |
319 // [:span.uri:] might be [:null:] in case of a [Script] with no [uri]. For | |
320 // instance in the [Types] constructor in typechecker.dart. | |
321 if (span == null || span.uri == null) { | |
322 callUserHandler(null, null, null, '$message', kind); | |
323 } else { | |
324 callUserHandler( | |
325 translateUri(null, span.uri), span.begin, span.end, '$message', kind); | |
326 } | |
327 } | |
328 | |
329 bool get isMockCompilation { | |
330 return mockableLibraryUsed | |
331 && (options.indexOf('--allow-mock-compilation') != -1); | |
332 } | |
333 | |
334 void callUserHandler(Uri uri, int begin, int end, | |
335 String message, api.Diagnostic kind) { | |
336 try { | |
337 userHandlerTask.measure(() { | |
338 handler(uri, begin, end, message, kind); | |
339 }); | |
340 } catch (ex, s) { | |
341 diagnoseCrashInUserCode( | |
342 'Uncaught exception in diagnostic handler', ex, s); | |
343 rethrow; | |
344 } | |
345 } | |
346 | |
347 Future callUserProvider(Uri uri) { | |
348 try { | |
349 return userProviderTask.measure(() => provider(uri)); | |
350 } catch (ex, s) { | |
351 diagnoseCrashInUserCode('Uncaught exception in input provider', ex, s); | |
352 rethrow; | |
353 } | |
354 } | |
355 | |
356 void diagnoseCrashInUserCode(String message, exception, stackTrace) { | |
357 hasCrashed = true; | |
358 print('$message: ${tryToString(exception)}'); | |
359 print(tryToString(stackTrace)); | |
360 } | |
361 | |
362 fromEnvironment(String name) => environment[name]; | |
363 } | |
OLD | NEW |