| // 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}'; |
| } |
| } |
| } |
| |
| // 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 |
| visitInstanceInvocation(InstanceInvocation node) { |
| super.visitInstanceInvocation(node); |
| if (node.receiver is! ThisExpression) { |
| nonThisSelectors.add(new Selector.doInvoke(node.name)); |
| } |
| } |
| |
| @override |
| visitDynamicInvocation(DynamicInvocation node) { |
| super.visitDynamicInvocation(node); |
| dynamicSelectors.add(new Selector.doInvoke(node.name)); |
| } |
| |
| @override |
| visitEqualsCall(EqualsCall node) { |
| super.visitEqualsCall(node); |
| if (node.left is! ThisExpression) { |
| nonThisSelectors.add(new Selector.doInvoke(Name('=='))); |
| } |
| } |
| |
| @override |
| visitInstanceGet(InstanceGet node) { |
| super.visitInstanceGet(node); |
| if (node.receiver is! ThisExpression) { |
| nonThisSelectors.add(new Selector.doGet(node.name)); |
| } |
| } |
| |
| @override |
| visitDynamicGet(DynamicGet node) { |
| super.visitDynamicGet(node); |
| dynamicSelectors.add(new Selector.doGet(node.name)); |
| } |
| |
| @override |
| visitInstanceTearOff(InstanceTearOff node) { |
| super.visitInstanceTearOff(node); |
| if (node.receiver is! ThisExpression) { |
| nonThisSelectors.add(new Selector.doGet(node.name)); |
| } |
| tearOffSelectors.add(new Selector.doInvoke(node.name)); |
| } |
| |
| @override |
| visitInstanceSet(InstanceSet node) { |
| super.visitInstanceSet(node); |
| if (node.receiver is! ThisExpression) { |
| nonThisSelectors.add(new Selector.doSet(node.name)); |
| } |
| } |
| |
| @override |
| visitDynamicSet(DynamicSet node) { |
| super.visitDynamicSet(node); |
| dynamicSelectors.add(new Selector.doSet(node.name)); |
| } |
| } |