blob: 4c5811f50c81bc38bf0d7e67d11ecc8ab91a3cd4 [file] [log] [blame]
// 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;
}