blob: d664020431ef06369d2f138951d134ef948cb70e [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/ir/util.dart';
import 'package:compiler/src/js_model/locals.dart';
import 'package:expect/expect.dart';
import 'package:kernel/ast.dart' as ir;
enum IdKind {
element,
cls,
node,
invoke,
update,
iterator,
current,
moveNext,
}
/// Id for a code point or element with type inference information.
abstract class Id {
IdKind get kind;
bool get isGlobal;
/// Display name for this id.
String get descriptor;
}
class IdValue {
final Id id;
final String value;
const IdValue(this.id, this.value);
@override
int get hashCode => id.hashCode * 13 + value.hashCode * 17;
@override
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! IdValue) return false;
return id == other.id && value == other.value;
}
@override
String toString() => idToString(id, value);
static String idToString(Id id, String value) {
switch (id.kind) {
case IdKind.element:
ElementId elementId = id;
return '$elementPrefix${elementId.name}:$value';
case IdKind.cls:
ClassId classId = id;
return '$classPrefix${classId.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 globalPrefix = "global#";
static const String elementPrefix = "element: ";
static const String classPrefix = "class: ";
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(':');
if (colonPos == -1) throw "Invalid element id: '$text'";
String name = text.substring(0, colonPos);
bool isGlobal = name.startsWith(globalPrefix);
if (isGlobal) {
name = name.substring(globalPrefix.length);
}
id = new ElementId(name, isGlobal: isGlobal);
expected = text.substring(colonPos + 1);
} else if (text.startsWith(classPrefix)) {
text = text.substring(classPrefix.length);
int colonPos = text.indexOf(':');
if (colonPos == -1) throw "Invalid class id: '$text'";
String name = text.substring(0, colonPos);
bool isGlobal = name.startsWith(globalPrefix);
if (isGlobal) {
name = name.substring(globalPrefix.length);
}
id = new ClassId(name, isGlobal: isGlobal);
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;
}
// Remove newlines.
expected = expected.replaceAll(new RegExp(r'\s*(\n\s*)+\s*'), '');
return new IdValue(id, expected);
}
}
/// Id for an member element.
class ElementId implements Id {
final String className;
final String memberName;
@override
final bool isGlobal;
factory ElementId(String text, {bool isGlobal: false}) {
int dotPos = text.indexOf('.');
if (dotPos != -1) {
return new ElementId.internal(text.substring(dotPos + 1),
className: text.substring(0, dotPos), isGlobal: isGlobal);
} else {
return new ElementId.internal(text, isGlobal: isGlobal);
}
}
ElementId.internal(this.memberName, {this.className, this.isGlobal: false});
@override
int get hashCode => className.hashCode * 13 + memberName.hashCode * 17;
@override
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! ElementId) return false;
return className == other.className && memberName == other.memberName;
}
@override
IdKind get kind => IdKind.element;
String get name => className != null ? '$className.$memberName' : memberName;
@override
String get descriptor => 'member $name';
@override
String toString() => 'element:$name';
}
/// Id for a class.
class ClassId implements Id {
final String className;
@override
final bool isGlobal;
ClassId(this.className, {this.isGlobal: false});
@override
int get hashCode => className.hashCode * 13;
@override
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! ClassId) return false;
return className == other.className;
}
@override
IdKind get kind => IdKind.cls;
String get name => className;
@override
String get descriptor => 'class $name';
@override
String toString() => 'class:$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;
@override
final IdKind kind;
const NodeId(this.value, this.kind);
@override
bool get isGlobal => false;
@override
int get hashCode => value.hashCode * 13 + kind.hashCode * 17;
@override
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! NodeId) return false;
return value == other.value && kind == other.kind;
}
@override
String get descriptor => 'offset $value ($kind)';
@override
String toString() => '$kind:$value';
}
class ActualData<T> {
final Id id;
final T value;
final SourceSpan sourceSpan;
final Object object;
ActualData(this.id, this.value, this.sourceSpan, this.object);
int get offset {
if (id is NodeId) {
NodeId nodeId = id;
return nodeId.value;
} else {
return sourceSpan.begin;
}
}
String get objectText {
return 'object `${'$object'.replaceAll('\n', '')}` (${object.runtimeType})';
}
@override
String toString() =>
'ActualData(id=$id,value=$value,sourceSpan=$sourceSpan,object=$objectText)';
}
abstract class DataRegistry<T> {
DiagnosticReporter get reporter;
Map<Id, ActualData<T>> get actualMap;
void registerValue(SourceSpan sourceSpan, Id id, T value, Object object) {
if (actualMap.containsKey(id)) {
ActualData<T> 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<T>(id, value, sourceSpan, object);
}
}
}
/// Compute a canonical [Id] for kernel-based nodes.
Id computeEntityId(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: className);
}
/// Abstract IR visitor for computing data corresponding to a node or element,
/// and record it with a generic [Id]
abstract class IrDataExtractor<T> extends ir.Visitor with DataRegistry<T> {
@override
final DiagnosticReporter reporter;
@override
final Map<Id, ActualData<T>> actualMap;
/// Implement this to compute the data corresponding to [member].
///
/// If `null` is returned, [member] has no associated data.
T computeMemberValue(Id id, ir.Member member);
/// Implement this to compute the data corresponding to [node].
///
/// If `null` is returned, [node] has no associated data.
T computeNodeValue(Id id, ir.TreeNode node);
IrDataExtractor(this.reporter, this.actualMap);
void computeForMember(ir.Member member) {
ElementId id = computeEntityId(member);
if (id == null) return;
T value = computeMemberValue(id, member);
registerValue(computeSourceSpan(member), id, value, member);
}
void computeForNode(ir.TreeNode node, NodeId id) {
if (id == null) return;
T 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);
}
@override
defaultNode(ir.Node node) {
node.visitChildren(this);
}
@override
defaultMember(ir.Member node) {
computeForMember(node);
super.defaultMember(node);
}
@override
visitMethodInvocation(ir.MethodInvocation node) {
ir.TreeNode receiver = node.receiver;
if (receiver is ir.VariableGet &&
receiver.variable.parent is ir.FunctionDeclaration) {
// This is an invocation of a named local function.
computeForNode(node, createInvokeId(node.receiver));
node.arguments.accept(this);
} else if (node.name.name == '==' &&
receiver is ir.VariableGet &&
receiver.variable.name == null) {
// This is a desugared `?.`.
} else if (node.name.name == '[]') {
computeForNode(node, computeDefaultNodeId(node));
super.visitMethodInvocation(node);
} else if (node.name.name == '[]=') {
computeForNode(node, createUpdateId(node));
super.visitMethodInvocation(node);
} else {
computeForNode(node, createInvokeId(node));
super.visitMethodInvocation(node);
}
}
@override
visitLoadLibrary(ir.LoadLibrary node) {
computeForNode(node, createInvokeId(node));
}
@override
visitPropertyGet(ir.PropertyGet node) {
computeForNode(node, computeDefaultNodeId(node));
super.visitPropertyGet(node);
}
@override
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);
}
@override
visitFunctionDeclaration(ir.FunctionDeclaration node) {
computeForNode(node, computeDefaultNodeId(node));
super.visitFunctionDeclaration(node);
}
@override
visitFunctionExpression(ir.FunctionExpression node) {
computeForNode(node, computeDefaultNodeId(node));
super.visitFunctionExpression(node);
}
@override
visitVariableGet(ir.VariableGet node) {
if (node.variable.name != null && !node.variable.isFieldFormal) {
// Skip use of synthetic variables.
computeForNode(node, computeDefaultNodeId(node));
}
super.visitVariableGet(node);
}
@override
visitPropertySet(ir.PropertySet node) {
computeForNode(node, createUpdateId(node));
super.visitPropertySet(node);
}
@override
visitVariableSet(ir.VariableSet node) {
if (node.variable.name != null) {
// Skip use of synthetic variables.
computeForNode(node, createUpdateId(node));
}
super.visitVariableSet(node);
}
@override
visitDoStatement(ir.DoStatement node) {
computeForNode(node, createLoopId(node));
super.visitDoStatement(node);
}
@override
visitForStatement(ir.ForStatement node) {
computeForNode(node, createLoopId(node));
super.visitForStatement(node);
}
@override
visitForInStatement(ir.ForInStatement node) {
computeForNode(node, createLoopId(node));
computeForNode(node, createIteratorId(node));
computeForNode(node, createCurrentId(node));
computeForNode(node, createMoveNextId(node));
super.visitForInStatement(node);
}
@override
visitWhileStatement(ir.WhileStatement node) {
computeForNode(node, createLoopId(node));
super.visitWhileStatement(node);
}
@override
visitLabeledStatement(ir.LabeledStatement node) {
if (!JumpVisitor.canBeBreakTarget(node.body) &&
!JumpVisitor.canBeContinueTarget(node.parent)) {
computeForNode(node, createLabeledStatementId(node));
}
super.visitLabeledStatement(node);
}
@override
visitBreakStatement(ir.BreakStatement node) {
computeForNode(node, createGotoId(node));
super.visitBreakStatement(node);
}
@override
visitSwitchStatement(ir.SwitchStatement node) {
computeForNode(node, createSwitchId(node));
super.visitSwitchStatement(node);
}
@override
visitSwitchCase(ir.SwitchCase node) {
if (node.expressionOffsets.isNotEmpty) {
computeForNode(node, createSwitchCaseId(node));
}
super.visitSwitchCase(node);
}
@override
visitContinueSwitchStatement(ir.ContinueSwitchStatement node) {
computeForNode(node, createGotoId(node));
super.visitContinueSwitchStatement(node);
}
}