6e4674d445
The type parameter was a left-over from a pre-nnbd encoding of the visitors, but Flutter dependency on the code prevented its removal. TEST=existing Change-Id: I02657a469290f169077faa1167cf59b75de13c9c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/350840 Reviewed-by: Mayank Patke <fishythefish@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Johnni Winther <johnniwinther@google.com> Reviewed-by: Jens Johansen <jensj@google.com>
216 lines
6.7 KiB
Dart
216 lines
6.7 KiB
Dart
// Copyright (c) 2023, 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 'dart:collection';
|
|
|
|
import 'kernel.dart';
|
|
|
|
class _ConstVisitor extends RecursiveVisitor {
|
|
_ConstVisitor(
|
|
this.classLibraryUri,
|
|
this.className,
|
|
this.annotationClassLibraryUri,
|
|
this.annotationClassName,
|
|
) : _visitedInstances = <String>{},
|
|
constantInstances = <Map<String, dynamic>>[],
|
|
nonConstantLocations = <Map<String, dynamic>>[];
|
|
|
|
/// The library URI for the class to find.
|
|
final String classLibraryUri;
|
|
|
|
/// The name of the class to find.
|
|
final String className;
|
|
|
|
final Set<String> _visitedInstances;
|
|
// TODO(johnniwinther): Use extension types to type these.
|
|
final List<Map<String, dynamic>> constantInstances;
|
|
final List<Map<String, dynamic>> nonConstantLocations;
|
|
|
|
bool inIgnoredClass = false;
|
|
|
|
/// Whether or not we are currently within the declaration of the target
|
|
/// class.
|
|
///
|
|
/// We use this to determine when to skip tracking non-constant
|
|
/// [ConstructorInvocation]s. This is because, in web builds, a static
|
|
/// method is always created called _#new#tearOff() which returns the result
|
|
/// of a non-constant invocation of the unnamed constructor.
|
|
///
|
|
/// For the following Dart class "FooBar":
|
|
///
|
|
/// class FooBar {
|
|
/// const FooBar();
|
|
/// }
|
|
///
|
|
/// The following kernel structure is generated:
|
|
///
|
|
/// class FooBar extends core::Object /*hasConstConstructor*/ {
|
|
/// const constructor •() → min::FooBar
|
|
/// : super core::Object::•()
|
|
/// ;
|
|
/// static method _#new#tearOff() → min::FooBar
|
|
/// /* this is a non-const constructor invocation */
|
|
/// return new min::FooBar::•();
|
|
/// method noOp() → void {}
|
|
/// }
|
|
bool inTargetClass = false;
|
|
|
|
bool inTargetTearOff = false;
|
|
|
|
/// The name of the name of the class of the annotation marking classes
|
|
/// whose constant references should be ignored.
|
|
final String? annotationClassName;
|
|
|
|
/// The library URI of the class of the annotation marking classes whose
|
|
/// constant references should be ignored.
|
|
final String? annotationClassLibraryUri;
|
|
|
|
// A cache of previously evaluated classes.
|
|
static final Map<Class, bool> _classHierarchyCache = <Class, bool>{};
|
|
bool _matches(Class node) {
|
|
final bool? result = _classHierarchyCache[node];
|
|
if (result != null) {
|
|
return result;
|
|
}
|
|
final bool exactMatch = node.name == className &&
|
|
node.enclosingLibrary.importUri.toString() == classLibraryUri;
|
|
final bool match = exactMatch ||
|
|
node.supers.any((Supertype supertype) => _matches(supertype.classNode));
|
|
_classHierarchyCache[node] = match;
|
|
return match;
|
|
}
|
|
|
|
// Avoid visiting the same constant more than once.
|
|
// TODO(johnniwinther): Use [VisitOnceConstantVisitor] instead.
|
|
final Set<Constant> _cache = LinkedHashSet<Constant>.identity();
|
|
|
|
@override
|
|
void visitProcedure(Procedure node) {
|
|
// TODO(johnniwinther): Use lowering predicates.
|
|
final bool isTearOff = node.isStatic &&
|
|
node.kind == ProcedureKind.Method &&
|
|
node.name.text == '_#new#tearOff';
|
|
if (inTargetClass && isTearOff) {
|
|
inTargetTearOff = true;
|
|
}
|
|
super.visitProcedure(node);
|
|
inTargetTearOff = false;
|
|
}
|
|
|
|
@override
|
|
void defaultConstant(Constant node) {
|
|
if (_cache.add(node)) {
|
|
super.defaultConstant(node);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void defaultConstantReference(Constant node) {
|
|
defaultConstant(node);
|
|
}
|
|
|
|
@override
|
|
void visitConstructorInvocation(ConstructorInvocation node) {
|
|
final Class parentClass = node.target.parent! as Class;
|
|
if (!inTargetTearOff && _matches(parentClass)) {
|
|
final Location location = node.location!;
|
|
nonConstantLocations.add(<String, dynamic>{
|
|
'file': location.file.toString(),
|
|
'line': location.line,
|
|
'column': location.column,
|
|
});
|
|
}
|
|
super.visitConstructorInvocation(node);
|
|
}
|
|
|
|
@override
|
|
void visitClass(Class node) {
|
|
inTargetClass = _matches(node);
|
|
// check if this is a class that we should ignore
|
|
inIgnoredClass = _classShouldBeIgnored(node);
|
|
super.visitClass(node);
|
|
inTargetClass = false;
|
|
inIgnoredClass = false;
|
|
}
|
|
|
|
// If any annotations on the class match annotationClassName AND
|
|
// annotationClassLibraryUri.
|
|
bool _classShouldBeIgnored(Class node) {
|
|
if (annotationClassName == null || annotationClassLibraryUri == null) {
|
|
return false;
|
|
}
|
|
return node.annotations.any((Expression expression) {
|
|
if (expression is! ConstantExpression) {
|
|
return false;
|
|
}
|
|
|
|
final Constant constant = expression.constant;
|
|
return constant is InstanceConstant &&
|
|
constant.classNode.name == annotationClassName &&
|
|
constant.classNode.enclosingLibrary.importUri.toString() ==
|
|
annotationClassLibraryUri;
|
|
});
|
|
}
|
|
|
|
@override
|
|
void visitInstanceConstantReference(InstanceConstant node) {
|
|
super.visitInstanceConstantReference(node);
|
|
if (!_matches(node.classNode) || inIgnoredClass) {
|
|
return;
|
|
}
|
|
|
|
final Map<String, dynamic> instance = <String, dynamic>{};
|
|
for (final MapEntry<Reference, Constant> entry
|
|
in node.fieldValues.entries) {
|
|
if (entry.value is! PrimitiveConstant<dynamic>) {
|
|
continue;
|
|
}
|
|
final PrimitiveConstant<dynamic> value =
|
|
entry.value as PrimitiveConstant<dynamic>;
|
|
instance[entry.key.asField.name.text] = value.value;
|
|
}
|
|
if (_visitedInstances.add(instance.toString())) {
|
|
constantInstances.add(instance);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A kernel AST visitor that finds const references.
|
|
class ConstFinder {
|
|
/// The path to the file to open.
|
|
final String kernelFilePath;
|
|
|
|
/// Creates a new ConstFinder class.
|
|
///
|
|
/// The `kernelFilePath` is the path to a dill (kernel) file to process.
|
|
ConstFinder({
|
|
required this.kernelFilePath,
|
|
required String classLibraryUri,
|
|
required String className,
|
|
String? annotationClassLibraryUri,
|
|
String? annotationClassName,
|
|
}) : _visitor = _ConstVisitor(
|
|
classLibraryUri,
|
|
className,
|
|
annotationClassLibraryUri,
|
|
annotationClassName,
|
|
);
|
|
|
|
final _ConstVisitor _visitor;
|
|
|
|
/// Finds all instances
|
|
// TODO(johnniwinther): Use extension types to type the result.
|
|
Map<String, dynamic> findInstances() {
|
|
_visitor._visitedInstances.clear();
|
|
for (final Library library
|
|
in loadComponentFromBinary(kernelFilePath).libraries) {
|
|
library.visitChildren(_visitor);
|
|
}
|
|
return <String, dynamic>{
|
|
'constantInstances': _visitor.constantInstances,
|
|
'nonConstantLocations': _visitor.nonConstantLocations,
|
|
};
|
|
}
|
|
}
|