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:
Sigmund Cherem 2018-07-31 21:41:50 +00:00 committed by commit-bot@chromium.org
parent 4206131030
commit cb7341fceb
11 changed files with 559 additions and 70 deletions

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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 {

View file

@ -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;
}

View file

@ -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';

View file

@ -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

View file

@ -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);

View file

@ -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;
}

View file

@ -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) {

View file

@ -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();

View file

@ -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