blob: 9b7e27c5d07a763399255e860585d9708e5bd604 [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.
library vm.transformations.no_dynamic_invocations_annotator;
import 'package:kernel/ast.dart';
import '../metadata/procedure_attributes.dart';
/// Assumes strong mode and closed world. If a procedure can not be riched
/// via dynamic invocation from anywhere then annotates it with appropriate
/// [ProcedureAttributeMetadata] annotation.
Component transformComponent(Component component) {
new NoDynamicUsesAnnotator(component).visitComponent(component);
return component;
}
enum Action { get, set, invoke }
class Selector {
final Action action;
final Name target;
Selector(this.action, this.target);
Selector.doInvoke(Name target) : this(Action.invoke, target);
Selector.doGet(Name target) : this(Action.get, target);
Selector.doSet(Name target) : this(Action.set, target);
bool operator ==(other) {
return other is Selector &&
other.action == this.action &&
other.target == this.target;
}
int get hashCode => (action.index * 31) ^ target.hashCode;
@override
String toString() {
switch (action) {
case Action.get:
return 'get:${target}';
case Action.set:
return 'set:${target}';
case Action.invoke:
return '${target}';
}
return '?';
}
}
// TODO(kustermann): Try to extend the idea of tracking uses based on the 'this'
// hierarchy.
class NoDynamicUsesAnnotator {
final DynamicSelectorsCollector _selectors;
final ProcedureAttributesMetadataRepository _metadata;
NoDynamicUsesAnnotator(Component component)
: _selectors = DynamicSelectorsCollector.collect(component),
_metadata = new ProcedureAttributesMetadataRepository() {
component.addMetadataRepository(_metadata);
}
visitComponent(Component component) {
for (var library in component.libraries) {
for (var klass in library.classes) {
visitClass(klass);
}
}
}
visitClass(Class node) {
for (var member in node.members) {
if (member is Procedure) {
visitProcedure(member);
} else if (member is Field) {
visitField(member);
}
}
}
visitField(Field node) {
if (node.isStatic || node.name.text == 'call') {
return;
}
final selector = new Selector.doSet(node.name);
if (_selectors.dynamicSelectors.contains(selector)) {
return;
}
ProcedureAttributesMetadata metadata;
if (!_selectors.nonThisSelectors.contains(selector)) {
metadata = const ProcedureAttributesMetadata(
methodOrSetterCalledDynamically: false,
getterCalledDynamically: false,
hasNonThisUses: true,
hasTearOffUses: false);
} else {
metadata = const ProcedureAttributesMetadata.noDynamicUses();
}
_metadata.mapping[node] = metadata;
}
visitProcedure(Procedure node) {
if (node.isStatic || node.name.text == 'call') {
return;
}
Selector selector;
if (node.kind == ProcedureKind.Method) {
selector = new Selector.doInvoke(node.name);
} else if (node.kind == ProcedureKind.Setter) {
selector = new Selector.doSet(node.name);
} else {
return;
}
if (_selectors.dynamicSelectors.contains(selector)) {
return;
}
final bool hasNonThisUses = _selectors.nonThisSelectors.contains(selector);
final bool hasTearOffUses = _selectors.tearOffSelectors.contains(selector);
ProcedureAttributesMetadata metadata;
if (!hasNonThisUses && !hasTearOffUses) {
metadata = const ProcedureAttributesMetadata(
methodOrSetterCalledDynamically: false,
getterCalledDynamically: false,
hasNonThisUses: false,
hasTearOffUses: false);
} else if (!hasNonThisUses && hasTearOffUses) {
metadata = const ProcedureAttributesMetadata(
methodOrSetterCalledDynamically: false,
getterCalledDynamically: false,
hasNonThisUses: false,
hasTearOffUses: true);
} else if (hasNonThisUses && !hasTearOffUses) {
metadata = const ProcedureAttributesMetadata(
methodOrSetterCalledDynamically: false,
getterCalledDynamically: false,
hasNonThisUses: true,
hasTearOffUses: false);
} else {
metadata = const ProcedureAttributesMetadata.noDynamicUses();
}
_metadata.mapping[node] = metadata;
}
}
class DynamicSelectorsCollector extends RecursiveVisitor {
final Set<Selector> dynamicSelectors = new Set<Selector>();
final Set<Selector> nonThisSelectors = new Set<Selector>();
final Set<Selector> tearOffSelectors = new Set<Selector>();
static DynamicSelectorsCollector collect(Component component) {
final v = new DynamicSelectorsCollector();
v.visitComponent(component);
// We only populate [nonThisSelectors] and [tearOffSelectors] inside the
// non-dynamic case (for efficiency reasons) while visiting the [Component].
//
// After the recursive visit of [Component] we complete the sets here.
for (final Selector selector in v.dynamicSelectors) {
// All dynamic getters can be tearoffs.
if (selector.action == Action.get) {
v.tearOffSelectors.add(new Selector.doInvoke(selector.target));
}
// All dynamic selectors are non-this selectors.
v.nonThisSelectors.add(selector);
}
return v;
}
@override
visitMethodInvocation(MethodInvocation node) {
super.visitMethodInvocation(node);
Selector selector;
if (node.interfaceTarget == null) {
dynamicSelectors.add(new Selector.doInvoke(node.name));
} else {
if (node.receiver is! ThisExpression) {
nonThisSelectors.add(selector ??= new Selector.doInvoke(node.name));
}
}
}
@override
visitPropertyGet(PropertyGet node) {
super.visitPropertyGet(node);
Selector selector;
if (node.interfaceTarget == null) {
dynamicSelectors.add(selector = new Selector.doGet(node.name));
} else {
if (node.receiver is! ThisExpression) {
nonThisSelectors.add(selector ??= new Selector.doGet(node.name));
}
final target = node.interfaceTarget;
if (target is Procedure && target.kind == ProcedureKind.Method) {
tearOffSelectors.add(new Selector.doInvoke(node.name));
}
}
}
@override
visitPropertySet(PropertySet node) {
super.visitPropertySet(node);
Selector selector;
if (node.interfaceTarget == null) {
dynamicSelectors.add(selector = new Selector.doSet(node.name));
} else {
if (node.receiver is! ThisExpression) {
nonThisSelectors.add(selector ??= new Selector.doSet(node.name));
}
}
}
}