| // Copyright (c) 2022, 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:_fe_analyzer_shared/src/util/dependency_walker.dart'; | |
| import 'flow_analysis/flow_analysis_operations.dart'; | |
| /// Information about a [Class] that is necessary for computing the set of | |
| /// private `noSuchMethod` forwarding getters the compiler will generate. | |
| /// | |
| /// The type parameter [Class] has the same meaning as in [FieldPromotability]. | |
| class ClassInfo<Class extends Object> { | |
| final _InterfaceNode<Class> _interfaceNode; | |
| final _ImplementedNode<Class> _implementedNode; | |
| ClassInfo(this._interfaceNode, this._implementedNode); | |
| Class get _class => _interfaceNode._class; | |
| } | |
| /// Reasons why property accesses having a given field name are non-promotable. | |
| /// | |
| /// This class is part of the data structure output by | |
| /// [FieldPromotability.computeNonPromotabilityInfo]. | |
| /// | |
| /// The type parameters [Class], [Field], and [Getter] have the same meaning as | |
| /// in [FieldPromotability]. | |
| class FieldNameNonPromotabilityInfo<Class extends Object, Field, Getter> { | |
| /// The fields with the given name that are not promotable (either because | |
| /// they are not final or because they are external). | |
| /// | |
| /// (This list is initially empty and | |
| /// [FieldPromotability.computeNonPromotabilityInfo] accumulates entries into | |
| /// it.) | |
| final List<Field> conflictingFields = []; | |
| /// The explicit concrete getters with the given name. | |
| /// | |
| /// (This list is initially empty and | |
| /// [FieldPromotability.computeNonPromotabilityInfo] accumulates entries into | |
| /// it.) | |
| final List<Getter> conflictingGetters = []; | |
| /// The classes that implicitly forward a getter with the given name to | |
| /// `noSuchMethod`. | |
| /// | |
| /// The purpose of this list is so that the client can generate error messages | |
| /// that clarify to the user that field promotion was not possible due to | |
| /// `noSuchMethod` forwarding getters. It is a list of [Class] rather than | |
| /// [Getter] because `noSuchMethod` forwarding getters are always implicit; | |
| /// hence the error messages should be associated with the class. | |
| /// | |
| /// (This list is initially empty and | |
| /// [FieldPromotability.computeNonPromotabilityInfo] accumulates entries into | |
| /// it.) | |
| final List<Class> conflictingNsmClasses = []; | |
| FieldNameNonPromotabilityInfo._(); | |
| } | |
| /// This class examines all the [Class]es in a library and determines which | |
| /// fields are promotable within that library. | |
| /// | |
| /// The type parameters [Class], [Field] and [Getter] should be the data | |
| /// structures used by the client to represent classes, fields, and getters in | |
| /// the user's program. This class treats these types abstractly, so that each | |
| /// concrete Dart implementation can use their own representation of the user's | |
| /// code. | |
| /// | |
| /// Note: the term [Class] is used in a general sense here; it can represent any | |
| /// class, mixin, or enum declared in the user's program. | |
| /// | |
| /// A field is considered promotable if all of the following are true: | |
| /// - It is final | |
| /// - Its name is private | |
| /// - The library doesn't contain any non-final fields, concrete getters, or | |
| /// `noSuchMethod` getters with the same name. | |
| /// | |
| /// These rules were chosen because they are sufficient to determine that all | |
| /// reads of the field that complete normally will either throw an exception, | |
| /// return a single stable object, or return objects with a single stable | |
| /// runtime type; this ensures that type promotion is safe. | |
| /// | |
| /// The reason the field must be private is because if it isn't, then at | |
| /// runtime, a read of the field might resolve to a getter or a non-final field | |
| /// in a subclass that's implemented in some other library, and hence the result | |
| /// of the read won't be stable. | |
| /// | |
| /// Note that as a result of the third bullet item, any private name that's | |
| /// associated with a non-final field, concrete getter, or `noSuchMethod` getter | |
| /// renders all fields with the same name unpromotable within the library. The | |
| /// reason for this is that since most Dart classes can also be used as | |
| /// interfaces (via an `implements` clause), a read that statically resolves to | |
| /// a private final field might not actually resolve to that field at runtime; | |
| /// it might resolve to some other field or getter with the same name. (In | |
| /// principle it would be possible to narrow the set of possible resolution | |
| /// targets through whole-program analysis, or by careful consideration of class | |
| /// modifiers and public vs. private class names; however that would make the | |
| /// analysis much more complex, and also make it difficult to explain to users | |
| /// when to expect field promotion to work; so we've elected to simply assume | |
| /// that all fields and getters with the same name might potentially alias to | |
| /// one another). | |
| /// | |
| /// Since all fields with the same name within the library have the same | |
| /// promotability, this class doesn't attempt to assign a promotability boolean | |
| /// to each field; instead it computes the set of private names for which field | |
| /// promotion is not allowed. | |
| /// | |
| /// A word about `noSuchMethod` getters: a `noSuchMethod` getter is a | |
| /// `noSuchMethod` forwarder that implements a getter. The compiler generates | |
| /// `noSuchMethod` forwarders when a concrete [Class] inherits a member into its | |
| /// interface (via an `implements` clause), but it doesn't contain or inherit a | |
| /// concrete implementation of that member. (Note that this is only legal if the | |
| /// class contains or inherits an implementation of `noSuchMethod`). If the name | |
| /// of the inherited member is accessible in the [Class]'s library (i.e. it's | |
| /// either a public name or it's a private name that belongs to the [Class]'s | |
| /// library), then the synthetic `noSuchMethod` forwarder calls `noSuchMethod` | |
| /// and passes along the return value (casting it to the proper type). If the | |
| /// name of the inherited member *isn't* accessible in the [Class]'s library, | |
| /// then the synthetic `noSuchMethod` forwarder throws an exception. | |
| /// | |
| /// `noSuchMethod` getters that forward to `noSuchMethod` defeat field promotion | |
| /// for the same reason that ordinary getters do (they aren't guaranteed to | |
| /// return a stable value). However, `noSuchMethod` getters that simply throw an | |
| /// exception do not defeat field promotion, because the exception prevents any | |
| /// code that relies on field promotion soundness from being reachable. Since | |
| /// only private fields are eligible from promotion in the first place, this | |
| /// means that it's only necessary to search the current library for | |
| /// `noSuchMethod` getters that might defeat field promotion (because a | |
| /// `noSuchMethod` getter in another library that's associated with a private | |
| /// name in *this* library will always throw). | |
| /// | |
| /// Note that it's possible that one class will have a final field with a given | |
| /// private name, while another class will have a method with the same name (or | |
| /// a `noSuchMethod` forwarder for such a method). If this happens, then an | |
| /// attempt to read the field might at runtime resolve to a tear-off of the | |
| /// corresponding method. This is ok (it's not necessary to suppress promotion | |
| /// of the field), because even though the resulting tear-offs might not be | |
| /// stable in the sense of always being identical, they will nonetheless always | |
| /// have the same runtime type. Hence, we can completely ignore methods when | |
| /// computing which fields in the library are promotable. | |
| abstract class FieldPromotability<Class extends Object, Field, Getter> { | |
| /// Map whose keys are the field names in the library that have been | |
| /// determined to be unsafe to promote, and whose values are an instance of | |
| /// `FieldNameNonPromotabilityInfo` describing why. | |
| final Map<String, FieldNameNonPromotabilityInfo<Class, Field, Getter>> | |
| _nonPromotabilityInfo = {}; | |
| /// Map from a [Class] object to the [_ImplementedNode] that records the names | |
| /// of concrete fields and getters declared in or inherited by the [Class]. | |
| final Map<Class, _ImplementedNode<Class>> _implementedNodes = | |
| new Map.identity(); | |
| /// Map from a [Class] object to the [_InterfaceNode] that records the names | |
| /// of getters in the interface for a [Class]. | |
| final Map<Class, _InterfaceNode<Class>> _interfaceNodes = new Map.identity(); | |
| /// List of information about concrete [Class]es in the library. | |
| final List<ClassInfo<Class>> _concreteInfoList = []; | |
| /// Records the presence of [class_] in the library. The client should call | |
| /// this method once for each class, mixin, and enum declared in the library. | |
| /// | |
| /// Returns a [ClassInfo] object describing the class. After calling this | |
| /// method, the client should call [addField] and [addGetter] to record all | |
| /// the non-synthetic instance fields and getters in the class. | |
| ClassInfo<Class> addClass(Class class_, {required bool isAbstract}) { | |
| ClassInfo<Class> classInfo = new ClassInfo<Class>( | |
| _getInterfaceNode(class_), | |
| _getImplementedNode(class_), | |
| ); | |
| if (!isAbstract) { | |
| _concreteInfoList.add(classInfo); | |
| } | |
| return classInfo; | |
| } | |
| /// Records that the [Class] described by [classInfo] contains non-synthetic | |
| /// instance [field] with the given [name]. | |
| /// | |
| /// [isFinal] indicates whether the field is a final field. [isAbstract] | |
| /// indicates whether the field is abstract. [isExternal] indicates whether | |
| /// the field is external. | |
| /// | |
| /// A return value of `null` indicates that this field *might* wind up being | |
| /// promotable; any other return value indicates the reason why it | |
| /// *definitely* isn't promotable. | |
| PropertyNonPromotabilityReason? addField( | |
| ClassInfo<Class> classInfo, | |
| Field field, | |
| String name, { | |
| required bool isFinal, | |
| required bool isAbstract, | |
| required bool isExternal, | |
| }) { | |
| // Public fields are never promotable, so we may safely ignore fields with | |
| // public names. | |
| if (!name.startsWith('_')) { | |
| return PropertyNonPromotabilityReason.isNotPrivate; | |
| } | |
| // Record the field name for later use in computation of `noSuchMethod` | |
| // getters. | |
| classInfo._interfaceNode._directNames.add(name); | |
| if (!isAbstract) { | |
| classInfo._implementedNode._directNames.add(name); | |
| } | |
| if (isExternal || !isFinal) { | |
| // The field isn't promotable, nor is any other field in the library with | |
| // the same name. | |
| _fieldNonPromoInfo(name).conflictingFields.add(field); | |
| return isExternal | |
| ? PropertyNonPromotabilityReason.isExternal | |
| : PropertyNonPromotabilityReason.isNotFinal; | |
| } | |
| // The field is final and not external, so it might wind up being | |
| // promotable. | |
| return null; | |
| } | |
| /// Records that the [Class] described by [classInfo] contains a non-synthetic | |
| /// instance [getter] with the given [name]. | |
| /// | |
| /// [isAbstract] indicates whether the getter is abstract. | |
| /// | |
| /// A return value of `null` indicates that this getter *might* wind up being | |
| /// promotable; any other return value indicates the reason why it | |
| /// *definitely* isn't promotable. | |
| PropertyNonPromotabilityReason? addGetter( | |
| ClassInfo<Class> classInfo, | |
| Getter getter, | |
| String name, { | |
| required bool isAbstract, | |
| }) { | |
| // Public fields are never promotable, so we may safely ignore getters with | |
| // public names. | |
| if (!name.startsWith('_')) { | |
| return PropertyNonPromotabilityReason.isNotPrivate; | |
| } | |
| // Record the getter name for later use in computation of `noSuchMethod` | |
| // getters. | |
| classInfo._interfaceNode._directNames.add(name); | |
| if (!isAbstract) { | |
| classInfo._implementedNode._directNames.add(name); | |
| // The getter is concrete, so no fields with the same name are promotable. | |
| _fieldNonPromoInfo(name).conflictingGetters.add(getter); | |
| return PropertyNonPromotabilityReason.isNotField; | |
| } else { | |
| return null; | |
| } | |
| } | |
| /// Computes the set of private field names which are not safe to promote in | |
| /// the library, along with the reasons why. | |
| /// | |
| /// The client should call this method once after all [Class]es, fields, and | |
| /// getters have been recorded using [addClass], [addField], and [addGetter]. | |
| Map<String, FieldNameNonPromotabilityInfo<Class, Field, Getter>> | |
| computeNonPromotabilityInfo() { | |
| // The names of private non-final fields and private getters have already | |
| // been added to [_unpromotableFieldNames] by [addField] and [addGetter]. So | |
| // all that remains to do is figure out which field names are unpromotable | |
| // due to the presence of `noSuchMethod` getters. To do this, we'll need to | |
| // walk the class hierarchy and gather (a) the names of private instance | |
| // getters in each class's interface, and (b) the names of private instance | |
| // fields and getters in each class's implementation. | |
| _ClassHierarchyWalker<Class> interfaceWalker = | |
| new _ClassHierarchyWalker<Class>(); | |
| _ClassHierarchyWalker<Class> implementedWalker = | |
| new _ClassHierarchyWalker<Class>(); | |
| // For each concrete class in the library, | |
| for (ClassInfo<Class> info in _concreteInfoList) { | |
| // Compute names of getters in the interface. | |
| _InterfaceNode<Class> interfaceNode = info._interfaceNode; | |
| interfaceWalker.walk(interfaceNode); | |
| Set<String> interfaceNames = interfaceNode._transitiveNames!; | |
| // Compute names of actually implemented getters. | |
| _ImplementedNode<Class> implementedNode = info._implementedNode; | |
| implementedWalker.walk(implementedNode); | |
| Set<String> implementedNames = implementedNode._transitiveNames!; | |
| // `noSuchMethod`-forwarding getters will be generated for getters that | |
| // are in the interface, but not actually implemented; consequently, | |
| // fields with these names are not safe to promote. | |
| for (String name in interfaceNames) { | |
| if (!implementedNames.contains(name)) { | |
| _fieldNonPromoInfo(name).conflictingNsmClasses.add(info._class); | |
| } | |
| } | |
| } | |
| return _nonPromotabilityInfo; | |
| } | |
| /// Returns an iterable of the direct superclasses of [class_]. If | |
| /// [ignoreImplements] is `true`, then superclasses reached through an | |
| /// `implements` clause are ignored. | |
| /// | |
| /// This is used to gather the transitive closure of fields and getters | |
| /// present in a class's interface and implementation. Therefore, it does not | |
| /// matter whether the client uses a fully sugared model of mixins (in which | |
| /// `class C extends B with M1, M2 {}` is represented as a single class with | |
| /// direct superclasses `B`, `M1`, and `M2`) or a fully desugared model (in | |
| /// which `class C extends B with M1, M2` represents a class `C` with | |
| /// superclass `B&M1&M2`, which in turn has supertypes `B&M1` and `M2`, etc.) | |
| Iterable<Class> getSuperclasses( | |
| Class class_, { | |
| required bool ignoreImplements, | |
| }); | |
| /// Gets the [FieldNameNonPromotabilityInfo] object corresponding to [name] | |
| /// from [_nonPromotabilityInfo], creating it if necessary. | |
| FieldNameNonPromotabilityInfo<Class, Field, Getter> _fieldNonPromoInfo( | |
| String name, | |
| ) => _nonPromotabilityInfo.putIfAbsent(name, FieldNameNonPromotabilityInfo._); | |
| /// Gets or creates the [_ImplementedNode] for [class_]. | |
| _ImplementedNode<Class> _getImplementedNode(Class class_) => | |
| _implementedNodes[class_] ??= new _ImplementedNode<Class>(this, class_); | |
| /// Gets or creates the [_InterfaceNode] for [class_]. | |
| _InterfaceNode<Class> _getInterfaceNode(Class class_) => | |
| _interfaceNodes[class_] ??= new _InterfaceNode<Class>(this, class_); | |
| } | |
| /// Dependency walker that traverses the graph of a class's type hierarchy, | |
| /// gathering the transitive closure of field and getter names. | |
| /// | |
| /// This is based on the [DependencyWalker] class, which implements Tarjan's | |
| /// strongly connected component's algorithm in order to efficiently handle | |
| /// cycles in the class hierarchy (which the analyzer tolerates). | |
| class _ClassHierarchyWalker<Class extends Object> | |
| extends DependencyWalker<_Node<Class>> { | |
| @override | |
| void evaluate(_Node<Class> v) => evaluateScc([v]); | |
| @override | |
| void evaluateScc(List<_Node<Class>> scc) { | |
| // Gather the names directly declared in all the classes in this strongly | |
| // connected component, plus all the names in the transitive closure of the | |
| // strongly connected components this component depends on. | |
| Set<String> transitiveNames = <String>{}; | |
| for (_Node<Class> node in scc) { | |
| transitiveNames.addAll(node._directNames); | |
| for (_Node<Class> dependency in Node.getDependencies(node)) { | |
| Set<String>? namesFromDependency = dependency._transitiveNames; | |
| if (namesFromDependency != null) { | |
| transitiveNames.addAll(namesFromDependency); | |
| } | |
| } | |
| } | |
| // Store this list of names in all the nodes of this strongly connected | |
| // component. | |
| for (_Node<Class> node in scc) { | |
| node._transitiveNames = transitiveNames; | |
| } | |
| } | |
| } | |
| /// Data structure tracking the set of private fields and getters a [Class] | |
| /// concretely implements. | |
| /// | |
| /// This data structure extends [_Node] so that we can efficiently walk the | |
| /// superclass chain (without having to worry about circularities) in order to | |
| /// include getters concretely implemented in superclasses and mixins. | |
| class _ImplementedNode<Class extends Object> extends _Node<Class> { | |
| _ImplementedNode(super.fieldPromotability, super.element); | |
| @override | |
| List<_Node<Class>> computeDependencies() { | |
| // We need to gather field and getter names from the transitive closure of | |
| // superclasses, following `with` and `extends` edges but not `implements` | |
| // edges. So the set of dependencies of this node is the set of immediate | |
| // superclasses, ignoring `implements`. | |
| List<_Node<Class>> dependencies = []; | |
| for (Class supertype in _fieldPromotability.getSuperclasses( | |
| _class, | |
| ignoreImplements: true, | |
| )) { | |
| dependencies.add(_fieldPromotability._getImplementedNode(supertype)); | |
| } | |
| return dependencies; | |
| } | |
| } | |
| /// Data structure tracking the set of getters in a [Class]'s interface. | |
| /// | |
| /// This data structure extends [_Node] so that we can efficiently walk the | |
| /// class hierarchy (without having to worry about circularities) in order to | |
| /// include getters defined in superclasses, mixins, and interfaces. | |
| class _InterfaceNode<Class extends Object> extends _Node<Class> { | |
| _InterfaceNode(super.fieldPromotability, super.element); | |
| @override | |
| List<_Node<Class>> computeDependencies() { | |
| // We need to gather field and getter names from the transitive closure of | |
| // superclasses, following `with`, `extends`, `implements`, and `on` edges. | |
| // So the set of dependencies of this node is the set of immediate | |
| // superclasses, including `implements`. | |
| List<_Node<Class>> dependencies = []; | |
| for (Class supertype in _fieldPromotability.getSuperclasses( | |
| _class, | |
| ignoreImplements: false, | |
| )) { | |
| dependencies.add(_fieldPromotability._getInterfaceNode(supertype)); | |
| } | |
| return dependencies; | |
| } | |
| } | |
| /// A node in either the graph of a class's type hierarchy, recording the | |
| /// information necessary for computing the set of private `noSuchMethod` | |
| /// getters the compiler will generate. | |
| abstract class _Node<Class extends Object> extends Node<_Node<Class>> { | |
| /// A reference back to the [FieldPromotability] object. | |
| final FieldPromotability<Class, Object?, Object?> _fieldPromotability; | |
| /// The [Class] represented by this node. | |
| final Class _class; | |
| /// The names of getters declared by [_class] directly. | |
| final Set<String> _directNames = {}; | |
| /// The names of getters declared by [_class] and its superinterfaces. | |
| /// | |
| /// Populated when [_ClassHierarchyWalker] encounters a strongly connected | |
| /// component. | |
| Set<String>? _transitiveNames; | |
| _Node(this._fieldPromotability, this._class); | |
| @override | |
| bool get isEvaluated => _transitiveNames != null; | |
| } |