blob: e6e35caebdb2b0963b6714c5034a41b364f30262 [file] [log] [blame]
#!/usr/bin/env dart
// 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 'dart:io';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart';
import 'package:kernel/src/tool/command_line_util.dart';
void usage() {
print("Compares the hierarchies of two dill files.");
print("");
print("Usage: dart <script> dillFile1.dill dillFile2.dill "
"[import:uri#classToIgnore|another:uri#andClassToIgnore]");
exit(1);
}
void main(List<String> args) {
CommandLineHelper.requireVariableArgumentCount([2, 3], args, usage);
CommandLineHelper.requireFileExists(args[0]);
CommandLineHelper.requireFileExists(args[1]);
Component binary1 = CommandLineHelper.tryLoadDill(args[0]);
Component binary2 = CommandLineHelper.tryLoadDill(args[1]);
Map<Uri, Set<String>> ignoresMap = {};
if (args.length >= 3) {
List<String> ignores = args[2].split("|");
for (String ignore in ignores) {
List<String> uriClassName = ignore.split("#");
if (uriClassName.length != 2) {
print("Ignoring '$ignore' as it doesn't conform to "
"'importUri#className'");
continue;
}
Uri uri = Uri.parse(uriClassName[0]);
String className = uriClassName[1];
(ignoresMap[uri] ??= {}).add(className);
}
}
print("(1): ${args[0]}");
print("(2): ${args[1]}");
print("");
ClosedWorldClassHierarchy ch1 =
new ClassHierarchy(binary1, new CoreTypes(binary1))
as ClosedWorldClassHierarchy;
ClosedWorldClassHierarchy ch2 =
new ClassHierarchy(binary2, new CoreTypes(binary2))
as ClosedWorldClassHierarchy;
Map<Uri, Library> libMap1 = createLibMap(binary1);
Map<Uri, Library> libMap2 = createLibMap(binary2);
Set<Uri> agreeingImportUris = new Set<Uri>.from(libMap1.keys)
..retainAll(libMap2.keys);
for (Uri uri in agreeingImportUris) {
Library lib1 = libMap1[uri]!;
Library lib2 = libMap2[uri]!;
Map<String, Class> libClass1 =
createPublicClassMap(lib1, ignored: ignoresMap[uri]);
Map<String, Class> libClass2 =
createPublicClassMap(lib2, ignored: ignoresMap[uri]);
Set<String> agreeingClasses = new Set<String>.from(libClass1.keys)
..retainAll(libClass2.keys);
if (agreeingClasses.length != libClass1.length ||
libClass1.length != libClass2.length) {
print("Missing classes in lib $uri");
Set<String> missing = new Set<String>.from(libClass1.keys)
..removeAll(libClass2.keys);
if (missing.isNotEmpty) {
print("In (1) but not in (2): ${missing.toList()}");
}
missing = new Set<String>.from(libClass2.keys)..removeAll(libClass1.keys);
if (missing.isNotEmpty) {
print("In (2) but not in (1): ${missing.toList()}");
}
print("");
}
for (String className in agreeingClasses) {
Class c1 = libClass1[className]!;
Class c2 = libClass2[className]!;
Set<ClassReference> c1Supertypes = createClassReferenceSet(
ch1.getAllSupertypeClassesForTesting(c1),
onlyPublic: true,
ignoresMap: ignoresMap);
Set<ClassReference> c2Supertypes = createClassReferenceSet(
ch2.getAllSupertypeClassesForTesting(c2),
onlyPublic: true,
ignoresMap: ignoresMap);
Set<ClassReference> missing = new Set<ClassReference>.from(c1Supertypes)
..removeAll(c2Supertypes);
if (missing.isNotEmpty) {
print("$c1 in $lib1 from (1) has these extra supertypes: "
"${missing.toList()}");
}
missing = new Set<ClassReference>.from(c2Supertypes)
..removeAll(c1Supertypes);
if (missing.isNotEmpty) {
print("$c2 in $lib2 from (2) has these extra supertypes: "
"${missing.toList()}");
}
}
}
}
Map<Uri, Library> createLibMap(Component c) {
Map<Uri, Library> map = {};
for (Library lib in c.libraries) {
map[lib.importUri] = lib;
}
return map;
}
Map<String, Class> createPublicClassMap(Library lib,
{required Set<String>? ignored}) {
Map<String, Class> map = {};
for (Class c in lib.classes) {
if (c.name.startsWith("_")) continue;
if (ignored?.contains(c.name) ?? false) continue;
map[c.name] = c;
}
return map;
}
Set<ClassReference> createClassReferenceSet(List<Class> classes,
{required bool onlyPublic, required Map<Uri, Set<String>> ignoresMap}) {
Set<ClassReference> result = {};
for (Class c in classes) {
if (onlyPublic && c.name.startsWith("_")) continue;
Set<String>? ignored = ignoresMap[c.enclosingLibrary.importUri];
if (ignored?.contains(c.name) ?? false) continue;
result.add(new ClassReference(c.name, c.enclosingLibrary.importUri));
}
return result;
}
class ClassReference {
final String name;
final Uri libImportUri;
const ClassReference(this.name, this.libImportUri);
@override
int get hashCode => name.hashCode * 13 + libImportUri.hashCode * 17;
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other is! ClassReference) return false;
if (name != other.name) return false;
if (libImportUri != other.libImportUri) return false;
return true;
}
@override
String toString() {
return "$name ($libImportUri)";
}
}