blob: 520fbb4016194f243cdf50caba410548e5be0068 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// Source information system mapping that attempts a semantic mapping between
/// offsets of JavaScript code points to offsets of Dart code points.
library dart2js.source_information.kernel;
import 'package:kernel/ast.dart' as ir;
import '../elements/entities.dart';
import '../js_model/element_map.dart';
import '../js_model/js_strategy.dart';
import '../universe/call_structure.dart';
import 'source_information.dart';
import 'position_information.dart';
class KernelSourceInformationStrategy
extends AbstractPositionSourceInformationStrategy {
final JsBackendStrategy _backendStrategy;
const KernelSourceInformationStrategy(this._backendStrategy);
@override
SourceInformationBuilder createBuilderForContext(MemberEntity member) {
return new KernelSourceInformationBuilder(
_backendStrategy
// ignore:deprecated_member_use_from_same_package
.elementMap,
member);
}
}
/// Compute the source map name for kernel based [member]. If [callStructure]
/// is non-null it is used to name the parameter stub for [element].
///
/// [elementMap] is used to compute names for closure call methods.
// TODO(johnniwinther): Make the closure call names available to
// `sourcemap_helper.dart`.
String computeKernelElementNameForSourceMaps(
JsToElementMap elementMap, MemberEntity member,
[CallStructure callStructure]) {
MemberDefinition definition = elementMap.getMemberDefinition(member);
switch (definition.kind) {
case MemberKind.closureCall:
ir.TreeNode node = definition.node;
String name;
while (node is! ir.Member) {
if (node is ir.FunctionDeclaration) {
if (name != null) {
name = '${node.variable.name}.$name';
} else {
name = node.variable.name;
}
} else if (node is ir.FunctionExpression) {
if (name != null) {
name = '<anonymous function>.$name';
} else {
name = '<anonymous function>';
}
}
node = node.parent;
}
MemberEntity enclosingMember = elementMap.getMember(node);
String enclosingMemberName =
computeElementNameForSourceMaps(enclosingMember, callStructure);
return '$enclosingMemberName.$name';
default:
return computeElementNameForSourceMaps(member, callStructure);
}
}
/// [SourceInformationBuilder] that generates [PositionSourceInformation] from
/// Kernel nodes.
class KernelSourceInformationBuilder implements SourceInformationBuilder {
final JsToElementMap _elementMap;
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)
: _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.
///
/// If [offset] is `null`, the first `fileOffset` of [node] or its parents is
/// used.
SourceLocation _getSourceLocation(String name, ir.TreeNode node,
[int offset]) {
ir.Location location;
if (offset != null) {
location = node.location;
location = node.enclosingComponent.getLocation(location.file, offset);
} else {
while (node != null && node.fileOffset == ir.TreeNode.noOffset) {
node = node.parent;
}
location = node.location;
offset = node.fileOffset;
}
assert(
location != null, "No location found for $node (${node.runtimeType})");
return new KernelSourceLocation(location, offset, name);
}
/// Creates the source information for a function definition defined by the
/// root [node] and its [functionNode].
///
/// This method handles both methods, constructors, and local functions.
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),
this.inliningContext);
}
return _buildTreeNode(node);
}
/// Creates the source information for a [base] and end of [member]. If [base]
/// is not provided, the offset of [member] is used as the start position.
///
/// This is used function declarations and return expressions which both point
/// to the end of the member as the closing position.
SourceInformation _buildFunctionEnd(MemberEntity member, [ir.TreeNode base]) {
MemberDefinition definition = _elementMap.getMemberDefinition(member);
String name = computeKernelElementNameForSourceMaps(_elementMap, member);
ir.Node node = definition.node;
switch (definition.kind) {
case MemberKind.regular:
if (node is ir.Procedure) {
return _buildFunction(name, base ?? node, node.function);
}
break;
case MemberKind.constructor:
case MemberKind.constructorBody:
if (node is ir.Procedure) {
return _buildFunction(name, base ?? node, node.function);
} else if (node is ir.Constructor) {
return _buildFunction(name, base ?? node, node.function);
}
break;
case MemberKind.closureCall:
if (node is ir.FunctionDeclaration) {
return _buildFunction(name, base ?? node, node.function);
} else if (node is ir.FunctionExpression) {
return _buildFunction(name, base ?? node, node.function);
}
break;
// TODO(sra): generatorBody
default:
}
return _buildTreeNode(base ?? node, name: name);
}
/// Creates the source information for exiting a function definition defined
/// by the root [node] and its [functionNode].
///
/// This method handles both methods, constructors, and local functions.
SourceInformation _buildFunctionExit(
ir.TreeNode node, ir.FunctionNode functionNode) {
if (functionNode.fileEndOffset != ir.TreeNode.noOffset) {
return new PositionSourceInformation(
_getSourceLocation(_name, functionNode, functionNode.fileEndOffset),
null,
this.inliningContext);
}
return _buildTreeNode(node);
}
/// Creates the source information for the [body] of [node].
///
/// This method is used to for code in the beginning of a method, like
/// variable declarations in the start of a function.
SourceInformation _buildBody(ir.TreeNode node, ir.TreeNode body) {
SourceLocation location;
if (body != null) {
if (body is ir.Block && body.statements.isNotEmpty) {
location = _getSourceLocation(_name, body.statements.first);
} else {
location = _getSourceLocation(_name, body);
}
} else {
location = _getSourceLocation(_name, node);
}
return new PositionSourceInformation(location, null, inliningContext);
}
/// Creates source information for the body of the current member.
SourceInformation _buildMemberBody() {
MemberDefinition definition = _elementMap.getMemberDefinition(_member);
switch (definition.kind) {
case MemberKind.regular:
ir.Node node = definition.node;
if (node is ir.Procedure) {
return _buildBody(node, node.function.body);
} else if (node is ir.Field) {
return _buildBody(node, node.initializer);
}
break;
case MemberKind.constructor:
case MemberKind.constructorBody:
ir.Node node = definition.node;
if (node is ir.Procedure) {
return _buildBody(node, node.function.body);
} else if (node is ir.Constructor) {
return _buildBody(node, node.function.body);
}
break;
case MemberKind.closureCall:
ir.Node node = definition.node;
if (node is ir.FunctionDeclaration) {
return _buildBody(node, node.function.body);
} else if (node is ir.FunctionExpression) {
return _buildBody(node, node.function.body);
}
break;
case MemberKind.generatorBody:
ir.Node node = definition.node;
if (node is ir.FunctionDeclaration) {
return _buildBody(node, node.function.body);
} else if (node is ir.FunctionExpression) {
return _buildBody(node, node.function.body);
} else if (node is ir.Member && node.function != null) {
return _buildBody(node, node.function.body);
}
break;
default:
}
return _buildTreeNode(definition.node);
}
/// Creates source information for the exit of the current member.
SourceInformation _buildMemberExit() {
MemberDefinition definition = _elementMap.getMemberDefinition(_member);
switch (definition.kind) {
case MemberKind.regular:
ir.Node node = definition.node;
if (node is ir.Procedure) {
return _buildFunctionExit(node, node.function);
}
break;
case MemberKind.constructor:
case MemberKind.constructorBody:
ir.Node node = definition.node;
if (node is ir.Procedure) {
return _buildFunctionExit(node, node.function);
} else if (node is ir.Constructor) {
return _buildFunctionExit(node, node.function);
}
break;
case MemberKind.closureCall:
ir.Node node = definition.node;
if (node is ir.FunctionDeclaration) {
return _buildFunctionExit(node, node.function);
} else if (node is ir.FunctionExpression) {
return _buildFunctionExit(node, node.function);
}
break;
default:
}
return _buildTreeNode(definition.node);
}
/// Creates source information based on the location of [node].
SourceInformation _buildTreeNode(ir.TreeNode node,
{SourceLocation closingPosition, String name}) {
return new PositionSourceInformation(
_getSourceLocation(name ?? _name, node),
closingPosition,
inliningContext);
}
@override
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;
@override
SourceInformation buildSwitch(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildAs(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildIs(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildTry(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildCatch(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildBinary(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildUnary(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildIndexSet(ir.Node node) => null;
@override
SourceInformation buildIndex(ir.Node node) => null;
@override
SourceInformation buildForInSet(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildForInCurrent(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildForInMoveNext(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildForInIterator(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildStringInterpolation(ir.Node node) => null;
@override
SourceInformation buildForeignCode(ir.Node node) => null;
@override
SourceInformation buildVariableDeclaration() {
return _buildMemberBody();
}
@override
SourceInformation buildAwait(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildYield(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildAsyncBody() {
return _buildMemberBody();
}
@override
SourceInformation buildAsyncExit() {
return _buildMemberExit();
}
@override
SourceInformation buildAssignment(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildThrow(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildAssert(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildNew(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildIf(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildCall(
covariant ir.TreeNode receiver, covariant ir.TreeNode call) {
return new PositionSourceInformation(_getSourceLocation(_name, receiver),
_getSourceLocation(_name, call), inliningContext);
}
@override
SourceInformation buildGet(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildSet(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildLoop(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildImplicitReturn(MemberEntity element) => null;
@override
SourceInformation buildReturn(ir.Node node) {
return _buildFunctionEnd(_member, node);
}
@override
SourceInformation buildCreate(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildListLiteral(ir.Node node) {
return _buildTreeNode(node);
}
@override
SourceInformation buildGeneric(ir.Node node) => null;
@override
SourceInformation buildDeclaration(MemberEntity member) {
return _buildFunctionEnd(member);
}
@override
SourceInformation buildStub(
FunctionEntity function, CallStructure callStructure) {
MemberDefinition definition = _elementMap.getMemberDefinition(function);
String name = computeKernelElementNameForSourceMaps(
_elementMap, function, callStructure);
ir.Node node = definition.node;
return _buildTreeNode(node, name: name);
}
@override
SourceInformation buildGoto(ir.Node node) {
return _buildTreeNode(node);
}
}
class KernelSourceLocation extends AbstractSourceLocation {
@override
final int offset;
@override
final String sourceName;
@override
final Uri sourceUri;
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);
}