blob: f517a9d06833307095185caf804409010b561394 [file] [log] [blame]
// Copyright (c) 2017, 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.
import 'package:compiler/src/common.dart';
import 'package:compiler/src/elements/elements.dart';
import 'package:compiler/src/kernel/element_map.dart';
import 'package:compiler/src/js_model/locals.dart';
import 'package:compiler/src/resolution/access_semantics.dart';
import 'package:compiler/src/resolution/send_structure.dart';
import 'package:compiler/src/resolution/tree_elements.dart';
import 'package:compiler/src/tree/nodes.dart' as ast;
import 'package:expect/expect.dart';
import 'package:kernel/ast.dart' as ir;
enum IdKind {
element,
node,
invoke,
update,
iterator,
current,
moveNext,
}
/// Id for a code point or element with type inference information.
abstract class Id {
IdKind get kind;
}
class IdValue {
final Id id;
final String value;
const IdValue(this.id, this.value);
int get hashCode => id.hashCode * 13 + value.hashCode * 17;
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! IdValue) return false;
return id == other.id && value == other.value;
}
String toString() {
switch (id.kind) {
case IdKind.element:
ElementId elementId = id;
return '$elementPrefix${elementId.name}:$value';
case IdKind.node:
return value;
case IdKind.invoke:
return '$invokePrefix$value';
case IdKind.update:
return '$updatePrefix$value';
case IdKind.iterator:
return '$iteratorPrefix$value';
case IdKind.current:
return '$currentPrefix$value';
case IdKind.moveNext:
return '$moveNextPrefix$value';
}
throw new UnsupportedError("Unexpected id kind: ${id.kind}");
}
static const String elementPrefix = "element: ";
static const String invokePrefix = "invoke: ";
static const String updatePrefix = "update: ";
static const String iteratorPrefix = "iterator: ";
static const String currentPrefix = "current: ";
static const String moveNextPrefix = "moveNext: ";
static IdValue decode(int offset, String text) {
Id id;
String expected;
if (text.startsWith(elementPrefix)) {
text = text.substring(elementPrefix.length);
int colonPos = text.indexOf(':');
id = new ElementId(text.substring(0, colonPos));
expected = text.substring(colonPos + 1);
} else if (text.startsWith(invokePrefix)) {
id = new NodeId(offset, IdKind.invoke);
expected = text.substring(invokePrefix.length);
} else if (text.startsWith(updatePrefix)) {
id = new NodeId(offset, IdKind.update);
expected = text.substring(updatePrefix.length);
} else if (text.startsWith(iteratorPrefix)) {
id = new NodeId(offset, IdKind.iterator);
expected = text.substring(iteratorPrefix.length);
} else if (text.startsWith(currentPrefix)) {
id = new NodeId(offset, IdKind.current);
expected = text.substring(currentPrefix.length);
} else if (text.startsWith(moveNextPrefix)) {
id = new NodeId(offset, IdKind.moveNext);
expected = text.substring(moveNextPrefix.length);
} else {
id = new NodeId(offset, IdKind.node);
expected = text;
}
return new IdValue(id, expected);
}
}
/// Id for an element with type inference information.
// TODO(johnniwinther): Support local variables, functions and parameters.
class ElementId implements Id {
final String className;
final String memberName;
factory ElementId(String text) {
int dotPos = text.indexOf('.');
if (dotPos != -1) {
return new ElementId.internal(
text.substring(dotPos + 1), text.substring(0, dotPos));
} else {
return new ElementId.internal(text);
}
}
ElementId.internal(this.memberName, [this.className]);
int get hashCode => className.hashCode * 13 + memberName.hashCode * 17;
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! ElementId) return false;
return className == other.className && memberName == other.memberName;
}
IdKind get kind => IdKind.element;
String get name => className != null ? '$className.$memberName' : memberName;
String toString() => name;
}
/// Id for a code point with type inference information.
// TODO(johnniwinther): Create an [NodeId]-based equivalence with the kernel IR.
class NodeId implements Id {
final int value;
final IdKind kind;
const NodeId(this.value, this.kind);
int get hashCode => value.hashCode * 13 + kind.hashCode * 17;
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! NodeId) return false;
return value == other.value && kind == other.kind;
}
String toString() => '$kind:$value';
}
class ActualData {
final IdValue value;
final SourceSpan sourceSpan;
final Object object;
ActualData(this.value, this.sourceSpan, this.object);
String toString() =>
'ActualData(value=$value,sourceSpan=$sourceSpan,object=$object)';
}
abstract class DataRegistry {
DiagnosticReporter get reporter;
Map<Id, ActualData> get actualMap;
void registerValue(
SourceSpan sourceSpan, Id id, String value, Object object) {
if (actualMap.containsKey(id)) {
ActualData existingData = actualMap[id];
reportHere(reporter, sourceSpan,
"Duplicate id ${id}, value=$value, object=$object");
reportHere(
reporter,
sourceSpan,
"Duplicate id ${id}, value=${existingData.value}, "
"object=${existingData.object}");
Expect.fail("Duplicate id $id.");
}
if (value != null) {
actualMap[id] =
new ActualData(new IdValue(id, value), sourceSpan, object);
}
}
}
/// Abstract AST visitor for computing data corresponding to a node or element,
// and record it with a generic [Id].
abstract class AstDataExtractor extends ast.Visitor with DataRegistry {
final DiagnosticReporter reporter;
final Map<Id, ActualData> actualMap;
final ResolvedAst resolvedAst;
AstDataExtractor(this.reporter, this.actualMap, this.resolvedAst);
/// Implement this to compute the data corresponding to [element].
///
/// If `null` is returned, [element] has no associated data.
String computeElementValue(Id id, AstElement element);
/// Implement this to compute the data corresponding to [node]. If [node] has
/// a corresponding [AstElement] this is provided in [element].
///
/// If `null` is returned, [node] has no associated data.
String computeNodeValue(Id id, ast.Node node, AstElement element);
TreeElements get elements => resolvedAst.elements;
ElementId computeElementId(AstElement element) {
String memberName = element.name;
if (element.isSetter) {
memberName += '=';
}
String className = element.enclosingClass?.name;
return new ElementId.internal(memberName, className);
}
ast.Node computeAccessPosition(ast.Send node, AccessSemantics access) {
switch (access.kind) {
case AccessKind.THIS_PROPERTY:
case AccessKind.DYNAMIC_PROPERTY:
case AccessKind.LOCAL_VARIABLE:
case AccessKind.FINAL_LOCAL_VARIABLE:
case AccessKind.LOCAL_FUNCTION:
case AccessKind.PARAMETER:
case AccessKind.FINAL_PARAMETER:
case AccessKind.EXPRESSION:
return node.selector;
default:
return null;
}
}
ast.Node computeUpdatePosition(ast.Send node, AccessSemantics access) {
switch (access.kind) {
case AccessKind.THIS_PROPERTY:
case AccessKind.DYNAMIC_PROPERTY:
case AccessKind.LOCAL_VARIABLE:
case AccessKind.PARAMETER:
return node.selector;
default:
return null;
}
}
void computeForElement(AstElement element) {
ElementId id = computeElementId(element);
if (id == null) return;
String value = computeElementValue(id, element);
registerValue(element.sourcePosition, id, value, element);
}
void computeForNode(ast.Node node, NodeId id, [AstElement element]) {
if (id == null) return;
String value = computeNodeValue(id, node, element);
SourceSpan sourceSpan = computeSourceSpan(node);
registerValue(sourceSpan, id, value, element ?? node);
}
SourceSpan computeSourceSpan(ast.Node node) {
return new SourceSpan(resolvedAst.sourceUri,
node.getBeginToken().charOffset, node.getEndToken().charEnd);
}
NodeId computeDefaultNodeId(ast.Node node) {
return new NodeId(node.getBeginToken().charOffset, IdKind.node);
}
NodeId createAccessId(ast.Node node) {
return new NodeId(node.getBeginToken().charOffset, IdKind.node);
}
NodeId createInvokeId(ast.Node node) {
return new NodeId(node.getBeginToken().charOffset, IdKind.invoke);
}
NodeId createUpdateId(ast.Node node) {
return new NodeId(node.getBeginToken().charOffset, IdKind.update);
}
NodeId createIteratorId(ast.ForIn node) {
return new NodeId(node.getBeginToken().charOffset, IdKind.iterator);
}
NodeId createCurrentId(ast.ForIn node) {
return new NodeId(node.getBeginToken().charOffset, IdKind.current);
}
NodeId createMoveNextId(ast.ForIn node) {
return new NodeId(node.getBeginToken().charOffset, IdKind.moveNext);
}
NodeId createLabeledStatementId(ast.LabeledStatement node) =>
computeDefaultNodeId(node.statement);
NodeId createLoopId(ast.Node node) => computeDefaultNodeId(node);
NodeId createGotoId(ast.Node node) => computeDefaultNodeId(node);
NodeId createSwitchId(ast.SwitchStatement node) => computeDefaultNodeId(node);
NodeId createSwitchCaseId(ast.SwitchCase node) {
ast.Node position;
for (ast.Node child in node.labelsAndCases) {
if (child.asCaseMatch() != null) {
ast.CaseMatch caseMatch = child;
position = caseMatch.expression;
break;
}
}
return computeDefaultNodeId(position);
}
void run() {
if (resolvedAst.kind == ResolvedAstKind.PARSED) {
resolvedAst.node.accept(this);
} else {
computeForElement(resolvedAst.element);
}
}
visitNode(ast.Node node) {
node.visitChildren(this);
}
visitVariableDefinitions(ast.VariableDefinitions node) {
for (ast.Node child in node.definitions) {
AstElement element = elements[child];
if (element == null) {
reportHere(reporter, child, 'No element for variable.');
} else if (!element.isLocal) {
computeForElement(element);
} else if (element.isInitializingFormal) {
ast.Send send = child;
computeForNode(child, computeDefaultNodeId(send.selector), element);
} else {
computeForNode(child, computeDefaultNodeId(child), element);
}
}
visitNode(node);
}
visitFunctionExpression(ast.FunctionExpression node) {
AstElement element = elements.getFunctionDefinition(node);
if (!element.isLocal) {
computeForElement(element);
} else {
computeForNode(node, computeDefaultNodeId(node), element);
}
visitNode(node);
}
visitSend(ast.Send node) {
dynamic sendStructure = elements.getSendStructure(node);
if (sendStructure != null) {
switch (sendStructure.kind) {
case SendStructureKind.GET:
ast.Node position =
computeAccessPosition(node, sendStructure.semantics);
if (position != null) {
computeForNode(node, computeDefaultNodeId(position));
}
break;
case SendStructureKind.INVOKE:
case SendStructureKind.BINARY:
case SendStructureKind.EQUALS:
case SendStructureKind.NOT_EQUALS:
ast.Node position =
computeAccessPosition(node, sendStructure.semantics);
if (position != null) {
computeForNode(node, createInvokeId(position));
}
break;
case SendStructureKind.SET:
break;
default:
}
}
visitNode(node);
}
visitSendSet(ast.SendSet node) {
dynamic sendStructure = elements.getSendStructure(node);
if (sendStructure != null) {
switch (sendStructure.kind) {
case SendStructureKind.SET:
ast.Node position =
computeUpdatePosition(node, sendStructure.semantics);
if (position != null) {
computeForNode(node, createUpdateId(position));
}
break;
case SendStructureKind.PREFIX:
case SendStructureKind.POSTFIX:
case SendStructureKind.COMPOUND:
computeForNode(node, createAccessId(node.selector));
computeForNode(node, createInvokeId(node.assignmentOperator));
computeForNode(node, createUpdateId(node.selector));
break;
default:
}
}
visitNode(node);
}
visitLoop(ast.Loop node) {
computeForNode(node, createLoopId(node));
visitNode(node);
}
visitGotoStatement(ast.GotoStatement node) {
computeForNode(node, createGotoId(node));
visitNode(node);
}
visitLabeledStatement(ast.LabeledStatement node) {
if (node.statement is! ast.Loop && node.statement is! ast.SwitchStatement) {
computeForNode(node, createLabeledStatementId(node));
}
visitNode(node);
}
visitSwitchStatement(ast.SwitchStatement node) {
computeForNode(node, createSwitchId(node));
visitNode(node);
}
visitSwitchCase(ast.SwitchCase node) {
computeForNode(node, createSwitchCaseId(node));
visitNode(node);
}
visitForIn(ast.ForIn node) {
computeForNode(node, createIteratorId(node));
computeForNode(node, createCurrentId(node));
computeForNode(node, createMoveNextId(node));
visitLoop(node);
}
}
/// Abstract IR visitor for computing data corresponding to a node or element,
/// and record it with a generic [Id]
abstract class IrDataExtractor extends ir.Visitor with DataRegistry {
final DiagnosticReporter reporter;
final Map<Id, ActualData> actualMap;
/// Implement this to compute the data corresponding to [member].
///
/// If `null` is returned, [member] has no associated data.
String computeMemberValue(Id id, ir.Member member);
/// Implement this to compute the data corresponding to [node].
///
/// If `null` is returned, [node] has no associated data.
String computeNodeValue(Id id, ir.TreeNode node);
IrDataExtractor(this.reporter, this.actualMap);
Id computeElementId(ir.Member node) {
String className;
if (node.enclosingClass != null) {
className = node.enclosingClass.name;
}
String memberName = node.name.name;
if (node is ir.Procedure && node.kind == ir.ProcedureKind.Setter) {
memberName += '=';
}
return new ElementId.internal(memberName, className);
}
void computeForMember(ir.Member member) {
ElementId id = computeElementId(member);
if (id == null) return;
String value = computeMemberValue(id, member);
registerValue(computeSourceSpan(member), id, value, member);
}
void computeForNode(ir.TreeNode node, NodeId id) {
if (id == null) return;
String value = computeNodeValue(id, node);
registerValue(computeSourceSpan(node), id, value, node);
}
SourceSpan computeSourceSpan(ir.TreeNode node) {
return computeSourceSpanFromTreeNode(node);
}
NodeId computeDefaultNodeId(ir.TreeNode node) {
assert(node.fileOffset != ir.TreeNode.noOffset,
"No fileOffset on $node (${node.runtimeType})");
return new NodeId(node.fileOffset, IdKind.node);
}
NodeId createInvokeId(ir.TreeNode node) {
assert(node.fileOffset != ir.TreeNode.noOffset,
"No fileOffset on ${node} (${node.runtimeType})");
return new NodeId(node.fileOffset, IdKind.invoke);
}
NodeId createUpdateId(ir.TreeNode node) {
assert(node.fileOffset != ir.TreeNode.noOffset,
"No fileOffset on ${node} (${node.runtimeType})");
return new NodeId(node.fileOffset, IdKind.update);
}
NodeId createIteratorId(ir.ForInStatement node) {
assert(node.fileOffset != ir.TreeNode.noOffset,
"No fileOffset on ${node} (${node.runtimeType})");
return new NodeId(node.fileOffset, IdKind.iterator);
}
NodeId createCurrentId(ir.ForInStatement node) {
assert(node.fileOffset != ir.TreeNode.noOffset,
"No fileOffset on ${node} (${node.runtimeType})");
return new NodeId(node.fileOffset, IdKind.current);
}
NodeId createMoveNextId(ir.ForInStatement node) {
assert(node.fileOffset != ir.TreeNode.noOffset,
"No fileOffset on ${node} (${node.runtimeType})");
return new NodeId(node.fileOffset, IdKind.moveNext);
}
NodeId createLabeledStatementId(ir.LabeledStatement node) =>
computeDefaultNodeId(node.body);
NodeId createLoopId(ir.TreeNode node) => computeDefaultNodeId(node);
NodeId createGotoId(ir.TreeNode node) => computeDefaultNodeId(node);
NodeId createSwitchId(ir.SwitchStatement node) => computeDefaultNodeId(node);
NodeId createSwitchCaseId(ir.SwitchCase node) =>
new NodeId(node.expressionOffsets.first, IdKind.node);
void run(ir.Node root) {
root.accept(this);
}
defaultNode(ir.Node node) {
node.visitChildren(this);
}
defaultMember(ir.Member node) {
computeForMember(node);
super.defaultMember(node);
}
visitMethodInvocation(ir.MethodInvocation node) {
computeForNode(node, createInvokeId(node));
super.visitMethodInvocation(node);
}
visitPropertyGet(ir.PropertyGet node) {
computeForNode(node, computeDefaultNodeId(node));
super.visitPropertyGet(node);
}
visitVariableDeclaration(ir.VariableDeclaration node) {
if (node.name != null && node.parent is! ir.FunctionDeclaration) {
// Skip synthetic variables and function declaration variables.
computeForNode(node, computeDefaultNodeId(node));
}
super.visitVariableDeclaration(node);
}
visitFunctionDeclaration(ir.FunctionDeclaration node) {
computeForNode(node, computeDefaultNodeId(node));
super.visitFunctionDeclaration(node);
}
visitFunctionExpression(ir.FunctionExpression node) {
computeForNode(node, computeDefaultNodeId(node));
super.visitFunctionExpression(node);
}
visitVariableGet(ir.VariableGet node) {
if (node.variable.name != null && !node.variable.isFieldFormal) {
// Skip use of synthetic variables.
computeForNode(node, computeDefaultNodeId(node));
}
super.visitVariableGet(node);
}
visitPropertySet(ir.PropertySet node) {
computeForNode(node, createUpdateId(node));
super.visitPropertySet(node);
}
visitVariableSet(ir.VariableSet node) {
if (node.variable.name != null) {
// Skip use of synthetic variables.
computeForNode(node, createUpdateId(node));
}
super.visitVariableSet(node);
}
visitDoStatement(ir.DoStatement node) {
computeForNode(node, createLoopId(node));
super.visitDoStatement(node);
}
visitForStatement(ir.ForStatement node) {
computeForNode(node, createLoopId(node));
super.visitForStatement(node);
}
visitForInStatement(ir.ForInStatement node) {
computeForNode(node, createLoopId(node));
computeForNode(node, createIteratorId(node));
computeForNode(node, createCurrentId(node));
computeForNode(node, createMoveNextId(node));
super.visitForInStatement(node);
}
visitWhileStatement(ir.WhileStatement node) {
computeForNode(node, createLoopId(node));
super.visitWhileStatement(node);
}
visitLabeledStatement(ir.LabeledStatement node) {
if (!JumpVisitor.canBeBreakTarget(node.body) &&
!JumpVisitor.canBeContinueTarget(node.parent)) {
computeForNode(node, createLabeledStatementId(node));
}
super.visitLabeledStatement(node);
}
visitBreakStatement(ir.BreakStatement node) {
computeForNode(node, createGotoId(node));
super.visitBreakStatement(node);
}
visitSwitchStatement(ir.SwitchStatement node) {
computeForNode(node, createSwitchId(node));
super.visitSwitchStatement(node);
}
visitSwitchCase(ir.SwitchCase node) {
computeForNode(node, createSwitchCaseId(node));
super.visitSwitchCase(node);
}
visitContinueSwitchStatement(ir.ContinueSwitchStatement node) {
computeForNode(node, createGotoId(node));
super.visitContinueSwitchStatement(node);
}
}