mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 13:08:10 +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);
|
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 {
|
abstract class SourceLocations {
|
||||||
/// The name identifying this source mapping.
|
/// The name identifying this source mapping.
|
||||||
String get name;
|
String get name;
|
||||||
|
@ -24,15 +29,31 @@ abstract class SourceLocations {
|
||||||
/// Adds a [sourceLocation] at the specified [targetOffset].
|
/// Adds a [sourceLocation] at the specified [targetOffset].
|
||||||
void addSourceLocation(int targetOffset, SourceLocation sourcePosition);
|
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.
|
/// Applies [f] to every target offset and associated source location.
|
||||||
void forEachSourceLocation(
|
void forEachSourceLocation(
|
||||||
void f(int targetOffset, SourceLocation sourceLocation));
|
void f(int targetOffset, SourceLocation sourceLocation));
|
||||||
|
|
||||||
|
/// Recorded inlining data per target-offset.
|
||||||
|
Map<int, List<FrameEntry>> get frameMarkers;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SourceLocationsImpl implements SourceLocations {
|
class _SourceLocationsImpl implements SourceLocations {
|
||||||
final String name;
|
final String name;
|
||||||
final AbstractCodeOutput codeOutput;
|
final AbstractCodeOutput codeOutput;
|
||||||
Map<int, List<SourceLocation>> markers = <int, List<SourceLocation>>{};
|
Map<int, List<SourceLocation>> markers = <int, List<SourceLocation>>{};
|
||||||
|
Map<int, List<FrameEntry>> frameMarkers = <int, List<FrameEntry>>{};
|
||||||
|
|
||||||
_SourceLocationsImpl(this.name, this.codeOutput);
|
_SourceLocationsImpl(this.name, this.codeOutput);
|
||||||
|
|
||||||
|
@ -44,6 +65,21 @@ class _SourceLocationsImpl implements SourceLocations {
|
||||||
sourceLocations.add(sourceLocation);
|
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
|
@override
|
||||||
void forEachSourceLocation(
|
void forEachSourceLocation(
|
||||||
void f(int targetOffset, SourceLocation sourceLocation)) {
|
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);
|
assert(name == other.name);
|
||||||
|
int length = codeOutput.length;
|
||||||
if (other.markers.length > 0) {
|
if (other.markers.length > 0) {
|
||||||
other.markers
|
other.markers
|
||||||
.forEach((int targetOffset, List<SourceLocation> sourceLocations) {
|
.forEach((int targetOffset, List<SourceLocation> sourceLocations) {
|
||||||
markers
|
(markers[length + targetOffset] ??= <SourceLocation>[])
|
||||||
.putIfAbsent(
|
|
||||||
codeOutput.length + targetOffset, () => <SourceLocation>[])
|
|
||||||
.addAll(sourceLocations);
|
.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
|
@override
|
||||||
void addBuffer(CodeBuffer other) {
|
void addBuffer(CodeBuffer other) {
|
||||||
other.sourceLocationsMap.forEach((String name, _SourceLocationsImpl other) {
|
other.sourceLocationsMap.forEach((String name, _SourceLocationsImpl other) {
|
||||||
createSourceLocations(name)._addSourceLocations(other);
|
createSourceLocations(name)._merge(other);
|
||||||
});
|
});
|
||||||
if (!other.isClosed) {
|
if (!other.isClosed) {
|
||||||
other.close();
|
other.close();
|
||||||
|
@ -138,8 +179,7 @@ abstract class AbstractCodeOutput extends CodeOutput {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_SourceLocationsImpl createSourceLocations(String name) {
|
_SourceLocationsImpl createSourceLocations(String name) {
|
||||||
return sourceLocationsMap.putIfAbsent(
|
return sourceLocationsMap[name] ??= new _SourceLocationsImpl(name, this);
|
||||||
name, () => new _SourceLocationsImpl(name, this));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,9 +74,19 @@ class KernelSourceInformationBuilder implements SourceInformationBuilder {
|
||||||
final MemberEntity _member;
|
final MemberEntity _member;
|
||||||
final String _name;
|
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)
|
KernelSourceInformationBuilder(this._elementMap, this._member)
|
||||||
: this._name =
|
: _name = computeKernelElementNameForSourceMaps(_elementMap, _member),
|
||||||
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]
|
/// Returns the [SourceLocation] for the [offset] within [node] using [name]
|
||||||
/// as the name of the source location.
|
/// as the name of the source location.
|
||||||
|
@ -106,8 +116,10 @@ class KernelSourceInformationBuilder implements SourceInformationBuilder {
|
||||||
SourceInformation _buildFunction(
|
SourceInformation _buildFunction(
|
||||||
String name, ir.TreeNode node, ir.FunctionNode functionNode) {
|
String name, ir.TreeNode node, ir.FunctionNode functionNode) {
|
||||||
if (functionNode.fileEndOffset != ir.TreeNode.noOffset) {
|
if (functionNode.fileEndOffset != ir.TreeNode.noOffset) {
|
||||||
return new PositionSourceInformation(_getSourceLocation(name, node),
|
return new PositionSourceInformation(
|
||||||
_getSourceLocation(name, functionNode, functionNode.fileEndOffset));
|
_getSourceLocation(name, node),
|
||||||
|
_getSourceLocation(name, functionNode, functionNode.fileEndOffset),
|
||||||
|
this.inliningContext);
|
||||||
}
|
}
|
||||||
return _buildTreeNode(node);
|
return _buildTreeNode(node);
|
||||||
}
|
}
|
||||||
|
@ -156,7 +168,9 @@ class KernelSourceInformationBuilder implements SourceInformationBuilder {
|
||||||
ir.TreeNode node, ir.FunctionNode functionNode) {
|
ir.TreeNode node, ir.FunctionNode functionNode) {
|
||||||
if (functionNode.fileEndOffset != ir.TreeNode.noOffset) {
|
if (functionNode.fileEndOffset != ir.TreeNode.noOffset) {
|
||||||
return new PositionSourceInformation(
|
return new PositionSourceInformation(
|
||||||
_getSourceLocation(_name, functionNode, functionNode.fileEndOffset));
|
_getSourceLocation(_name, functionNode, functionNode.fileEndOffset),
|
||||||
|
null,
|
||||||
|
this.inliningContext);
|
||||||
}
|
}
|
||||||
return _buildTreeNode(node);
|
return _buildTreeNode(node);
|
||||||
}
|
}
|
||||||
|
@ -176,7 +190,7 @@ class KernelSourceInformationBuilder implements SourceInformationBuilder {
|
||||||
} else {
|
} else {
|
||||||
location = _getSourceLocation(_name, node);
|
location = _getSourceLocation(_name, node);
|
||||||
}
|
}
|
||||||
return new PositionSourceInformation(location);
|
return new PositionSourceInformation(location, null, inliningContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates source information for the body of the current member.
|
/// Creates source information for the body of the current member.
|
||||||
|
@ -259,12 +273,27 @@ class KernelSourceInformationBuilder implements SourceInformationBuilder {
|
||||||
SourceInformation _buildTreeNode(ir.TreeNode node,
|
SourceInformation _buildTreeNode(ir.TreeNode node,
|
||||||
{SourceLocation closingPosition, String name}) {
|
{SourceLocation closingPosition, String name}) {
|
||||||
return new PositionSourceInformation(
|
return new PositionSourceInformation(
|
||||||
_getSourceLocation(name ?? _name, node), closingPosition);
|
_getSourceLocation(name ?? _name, node),
|
||||||
|
closingPosition,
|
||||||
|
inliningContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SourceInformationBuilder forContext(MemberEntity member) =>
|
SourceInformationBuilder forContext(
|
||||||
new KernelSourceInformationBuilder(_elementMap, member);
|
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
|
@override
|
||||||
SourceInformation buildSwitchCase(ir.Node node) => null;
|
SourceInformation buildSwitchCase(ir.Node node) => null;
|
||||||
|
@ -371,6 +400,11 @@ class KernelSourceInformationBuilder implements SourceInformationBuilder {
|
||||||
return _buildTreeNode(node);
|
return _buildTreeNode(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
SourceInformation buildAssert(ir.Node node) {
|
||||||
|
return _buildTreeNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SourceInformation buildNew(ir.Node node) {
|
SourceInformation buildNew(ir.Node node) {
|
||||||
return _buildTreeNode(node);
|
return _buildTreeNode(node);
|
||||||
|
@ -384,8 +418,8 @@ class KernelSourceInformationBuilder implements SourceInformationBuilder {
|
||||||
@override
|
@override
|
||||||
SourceInformation buildCall(
|
SourceInformation buildCall(
|
||||||
covariant ir.TreeNode receiver, covariant ir.TreeNode call) {
|
covariant ir.TreeNode receiver, covariant ir.TreeNode call) {
|
||||||
return new PositionSourceInformation(
|
return new PositionSourceInformation(_getSourceLocation(_name, receiver),
|
||||||
_getSourceLocation(_name, receiver), _getSourceLocation(_name, call));
|
_getSourceLocation(_name, call), inliningContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -393,6 +427,11 @@ class KernelSourceInformationBuilder implements SourceInformationBuilder {
|
||||||
return _buildTreeNode(node);
|
return _buildTreeNode(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
SourceInformation buildSet(ir.Node node) {
|
||||||
|
return _buildTreeNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SourceInformation buildLoop(ir.Node node) {
|
SourceInformation buildLoop(ir.Node node) {
|
||||||
return _buildTreeNode(node);
|
return _buildTreeNode(node);
|
||||||
|
@ -448,4 +487,9 @@ class KernelSourceLocation extends AbstractSourceLocation {
|
||||||
KernelSourceLocation(ir.Location location, this.offset, this.sourceName)
|
KernelSourceLocation(ir.Location location, this.offset, this.sourceName)
|
||||||
: sourceUri = location.file,
|
: sourceUri = location.file,
|
||||||
super.fromLocation(location);
|
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
|
@override
|
||||||
final SourceLocation innerPosition;
|
final SourceLocation innerPosition;
|
||||||
|
|
||||||
PositionSourceInformation(this.startPosition, [this.innerPosition]);
|
final List<FrameContext> inliningContext;
|
||||||
|
|
||||||
|
PositionSourceInformation(
|
||||||
|
this.startPosition, this.innerPosition, this.inliningContext);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<SourceLocation> get sourceLocations {
|
List<SourceLocation> get sourceLocations {
|
||||||
|
@ -275,14 +278,17 @@ class PositionSourceInformationProcessor extends SourceInformationProcessor {
|
||||||
final SourceInformationReader reader;
|
final SourceInformationReader reader;
|
||||||
CodePositionMap codePositionMap;
|
CodePositionMap codePositionMap;
|
||||||
List<TraceListener> traceListeners;
|
List<TraceListener> traceListeners;
|
||||||
|
InliningTraceListener inliningListener;
|
||||||
|
|
||||||
PositionSourceInformationProcessor(SourceMapperProvider provider, this.reader,
|
PositionSourceInformationProcessor(SourceMapperProvider provider, this.reader,
|
||||||
[Coverage coverage]) {
|
[Coverage coverage]) {
|
||||||
codePositionMap = coverage != null
|
codePositionMap = coverage != null
|
||||||
? new CodePositionCoverage(codePositionRecorder, coverage)
|
? new CodePositionCoverage(codePositionRecorder, coverage)
|
||||||
: codePositionRecorder;
|
: codePositionRecorder;
|
||||||
|
var sourceMapper = provider.createSourceMapper(id);
|
||||||
traceListeners = [
|
traceListeners = [
|
||||||
new PositionTraceListener(provider.createSourceMapper(id), reader)
|
new PositionTraceListener(sourceMapper, reader),
|
||||||
|
inliningListener = new InliningTraceListener(sourceMapper, reader),
|
||||||
];
|
];
|
||||||
if (coverage != null) {
|
if (coverage != null) {
|
||||||
traceListeners.add(new CoverageListener(coverage, reader));
|
traceListeners.add(new CoverageListener(coverage, reader));
|
||||||
|
@ -291,6 +297,7 @@ class PositionSourceInformationProcessor extends SourceInformationProcessor {
|
||||||
|
|
||||||
void process(js.Node node, BufferedCodeOutput code) {
|
void process(js.Node node, BufferedCodeOutput code) {
|
||||||
new JavaScriptTracer(codePositionMap, reader, traceListeners).apply(node);
|
new JavaScriptTracer(codePositionMap, reader, traceListeners).apply(node);
|
||||||
|
inliningListener?.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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].
|
/// [TraceListener] that register [SourceLocation]s with a [SourceMapper].
|
||||||
class PositionTraceListener extends TraceListener
|
class PositionTraceListener extends TraceListener
|
||||||
with NodeToSourceInformationMixin {
|
with NodeToSourceInformationMixin {
|
||||||
|
|
|
@ -29,13 +29,33 @@ abstract class SourceInformation extends JavaScriptNodeSourceInformation {
|
||||||
/// The source location associated with the end of the JS node.
|
/// The source location associated with the end of the JS node.
|
||||||
SourceLocation get endPosition => null;
|
SourceLocation get endPosition => null;
|
||||||
|
|
||||||
/// All source locations associated with this source information.
|
/// A list containing start, inner, and end positions.
|
||||||
List<SourceLocation> get sourceLocations;
|
List<SourceLocation> get sourceLocations;
|
||||||
|
|
||||||
|
/// A list of inlining context locations.
|
||||||
|
List<FrameContext> get inliningContext => null;
|
||||||
|
|
||||||
/// Return a short textual representation of the source location.
|
/// Return a short textual representation of the source location.
|
||||||
String get shortText;
|
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].
|
/// Strategy for creating, processing and applying [SourceInformation].
|
||||||
class SourceInformationStrategy {
|
class SourceInformationStrategy {
|
||||||
const SourceInformationStrategy();
|
const SourceInformationStrategy();
|
||||||
|
@ -57,8 +77,11 @@ class SourceInformationStrategy {
|
||||||
class SourceInformationBuilder {
|
class SourceInformationBuilder {
|
||||||
const SourceInformationBuilder();
|
const SourceInformationBuilder();
|
||||||
|
|
||||||
/// Create a [SourceInformationBuilder] for [member].
|
/// Create a [SourceInformationBuilder] for [member] with additional inlining
|
||||||
SourceInformationBuilder forContext(covariant MemberEntity member) => this;
|
/// [context].
|
||||||
|
SourceInformationBuilder forContext(
|
||||||
|
covariant MemberEntity member, SourceInformation context) =>
|
||||||
|
this;
|
||||||
|
|
||||||
/// Generate [SourceInformation] for the declaration of the [member].
|
/// Generate [SourceInformation] for the declaration of the [member].
|
||||||
SourceInformation buildDeclaration(covariant MemberEntity member) => null;
|
SourceInformation buildDeclaration(covariant MemberEntity member) => null;
|
||||||
|
@ -85,13 +108,13 @@ class SourceInformationBuilder {
|
||||||
/// Generate [SourceInformation] for the loop [node].
|
/// Generate [SourceInformation] for the loop [node].
|
||||||
SourceInformation buildLoop(ir.Node node) => null;
|
SourceInformation buildLoop(ir.Node node) => null;
|
||||||
|
|
||||||
/// Generate [SourceInformation] for a read access like `a.b` where in
|
/// Generate [SourceInformation] for a read access like `a.b`.
|
||||||
/// [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.
|
|
||||||
SourceInformation buildGet(ir.Node node) => null;
|
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;
|
SourceInformation buildCall(ir.Node receiver, ir.Node call) => null;
|
||||||
|
|
||||||
/// Generate [SourceInformation] for the if statement in [node].
|
/// Generate [SourceInformation] for the if statement in [node].
|
||||||
|
@ -103,6 +126,9 @@ class SourceInformationBuilder {
|
||||||
/// Generate [SourceInformation] for the throw in [node].
|
/// Generate [SourceInformation] for the throw in [node].
|
||||||
SourceInformation buildThrow(ir.Node node) => null;
|
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].
|
/// Generate [SourceInformation] for the assignment in [node].
|
||||||
SourceInformation buildAssignment(ir.Node node) => null;
|
SourceInformation buildAssignment(ir.Node node) => null;
|
||||||
|
|
||||||
|
@ -234,6 +260,9 @@ abstract class AbstractSourceLocation extends SourceLocation {
|
||||||
|
|
||||||
AbstractSourceLocation.fromLocation(this._location) : _sourceFile = null;
|
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.
|
/// The absolute URI of the source file of this source location.
|
||||||
Uri get sourceUri => _sourceFile.uri;
|
Uri get sourceUri => _sourceFile.uri;
|
||||||
|
|
||||||
|
@ -338,3 +367,29 @@ class NoSourceLocationMarker extends SourceLocation {
|
||||||
|
|
||||||
String toString() => '<no-location>';
|
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 '../util/util.dart';
|
||||||
import 'location_provider.dart';
|
import 'location_provider.dart';
|
||||||
import 'code_output.dart' show SourceLocationsProvider, SourceLocations;
|
import 'code_output.dart' show SourceLocationsProvider, SourceLocations;
|
||||||
import 'source_information.dart' show SourceLocation;
|
import 'source_information.dart' show SourceLocation, FrameEntry;
|
||||||
|
|
||||||
class SourceMapBuilder {
|
class SourceMapBuilder {
|
||||||
final String version;
|
final String version;
|
||||||
|
@ -28,13 +28,17 @@ class SourceMapBuilder {
|
||||||
final Map<String, String> minifiedGlobalNames;
|
final Map<String, String> minifiedGlobalNames;
|
||||||
final Map<String, String> minifiedInstanceNames;
|
final Map<String, String> minifiedInstanceNames;
|
||||||
|
|
||||||
|
/// Extension used to deobfuscate inlined stack frames.
|
||||||
|
final Map<int, List<FrameEntry>> frames;
|
||||||
|
|
||||||
SourceMapBuilder(
|
SourceMapBuilder(
|
||||||
this.version,
|
this.version,
|
||||||
this.sourceMapUri,
|
this.sourceMapUri,
|
||||||
this.targetFileUri,
|
this.targetFileUri,
|
||||||
this.locationProvider,
|
this.locationProvider,
|
||||||
this.minifiedGlobalNames,
|
this.minifiedGlobalNames,
|
||||||
this.minifiedInstanceNames);
|
this.minifiedInstanceNames,
|
||||||
|
this.frames);
|
||||||
|
|
||||||
void addMapping(int targetOffset, SourceLocation sourceLocation) {
|
void addMapping(int targetOffset, SourceLocation sourceLocation) {
|
||||||
entries.add(new SourceMapEntry(sourceLocation, targetOffset));
|
entries.add(new SourceMapEntry(sourceLocation, targetOffset));
|
||||||
|
@ -84,8 +88,7 @@ class SourceMapBuilder {
|
||||||
IndexMap<Uri> uriMap = new IndexMap<Uri>();
|
IndexMap<Uri> uriMap = new IndexMap<Uri>();
|
||||||
IndexMap<String> nameMap = new IndexMap<String>();
|
IndexMap<String> nameMap = new IndexMap<String>();
|
||||||
|
|
||||||
lineColumnMap.forEachElement((SourceMapEntry entry) {
|
void registerLocation(SourceLocation sourceLocation) {
|
||||||
SourceLocation sourceLocation = entry.sourceLocation;
|
|
||||||
if (sourceLocation != null) {
|
if (sourceLocation != null) {
|
||||||
if (sourceLocation.sourceUri != null) {
|
if (sourceLocation.sourceUri != null) {
|
||||||
uriMap.register(sourceLocation.sourceUri);
|
uriMap.register(sourceLocation.sourceUri);
|
||||||
|
@ -94,10 +97,22 @@ class SourceMapBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lineColumnMap.forEachElement((SourceMapEntry entry) {
|
||||||
|
registerLocation(entry.sourceLocation);
|
||||||
});
|
});
|
||||||
|
|
||||||
minifiedGlobalNames.values.forEach(nameMap.register);
|
minifiedGlobalNames.values.forEach(nameMap.register);
|
||||||
minifiedInstanceNames.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();
|
StringBuffer mappingsBuffer = new StringBuffer();
|
||||||
writeEntries(lineColumnMap, uriMap, nameMap, mappingsBuffer);
|
writeEntries(lineColumnMap, uriMap, nameMap, mappingsBuffer);
|
||||||
|
@ -132,7 +147,10 @@ class SourceMapBuilder {
|
||||||
buffer.write(',\n');
|
buffer.write(',\n');
|
||||||
buffer.write(' "instance": ');
|
buffer.write(' "instance": ');
|
||||||
writeMinifiedNames(minifiedInstanceNames, nameMap, buffer);
|
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();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,6 +224,37 @@ class SourceMapBuilder {
|
||||||
buffer.write('}');
|
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
|
/// 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].
|
/// make it point to the source map file in [sourceMapUri].
|
||||||
static String generateSourceMapTag(Uri sourceMapUri, Uri fileUri) {
|
static String generateSourceMapTag(Uri sourceMapUri, Uri fileUri) {
|
||||||
|
@ -244,7 +293,8 @@ class SourceMapBuilder {
|
||||||
fileUri,
|
fileUri,
|
||||||
locationProvider,
|
locationProvider,
|
||||||
minifiedGlobalNames,
|
minifiedGlobalNames,
|
||||||
minifiedInstanceNames);
|
minifiedInstanceNames,
|
||||||
|
sourceLocations.frameMarkers);
|
||||||
sourceLocations.forEachSourceLocation(sourceMapBuilder.addMapping);
|
sourceLocations.forEachSourceLocation(sourceMapBuilder.addMapping);
|
||||||
String sourceMap = sourceMapBuilder.build();
|
String sourceMap = sourceMapBuilder.build();
|
||||||
String extension = 'js.map';
|
String extension = 'js.map';
|
||||||
|
|
|
@ -73,6 +73,16 @@ class SourceMapperProviderImpl implements SourceMapperProvider {
|
||||||
abstract class SourceMapper {
|
abstract class SourceMapper {
|
||||||
/// Associate [codeOffset] with [sourceLocation] for [node].
|
/// Associate [codeOffset] with [sourceLocation] for [node].
|
||||||
void register(Node node, int codeOffset, SourceLocation sourceLocation);
|
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
|
/// 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) {
|
void register(Node node, int codeOffset, SourceLocation sourceLocation) {
|
||||||
sourceLocations.addSourceLocation(codeOffset, 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
|
/// A processor that associates [SourceInformation] with code position of
|
||||||
|
|
|
@ -138,7 +138,7 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
||||||
) : this.targetElement = _effectiveTargetElementFor(initialTargetElement),
|
) : this.targetElement = _effectiveTargetElementFor(initialTargetElement),
|
||||||
_infoReporter = compiler.dumpInfoTask,
|
_infoReporter = compiler.dumpInfoTask,
|
||||||
_allocatorAnalysis = closedWorld.allocatorAnalysis {
|
_allocatorAnalysis = closedWorld.allocatorAnalysis {
|
||||||
_enterFrame(targetElement);
|
_enterFrame(targetElement, null);
|
||||||
this.loopHandler = new KernelLoopHandler(this);
|
this.loopHandler = new KernelLoopHandler(this);
|
||||||
typeBuilder = new KernelTypeBuilder(this, _elementMap, _globalLocalsMap);
|
typeBuilder = new KernelTypeBuilder(this, _elementMap, _globalLocalsMap);
|
||||||
graph.element = targetElement;
|
graph.element = targetElement;
|
||||||
|
@ -163,7 +163,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
||||||
return member;
|
return member;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _enterFrame(MemberEntity member) {
|
void _enterFrame(
|
||||||
|
MemberEntity member, SourceInformation callSourceInformation) {
|
||||||
AsyncMarker asyncMarker = AsyncMarker.SYNC;
|
AsyncMarker asyncMarker = AsyncMarker.SYNC;
|
||||||
ir.FunctionNode function = getFunctionNode(_elementMap, member);
|
ir.FunctionNode function = getFunctionNode(_elementMap, member);
|
||||||
if (function != null) {
|
if (function != null) {
|
||||||
|
@ -176,7 +177,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
||||||
_globalLocalsMap.getLocalsMap(member),
|
_globalLocalsMap.getLocalsMap(member),
|
||||||
new KernelToTypeInferenceMapImpl(member, globalInferenceResults),
|
new KernelToTypeInferenceMapImpl(member, globalInferenceResults),
|
||||||
_currentFrame != null
|
_currentFrame != null
|
||||||
? _currentFrame.sourceInformationBuilder.forContext(member)
|
? _currentFrame.sourceInformationBuilder
|
||||||
|
.forContext(member, callSourceInformation)
|
||||||
: _sourceInformationStrategy.createBuilderForContext(member));
|
: _sourceInformationStrategy.createBuilderForContext(member));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -579,7 +581,9 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
||||||
|
|
||||||
ConstructorEntity inlinedConstructor = _elementMap.getConstructor(body);
|
ConstructorEntity inlinedConstructor = _elementMap.getConstructor(body);
|
||||||
|
|
||||||
inlinedFrom(inlinedConstructor, () {
|
inlinedFrom(
|
||||||
|
inlinedConstructor, _sourceInformationBuilder.buildCall(body, body),
|
||||||
|
() {
|
||||||
void handleParameter(ir.VariableDeclaration node) {
|
void handleParameter(ir.VariableDeclaration node) {
|
||||||
Local parameter = localsMap.getLocalVariable(node);
|
Local parameter = localsMap.getLocalVariable(node);
|
||||||
// If [parameter] is boxed, it will be a field in the box passed as
|
// 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
|
/// Sets context for generating code that is the result of inlining
|
||||||
/// [inlinedTarget].
|
/// [inlinedTarget].
|
||||||
inlinedFrom(MemberEntity inlinedTarget, f()) {
|
inlinedFrom(MemberEntity inlinedTarget,
|
||||||
|
SourceInformation callSourceInformation, f()) {
|
||||||
reporter.withCurrentElement(inlinedTarget, () {
|
reporter.withCurrentElement(inlinedTarget, () {
|
||||||
_enterFrame(inlinedTarget);
|
_enterFrame(inlinedTarget, callSourceInformation);
|
||||||
var result = f();
|
var result = f();
|
||||||
_leaveFrame();
|
_leaveFrame();
|
||||||
return result;
|
return result;
|
||||||
|
@ -724,7 +729,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
||||||
// TODO(sra): It would be sufficient to know the context was a field
|
// TODO(sra): It would be sufficient to know the context was a field
|
||||||
// initializer.
|
// initializer.
|
||||||
if (!_allocatorAnalysis.isInitializedInAllocator(field)) {
|
if (!_allocatorAnalysis.isInitializedInAllocator(field)) {
|
||||||
inlinedFrom(field, () {
|
inlinedFrom(field,
|
||||||
|
_sourceInformationBuilder.buildAssignment(node.initializer), () {
|
||||||
node.initializer.accept(this);
|
node.initializer.accept(this);
|
||||||
constructorData.fieldValues[field] = pop();
|
constructorData.fieldValues[field] = pop();
|
||||||
});
|
});
|
||||||
|
@ -902,7 +908,9 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
||||||
ConstructorEntity element = _elementMap.getConstructor(constructor);
|
ConstructorEntity element = _elementMap.getConstructor(constructor);
|
||||||
ScopeInfo oldScopeInfo = localsHandler.scopeInfo;
|
ScopeInfo oldScopeInfo = localsHandler.scopeInfo;
|
||||||
|
|
||||||
inlinedFrom(element, () {
|
inlinedFrom(
|
||||||
|
element, _sourceInformationBuilder.buildCall(initializer, initializer),
|
||||||
|
() {
|
||||||
void handleParameter(ir.VariableDeclaration node) {
|
void handleParameter(ir.VariableDeclaration node) {
|
||||||
Local parameter = localsMap.getLocalVariable(node);
|
Local parameter = localsMap.getLocalVariable(node);
|
||||||
HInstruction argument = arguments[index++];
|
HInstruction argument = arguments[index++];
|
||||||
|
@ -1236,7 +1244,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
||||||
_commonElements.closureConverter,
|
_commonElements.closureConverter,
|
||||||
[argument, graph.addConstantInt(arity, closedWorld)],
|
[argument, graph.addConstantInt(arity, closedWorld)],
|
||||||
abstractValueDomain.dynamicType,
|
abstractValueDomain.dynamicType,
|
||||||
const <DartType>[]);
|
const <DartType>[],
|
||||||
|
sourceInformation: null);
|
||||||
argument = pop();
|
argument = pop();
|
||||||
}
|
}
|
||||||
inputs.add(argument);
|
inputs.add(argument);
|
||||||
|
@ -1379,7 +1388,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
||||||
[prefixConstant, uriConstant],
|
[prefixConstant, uriConstant],
|
||||||
_typeInferenceMap
|
_typeInferenceMap
|
||||||
.getReturnTypeOf(_commonElements.checkDeferredIsLoaded),
|
.getReturnTypeOf(_commonElements.checkDeferredIsLoaded),
|
||||||
const <DartType>[]);
|
const <DartType>[],
|
||||||
|
sourceInformation: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -2104,13 +2114,15 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
||||||
@override
|
@override
|
||||||
void visitAssertStatement(ir.AssertStatement node) {
|
void visitAssertStatement(ir.AssertStatement node) {
|
||||||
if (!options.enableUserAssertions) return;
|
if (!options.enableUserAssertions) return;
|
||||||
|
var sourceInformation = _sourceInformationBuilder.buildAssert(node);
|
||||||
if (node.message == null) {
|
if (node.message == null) {
|
||||||
node.condition.accept(this);
|
node.condition.accept(this);
|
||||||
_pushStaticInvocation(
|
_pushStaticInvocation(
|
||||||
_commonElements.assertHelper,
|
_commonElements.assertHelper,
|
||||||
<HInstruction>[pop()],
|
<HInstruction>[pop()],
|
||||||
_typeInferenceMap.getReturnTypeOf(_commonElements.assertHelper),
|
_typeInferenceMap.getReturnTypeOf(_commonElements.assertHelper),
|
||||||
const <DartType>[]);
|
const <DartType>[],
|
||||||
|
sourceInformation: sourceInformation);
|
||||||
pop();
|
pop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2122,7 +2134,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
||||||
_commonElements.assertTest,
|
_commonElements.assertTest,
|
||||||
<HInstruction>[pop()],
|
<HInstruction>[pop()],
|
||||||
_typeInferenceMap.getReturnTypeOf(_commonElements.assertTest),
|
_typeInferenceMap.getReturnTypeOf(_commonElements.assertTest),
|
||||||
const <DartType>[]);
|
const <DartType>[],
|
||||||
|
sourceInformation: sourceInformation);
|
||||||
}
|
}
|
||||||
|
|
||||||
void fail() {
|
void fail() {
|
||||||
|
@ -2131,7 +2144,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
||||||
_commonElements.assertThrow,
|
_commonElements.assertThrow,
|
||||||
<HInstruction>[pop()],
|
<HInstruction>[pop()],
|
||||||
_typeInferenceMap.getReturnTypeOf(_commonElements.assertThrow),
|
_typeInferenceMap.getReturnTypeOf(_commonElements.assertThrow),
|
||||||
const <DartType>[]);
|
const <DartType>[],
|
||||||
|
sourceInformation: sourceInformation);
|
||||||
pop();
|
pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2814,7 +2828,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
||||||
|
|
||||||
addImplicitInstantiation(type);
|
addImplicitInstantiation(type);
|
||||||
_pushStaticInvocation(
|
_pushStaticInvocation(
|
||||||
constructor, inputs, instructionType, const <DartType>[]);
|
constructor, inputs, instructionType, const <DartType>[],
|
||||||
|
sourceInformation: _sourceInformationBuilder.buildNew(node));
|
||||||
removeImplicitInstantiation(type);
|
removeImplicitInstantiation(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2914,7 +2929,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
||||||
FunctionEntity setter = _elementMap.getMember(staticTarget);
|
FunctionEntity setter = _elementMap.getMember(staticTarget);
|
||||||
// Invoke the setter
|
// Invoke the setter
|
||||||
_pushStaticInvocation(setter, <HInstruction>[value],
|
_pushStaticInvocation(setter, <HInstruction>[value],
|
||||||
_typeInferenceMap.getReturnTypeOf(setter), const <DartType>[]);
|
_typeInferenceMap.getReturnTypeOf(setter), const <DartType>[],
|
||||||
|
sourceInformation: _sourceInformationBuilder.buildSet(node));
|
||||||
pop();
|
pop();
|
||||||
} else {
|
} else {
|
||||||
add(new HStaticStore(
|
add(new HStaticStore(
|
||||||
|
@ -3662,8 +3678,9 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_addTypeArguments(arguments, typeArguments,
|
SourceInformation sourceInformation =
|
||||||
_sourceInformationBuilder.buildCall(invocation, invocation));
|
_sourceInformationBuilder.buildCall(invocation, invocation);
|
||||||
|
_addTypeArguments(arguments, typeArguments, sourceInformation);
|
||||||
|
|
||||||
HInstruction argumentsInstruction = buildLiteralList(arguments);
|
HInstruction argumentsInstruction = buildLiteralList(arguments);
|
||||||
add(argumentsInstruction);
|
add(argumentsInstruction);
|
||||||
|
@ -3697,7 +3714,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
||||||
typeArgumentCount,
|
typeArgumentCount,
|
||||||
],
|
],
|
||||||
abstractValueDomain.dynamicType,
|
abstractValueDomain.dynamicType,
|
||||||
const <DartType>[]);
|
const <DartType>[],
|
||||||
|
sourceInformation: sourceInformation);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _unexpectedForeignArguments(ir.StaticInvocation invocation,
|
bool _unexpectedForeignArguments(ir.StaticInvocation invocation,
|
||||||
|
@ -4472,7 +4490,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
||||||
graph.addConstantInt(typeArguments.length, closedWorld),
|
graph.addConstantInt(typeArguments.length, closedWorld),
|
||||||
],
|
],
|
||||||
abstractValueDomain.dynamicType,
|
abstractValueDomain.dynamicType,
|
||||||
typeArguments);
|
typeArguments,
|
||||||
|
sourceInformation: sourceInformation);
|
||||||
|
|
||||||
_buildInvokeSuper(Selectors.noSuchMethod_, containingClass, noSuchMethod,
|
_buildInvokeSuper(Selectors.noSuchMethod_, containingClass, noSuchMethod,
|
||||||
<HInstruction>[pop()], typeArguments, sourceInformation);
|
<HInstruction>[pop()], typeArguments, sourceInformation);
|
||||||
|
@ -5167,7 +5186,7 @@ class KernelSsaGraphBuilder extends ir.Visitor
|
||||||
List<HInstruction> compiledArguments = _completeCallArgumentsList(
|
List<HInstruction> compiledArguments = _completeCallArgumentsList(
|
||||||
function, selector, providedArguments, currentNode);
|
function, selector, providedArguments, currentNode);
|
||||||
_enterInlinedMethod(function, compiledArguments, instanceType);
|
_enterInlinedMethod(function, compiledArguments, instanceType);
|
||||||
inlinedFrom(function, () {
|
inlinedFrom(function, sourceInformation, () {
|
||||||
if (!isReachable) {
|
if (!isReachable) {
|
||||||
_emitReturn(graph.addConstantNull(closedWorld), sourceInformation);
|
_emitReturn(graph.addConstantNull(closedWorld), sourceInformation);
|
||||||
} else {
|
} else {
|
||||||
|
@ -6007,7 +6026,9 @@ class TryCatchFinallyBuilder {
|
||||||
[exception],
|
[exception],
|
||||||
kernelBuilder._typeInferenceMap.getReturnTypeOf(
|
kernelBuilder._typeInferenceMap.getReturnTypeOf(
|
||||||
kernelBuilder._commonElements.traceFromException),
|
kernelBuilder._commonElements.traceFromException),
|
||||||
const <DartType>[]);
|
const <DartType>[],
|
||||||
|
sourceInformation:
|
||||||
|
kernelBuilder._sourceInformationBuilder.buildCatch(catchBlock));
|
||||||
HInstruction traceInstruction = kernelBuilder.pop();
|
HInstruction traceInstruction = kernelBuilder.pop();
|
||||||
Local traceVariable =
|
Local traceVariable =
|
||||||
kernelBuilder.localsMap.getLocalVariable(catchBlock.stackTrace);
|
kernelBuilder.localsMap.getLocalVariable(catchBlock.stackTrace);
|
||||||
|
|
|
@ -4,10 +4,12 @@
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:convert' show jsonDecode;
|
||||||
|
|
||||||
import 'package:expect/expect.dart';
|
import 'package:expect/expect.dart';
|
||||||
import 'package:source_maps/source_maps.dart';
|
import 'package:source_maps/source_maps.dart';
|
||||||
import 'package:source_maps/src/utils.dart';
|
import 'package:source_maps/src/utils.dart';
|
||||||
|
import 'package:source_span/source_span.dart';
|
||||||
|
|
||||||
import 'annotated_code_helper.dart';
|
import 'annotated_code_helper.dart';
|
||||||
|
|
||||||
|
@ -105,7 +107,8 @@ Future testStackTrace(Test test, String config, CompileFunc compile,
|
||||||
bool useJsMethodNamesOnAbsence: false,
|
bool useJsMethodNamesOnAbsence: false,
|
||||||
String Function(String name) jsNameConverter: identityConverter,
|
String Function(String name) jsNameConverter: identityConverter,
|
||||||
Directory forcedTmpDir: null,
|
Directory forcedTmpDir: null,
|
||||||
int stackTraceLimit: 10}) async {
|
int stackTraceLimit: 10,
|
||||||
|
expandDart2jsInliningData: false}) async {
|
||||||
Expect.isTrue(test.expectationMap.keys.contains(config),
|
Expect.isTrue(test.expectationMap.keys.contains(config),
|
||||||
"No expectations found for '$config' in ${test.expectationMap.keys}");
|
"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");
|
sourceMapFile.existsSync(), "Source map not generated for $input");
|
||||||
String sourceMapText = sourceMapFile.readAsStringSync();
|
String sourceMapText = sourceMapFile.readAsStringSync();
|
||||||
SingleMapping sourceMap = parse(sourceMapText);
|
SingleMapping sourceMap = parse(sourceMapText);
|
||||||
|
String jsOutput = new File(output).readAsStringSync();
|
||||||
|
|
||||||
if (printJs) {
|
if (printJs) {
|
||||||
print('JavaScript output:');
|
print('JavaScript output:');
|
||||||
print(new File(output).readAsStringSync());
|
print(jsOutput);
|
||||||
}
|
}
|
||||||
if (writeJs) {
|
if (writeJs) {
|
||||||
new File('out.js').writeAsStringSync(new File(output).readAsStringSync());
|
new File('out.js').writeAsStringSync(jsOutput);
|
||||||
new File('out.js.map').writeAsStringSync(sourceMapText);
|
new File('out.js.map').writeAsStringSync(sourceMapText);
|
||||||
}
|
}
|
||||||
print("Running d8 $output");
|
print("Running d8 $output");
|
||||||
|
@ -161,18 +165,58 @@ Future testStackTrace(Test test, String config, CompileFunc compile,
|
||||||
if (targetEntry == null || targetEntry.sourceUrlId == null) {
|
if (targetEntry == null || targetEntry.sourceUrlId == null) {
|
||||||
dartStackTrace.add(line);
|
dartStackTrace.add(line);
|
||||||
} else {
|
} 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;
|
String methodName;
|
||||||
if (targetEntry.sourceNameId != null) {
|
if (targetEntry.sourceNameId != null) {
|
||||||
methodName = sourceMap.names[targetEntry.sourceNameId];
|
methodName = sourceMap.names[targetEntry.sourceNameId];
|
||||||
} else if (useJsMethodNamesOnAbsence) {
|
} else if (useJsMethodNamesOnAbsence) {
|
||||||
methodName = jsNameConverter(line.methodName);
|
methodName = jsNameConverter(line.methodName);
|
||||||
}
|
}
|
||||||
String fileName;
|
|
||||||
if (targetEntry.sourceUrlId != null) {
|
dartStackTrace.add(new StackTraceLine(
|
||||||
fileName = sourceMap.urls[targetEntry.sourceUrlId];
|
methodName, fileName, targetLine, targetColumn,
|
||||||
}
|
|
||||||
dartStackTrace.add(new StackTraceLine(methodName, fileName,
|
|
||||||
targetEntry.sourceLine + 1, targetEntry.sourceColumn + 1,
|
|
||||||
isMapped: true));
|
isMapped: true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -344,8 +388,10 @@ TargetLineEntry _findLine(SingleMapping sourceMap, StackTraceLine stLine) {
|
||||||
String filename = stLine.fileName
|
String filename = stLine.fileName
|
||||||
.substring(stLine.fileName.lastIndexOf(new RegExp("[\\\/]")) + 1);
|
.substring(stLine.fileName.lastIndexOf(new RegExp("[\\\/]")) + 1);
|
||||||
if (sourceMap.targetUrl != filename) return null;
|
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);
|
int index = binarySearch(sourceMap.lines, (e) => e.line > line);
|
||||||
return (index <= 0) ? null : sourceMap.lines[index - 1];
|
return (index <= 0) ? null : sourceMap.lines[index - 1];
|
||||||
}
|
}
|
||||||
|
@ -384,3 +430,80 @@ class LineException {
|
||||||
|
|
||||||
const LineException(this.methodName, this.fileName);
|
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);
|
nodeToSourceLocationsMap.register(node, codeOffset, sourceLocation);
|
||||||
sourceMapper.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
|
/// A wrapper of [SourceInformationProcessor] that records source locations and
|
||||||
|
@ -448,6 +459,13 @@ class _LocationRecorder implements SourceMapper, LocationMap {
|
||||||
.add(sourceLocation);
|
.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;
|
Iterable<js.Node> get nodes => _nodeMap.keys;
|
||||||
|
|
||||||
Map<int, List<SourceLocation>> operator [](js.Node node) {
|
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;
|
bool continuing = false;
|
||||||
await for (FileSystemEntity entity in dataDir.list()) {
|
await for (FileSystemEntity entity in dataDir.list()) {
|
||||||
String name = entity.uri.pathSegments.last;
|
String name = entity.uri.pathSegments.last;
|
||||||
|
if (!name.endsWith('.dart')) continue;
|
||||||
if (argResults.rest.isNotEmpty &&
|
if (argResults.rest.isNotEmpty &&
|
||||||
!argResults.rest.contains(name) &&
|
!argResults.rest.contains(name) &&
|
||||||
!continuing) {
|
!continuing) {
|
||||||
|
@ -37,7 +38,8 @@ void main(List<String> args) {
|
||||||
await testAnnotatedCode(annotatedCode,
|
await testAnnotatedCode(annotatedCode,
|
||||||
verbose: argResults['verbose'],
|
verbose: argResults['verbose'],
|
||||||
printJs: argResults['print-js'],
|
printJs: argResults['print-js'],
|
||||||
writeJs: argResults['write-js']);
|
writeJs: argResults['write-js'],
|
||||||
|
inlineData: name.endsWith('_inlining.dart'));
|
||||||
if (argResults['continued']) {
|
if (argResults['continued']) {
|
||||||
continuing = true;
|
continuing = true;
|
||||||
}
|
}
|
||||||
|
@ -48,18 +50,25 @@ void main(List<String> args) {
|
||||||
const String kernelMarker = 'kernel.';
|
const String kernelMarker = 'kernel.';
|
||||||
|
|
||||||
Future testAnnotatedCode(String code,
|
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]);
|
Test test = processTestCode(code, [kernelMarker]);
|
||||||
print(test.code);
|
print(test.code);
|
||||||
print('---from kernel------------------------------------------------------');
|
print('---from kernel------------------------------------------------------');
|
||||||
await runTest(test, kernelMarker,
|
await runTest(test, kernelMarker,
|
||||||
printJs: printJs, writeJs: writeJs, verbose: verbose);
|
printJs: printJs,
|
||||||
|
writeJs: writeJs,
|
||||||
|
verbose: verbose,
|
||||||
|
inlineData: inlineData);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future runTest(Test test, String config,
|
Future runTest(Test test, String config,
|
||||||
{bool printJs: false,
|
{bool printJs: false,
|
||||||
bool writeJs: false,
|
bool writeJs: false,
|
||||||
bool verbose: false,
|
bool verbose: false,
|
||||||
|
bool inlineData: false,
|
||||||
List<String> options: const <String>[]}) async {
|
List<String> options: const <String>[]}) async {
|
||||||
List<LineException> testAfterExceptions = <LineException>[];
|
List<LineException> testAfterExceptions = <LineException>[];
|
||||||
if (config == kernelMarker) {
|
if (config == kernelMarker) {
|
||||||
|
@ -87,7 +96,8 @@ Future runTest(Test test, String config,
|
||||||
verbose: verbose,
|
verbose: verbose,
|
||||||
printJs: printJs,
|
printJs: printJs,
|
||||||
writeJs: writeJs,
|
writeJs: writeJs,
|
||||||
stackTraceLimit: 100);
|
stackTraceLimit: 100,
|
||||||
|
expandDart2jsInliningData: inlineData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lines allowed before the intended stack trace. Typically from helper
|
/// Lines allowed before the intended stack trace. Typically from helper
|
||||||
|
|
Loading…
Reference in a new issue