mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 13:28:03 +00:00
Initial support for inlining-data in source-maps.
This starts tracking inlining data and records it as an extension to source-map files so we are able to expand inlined calls when deobfuscating production stack traces. Change-Id: I46daf21c2f42305b2bd6ca17fbafb65226355096 Reviewed-on: https://dart-review.googlesource.com/67083 Reviewed-by: Stephen Adams <sra@google.com> Commit-Queue: Sigmund Cherem <sigmund@google.com>
This commit is contained in:
parent
4206131030
commit
cb7341fceb
|
@ -16,7 +16,12 @@ abstract class CodeOutputListener {
|
|||
void onDone(int length);
|
||||
}
|
||||
|
||||
/// Interface for a mapping of target offsets to source locations.
|
||||
/// Interface for a mapping of target offsets to source locations and for
|
||||
/// tracking inlining frame data.
|
||||
///
|
||||
/// Source-location mapping is used to build standard source-maps files.
|
||||
/// Inlining frames is used to attach an extension to source-map files to
|
||||
/// improve deobfuscation of production stack traces.
|
||||
abstract class SourceLocations {
|
||||
/// The name identifying this source mapping.
|
||||
String get name;
|
||||
|
@ -24,15 +29,31 @@ abstract class SourceLocations {
|
|||
/// Adds a [sourceLocation] at the specified [targetOffset].
|
||||
void addSourceLocation(int targetOffset, SourceLocation sourcePosition);
|
||||
|
||||
/// Record an inlining call at the [targetOffset].
|
||||
///
|
||||
/// The inlining call-site was made from [pushLocation] and calls
|
||||
/// [inlinedMethodName].
|
||||
void addPush(
|
||||
int targetOffset, SourceLocation pushPosition, String inlinedMethodName);
|
||||
|
||||
/// Record a return of an inlining call at the [targetOffset].
|
||||
///
|
||||
/// [isEmpty] indicates that this return also makes the inlining stack empty.
|
||||
void addPop(int targetOffset, bool isEmpty);
|
||||
|
||||
/// Applies [f] to every target offset and associated source location.
|
||||
void forEachSourceLocation(
|
||||
void f(int targetOffset, SourceLocation sourceLocation));
|
||||
|
||||
/// Recorded inlining data per target-offset.
|
||||
Map<int, List<FrameEntry>> get frameMarkers;
|
||||
}
|
||||
|
||||
class _SourceLocationsImpl implements SourceLocations {
|
||||
final String name;
|
||||
final AbstractCodeOutput codeOutput;
|
||||
Map<int, List<SourceLocation>> markers = <int, List<SourceLocation>>{};
|
||||
Map<int, List<FrameEntry>> frameMarkers = <int, List<FrameEntry>>{};
|
||||
|
||||
_SourceLocationsImpl(this.name, this.codeOutput);
|
||||
|
||||
|
@ -44,6 +65,21 @@ class _SourceLocationsImpl implements SourceLocations {
|
|||
sourceLocations.add(sourceLocation);
|
||||
}
|
||||
|
||||
@override
|
||||
void addPush(int targetOffset, SourceLocation sourceLocation,
|
||||
String inlinedMethodName) {
|
||||
assert(targetOffset <= codeOutput.length);
|
||||
List<FrameEntry> frames = frameMarkers[targetOffset] ??= [];
|
||||
frames.add(new FrameEntry.push(sourceLocation, inlinedMethodName));
|
||||
}
|
||||
|
||||
@override
|
||||
void addPop(int targetOffset, bool isEmpty) {
|
||||
assert(targetOffset <= codeOutput.length);
|
||||
List<FrameEntry> frames = frameMarkers[targetOffset] ??= [];
|
||||
frames.add(new FrameEntry.pop(isEmpty));
|
||||
}
|
||||
|
||||
@override
|
||||
void forEachSourceLocation(
|
||||
void f(int targetOffset, SourceLocation sourceLocation)) {
|
||||
|
@ -54,17 +90,22 @@ class _SourceLocationsImpl implements SourceLocations {
|
|||
});
|
||||
}
|
||||
|
||||
void _addSourceLocations(_SourceLocationsImpl other) {
|
||||
void _merge(_SourceLocationsImpl other) {
|
||||
assert(name == other.name);
|
||||
int length = codeOutput.length;
|
||||
if (other.markers.length > 0) {
|
||||
other.markers
|
||||
.forEach((int targetOffset, List<SourceLocation> sourceLocations) {
|
||||
markers
|
||||
.putIfAbsent(
|
||||
codeOutput.length + targetOffset, () => <SourceLocation>[])
|
||||
(markers[length + targetOffset] ??= <SourceLocation>[])
|
||||
.addAll(sourceLocations);
|
||||
});
|
||||
}
|
||||
|
||||
if (other.frameMarkers.length > 0) {
|
||||
other.frameMarkers.forEach((int targetOffset, List<FrameEntry> frames) {
|
||||
(frameMarkers[length + targetOffset] ??= <FrameEntry>[]).addAll(frames);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,7 +158,7 @@ abstract class AbstractCodeOutput extends CodeOutput {
|
|||
@override
|
||||
void addBuffer(CodeBuffer other) {
|
||||
other.sourceLocationsMap.forEach((String name, _SourceLocationsImpl other) {
|
||||
createSourceLocations(name)._addSourceLocations(other);
|
||||
createSourceLocations(name)._merge(other);
|
||||
});
|
||||
if (!other.isClosed) {
|
||||
other.close();
|
||||
|
@ -138,8 +179,7 @@ abstract class AbstractCodeOutput extends CodeOutput {
|
|||
|
||||
@override
|
||||
_SourceLocationsImpl createSourceLocations(String name) {
|
||||
return sourceLocationsMap.putIfAbsent(
|
||||
name, () => new _SourceLocationsImpl(name, this));
|
||||
return sourceLocationsMap[name] ??= new _SourceLocationsImpl(name, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -74,9 +74,19 @@ class KernelSourceInformationBuilder implements SourceInformationBuilder {
|
|||
final MemberEntity _member;
|
||||
final String _name;
|
||||
|
||||
/// Inlining context or null when no inlining has taken place.
|
||||
///
|
||||
/// A new builder is created every time the backend inlines a method. This
|
||||
/// field contains the location of every call site that has been inlined. The
|
||||
/// last entry on the list is always a call to [_member].
|
||||
final List<FrameContext> inliningContext;
|
||||
|
||||
KernelSourceInformationBuilder(this._elementMap, this._member)
|
||||
: this._name =
|
||||
computeKernelElementNameForSourceMaps(_elementMap, _member);
|
||||
: _name = computeKernelElementNameForSourceMaps(_elementMap, _member),
|
||||
inliningContext = null;
|
||||
|
||||
KernelSourceInformationBuilder.withContext(
|
||||
this._elementMap, this._member, this.inliningContext, this._name);
|
||||
|
||||
/// Returns the [SourceLocation] for the [offset] within [node] using [name]
|
||||
/// as the name of the source location.
|
||||
|
@ -106,8 +116,10 @@ class KernelSourceInformationBuilder implements SourceInformationBuilder {
|
|||
SourceInformation _buildFunction(
|
||||
String name, ir.TreeNode node, ir.FunctionNode functionNode) {
|
||||
if (functionNode.fileEndOffset != ir.TreeNode.noOffset) {
|
||||
return new PositionSourceInformation(_getSourceLocation(name, node),
|
||||
_getSourceLocation(name, functionNode, functionNode.fileEndOffset));
|
||||
return new PositionSourceInformation(
|
||||
_getSourceLocation(name, node),
|
||||
_getSourceLocation(name, functionNode, functionNode.fileEndOffset),
|
||||
this.inliningContext);
|
||||
}
|
||||
return _buildTreeNode(node);
|
||||
}
|
||||
|
@ -156,7 +168,9 @@ class KernelSourceInformationBuilder implements SourceInformationBuilder {
|
|||
ir.TreeNode node, ir.FunctionNode functionNode) {
|
||||
if (functionNode.fileEndOffset != ir.TreeNode.noOffset) {
|
||||
return new PositionSourceInformation(
|
||||
_getSourceLocation(_name, functionNode, functionNode.fileEndOffset));
|
||||
_getSourceLocation(_name, functionNode, functionNode.fileEndOffset),
|
||||
null,
|
||||
this.inliningContext);
|
||||
}
|
||||
return _buildTreeNode(node);
|
||||
}
|
||||
|
@ -176,7 +190,7 @@ class KernelSourceInformationBuilder implements SourceInformationBuilder {
|
|||
} else {
|
||||
location = _getSourceLocation(_name, node);
|
||||
}
|
||||
return new PositionSourceInformation(location);
|
||||
return new PositionSourceInformation(location, null, inliningContext);
|
||||
}
|
||||
|
||||
/// Creates source information for the body of the current member.
|
||||
|
@ -259,12 +273,27 @@ class KernelSourceInformationBuilder implements SourceInformationBuilder {
|
|||
SourceInformation _buildTreeNode(ir.TreeNode node,
|
||||
{SourceLocation closingPosition, String name}) {
|
||||
return new PositionSourceInformation(
|
||||
_getSourceLocation(name ?? _name, node), closingPosition);
|
||||
_getSourceLocation(name ?? _name, node),
|
||||
closingPosition,
|
||||
inliningContext);
|
||||
}
|
||||
|
||||
@override
|
||||
SourceInformationBuilder forContext(MemberEntity member) =>
|
||||
new KernelSourceInformationBuilder(_elementMap, member);
|
||||
SourceInformationBuilder forContext(
|
||||
MemberEntity member, SourceInformation context) {
|
||||
List<FrameContext> newContext = inliningContext?.toList() ?? [];
|
||||
if (context != null) {
|
||||
newContext.add(new FrameContext(context, member.name));
|
||||
} else {
|
||||
// TODO(sigmund): investigate whether we have any more cases where context
|
||||
// is null.
|
||||
newContext = inliningContext;
|
||||
}
|
||||
|
||||
String name = computeKernelElementNameForSourceMaps(_elementMap, _member);
|
||||
return new KernelSourceInformationBuilder.withContext(
|
||||
_elementMap, member, newContext, name);
|
||||
}
|
||||
|
||||
@override
|
||||
SourceInformation buildSwitchCase(ir.Node node) => null;
|
||||
|
@ -371,6 +400,11 @@ class KernelSourceInformationBuilder implements SourceInformationBuilder {
|
|||
return _buildTreeNode(node);
|
||||
}
|
||||
|
||||
@override
|
||||
SourceInformation buildAssert(ir.Node node) {
|
||||
return _buildTreeNode(node);
|
||||
}
|
||||
|
||||
@override
|
||||
SourceInformation buildNew(ir.Node node) {
|
||||
return _buildTreeNode(node);
|
||||
|
@ -384,8 +418,8 @@ class KernelSourceInformationBuilder implements SourceInformationBuilder {
|
|||
@override
|
||||
SourceInformation buildCall(
|
||||
covariant ir.TreeNode receiver, covariant ir.TreeNode call) {
|
||||
return new PositionSourceInformation(
|
||||
_getSourceLocation(_name, receiver), _getSourceLocation(_name, call));
|
||||
return new PositionSourceInformation(_getSourceLocation(_name, receiver),
|
||||
_getSourceLocation(_name, call), inliningContext);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -393,6 +427,11 @@ class KernelSourceInformationBuilder implements SourceInformationBuilder {
|
|||
return _buildTreeNode(node);
|
||||
}
|
||||
|
||||
@override
|
||||
SourceInformation buildSet(ir.Node node) {
|
||||
return _buildTreeNode(node);
|
||||
}
|
||||
|
||||
@override
|
||||
SourceInformation buildLoop(ir.Node node) {
|
||||
return _buildTreeNode(node);
|
||||
|
@ -448,4 +487,9 @@ class KernelSourceLocation extends AbstractSourceLocation {
|
|||
KernelSourceLocation(ir.Location location, this.offset, this.sourceName)
|
||||
: sourceUri = location.file,
|
||||
super.fromLocation(location);
|
||||
|
||||
KernelSourceLocation.fromOther(KernelSourceLocation other, this.sourceName)
|
||||
: sourceUri = other.sourceUri,
|
||||
offset = other.offset,
|
||||
super.fromOther(other);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,10 @@ class PositionSourceInformation extends SourceInformation {
|
|||
@override
|
||||
final SourceLocation innerPosition;
|
||||
|
||||
PositionSourceInformation(this.startPosition, [this.innerPosition]);
|
||||
final List<FrameContext> inliningContext;
|
||||
|
||||
PositionSourceInformation(
|
||||
this.startPosition, this.innerPosition, this.inliningContext);
|
||||
|
||||
@override
|
||||
List<SourceLocation> get sourceLocations {
|
||||
|
@ -275,14 +278,17 @@ class PositionSourceInformationProcessor extends SourceInformationProcessor {
|
|||
final SourceInformationReader reader;
|
||||
CodePositionMap codePositionMap;
|
||||
List<TraceListener> traceListeners;
|
||||
InliningTraceListener inliningListener;
|
||||
|
||||
PositionSourceInformationProcessor(SourceMapperProvider provider, this.reader,
|
||||
[Coverage coverage]) {
|
||||
codePositionMap = coverage != null
|
||||
? new CodePositionCoverage(codePositionRecorder, coverage)
|
||||
: codePositionRecorder;
|
||||
var sourceMapper = provider.createSourceMapper(id);
|
||||
traceListeners = [
|
||||
new PositionTraceListener(provider.createSourceMapper(id), reader)
|
||||
new PositionTraceListener(sourceMapper, reader),
|
||||
inliningListener = new InliningTraceListener(sourceMapper, reader),
|
||||
];
|
||||
if (coverage != null) {
|
||||
traceListeners.add(new CoverageListener(coverage, reader));
|
||||
|
@ -291,6 +297,7 @@ class PositionSourceInformationProcessor extends SourceInformationProcessor {
|
|||
|
||||
void process(js.Node node, BufferedCodeOutput code) {
|
||||
new JavaScriptTracer(codePositionMap, reader, traceListeners).apply(node);
|
||||
inliningListener?.finish();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -368,6 +375,86 @@ abstract class NodeToSourceInformationMixin {
|
|||
}
|
||||
}
|
||||
|
||||
/// [TraceListener] that register inlining context-data with a [SourceMapper].
|
||||
class InliningTraceListener extends TraceListener
|
||||
with NodeToSourceInformationMixin {
|
||||
final SourceMapper sourceMapper;
|
||||
final SourceInformationReader reader;
|
||||
final Map<int, List<FrameContext>> _frames = {};
|
||||
|
||||
InliningTraceListener(this.sourceMapper, this.reader);
|
||||
|
||||
@override
|
||||
void onStep(js.Node node, Offset offset, StepKind kind) {
|
||||
SourceInformation sourceInformation = computeSourceInformation(node);
|
||||
if (sourceInformation == null) return;
|
||||
// TODO(sigmund): enable this assertion.
|
||||
// assert(offset.value != null, "Expected a valid offset: $node $offset");
|
||||
if (offset.value == null) return;
|
||||
|
||||
// TODO(sigmund): enable this assertion
|
||||
//assert(_frames[offset.value] == null,
|
||||
// "Expect a single entry per offset: $offset $node");
|
||||
if (_frames[offset.value] != null) return;
|
||||
|
||||
// During tracing we only collect information per offset because the tracer
|
||||
// visits nodes in tree order. We'll later sort the data by offset before
|
||||
// registering the frame data with [SourceMapper].
|
||||
if (kind == StepKind.FUN_EXIT) {
|
||||
_frames[offset.value] = null;
|
||||
} else {
|
||||
_frames[offset.value] = sourceInformation.inliningContext;
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the inlining context data collected during tracing into push/pop
|
||||
/// stack operations that will be emitted with the source-map files.
|
||||
void finish() {
|
||||
List<FrameContext> lastInliningContext;
|
||||
for (var offset in _frames.keys.toList()..sort()) {
|
||||
var newInliningContext = _frames[offset];
|
||||
|
||||
// Note: this relies on the invariant that, when we built the inlining
|
||||
// context lists during SSA, we kept lists identical whenever there were
|
||||
// no inlining changes.
|
||||
if (lastInliningContext == newInliningContext) continue;
|
||||
|
||||
bool isEmpty = false;
|
||||
int popCount = 0;
|
||||
List<FrameContext> pushes = const [];
|
||||
if (newInliningContext == null) {
|
||||
popCount = lastInliningContext.length;
|
||||
isEmpty = true;
|
||||
} else if (lastInliningContext == null) {
|
||||
pushes = newInliningContext;
|
||||
} else {
|
||||
int min = newInliningContext.length;
|
||||
if (min > lastInliningContext.length) min = lastInliningContext.length;
|
||||
// Determine the total number of common frames, to produce the minimal
|
||||
// set of pop and push operations.
|
||||
int i = 0;
|
||||
for (i = 0; i < min; i++) {
|
||||
if (!identical(newInliningContext[i], lastInliningContext[i])) break;
|
||||
}
|
||||
isEmpty = i == 0;
|
||||
popCount = lastInliningContext.length - i;
|
||||
if (i < newInliningContext.length) {
|
||||
pushes = newInliningContext.sublist(i);
|
||||
}
|
||||
}
|
||||
lastInliningContext = newInliningContext;
|
||||
|
||||
while (popCount-- > 0) {
|
||||
sourceMapper.registerPop(offset, isEmpty: popCount == 0 && isEmpty);
|
||||
}
|
||||
for (FrameContext push in pushes) {
|
||||
sourceMapper.registerPush(offset,
|
||||
getSourceLocation(push.callInformation), push.inlinedMethodName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [TraceListener] that register [SourceLocation]s with a [SourceMapper].
|
||||
class PositionTraceListener extends TraceListener
|
||||
with NodeToSourceInformationMixin {
|
||||
|
|
|
@ -29,13 +29,33 @@ abstract class SourceInformation extends JavaScriptNodeSourceInformation {
|
|||
/// The source location associated with the end of the JS node.
|
||||
SourceLocation get endPosition => null;
|
||||
|
||||
/// All source locations associated with this source information.
|
||||
/// A list containing start, inner, and end positions.
|
||||
List<SourceLocation> get sourceLocations;
|
||||
|
||||
/// A list of inlining context locations.
|
||||
List<FrameContext> get inliningContext => null;
|
||||
|
||||
/// Return a short textual representation of the source location.
|
||||
String get shortText;
|
||||
}
|
||||
|
||||
/// Context information about inlined calls.
|
||||
///
|
||||
/// This is associated with SourceInformation objects to be able to emit
|
||||
/// precise data about inlining that can then be used by defobuscation tools
|
||||
/// when reconstructing a source stack from a production stack trace.
|
||||
class FrameContext {
|
||||
/// Location of the call that was inlined.
|
||||
final SourceInformation callInformation;
|
||||
|
||||
/// Name of the method that was inlined.
|
||||
final String inlinedMethodName;
|
||||
|
||||
FrameContext(this.callInformation, this.inlinedMethodName);
|
||||
|
||||
String toString() => "(FrameContext: $callInformation, $inlinedMethodName)";
|
||||
}
|
||||
|
||||
/// Strategy for creating, processing and applying [SourceInformation].
|
||||
class SourceInformationStrategy {
|
||||
const SourceInformationStrategy();
|
||||
|
@ -57,8 +77,11 @@ class SourceInformationStrategy {
|
|||
class SourceInformationBuilder {
|
||||
const SourceInformationBuilder();
|
||||
|
||||
/// Create a [SourceInformationBuilder] for [member].
|
||||
SourceInformationBuilder forContext(covariant MemberEntity member) => this;
|
||||
/// Create a [SourceInformationBuilder] for [member] with additional inlining
|
||||
/// [context].
|
||||
SourceInformationBuilder forContext(
|
||||
covariant MemberEntity member, SourceInformation context) =>
|
||||
this;
|
||||
|
||||
/// Generate [SourceInformation] for the declaration of the [member].
|
||||
SourceInformation buildDeclaration(covariant MemberEntity member) => null;
|
||||
|
@ -85,13 +108,13 @@ class SourceInformationBuilder {
|
|||
/// Generate [SourceInformation] for the loop [node].
|
||||
SourceInformation buildLoop(ir.Node node) => null;
|
||||
|
||||
/// Generate [SourceInformation] for a read access like `a.b` where in
|
||||
/// [receiver] points to the left-most part of the access, `a` in the example,
|
||||
/// and [property] points to the 'name' of accessed property, `b` in the
|
||||
/// example.
|
||||
/// Generate [SourceInformation] for a read access like `a.b`.
|
||||
SourceInformation buildGet(ir.Node node) => null;
|
||||
|
||||
/// Generate [SourceInformation] for the read access in [node].
|
||||
/// Generate [SourceInformation] for a write access like `a.b = 3`.
|
||||
SourceInformation buildSet(ir.Node node) => null;
|
||||
|
||||
/// Generate [SourceInformation] for a call in [node].
|
||||
SourceInformation buildCall(ir.Node receiver, ir.Node call) => null;
|
||||
|
||||
/// Generate [SourceInformation] for the if statement in [node].
|
||||
|
@ -103,6 +126,9 @@ class SourceInformationBuilder {
|
|||
/// Generate [SourceInformation] for the throw in [node].
|
||||
SourceInformation buildThrow(ir.Node node) => null;
|
||||
|
||||
/// Generate [SourceInformation] for the assert in [node].
|
||||
SourceInformation buildAssert(ir.Node node) => null;
|
||||
|
||||
/// Generate [SourceInformation] for the assignment in [node].
|
||||
SourceInformation buildAssignment(ir.Node node) => null;
|
||||
|
||||
|
@ -234,6 +260,9 @@ abstract class AbstractSourceLocation extends SourceLocation {
|
|||
|
||||
AbstractSourceLocation.fromLocation(this._location) : _sourceFile = null;
|
||||
|
||||
AbstractSourceLocation.fromOther(AbstractSourceLocation location)
|
||||
: this.fromLocation(location._location);
|
||||
|
||||
/// The absolute URI of the source file of this source location.
|
||||
Uri get sourceUri => _sourceFile.uri;
|
||||
|
||||
|
@ -338,3 +367,29 @@ class NoSourceLocationMarker extends SourceLocation {
|
|||
|
||||
String toString() => '<no-location>';
|
||||
}
|
||||
|
||||
/// Information tracked about inlined frames.
|
||||
///
|
||||
/// Dart2js adds an extension to source-map files to track where calls are
|
||||
/// inlined. This information is used to improve the precision of tools that
|
||||
/// deobfuscate production stack traces.
|
||||
class FrameEntry {
|
||||
/// For push operations, the location of the inlining call, otherwise null.
|
||||
final SourceLocation pushLocation;
|
||||
|
||||
/// For push operations, the inlined method name, otherwise null.
|
||||
final String inlinedMethodName;
|
||||
|
||||
/// Whether a pop is the last pop that makes the inlining stack empty.
|
||||
final bool isEmptyPop;
|
||||
|
||||
FrameEntry.push(this.pushLocation, this.inlinedMethodName)
|
||||
: isEmptyPop = false;
|
||||
|
||||
FrameEntry.pop(this.isEmptyPop)
|
||||
: pushLocation = null,
|
||||
inlinedMethodName = null;
|
||||
|
||||
bool get isPush => pushLocation != null;
|
||||
bool get isPop => pushLocation == null;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import '../util/uri_extras.dart' show relativize;
|
|||
import '../util/util.dart';
|
||||
import 'location_provider.dart';
|
||||
import 'code_output.dart' show SourceLocationsProvider, SourceLocations;
|
||||
import 'source_information.dart' show SourceLocation;
|
||||
import 'source_information.dart' show SourceLocation, FrameEntry;
|
||||
|
||||
class SourceMapBuilder {
|
||||
final String version;
|
||||
|
@ -28,13 +28,17 @@ class SourceMapBuilder {
|
|||
final Map<String, String> minifiedGlobalNames;
|
||||
final Map<String, String> minifiedInstanceNames;
|
||||
|
||||
/// Extension used to deobfuscate inlined stack frames.
|
||||
final Map<int, List<FrameEntry>> frames;
|
||||
|
||||
SourceMapBuilder(
|
||||
this.version,
|
||||
this.sourceMapUri,
|
||||
this.targetFileUri,
|
||||
this.locationProvider,
|
||||
this.minifiedGlobalNames,
|
||||
this.minifiedInstanceNames);
|
||||
this.minifiedInstanceNames,
|
||||
this.frames);
|
||||
|
||||
void addMapping(int targetOffset, SourceLocation sourceLocation) {
|
||||
entries.add(new SourceMapEntry(sourceLocation, targetOffset));
|
||||
|
@ -84,8 +88,7 @@ class SourceMapBuilder {
|
|||
IndexMap<Uri> uriMap = new IndexMap<Uri>();
|
||||
IndexMap<String> nameMap = new IndexMap<String>();
|
||||
|
||||
lineColumnMap.forEachElement((SourceMapEntry entry) {
|
||||
SourceLocation sourceLocation = entry.sourceLocation;
|
||||
void registerLocation(SourceLocation sourceLocation) {
|
||||
if (sourceLocation != null) {
|
||||
if (sourceLocation.sourceUri != null) {
|
||||
uriMap.register(sourceLocation.sourceUri);
|
||||
|
@ -94,10 +97,22 @@ class SourceMapBuilder {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lineColumnMap.forEachElement((SourceMapEntry entry) {
|
||||
registerLocation(entry.sourceLocation);
|
||||
});
|
||||
|
||||
minifiedGlobalNames.values.forEach(nameMap.register);
|
||||
minifiedInstanceNames.values.forEach(nameMap.register);
|
||||
for (List<FrameEntry> entries in frames.values) {
|
||||
for (var frame in entries) {
|
||||
registerLocation(frame.pushLocation);
|
||||
if (frame.inlinedMethodName != null) {
|
||||
nameMap.register(frame.inlinedMethodName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StringBuffer mappingsBuffer = new StringBuffer();
|
||||
writeEntries(lineColumnMap, uriMap, nameMap, mappingsBuffer);
|
||||
|
@ -132,7 +147,10 @@ class SourceMapBuilder {
|
|||
buffer.write(',\n');
|
||||
buffer.write(' "instance": ');
|
||||
writeMinifiedNames(minifiedInstanceNames, nameMap, buffer);
|
||||
buffer.write('\n }\n }\n}\n');
|
||||
buffer.write('\n },\n');
|
||||
buffer.write(' "frames": ');
|
||||
writeFrames(uriMap, nameMap, buffer);
|
||||
buffer.write('\n }\n}\n');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
|
@ -206,6 +224,37 @@ class SourceMapBuilder {
|
|||
buffer.write('}');
|
||||
}
|
||||
|
||||
void writeFrames(
|
||||
IndexMap<Uri> uriMap, IndexMap<String> nameMap, StringBuffer buffer) {
|
||||
bool first = true;
|
||||
buffer.write('[');
|
||||
frames.forEach((int offset, List<FrameEntry> entries) {
|
||||
if (!first) buffer.write(',');
|
||||
buffer.write('[');
|
||||
buffer.write(offset);
|
||||
for (var entry in entries) {
|
||||
buffer.write(',');
|
||||
if (entry.isPush) {
|
||||
SourceLocation location = entry.pushLocation;
|
||||
buffer.write('[');
|
||||
buffer.write(uriMap[location.sourceUri]);
|
||||
buffer.write(',');
|
||||
buffer.write(location.line - 1);
|
||||
buffer.write(',');
|
||||
buffer.write(location.column - 1);
|
||||
buffer.write(',');
|
||||
buffer.write(nameMap[entry.inlinedMethodName]);
|
||||
buffer.write(']');
|
||||
} else {
|
||||
buffer.write(entry.isEmptyPop ? 0 : -1);
|
||||
}
|
||||
}
|
||||
buffer.write(']');
|
||||
first = false;
|
||||
});
|
||||
buffer.write(']');
|
||||
}
|
||||
|
||||
/// Returns the source map tag to put at the end a .js file in [fileUri] to
|
||||
/// make it point to the source map file in [sourceMapUri].
|
||||
static String generateSourceMapTag(Uri sourceMapUri, Uri fileUri) {
|
||||
|
@ -244,7 +293,8 @@ class SourceMapBuilder {
|
|||
fileUri,
|
||||
locationProvider,
|
||||
minifiedGlobalNames,
|
||||
minifiedInstanceNames);
|
||||
minifiedInstanceNames,
|
||||
sourceLocations.frameMarkers);
|
||||
sourceLocations.forEachSourceLocation(sourceMapBuilder.addMapping);
|
||||
String sourceMap = sourceMapBuilder.build();
|
||||
String extension = 'js.map';
|
||||
|
|
|
@ -73,6 +73,16 @@ class SourceMapperProviderImpl implements SourceMapperProvider {
|
|||
abstract class SourceMapper {
|
||||
/// Associate [codeOffset] with [sourceLocation] for [node].
|
||||
void register(Node node, int codeOffset, SourceLocation sourceLocation);
|
||||
|
||||
/// Associate [codeOffset] with an inlining call at [sourceLocation].
|
||||
void registerPush(
|
||||
int codeOffset, SourceLocation sourceLocation, String inlinedMethodName);
|
||||
|
||||
/// Associate [codeOffset] with an inlining return.
|
||||
///
|
||||
/// If [isEmpty] is true, also associate that the inlining stack is empty at
|
||||
/// [codeOffset].
|
||||
void registerPop(int codeOffset, {bool isEmpty: false});
|
||||
}
|
||||
|
||||
/// An implementation of [SourceMapper] that stores the information directly
|
||||
|
@ -86,6 +96,17 @@ class SourceLocationsMapper implements SourceMapper {
|
|||
void register(Node node, int codeOffset, SourceLocation sourceLocation) {
|
||||
sourceLocations.addSourceLocation(codeOffset, sourceLocation);
|
||||
}
|
||||
|
||||
@override
|
||||
void registerPush(
|
||||
int codeOffset, SourceLocation sourceLocation, String inlinedMethodName) {
|
||||
sourceLocations.addPush(codeOffset, sourceLocation, inlinedMethodName);
|
||||
}
|
||||
|
||||
@override
|
||||
void registerPop(int codeOffset, {bool isEmpty: false}) {
|
||||
sourceLocations.addPop(codeOffset, isEmpty);
|
||||
}
|
||||
}
|
||||
|
||||
/// A processor that associates [SourceInformation] with code position of
|
||||
|
|
|
@ -138,7 +138,7 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
|||
) : this.targetElement = _effectiveTargetElementFor(initialTargetElement),
|
||||
_infoReporter = compiler.dumpInfoTask,
|
||||
_allocatorAnalysis = closedWorld.allocatorAnalysis {
|
||||
_enterFrame(targetElement);
|
||||
_enterFrame(targetElement, null);
|
||||
this.loopHandler = new KernelLoopHandler(this);
|
||||
typeBuilder = new KernelTypeBuilder(this, _elementMap, _globalLocalsMap);
|
||||
graph.element = targetElement;
|
||||
|
@ -163,7 +163,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
|||
return member;
|
||||
}
|
||||
|
||||
void _enterFrame(MemberEntity member) {
|
||||
void _enterFrame(
|
||||
MemberEntity member, SourceInformation callSourceInformation) {
|
||||
AsyncMarker asyncMarker = AsyncMarker.SYNC;
|
||||
ir.FunctionNode function = getFunctionNode(_elementMap, member);
|
||||
if (function != null) {
|
||||
|
@ -176,7 +177,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
|||
_globalLocalsMap.getLocalsMap(member),
|
||||
new KernelToTypeInferenceMapImpl(member, globalInferenceResults),
|
||||
_currentFrame != null
|
||||
? _currentFrame.sourceInformationBuilder.forContext(member)
|
||||
? _currentFrame.sourceInformationBuilder
|
||||
.forContext(member, callSourceInformation)
|
||||
: _sourceInformationStrategy.createBuilderForContext(member));
|
||||
}
|
||||
|
||||
|
@ -579,7 +581,9 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
|||
|
||||
ConstructorEntity inlinedConstructor = _elementMap.getConstructor(body);
|
||||
|
||||
inlinedFrom(inlinedConstructor, () {
|
||||
inlinedFrom(
|
||||
inlinedConstructor, _sourceInformationBuilder.buildCall(body, body),
|
||||
() {
|
||||
void handleParameter(ir.VariableDeclaration node) {
|
||||
Local parameter = localsMap.getLocalVariable(node);
|
||||
// If [parameter] is boxed, it will be a field in the box passed as
|
||||
|
@ -656,9 +660,10 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
|||
|
||||
/// Sets context for generating code that is the result of inlining
|
||||
/// [inlinedTarget].
|
||||
inlinedFrom(MemberEntity inlinedTarget, f()) {
|
||||
inlinedFrom(MemberEntity inlinedTarget,
|
||||
SourceInformation callSourceInformation, f()) {
|
||||
reporter.withCurrentElement(inlinedTarget, () {
|
||||
_enterFrame(inlinedTarget);
|
||||
_enterFrame(inlinedTarget, callSourceInformation);
|
||||
var result = f();
|
||||
_leaveFrame();
|
||||
return result;
|
||||
|
@ -724,7 +729,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
|||
// TODO(sra): It would be sufficient to know the context was a field
|
||||
// initializer.
|
||||
if (!_allocatorAnalysis.isInitializedInAllocator(field)) {
|
||||
inlinedFrom(field, () {
|
||||
inlinedFrom(field,
|
||||
_sourceInformationBuilder.buildAssignment(node.initializer), () {
|
||||
node.initializer.accept(this);
|
||||
constructorData.fieldValues[field] = pop();
|
||||
});
|
||||
|
@ -902,7 +908,9 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
|||
ConstructorEntity element = _elementMap.getConstructor(constructor);
|
||||
ScopeInfo oldScopeInfo = localsHandler.scopeInfo;
|
||||
|
||||
inlinedFrom(element, () {
|
||||
inlinedFrom(
|
||||
element, _sourceInformationBuilder.buildCall(initializer, initializer),
|
||||
() {
|
||||
void handleParameter(ir.VariableDeclaration node) {
|
||||
Local parameter = localsMap.getLocalVariable(node);
|
||||
HInstruction argument = arguments[index++];
|
||||
|
@ -1236,7 +1244,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
|||
_commonElements.closureConverter,
|
||||
[argument, graph.addConstantInt(arity, closedWorld)],
|
||||
abstractValueDomain.dynamicType,
|
||||
const <DartType>[]);
|
||||
const <DartType>[],
|
||||
sourceInformation: null);
|
||||
argument = pop();
|
||||
}
|
||||
inputs.add(argument);
|
||||
|
@ -1379,7 +1388,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
|||
[prefixConstant, uriConstant],
|
||||
_typeInferenceMap
|
||||
.getReturnTypeOf(_commonElements.checkDeferredIsLoaded),
|
||||
const <DartType>[]);
|
||||
const <DartType>[],
|
||||
sourceInformation: null);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -2104,13 +2114,15 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
|||
@override
|
||||
void visitAssertStatement(ir.AssertStatement node) {
|
||||
if (!options.enableUserAssertions) return;
|
||||
var sourceInformation = _sourceInformationBuilder.buildAssert(node);
|
||||
if (node.message == null) {
|
||||
node.condition.accept(this);
|
||||
_pushStaticInvocation(
|
||||
_commonElements.assertHelper,
|
||||
<HInstruction>[pop()],
|
||||
_typeInferenceMap.getReturnTypeOf(_commonElements.assertHelper),
|
||||
const <DartType>[]);
|
||||
const <DartType>[],
|
||||
sourceInformation: sourceInformation);
|
||||
pop();
|
||||
return;
|
||||
}
|
||||
|
@ -2122,7 +2134,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
|||
_commonElements.assertTest,
|
||||
<HInstruction>[pop()],
|
||||
_typeInferenceMap.getReturnTypeOf(_commonElements.assertTest),
|
||||
const <DartType>[]);
|
||||
const <DartType>[],
|
||||
sourceInformation: sourceInformation);
|
||||
}
|
||||
|
||||
void fail() {
|
||||
|
@ -2131,7 +2144,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
|||
_commonElements.assertThrow,
|
||||
<HInstruction>[pop()],
|
||||
_typeInferenceMap.getReturnTypeOf(_commonElements.assertThrow),
|
||||
const <DartType>[]);
|
||||
const <DartType>[],
|
||||
sourceInformation: sourceInformation);
|
||||
pop();
|
||||
}
|
||||
|
||||
|
@ -2814,7 +2828,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
|||
|
||||
addImplicitInstantiation(type);
|
||||
_pushStaticInvocation(
|
||||
constructor, inputs, instructionType, const <DartType>[]);
|
||||
constructor, inputs, instructionType, const <DartType>[],
|
||||
sourceInformation: _sourceInformationBuilder.buildNew(node));
|
||||
removeImplicitInstantiation(type);
|
||||
}
|
||||
|
||||
|
@ -2914,7 +2929,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
|||
FunctionEntity setter = _elementMap.getMember(staticTarget);
|
||||
// Invoke the setter
|
||||
_pushStaticInvocation(setter, <HInstruction>[value],
|
||||
_typeInferenceMap.getReturnTypeOf(setter), const <DartType>[]);
|
||||
_typeInferenceMap.getReturnTypeOf(setter), const <DartType>[],
|
||||
sourceInformation: _sourceInformationBuilder.buildSet(node));
|
||||
pop();
|
||||
} else {
|
||||
add(new HStaticStore(
|
||||
|
@ -3662,8 +3678,9 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
|||
}
|
||||
}
|
||||
|
||||
_addTypeArguments(arguments, typeArguments,
|
||||
_sourceInformationBuilder.buildCall(invocation, invocation));
|
||||
SourceInformation sourceInformation =
|
||||
_sourceInformationBuilder.buildCall(invocation, invocation);
|
||||
_addTypeArguments(arguments, typeArguments, sourceInformation);
|
||||
|
||||
HInstruction argumentsInstruction = buildLiteralList(arguments);
|
||||
add(argumentsInstruction);
|
||||
|
@ -3697,7 +3714,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
|||
typeArgumentCount,
|
||||
],
|
||||
abstractValueDomain.dynamicType,
|
||||
const <DartType>[]);
|
||||
const <DartType>[],
|
||||
sourceInformation: sourceInformation);
|
||||
}
|
||||
|
||||
bool _unexpectedForeignArguments(ir.StaticInvocation invocation,
|
||||
|
@ -4472,7 +4490,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
|||
graph.addConstantInt(typeArguments.length, closedWorld),
|
||||
],
|
||||
abstractValueDomain.dynamicType,
|
||||
typeArguments);
|
||||
typeArguments,
|
||||
sourceInformation: sourceInformation);
|
||||
|
||||
_buildInvokeSuper(Selectors.noSuchMethod_, containingClass, noSuchMethod,
|
||||
<HInstruction>[pop()], typeArguments, sourceInformation);
|
||||
|
@ -5167,7 +5186,7 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
|||
List<HInstruction> compiledArguments = _completeCallArgumentsList(
|
||||
function, selector, providedArguments, currentNode);
|
||||
_enterInlinedMethod(function, compiledArguments, instanceType);
|
||||
inlinedFrom(function, () {
|
||||
inlinedFrom(function, sourceInformation, () {
|
||||
if (!isReachable) {
|
||||
_emitReturn(graph.addConstantNull(closedWorld), sourceInformation);
|
||||
} else {
|
||||
|
@ -6007,7 +6026,9 @@ class TryCatchFinallyBuilder {
|
|||
[exception],
|
||||
kernelBuilder._typeInferenceMap.getReturnTypeOf(
|
||||
kernelBuilder._commonElements.traceFromException),
|
||||
const <DartType>[]);
|
||||
const <DartType>[],
|
||||
sourceInformation:
|
||||
kernelBuilder._sourceInformationBuilder.buildCatch(catchBlock));
|
||||
HInstruction traceInstruction = kernelBuilder.pop();
|
||||
Local traceVariable =
|
||||
kernelBuilder.localsMap.getLocalVariable(catchBlock.stackTrace);
|
||||
|
|
|
@ -4,10 +4,12 @@
|
|||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:convert' show jsonDecode;
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:source_maps/source_maps.dart';
|
||||
import 'package:source_maps/src/utils.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import 'annotated_code_helper.dart';
|
||||
|
||||
|
@ -105,7 +107,8 @@ Future testStackTrace(Test test, String config, CompileFunc compile,
|
|||
bool useJsMethodNamesOnAbsence: false,
|
||||
String Function(String name) jsNameConverter: identityConverter,
|
||||
Directory forcedTmpDir: null,
|
||||
int stackTraceLimit: 10}) async {
|
||||
int stackTraceLimit: 10,
|
||||
expandDart2jsInliningData: false}) async {
|
||||
Expect.isTrue(test.expectationMap.keys.contains(config),
|
||||
"No expectations found for '$config' in ${test.expectationMap.keys}");
|
||||
|
||||
|
@ -122,13 +125,14 @@ Future testStackTrace(Test test, String config, CompileFunc compile,
|
|||
sourceMapFile.existsSync(), "Source map not generated for $input");
|
||||
String sourceMapText = sourceMapFile.readAsStringSync();
|
||||
SingleMapping sourceMap = parse(sourceMapText);
|
||||
String jsOutput = new File(output).readAsStringSync();
|
||||
|
||||
if (printJs) {
|
||||
print('JavaScript output:');
|
||||
print(new File(output).readAsStringSync());
|
||||
print(jsOutput);
|
||||
}
|
||||
if (writeJs) {
|
||||
new File('out.js').writeAsStringSync(new File(output).readAsStringSync());
|
||||
new File('out.js').writeAsStringSync(jsOutput);
|
||||
new File('out.js.map').writeAsStringSync(sourceMapText);
|
||||
}
|
||||
print("Running d8 $output");
|
||||
|
@ -161,18 +165,58 @@ Future testStackTrace(Test test, String config, CompileFunc compile,
|
|||
if (targetEntry == null || targetEntry.sourceUrlId == null) {
|
||||
dartStackTrace.add(line);
|
||||
} else {
|
||||
String fileName;
|
||||
if (targetEntry.sourceUrlId != null) {
|
||||
fileName = sourceMap.urls[targetEntry.sourceUrlId];
|
||||
}
|
||||
int targetLine = targetEntry.sourceLine + 1;
|
||||
int targetColumn = targetEntry.sourceColumn + 1;
|
||||
|
||||
if (expandDart2jsInliningData) {
|
||||
SourceFile file = new SourceFile.fromString(jsOutput);
|
||||
int offset = file.getOffset(line.lineNo - 1, line.columnNo - 1);
|
||||
Map<int, List<FrameEntry>> frames =
|
||||
_loadInlinedFrameData(sourceMap, sourceMapText);
|
||||
List<int> indices = frames.keys.toList()..sort();
|
||||
int key = binarySearch(indices, (i) => i > offset) - 1;
|
||||
int depth = 0;
|
||||
outer:
|
||||
while (key >= 0) {
|
||||
for (var frame in frames[indices[key]].reversed) {
|
||||
if (frame.isEmpty) break outer;
|
||||
if (frame.isPush) {
|
||||
if (depth <= 0) {
|
||||
dartStackTrace.add(new StackTraceLine(
|
||||
frame.inlinedMethodName + "(inlined)",
|
||||
fileName,
|
||||
targetLine,
|
||||
targetColumn,
|
||||
isMapped: true));
|
||||
fileName = frame.callUri;
|
||||
targetLine = frame.callLine + 1;
|
||||
targetColumn = frame.callColumn + 1;
|
||||
} else {
|
||||
depth--;
|
||||
}
|
||||
}
|
||||
if (frame.isPop) {
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
key--;
|
||||
}
|
||||
targetEntry = findEnclosingFunction(jsOutput, file, offset, sourceMap);
|
||||
}
|
||||
|
||||
String methodName;
|
||||
if (targetEntry.sourceNameId != null) {
|
||||
methodName = sourceMap.names[targetEntry.sourceNameId];
|
||||
} else if (useJsMethodNamesOnAbsence) {
|
||||
methodName = jsNameConverter(line.methodName);
|
||||
}
|
||||
String fileName;
|
||||
if (targetEntry.sourceUrlId != null) {
|
||||
fileName = sourceMap.urls[targetEntry.sourceUrlId];
|
||||
}
|
||||
dartStackTrace.add(new StackTraceLine(methodName, fileName,
|
||||
targetEntry.sourceLine + 1, targetEntry.sourceColumn + 1,
|
||||
|
||||
dartStackTrace.add(new StackTraceLine(
|
||||
methodName, fileName, targetLine, targetColumn,
|
||||
isMapped: true));
|
||||
}
|
||||
}
|
||||
|
@ -344,8 +388,10 @@ TargetLineEntry _findLine(SingleMapping sourceMap, StackTraceLine stLine) {
|
|||
String filename = stLine.fileName
|
||||
.substring(stLine.fileName.lastIndexOf(new RegExp("[\\\/]")) + 1);
|
||||
if (sourceMap.targetUrl != filename) return null;
|
||||
return _findLineInternal(sourceMap, stLine.lineNo - 1);
|
||||
}
|
||||
|
||||
int line = stLine.lineNo - 1;
|
||||
TargetLineEntry _findLineInternal(SingleMapping sourceMap, int line) {
|
||||
int index = binarySearch(sourceMap.lines, (e) => e.line > line);
|
||||
return (index <= 0) ? null : sourceMap.lines[index - 1];
|
||||
}
|
||||
|
@ -384,3 +430,80 @@ class LineException {
|
|||
|
||||
const LineException(this.methodName, this.fileName);
|
||||
}
|
||||
|
||||
class FrameEntry {
|
||||
final String callUri;
|
||||
final int callLine;
|
||||
final int callColumn;
|
||||
final String inlinedMethodName;
|
||||
final bool isEmpty;
|
||||
FrameEntry.push(
|
||||
this.callUri, this.callLine, this.callColumn, this.inlinedMethodName)
|
||||
: isEmpty = false;
|
||||
FrameEntry.pop(this.isEmpty)
|
||||
: callUri = null,
|
||||
callLine = null,
|
||||
callColumn = null,
|
||||
inlinedMethodName = null;
|
||||
|
||||
bool get isPush => callUri != null;
|
||||
bool get isPop => callUri == null;
|
||||
}
|
||||
|
||||
/// Search backwards in [sources] for a function declaration that includes the
|
||||
/// [start] offset.
|
||||
TargetEntry findEnclosingFunction(
|
||||
String sources, SourceFile file, int start, SingleMapping mapping) {
|
||||
if (sources == null) return null;
|
||||
int index = sources.lastIndexOf(': function(', start);
|
||||
if (index < 0) return null;
|
||||
index += 2;
|
||||
var line = file.getLine(index);
|
||||
var lineEntry = _findLineInternal(mapping, line);
|
||||
return _findColumn(line, file.getColumn(index), lineEntry);
|
||||
}
|
||||
|
||||
Map<int, List<FrameEntry>> _loadInlinedFrameData(
|
||||
SingleMapping mapping, String sourceMapText) {
|
||||
var json = jsonDecode(sourceMapText);
|
||||
var frames = <int, List<FrameEntry>>{};
|
||||
var extensions = json['x_org_dartlang_dart2js'];
|
||||
if (extensions == null) return null;
|
||||
List jsonFrames = extensions['frames'];
|
||||
if (jsonFrames == null) return null;
|
||||
|
||||
for (List values in jsonFrames) {
|
||||
if (values.length < 2) {
|
||||
print("warning: incomplete frame data: $values");
|
||||
continue;
|
||||
}
|
||||
|
||||
int offset = values[0];
|
||||
List<FrameEntry> entries = frames[offset] ??= [];
|
||||
if (entries.length > 0) {
|
||||
print("warning: duplicate entries for $offset");
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 1; i < values.length; i++) {
|
||||
var current = values[i];
|
||||
if (current == -1) {
|
||||
entries.add(new FrameEntry.pop(false));
|
||||
} else if (current == 0) {
|
||||
entries.add(new FrameEntry.pop(true));
|
||||
} else {
|
||||
if (current is List) {
|
||||
if (current.length == 4) {
|
||||
entries.add(new FrameEntry.push(mapping.urls[current[0]],
|
||||
current[1], current[2], mapping.names[current[3]]));
|
||||
} else {
|
||||
print("warning: unexpected entry $current");
|
||||
}
|
||||
} else {
|
||||
print("warning: unexpected entry $current");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
|
|
@ -144,6 +144,17 @@ class RecordingSourceMapper implements SourceMapper {
|
|||
nodeToSourceLocationsMap.register(node, codeOffset, sourceLocation);
|
||||
sourceMapper.register(node, codeOffset, sourceLocation);
|
||||
}
|
||||
|
||||
@override
|
||||
void registerPush(
|
||||
int codeOffset, SourceLocation sourceLocation, String inlinedMethodName) {
|
||||
sourceMapper.registerPush(codeOffset, sourceLocation, inlinedMethodName);
|
||||
}
|
||||
|
||||
@override
|
||||
void registerPop(int codeOffset, {bool isEmpty: false}) {
|
||||
sourceMapper.registerPop(codeOffset, isEmpty: isEmpty);
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper of [SourceInformationProcessor] that records source locations and
|
||||
|
@ -448,6 +459,13 @@ class _LocationRecorder implements SourceMapper, LocationMap {
|
|||
.add(sourceLocation);
|
||||
}
|
||||
|
||||
@override
|
||||
void registerPush(int codeOffset, SourceLocation sourceLocation,
|
||||
String inlinedMethodName) {}
|
||||
|
||||
@override
|
||||
void registerPop(int codeOffset, {bool isEmpty: false}) {}
|
||||
|
||||
Iterable<js.Node> get nodes => _nodeMap.keys;
|
||||
|
||||
Map<int, List<SourceLocation>> operator [](js.Node node) {
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import 'package:expect/expect.dart';
|
||||
|
||||
class MyClass {}
|
||||
|
||||
@NoInline()
|
||||
method3() {
|
||||
/*4:method3*/ throw new MyClass();
|
||||
}
|
||||
|
||||
method2() => /*3:method2*/ method3();
|
||||
method4() {
|
||||
/*2:method4(inlined)*/ method2();
|
||||
}
|
||||
|
||||
method1() {
|
||||
print('hi');
|
||||
/*1:method1(inlined)*/ method4();
|
||||
}
|
||||
|
||||
main() => /*0:main*/ method1();
|
|
@ -25,6 +25,7 @@ void main(List<String> args) {
|
|||
bool continuing = false;
|
||||
await for (FileSystemEntity entity in dataDir.list()) {
|
||||
String name = entity.uri.pathSegments.last;
|
||||
if (!name.endsWith('.dart')) continue;
|
||||
if (argResults.rest.isNotEmpty &&
|
||||
!argResults.rest.contains(name) &&
|
||||
!continuing) {
|
||||
|
@ -37,7 +38,8 @@ void main(List<String> args) {
|
|||
await testAnnotatedCode(annotatedCode,
|
||||
verbose: argResults['verbose'],
|
||||
printJs: argResults['print-js'],
|
||||
writeJs: argResults['write-js']);
|
||||
writeJs: argResults['write-js'],
|
||||
inlineData: name.endsWith('_inlining.dart'));
|
||||
if (argResults['continued']) {
|
||||
continuing = true;
|
||||
}
|
||||
|
@ -48,18 +50,25 @@ void main(List<String> args) {
|
|||
const String kernelMarker = 'kernel.';
|
||||
|
||||
Future testAnnotatedCode(String code,
|
||||
{bool printJs: false, bool writeJs: false, bool verbose: false}) async {
|
||||
{bool printJs: false,
|
||||
bool writeJs: false,
|
||||
bool verbose: false,
|
||||
bool inlineData: false}) async {
|
||||
Test test = processTestCode(code, [kernelMarker]);
|
||||
print(test.code);
|
||||
print('---from kernel------------------------------------------------------');
|
||||
await runTest(test, kernelMarker,
|
||||
printJs: printJs, writeJs: writeJs, verbose: verbose);
|
||||
printJs: printJs,
|
||||
writeJs: writeJs,
|
||||
verbose: verbose,
|
||||
inlineData: inlineData);
|
||||
}
|
||||
|
||||
Future runTest(Test test, String config,
|
||||
{bool printJs: false,
|
||||
bool writeJs: false,
|
||||
bool verbose: false,
|
||||
bool inlineData: false,
|
||||
List<String> options: const <String>[]}) async {
|
||||
List<LineException> testAfterExceptions = <LineException>[];
|
||||
if (config == kernelMarker) {
|
||||
|
@ -87,7 +96,8 @@ Future runTest(Test test, String config,
|
|||
verbose: verbose,
|
||||
printJs: printJs,
|
||||
writeJs: writeJs,
|
||||
stackTraceLimit: 100);
|
||||
stackTraceLimit: 100,
|
||||
expandDart2jsInliningData: inlineData);
|
||||
}
|
||||
|
||||
/// Lines allowed before the intended stack trace. Typically from helper
|
||||
|
|
Loading…
Reference in a new issue