| // 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.noDynamicUses( | |
| 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.noDynamicUses( | |
| hasNonThisUses: false, | |
| hasTearOffUses: false, | |
| ); | |
| } else if (!hasNonThisUses && hasTearOffUses) { | |
| metadata = const ProcedureAttributesMetadata.noDynamicUses( | |
| hasNonThisUses: false, | |
| hasTearOffUses: true, | |
| ); | |
| } else if (hasNonThisUses && !hasTearOffUses) { | |
| metadata = const ProcedureAttributesMetadata.noDynamicUses( | |
| 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)); | |
| } | |
| } |