| // 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 '../kernel/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.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; | |
| } | |
| 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 { | |
| final int offset; | |
| final String sourceName; | |
| 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); | |
| } |