[cfe] Copy const_finder visitor from Flutter SDK

This copies the const_finder visitor from the Flutter SDK to the
Dart SDK to avoid having source code dependency on package:kernel
in the Flutter repository.

Change-Id: I76f98453c1650e63623708a9f4860d80ab4b25aa
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/326645
Reviewed-by: Christopher Fujino <fujino@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: William Hesse <whesse@google.com>
This commit is contained in:
Johnni Winther 2023-09-19 12:19:26 +00:00 committed by Commit Queue
parent 3ee5bdfcf3
commit f0933ab69e
12 changed files with 1281 additions and 0 deletions

View file

@ -6,3 +6,7 @@ linter:
- directives_ordering
- sort_pub_dependencies
- unawaited_futures
analyzer:
exclude:
- test/fixtures/**

View file

@ -23,5 +23,8 @@ dependencies:
# Use 'any' constraints here; we get our versions from the DEPS file.
dev_dependencies:
collection: any
const_finder_fixtures: any
const_finder_fixtures_package: any
lints: any
test: any

View file

@ -0,0 +1,583 @@
// 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:convert';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:front_end/src/api_unstable/vm.dart';
import 'package:kernel/const_finder.dart';
import 'package:path/path.dart' as path;
void expect<T>(T value, T expected) {
if (value != expected) {
stderr.writeln('Expected: $expected');
stderr.writeln('Actual: $value');
exitCode = -1;
}
}
void expectInstances(dynamic value, dynamic expected, Compiler compiler) {
// To ensure we ignore insertion order into maps as well as lists we use
// DeepCollectionEquality as well as sort the lists.
int compareByStringValue(dynamic a, dynamic b) {
return a['stringValue'].compareTo(b['stringValue']) as int;
}
value['constantInstances'].sort(compareByStringValue);
expected['constantInstances'].sort(compareByStringValue);
final Equality<Object?> equality;
if (compiler == Compiler.dart2js) {
equality = const Dart2JSDeepCollectionEquality();
} else {
equality = const DeepCollectionEquality();
}
if (!equality.equals(value, expected)) {
stderr.writeln('Expected: ${jsonEncode(expected)}');
stderr.writeln('Actual: ${jsonEncode(value)}');
exitCode = -1;
}
}
// This test is assuming the `dart` used to invoke the tests is compatible
// with the version of package:kernel in //third-party/dart/pkg/kernel
final String dart = Platform.resolvedExecutable;
final String bat = Platform.isWindows ? '.bat' : '';
void _checkRecursion(String dillPath, Compiler compiler) {
stdout.writeln('Checking recursive calls.');
final ConstFinder finder = ConstFinder(
kernelFilePath: dillPath,
classLibraryUri: 'package:const_finder_fixtures/box.dart',
className: 'Box',
);
// Will timeout if we did things wrong.
jsonEncode(finder.findInstances());
}
void _checkConsts(String dillPath, Compiler compiler) {
stdout.writeln('Checking for expected constants.');
final ConstFinder finder = ConstFinder(
kernelFilePath: dillPath,
classLibraryUri: 'package:const_finder_fixtures/target.dart',
className: 'Target',
);
final Map<String, Object?> expectation = <String, dynamic>{
'constantInstances': <Map<String, dynamic>>[
<String, dynamic>{
'stringValue': '100',
'intValue': 100,
'targetValue': null
},
<String, dynamic>{
'stringValue': '102',
'intValue': 102,
'targetValue': null
},
<String, dynamic>{'stringValue': '101', 'intValue': 101},
<String, dynamic>{
'stringValue': '103',
'intValue': 103,
'targetValue': null
},
<String, dynamic>{
'stringValue': '105',
'intValue': 105,
'targetValue': null
},
<String, dynamic>{'stringValue': '104', 'intValue': 104},
<String, dynamic>{
'stringValue': '106',
'intValue': 106,
'targetValue': null
},
<String, dynamic>{
'stringValue': '108',
'intValue': 108,
'targetValue': null
},
<String, dynamic>{'stringValue': '107', 'intValue': 107},
<String, dynamic>{'stringValue': '1', 'intValue': 1, 'targetValue': null},
<String, dynamic>{'stringValue': '4', 'intValue': 4, 'targetValue': null},
<String, dynamic>{'stringValue': '2', 'intValue': 2},
<String, dynamic>{'stringValue': '6', 'intValue': 6, 'targetValue': null},
<String, dynamic>{'stringValue': '8', 'intValue': 8, 'targetValue': null},
<String, dynamic>{
'stringValue': '10',
'intValue': 10,
'targetValue': null
},
<String, dynamic>{'stringValue': '9', 'intValue': 9},
<String, dynamic>{'stringValue': '7', 'intValue': 7, 'targetValue': null},
<String, dynamic>{
'stringValue': '11',
'intValue': 11,
'targetValue': null
},
<String, dynamic>{
'stringValue': '12',
'intValue': 12,
'targetValue': null
},
<String, dynamic>{
'stringValue': 'package',
'intValue': -1,
'targetValue': null
},
],
'nonConstantLocations': <dynamic>[],
};
if (compiler == Compiler.aot) {
expectation['nonConstantLocations'] = <Object?>[];
} else {
final String fixturesUrl = Platform.isWindows
? '/$fixtures'.replaceAll(Platform.pathSeparator, '/')
: fixtures;
// Without true tree-shaking, there is a non-const reference in a
// never-invoked function that will be present in the dill.
expectation['nonConstantLocations'] = <Object?>[
<String, dynamic>{
'file': 'file://$fixturesUrl/pkg/package.dart',
'line': 13,
'column': 25,
},
];
}
expectInstances(
finder.findInstances(),
expectation,
compiler,
);
final ConstFinder finder2 = ConstFinder(
kernelFilePath: dillPath,
classLibraryUri: 'package:const_finder_fixtures/target.dart',
className: 'MixedInTarget',
);
expectInstances(
finder2.findInstances(),
<String, dynamic>{
'constantInstances': <Map<String, dynamic>>[
<String, dynamic>{'val': '13'},
],
'nonConstantLocations': <dynamic>[],
},
compiler,
);
}
void _checkAnnotation(String dillPath, Compiler compiler) {
stdout.writeln(
'Checking constant instances in a class annotated with instance of '
'StaticIconProvider are ignored with $compiler');
final ConstFinder finder = ConstFinder(
kernelFilePath: dillPath,
classLibraryUri: 'package:const_finder_fixtures/target.dart',
className: 'Target',
annotationClassName: 'StaticIconProvider',
annotationClassLibraryUri:
'package:const_finder_fixtures/static_icon_provider.dart',
);
final Map<String, dynamic> instances = finder.findInstances();
expectInstances(
instances,
<String, dynamic>{
'constantInstances': <Map<String, Object?>>[
<String, Object?>{
'stringValue': 'used1',
'intValue': 1,
'targetValue': null,
},
<String, Object?>{
'stringValue': 'used2',
'intValue': 2,
'targetValue': null,
},
],
// TODO(fujino): This should have non-constant locations from the use of
// a tear-off, see https://github.com/flutter/flutter/issues/116797
'nonConstantLocations': <Object?>[],
},
compiler,
);
}
void _checkNonConstsFrontend(String dillPath, Compiler compiler) {
stdout.writeln('Checking for non-constant instances with $compiler');
final ConstFinder finder = ConstFinder(
kernelFilePath: dillPath,
classLibraryUri: 'package:const_finder_fixtures/target.dart',
className: 'Target',
);
final String fixturesUrl = Platform.isWindows
? '/$fixtures'.replaceAll(Platform.pathSeparator, '/')
: fixtures;
expectInstances(
finder.findInstances(),
<String, dynamic>{
'constantInstances': <dynamic>[
<String, dynamic>{
'stringValue': '1',
'intValue': 1,
'targetValue': null
},
<String, dynamic>{
'stringValue': '4',
'intValue': 4,
'targetValue': null
},
<String, dynamic>{
'stringValue': '6',
'intValue': 6,
'targetValue': null
},
<String, dynamic>{
'stringValue': '8',
'intValue': 8,
'targetValue': null
},
<String, dynamic>{
'stringValue': '10',
'intValue': 10,
'targetValue': null
},
<String, dynamic>{'stringValue': '9', 'intValue': 9},
<String, dynamic>{
'stringValue': '7',
'intValue': 7,
'targetValue': null
},
],
'nonConstantLocations': <dynamic>[
<String, dynamic>{
'file': 'file://$fixturesUrl/lib/consts_and_non.dart',
'line': 13,
'column': 26,
},
<String, dynamic>{
'file': 'file://$fixturesUrl/lib/consts_and_non.dart',
'line': 16,
'column': 7,
},
<String, dynamic>{
'file': 'file://$fixturesUrl/lib/consts_and_non.dart',
'line': 16,
'column': 22,
},
<String, dynamic>{
'file': 'file://$fixturesUrl/lib/consts_and_non.dart',
'line': 17,
'column': 26,
},
<String, dynamic>{
'file': 'file://$fixturesUrl/pkg/package.dart',
'line': 13,
'column': 25,
}
]
},
compiler,
);
}
// Note, since web dills don't have tree shaking, we aren't able to eliminate
// an additional const versus _checkNonConstFrontend.
void _checkNonConstsWeb(String dillPath, Compiler compiler) {
assert(compiler == Compiler.dart2js);
stdout.writeln('Checking for non-constant instances with $compiler');
final ConstFinder finder = ConstFinder(
kernelFilePath: dillPath,
classLibraryUri: 'package:const_finder_fixtures/target.dart',
className: 'Target',
);
final String fixturesUrl = Platform.isWindows
? '/$fixtures'.replaceAll(Platform.pathSeparator, '/')
: fixtures;
expectInstances(
finder.findInstances(),
<String, dynamic>{
'constantInstances': <dynamic>[
<String, dynamic>{
'stringValue': '1',
'intValue': 1,
'targetValue': null
},
<String, dynamic>{
'stringValue': '4',
'intValue': 4,
'targetValue': null
},
<String, dynamic>{
'stringValue': '6',
'intValue': 6,
'targetValue': null
},
<String, dynamic>{
'stringValue': '8',
'intValue': 8,
'targetValue': null
},
<String, dynamic>{
'stringValue': '10',
'intValue': 10,
'targetValue': null
},
<String, dynamic>{'stringValue': '9', 'intValue': 9},
<String, dynamic>{
'stringValue': '7',
'intValue': 7,
'targetValue': null
},
<String, dynamic>{
'stringValue': 'package',
'intValue': -1,
'targetValue': null
},
],
'nonConstantLocations': <dynamic>[
<String, dynamic>{
'file': 'file://$fixturesUrl/lib/consts_and_non.dart',
'line': 13,
'column': 26,
},
<String, dynamic>{
'file': 'file://$fixturesUrl/lib/consts_and_non.dart',
'line': 16,
'column': 7,
},
<String, dynamic>{
'file': 'file://$fixturesUrl/lib/consts_and_non.dart',
'line': 16,
'column': 22,
},
<String, dynamic>{
'file': 'file://$fixturesUrl/lib/consts_and_non.dart',
'line': 17,
'column': 26,
},
<String, dynamic>{
'file': 'file://$fixturesUrl/pkg/package.dart',
'line': 13,
'column': 25,
}
],
},
compiler,
);
}
void checkProcessResult(ProcessResult result) {
if (result.exitCode != 0) {
stdout.writeln(result.stdout);
stderr.writeln(result.stderr);
}
expect(result.exitCode, 0);
}
final String basePath = path
.canonicalize(path.join(path.dirname(Platform.script.toFilePath()), '..'));
final String fixtures = path.join(basePath, 'test', 'fixtures');
final String packageConfig =
path.join(fixtures, '.dart_tool', 'package_config.json');
// TODO(johnniwinther): Add unittests to package:kernel.
// TODO(johnniwinther): Avoid emitting .deps files during testing.
Future<void> main() async {
final frontendServer = 'pkg/frontend_server/bin/frontend_server_starter.dart';
final sdkRoot = computePlatformBinariesLocation().toFilePath();
final String librariesSpec = 'sdk/lib/libraries.json';
final List<_Test> tests = <_Test>[
_Test(
name: 'box_frontend',
dartSource: path.join(fixtures, 'lib', 'box.dart'),
frontendServer: frontendServer,
sdkRoot: sdkRoot,
librariesSpec: librariesSpec,
verify: _checkRecursion,
compiler: Compiler.aot,
),
_Test(
name: 'box_web',
dartSource: path.join(fixtures, 'lib', 'box.dart'),
frontendServer: frontendServer,
sdkRoot: sdkRoot,
librariesSpec: librariesSpec,
verify: _checkRecursion,
compiler: Compiler.dart2js,
),
_Test(
name: 'consts_frontend',
dartSource: path.join(fixtures, 'lib', 'consts.dart'),
frontendServer: frontendServer,
sdkRoot: sdkRoot,
librariesSpec: librariesSpec,
verify: _checkConsts,
compiler: Compiler.aot,
),
_Test(
name: 'consts_web',
dartSource: path.join(fixtures, 'lib', 'consts.dart'),
frontendServer: frontendServer,
sdkRoot: sdkRoot,
librariesSpec: librariesSpec,
verify: _checkConsts,
compiler: Compiler.dart2js,
),
_Test(
name: 'consts_and_non_frontend',
dartSource: path.join(fixtures, 'lib', 'consts_and_non.dart'),
frontendServer: frontendServer,
sdkRoot: sdkRoot,
librariesSpec: librariesSpec,
verify: _checkNonConstsFrontend,
compiler: Compiler.aot,
),
_Test(
name: 'consts_and_non_web',
dartSource: path.join(fixtures, 'lib', 'consts_and_non.dart'),
frontendServer: frontendServer,
sdkRoot: sdkRoot,
librariesSpec: librariesSpec,
verify: _checkNonConstsWeb,
compiler: Compiler.dart2js,
),
_Test(
name: 'static_icon_provider_frontend',
dartSource: path.join(fixtures, 'lib', 'static_icon_provider.dart'),
frontendServer: frontendServer,
sdkRoot: sdkRoot,
librariesSpec: librariesSpec,
verify: _checkAnnotation,
compiler: Compiler.aot,
),
_Test(
name: 'static_icon_provider_web',
dartSource: path.join(fixtures, 'lib', 'static_icon_provider.dart'),
frontendServer: frontendServer,
sdkRoot: sdkRoot,
librariesSpec: librariesSpec,
verify: _checkAnnotation,
compiler: Compiler.dart2js,
),
];
try {
stdout.writeln('Generating kernel fixtures...');
for (final _Test test in tests) {
test.run();
}
} finally {
try {
for (final _Test test in tests) {
test.dispose();
}
} finally {
stdout.writeln('Tests ${exitCode == 0 ? 'succeeded' : 'failed'} '
'- exit code: $exitCode');
}
}
}
enum Compiler {
// Uses TFA tree-shaking.
aot,
// Does not have TFA tree-shaking.
dart2js,
}
class _Test {
_Test({
required this.name,
required this.dartSource,
required this.sdkRoot,
required this.verify,
required this.frontendServer,
required this.librariesSpec,
required this.compiler,
}) : dillPath = path.join(fixtures, '$name.dill');
final String name;
final String dartSource;
final String sdkRoot;
final String frontendServer;
final String librariesSpec;
final String dillPath;
void Function(String, Compiler) verify;
final Compiler compiler;
final List<String> resourcesToDispose = <String>[];
void run() {
stdout.writeln('Compiling $dartSource to $dillPath with $compiler');
if (compiler == Compiler.aot) {
_compileAOTDill();
} else {
_compileWebDill();
}
stdout.writeln('Testing $dillPath');
verify(dillPath, compiler);
}
void dispose() {
for (final String resource in resourcesToDispose) {
stdout.writeln('Deleting $resource');
File(resource).deleteSync();
}
}
void _compileAOTDill() {
checkProcessResult(Process.runSync(dart, <String>[
frontendServer,
'--sdk-root=$sdkRoot',
'--platform=vm_platform_strong.dill',
'--target=vm',
'--aot',
'--tfa',
'--packages=$packageConfig',
'--output-dill=$dillPath',
dartSource,
]));
resourcesToDispose.add(dillPath);
}
void _compileWebDill() {
final ProcessResult result = Process.runSync(dart, <String>[
'compile',
'js',
'--libraries-spec=$librariesSpec',
'-Ddart.vm.product=true',
'-o',
dillPath,
'--packages=$packageConfig',
'--cfe-only',
dartSource,
]);
checkProcessResult(result);
resourcesToDispose.add(dillPath);
}
}
/// Equality that casts all [num]'s to [double] before comparing.
class Dart2JSDeepCollectionEquality extends DeepCollectionEquality {
const Dart2JSDeepCollectionEquality();
@override
bool equals(Object? e1, Object? e2) {
if (e1 is num && e2 is num) {
return e1.toDouble() == e2.toDouble();
}
return super.equals(e1, e2);
}
}

View file

@ -0,0 +1,15 @@
{
"configVersion": 2,
"packages": [
{
"name": "const_finder_fixtures",
"rootUri": "../lib/",
"languageVersion": "2.17"
},
{
"name": "const_finder_fixtures_package",
"rootUri": "../pkg/",
"languageVersion": "2.17"
}
]
}

View file

@ -0,0 +1,231 @@
// 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.
// If canonicalization uses deep structural hashing without memoizing, this
// will exhibit superlinear time.
// Compare with Dart version of this test at:
// https://github.com/dart-lang/sdk/blob/ca3ad264a64937d5d336cd04dbf2746d1b7d8fc4/tests/language_2/canonicalize/hashing_memoize_instance_test.dart
class Box {
const Box(this.content1, this.content2);
final Object? content1;
final Object? content2;
}
const Box box1_0 = Box(null, null);
const Box box1_1 = Box(box1_0, box1_0);
const Box box1_2 = Box(box1_1, box1_1);
const Box box1_3 = Box(box1_2, box1_2);
const Box box1_4 = Box(box1_3, box1_3);
const Box box1_5 = Box(box1_4, box1_4);
const Box box1_6 = Box(box1_5, box1_5);
const Box box1_7 = Box(box1_6, box1_6);
const Box box1_8 = Box(box1_7, box1_7);
const Box box1_9 = Box(box1_8, box1_8);
const Box box1_10 = Box(box1_9, box1_9);
const Box box1_11 = Box(box1_10, box1_10);
const Box box1_12 = Box(box1_11, box1_11);
const Box box1_13 = Box(box1_12, box1_12);
const Box box1_14 = Box(box1_13, box1_13);
const Box box1_15 = Box(box1_14, box1_14);
const Box box1_16 = Box(box1_15, box1_15);
const Box box1_17 = Box(box1_16, box1_16);
const Box box1_18 = Box(box1_17, box1_17);
const Box box1_19 = Box(box1_18, box1_18);
const Box box1_20 = Box(box1_19, box1_19);
const Box box1_21 = Box(box1_20, box1_20);
const Box box1_22 = Box(box1_21, box1_21);
const Box box1_23 = Box(box1_22, box1_22);
const Box box1_24 = Box(box1_23, box1_23);
const Box box1_25 = Box(box1_24, box1_24);
const Box box1_26 = Box(box1_25, box1_25);
const Box box1_27 = Box(box1_26, box1_26);
const Box box1_28 = Box(box1_27, box1_27);
const Box box1_29 = Box(box1_28, box1_28);
const Box box1_30 = Box(box1_29, box1_29);
const Box box1_31 = Box(box1_30, box1_30);
const Box box1_32 = Box(box1_31, box1_31);
const Box box1_33 = Box(box1_32, box1_32);
const Box box1_34 = Box(box1_33, box1_33);
const Box box1_35 = Box(box1_34, box1_34);
const Box box1_36 = Box(box1_35, box1_35);
const Box box1_37 = Box(box1_36, box1_36);
const Box box1_38 = Box(box1_37, box1_37);
const Box box1_39 = Box(box1_38, box1_38);
const Box box1_40 = Box(box1_39, box1_39);
const Box box1_41 = Box(box1_40, box1_40);
const Box box1_42 = Box(box1_41, box1_41);
const Box box1_43 = Box(box1_42, box1_42);
const Box box1_44 = Box(box1_43, box1_43);
const Box box1_45 = Box(box1_44, box1_44);
const Box box1_46 = Box(box1_45, box1_45);
const Box box1_47 = Box(box1_46, box1_46);
const Box box1_48 = Box(box1_47, box1_47);
const Box box1_49 = Box(box1_48, box1_48);
const Box box1_50 = Box(box1_49, box1_49);
const Box box1_51 = Box(box1_50, box1_50);
const Box box1_52 = Box(box1_51, box1_51);
const Box box1_53 = Box(box1_52, box1_52);
const Box box1_54 = Box(box1_53, box1_53);
const Box box1_55 = Box(box1_54, box1_54);
const Box box1_56 = Box(box1_55, box1_55);
const Box box1_57 = Box(box1_56, box1_56);
const Box box1_58 = Box(box1_57, box1_57);
const Box box1_59 = Box(box1_58, box1_58);
const Box box1_60 = Box(box1_59, box1_59);
const Box box1_61 = Box(box1_60, box1_60);
const Box box1_62 = Box(box1_61, box1_61);
const Box box1_63 = Box(box1_62, box1_62);
const Box box1_64 = Box(box1_63, box1_63);
const Box box1_65 = Box(box1_64, box1_64);
const Box box1_66 = Box(box1_65, box1_65);
const Box box1_67 = Box(box1_66, box1_66);
const Box box1_68 = Box(box1_67, box1_67);
const Box box1_69 = Box(box1_68, box1_68);
const Box box1_70 = Box(box1_69, box1_69);
const Box box1_71 = Box(box1_70, box1_70);
const Box box1_72 = Box(box1_71, box1_71);
const Box box1_73 = Box(box1_72, box1_72);
const Box box1_74 = Box(box1_73, box1_73);
const Box box1_75 = Box(box1_74, box1_74);
const Box box1_76 = Box(box1_75, box1_75);
const Box box1_77 = Box(box1_76, box1_76);
const Box box1_78 = Box(box1_77, box1_77);
const Box box1_79 = Box(box1_78, box1_78);
const Box box1_80 = Box(box1_79, box1_79);
const Box box1_81 = Box(box1_80, box1_80);
const Box box1_82 = Box(box1_81, box1_81);
const Box box1_83 = Box(box1_82, box1_82);
const Box box1_84 = Box(box1_83, box1_83);
const Box box1_85 = Box(box1_84, box1_84);
const Box box1_86 = Box(box1_85, box1_85);
const Box box1_87 = Box(box1_86, box1_86);
const Box box1_88 = Box(box1_87, box1_87);
const Box box1_89 = Box(box1_88, box1_88);
const Box box1_90 = Box(box1_89, box1_89);
const Box box1_91 = Box(box1_90, box1_90);
const Box box1_92 = Box(box1_91, box1_91);
const Box box1_93 = Box(box1_92, box1_92);
const Box box1_94 = Box(box1_93, box1_93);
const Box box1_95 = Box(box1_94, box1_94);
const Box box1_96 = Box(box1_95, box1_95);
const Box box1_97 = Box(box1_96, box1_96);
const Box box1_98 = Box(box1_97, box1_97);
const Box box1_99 = Box(box1_98, box1_98);
const Box box2_0 = Box(null, null);
const Box box2_1 = Box(box2_0, box2_0);
const Box box2_2 = Box(box2_1, box2_1);
const Box box2_3 = Box(box2_2, box2_2);
const Box box2_4 = Box(box2_3, box2_3);
const Box box2_5 = Box(box2_4, box2_4);
const Box box2_6 = Box(box2_5, box2_5);
const Box box2_7 = Box(box2_6, box2_6);
const Box box2_8 = Box(box2_7, box2_7);
const Box box2_9 = Box(box2_8, box2_8);
const Box box2_10 = Box(box2_9, box2_9);
const Box box2_11 = Box(box2_10, box2_10);
const Box box2_12 = Box(box2_11, box2_11);
const Box box2_13 = Box(box2_12, box2_12);
const Box box2_14 = Box(box2_13, box2_13);
const Box box2_15 = Box(box2_14, box2_14);
const Box box2_16 = Box(box2_15, box2_15);
const Box box2_17 = Box(box2_16, box2_16);
const Box box2_18 = Box(box2_17, box2_17);
const Box box2_19 = Box(box2_18, box2_18);
const Box box2_20 = Box(box2_19, box2_19);
const Box box2_21 = Box(box2_20, box2_20);
const Box box2_22 = Box(box2_21, box2_21);
const Box box2_23 = Box(box2_22, box2_22);
const Box box2_24 = Box(box2_23, box2_23);
const Box box2_25 = Box(box2_24, box2_24);
const Box box2_26 = Box(box2_25, box2_25);
const Box box2_27 = Box(box2_26, box2_26);
const Box box2_28 = Box(box2_27, box2_27);
const Box box2_29 = Box(box2_28, box2_28);
const Box box2_30 = Box(box2_29, box2_29);
const Box box2_31 = Box(box2_30, box2_30);
const Box box2_32 = Box(box2_31, box2_31);
const Box box2_33 = Box(box2_32, box2_32);
const Box box2_34 = Box(box2_33, box2_33);
const Box box2_35 = Box(box2_34, box2_34);
const Box box2_36 = Box(box2_35, box2_35);
const Box box2_37 = Box(box2_36, box2_36);
const Box box2_38 = Box(box2_37, box2_37);
const Box box2_39 = Box(box2_38, box2_38);
const Box box2_40 = Box(box2_39, box2_39);
const Box box2_41 = Box(box2_40, box2_40);
const Box box2_42 = Box(box2_41, box2_41);
const Box box2_43 = Box(box2_42, box2_42);
const Box box2_44 = Box(box2_43, box2_43);
const Box box2_45 = Box(box2_44, box2_44);
const Box box2_46 = Box(box2_45, box2_45);
const Box box2_47 = Box(box2_46, box2_46);
const Box box2_48 = Box(box2_47, box2_47);
const Box box2_49 = Box(box2_48, box2_48);
const Box box2_50 = Box(box2_49, box2_49);
const Box box2_51 = Box(box2_50, box2_50);
const Box box2_52 = Box(box2_51, box2_51);
const Box box2_53 = Box(box2_52, box2_52);
const Box box2_54 = Box(box2_53, box2_53);
const Box box2_55 = Box(box2_54, box2_54);
const Box box2_56 = Box(box2_55, box2_55);
const Box box2_57 = Box(box2_56, box2_56);
const Box box2_58 = Box(box2_57, box2_57);
const Box box2_59 = Box(box2_58, box2_58);
const Box box2_60 = Box(box2_59, box2_59);
const Box box2_61 = Box(box2_60, box2_60);
const Box box2_62 = Box(box2_61, box2_61);
const Box box2_63 = Box(box2_62, box2_62);
const Box box2_64 = Box(box2_63, box2_63);
const Box box2_65 = Box(box2_64, box2_64);
const Box box2_66 = Box(box2_65, box2_65);
const Box box2_67 = Box(box2_66, box2_66);
const Box box2_68 = Box(box2_67, box2_67);
const Box box2_69 = Box(box2_68, box2_68);
const Box box2_70 = Box(box2_69, box2_69);
const Box box2_71 = Box(box2_70, box2_70);
const Box box2_72 = Box(box2_71, box2_71);
const Box box2_73 = Box(box2_72, box2_72);
const Box box2_74 = Box(box2_73, box2_73);
const Box box2_75 = Box(box2_74, box2_74);
const Box box2_76 = Box(box2_75, box2_75);
const Box box2_77 = Box(box2_76, box2_76);
const Box box2_78 = Box(box2_77, box2_77);
const Box box2_79 = Box(box2_78, box2_78);
const Box box2_80 = Box(box2_79, box2_79);
const Box box2_81 = Box(box2_80, box2_80);
const Box box2_82 = Box(box2_81, box2_81);
const Box box2_83 = Box(box2_82, box2_82);
const Box box2_84 = Box(box2_83, box2_83);
const Box box2_85 = Box(box2_84, box2_84);
const Box box2_86 = Box(box2_85, box2_85);
const Box box2_87 = Box(box2_86, box2_86);
const Box box2_88 = Box(box2_87, box2_87);
const Box box2_89 = Box(box2_88, box2_88);
const Box box2_90 = Box(box2_89, box2_89);
const Box box2_91 = Box(box2_90, box2_90);
const Box box2_92 = Box(box2_91, box2_91);
const Box box2_93 = Box(box2_92, box2_92);
const Box box2_94 = Box(box2_93, box2_93);
const Box box2_95 = Box(box2_94, box2_94);
const Box box2_96 = Box(box2_95, box2_95);
const Box box2_97 = Box(box2_96, box2_96);
const Box box2_98 = Box(box2_97, box2_97);
const Box box2_99 = Box(box2_98, box2_98);
Object confuse(Box x) {
try {
throw x;
} catch (e) {
return e;
} // ignore: only_throw_errors
}
void main() {
if (!identical(confuse(box1_99), confuse(box2_99))) {
throw Exception('box1_99 !== box2_99');
}
}

View file

@ -0,0 +1,78 @@
// 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:core';
import 'package:const_finder_fixtures_package/package.dart';
import 'target.dart';
void main() {
const Target target1 = Target('1', 1, null);
const Target target2 = Target('2', 2, Target('4', 4, null));
const Target target3 =
Target('3', 3, Target('5', 5, null)); // should be tree shaken out.
target1.hit();
target2.hit();
blah(const Target('6', 6, null));
const IgnoreMe ignoreMe =
IgnoreMe(Target('7', 7, null)); // IgnoreMe is ignored but 7 is not.
final IgnoreMe ignoreMe2 = IgnoreMe(const Target('8', 8, null));
final IgnoreMe ignoreMe3 =
IgnoreMe(const Target('9', 9, Target('10', 10, null)));
print(ignoreMe);
print(ignoreMe2);
print(ignoreMe3);
createTargetInPackage();
final StaticConstInitializer staticConstMap = StaticConstInitializer();
staticConstMap.useOne(1);
const ExtendsTarget extendsTarget = ExtendsTarget('11', 11, null);
extendsTarget.hit();
const ImplementsTarget implementsTarget = ImplementsTarget('12', 12, null);
implementsTarget.hit();
const MixedInTarget mixedInTraget = MixedInTarget('13');
mixedInTraget.hit();
}
class IgnoreMe {
const IgnoreMe(this.target);
final Target target;
@override
String toString() => target.toString();
}
class StaticConstInitializer {
static const List<Target> targets = <Target>[
Target('100', 100, null),
Target('101', 101, Target('102', 102, null)),
];
static const Set<Target> targetSet = <Target>{
Target('103', 103, null),
Target('104', 104, Target('105', 105, null)),
};
static const Map<int, Target> targetMap = <int, Target>{
0: Target('106', 106, null),
1: Target('107', 107, Target('108', 108, null)),
};
void useOne(int index) {
targets[index].hit();
targetSet.skip(index).first.hit();
targetMap[index]!.hit();
}
}
void blah(Target target) {
print(target);
}

View file

@ -0,0 +1,46 @@
// 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:core';
import 'package:const_finder_fixtures_package/package.dart';
import 'target.dart';
void main() {
const Target target1 = Target('1', 1, null);
final Target target2 = Target('2', 2, const Target('4', 4, null));
final Target target3 =
Target('3', 3, Target('5', 5, null)); // should be tree shaken out.
final Target target6 = Target('6', 6, null); // should be tree shaken out.
target1.hit();
target2.hit();
blah(const Target('6', 6, null));
const IgnoreMe ignoreMe =
IgnoreMe(Target('7', 7, null)); // IgnoreMe is ignored but 7 is not.
final IgnoreMe ignoreMe2 = IgnoreMe(const Target('8', 8, null));
final IgnoreMe ignoreMe3 =
IgnoreMe(const Target('9', 9, Target('10', 10, null)));
print(ignoreMe);
print(ignoreMe2);
print(ignoreMe3);
createNonConstTargetInPackage();
}
class IgnoreMe {
const IgnoreMe(this.target);
final Target target;
@override
String toString() => target.toString();
}
void blah(Target target) {
print(target);
}

View file

@ -0,0 +1,31 @@
// 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 'target.dart';
void main() {
Targets.used1.hit();
Targets.used2.hit();
final Target nonConstUsed3 = helper(Target.new);
nonConstUsed3.hit();
}
Target helper(Target Function(String, int, Target?) tearOff) {
return tearOff('from tear-off', 3, null);
}
@staticIconProvider
class Targets {
static const Target used1 = Target('used1', 1, null);
static const Target used2 = Target('used2', 2, null);
static const Target unused1 = Target('unused1', 1, null);
}
// const_finder explicitly does not retain constants appearing within a class
// with this annotation.
class StaticIconProvider {
const StaticIconProvider();
}
const StaticIconProvider staticIconProvider = StaticIconProvider();

View file

@ -0,0 +1,50 @@
// 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.
class Target {
const Target(this.stringValue, this.intValue, this.targetValue);
final String stringValue;
final int intValue;
final Target? targetValue;
void hit() {
print('$stringValue $intValue');
}
}
class ExtendsTarget extends Target {
const ExtendsTarget(super.stringValue, super.intValue, super.targetValue);
}
class ImplementsTarget implements Target {
const ImplementsTarget(this.stringValue, this.intValue, this.targetValue);
@override
final String stringValue;
@override
final int intValue;
@override
final Target? targetValue;
@override
void hit() {
print('ImplementsTarget - $stringValue $intValue');
}
}
mixin MixableTarget {
String get val;
void hit() {
print(val);
}
}
class MixedInTarget with MixableTarget {
const MixedInTarget(this.val);
@override
final String val;
}

View file

@ -0,0 +1,15 @@
// 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 'package:const_finder_fixtures/target.dart';
void createTargetInPackage() {
const Target target = Target('package', -1, null);
target.hit();
}
void createNonConstTargetInPackage() {
final Target target = Target('package_non', -2, null);
target.hit();
}

View file

@ -0,0 +1,215 @@
// 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<void> {
_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,
};
}
}

View file

@ -46,6 +46,10 @@ void main(List<String> args) {
platform('pkg/_fe_analyzer_shared/test/inheritance'),
];
var frontendServerPackageDirs = [
platform('pkg/frontend_server/test/fixtures'),
];
var pkgVmPackageDirs = [
platform('pkg/vm/testcases'),
];
@ -72,6 +76,7 @@ void main(List<String> args) {
...makePackageConfigs(packageDirs),
...makeCfePackageConfigs(cfePackageDirs),
...makeFeAnalyzerSharedPackageConfigs(feAnalyzerSharedPackageDirs),
...makeFrontendServerPackageConfigs(frontendServerPackageDirs),
...makePkgVmPackageConfigs(pkgVmPackageDirs),
];
packages.sort((a, b) => a.name.compareTo(b.name));
@ -175,6 +180,11 @@ Iterable<Package> makeFeAnalyzerSharedPackageConfigs(
List<String> packageDirs) =>
makeSpecialPackageConfigs('_fe_analyzer_shared', packageDirs);
/// Generates package configurations for the special pseudo-packages used by the
/// frontend_server tests.
Iterable<Package> makeFrontendServerPackageConfigs(List<String> packageDirs) =>
makeSpecialPackageConfigs('frontend_server', packageDirs);
/// Generates package configurations for the special pseudo-packages used by the
/// pkg/vm unit tests (`pkg/vm/test`).
Iterable<Package> makePkgVmPackageConfigs(List<String> packageDirs) =>