OLD | NEW |
---|---|
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /// Source information system mapping that attempts a semantic mapping between | 5 /// Source information system mapping that attempts a semantic mapping between |
6 /// offsets of JavaScript code points to offsets of Dart code points. | 6 /// offsets of JavaScript code points to offsets of Dart code points. |
7 | 7 |
8 library dart2js.source_information.position; | 8 library dart2js.source_information.position; |
9 | 9 |
10 import '../common.dart'; | 10 import '../common.dart'; |
11 import '../elements/elements.dart' show | 11 import '../elements/elements.dart' show |
12 AstElement, | 12 AstElement, |
13 LocalElement; | 13 LocalElement; |
14 import '../js/js.dart' as js; | 14 import '../js/js.dart' as js; |
15 import '../js/js_source_mapping.dart'; | 15 import '../js/js_source_mapping.dart'; |
16 import '../js/js_debug.dart'; | 16 import '../js/js_debug.dart'; |
17 import '../tree/tree.dart' show | 17 import '../tree/tree.dart' show |
18 Node, | 18 Node, |
19 Send; | 19 Send; |
20 | 20 |
21 import 'code_output.dart' show | |
22 CodeBuffer; | |
21 import 'source_file.dart'; | 23 import 'source_file.dart'; |
22 import 'source_information.dart'; | 24 import 'source_information.dart'; |
23 | 25 |
24 /// [SourceInformation] that consists of an offset position into the source | 26 /// [SourceInformation] that consists of an offset position into the source |
25 /// code. | 27 /// code. |
26 class PositionSourceInformation extends SourceInformation { | 28 class PositionSourceInformation extends SourceInformation { |
27 @override | 29 @override |
28 final SourceLocation startPosition; | 30 final SourceLocation startPosition; |
29 | 31 |
30 @override | 32 @override |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
106 | 108 |
107 @override | 109 @override |
108 SourceInformationBuilder createBuilderForContext(AstElement element) { | 110 SourceInformationBuilder createBuilderForContext(AstElement element) { |
109 return new PositionSourceInformationBuilder(element); | 111 return new PositionSourceInformationBuilder(element); |
110 } | 112 } |
111 | 113 |
112 @override | 114 @override |
113 SourceInformationProcessor createProcessor(SourceMapper mapper) { | 115 SourceInformationProcessor createProcessor(SourceMapper mapper) { |
114 return new PositionSourceInformationProcessor(mapper); | 116 return new PositionSourceInformationProcessor(mapper); |
115 } | 117 } |
118 | |
119 @override | |
120 void onComplete() {} | |
121 | |
122 @override | |
123 SourceInformation buildSourceMappedMarker() { | |
124 return const SourceMappedMarker(); | |
125 } | |
126 } | |
127 | |
128 /// Marker used to tag the root nodes of source-mapped code. | |
129 /// | |
130 /// This is needed to be able to distinguish JavaScript nodes that shouldn't | |
131 /// have source locations (like the premable) from the nodes that should | |
132 /// (like functions compiled from Dart code). | |
133 class SourceMappedMarker extends SourceInformation { | |
134 const SourceMappedMarker(); | |
135 | |
136 @override | |
137 String get shortText => ''; | |
138 | |
139 @override | |
140 List<SourceLocation> get sourceLocations => const <SourceLocation>[]; | |
141 | |
142 @override | |
143 SourceSpan get sourceSpan => new SourceSpan(null, null, null); | |
116 } | 144 } |
117 | 145 |
118 /// [SourceInformationBuilder] that generates [PositionSourceInformation]. | 146 /// [SourceInformationBuilder] that generates [PositionSourceInformation]. |
119 class PositionSourceInformationBuilder implements SourceInformationBuilder { | 147 class PositionSourceInformationBuilder implements SourceInformationBuilder { |
120 final SourceFile sourceFile; | 148 final SourceFile sourceFile; |
121 final String name; | 149 final String name; |
122 | 150 |
123 PositionSourceInformationBuilder(AstElement element) | 151 PositionSourceInformationBuilder(AstElement element) |
124 : sourceFile = element.implementation.compilationUnit.script.file, | 152 : sourceFile = element.implementation.compilationUnit.script.file, |
125 name = computeElementNameForSourceMaps(element); | 153 name = computeElementNameForSourceMaps(element); |
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
218 } | 246 } |
219 } | 247 } |
220 | 248 |
221 /// The start, end and closing offsets for a [js.Node]. | 249 /// The start, end and closing offsets for a [js.Node]. |
222 class CodePosition { | 250 class CodePosition { |
223 final int startPosition; | 251 final int startPosition; |
224 final int endPosition; | 252 final int endPosition; |
225 final int closingPosition; | 253 final int closingPosition; |
226 | 254 |
227 CodePosition(this.startPosition, this.endPosition, this.closingPosition); | 255 CodePosition(this.startPosition, this.endPosition, this.closingPosition); |
256 | |
257 int getPosition(CodePositionKind kind) { | |
258 switch (kind) { | |
259 case CodePositionKind.START: | |
260 return startPosition; | |
261 case CodePositionKind.END: | |
262 return endPosition; | |
263 case CodePositionKind.CLOSING: | |
264 return closingPosition; | |
265 } | |
266 } | |
267 | |
268 String toString() { | |
269 return 'CodePosition(start=$startPosition,' | |
270 'end=$endPosition,closing=$closingPosition)'; | |
271 } | |
272 } | |
273 | |
274 /// A map from a [js.Node] to its [CodePosition]. | |
275 abstract class CodePositionMap { | |
276 CodePosition operator [](js.Node node); | |
228 } | 277 } |
229 | 278 |
230 /// Registry for mapping [js.Node]s to their [CodePosition]. | 279 /// Registry for mapping [js.Node]s to their [CodePosition]. |
231 class CodePositionRecorder { | 280 class CodePositionRecorder implements CodePositionMap { |
232 Map<js.Node, CodePosition> _codePositionMap = | 281 Map<js.Node, CodePosition> _codePositionMap = |
233 new Map<js.Node, CodePosition>.identity(); | 282 new Map<js.Node, CodePosition>.identity(); |
234 | 283 |
235 void registerPositions(js.Node node, | 284 void registerPositions(js.Node node, |
236 int startPosition, | 285 int startPosition, |
237 int endPosition, | 286 int endPosition, |
238 int closingPosition) { | 287 int closingPosition) { |
239 registerCodePosition(node, | 288 registerCodePosition(node, |
240 new CodePosition(startPosition, endPosition, closingPosition)); | 289 new CodePosition(startPosition, endPosition, closingPosition)); |
241 } | 290 } |
242 | 291 |
243 void registerCodePosition(js.Node node, CodePosition codePosition) { | 292 void registerCodePosition(js.Node node, CodePosition codePosition) { |
244 _codePositionMap[node] = codePosition; | 293 _codePositionMap[node] = codePosition; |
245 } | 294 } |
246 | 295 |
247 CodePosition operator [](js.Node node) => _codePositionMap[node]; | 296 CodePosition operator [](js.Node node) => _codePositionMap[node]; |
248 } | 297 } |
249 | 298 |
299 /// Enum values for the part of a Dart node used for the source location offset. | |
250 enum SourcePositionKind { | 300 enum SourcePositionKind { |
301 /// The source mapping should point to the start of the Dart node. | |
302 /// | |
303 /// For instance the first '(' for the `(*)()` call and 'f' of both the | |
304 /// `foo()` and the `*.bar()` call: | |
305 /// | |
306 /// (foo().bar())() | |
307 /// ^ // the start of the `(*)()` node | |
308 /// ^ // the start of the `foo()` node | |
309 /// ^ // the start of the `*.bar()` node | |
310 /// | |
251 START, | 311 START, |
312 | |
313 /// The source mapping should point an inner position of the Dart node. | |
314 /// | |
315 /// For instance the second '(' of the `(*)()` call, the 'f' of the `foo()` | |
316 /// call and the 'b' of the `*.bar()` call: | |
317 /// | |
318 /// (foo().bar())() | |
319 /// ^ // the inner position of the `(*)()` node | |
320 /// ^ // the inner position of the `foo()` node | |
321 /// ^ // the inner position of the `*.bar()` node | |
322 /// | |
323 /// For function expressions the inner position is the closing brace or the | |
324 /// arrow: | |
325 /// | |
326 /// foo() => () {} | |
327 /// ^ // the inner position of the 'foo' function | |
328 /// ^ // the inner position of the closure | |
329 /// | |
330 INNER, | |
331 } | |
332 | |
333 SourceLocation getSourceLocation( | |
334 SourceInformation sourceInformation, | |
335 [SourcePositionKind sourcePositionKind = SourcePositionKind.START]) { | |
336 if (sourceInformation == null) return null; | |
337 switch (sourcePositionKind) { | |
338 case SourcePositionKind.START: | |
339 return sourceInformation.startPosition; | |
340 case SourcePositionKind.INNER: | |
341 return sourceInformation.closingPosition; | |
342 } | |
343 } | |
344 | |
345 /// Enum values for the part of the JavaScript node used for the JavaScript | |
346 /// code offset of a source mapping. | |
347 enum CodePositionKind { | |
348 /// The source mapping is put on left-most offset of the node. | |
349 /// | |
350 /// For instance on the 'f' of a function or 'r' of a return statement: | |
351 /// | |
352 /// foo: function() { return 0; } | |
353 /// ^ // the function start position | |
354 /// ^ // the return start position | |
355 START, | |
356 | |
357 /// The source mapping is put on the closing token. | |
358 /// | |
359 /// For instance on the '}' of a function or the ';' of a return statement: | |
360 /// | |
361 /// foo: function() { return 0; } | |
362 /// ^ // the function closing position | |
363 /// ^ // the return closing position | |
364 /// | |
252 CLOSING, | 365 CLOSING, |
253 END, | 366 |
254 } | 367 /// The source mapping is put at the end of the code for the node. |
255 | 368 /// |
256 enum CodePositionKind { | 369 /// For instance after '}' of a function or after the ';' of a return |
257 START, | 370 /// statement: |
258 CLOSING, | 371 /// |
372 /// foo: function() { return 0; } | |
373 /// ^ // the function end position | |
374 /// ^ // the return end position | |
375 /// | |
259 END, | 376 END, |
260 } | 377 } |
261 | 378 |
262 /// Processor that associates [SourceLocation]s from [SourceInformation] on | 379 /// Processor that associates [SourceLocation]s from [SourceInformation] on |
263 /// [js.Node]s with the target offsets in a [SourceMapper]. | 380 /// [js.Node]s with the target offsets in a [SourceMapper]. |
264 class PositionSourceInformationProcessor | 381 class PositionSourceInformationProcessor implements SourceInformationProcessor { |
265 extends js.BaseVisitor implements SourceInformationProcessor { | 382 final CodePositionRecorder codePositionRecorder = new CodePositionRecorder(); |
266 final CodePositionRecorder codePositions = new CodePositionRecorder(); | 383 CodePositionMap codePositionMap; |
267 final SourceMapper sourceMapper; | 384 List<TraceListener> traceListeners; |
268 | 385 |
269 PositionSourceInformationProcessor(this.sourceMapper); | 386 PositionSourceInformationProcessor( |
270 | 387 SourceMapper sourceMapper, |
271 void process(js.Node node) { | 388 [Coverage coverage]) { |
272 node.accept(this); | 389 codePositionMap = coverage != null |
273 } | 390 ? new CodePositionCoverage(codePositionRecorder, coverage) |
274 | 391 : codePositionRecorder; |
275 void visitChildren(js.Node node) { | 392 traceListeners = [new PositionTraceListener(sourceMapper)]; |
276 node.visitChildren(this); | 393 if (coverage != null) { |
277 } | 394 traceListeners.add(new CoverageListener(coverage)); |
278 | 395 } |
279 CodePosition getCodePosition(js.Node node) { | 396 } |
280 return codePositions[node]; | 397 |
281 } | 398 void process(js.Node node, CodeBuffer codeBuffer) { |
282 | 399 new JavaScriptTracer(codePositionMap, traceListeners).apply(node); |
283 /// Associates [sourceInformation] with the JavaScript [node]. | |
284 /// | |
285 /// The offset into the JavaScript code is computed by pulling the | |
286 /// [codePositionKind] from the code positions associated with | |
287 /// [codePositionNode]. | |
288 /// | |
289 /// The mapped Dart source location is computed by pulling the | |
290 /// [sourcePositionKind] source location from [sourceInformation]. | |
291 void apply(js.Node node, | |
292 js.Node codePositionNode, | |
293 CodePositionKind codePositionKind, | |
294 SourceInformation sourceInformation, | |
295 SourcePositionKind sourcePositionKind) { | |
296 if (sourceInformation != null) { | |
297 CodePosition codePosition = getCodePosition(codePositionNode); | |
298 // We should always have recorded the needed code positions. | |
299 assert(invariant( | |
300 NO_LOCATION_SPANNABLE, | |
301 codePosition != null, | |
302 message: | |
303 "Code position missing for " | |
304 "${nodeToString(codePositionNode)}:\n" | |
305 "${DebugPrinter.prettyPrint(node)}")); | |
306 if (codePosition == null) return; | |
307 int codeLocation; | |
308 SourceLocation sourceLocation; | |
309 switch (codePositionKind) { | |
310 case CodePositionKind.START: | |
311 codeLocation = codePosition.startPosition; | |
312 break; | |
313 case CodePositionKind.CLOSING: | |
314 codeLocation = codePosition.closingPosition; | |
315 break; | |
316 case CodePositionKind.END: | |
317 codeLocation = codePosition.endPosition; | |
318 break; | |
319 } | |
320 switch (sourcePositionKind) { | |
321 case SourcePositionKind.START: | |
322 sourceLocation = sourceInformation.startPosition; | |
323 break; | |
324 case SourcePositionKind.CLOSING: | |
325 sourceLocation = sourceInformation.closingPosition; | |
326 break; | |
327 case SourcePositionKind.END: | |
328 sourceLocation = sourceInformation.endPosition; | |
329 break; | |
330 } | |
331 if (codeLocation != null && sourceLocation != null) { | |
332 sourceMapper.register(node, codeLocation, sourceLocation); | |
333 } | |
334 } | |
335 } | |
336 | |
337 @override | |
338 visitNode(js.Node node) { | |
339 SourceInformation sourceInformation = node.sourceInformation; | |
340 if (sourceInformation != null) { | |
341 /// Associates the left-most position of the JS code with the left-most | |
342 /// position of the Dart code. | |
343 apply(node, | |
344 node, CodePositionKind.START, | |
345 sourceInformation, SourcePositionKind.START); | |
346 } | |
347 visitChildren(node); | |
348 } | |
349 | |
350 @override | |
351 visitFun(js.Fun node) { | |
352 SourceInformation sourceInformation = node.sourceInformation; | |
353 if (sourceInformation != null) { | |
354 /// Associates the end brace of the JavaScript function with the end brace | |
355 /// of the Dart function (or the `;` in case of arrow notation). | |
356 apply(node, | |
357 node, CodePositionKind.CLOSING, | |
358 sourceInformation, SourcePositionKind.CLOSING); | |
359 } | |
360 | |
361 visitChildren(node); | |
362 } | |
363 | |
364 @override | |
365 visitExpressionStatement(js.ExpressionStatement node) { | |
366 visitChildren(node); | |
367 } | |
368 | |
369 @override | |
370 visitBinary(js.Binary node) { | |
371 visitChildren(node); | |
372 } | |
373 | |
374 @override | |
375 visitAccess(js.PropertyAccess node) { | |
376 visitChildren(node); | |
377 } | |
378 | |
379 @override | |
380 visitCall(js.Call node) { | |
381 SourceInformation sourceInformation = node.sourceInformation; | |
382 if (sourceInformation != null) { | |
383 if (node.target is js.PropertyAccess) { | |
384 js.PropertyAccess access = node.target; | |
385 js.Node target = access; | |
386 bool pureAccess = false; | |
387 while (target is js.PropertyAccess) { | |
388 js.PropertyAccess targetAccess = target; | |
389 if (targetAccess.receiver is js.VariableUse || | |
390 targetAccess.receiver is js.This) { | |
391 pureAccess = true; | |
392 break; | |
393 } else { | |
394 target = targetAccess.receiver; | |
395 } | |
396 } | |
397 if (pureAccess) { | |
398 // a.m() this.m() a.b.c.d.m() | |
399 // ^ ^ ^ | |
400 apply( | |
401 node, | |
402 node, | |
403 CodePositionKind.START, | |
404 sourceInformation, | |
405 SourcePositionKind.START); | |
406 } else { | |
407 // *.m() *.a.b.c.d.m() | |
408 // ^ ^ | |
409 apply( | |
410 node, | |
411 access.selector, | |
412 CodePositionKind.START, | |
413 sourceInformation, | |
414 SourcePositionKind.CLOSING); | |
415 } | |
416 } else if (node.target is js.VariableUse) { | |
417 // m() | |
418 // ^ | |
419 apply( | |
420 node, | |
421 node, | |
422 CodePositionKind.START, | |
423 sourceInformation, | |
424 SourcePositionKind.START); | |
425 } else if (node.target is js.Fun || node.target is js.New) { | |
426 // function(){}() new Function("...")() | |
427 // ^ ^ | |
428 apply( | |
429 node, | |
430 node.target, | |
431 CodePositionKind.END, | |
432 sourceInformation, | |
433 SourcePositionKind.CLOSING); | |
434 } else { | |
435 assert(invariant(NO_LOCATION_SPANNABLE, false, | |
436 message: "Unexpected property access ${nodeToString(node)}:\n" | |
437 "${DebugPrinter.prettyPrint(node)}")); | |
438 // Don't know.... | |
439 apply( | |
440 node, | |
441 node, | |
442 CodePositionKind.START, | |
443 sourceInformation, | |
444 SourcePositionKind.START); | |
445 } | |
446 } | |
447 visitChildren(node); | |
448 } | 400 } |
449 | 401 |
450 @override | 402 @override |
451 void onPositions(js.Node node, | 403 void onPositions(js.Node node, |
452 int startPosition, | 404 int startPosition, |
453 int endPosition, | 405 int endPosition, |
454 int closingPosition) { | 406 int closingPosition) { |
455 codePositions.registerPositions( | 407 codePositionRecorder.registerPositions( |
456 node, startPosition, endPosition, closingPosition); | 408 node, startPosition, endPosition, closingPosition); |
457 } | 409 } |
458 } | 410 } |
411 | |
412 /// [TraceListener] that register [SourceLocation]s with a [SourceMapper]. | |
413 class PositionTraceListener extends TraceListener { | |
414 final SourceMapper sourceMapper; | |
415 | |
416 PositionTraceListener(this.sourceMapper); | |
417 | |
418 @override | |
419 void onStep(js.Node node, Offset offset, StepKind kind) { | |
420 SourceInformation sourceInformation = node.sourceInformation; | |
421 if (sourceInformation == null) return; | |
422 | |
423 SourcePositionKind sourcePositionKind = SourcePositionKind.START; | |
424 switch (kind) { | |
425 case StepKind.FUN: | |
426 sourcePositionKind = SourcePositionKind.INNER; | |
427 break; | |
428 case StepKind.CALL: | |
429 CallPosition callPosition = | |
430 CallPosition.getSemanticPositionForCall(node); | |
431 sourcePositionKind = callPosition.sourcePositionKind; | |
432 break; | |
433 case StepKind.NEW: | |
434 case StepKind.RETURN: | |
435 case StepKind.BREAK: | |
436 case StepKind.CONTINUE: | |
437 case StepKind.THROW: | |
438 case StepKind.EXPRESSION_STATEMENT: | |
439 case StepKind.IF_CONDITION: | |
440 case StepKind.FOR_INITIALIZER: | |
441 case StepKind.FOR_CONDITION: | |
442 case StepKind.FOR_UPDATE: | |
443 case StepKind.WHILE_CONDITION: | |
444 case StepKind.DO_CONDITION: | |
445 case StepKind.SWITCH_EXPRESSION: | |
446 break; | |
447 } | |
448 int codeLocation = offset.codeOffset; | |
449 SourceLocation sourceLocation = | |
450 getSourceLocation(sourceInformation, sourcePositionKind); | |
451 if (codeLocation != null && sourceLocation != null) { | |
452 sourceMapper.register(node, codeLocation, sourceLocation); | |
453 } | |
454 } | |
455 } | |
456 | |
457 /// The position of a [js.Call] node. | |
458 class CallPosition { | |
459 final js.Node node; | |
460 final CodePositionKind codePositionKind; | |
461 final SourcePositionKind sourcePositionKind; | |
462 | |
463 CallPosition(this.node, this.codePositionKind, this.sourcePositionKind); | |
464 | |
465 /// Computes the [CallPosition] for [node]. | |
466 static CallPosition getSemanticPositionForCall(js.Call node) { | |
467 if (node.target is js.PropertyAccess) { | |
468 js.PropertyAccess access = node.target; | |
469 js.Node target = access; | |
470 bool pureAccess = false; | |
471 while (target is js.PropertyAccess) { | |
472 js.PropertyAccess targetAccess = target; | |
473 if (targetAccess.receiver is js.VariableUse || | |
474 targetAccess.receiver is js.This) { | |
475 pureAccess = true; | |
476 break; | |
477 } else { | |
478 target = targetAccess.receiver; | |
479 } | |
480 } | |
481 if (pureAccess) { | |
482 // a.m() this.m() a.b.c.d.m() | |
483 // ^ ^ ^ | |
484 return new CallPosition( | |
485 node, | |
486 CodePositionKind.START, | |
487 SourcePositionKind.START); | |
488 } else { | |
489 // *.m() *.a.b.c.d.m() | |
490 // ^ ^ | |
491 return new CallPosition( | |
492 access.selector, | |
493 CodePositionKind.START, | |
494 SourcePositionKind.INNER); | |
495 } | |
496 } else if (node.target is js.VariableUse) { | |
497 // m() | |
498 // ^ | |
499 return new CallPosition( | |
500 node, | |
501 CodePositionKind.START, | |
502 SourcePositionKind.START); | |
503 } else if (node.target is js.Fun || node.target is js.New) { | |
504 // function(){}() new Function("...")() | |
505 // ^ ^ | |
506 return new CallPosition( | |
507 node.target, | |
508 CodePositionKind.END, | |
509 SourcePositionKind.INNER); | |
510 } else if (node.target is js.Binary || node.target is js.Call) { | |
511 // (0,a)() m()() | |
512 // ^ ^ | |
513 return new CallPosition( | |
514 node.target, | |
515 CodePositionKind.END, | |
516 SourcePositionKind.INNER); | |
517 } else { | |
518 assert(invariant(NO_LOCATION_SPANNABLE, false, | |
519 message: "Unexpected property access ${nodeToString(node)}:\n" | |
520 "${DebugPrinter.prettyPrint(node)}")); | |
521 // Don't know.... | |
522 return new CallPosition( | |
523 node, | |
524 CodePositionKind.START, | |
525 SourcePositionKind.START); | |
526 } | |
527 } | |
528 } | |
529 | |
530 class Offset { | |
531 /// The offset of the enclosing statement relative to the beginning of the | |
532 /// file. | |
533 /// | |
534 /// For instance: | |
535 /// | |
536 /// foo().bar(baz()); | |
537 /// ^ // the statement offset of the `foo()` call | |
538 /// ^ // the statement offset of the `*.bar()` call | |
539 /// ^ // the statement offset of the `baz()` call | |
540 /// | |
541 final int statementOffset; | |
542 | |
543 /// The `subexpression` offset of the step. This is the (mostly) unique | |
544 /// offset relative to the beginning of the file, that identifies the | |
545 /// current of execution. | |
546 /// | |
547 /// For instance: | |
548 /// | |
549 /// foo().bar(baz()); | |
550 /// ^ // the subexpression offset of the `foo()` call | |
551 /// ^ // the subexpression offset of the `*.bar()` call | |
552 /// ^ // the subexpression offset of the `baz()` call | |
553 /// | |
554 /// Here, even though the JavaScript node for the `*.bar()` call contains | |
555 /// the `foo()` its execution is identified by the `bar` identifier more than | |
556 /// the foo identifier. | |
557 /// | |
558 final int codeOffset; | |
559 | |
560 /// The `left-to-right` offset of the step. This is a restricted version of | |
561 /// [subexpressionOffset] where offset by the execution order are always | |
Siggi Cherem (dart-lang)
2016/01/22 19:32:58
A few notes:
- [subexpressionOffset] => [codeOffs
Johnni Winther
2016/01/25 09:58:55
Done.
| |
562 /// increasing within the same statement. | |
563 /// | |
564 /// For instance: | |
565 /// | |
566 /// foo().bar(baz()); | |
567 /// ^ // the left-to-right offset of the `foo()` call | |
568 /// ^ // the left-to-right offset of the `*.bar()` call | |
569 /// ^ // the left-to-right offset of the `baz()` call | |
570 /// | |
571 /// Here, `baz()` is executed before `foo()` be needs to use 'f' as its best | |
Siggi Cherem (dart-lang)
2016/01/22 19:32:58
nit: "be needs" => "so we need to"?
Johnni Winther
2016/01/25 09:58:55
Done.
| |
572 /// position under the restriction. | |
573 /// | |
574 final int leftToRightOffset; | |
Siggi Cherem (dart-lang)
2016/01/22 19:32:58
another idea for the name here: executionOrderOffs
Johnni Winther
2016/01/25 09:58:55
I find that equally confusing :(
| |
575 | |
576 Offset(this.statementOffset, this.leftToRightOffset, this.codeOffset); | |
577 | |
578 String toString() { | |
579 return 'Offset[statementOffset=$statementOffset,' | |
580 'leftToRightOffset=$leftToRightOffset,' | |
581 'codeOffset=$codeOffset]'; | |
582 } | |
583 } | |
584 | |
585 enum BranchKind { | |
586 CONDITION, | |
587 LOOP, | |
588 CATCH, | |
589 FINALLY, | |
590 CASE, | |
591 } | |
592 | |
593 enum StepKind { | |
594 FUN, | |
595 CALL, | |
596 NEW, | |
597 RETURN, | |
598 BREAK, | |
599 CONTINUE, | |
600 THROW, | |
601 EXPRESSION_STATEMENT, | |
602 IF_CONDITION, | |
603 FOR_INITIALIZER, | |
604 FOR_CONDITION, | |
605 FOR_UPDATE, | |
606 WHILE_CONDITION, | |
607 DO_CONDITION, | |
608 SWITCH_EXPRESSION, | |
609 } | |
610 | |
611 /// Listener for the [JavaScriptTracer]. | |
612 abstract class TraceListener { | |
613 /// Called before [root] node is procesed by the [JavaScriptTracer]. | |
614 void onStart(js.Node root) {} | |
615 | |
616 /// Called after [root] node has been procesed by the [JavaScriptTracer]. | |
617 void onEnd(js.Node root) {} | |
618 | |
619 /// Called when a branch of the given [kind] is started. [value] is provided | |
620 /// to distinguish true/false branches of [BranchKind.CONDITION] and cases of | |
621 /// [Branch.CASE]. | |
622 void pushBranch(BranchKind kind, [value]) {} | |
623 | |
624 /// Called when the current branch ends. | |
625 void popBranch() {} | |
626 | |
627 /// Called when [node] defines a step of the given [kind] at the given | |
628 /// [offset] when the generated JavaScript code. | |
629 void onStep(js.Node node, Offset offset, StepKind kind) {} | |
630 } | |
631 | |
632 /// Visitor that computes the [js.Node]s the are part of the JavaScript | |
633 /// steppable execution and thus needs source mapping locations. | |
634 class JavaScriptTracer extends js.BaseVisitor { | |
635 final CodePositionMap codePositions; | |
636 final List<TraceListener> listeners; | |
637 | |
638 /// The steps added by subexpressions. | |
639 List steps = []; | |
640 | |
641 /// The offset of the current statement. | |
642 int statementOffset; | |
643 | |
644 /// The current offset in left-to-right progression. | |
645 int leftToRightOffset; | |
646 | |
647 /// The offset of the surrounding statement, used for the first subexpression. | |
648 int offsetPosition; | |
649 | |
650 bool active; | |
651 | |
652 JavaScriptTracer(this.codePositions, | |
653 this.listeners, | |
654 {this.active: false}); | |
655 | |
656 void notifyStart(js.Node node) { | |
657 listeners.forEach((listener) => listener.onStart(node)); | |
658 } | |
659 | |
660 void notifyEnd(js.Node node) { | |
661 listeners.forEach((listener) => listener.onEnd(node)); | |
662 } | |
663 | |
664 void notifyPushBranch(BranchKind kind, [value]) { | |
665 if (active) { | |
666 listeners.forEach((listener) => listener.pushBranch(kind, value)); | |
667 } | |
668 } | |
669 | |
670 void notifyPopBranch() { | |
671 if (active) { | |
672 listeners.forEach((listener) => listener.popBranch()); | |
673 } | |
674 } | |
675 | |
676 void notifyStep(js.Node node, Offset offset, StepKind kind) { | |
677 if (active) { | |
678 listeners.forEach((listener) => listener.onStep(node, offset, kind)); | |
679 } | |
680 } | |
681 | |
682 void apply(js.Node node) { | |
683 notifyStart(node); | |
684 node.accept(this); | |
685 notifyEnd(node); | |
686 } | |
687 | |
688 @override | |
689 visitNode(js.Node node) { | |
690 node.visitChildren(this); | |
691 } | |
692 | |
693 visit(js.Node node, [BranchKind branch, value]) { | |
694 if (node != null) { | |
695 if (branch != null) { | |
696 notifyPushBranch(branch, value); | |
697 node.accept(this); | |
698 notifyPopBranch(); | |
699 } else { | |
700 node.accept(this); | |
701 } | |
702 } | |
703 } | |
704 | |
705 visitList(List<js.Node> nodeList) { | |
706 if (nodeList != null) { | |
707 for (js.Node node in nodeList) { | |
708 visit(node); | |
709 } | |
710 } | |
711 } | |
712 | |
713 @override | |
714 visitFun(js.Fun node) { | |
715 bool activeBefore = active; | |
716 if (!active) { | |
717 active = node.sourceInformation != null; | |
718 } | |
719 visit(node.body); | |
720 leftToRightOffset = statementOffset = | |
721 getSyntaxOffset(node, kind: CodePositionKind.CLOSING); | |
722 Offset offset = getOffsetForNode(node, statementOffset); | |
723 notifyStep(node, offset, StepKind.FUN); | |
724 active = activeBefore; | |
725 } | |
726 | |
727 @override | |
728 visitBlock(js.Block node) { | |
729 for (js.Statement statement in node.statements) { | |
730 visit(statement); | |
731 } | |
732 } | |
733 | |
734 int getSyntaxOffset(js.Node node, | |
735 {CodePositionKind kind: CodePositionKind.START}) { | |
736 CodePosition codePosition = codePositions[node]; | |
737 if (codePosition != null) { | |
738 return codePosition.getPosition(kind); | |
739 } | |
740 return null; | |
741 } | |
742 | |
743 visitSubexpression(js.Node parent, | |
744 js.Expression child, | |
745 int codeOffset, | |
746 StepKind kind) { | |
747 var oldSteps = steps; | |
748 steps = []; | |
749 offsetPosition = codeOffset; | |
750 visit(child); | |
751 if (steps.isEmpty) { | |
752 notifyStep(parent, | |
753 getOffsetForNode(parent, offsetPosition), | |
754 kind); | |
755 } | |
756 steps = oldSteps; | |
757 } | |
758 | |
759 @override | |
760 visitExpressionStatement(js.ExpressionStatement node) { | |
761 statementOffset = getSyntaxOffset(node); | |
762 visitSubexpression( | |
763 node, node.expression, statementOffset, | |
764 StepKind.EXPRESSION_STATEMENT); | |
765 statementOffset = null; | |
766 leftToRightOffset = null; | |
767 } | |
768 | |
769 @override | |
770 visitEmptyStatement(js.EmptyStatement node) {} | |
771 | |
772 @override | |
773 visitCall(js.Call node) { | |
774 visit(node.target); | |
775 int oldPosition = offsetPosition; | |
776 offsetPosition = null; | |
777 visitList(node.arguments); | |
778 offsetPosition = oldPosition; | |
779 CallPosition callPosition = | |
780 CallPosition.getSemanticPositionForCall(node); | |
781 js.Node positionNode = callPosition.node; | |
782 int callOffset = getSyntaxOffset( | |
783 positionNode, kind: callPosition.codePositionKind); | |
784 if (offsetPosition == null) { | |
785 offsetPosition = callOffset; | |
786 } | |
787 Offset offset = getOffsetForNode(positionNode, offsetPosition); | |
788 notifyStep(node, offset, StepKind.CALL); | |
789 steps.add(node); | |
790 offsetPosition = null; | |
791 } | |
792 | |
793 @override | |
794 visitNew(js.New node) { | |
795 visit(node.target); | |
796 visitList(node.arguments); | |
797 notifyStep( | |
798 node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.NEW); | |
799 steps.add(node); | |
800 offsetPosition = null; | |
801 } | |
802 | |
803 @override | |
804 visitAccess(js.PropertyAccess node) { | |
805 visit(node.receiver); | |
806 visit(node.selector); | |
807 } | |
808 | |
809 @override | |
810 visitVariableUse(js.VariableUse node) {} | |
811 | |
812 @override | |
813 visitLiteralBool(js.LiteralBool node) {} | |
814 | |
815 @override | |
816 visitLiteralString(js.LiteralString node) {} | |
817 | |
818 @override | |
819 visitLiteralNumber(js.LiteralNumber node) {} | |
820 | |
821 @override | |
822 visitLiteralNull(js.LiteralNull node) {} | |
823 | |
824 @override | |
825 visitName(js.Name node) {} | |
826 | |
827 @override | |
828 visitVariableDeclarationList(js.VariableDeclarationList node) { | |
829 visitList(node.declarations); | |
830 } | |
831 | |
832 @override | |
833 visitVariableDeclaration(js.VariableDeclaration node) {} | |
834 | |
835 @override | |
836 visitVariableInitialization(js.VariableInitialization node) { | |
837 visit(node.leftHandSide); | |
838 visit(node.value); | |
839 } | |
840 | |
841 @override | |
842 visitAssignment(js.Assignment node) { | |
843 visit(node.leftHandSide); | |
844 visit(node.value); | |
845 } | |
846 | |
847 @override | |
848 visitIf(js.If node) { | |
849 statementOffset = getSyntaxOffset(node); | |
850 visitSubexpression(node, node.condition, statementOffset, | |
851 StepKind.IF_CONDITION); | |
852 statementOffset = null; | |
853 visit(node.then, BranchKind.CONDITION, true); | |
854 visit(node.otherwise, BranchKind.CONDITION, false); | |
855 } | |
856 | |
857 @override | |
858 visitFor(js.For node) { | |
859 int offset = statementOffset = getSyntaxOffset(node); | |
860 statementOffset = offset; | |
861 leftToRightOffset = null; | |
862 if (node.init != null) { | |
863 visitSubexpression(node, node.init, getSyntaxOffset(node), | |
864 StepKind.FOR_INITIALIZER); | |
865 } | |
866 | |
867 if (node.condition != null) { | |
868 visitSubexpression(node, node.condition, getSyntaxOffset(node.condition), | |
869 StepKind.FOR_CONDITION); | |
870 } | |
871 | |
872 notifyPushBranch(BranchKind.LOOP); | |
873 visit(node.body); | |
874 | |
875 statementOffset = offset; | |
876 if (node.update != null) { | |
877 visitSubexpression(node, node.update, getSyntaxOffset(node.update), | |
878 StepKind.FOR_UPDATE); | |
879 } | |
880 | |
881 notifyPopBranch(); | |
882 } | |
883 | |
884 @override | |
885 visitWhile(js.While node) { | |
886 statementOffset = getSyntaxOffset(node); | |
887 if (node.condition != null) { | |
888 visitSubexpression(node, node.condition, getSyntaxOffset(node.condition), | |
889 StepKind.WHILE_CONDITION); | |
890 } | |
891 statementOffset = null; | |
892 leftToRightOffset = null; | |
893 | |
894 visit(node.body, BranchKind.LOOP); | |
895 } | |
896 | |
897 @override | |
898 visitDo(js.Do node) { | |
899 statementOffset = getSyntaxOffset(node); | |
900 visit(node.body); | |
901 if (node.condition != null) { | |
902 visitSubexpression(node, node.condition, getSyntaxOffset(node.condition), | |
903 StepKind.DO_CONDITION); | |
904 } | |
905 statementOffset = null; | |
906 leftToRightOffset = null; | |
907 } | |
908 | |
909 @override | |
910 visitBinary(js.Binary node) { | |
911 visit(node.left); | |
912 visit(node.right); | |
913 } | |
914 | |
915 @override | |
916 visitThis(js.This node) {} | |
917 | |
918 @override | |
919 visitReturn(js.Return node) { | |
920 statementOffset = getSyntaxOffset(node); | |
921 visit(node.value); | |
922 notifyStep( | |
923 node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.RETURN); | |
924 statementOffset = null; | |
925 leftToRightOffset = null; | |
926 } | |
927 | |
928 @override | |
929 visitThrow(js.Throw node) { | |
930 statementOffset = getSyntaxOffset(node); | |
931 visit(node.expression); | |
932 notifyStep( | |
933 node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.THROW); | |
934 statementOffset = null; | |
935 leftToRightOffset = null; | |
936 } | |
937 | |
938 @override | |
939 visitContinue(js.Continue node) { | |
940 statementOffset = getSyntaxOffset(node); | |
941 notifyStep( | |
942 node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.CONTINUE); | |
943 statementOffset = null; | |
944 leftToRightOffset = null; | |
945 } | |
946 | |
947 @override | |
948 visitBreak(js.Break node) { | |
949 statementOffset = getSyntaxOffset(node); | |
950 notifyStep( | |
951 node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.BREAK); | |
952 statementOffset = null; | |
953 leftToRightOffset = null; | |
954 } | |
955 | |
956 @override | |
957 visitTry(js.Try node) { | |
958 visit(node.body); | |
959 visit(node.catchPart, BranchKind.CATCH); | |
960 visit(node.finallyPart, BranchKind.FINALLY); | |
961 } | |
962 | |
963 @override | |
964 visitCatch(js.Catch node) { | |
965 visit(node.body); | |
966 } | |
967 | |
968 @override | |
969 visitConditional(js.Conditional node) { | |
970 visit(node.condition); | |
971 visit(node.then, BranchKind.CONDITION, true); | |
972 visit(node.otherwise, BranchKind.CONDITION, false); | |
973 } | |
974 | |
975 @override | |
976 visitPrefix(js.Prefix node) { | |
977 visit(node.argument); | |
978 } | |
979 | |
980 @override | |
981 visitPostfix(js.Postfix node) { | |
982 visit(node.argument); | |
983 } | |
984 | |
985 @override | |
986 visitObjectInitializer(js.ObjectInitializer node) { | |
987 visitList(node.properties); | |
988 } | |
989 | |
990 @override | |
991 visitProperty(js.Property node) { | |
992 visit(node.name); | |
993 visit(node.value); | |
994 } | |
995 | |
996 @override | |
997 visitRegExpLiteral(js.RegExpLiteral node) {} | |
998 | |
999 @override | |
1000 visitSwitch(js.Switch node) { | |
1001 statementOffset = getSyntaxOffset(node); | |
1002 visitSubexpression(node, node.key, getSyntaxOffset(node), | |
1003 StepKind.SWITCH_EXPRESSION); | |
1004 statementOffset = null; | |
1005 leftToRightOffset = null; | |
1006 for (int i = 0; i < node.cases.length; i++) { | |
1007 visit(node.cases[i], BranchKind.CASE, i); | |
1008 } | |
1009 } | |
1010 | |
1011 @override | |
1012 visitCase(js.Case node) { | |
1013 visit(node.expression); | |
1014 visit(node.body); | |
1015 } | |
1016 | |
1017 @override | |
1018 visitDefault(js.Default node) { | |
1019 visit(node.body); | |
1020 } | |
1021 | |
1022 @override | |
1023 visitArrayInitializer(js.ArrayInitializer node) { | |
1024 visitList(node.elements); | |
1025 } | |
1026 | |
1027 @override | |
1028 visitArrayHole(js.ArrayHole node) {} | |
1029 | |
1030 @override | |
1031 visitLabeledStatement(js.LabeledStatement node) { | |
1032 statementOffset = getSyntaxOffset(node); | |
1033 visit(node.body); | |
1034 statementOffset = null; | |
1035 } | |
1036 | |
1037 Offset getOffsetForNode(js.Node node, int codeOffset) { | |
1038 if (codeOffset == null) { | |
1039 CodePosition codePosition = codePositions[node]; | |
1040 if (codePosition != null) { | |
1041 codeOffset = codePosition.startPosition; | |
1042 } | |
1043 } | |
1044 if (leftToRightOffset != null && leftToRightOffset < codeOffset) { | |
1045 leftToRightOffset = codeOffset; | |
1046 } | |
1047 if (leftToRightOffset == null) { | |
1048 leftToRightOffset = statementOffset; | |
1049 } | |
1050 return new Offset(statementOffset, leftToRightOffset, codeOffset); | |
1051 } | |
1052 } | |
1053 | |
1054 | |
1055 class Coverage { | |
1056 Set<js.Node> _nodesWithInfo = new Set<js.Node>(); | |
1057 int _nodesWithInfoCount = 0; | |
1058 Set<js.Node> _nodesWithoutInfo = new Set<js.Node>(); | |
1059 int _nodesWithoutInfoCount = 0; | |
1060 Map<Type, int> _nodesWithoutInfoCountByType = <Type, int>{}; | |
1061 Set<js.Node> _nodesWithoutOffset = new Set<js.Node>(); | |
1062 int _nodesWithoutOffsetCount = 0; | |
1063 | |
1064 void registerNodeWithInfo(js.Node node) { | |
1065 _nodesWithInfo.add(node); | |
1066 } | |
1067 | |
1068 void registerNodeWithoutInfo(js.Node node) { | |
1069 _nodesWithoutInfo.add(node); | |
1070 } | |
1071 | |
1072 void registerNodesWithoutOffset(js.Node node) { | |
1073 _nodesWithoutOffset.add(node); | |
1074 } | |
1075 | |
1076 void collapse() { | |
1077 _nodesWithInfoCount += _nodesWithInfo.length; | |
1078 _nodesWithInfo.clear(); | |
1079 _nodesWithoutOffsetCount += _nodesWithoutOffset.length; | |
1080 _nodesWithoutOffset.clear(); | |
1081 | |
1082 _nodesWithoutInfoCount += _nodesWithoutInfo.length; | |
1083 for (js.Node node in _nodesWithoutInfo) { | |
1084 if (node is js.ExpressionStatement) { | |
1085 _nodesWithoutInfoCountByType.putIfAbsent( | |
1086 node.expression.runtimeType, () => 0); | |
1087 _nodesWithoutInfoCountByType[node.expression.runtimeType]++; | |
1088 } else { | |
1089 _nodesWithoutInfoCountByType.putIfAbsent( | |
1090 node.runtimeType, () => 0); | |
1091 _nodesWithoutInfoCountByType[node.runtimeType]++; | |
1092 } | |
1093 } | |
1094 _nodesWithoutInfo.clear(); | |
1095 } | |
1096 | |
1097 String getCoverageReport() { | |
1098 collapse(); | |
1099 StringBuffer sb = new StringBuffer(); | |
1100 int total = _nodesWithInfoCount + _nodesWithoutInfoCount; | |
1101 if (total > 0) { | |
1102 sb.write(_nodesWithoutInfoCount); | |
1103 sb.write('/'); | |
1104 sb.write(total); | |
1105 sb.write(' ('); | |
1106 sb.write((100.0 * _nodesWithInfoCount / total).toStringAsFixed(2)); | |
1107 sb.write('%) nodes with info.'); | |
1108 } else { | |
1109 sb.write('No nodes.'); | |
1110 } | |
1111 if (_nodesWithoutOffsetCount > 0) { | |
1112 sb.write(' '); | |
1113 sb.write(_nodesWithoutOffsetCount); | |
1114 sb.write(' node'); | |
1115 if (_nodesWithoutOffsetCount > 1) { | |
1116 sb.write('s'); | |
1117 } | |
1118 sb.write(' without offset.'); | |
1119 } | |
1120 if (_nodesWithoutInfoCount > 0) { | |
1121 sb.write('\nNodes without info ('); | |
1122 sb.write(_nodesWithoutInfoCount); | |
1123 sb.write(') by runtime type:'); | |
1124 _nodesWithoutInfoCountByType.forEach((Type type, int count) { | |
1125 sb.write('\n '); | |
1126 sb.write(count); | |
1127 sb.write(' '); | |
1128 sb.write(type); | |
1129 sb.write(' node'); | |
1130 if (count > 1) { | |
1131 sb.write('s'); | |
1132 } | |
1133 }); | |
1134 sb.write('\n'); | |
1135 } | |
1136 return sb.toString(); | |
1137 } | |
1138 | |
1139 String toString() => getCoverageReport(); | |
1140 } | |
1141 | |
1142 /// [TraceListener] that registers [onStep] callbacks with [coverage]. | |
1143 class CoverageListener extends TraceListener { | |
1144 final Coverage coverage; | |
1145 | |
1146 CoverageListener(this.coverage); | |
1147 | |
1148 @override | |
1149 void onStep(js.Node node, Offset offset, StepKind kind) { | |
1150 SourceInformation sourceInformation = node.sourceInformation; | |
1151 if (sourceInformation != null) { | |
1152 coverage.registerNodeWithInfo(node); | |
1153 } else { | |
1154 coverage.registerNodeWithoutInfo(node); | |
1155 } | |
1156 } | |
1157 | |
1158 @override | |
1159 void onEnd(js.Node node) { | |
1160 coverage.collapse(); | |
1161 } | |
1162 } | |
1163 | |
1164 /// [CodePositionMap] that registers calls with [Coverage]. | |
1165 class CodePositionCoverage implements CodePositionMap { | |
1166 final CodePositionMap codePositions; | |
1167 final Coverage coverage; | |
1168 | |
1169 CodePositionCoverage(this.codePositions, this.coverage); | |
1170 | |
1171 @override | |
1172 CodePosition operator [](js.Node node) { | |
1173 CodePosition codePosition = codePositions[node]; | |
1174 if (codePosition == null) { | |
1175 coverage.registerNodesWithoutOffset(node); | |
1176 } | |
1177 return codePosition; | |
1178 } | |
1179 } | |
OLD | NEW |