| // 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; | |
| import 'package:kernel/ast.dart' as ir; | |
| import '../elements/entities.dart'; | |
| import '../js_model/element_map.dart'; | |
| import '../js_model/elements.dart'; | |
| import '../universe/call_structure.dart'; | |
| import 'position_information.dart'; | |
| import 'source_information.dart'; | |
| class OnlineKernelSourceInformationStrategy | |
| extends OnlinePositionSourceInformationStrategy { | |
| late JsToElementMap _elementMap; | |
| @override | |
| void onElementMapAvailable(JsToElementMap elementMap) { | |
| _elementMap = elementMap; | |
| } | |
| @override | |
| SourceInformationBuilder createBuilderForContext(MemberEntity member) { | |
| return KernelSourceInformationBuilder(_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.regular: | |
| final node = definition.node as ir.Member; | |
| if (node.isExtensionMember) return _findExtensionMemberName(node); | |
| return computeElementNameForSourceMaps(member, callStructure); | |
| case MemberKind.closureCall: | |
| var node = definition.node as ir.TreeNode; | |
| 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'; | |
| case MemberKind.constructor: | |
| case MemberKind.constructorBody: | |
| case MemberKind.recordGetter: | |
| case MemberKind.signature: | |
| case MemberKind.closureField: | |
| case MemberKind.generatorBody: | |
| case MemberKind.parameterStub: | |
| return computeElementNameForSourceMaps(member, callStructure); | |
| } | |
| } | |
| /// Extract a simple readable name for an extension member. | |
| String _findExtensionMemberName(ir.Member member) { | |
| assert(member.isExtensionMember); | |
| for (ir.Extension extension in member.enclosingLibrary.extensions) { | |
| for (ir.ExtensionMemberDescriptor descriptor | |
| in extension.memberDescriptors) { | |
| if (descriptor.memberReference == member.reference || | |
| descriptor.tearOffReference == member.reference) { | |
| String extensionName; | |
| // Anonymous extensions contain a # on their synthetic name. | |
| if (extension.name.contains('#')) { | |
| ir.DartType type = extension.onType; | |
| if (type is ir.InterfaceType) { | |
| extensionName = "${type.classNode.name}.<anonymous extension>"; | |
| } else { | |
| extensionName = "<anonymous extension>"; | |
| } | |
| } else { | |
| extensionName = extension.name; | |
| } | |
| String memberName = descriptor.name.text; | |
| return '$extensionName.$memberName'; | |
| } | |
| } | |
| } | |
| throw StateError('No original name found for extension member $member.'); | |
| } | |
| /// [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.fileOffset == ir.TreeNode.noOffset) { | |
| node = node.parent!; | |
| } | |
| location = node.location!; | |
| offset = node.fileOffset; | |
| } | |
| return 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 PositionSourceInformation( | |
| _getSourceLocation(name, node), | |
| _getSourceLocation(name, functionNode, functionNode.fileEndOffset), | |
| 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); | |
| switch (definition.kind) { | |
| case MemberKind.regular: | |
| final node = definition.node as ir.Member; | |
| if (node is ir.Procedure) { | |
| return _buildFunction(name, base ?? node, node.function); | |
| } | |
| break; | |
| case MemberKind.constructor: | |
| case MemberKind.constructorBody: | |
| final node = definition.node as ir.Member; | |
| return _buildFunction(name, base ?? node, node.function!); | |
| case MemberKind.closureCall: | |
| final node = definition.node as ir.LocalFunction; | |
| return _buildFunction(name, base ?? node, node.function); | |
| case MemberKind.parameterStub: | |
| return buildStub( | |
| member as JParameterStub, | |
| member.parameterStructure.callStructure, | |
| ); | |
| case MemberKind.recordGetter: | |
| return null; | |
| case MemberKind.closureField: | |
| case MemberKind.signature: | |
| case MemberKind.generatorBody: | |
| // TODO(sra): Should we target the generator itself for generatorBody? | |
| break; | |
| } | |
| return _buildTreeNode(base ?? definition.node as ir.TreeNode, 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 PositionSourceInformation( | |
| _getSourceLocation(_name, functionNode, functionNode.fileEndOffset), | |
| null, | |
| 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 PositionSourceInformation(location, null, inliningContext); | |
| } | |
| /// Creates source information for the body of the current member. | |
| // TODO(51310): Remove nullable return type. | |
| 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: | |
| final node = definition.node as ir.LocalFunction; | |
| return _buildBody(node, node.function.body); | |
| case MemberKind.generatorBody: | |
| ir.Node node = definition.node; | |
| if (node is ir.LocalFunction) { | |
| return _buildBody(node, node.function.body); | |
| } else if (node is ir.Member && node.function != null) { | |
| return _buildBody(node, node.function!.body); | |
| } | |
| break; | |
| case MemberKind.parameterStub: | |
| case MemberKind.recordGetter: | |
| // This is a completely synthetic element. Perhaps we can use | |
| // definition.location, but that is often 'nowhere'. | |
| // TODO(51310): Perhaps we should not end up in | |
| // [KernelSourceInformationBuilder] for synthetic elements that are not | |
| // defined by Kernel ASTs. | |
| return null; | |
| case MemberKind.signature: | |
| case MemberKind.closureField: | |
| break; | |
| } | |
| return _buildTreeNode(definition.node as ir.TreeNode); | |
| } | |
| /// 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: | |
| final node = definition.node as ir.Member; | |
| return _buildFunctionExit(node, node.function!); | |
| case MemberKind.closureCall: | |
| final node = definition.node as ir.LocalFunction; | |
| return _buildFunctionExit(node, node.function); | |
| case MemberKind.signature: | |
| case MemberKind.closureField: | |
| case MemberKind.recordGetter: | |
| case MemberKind.generatorBody: | |
| case MemberKind.parameterStub: | |
| break; | |
| } | |
| return _buildTreeNode(definition.node as ir.TreeNode); | |
| } | |
| /// Creates source information based on the location of [node]. | |
| SourceInformation _buildTreeNode( | |
| ir.TreeNode node, { | |
| SourceLocation? closingPosition, | |
| String? name, | |
| }) { | |
| return PositionSourceInformation( | |
| _getSourceLocation(name ?? _name, node), | |
| closingPosition, | |
| inliningContext, | |
| ); | |
| } | |
| @override | |
| SourceInformationBuilder forContext( | |
| MemberEntity member, | |
| SourceInformation? context, | |
| ) { | |
| List<FrameContext>? newContext = inliningContext?.toList() ?? []; | |
| if (context != null) { | |
| newContext.add(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 KernelSourceInformationBuilder.withContext( | |
| _elementMap, | |
| member, | |
| newContext, | |
| name, | |
| ); | |
| } | |
| @override | |
| SourceInformation? buildSwitchCase(ir.Node node) => null; | |
| @override | |
| SourceInformation buildSwitch(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildAs(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildIs(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildTry(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildCatch(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildBinary(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildUnary(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation? buildIndexSet(ir.Node node) => null; | |
| @override | |
| SourceInformation? buildIndex(ir.Node node) => null; | |
| @override | |
| SourceInformation buildForInSet(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildForInCurrent(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildForInMoveNext(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildForInIterator(ir.TreeNode 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.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildYield(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildAsyncBody() { | |
| return _buildMemberBody()!; | |
| } | |
| @override | |
| SourceInformation buildAsyncExit() { | |
| return _buildMemberExit(); | |
| } | |
| @override | |
| SourceInformation buildAssignment(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildThrow(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildAssert(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildNew(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildIf(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildBlock(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildCall( | |
| covariant ir.TreeNode receiver, | |
| covariant ir.TreeNode call, | |
| ) { | |
| return PositionSourceInformation( | |
| _getSourceLocation(_name, receiver), | |
| _getSourceLocation(_name, call), | |
| inliningContext, | |
| ); | |
| } | |
| @override | |
| SourceInformation buildGet(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildSet(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildLoop(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation? buildImplicitReturn(MemberEntity element) => null; | |
| @override | |
| SourceInformation buildReturn(ir.TreeNode node) { | |
| return _buildFunctionEnd(_member, node)!; | |
| } | |
| @override | |
| SourceInformation buildCreate(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| @override | |
| SourceInformation buildListLiteral(ir.TreeNode 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 as ir.TreeNode, name: name); | |
| } | |
| @override | |
| SourceInformation buildGoto(ir.TreeNode node) { | |
| return _buildTreeNode(node); | |
| } | |
| } | |
| class KernelSourceLocation extends SourceLocation { | |
| @override | |
| final int offset; | |
| @override | |
| final String? sourceName; | |
| final ir.Location location; | |
| KernelSourceLocation(this.location, this.offset, this.sourceName); | |
| @override | |
| int get column => location.column; | |
| @override | |
| int get line => location.line; | |
| @override | |
| Uri? get sourceUri => location.file; | |
| } |