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 tasks.addAll([ | |
82 userHandlerTask = new leg.GenericTask('Diagnostic handler', this), | |
83 userProviderTask = new leg.GenericTask('Input provider', this), | |
84 ]); | |
85 if (!libraryRoot.path.endsWith("/")) { | |
86 throw new ArgumentError("libraryRoot must end with a /"); | |
87 } | |
88 if (packageRoot != null && !packageRoot.path.endsWith("/")) { | |
89 throw new ArgumentError("packageRoot must end with a /"); | |
90 } | |
91 } | |
92 | |
93 static String extractStringOption(List<String> options, | |
94 String prefix, | |
95 String defaultValue) { | |
96 for (String option in options) { | |
97 if (option.startsWith(prefix)) { | |
98 return option.substring(prefix.length); | |
99 } | |
100 } | |
101 return defaultValue; | |
102 } | |
103 | |
104 static Uri extractUriOption(List<String> options, String prefix) { | |
105 var option = extractStringOption(options, prefix, null); | |
106 return (option == null) ? null : Uri.parse(option); | |
107 } | |
108 | |
109 // CSV: Comma separated values. | |
110 static List<String> extractCsvOption(List<String> options, String prefix) { | |
111 for (String option in options) { | |
112 if (option.startsWith(prefix)) { | |
113 return option.substring(prefix.length).split(','); | |
114 } | |
115 } | |
116 return const <String>[]; | |
117 } | |
118 | |
119 static Set<String> getAllowedLibraryCategories(List<String> options) { | |
120 var result = extractCsvOption(options, '--categories='); | |
121 if (result.isEmpty) { | |
122 result = ['Client']; | |
123 } | |
124 result.add('Shared'); | |
125 result.add('Internal'); | |
126 return new Set<String>.from(result); | |
127 } | |
128 | |
129 static bool hasOption(List<String> options, String option) { | |
130 return options.indexOf(option) >= 0; | |
131 } | |
132 | |
133 // TODO(johnniwinther): Merge better with [translateDartUri] when | |
134 // [scanBuiltinLibrary] is removed. | |
135 String lookupLibraryPath(String dartLibraryName) { | |
136 LibraryInfo info = LIBRARIES[dartLibraryName]; | |
137 if (info == null) return null; | |
138 if (!info.isDart2jsLibrary) return null; | |
139 if (!allowedLibraryCategories.contains(info.category)) return null; | |
140 String path = info.dart2jsPath; | |
141 if (path == null) { | |
142 path = info.path; | |
143 } | |
144 return "lib/$path"; | |
145 } | |
146 | |
147 String lookupPatchPath(String dartLibraryName) { | |
148 LibraryInfo info = LIBRARIES[dartLibraryName]; | |
149 if (info == null) return null; | |
150 if (!info.isDart2jsLibrary) return null; | |
151 String path = info.dart2jsPatchPath; | |
152 if (path == null) return null; | |
153 return "lib/$path"; | |
154 } | |
155 | |
156 void log(message) { | |
157 handler(null, null, null, message, api.Diagnostic.VERBOSE_INFO); | |
158 } | |
159 | |
160 /// See [leg.Compiler.translateResolvedUri]. | |
161 Uri translateResolvedUri(elements.LibraryElement importingLibrary, | |
162 Uri resolvedUri, tree.Node node) { | |
163 if (resolvedUri.scheme == 'dart') { | |
164 return translateDartUri(importingLibrary, resolvedUri, node); | |
165 } | |
166 return resolvedUri; | |
167 } | |
168 | |
169 /** | |
170 * Reads the script designated by [readableUri]. | |
171 */ | |
172 Future<leg.Script> readScript(leg.Spannable node, Uri readableUri) { | |
173 if (!readableUri.isAbsolute) { | |
174 if (node == null) node = leg.NO_LOCATION_SPANNABLE; | |
175 internalError(node, | |
176 'Relative uri $readableUri provided to readScript(Uri).'); | |
177 } | |
178 | |
179 // We need to store the current element since we are reporting read errors | |
180 // asynchronously and therefore need to restore the current element for | |
181 // [node] to be valid. | |
182 elements.Element element = currentElement; | |
183 void reportReadError(exception) { | |
184 withCurrentElement(element, () { | |
185 reportError(node, | |
186 leg.MessageKind.READ_SCRIPT_ERROR, | |
187 {'uri': readableUri, 'exception': exception}); | |
188 }); | |
189 } | |
190 | |
191 Uri resourceUri = translateUri(node, readableUri); | |
192 // TODO(johnniwinther): Wrap the result from [provider] in a specialized | |
193 // [Future] to ensure that we never execute an asynchronous action without | |
194 // setting up the current element of the compiler. | |
195 return new Future.sync(() => callUserProvider(resourceUri)).then((data) { | |
196 SourceFile sourceFile; | |
197 String resourceUriString = resourceUri.toString(); | |
198 if (data is List<int>) { | |
199 sourceFile = new Utf8BytesSourceFile(resourceUriString, data); | |
200 } else if (data is String) { | |
201 sourceFile = new StringSourceFile(resourceUriString, data); | |
202 } else { | |
203 String message = "Expected a 'String' or a 'List<int>' from the input " | |
204 "provider, but got: ${Error.safeToString(data)}."; | |
205 reportReadError(message); | |
206 } | |
207 // We use [readableUri] as the URI for the script since need to preserve | |
208 // the scheme in the script because [Script.uri] is used for resolving | |
209 // relative URIs mentioned in the script. See the comment on | |
210 // [LibraryLoader] for more details. | |
211 return new leg.Script(readableUri, resourceUri, sourceFile); | |
212 }).catchError((error) { | |
213 reportReadError(error); | |
214 return null; | |
215 }); | |
216 } | |
217 | |
218 /** | |
219 * Translates a readable URI into a resource URI. | |
220 * | |
221 * See [LibraryLoader] for terminology on URIs. | |
222 */ | |
223 Uri translateUri(leg.Spannable node, Uri readableUri) { | |
224 switch (readableUri.scheme) { | |
225 case 'package': return translatePackageUri(node, readableUri); | |
226 default: return readableUri; | |
227 } | |
228 } | |
229 | |
230 Uri translateDartUri(elements.LibraryElement importingLibrary, | |
231 Uri resolvedUri, tree.Node node) { | |
232 LibraryInfo libraryInfo = LIBRARIES[resolvedUri.path]; | |
233 String path = lookupLibraryPath(resolvedUri.path); | |
234 if (libraryInfo != null && | |
235 libraryInfo.category == "Internal") { | |
236 bool allowInternalLibraryAccess = false; | |
237 if (importingLibrary != null) { | |
238 if (importingLibrary.isPlatformLibrary || importingLibrary.isPatch) { | |
239 allowInternalLibraryAccess = true; | |
240 } else if (importingLibrary.canonicalUri.path.contains( | |
241 'dart/tests/compiler/dart2js_native')) { | |
242 allowInternalLibraryAccess = true; | |
243 } | |
244 } | |
245 if (!allowInternalLibraryAccess) { | |
246 if (importingLibrary != null) { | |
247 reportError( | |
248 node, | |
249 leg.MessageKind.INTERNAL_LIBRARY_FROM, | |
250 {'resolvedUri': resolvedUri, | |
251 'importingUri': importingLibrary.canonicalUri}); | |
252 } else { | |
253 reportError( | |
254 node, | |
255 leg.MessageKind.INTERNAL_LIBRARY, | |
256 {'resolvedUri': resolvedUri}); | |
257 } | |
258 } | |
259 } | |
260 if (path == null) { | |
261 reportError(node, leg.MessageKind.LIBRARY_NOT_FOUND, | |
262 {'resolvedUri': resolvedUri}); | |
263 return null; | |
264 } | |
265 if (resolvedUri.path == 'html' || | |
266 resolvedUri.path == 'io') { | |
267 // TODO(ahe): Get rid of mockableLibraryUsed when test.dart | |
268 // supports this use case better. | |
269 mockableLibraryUsed = true; | |
270 } | |
271 return libraryRoot.resolve(path); | |
272 } | |
273 | |
274 Uri resolvePatchUri(String dartLibraryPath) { | |
275 String patchPath = lookupPatchPath(dartLibraryPath); | |
276 if (patchPath == null) return null; | |
277 return libraryRoot.resolve(patchPath); | |
278 } | |
279 | |
280 Uri translatePackageUri(leg.Spannable node, Uri uri) { | |
281 if (packageRoot == null) { | |
282 reportFatalError( | |
283 node, leg.MessageKind.PACKAGE_ROOT_NOT_SET, {'uri': uri}); | |
284 } | |
285 return packageRoot.resolve(uri.path); | |
286 } | |
287 | |
288 Future<bool> run(Uri uri) { | |
289 log('Allowed library categories: $allowedLibraryCategories'); | |
290 return super.run(uri).then((bool success) { | |
291 int cumulated = 0; | |
292 for (final task in tasks) { | |
293 int elapsed = task.timing; | |
294 if (elapsed != 0) { | |
295 cumulated += elapsed; | |
296 log('${task.name} took ${elapsed}msec'); | |
297 } | |
298 } | |
299 int total = totalCompileTime.elapsedMilliseconds; | |
300 log('Total compile-time ${total}msec;' | |
301 ' unaccounted ${total - cumulated}msec'); | |
302 return success; | |
303 }); | |
304 } | |
305 | |
306 void reportDiagnostic(leg.Spannable node, | |
307 leg.Message message, | |
308 api.Diagnostic kind) { | |
309 leg.SourceSpan span = spanFromSpannable(node); | |
310 if (identical(kind, api.Diagnostic.ERROR) | |
311 || identical(kind, api.Diagnostic.CRASH)) { | |
312 compilationFailed = true; | |
313 } | |
314 // [:span.uri:] might be [:null:] in case of a [Script] with no [uri]. For | |
315 // instance in the [Types] constructor in typechecker.dart. | |
316 if (span == null || span.uri == null) { | |
317 callUserHandler(null, null, null, '$message', kind); | |
318 } else { | |
319 callUserHandler( | |
320 translateUri(null, span.uri), span.begin, span.end, '$message', kind); | |
321 } | |
322 } | |
323 | |
324 bool get isMockCompilation { | |
325 return mockableLibraryUsed | |
326 && (options.indexOf('--allow-mock-compilation') != -1); | |
327 } | |
328 | |
329 void callUserHandler(Uri uri, int begin, int end, | |
330 String message, api.Diagnostic kind) { | |
331 try { | |
332 userHandlerTask.measure(() { | |
333 handler(uri, begin, end, message, kind); | |
334 }); | |
335 } catch (ex, s) { | |
336 diagnoseCrashInUserCode( | |
337 'Uncaught exception in diagnostic handler', ex, s); | |
338 rethrow; | |
339 } | |
340 } | |
341 | |
342 Future callUserProvider(Uri uri) { | |
343 try { | |
344 return userProviderTask.measure(() => provider(uri)); | |
345 } catch (ex, s) { | |
346 diagnoseCrashInUserCode('Uncaught exception in input provider', ex, s); | |
347 rethrow; | |
348 } | |
349 } | |
350 | |
351 void diagnoseCrashInUserCode(String message, exception, stackTrace) { | |
352 hasCrashed = true; | |
353 print('$message: ${tryToString(exception)}'); | |
354 print(tryToString(stackTrace)); | |
355 } | |
356 | |
357 fromEnvironment(String name) => environment[name]; | |
358 } | |
OLD | NEW |