Merge commit 'd5ec9706606f21779334191e135fb3613c1aba80' into dev-release
diff --git a/.gitignore b/.gitignore
index 2b1d423..5885615 100644
--- a/.gitignore
+++ b/.gitignore
@@ -143,6 +143,8 @@
third_party/rhino-android-1.1.1.tar.gz
third_party/sample_libraries
third_party/sample_libraries.tar.gz
+third_party/tachiyomi
+third_party/tachiyomi.tar.gz
third_party/youtube/*
third_party/youtube-developer/20200415
third_party/youtube-developer/20200415.tar.gz
diff --git a/build.gradle b/build.gradle
index 5c95cd6..12d14c9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -333,7 +333,8 @@
"proguard/proguard5.2.1",
"proguard/proguard6.0.1",
"r8",
- "r8mappings"
+ "r8mappings",
+ "tachiyomi"
],
// All dex-vms have a fixed OS of Linux, as they are only supported on Linux, and will be run in a Docker
// container on other platforms where supported.
@@ -884,25 +885,24 @@
include "dalvik/**"
}
-def generateR8LibKeepRules(name, r8Source, testSource, output) {
- return tasks.create("generateR8LibKeepRules_" + name, Exec) {
- doFirst {
- standardOutput new FileOutputStream(output)
- }
- dependsOn r8WithRelocatedDeps
- dependsOn r8Source
- dependsOn testSource
- dependsOn downloadOpenJDKrt
- inputs.files ([r8WithRelocatedDeps.outputs, r8Source.outputs, testSource.outputs])
- outputs.file output
- commandLine baseR8CommandLine([
- "printuses",
- "--keeprules-allowobfuscation",
- "third_party/openjdk/openjdk-rt-1.8/rt.jar",
- r8Source.outputs.files[0],
- testSource.outputs.files[0]])
- workingDir = projectDir
+task generateR8LibKeepRules(type: Exec) {
+ doFirst {
+ // TODO(b/154785341): We should remove this.
+ standardOutput new FileOutputStream(r8LibGeneratedKeepRulesPath)
}
+ dependsOn R8NoManifest
+ dependsOn r8WithRelocatedDeps
+ dependsOn testJar
+ dependsOn downloadOpenJDKrt
+ inputs.files ([r8WithRelocatedDeps.outputs, R8NoManifest.outputs, testJar.outputs])
+ outputs.file r8LibGeneratedKeepRulesPath
+ commandLine baseR8CommandLine([
+ "printuses",
+ "--keeprules-allowobfuscation",
+ "third_party/openjdk/openjdk-rt-1.8/rt.jar",
+ R8NoManifest.outputs.files[0],
+ testJar.outputs.files[0]])
+ workingDir = projectDir
}
task R8LibApiOnly {
@@ -911,35 +911,26 @@
}
task R8Lib {
- def genRulesTask = generateR8LibKeepRules(
- "Main",
- R8NoManifest,
- testJar,
- r8LibGeneratedKeepRulesPath)
dependsOn r8LibCreateTask(
"Main",
- ["src/main/keep.txt", "src/main/keep-applymapping.txt", genRulesTask.outputs.files[0]],
+ ["src/main/keep.txt",
+ "src/main/keep-applymapping.txt",
+ generateR8LibKeepRules.outputs.files[0]],
R8NoManifest,
r8LibPath,
- ).dependsOn(genRulesTask)
+ ).dependsOn(generateR8LibKeepRules)
outputs.file r8LibPath
}
task R8LibNoDeps {
- def genRulesTask = generateR8LibKeepRules(
- "NoDeps",
- R8NoManifestNoDeps,
- testJar,
- r8LibGeneratedKeepRulesExcludeDepsPath
- )
dependsOn r8LibCreateTask(
"NoDeps",
- ["src/main/keep.txt", "src/main/keep-applymapping.txt", genRulesTask.outputs.files[0]],
+ ["src/main/keep.txt", "src/main/keep-applymapping.txt"],
R8NoManifestNoDeps,
r8LibExludeDepsPath,
"--release",
repackageDepsNoRelocate.outputs.files
- ).dependsOn(repackageDepsNoRelocate, genRulesTask)
+ ).dependsOn(repackageDepsNoRelocate)
outputs.file r8LibExludeDepsPath
}
@@ -1730,48 +1721,26 @@
task getJarsFromSupportLibs(type: GetJarsFromConfiguration) {
setConfiguration(configurations.supportLibs)
}
-def getR8LibSourceTask() {
- if (project.hasProperty('r8lib')) {
- return R8NoManifest
- } else if (project.hasProperty('r8lib_no_deps')) {
- return R8NoManifestNoDeps
- }
- return null
-}
-
-def getR8LibTask() {
- if (project.hasProperty('r8lib')) {
- return R8Lib
- } else if (project.hasProperty('r8lib_no_deps')) {
- return R8LibNoDeps
- }
- return null
-}
task generateR8TestKeepRules {
def path = "build/generated/r8tests-keep.txt"
outputs.file path
- if (getR8LibTask() != null) {
- dependsOn getR8LibTask()
- doLast {
- file(path).write """-keep class ** { *; }
+ dependsOn R8Lib
+ doLast {
+ file(path).write """-keep class ** { *; }
-dontshrink
-dontoptimize
-keepattributes *
--applymapping ${getR8LibTask().outputs.files[0]}.map
+-applymapping ${R8Lib.outputs.files[0]}.map
"""
- }
}
}
task buildR8LibCfTestDeps(type: Exec) {
- if (getR8LibTask() == null) {
- return
- }
def outputPath = "build/libs/r8libtestdeps-cf.jar"
dependsOn downloadDeps
- dependsOn getR8LibTask()
- dependsOn getR8LibSourceTask()
+ dependsOn R8NoManifest
+ dependsOn R8Lib
dependsOn generateR8TestKeepRules
dependsOn testJar
// Take all .jar files as libraries and append the generated test classes in classes/java/test.
@@ -1780,13 +1749,13 @@
} + ["${buildDir}/classes/java/test"]
inputs.files testJar.outputs.files +
generateR8TestKeepRules.outputs.files +
- getR8LibTask().outputs
+ R8Lib.outputs
commandLine = r8CfCommandLine(
testJar.outputs.files[0],
outputPath,
[generateR8TestKeepRules.outputs.files[0]],
- ["--debug", "--classpath", getR8LibSourceTask().outputs.files[0]],
- getR8LibSourceTask().outputs.files + addedLibraries)
+ ["--debug", "--classpath", R8NoManifest.outputs.files[0]],
+ R8NoManifest.outputs.files + addedLibraries)
workingDir = projectDir
outputs.file outputPath
}
@@ -1794,17 +1763,15 @@
task configureTestForR8Lib(type: Copy) {
dependsOn testJar
inputs.files buildR8LibCfTestDeps.outputs
- if (getR8LibTask() != null) {
- dependsOn getR8LibTask()
- delete r8LibTestPath
- from zipTree(buildR8LibCfTestDeps.outputs.files[0])
- def examplesDir = file("build/test")
- examplesDir.eachDir { dir ->
- from ("${buildDir}/test/${dir.getName()}/classes")
- }
- from ("${buildDir}/runtime/examples")
- into r8LibTestPath
+ dependsOn R8Lib
+ delete r8LibTestPath
+ from zipTree(buildR8LibCfTestDeps.outputs.files[0])
+ def examplesDir = file("build/test")
+ examplesDir.eachDir { dir ->
+ from ("${buildDir}/test/${dir.getName()}/classes")
}
+ from ("${buildDir}/runtime/examples")
+ into r8LibTestPath
outputs.dir r8LibTestPath
}
@@ -1952,10 +1919,10 @@
if (project.hasProperty('test_dir')) {
systemProperty 'test_dir', project.property('test_dir')
}
- if (project.hasProperty('r8lib') || project.hasProperty('r8lib_no_deps')) {
+ if (project.hasProperty('r8lib')) {
dependsOn configureTestForR8Lib
- // R8lib should be used instead of the main output and all the tests in r8 should be mapped
- // and exists in r8LibtestPath.
+ // R8lib should be used instead of the main output and all the tests in
+ // r8 should be mapped and exists in r8LibTestPath.
classpath = sourceSets.test.runtimeClasspath.filter {
!it.getAbsolutePath().contains("/build/")
}
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index e9df325..aad3468 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -175,7 +175,7 @@
boolean hasDefinedApiLevel = false;
OrderedClassFileResourceProvider.Builder classpathBuilder =
OrderedClassFileResourceProvider.builder();
- String[] expandedArgs = FlagFile.expandFlagFiles(args, builder);
+ String[] expandedArgs = FlagFile.expandFlagFiles(args, builder::error);
for (int i = 0; i < expandedArgs.length; i++) {
String arg = expandedArgs[i].trim();
String nextArg = null;
diff --git a/src/main/java/com/android/tools/r8/L8CommandParser.java b/src/main/java/com/android/tools/r8/L8CommandParser.java
index 976fb4a..90db2c6 100644
--- a/src/main/java/com/android/tools/r8/L8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/L8CommandParser.java
@@ -92,7 +92,7 @@
boolean hasDefinedApiLevel = false;
OrderedClassFileResourceProvider.Builder classpathBuilder =
OrderedClassFileResourceProvider.builder();
- String[] expandedArgs = FlagFile.expandFlagFiles(args, builder);
+ String[] expandedArgs = FlagFile.expandFlagFiles(args, builder::error);
for (int i = 0; i < expandedArgs.length; i++) {
String arg = expandedArgs[i].trim();
String nextArg = null;
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 17c6a0a..61c4930 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -28,6 +28,7 @@
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
import com.android.tools.r8.inspector.internal.InspectorImpl;
@@ -292,8 +293,10 @@
List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
timing.begin("Strip unused code");
Set<DexType> classesToRetainInnerClassAttributeFor = null;
- Set<DexType> missingClasses = appView.appInfo().getMissingClasses();
+ Set<DexType> missingClasses = null;
try {
+ // TODO(b/154849103): Find a better way to determine missing classes.
+ missingClasses = new SubtypingInfo(application.allClasses(), appView).getMissingClasses();
missingClasses = filterMissingClasses(
missingClasses, options.getProguardConfiguration().getDontWarnPatterns());
if (!missingClasses.isEmpty()) {
@@ -385,7 +388,9 @@
removedClasses,
pruner.getMethodsToKeepForConfigurationDebugging()));
appView.setAppServices(appView.appServices().prunedCopy(removedClasses));
- new AbstractMethodRemover(appView.appInfo().withLiveness()).run();
+ new AbstractMethodRemover(
+ appViewWithLiveness, appViewWithLiveness.appInfo().computeSubtypingInfo())
+ .run();
AnnotationRemover annotationRemover =
annotationRemoverBuilder
@@ -426,9 +431,11 @@
appView.dexItemFactory().clearTypeElementsCache();
if (options.getProguardConfiguration().isAccessModificationAllowed()) {
+ AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+ SubtypingInfo subtypingInfo = appViewWithLiveness.appInfo().computeSubtypingInfo();
GraphLense publicizedLense =
ClassAndMemberPublicizer.run(
- executorService, timing, application, appView.withLiveness());
+ executorService, timing, application, appViewWithLiveness, subtypingInfo);
boolean changed = appView.setGraphLense(publicizedLense);
if (changed) {
// We can now remove visibility bridges. Note that we do not need to update the
@@ -494,11 +501,13 @@
timing.end();
}
if (options.enableArgumentRemoval) {
+ SubtypingInfo subtypingInfo = appViewWithLiveness.appInfo().computeSubtypingInfo();
if (options.enableUnusedArgumentRemoval) {
timing.begin("UnusedArgumentRemoval");
UnusedArgumentsGraphLense lens =
new UnusedArgumentsCollector(
- appViewWithLiveness, new MethodPoolCollection(appViewWithLiveness))
+ appViewWithLiveness,
+ new MethodPoolCollection(appViewWithLiveness, subtypingInfo))
.run(executorService, timing);
if (lens != null) {
boolean changed = appView.setGraphLense(lens);
@@ -513,7 +522,10 @@
timing.begin("UninstantiatedTypeOptimization");
UninstantiatedTypeOptimizationGraphLense lens =
new UninstantiatedTypeOptimization(appViewWithLiveness)
- .run(new MethodPoolCollection(appViewWithLiveness), executorService, timing);
+ .run(
+ new MethodPoolCollection(appViewWithLiveness, subtypingInfo),
+ executorService,
+ timing);
if (lens != null) {
boolean changed = appView.setGraphLense(lens);
assert changed;
@@ -730,16 +742,12 @@
// If proto shrinking is enabled, we need to reprocess every dynamicMethod(). This ensures
// that proto fields that have been removed by the second round of tree shaking are also
// removed from the proto schemas in the bytecode.
- // TODO(b/112437944): Avoid iterating the entire application to post-process every
- // dynamicMethod() method.
appView.withGeneratedMessageLiteShrinker(
shrinker -> shrinker.postOptimizeDynamicMethods(converter, executorService, timing));
// If proto shrinking is enabled, we need to post-process every
// findLiteExtensionByNumber() method. This ensures that there are no references to dead
// extensions that have been removed by the second round of tree shaking.
- // TODO(b/112437944): Avoid iterating the entire application to post-process every
- // findLiteExtensionByNumber() method.
appView.withGeneratedExtensionRegistryShrinker(
shrinker ->
shrinker.postOptimizeGeneratedExtensionRegistry(
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index 84aa83e..f04f937 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -130,7 +130,7 @@
private void parse(
String[] args, Origin argsOrigin, R8Command.Builder builder, ParseState state) {
- String[] expandedArgs = FlagFile.expandFlagFiles(args, builder);
+ String[] expandedArgs = FlagFile.expandFlagFiles(args, builder::error);
for (int i = 0; i < expandedArgs.length; i++) {
String arg = expandedArgs[i].trim();
String nextArg = null;
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 75ee97e..794745f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -3,148 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
-import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
-
-import com.android.tools.r8.ir.desugar.LambdaDescriptor;
-import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.WorkList;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
-import java.util.ArrayDeque;
import java.util.Collection;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentSkipListSet;
-import java.util.function.Consumer;
-import java.util.function.Function;
-public class AppInfoWithSubtyping extends AppInfoWithClassHierarchy
- implements InstantiatedSubTypeInfo {
-
- private static final int ROOT_LEVEL = 0;
- private static final int UNKNOWN_LEVEL = -1;
- private static final int INTERFACE_LEVEL = -2;
- // Since most Java types has no sub-types, we can just share an empty immutable set until we need
- // to add to it.
- private static final Set<DexType> NO_DIRECT_SUBTYPE = ImmutableSet.of();
-
- @Override
- public void forEachInstantiatedSubType(
- DexType type,
- Consumer<DexProgramClass> subTypeConsumer,
- Consumer<LambdaDescriptor> lambdaConsumer) {
- WorkList<DexType> workList = WorkList.newIdentityWorkList();
- workList.addIfNotSeen(type);
- while (workList.hasNext()) {
- DexType subType = workList.next();
- DexProgramClass clazz = definitionForProgramType(subType);
- if (clazz != null) {
- subTypeConsumer.accept(clazz);
- }
- workList.addIfNotSeen(allImmediateSubtypes(subType));
- }
- // TODO(b/148769279): Change this when we have information about callsites.
- // This should effectively disappear once AppInfoWithLiveness implements support.
- }
-
- private static class TypeInfo {
-
- private final DexType type;
-
- int hierarchyLevel = UNKNOWN_LEVEL;
- /**
- * Set of direct subtypes. This set has to remain sorted to ensure determinism. The actual
- * sorting is not important but {@link DexType#slowCompareTo(DexType)} works well.
- */
- Set<DexType> directSubtypes = NO_DIRECT_SUBTYPE;
-
- // Caching what interfaces this type is implementing. This includes super-interface hierarchy.
- Set<DexType> implementedInterfaces = null;
-
- TypeInfo(DexType type) {
- this.type = type;
- }
-
- @Override
- public String toString() {
- return "TypeInfo{" + type + ", level:" + hierarchyLevel + "}";
- }
-
- private void ensureDirectSubTypeSet() {
- if (directSubtypes == NO_DIRECT_SUBTYPE) {
- directSubtypes = new ConcurrentSkipListSet<>(DexType::slowCompareTo);
- }
- }
-
- private void setLevel(int level) {
- if (level == hierarchyLevel) {
- return;
- }
- if (hierarchyLevel == INTERFACE_LEVEL) {
- assert level == ROOT_LEVEL + 1;
- } else if (level == INTERFACE_LEVEL) {
- assert hierarchyLevel == ROOT_LEVEL + 1 || hierarchyLevel == UNKNOWN_LEVEL;
- hierarchyLevel = INTERFACE_LEVEL;
- } else {
- assert hierarchyLevel == UNKNOWN_LEVEL;
- hierarchyLevel = level;
- }
- }
-
- synchronized void addDirectSubtype(TypeInfo subtypeInfo) {
- assert hierarchyLevel != UNKNOWN_LEVEL;
- ensureDirectSubTypeSet();
- directSubtypes.add(subtypeInfo.type);
- subtypeInfo.setLevel(hierarchyLevel + 1);
- }
-
- void tagAsSubtypeRoot() {
- setLevel(ROOT_LEVEL);
- }
-
- void tagAsInterface() {
- setLevel(INTERFACE_LEVEL);
- }
-
- public boolean isInterface() {
- assert hierarchyLevel != UNKNOWN_LEVEL : "Program class missing: " + this;
- assert type.isClassType();
- return hierarchyLevel == INTERFACE_LEVEL;
- }
-
- public boolean isUnknown() {
- return hierarchyLevel == UNKNOWN_LEVEL;
- }
-
- synchronized void addInterfaceSubtype(DexType type) {
- // Interfaces all inherit from java.lang.Object. However, we assign a special level to
- // identify them later on.
- setLevel(INTERFACE_LEVEL);
- ensureDirectSubTypeSet();
- directSubtypes.add(type);
- }
- }
-
- // Set of missing classes, discovered during subtypeMap computation.
- private final Set<DexType> missingClasses = Sets.newIdentityHashSet();
-
- // Map from types to their subtypes.
- private final Map<DexType, ImmutableSet<DexType>> subtypeMap = new IdentityHashMap<>();
-
- // Map from synthesized classes to their supertypes.
- private final Map<DexType, ImmutableSet<DexType>> supertypesForSynthesizedClasses =
- new ConcurrentHashMap<>();
-
- // Map from types to their subtyping information.
- private final Map<DexType, TypeInfo> typeInfo;
+public class AppInfoWithSubtyping extends AppInfoWithClassHierarchy {
public AppInfoWithSubtyping(DirectMappedDexApplication application) {
this(application, application.allClasses());
@@ -153,75 +14,13 @@
public AppInfoWithSubtyping(
DirectMappedDexApplication application, Collection<DexClass> classes) {
super(application);
- typeInfo = new ConcurrentHashMap<>();
- // Recompute subtype map if we have modified the graph.
- populateSubtypeMap(classes, application::definitionFor, application.dexItemFactory);
}
protected AppInfoWithSubtyping(AppInfoWithSubtyping previous) {
super(previous);
- missingClasses.addAll(previous.missingClasses);
- subtypeMap.putAll(previous.subtypeMap);
- supertypesForSynthesizedClasses.putAll(previous.supertypesForSynthesizedClasses);
- typeInfo = new ConcurrentHashMap<>(previous.typeInfo);
assert app() instanceof DirectMappedDexApplication;
}
- @Override
- public void addSynthesizedClass(DexProgramClass synthesizedClass) {
- super.addSynthesizedClass(synthesizedClass);
- // Register synthesized type, which has two side effects:
- // 1) Set the hierarchy level of synthesized type based on that of its super type,
- // 2) Register the synthesized type as a subtype of the supertype.
- //
- // The first one makes method resolutions on that synthesized class free from assertion errors
- // about unknown hierarchy level.
- //
- // For the second one, note that such addition is synchronized, but the retrieval of direct
- // subtypes isn't. Thus, there is a chance of race conditions: utils that check/iterate direct
- // subtypes, e.g., allImmediateSubtypes, hasSubTypes, etc., may not be able to see this new
- // synthesized class. However, in practice, this would be okay because, in most cases,
- // synthesized class's super type is Object, which in general has other subtypes in any way.
- // Also, iterating all subtypes of Object usually happens before/after IR processing, i.e., as
- // part of structural changes, such as bottom-up traversal to collect all method signatures,
- // which are free from such race conditions. Another exceptional case is synthesized classes
- // whose synthesis is isolated from IR processing. For example, lambda group class that merges
- // lambdas with the same interface are synthesized/finalized even after post processing of IRs.
- assert synthesizedClass.superType == dexItemFactory().objectType
- || synthesizedClass.type.toString().contains(LAMBDA_GROUP_CLASS_NAME_PREFIX)
- : "Make sure retrieval and iteration of sub types of `" + synthesizedClass.superType
- + "` is guaranteed to be thread safe and able to see `" + synthesizedClass + "`";
- registerNewType(synthesizedClass.type, synthesizedClass.superType);
-
- // TODO(b/129458850): Remove when we no longer synthesize classes on-the-fly.
- Set<DexType> visited = SetUtils.newIdentityHashSet(synthesizedClass.allImmediateSupertypes());
- Deque<DexType> worklist = new ArrayDeque<>(visited);
- while (!worklist.isEmpty()) {
- DexType type = worklist.removeFirst();
- assert visited.contains(type);
-
- DexClass clazz = definitionFor(type);
- if (clazz == null) {
- continue;
- }
-
- for (DexType supertype : clazz.allImmediateSupertypes()) {
- if (visited.add(supertype)) {
- worklist.addLast(supertype);
- }
- }
- }
- if (!visited.isEmpty()) {
- supertypesForSynthesizedClasses.put(synthesizedClass.type, ImmutableSet.copyOf(visited));
- }
- }
-
- private boolean isSynthesizedClassStrictSubtypeOf(DexType synthesizedClass, DexType supertype) {
- Set<DexType> supertypesOfSynthesizedClass =
- supertypesForSynthesizedClasses.get(synthesizedClass);
- return supertypesOfSynthesizedClass != null && supertypesOfSynthesizedClass.contains(supertype);
- }
-
private DirectMappedDexApplication getDirectApplication() {
// TODO(herhut): Remove need for cast.
return (DirectMappedDexApplication) app();
@@ -232,136 +31,6 @@
return getDirectApplication().libraryClasses();
}
- public Set<DexType> getMissingClasses() {
- assert checkIfObsolete();
- return Collections.unmodifiableSet(missingClasses);
- }
-
- public Set<DexType> subtypes(DexType type) {
- assert checkIfObsolete();
- assert type.isClassType();
- ImmutableSet<DexType> subtypes = subtypeMap.get(type);
- return subtypes == null ? ImmutableSet.of() : subtypes;
- }
-
- private void populateSuperType(Map<DexType, Set<DexType>> map, DexType superType,
- DexClass baseClass, Function<DexType, DexClass> definitions) {
- if (superType != null) {
- Set<DexType> set = map.computeIfAbsent(superType, ignore -> new HashSet<>());
- if (set.add(baseClass.type)) {
- // Only continue recursion if type has been added to set.
- populateAllSuperTypes(map, superType, baseClass, definitions);
- }
- }
- }
-
- private TypeInfo getTypeInfo(DexType type) {
- assert type != null;
- return typeInfo.computeIfAbsent(type, TypeInfo::new);
- }
-
- private void populateAllSuperTypes(
- Map<DexType, Set<DexType>> map,
- DexType holder,
- DexClass baseClass,
- Function<DexType, DexClass> definitions) {
- DexClass holderClass = definitions.apply(holder);
- // Skip if no corresponding class is found.
- if (holderClass != null) {
- populateSuperType(map, holderClass.superType, baseClass, definitions);
- if (holderClass.superType != null) {
- getTypeInfo(holderClass.superType).addDirectSubtype(getTypeInfo(holder));
- } else {
- // We found java.lang.Object
- assert dexItemFactory().objectType == holder;
- }
- for (DexType inter : holderClass.interfaces.values) {
- populateSuperType(map, inter, baseClass, definitions);
- getTypeInfo(inter).addInterfaceSubtype(holder);
- }
- if (holderClass.isInterface()) {
- getTypeInfo(holder).tagAsInterface();
- }
- } else {
- if (baseClass.isProgramClass() || baseClass.isClasspathClass()) {
- missingClasses.add(holder);
- }
- // The subtype chain is broken, at least make this type a subtype of Object.
- if (holder != dexItemFactory().objectType) {
- getTypeInfo(dexItemFactory().objectType).addDirectSubtype(getTypeInfo(holder));
- }
- }
- }
-
- private void populateSubtypeMap(
- Collection<DexClass> classes,
- Function<DexType, DexClass> definitions,
- DexItemFactory dexItemFactory) {
- getTypeInfo(dexItemFactory.objectType).tagAsSubtypeRoot();
- Map<DexType, Set<DexType>> map = new IdentityHashMap<>();
- for (DexClass clazz : classes) {
- populateAllSuperTypes(map, clazz.type, clazz, definitions);
- }
- for (Map.Entry<DexType, Set<DexType>> entry : map.entrySet()) {
- subtypeMap.put(entry.getKey(), ImmutableSet.copyOf(entry.getValue()));
- }
- assert validateLevelsAreCorrect(definitions, dexItemFactory);
- }
-
- private boolean validateLevelsAreCorrect(
- Function<DexType, DexClass> definitions, DexItemFactory dexItemFactory) {
- Set<DexType> seenTypes = Sets.newIdentityHashSet();
- Deque<DexType> worklist = new ArrayDeque<>();
- DexType objectType = dexItemFactory.objectType;
- worklist.add(objectType);
- while (!worklist.isEmpty()) {
- DexType next = worklist.pop();
- DexClass nextHolder = definitions.apply(next);
- DexType superType;
- if (nextHolder == null) {
- // We might lack the definition of Object, so guard against that.
- superType = next == dexItemFactory.objectType ? null : dexItemFactory.objectType;
- } else {
- superType = nextHolder.superType;
- }
- assert !seenTypes.contains(next);
- seenTypes.add(next);
- TypeInfo nextInfo = getTypeInfo(next);
- if (superType == null) {
- assert nextInfo.hierarchyLevel == ROOT_LEVEL;
- } else {
- TypeInfo superInfo = getTypeInfo(superType);
- assert superInfo.hierarchyLevel == nextInfo.hierarchyLevel - 1
- || (superInfo.hierarchyLevel == ROOT_LEVEL
- && nextInfo.hierarchyLevel == INTERFACE_LEVEL);
- assert superInfo.directSubtypes.contains(next);
- }
- if (nextInfo.hierarchyLevel != INTERFACE_LEVEL) {
- // Only traverse the class hierarchy subtypes, not interfaces.
- worklist.addAll(nextInfo.directSubtypes);
- } else if (nextHolder != null) {
- // Test that the interfaces of this class are interfaces and have this class as subtype.
- for (DexType iface : nextHolder.interfaces.values) {
- TypeInfo ifaceInfo = getTypeInfo(iface);
- assert ifaceInfo.directSubtypes.contains(next);
- assert ifaceInfo.hierarchyLevel == INTERFACE_LEVEL;
- }
- }
- }
- return true;
- }
-
- private void registerNewType(DexType newType, DexType superType) {
- assert checkIfObsolete();
- // Register the relationship between this type and its superType.
- getTypeInfo(superType).addDirectSubtype(getTypeInfo(newType));
- }
-
- @VisibleForTesting
- public void registerNewTypeForTesting(DexType newType, DexType superType) {
- registerNewType(newType, superType);
- }
-
@Override
public boolean hasSubtyping() {
assert checkIfObsolete();
@@ -373,46 +42,4 @@
assert checkIfObsolete();
return this;
}
-
- public Set<DexType> allImmediateSubtypes(DexType type) {
- return getTypeInfo(type).directSubtypes;
- }
-
- public boolean hasSubtypes(DexType type) {
- return !getTypeInfo(type).directSubtypes.isEmpty();
- }
-
- // TODO(b/139464956): Remove this method.
- public Iterable<DexType> allImmediateExtendsSubtypes_(DexType type) {
- TypeInfo info = getTypeInfo(type);
- assert info.hierarchyLevel != UNKNOWN_LEVEL;
- if (info.hierarchyLevel == INTERFACE_LEVEL) {
- return Iterables.filter(info.directSubtypes, t -> getTypeInfo(t).isInterface());
- } else if (info.hierarchyLevel == ROOT_LEVEL) {
- // This is the object type. Filter out interfaces
- return Iterables.filter(info.directSubtypes, t -> !getTypeInfo(t).isInterface());
- } else {
- return info.directSubtypes;
- }
- }
-
- // TODO(b/139464956): Remove this method.
- public Iterable<DexType> allImmediateImplementsSubtypes_(DexType type) {
- TypeInfo info = getTypeInfo(type);
- if (info.hierarchyLevel == INTERFACE_LEVEL) {
- return Iterables.filter(info.directSubtypes, subtype -> !getTypeInfo(subtype).isInterface());
- }
- return ImmutableList.of();
- }
-
- // TODO(b/139464956): Remove this method.
- public DexType getSingleSubtype_(DexType type) {
- TypeInfo info = getTypeInfo(type);
- assert info.hierarchyLevel != UNKNOWN_LEVEL;
- if (info.directSubtypes.size() == 1) {
- return Iterables.getFirst(info.directSubtypes, null);
- } else {
- return null;
- }
- }
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 3a55054..ef76217 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -324,7 +324,7 @@
}
public boolean canUseInitClass() {
- return options.shouldRerunEnqueuer() && !initClassLens.isFinal();
+ return options.isShrinking() && !initClassLens.isFinal();
}
public InitClassLens initClassLens() {
diff --git a/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java
index f1cadda..9cdac51 100644
--- a/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java
+++ b/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java
@@ -7,9 +7,12 @@
public class BottomUpClassHierarchyTraversal<T extends DexClass>
extends ClassHierarchyTraversal<T, BottomUpClassHierarchyTraversal<T>> {
+ private final SubtypingInfo subtypingInfo;
+
private BottomUpClassHierarchyTraversal(
- AppView<? extends AppInfoWithSubtyping> appView, Scope scope) {
+ AppView<? extends AppInfoWithSubtyping> appView, SubtypingInfo subtypingInfo, Scope scope) {
super(appView, scope);
+ this.subtypingInfo = subtypingInfo;
}
/**
@@ -17,8 +20,8 @@
* classes) that are reachable from a given set of sources.
*/
public static BottomUpClassHierarchyTraversal<DexClass> forAllClasses(
- AppView<? extends AppInfoWithSubtyping> appView) {
- return new BottomUpClassHierarchyTraversal<>(appView, Scope.ALL_CLASSES);
+ AppView<? extends AppInfoWithSubtyping> appView, SubtypingInfo subtypingInfo) {
+ return new BottomUpClassHierarchyTraversal<>(appView, subtypingInfo, Scope.ALL_CLASSES);
}
/**
@@ -26,8 +29,9 @@
* given set of sources.
*/
public static BottomUpClassHierarchyTraversal<DexProgramClass> forProgramClasses(
- AppView<? extends AppInfoWithSubtyping> appView) {
- return new BottomUpClassHierarchyTraversal<>(appView, Scope.ONLY_PROGRAM_CLASSES);
+ AppView<? extends AppInfoWithSubtyping> appView, SubtypingInfo subtypingInfo) {
+ return new BottomUpClassHierarchyTraversal<>(
+ appView, subtypingInfo, Scope.ONLY_PROGRAM_CLASSES);
}
@Override
@@ -51,7 +55,7 @@
worklist.addFirst(clazzWithTypeT);
// Add subtypes to worklist.
- for (DexType subtype : appView.appInfo().allImmediateSubtypes(clazz.type)) {
+ for (DexType subtype : subtypingInfo.allImmediateSubtypes(clazz.type)) {
DexClass definition = appView.definitionFor(subtype);
if (definition != null) {
if (scope != Scope.ONLY_PROGRAM_CLASSES || definition.isProgramClass()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 221a19e..9085643 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -227,7 +227,7 @@
if (appInfo.isPinned(type)) {
return false;
}
- return !appInfo.hasSubtypes(type) || !appInfo.isInstantiatedIndirectly(this);
+ return !appInfo.isInstantiatedIndirectly(this);
}
return false;
}
diff --git a/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
new file mode 100644
index 0000000..6c6e3ba
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
@@ -0,0 +1,317 @@
+// Copyright (c) 2020, the R8 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.
+package com.android.tools.r8.graph;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class SubtypingInfo {
+
+ private static final int ROOT_LEVEL = 0;
+ private static final int UNKNOWN_LEVEL = -1;
+ private static final int INTERFACE_LEVEL = -2;
+ // Since most Java types has no sub-types, we can just share an empty immutable set until we
+ // need to add to it.
+ private static final Set<DexType> NO_DIRECT_SUBTYPE = ImmutableSet.of();
+ // Map from types to their subtyping information.
+ private final DexItemFactory factory;
+
+ private final Map<DexType, TypeInfo> typeInfo = new ConcurrentHashMap<>();
+
+ // Map from types to their subtypes.
+ private final Map<DexType, ImmutableSet<DexType>> subtypeMap = new IdentityHashMap<>();
+
+ // TODO(b/154849103): Don't compute these here.
+ // Set of missing classes, discovered during subtypeMap computation.
+ private final Set<DexType> missingClasses = Sets.newIdentityHashSet();
+
+ public SubtypingInfo(Collection<DexClass> classes, DexDefinitionSupplier definitions) {
+ factory = definitions.dexItemFactory();
+ // Recompute subtype map if we have modified the graph.
+ populateSubtypeMap(classes, definitions::definitionFor, factory);
+ }
+
+ private void populateSuperType(
+ Map<DexType, Set<DexType>> map,
+ DexType superType,
+ DexClass baseClass,
+ Function<DexType, DexClass> definitions) {
+ if (superType != null) {
+ Set<DexType> set = map.computeIfAbsent(superType, ignore -> new HashSet<>());
+ if (set.add(baseClass.type)) {
+ // Only continue recursion if type has been added to set.
+ populateAllSuperTypes(map, superType, baseClass, definitions);
+ }
+ }
+ }
+
+ private DexItemFactory dexItemFactory() {
+ return factory;
+ }
+
+ private TypeInfo getTypeInfo(DexType type) {
+ assert type != null;
+ return typeInfo.computeIfAbsent(type, TypeInfo::new);
+ }
+
+ private void populateAllSuperTypes(
+ Map<DexType, Set<DexType>> map,
+ DexType holder,
+ DexClass baseClass,
+ Function<DexType, DexClass> definitions) {
+ DexClass holderClass = definitions.apply(holder);
+ // Skip if no corresponding class is found.
+ if (holderClass != null) {
+ populateSuperType(map, holderClass.superType, baseClass, definitions);
+ if (holderClass.superType != null) {
+ getTypeInfo(holderClass.superType).addDirectSubtype(getTypeInfo(holder));
+ } else {
+ // We found java.lang.Object
+ assert dexItemFactory().objectType == holder;
+ }
+ for (DexType inter : holderClass.interfaces.values) {
+ populateSuperType(map, inter, baseClass, definitions);
+ getTypeInfo(inter).addInterfaceSubtype(holder);
+ }
+ if (holderClass.isInterface()) {
+ getTypeInfo(holder).tagAsInterface();
+ }
+ } else {
+ if (baseClass.isProgramClass() || baseClass.isClasspathClass()) {
+ missingClasses.add(holder);
+ }
+ // The subtype chain is broken, at least make this type a subtype of Object.
+ if (holder != dexItemFactory().objectType) {
+ getTypeInfo(dexItemFactory().objectType).addDirectSubtype(getTypeInfo(holder));
+ }
+ }
+ }
+
+ private void populateSubtypeMap(
+ Collection<DexClass> classes,
+ Function<DexType, DexClass> definitions,
+ DexItemFactory dexItemFactory) {
+ getTypeInfo(dexItemFactory.objectType).tagAsSubtypeRoot();
+ Map<DexType, Set<DexType>> map = new IdentityHashMap<>();
+ for (DexClass clazz : classes) {
+ populateAllSuperTypes(map, clazz.type, clazz, definitions);
+ }
+ for (Map.Entry<DexType, Set<DexType>> entry : map.entrySet()) {
+ subtypeMap.put(entry.getKey(), ImmutableSet.copyOf(entry.getValue()));
+ }
+ assert validateLevelsAreCorrect(definitions, dexItemFactory);
+ }
+
+ private boolean validateLevelsAreCorrect(
+ Function<DexType, DexClass> definitions, DexItemFactory dexItemFactory) {
+ Set<DexType> seenTypes = Sets.newIdentityHashSet();
+ Deque<DexType> worklist = new ArrayDeque<>();
+ DexType objectType = dexItemFactory.objectType;
+ worklist.add(objectType);
+ while (!worklist.isEmpty()) {
+ DexType next = worklist.pop();
+ DexClass nextHolder = definitions.apply(next);
+ DexType superType;
+ if (nextHolder == null) {
+ // We might lack the definition of Object, so guard against that.
+ superType = next == dexItemFactory.objectType ? null : dexItemFactory.objectType;
+ } else {
+ superType = nextHolder.superType;
+ }
+ assert !seenTypes.contains(next);
+ seenTypes.add(next);
+ TypeInfo nextInfo = getTypeInfo(next);
+ if (superType == null) {
+ assert nextInfo.hierarchyLevel == ROOT_LEVEL;
+ } else {
+ TypeInfo superInfo = getTypeInfo(superType);
+ assert superInfo.hierarchyLevel == nextInfo.hierarchyLevel - 1
+ || (superInfo.hierarchyLevel == ROOT_LEVEL
+ && nextInfo.hierarchyLevel == INTERFACE_LEVEL);
+ assert superInfo.directSubtypes.contains(next);
+ }
+ if (nextInfo.hierarchyLevel != INTERFACE_LEVEL) {
+ // Only traverse the class hierarchy subtypes, not interfaces.
+ worklist.addAll(nextInfo.directSubtypes);
+ } else if (nextHolder != null) {
+ // Test that the interfaces of this class are interfaces and have this class as subtype.
+ for (DexType iface : nextHolder.interfaces.values) {
+ TypeInfo ifaceInfo = getTypeInfo(iface);
+ assert ifaceInfo.directSubtypes.contains(next);
+ assert ifaceInfo.hierarchyLevel == INTERFACE_LEVEL;
+ }
+ }
+ }
+ return true;
+ }
+
+ public Set<DexType> getMissingClasses() {
+ return Collections.unmodifiableSet(missingClasses);
+ }
+
+ public Set<DexType> subtypes(DexType type) {
+ assert type.isClassType();
+ ImmutableSet<DexType> subtypes = subtypeMap.get(type);
+ return subtypes == null ? ImmutableSet.of() : subtypes;
+ }
+
+ public DexType getSingleDirectSubtype(DexType type) {
+ TypeInfo info = getTypeInfo(type);
+ assert info.hierarchyLevel != SubtypingInfo.UNKNOWN_LEVEL;
+ if (info.directSubtypes.size() == 1) {
+ return Iterables.getFirst(info.directSubtypes, null);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Apply the given function to all classes that directly extend this class.
+ *
+ * <p>If this class is an interface, then this method will visit all sub-interfaces. This deviates
+ * from the dex-file encoding, where subinterfaces "implement" their super interfaces. However, it
+ * is consistent with the source language.
+ */
+ public void forAllImmediateExtendsSubtypes(DexType type, Consumer<DexType> f) {
+ allImmediateExtendsSubtypes(type).forEach(f);
+ }
+
+ public Iterable<DexType> allImmediateExtendsSubtypes(DexType type) {
+ TypeInfo info = getTypeInfo(type);
+ assert info.hierarchyLevel != SubtypingInfo.UNKNOWN_LEVEL;
+ if (info.hierarchyLevel == SubtypingInfo.INTERFACE_LEVEL) {
+ return Iterables.filter(info.directSubtypes, t -> getTypeInfo(t).isInterface());
+ } else if (info.hierarchyLevel == SubtypingInfo.ROOT_LEVEL) {
+ // This is the object type. Filter out interfaces
+ return Iterables.filter(info.directSubtypes, t -> !getTypeInfo(t).isInterface());
+ } else {
+ return info.directSubtypes;
+ }
+ }
+
+ /**
+ * Apply the given function to all classes that directly implement this interface.
+ *
+ * <p>The implementation does not consider how the hierarchy is encoded in the dex file, where
+ * interfaces "implement" their super interfaces. Instead it takes the view of the source
+ * language, where interfaces "extend" their superinterface.
+ */
+ public void forAllImmediateImplementsSubtypes(DexType type, Consumer<DexType> f) {
+ allImmediateImplementsSubtypes(type).forEach(f);
+ }
+
+ public Iterable<DexType> allImmediateImplementsSubtypes(DexType type) {
+ TypeInfo info = getTypeInfo(type);
+ if (info.hierarchyLevel == SubtypingInfo.INTERFACE_LEVEL) {
+ return Iterables.filter(info.directSubtypes, subtype -> !getTypeInfo(subtype).isInterface());
+ }
+ return ImmutableList.of();
+ }
+
+ private static class TypeInfo {
+
+ private final DexType type;
+
+ int hierarchyLevel = UNKNOWN_LEVEL;
+ /**
+ * Set of direct subtypes. This set has to remain sorted to ensure determinism. The actual
+ * sorting is not important but {@link DexType#slowCompareTo(DexType)} works well.
+ */
+ Set<DexType> directSubtypes = NO_DIRECT_SUBTYPE;
+
+ TypeInfo(DexType type) {
+ this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ return "TypeInfo{" + type + ", level:" + hierarchyLevel + "}";
+ }
+
+ private void ensureDirectSubTypeSet() {
+ if (directSubtypes == NO_DIRECT_SUBTYPE) {
+ directSubtypes = new ConcurrentSkipListSet<>(DexType::slowCompareTo);
+ }
+ }
+
+ private void setLevel(int level) {
+ if (level == hierarchyLevel) {
+ return;
+ }
+ if (hierarchyLevel == INTERFACE_LEVEL) {
+ assert level == ROOT_LEVEL + 1;
+ } else if (level == INTERFACE_LEVEL) {
+ assert hierarchyLevel == ROOT_LEVEL + 1 || hierarchyLevel == UNKNOWN_LEVEL;
+ hierarchyLevel = INTERFACE_LEVEL;
+ } else {
+ assert hierarchyLevel == UNKNOWN_LEVEL;
+ hierarchyLevel = level;
+ }
+ }
+
+ synchronized void addDirectSubtype(TypeInfo subtypeInfo) {
+ assert hierarchyLevel != UNKNOWN_LEVEL;
+ ensureDirectSubTypeSet();
+ directSubtypes.add(subtypeInfo.type);
+ subtypeInfo.setLevel(hierarchyLevel + 1);
+ }
+
+ void tagAsSubtypeRoot() {
+ setLevel(ROOT_LEVEL);
+ }
+
+ void tagAsInterface() {
+ setLevel(INTERFACE_LEVEL);
+ }
+
+ public boolean isInterface() {
+ assert hierarchyLevel != UNKNOWN_LEVEL : "Program class missing: " + this;
+ assert type.isClassType();
+ return hierarchyLevel == INTERFACE_LEVEL;
+ }
+
+ public boolean isUnknown() {
+ return hierarchyLevel == UNKNOWN_LEVEL;
+ }
+
+ synchronized void addInterfaceSubtype(DexType type) {
+ // Interfaces all inherit from java.lang.Object. However, we assign a special level to
+ // identify them later on.
+ setLevel(INTERFACE_LEVEL);
+ ensureDirectSubTypeSet();
+ directSubtypes.add(type);
+ }
+ }
+
+ public Set<DexType> allImmediateSubtypes(DexType type) {
+ return getTypeInfo(type).directSubtypes;
+ }
+
+ public boolean isUnknown(DexType type) {
+ return getTypeInfo(type).isUnknown();
+ }
+
+ public boolean hasSubtypes(DexType type) {
+ return !getTypeInfo(type).directSubtypes.isEmpty();
+ }
+
+ void registerNewType(DexType newType, DexType superType) {
+ // Register the relationship between this type and its superType.
+ getTypeInfo(superType).addDirectSubtype(getTypeInfo(newType));
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index b4e025c..3e493b7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.ir.analysis.proto;
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.google.common.base.Predicates.not;
import com.android.tools.r8.graph.AppView;
@@ -14,7 +13,6 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.FieldAccessInfo;
@@ -176,16 +174,20 @@
}
private void forEachFindLiteExtensionByNumberMethod(Consumer<DexEncodedMethod> consumer) {
- for (DexType type : appView.appInfo().subtypes(references.extensionRegistryLiteType)) {
- DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
- if (clazz != null) {
- for (DexEncodedMethod method : clazz.methods()) {
- if (references.isFindLiteExtensionByNumberMethod(method.method)) {
- consumer.accept(method);
- }
- }
- }
- }
+ appView
+ .appInfo()
+ .forEachInstantiatedSubType(
+ references.extensionRegistryLiteType,
+ clazz -> {
+ for (DexEncodedMethod method : clazz.methods()) {
+ if (references.isFindLiteExtensionByNumberMethod(method.method)) {
+ consumer.accept(method);
+ }
+ }
+ },
+ lambda -> {
+ assert false;
+ });
}
public boolean isDeadProtoExtensionField(DexField field) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index 2914d30..2f152e8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -117,17 +118,14 @@
public static void addInliningHeuristicsForBuilderInlining(
AppView<? extends AppInfoWithSubtyping> appView,
+ SubtypingInfo subtypingInfo,
PredicateSet<DexType> alwaysClassInline,
Set<DexType> neverMerge,
Set<DexMethod> alwaysInline,
Set<DexMethod> bypassClinitforInlining) {
new RootSetExtension(
- appView,
- alwaysClassInline,
- neverMerge,
- alwaysInline,
- bypassClinitforInlining)
- .extend();
+ appView, alwaysClassInline, neverMerge, alwaysInline, bypassClinitforInlining)
+ .extend(subtypingInfo);
}
public void preprocessCallGraphBeforeCycleElimination(Map<DexMethod, Node> nodes) {
@@ -304,14 +302,14 @@
this.bypassClinitforInlining = bypassClinitforInlining;
}
- void extend() {
+ void extend(SubtypingInfo subtypingInfo) {
alwaysClassInlineGeneratedMessageLiteBuilders();
// GeneratedMessageLite heuristics.
alwaysInlineCreateBuilderFromGeneratedMessageLite();
// * extends GeneratedMessageLite heuristics.
- bypassClinitforInliningNewBuilderMethods();
+ bypassClinitforInliningNewBuilderMethods(subtypingInfo);
// GeneratedMessageLite$Builder heuristics.
alwaysInlineBuildPartialFromGeneratedMessageLiteExtendableBuilder();
@@ -326,8 +324,8 @@
.isStrictSubtypeOf(type, references.generatedMessageLiteBuilderType));
}
- private void bypassClinitforInliningNewBuilderMethods() {
- for (DexType type : appView.appInfo().subtypes(references.generatedMessageLiteType)) {
+ private void bypassClinitforInliningNewBuilderMethods(SubtypingInfo subtypingInfo) {
+ for (DexType type : subtypingInfo.subtypes(references.generatedMessageLiteType)) {
DexProgramClass clazz = appView.definitionFor(type).asProgramClass();
if (clazz != null) {
DexEncodedMethod newBuilderMethod =
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 40b8cb5..4414507 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.ir.analysis.proto;
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.getInfoValueFromMessageInfoConstructionInvoke;
import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.getObjectsValueFromMessageInfoConstructionInvoke;
import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.setObjectsValueForMessageInfoConstructionInvoke;
@@ -14,8 +13,6 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.proto.schema.ProtoMessageInfo;
import com.android.tools.r8.ir.analysis.proto.schema.ProtoObject;
import com.android.tools.r8.ir.analysis.type.Nullability;
@@ -94,18 +91,22 @@
private void forEachDynamicMethod(Consumer<DexEncodedMethod> consumer) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
- for (DexType type : appView.appInfo().subtypes(references.generatedMessageLiteType)) {
- DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
- if (clazz != null) {
- DexMethod dynamicMethod =
- dexItemFactory.createMethod(
- type, references.dynamicMethodProto, references.dynamicMethodName);
- DexEncodedMethod encodedDynamicMethod = clazz.lookupVirtualMethod(dynamicMethod);
- if (encodedDynamicMethod != null) {
- consumer.accept(encodedDynamicMethod);
- }
- }
- }
+ appView
+ .appInfo()
+ .forEachInstantiatedSubType(
+ references.generatedMessageLiteType,
+ clazz -> {
+ DexMethod dynamicMethod =
+ dexItemFactory.createMethod(
+ clazz.type, references.dynamicMethodProto, references.dynamicMethodName);
+ DexEncodedMethod encodedDynamicMethod = clazz.lookupVirtualMethod(dynamicMethod);
+ if (encodedDynamicMethod != null) {
+ consumer.accept(encodedDynamicMethod);
+ }
+ },
+ lambda -> {
+ assert false;
+ });
}
/**
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index f356690..7c34ec5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -98,6 +98,15 @@
if (refinedReceiverLowerBoundType != null) {
refinedReceiverLowerBound =
asProgramClassOrNull(appView.definitionFor(refinedReceiverLowerBoundType.getClassType()));
+ // TODO(b/154822960): Check if the lower bound is a subtype of the upper bound.
+ if (refinedReceiverUpperBound != null
+ && refinedReceiverLowerBound != null
+ && !appView
+ .appInfo()
+ .isSubtype(refinedReceiverLowerBound.type, refinedReceiverUpperBound.type)) {
+ // We cannot trust the lower bound.
+ refinedReceiverLowerBound = null;
+ }
}
ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
LookupResult lookupResult;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 689bf8f..a5c4e81 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -4,8 +4,10 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -82,10 +84,47 @@
TypeAnalysis.getRefinedReceiverType(appViewWithLiveness, this);
assert receiverLowerBoundType.getClassType() == refinedReceiverType
|| receiverLowerBoundType.isBasedOnMissingClass(appViewWithLiveness)
+ || upperBoundAssumedByCallSiteOptimizationAndNoLongerInstantiated(
+ appViewWithLiveness, refinedReceiverType, receiverLowerBoundType.getClassType())
: "The receiver lower bound does not match the receiver type";
}
}
return true;
}
+
+ private boolean upperBoundAssumedByCallSiteOptimizationAndNoLongerInstantiated(
+ AppView<AppInfoWithLiveness> appViewWithLiveness,
+ DexType upperBoundType,
+ DexType lowerBoundType) {
+ // Check that information came from the CallSiteOptimization.
+ if (!getReceiver().getAliasedValue().isArgument()) {
+ return false;
+ }
+ // Check that the receiver information comes from a dynamic type.
+ if (!getReceiver().definition.isAssumeDynamicType()) {
+ return false;
+ }
+ // Now, it can be that the upper bound is more precise than the lower:
+ // class A { }
+ // class B extends A { }
+ //
+ // class Main {
+ // new B();
+ // }
+ //
+ // Above, the callsite optimization will register that A.<init> will be called with an argument
+ // of type B and put B in as the dynamic upper bound type. However, we can also class-inline B,
+ // thereby removing the instantiation, making A effectively final.
+ // TODO(b/154822960): Perhaps we should not process this code at all?
+ DexProgramClass upperBound = appViewWithLiveness.definitionForProgramType(upperBoundType);
+ if (upperBound == null) {
+ return false;
+ }
+ if (appViewWithLiveness.appInfo().isInstantiatedDirectlyOrIndirectly(upperBound)) {
+ return false;
+ }
+ DexClass lowerBound = appViewWithLiveness.definitionFor(lowerBoundType);
+ return lowerBound != null && lowerBound.isEffectivelyFinal(appViewWithLiveness);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index ca2cdd4..87ee96f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -1171,6 +1171,16 @@
}
public ClassTypeElement getDynamicLowerBoundType(AppView<AppInfoWithLiveness> appView) {
+ // If it is a final or effectively-final class type, then we know the lower bound.
+ if (getType().isClassType()) {
+ ClassTypeElement classType = getType().asClassType();
+ DexType type = classType.getClassType();
+ DexClass clazz = appView.definitionFor(type);
+ if (clazz != null && clazz.isEffectivelyFinal(appView)) {
+ return classType;
+ }
+ }
+
Value root = getAliasedValue();
if (root.isPhi()) {
return null;
@@ -1197,16 +1207,6 @@
: lattice;
}
- // If it is a final or effectively-final class type, then we know the lower bound.
- if (getType().isClassType()) {
- ClassTypeElement classType = getType().asClassType();
- DexType type = classType.getClassType();
- DexClass clazz = appView.definitionFor(type);
- if (clazz != null && clazz.isEffectivelyFinal(appView)) {
- return classType;
- }
- }
-
return null;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 0d91bab..21228a9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -26,6 +26,7 @@
import static com.android.tools.r8.ir.code.Opcodes.MOVE_EXCEPTION;
import static com.android.tools.r8.ir.code.Opcodes.NEW_ARRAY_EMPTY;
import static com.android.tools.r8.ir.code.Opcodes.NEW_INSTANCE;
+import static com.android.tools.r8.ir.code.Opcodes.RETURN;
import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
@@ -80,6 +81,7 @@
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
@@ -491,6 +493,27 @@
}
break;
+ case RETURN:
+ {
+ Return ret = current.asReturn();
+ if (ret.isReturnVoid()) {
+ break;
+ }
+ DexType returnType = code.method.method.proto.returnType;
+ Value retValue = ret.returnValue();
+ DexType initialType =
+ retValue.getType().isPrimitiveType()
+ ? retValue.getType().asPrimitiveType().toDexType(factory)
+ : factory.objectType; // Place holder, any reference type will do.
+ Value rewrittenValue =
+ rewriteValueIfDefault(code, iterator, initialType, returnType, retValue);
+ if (retValue != rewrittenValue) {
+ Return newReturn = new Return(rewrittenValue);
+ iterator.replaceCurrentInstruction(newReturn);
+ }
+ }
+ break;
+
default:
if (current.hasOutValue()) {
// For all other instructions, substitute any changed type.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 49c8000..706cfa0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -326,6 +326,9 @@
if (options.minApiLevel < AndroidApiLevel.O.getLevel()) {
initializeAndroidOMethodProviders(factory);
}
+ if (options.minApiLevel < AndroidApiLevel.R.getLevel()) {
+ initializeAndroidRMethodProviders(factory);
+ }
// The following providers are currently not implemented at any API level in Android.
// They however require the Optional/Stream class to be present, either through desugared
@@ -1002,68 +1005,11 @@
method, BackportedMethods::StringMethods_joinIterable, "joinIterable"));
}
- private void initializeJava9MethodProviders(DexItemFactory factory) {
- // Math & StrictMath, which have some symmetric, binary-compatible APIs
- DexType[] mathTypes = {factory.mathType, factory.strictMathType};
- for (int i = 0; i < mathTypes.length; i++) {
- DexType type = mathTypes[i];
-
- // long {Math,StrictMath}.multiplyExact(long, int)
- DexString name = factory.createString("multiplyExact");
- DexProto proto = factory.createProto(factory.longType, factory.longType, factory.intType);
- DexMethod method = factory.createMethod(type, proto, name);
- addProvider(
- new MethodGenerator(
- method,
- BackportedMethods::MathMethods_multiplyExactLongInt,
- "multiplyExactLongInt"));
-
- // long {Math,StrictMath}.multiplyFull(int, int)
- name = factory.createString("multiplyFull");
- proto = factory.createProto(factory.longType, factory.intType, factory.intType);
- method = factory.createMethod(type, proto, name);
- addProvider(new MethodGenerator(method, BackportedMethods::MathMethods_multiplyFull));
-
- // long {Math,StrictMath}.multiplyHigh(long, long)
- name = factory.createString("multiplyHigh");
- proto = factory.createProto(factory.longType, factory.longType, factory.longType);
- method = factory.createMethod(type, proto, name);
- addProvider(new MethodGenerator(method, BackportedMethods::MathMethods_multiplyHigh));
-
- // long {Math,StrictMath}.floorDiv(long, int)
- name = factory.createString("floorDiv");
- proto = factory.createProto(factory.longType, factory.longType, factory.intType);
- method = factory.createMethod(type, proto, name);
- addProvider(
- new MethodGenerator(
- method, BackportedMethods::MathMethods_floorDivLongInt, "floorDivLongInt"));
-
- // int {Math,StrictMath}.floorMod(long, int)
- name = factory.createString("floorMod");
- proto = factory.createProto(factory.intType, factory.longType, factory.intType);
- method = factory.createMethod(type, proto, name);
- addProvider(
- new MethodGenerator(
- method, BackportedMethods::MathMethods_floorModLongInt, "floorModLongInt"));
- }
-
- // Byte
- DexType type = factory.boxedByteType;
-
- // int Byte.compareUnsigned(byte, byte)
- DexString name = factory.createString("compareUnsigned");
- DexProto proto = factory.createProto(factory.intType, factory.byteType, factory.byteType);
- DexMethod method = factory.createMethod(type, proto, name);
- addProvider(new MethodGenerator(method, BackportedMethods::ByteMethods_compareUnsigned));
-
- // Short
- type = factory.boxedShortType;
-
- // int Short.compareUnsigned(short, short)
- name = factory.createString("compareUnsigned");
- proto = factory.createProto(factory.intType, factory.shortType, factory.shortType);
- method = factory.createMethod(type, proto, name);
- addProvider(new MethodGenerator(method, BackportedMethods::ShortMethods_compareUnsigned));
+ private void initializeAndroidRMethodProviders(DexItemFactory factory) {
+ DexType type;
+ DexString name;
+ DexProto proto;
+ DexMethod method;
// Objects
type = factory.objectsType;
@@ -1172,6 +1118,70 @@
addProvider(new MethodGenerator(method, BackportedMethods::CollectionMethods_mapEntry));
}
+ private void initializeJava9MethodProviders(DexItemFactory factory) {
+ // Math & StrictMath, which have some symmetric, binary-compatible APIs
+ DexType[] mathTypes = {factory.mathType, factory.strictMathType};
+ for (int i = 0; i < mathTypes.length; i++) {
+ DexType type = mathTypes[i];
+
+ // long {Math,StrictMath}.multiplyExact(long, int)
+ DexString name = factory.createString("multiplyExact");
+ DexProto proto = factory.createProto(factory.longType, factory.longType, factory.intType);
+ DexMethod method = factory.createMethod(type, proto, name);
+ addProvider(
+ new MethodGenerator(
+ method,
+ BackportedMethods::MathMethods_multiplyExactLongInt,
+ "multiplyExactLongInt"));
+
+ // long {Math,StrictMath}.multiplyFull(int, int)
+ name = factory.createString("multiplyFull");
+ proto = factory.createProto(factory.longType, factory.intType, factory.intType);
+ method = factory.createMethod(type, proto, name);
+ addProvider(new MethodGenerator(method, BackportedMethods::MathMethods_multiplyFull));
+
+ // long {Math,StrictMath}.multiplyHigh(long, long)
+ name = factory.createString("multiplyHigh");
+ proto = factory.createProto(factory.longType, factory.longType, factory.longType);
+ method = factory.createMethod(type, proto, name);
+ addProvider(new MethodGenerator(method, BackportedMethods::MathMethods_multiplyHigh));
+
+ // long {Math,StrictMath}.floorDiv(long, int)
+ name = factory.createString("floorDiv");
+ proto = factory.createProto(factory.longType, factory.longType, factory.intType);
+ method = factory.createMethod(type, proto, name);
+ addProvider(
+ new MethodGenerator(
+ method, BackportedMethods::MathMethods_floorDivLongInt, "floorDivLongInt"));
+
+ // int {Math,StrictMath}.floorMod(long, int)
+ name = factory.createString("floorMod");
+ proto = factory.createProto(factory.intType, factory.longType, factory.intType);
+ method = factory.createMethod(type, proto, name);
+ addProvider(
+ new MethodGenerator(
+ method, BackportedMethods::MathMethods_floorModLongInt, "floorModLongInt"));
+ }
+
+ // Byte
+ DexType type = factory.boxedByteType;
+
+ // int Byte.compareUnsigned(byte, byte)
+ DexString name = factory.createString("compareUnsigned");
+ DexProto proto = factory.createProto(factory.intType, factory.byteType, factory.byteType);
+ DexMethod method = factory.createMethod(type, proto, name);
+ addProvider(new MethodGenerator(method, BackportedMethods::ByteMethods_compareUnsigned));
+
+ // Short
+ type = factory.boxedShortType;
+
+ // int Short.compareUnsigned(short, short)
+ name = factory.createString("compareUnsigned");
+ proto = factory.createProto(factory.intType, factory.shortType, factory.shortType);
+ method = factory.createMethod(type, proto, name);
+ addProvider(new MethodGenerator(method, BackportedMethods::ShortMethods_compareUnsigned));
+ }
+
private void initializeJava10MethodProviders(DexItemFactory factory) {
// List
DexType type = factory.listType;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
index 6e53d40..46ac1ce 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ThreadUtils;
@@ -32,11 +33,16 @@
final Equivalence<R> equivalence;
final AppView<AppInfoWithLiveness> appView;
+ final SubtypingInfo subtypingInfo;
final Map<DexClass, MemberPool<R>> memberPools = new ConcurrentHashMap<>();
- MemberPoolCollection(AppView<AppInfoWithLiveness> appView, Equivalence<R> equivalence) {
+ MemberPoolCollection(
+ AppView<AppInfoWithLiveness> appView,
+ Equivalence<R> equivalence,
+ SubtypingInfo subtypingInfo) {
this.appView = appView;
this.equivalence = equivalence;
+ this.subtypingInfo = subtypingInfo;
}
public void buildAll(ExecutorService executorService, Timing timing) throws ExecutionException {
@@ -128,33 +134,24 @@
return superTypes;
}
- // TODO(jsjeon): maybe be part of AppInfoWithSubtyping?
private Set<DexClass> getAllSubTypesExclusive(
DexClass subject, Predicate<DexClass> stoppingCriterion) {
Set<DexClass> subTypes = new HashSet<>();
Deque<DexClass> worklist = new ArrayDeque<>();
- appView
- .appInfo()
- .forAllImmediateExtendsSubtypes(
- subject.type, type -> addNonNull(worklist, appView.definitionFor(type)));
- appView
- .appInfo()
- .forAllImmediateImplementsSubtypes(
- subject.type, type -> addNonNull(worklist, appView.definitionFor(type)));
+ subtypingInfo.forAllImmediateExtendsSubtypes(
+ subject.type, type -> addNonNull(worklist, appView.definitionFor(type)));
+ subtypingInfo.forAllImmediateImplementsSubtypes(
+ subject.type, type -> addNonNull(worklist, appView.definitionFor(type)));
while (!worklist.isEmpty()) {
DexClass clazz = worklist.pop();
if (stoppingCriterion.test(clazz)) {
continue;
}
if (subTypes.add(clazz)) {
- appView
- .appInfo()
- .forAllImmediateExtendsSubtypes(
- clazz.type, type -> addNonNull(worklist, appView.definitionFor(type)));
- appView
- .appInfo()
- .forAllImmediateImplementsSubtypes(
- clazz.type, type -> addNonNull(worklist, appView.definitionFor(type)));
+ subtypingInfo.forAllImmediateExtendsSubtypes(
+ clazz.type, type -> addNonNull(worklist, appView.definitionFor(type)));
+ subtypingInfo.forAllImmediateImplementsSubtypes(
+ clazz.type, type -> addNonNull(worklist, appView.definitionFor(type)));
}
}
return subTypes;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
index 04b2874..593e827 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.google.common.base.Predicates;
@@ -33,14 +34,15 @@
private final Predicate<DexEncodedMethod> methodTester;
- public MethodPoolCollection(AppView<AppInfoWithLiveness> appView) {
- super(appView, MethodSignatureEquivalence.get());
- this.methodTester = Predicates.alwaysTrue();
+ public MethodPoolCollection(AppView<AppInfoWithLiveness> appView, SubtypingInfo subtypingInfo) {
+ this(appView, subtypingInfo, Predicates.alwaysTrue());
}
public MethodPoolCollection(
- AppView<AppInfoWithLiveness> appView, Predicate<DexEncodedMethod> methodTester) {
- super(appView, MethodSignatureEquivalence.get());
+ AppView<AppInfoWithLiveness> appView,
+ SubtypingInfo subtypingInfo,
+ Predicate<DexEncodedMethod> methodTester) {
+ super(appView, MethodSignatureEquivalence.get(), subtypingInfo);
this.methodTester = methodTester;
}
@@ -69,7 +71,7 @@
}
}
if (clazz.isInterface()) {
- for (DexType subtype : appView.appInfo().allImmediateSubtypes(clazz.type)) {
+ for (DexType subtype : subtypingInfo.allImmediateSubtypes(clazz.type)) {
DexClass subClazz = appView.definitionFor(subtype);
if (subClazz != null) {
MemberPool<DexMethod> childPool =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index 64a8c86..3b427cb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -119,8 +119,7 @@
return null;
}
// Only consider effectively final class. Exception: new Base().getClass().
- if (appView.appInfo().hasSubtypes(baseType)
- && appView.appInfo().isInstantiatedIndirectly(clazz)
+ if (!clazz.isEffectivelyFinal(appView)
&& (in.isPhi() || !in.definition.isCreatingInstanceOrArray())) {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 61a7c13..d878322 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -136,7 +136,7 @@
}
}
if (outValue.getType().isNullType()) {
- addNullDependencies(outValue.uniqueUsers(), eligibleEnums);
+ addNullDependencies(code, outValue.uniqueUsers(), eligibleEnums);
}
} else {
if (instruction.isInvokeMethod()) {
@@ -178,7 +178,7 @@
}
}
if (phi.getType().isNullType()) {
- addNullDependencies(phi.uniqueUsers(), eligibleEnums);
+ addNullDependencies(code, phi.uniqueUsers(), eligibleEnums);
}
}
}
@@ -244,7 +244,7 @@
eligibleEnums.add(constClass.getValue());
}
- private void addNullDependencies(Set<Instruction> uses, Set<DexType> eligibleEnums) {
+ private void addNullDependencies(IRCode code, Set<Instruction> uses, Set<DexType> eligibleEnums) {
for (Instruction use : uses) {
if (use.isInvokeMethod()) {
InvokeMethod invokeMethod = use.asInvokeMethod();
@@ -260,12 +260,16 @@
markEnumAsUnboxable(Reason.ENUM_METHOD_CALLED_WITH_NULL_RECEIVER, enumClass);
}
}
- }
- if (use.isFieldPut()) {
+ } else if (use.isFieldPut()) {
DexType type = use.asFieldInstruction().getField().type;
if (enumsUnboxingCandidates.containsKey(type)) {
eligibleEnums.add(type);
}
+ } else if (use.isReturn()) {
+ DexType returnType = code.method.method.proto.returnType;
+ if (enumsUnboxingCandidates.containsKey(returnType)) {
+ eligibleEnums.add(returnType);
+ }
}
}
}
@@ -621,6 +625,7 @@
public enum Reason {
ELIGIBLE,
+ ANNOTATION,
PINNED,
DOWN_CAST,
SUBTYPES,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 1bda5d7..8f228b9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -42,6 +42,7 @@
enumToUnboxCandidates.put(clazz.type, Sets.newConcurrentHashSet());
}
}
+ removeEnumsInAnnotations();
removePinnedCandidates();
return enumToUnboxCandidates;
}
@@ -128,6 +129,30 @@
return true;
}
+ private void removeEnumsInAnnotations() {
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ if (appView.appInfo().isSubtype(clazz.type, factory.annotationType)) {
+ removeEnumsInAnnotation(clazz);
+ }
+ }
+ }
+
+ private void removeEnumsInAnnotation(DexProgramClass clazz) {
+ // Browse annotation values types in search for enum.
+ // Each annotation value is represented by a virtual method.
+ for (DexEncodedMethod method : clazz.virtualMethods()) {
+ DexProto proto = method.method.proto;
+ assert proto.parameters.isEmpty();
+ DexType valueType = proto.returnType.toBaseType(appView.appInfo().dexItemFactory());
+ if (valueType.isClassType()
+ && enumToUnboxCandidates.containsKey(valueType)
+ && appView.appInfo().isSubtype(valueType, appView.appInfo().dexItemFactory().enumType)) {
+ enumUnboxer.reportFailure(valueType, Reason.ANNOTATION);
+ enumToUnboxCandidates.remove(valueType);
+ }
+ }
+ }
+
private void removePinnedCandidates() {
// A holder type, for field or method, should block enum unboxing only if the enum type is
// also kept. This is to allow the keep rule -keepclassmembers to be used on enums while
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 7b91c7e..383b03b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -193,7 +193,7 @@
instruction = arrayAccess.withMemberType(MemberType.INT);
iterator.replaceCurrentInstruction(instruction);
}
- assert validateArrayAccess(instruction.asArrayAccess());
+ assert validateArrayAccess(arrayAccess);
}
}
assert code.isConsistentSSABeforeTypesAreCorrect();
@@ -202,6 +202,10 @@
private boolean validateArrayAccess(ArrayAccess arrayAccess) {
ArrayTypeElement arrayType = arrayAccess.array().getType().asArrayType();
+ if (arrayType == null) {
+ assert arrayAccess.array().getType().isNullType();
+ return true;
+ }
assert arrayAccess.getMemberType() != MemberType.OBJECT
|| arrayType.getNesting() > 1
|| arrayType.getBaseType().isReferenceType();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
index 6bf4c74..3ff1799 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
@@ -44,8 +44,12 @@
public void recordInitializationInfo(
DexEncodedField field, InstanceFieldInitializationInfo info) {
- assert !infos.containsKey(field.field);
- infos.put(field.field, info);
+ recordInitializationInfo(field.field, info);
+ }
+
+ public void recordInitializationInfo(DexField field, InstanceFieldInitializationInfo info) {
+ assert !infos.containsKey(field);
+ infos.put(field, info);
}
public InstanceFieldInitializationInfoCollection build() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
index 7c7a563..2fe8a19 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.IdentityHashMap;
import java.util.Map;
import java.util.function.BiConsumer;
@@ -55,13 +54,15 @@
@Override
public InstanceFieldInitializationInfoCollection rewrittenWithLens(
AppView<AppInfoWithLiveness> appView, GraphLense lens) {
- Map<DexField, InstanceFieldInitializationInfo> rewrittenInfos = new IdentityHashMap<>();
+ Builder builder = InstanceFieldInitializationInfoCollection.builder();
infos.forEach(
(field, info) -> {
DexField rewrittenField = lens.lookupField(field);
InstanceFieldInitializationInfo rewrittenInfo = info.rewrittenWithLens(appView, lens);
- rewrittenInfos.put(rewrittenField, rewrittenInfo);
+ if (!rewrittenInfo.isUnknown()) {
+ builder.recordInitializationInfo(rewrittenField, rewrittenInfo);
+ }
});
- return new NonTrivialInstanceFieldInitializationInfoCollection(rewrittenInfos);
+ return builder.build();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index ed3e72a..3311e1d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -157,9 +157,8 @@
|| cls.instanceFields().size() > 0
// Only support classes directly extending java.lang.Object
|| cls.superType != factory.objectType
- // Instead of requiring the class being final,
- // just ensure it does not have subtypes
- || appView.appInfo().hasSubtypes(cls.type)
+ // The class must not have instantiated subtypes.
+ || !cls.isEffectivelyFinal(appView)
// Staticizing classes implementing interfaces is more
// difficult, so don't support it until we really need it.
|| !cls.interfaces.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index db37d8c..8be3f22 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.List;
@@ -72,7 +73,7 @@
}
@Override
- void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ void rewrite(AppView<AppInfoWithLiveness> appView, SubtypingInfo subtypingInfo, NamingLens lens) {
KotlinMetadataSynthesizer synthesizer = new KotlinMetadataSynthesizer(appView, lens, this);
if (appView.options().enableKotlinMetadataRewritingForRenamedClasses
&& lens.lookupType(clazz.type, appView.dexItemFactory()) != clazz.type) {
@@ -128,7 +129,7 @@
List<String> sealedSubclasses = kmClass.getSealedSubclasses();
sealedSubclasses.clear();
if (IS_SEALED.invoke(kmClass.getFlags())) {
- for (DexType subtype : appView.appInfo().allImmediateSubtypes(clazz.type)) {
+ for (DexType subtype : subtypingInfo.allImmediateSubtypes(clazz.type)) {
String classifier = synthesizer.toRenamedClassifier(subtype);
if (classifier != null) {
sealedSubclasses.add(classifier);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
index fc3fbbe..2a60521 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -42,7 +43,7 @@
}
@Override
- void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ void rewrite(AppView<AppInfoWithLiveness> appView, SubtypingInfo subtypingInfo, NamingLens lens) {
ListIterator<String> partClassIterator = partClassNames.listIterator();
KotlinMetadataSynthesizer synthesizer = new KotlinMetadataSynthesizer(appView, lens, this);
while (partClassIterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index 90f265c..f0d15a4 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -39,7 +40,7 @@
}
@Override
- void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ void rewrite(AppView<AppInfoWithLiveness> appView, SubtypingInfo subtypingInfo, NamingLens lens) {
DexType facadeClassType = appView.dexItemFactory().createType(
DescriptorUtils.getDescriptorFromClassBinaryName(facadeClassName));
KotlinMetadataSynthesizer synthesizer = new KotlinMetadataSynthesizer(appView, lens, this);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
index 8ec966b..ae7eb41 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import kotlinx.metadata.KmPackage;
@@ -34,7 +35,7 @@
}
@Override
- void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ void rewrite(AppView<AppInfoWithLiveness> appView, SubtypingInfo subtypingInfo, NamingLens lens) {
if (!appView.options().enableKotlinMetadataRewritingForMembers) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index e516ff9..ef1d2b6 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinFieldInfo;
import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinPropertyInfo;
import com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.KmPropertyGroup;
@@ -67,7 +68,8 @@
// Subtypes will define how to rewrite metadata after shrinking and minification.
// Subtypes that represent subtypes of {@link KmDeclarationContainer} can use
// {@link #rewriteDeclarationContainer} below.
- abstract void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens);
+ abstract void rewrite(
+ AppView<AppInfoWithLiveness> appView, SubtypingInfo subtypingInfo, NamingLens lens);
abstract KotlinClassHeader createHeader();
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index f83796f..1c597dc 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexValue.DexValueArray;
import com.android.tools.r8.graph.DexValue.DexValueInt;
import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ThreadUtils;
@@ -63,6 +64,7 @@
public void run(ExecutorService executorService) throws ExecutionException {
// TODO(b/152283077): Don't disable the assert.
appView.appInfo().disableDefinitionForAssert();
+ SubtypingInfo subtypingInfo = appView.appInfo().computeSubtypingInfo();
ThreadUtils.processItems(
appView.appInfo().classes(),
clazz -> {
@@ -86,7 +88,7 @@
return;
}
- kotlinInfo.rewrite(appView, lens);
+ kotlinInfo.rewrite(appView, subtypingInfo, lens);
DexAnnotation newMeta = createKotlinMetadataAnnotation(kotlinInfo.createHeader());
clazz.setAnnotations(
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
index 8c4ccaf..ced14b9 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import kotlinx.metadata.jvm.KotlinClassHeader;
@@ -52,7 +53,7 @@
}
@Override
- void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ void rewrite(AppView<AppInfoWithLiveness> appView, SubtypingInfo subtypingInfo, NamingLens lens) {
// TODO(b/151194794): no idea yet!
assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
|| appView.options().enableKotlinMetadataRewritingForRenamedClasses
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index 5506792..c57a91a 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.FieldAccessInfo;
import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Timing;
@@ -30,12 +31,17 @@
class FieldNameMinifier {
private final AppView<AppInfoWithLiveness> appView;
+ private final SubtypingInfo subtypingInfo;
private final Map<DexField, DexString> renaming = new IdentityHashMap<>();
private Map<DexType, ReservedFieldNamingState> reservedNamingStates = new IdentityHashMap<>();
private final MemberNamingStrategy strategy;
- FieldNameMinifier(AppView<AppInfoWithLiveness> appView, MemberNamingStrategy strategy) {
+ FieldNameMinifier(
+ AppView<AppInfoWithLiveness> appView,
+ SubtypingInfo subtypingInfo,
+ MemberNamingStrategy strategy) {
this.appView = appView;
+ this.subtypingInfo = subtypingInfo;
this.strategy = strategy;
}
@@ -107,7 +113,7 @@
// For interfaces, propagate reserved names to all implementing classes.
if (clazz.isInterface() && reservedNames != null) {
for (DexType implementationType :
- appView.appInfo().allImmediateImplementsSubtypes(clazz.type)) {
+ subtypingInfo.allImmediateImplementsSubtypes(clazz.type)) {
DexClass implementation = appView.definitionFor(implementationType);
if (implementation != null) {
assert !implementation.isInterface();
@@ -135,7 +141,7 @@
}
private void propagateReservedFieldNamesUpwards() {
- BottomUpClassHierarchyTraversal.forProgramClasses(appView)
+ BottomUpClassHierarchyTraversal.forProgramClasses(appView, subtypingInfo)
.visit(
appView.appInfo().classes(),
clazz -> {
@@ -183,7 +189,7 @@
}
private void renameFieldsInInterfaces(Collection<DexClass> interfaces) {
- InterfacePartitioning partioning = new InterfacePartitioning(appView);
+ InterfacePartitioning partioning = new InterfacePartitioning(this);
for (Set<DexClass> partition : partioning.sortedPartitions(interfaces)) {
renameFieldsInInterfacePartition(partition);
}
@@ -216,8 +222,7 @@
Set<DexType> visited = Sets.newIdentityHashSet();
for (DexClass clazz : partition) {
- for (DexType implementationType :
- appView.appInfo().allImmediateImplementsSubtypes(clazz.type)) {
+ for (DexType implementationType : subtypingInfo.allImmediateImplementsSubtypes(clazz.type)) {
if (!visited.add(implementationType)) {
continue;
}
@@ -283,11 +288,13 @@
static class InterfacePartitioning {
+ private final FieldNameMinifier minfier;
private final AppView<AppInfoWithLiveness> appView;
private final Set<DexType> visited = Sets.newIdentityHashSet();
- InterfacePartitioning(AppView<AppInfoWithLiveness> appView) {
- this.appView = appView;
+ InterfacePartitioning(FieldNameMinifier minifier) {
+ this.minfier = minifier;
+ appView = minifier.appView;
}
private List<Set<DexClass>> sortedPartitions(Collection<DexClass> interfaces) {
@@ -327,7 +334,7 @@
if (clazz.isInterface()) {
partition.add(clazz);
- for (DexType subtype : appView.appInfo().allImmediateSubtypes(type)) {
+ for (DexType subtype : minfier.subtypingInfo.allImmediateSubtypes(type)) {
if (visited.add(subtype)) {
worklist.add(subtype);
}
@@ -336,7 +343,7 @@
if (visited.add(clazz.superType)) {
worklist.add(clazz.superType);
}
- for (DexType subclass : appView.appInfo().allImmediateExtendsSubtypes(type)) {
+ for (DexType subclass : minfier.subtypingInfo.allImmediateExtendsSubtypes(type)) {
if (visited.add(subclass)) {
worklist.add(subclass);
}
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 9f72270..cdb9166 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
@@ -115,6 +116,7 @@
}
private final AppView<AppInfoWithLiveness> appView;
+ private final SubtypingInfo subtypingInfo;
private final MemberNamingStrategy strategy;
private final Map<DexMethod, DexString> renaming = new IdentityHashMap<>();
@@ -130,8 +132,12 @@
private final MethodNamingState<?> rootNamingState;
private final MethodReservationState<?> rootReservationState;
- MethodNameMinifier(AppView<AppInfoWithLiveness> appView, MemberNamingStrategy strategy) {
+ MethodNameMinifier(
+ AppView<AppInfoWithLiveness> appView,
+ SubtypingInfo subtypingInfo,
+ MemberNamingStrategy strategy) {
this.appView = appView;
+ this.subtypingInfo = subtypingInfo;
this.strategy = strategy;
rootReservationState = MethodReservationState.createRoot(getReservationKeyTransform());
rootNamingState =
@@ -209,7 +215,7 @@
assignNameToMethod(holder, method, namingState);
}
}
- for (DexType subType : appView.appInfo().allImmediateExtendsSubtypes(type)) {
+ for (DexType subType : subtypingInfo.allImmediateExtendsSubtypes(type)) {
assignNamesToClassesMethods(subType, namingState);
}
}
@@ -250,7 +256,7 @@
// frontier forward. This will ensure all reservations are put on the library or classpath
// frontier for the program path.
DexClass holder = appView.definitionFor(type);
- for (DexType subtype : appView.appInfo().allImmediateExtendsSubtypes(type)) {
+ for (DexType subtype : subtypingInfo.allImmediateExtendsSubtypes(type)) {
reserveNamesInClasses(
subtype,
holder == null || holder.isNotProgramClass() ? subtype : libraryFrontier,
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index f2d7c1a..d83a0f0 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
import com.android.tools.r8.naming.ClassNameMinifier.ClassNamingStrategy;
import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
@@ -45,6 +46,7 @@
public NamingLens run(ExecutorService executorService, Timing timing) throws ExecutionException {
assert appView.options().isMinifying();
+ SubtypingInfo subtypingInfo = appView.appInfo().computeSubtypingInfo();
timing.begin("ComputeInterfaces");
Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type));
interfaces.addAll(appView.appInfo().computeReachableInterfaces());
@@ -67,7 +69,8 @@
MemberNamingStrategy minifyMembers = new MinifierMemberNamingStrategy(appView);
timing.begin("MinifyMethods");
MethodRenaming methodRenaming =
- new MethodNameMinifier(appView, minifyMembers).computeRenaming(interfaces, timing);
+ new MethodNameMinifier(appView, subtypingInfo, minifyMembers)
+ .computeRenaming(interfaces, timing);
timing.end();
assert new MinifiedRenaming(appView, classRenaming, methodRenaming, FieldRenaming.empty())
@@ -75,7 +78,8 @@
timing.begin("MinifyFields");
FieldRenaming fieldRenaming =
- new FieldNameMinifier(appView, minifyMembers).computeRenaming(interfaces, timing);
+ new FieldNameMinifier(appView, subtypingInfo, minifyMembers)
+ .computeRenaming(interfaces, timing);
timing.end();
NamingLens lens = new MinifiedRenaming(appView, classRenaming, methodRenaming, fieldRenaming);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index f4c32c6..9a30e79 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
@@ -87,19 +88,20 @@
}
public NamingLens run(ExecutorService executorService, Timing timing) throws ExecutionException {
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ SubtypingInfo subtypingInfo = appInfo.computeSubtypingInfo();
ArrayDeque<Map<DexReference, MemberNaming>> nonPrivateMembers = new ArrayDeque<>();
Set<DexReference> notMappedReferences = new HashSet<>();
timing.begin("MappingInterfaces");
Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type));
- AppInfoWithLiveness appInfo = appView.appInfo();
Consumer<DexClass> consumer =
dexClass -> {
if (dexClass.isInterface()) {
// Only visit top level interfaces because computeMapping will visit the hierarchy.
if (dexClass.interfaces.isEmpty()) {
- computeMapping(dexClass.type, nonPrivateMembers, notMappedReferences);
+ computeMapping(dexClass.type, nonPrivateMembers, notMappedReferences, subtypingInfo);
}
interfaces.add(dexClass);
}
@@ -111,12 +113,12 @@
timing.end();
timing.begin("MappingClasses");
- appInfo.forAllImmediateExtendsSubtypes(
+ subtypingInfo.forAllImmediateExtendsSubtypes(
factory.objectType,
subType -> {
DexClass dexClass = appView.definitionFor(subType);
if (dexClass != null && !dexClass.isInterface()) {
- computeMapping(subType, nonPrivateMembers, notMappedReferences);
+ computeMapping(subType, nonPrivateMembers, notMappedReferences, subtypingInfo);
}
});
assert nonPrivateMembers.isEmpty();
@@ -148,7 +150,8 @@
new ApplyMappingMemberNamingStrategy(appView, memberNames);
timing.begin("MinifyMethods");
MethodRenaming methodRenaming =
- new MethodNameMinifier(appView, nameStrategy).computeRenaming(interfaces, timing);
+ new MethodNameMinifier(appView, subtypingInfo, nameStrategy)
+ .computeRenaming(interfaces, timing);
// Amend the method renamings with the default interface methods.
methodRenaming.renaming.putAll(defaultInterfaceMethodImplementationNames);
methodRenaming.renaming.putAll(additionalMethodNamings);
@@ -156,7 +159,8 @@
timing.begin("MinifyFields");
FieldRenaming fieldRenaming =
- new FieldNameMinifier(appView, nameStrategy).computeRenaming(interfaces, timing);
+ new FieldNameMinifier(appView, subtypingInfo, nameStrategy)
+ .computeRenaming(interfaces, timing);
fieldRenaming.renaming.putAll(additionalFieldNamings);
timing.end();
@@ -180,7 +184,8 @@
private void computeMapping(
DexType type,
Deque<Map<DexReference, MemberNaming>> buildUpNames,
- Set<DexReference> notMappedReferences) {
+ Set<DexReference> notMappedReferences,
+ SubtypingInfo subtypingInfo) {
ClassNamingForMapApplier classNaming = seedMapper.getClassNaming(type);
DexClass dexClass = appView.definitionFor(type);
@@ -253,16 +258,14 @@
if (nonPrivateMembers.size() > 0) {
buildUpNames.addLast(nonPrivateMembers);
- appView
- .appInfo()
- .forAllImmediateExtendsSubtypes(
- type, subType -> computeMapping(subType, buildUpNames, notMappedReferences));
+ subtypingInfo.forAllImmediateExtendsSubtypes(
+ type,
+ subType -> computeMapping(subType, buildUpNames, notMappedReferences, subtypingInfo));
buildUpNames.removeLast();
} else {
- appView
- .appInfo()
- .forAllImmediateExtendsSubtypes(
- type, subType -> computeMapping(subType, buildUpNames, notMappedReferences));
+ subtypingInfo.forAllImmediateExtendsSubtypes(
+ type,
+ subType -> computeMapping(subType, buildUpNames, notMappedReferences, subtypingInfo));
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index 6e88574..eef8234 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Reporter;
@@ -32,17 +31,16 @@
// TODO(b/129925954): Reimplement this by using the internal encoding and transformation logic.
public class GenericSignatureRewriter {
- private final AppView<AppInfoWithLiveness> appView;
+ private final AppView<?> appView;
private final Map<DexType, DexString> renaming;
private final InternalOptions options;
private final Reporter reporter;
- public GenericSignatureRewriter(AppView<AppInfoWithLiveness> appView) {
+ public GenericSignatureRewriter(AppView<?> appView) {
this(appView, Maps.newIdentityHashMap());
}
- public GenericSignatureRewriter(
- AppView<AppInfoWithLiveness> appView, Map<DexType, DexString> renaming) {
+ public GenericSignatureRewriter(AppView<?> appView, Map<DexType, DexString> renaming) {
this.appView = appView;
this.renaming = renaming;
this.options = appView.options();
@@ -90,8 +88,7 @@
options.warningInvalidSignature(
method, clazz.getOrigin(), signature, e))));
},
- executorService
- );
+ executorService);
}
private DexAnnotationSet rewriteGenericSignatures(
@@ -113,8 +110,10 @@
String signature = DexAnnotation.getSignature(annotation);
try {
parser.accept(signature);
+ String renamedSignature = collector.get();
+ assert verifyConsistentRenaming(parser, collector, renamedSignature);
DexAnnotation signatureAnnotation =
- DexAnnotation.createSignatureAnnotation(collector.get(), appView.dexItemFactory());
+ DexAnnotation.createSignatureAnnotation(renamedSignature, appView.dexItemFactory());
rewrittenAnnotations[i] = signatureAnnotation;
} catch (GenericSignatureFormatError e) {
parseError.accept(signature, e);
@@ -144,6 +143,21 @@
return new DexAnnotationSet(prunedAnnotations);
}
+ /**
+ * Calling this method will clobber the parsed signature in the collector - ideally with the same
+ * string. Only use this after the original result has been collected.
+ */
+ private boolean verifyConsistentRenaming(
+ Consumer<String> parser, Supplier<String> collector, String renamedSignature) {
+ if (!options.testing.assertConsistentRenamingOfSignature) {
+ return true;
+ }
+ parser.accept(renamedSignature);
+ String reRenamedSignature = collector.get();
+ assert renamedSignature.equals(reRenamedSignature);
+ return true;
+ }
+
private class GenericSignatureCollector implements GenericSignatureAction<DexType> {
private StringBuilder renamedSignature;
private final DexProgramClass currentClassContext;
@@ -187,7 +201,7 @@
String originalDescriptor = getDescriptorFromClassBinaryName(name);
DexType type =
appView.graphLense().lookupType(appView.dexItemFactory().createType(originalDescriptor));
- if (appView.appInfo().wasPruned(type)) {
+ if (appView.appInfo().hasLiveness() && appView.withLiveness().appInfo().wasPruned(type)) {
type = appView.dexItemFactory().objectType;
}
DexString renamedDescriptor = renaming.getOrDefault(type, type.descriptor);
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
index d3ca402..d4a5b7e 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.bridge.VirtualBridgeInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -61,9 +62,10 @@
}
public void run() {
- BottomUpClassHierarchyTraversal.forProgramClasses(appView)
+ SubtypingInfo subtypingInfo = appView.appInfo().computeSubtypingInfo();
+ BottomUpClassHierarchyTraversal.forProgramClasses(appView, subtypingInfo)
.excludeInterfaces()
- .visit(appView.appInfo().classes(), this::processClass);
+ .visit(appView.appInfo().classes(), clazz -> processClass(clazz, subtypingInfo));
if (!lensBuilder.isEmpty()) {
BridgeHoistingLens lens = lensBuilder.build(appView);
boolean changed = appView.setGraphLense(lens);
@@ -73,8 +75,8 @@
}
}
- private void processClass(DexProgramClass clazz) {
- Set<DexType> subtypes = appView.appInfo().allImmediateSubtypes(clazz.type);
+ private void processClass(DexProgramClass clazz, SubtypingInfo subtypingInfo) {
+ Set<DexType> subtypes = subtypingInfo.allImmediateSubtypes(clazz.type);
Set<DexProgramClass> subclasses = new TreeSet<>((x, y) -> x.type.slowCompareTo(y.type));
for (DexType subtype : subtypes) {
DexProgramClass subclass = asProgramClassOrNull(appView.definitionFor(subtype));
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index a52d044..8315925 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.ir.optimize.MethodPoolCollection;
import com.android.tools.r8.optimize.PublicizerLense.PublicizedLenseBuilder;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -29,17 +30,22 @@
private final DexApplication application;
private final AppView<AppInfoWithLiveness> appView;
+ private final SubtypingInfo subtypingInfo;
private final MethodPoolCollection methodPoolCollection;
private final PublicizedLenseBuilder lenseBuilder = PublicizerLense.createBuilder();
private ClassAndMemberPublicizer(
- DexApplication application, AppView<AppInfoWithLiveness> appView) {
+ DexApplication application,
+ AppView<AppInfoWithLiveness> appView,
+ SubtypingInfo subtypingInfo) {
this.application = application;
this.appView = appView;
+ this.subtypingInfo = subtypingInfo;
this.methodPoolCollection =
// We will add private instance methods when we promote them.
- new MethodPoolCollection(appView, MethodPoolCollection::excludesPrivateInstanceMethod);
+ new MethodPoolCollection(
+ appView, subtypingInfo, MethodPoolCollection::excludesPrivateInstanceMethod);
}
/**
@@ -52,9 +58,11 @@
ExecutorService executorService,
Timing timing,
DexApplication application,
- AppView<AppInfoWithLiveness> appView)
+ AppView<AppInfoWithLiveness> appView,
+ SubtypingInfo subtypingInfo)
throws ExecutionException {
- return new ClassAndMemberPublicizer(application, appView).run(executorService, timing);
+ return new ClassAndMemberPublicizer(application, appView, subtypingInfo)
+ .run(executorService, timing);
}
private GraphLense run(ExecutorService executorService, Timing timing)
@@ -102,7 +110,7 @@
}
}
- appView.appInfo().forAllImmediateExtendsSubtypes(type, this::publicizeType);
+ subtypingInfo.forAllImmediateExtendsSubtypes(type, this::publicizeType);
}
private boolean publicizeMethod(DexClass holder, DexEncodedMethod encodedMethod) {
diff --git a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
index fdd4da0..1fcb9e0 100644
--- a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
@@ -3,9 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
import java.util.ArrayList;
@@ -22,20 +24,22 @@
*/
public class AbstractMethodRemover {
- private final AppInfoWithLiveness appInfo;
+ private final AppView<AppInfoWithLiveness> appView;
+ private final SubtypingInfo subtypingInfo;
private ScopedDexMethodSet scope = new ScopedDexMethodSet();
- public AbstractMethodRemover(AppInfoWithLiveness appInfo) {
- this.appInfo = appInfo;
+ public AbstractMethodRemover(AppView<AppInfoWithLiveness> appView, SubtypingInfo subtypingInfo) {
+ this.appView = appView;
+ this.subtypingInfo = subtypingInfo;
}
public void run() {
assert scope.getParent() == null;
- processClass(appInfo.dexItemFactory().objectType);
+ processClass(appView.dexItemFactory().objectType);
}
private void processClass(DexType type) {
- DexClass holder = appInfo.definitionFor(type);
+ DexClass holder = appView.definitionFor(type);
scope = scope.newNestedScope();
if (holder != null && holder.isProgramClass()) {
DexEncodedMethod[] newVirtualMethods = processMethods(holder.virtualMethods());
@@ -43,7 +47,8 @@
holder.setVirtualMethods(newVirtualMethods);
}
}
- appInfo.forAllImmediateExtendsSubtypes(type, this::processClass);
+ // TODO(b/154881041): Does this need the full subtype hierarchy of referenced types!?
+ subtypingInfo.forAllImmediateExtendsSubtypes(type, this::processClass);
scope = scope.getParent();
}
@@ -57,7 +62,7 @@
DexEncodedMethod method = virtualMethods.get(i);
if (scope.addMethodIfMoreVisible(method) != AddMethodIfMoreVisibleResult.NOT_ADDED
|| !method.accessFlags.isAbstract()
- || appInfo.isPinned(method.method)) {
+ || appView.appInfo().isPinned(method.method)) {
if (methods != null) {
methods.add(method);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index c252c04..0de5218 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -36,6 +36,7 @@
import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
@@ -677,46 +678,6 @@
return result;
}
- // TODO(b/139464956): Reimplement using only reachable types.
- public DexProgramClass getSingleDirectSubtype(DexProgramClass clazz) {
- DexType subtype = super.getSingleSubtype_(clazz.type);
- return subtype == null ? null : asProgramClassOrNull(definitionFor(subtype));
- }
-
- /**
- * Apply the given function to all classes that directly extend this class.
- *
- * <p>If this class is an interface, then this method will visit all sub-interfaces. This deviates
- * from the dex-file encoding, where subinterfaces "implement" their super interfaces. However, it
- * is consistent with the source language.
- */
- // TODO(b/139464956): Reimplement using only reachable types.
- public void forAllImmediateExtendsSubtypes(DexType type, Consumer<DexType> f) {
- allImmediateExtendsSubtypes(type).forEach(f);
- }
-
- // TODO(b/139464956): Reimplement using only reachable types.
- public Iterable<DexType> allImmediateExtendsSubtypes(DexType type) {
- return super.allImmediateExtendsSubtypes_(type);
- }
-
- /**
- * Apply the given function to all classes that directly implement this interface.
- *
- * <p>The implementation does not consider how the hierarchy is encoded in the dex file, where
- * interfaces "implement" their super interfaces. Instead it takes the view of the source
- * language, where interfaces "extend" their superinterface.
- */
- // TODO(b/139464956): Reimplement using only reachable types.
- public void forAllImmediateImplementsSubtypes(DexType type, Consumer<DexType> f) {
- allImmediateImplementsSubtypes(type).forEach(f);
- }
-
- // TODO(b/139464956): Reimplement using only reachable types.
- public Iterable<DexType> allImmediateImplementsSubtypes(DexType type) {
- return super.allImmediateImplementsSubtypes_(type);
- }
-
/**
* Const-classes is a conservative set of types that may be lock-candidates and cannot be merged.
* When using synchronized blocks, we cannot ensure that const-class locks will not flow in. This
@@ -1207,6 +1168,11 @@
DexClass refinedLowerBoundClass = definitionFor(receiverLowerBoundType.getClassType());
if (refinedLowerBoundClass != null) {
refinedLowerBound = refinedLowerBoundClass.asProgramClass();
+ // TODO(b/154822960): Check if the lower bound is a subtype of the upper bound.
+ if (refinedLowerBound != null && !isSubtype(refinedLowerBound.type, refinedReceiverType)) {
+ // We cannot trust the lower bound, so null it out.
+ refinedLowerBound = null;
+ }
}
}
LookupResultSuccess lookupResult =
@@ -1386,6 +1352,10 @@
}
}
+ public SubtypingInfo computeSubtypingInfo() {
+ return new SubtypingInfo(app().asDirect().allClasses(), this);
+ }
+
public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(ClassTypeElement type) {
// Special case for java.lang.Object.
if (type.getClassType() == dexItemFactory().objectType) {
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 1319195..4fb2fe2 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -65,6 +65,7 @@
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.ResolutionResult.FailedResolutionResult;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
import com.android.tools.r8.graph.analysis.DesugaredLibraryConversionWrapperAnalysis;
import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
@@ -178,6 +179,7 @@
// Don't hold a direct pointer to app info (use appView).
private AppInfoWithSubtyping appInfo;
private final AppView<AppInfoWithSubtyping> appView;
+ private final SubtypingInfo subtypingInfo;
private final InternalOptions options;
private RootSet rootSet;
private ProguardClassFilter dontWarnPatterns;
@@ -349,6 +351,8 @@
InternalOptions options = appView.options();
this.appInfo = appView.appInfo();
this.appView = appView.withSubtyping();
+ this.subtypingInfo =
+ new SubtypingInfo(appView.appInfo().app().asDirect().allClasses(), appView);
this.forceProguardCompatibility = options.forceProguardCompatibility;
this.graphReporter = new GraphReporter(appView, keptGraphConsumer);
this.mode = mode;
@@ -3127,6 +3131,7 @@
IfRuleEvaluator ifRuleEvaluator =
new IfRuleEvaluator(
appView,
+ subtypingInfo,
this,
executorService,
activeIfRules,
@@ -3539,7 +3544,8 @@
// This is using appView.definitionFor() to avoid that we report reflectively accessed types
// as missing.
DexProgramClass clazz =
- asProgramClassOrNull(appView.definitionFor(identifierItem.asDexType()));
+ asProgramClassOrNull(
+ appInfo().definitionForWithoutExistenceAssert(identifierItem.asDexType()));
if (clazz == null) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index fe81bb7..225aff0 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
import com.android.tools.r8.utils.InternalOptions.TestingOptions.ProguardIfRuleEvaluationData;
import com.android.tools.r8.utils.ThreadUtils;
@@ -36,6 +37,7 @@
public class IfRuleEvaluator {
private final AppView<? extends AppInfoWithSubtyping> appView;
+ private final SubtypingInfo subtypingInfo;
private final Enqueuer enqueuer;
private final ExecutorService executorService;
private final List<Future<?>> futures = new ArrayList<>();
@@ -44,11 +46,13 @@
IfRuleEvaluator(
AppView<? extends AppInfoWithSubtyping> appView,
+ SubtypingInfo subtypingInfo,
Enqueuer enqueuer,
ExecutorService executorService,
Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRules,
ConsequentRootSetBuilder rootSetBuilder) {
this.appView = appView;
+ this.subtypingInfo = subtypingInfo;
this.enqueuer = enqueuer;
this.executorService = executorService;
this.ifRules = ifRules;
@@ -71,7 +75,8 @@
// -keep rule may vary (due to back references). So, we need to try all pairs of -if
// rule and live types.
for (DexProgramClass clazz :
- ifRule.relevantCandidatesForRule(appView, appView.appInfo().classes())) {
+ ifRule.relevantCandidatesForRule(
+ appView, subtypingInfo, appView.appInfo().classes())) {
if (!isEffectivelyLive(clazz)) {
continue;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index b92f82e..b46840b 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -121,7 +121,8 @@
}
private boolean isEnum(DexType valueType) {
- return appInfo.isSubtype(valueType, appInfo.dexItemFactory().enumType);
+ return valueType.isClassType()
+ && appInfo.isSubtype(valueType, appInfo.dexItemFactory().enumType);
}
private boolean isAnnotation(DexType valueType) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index d59c63e..edb901b 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
import com.android.tools.r8.utils.StringUtils;
@@ -82,7 +83,9 @@
}
Iterable<DexProgramClass> relevantCandidatesForRule(
- AppView<? extends AppInfoWithSubtyping> appView, Iterable<DexProgramClass> defaultValue) {
+ AppView<? extends AppInfoWithSubtyping> appView,
+ SubtypingInfo subtypingInfo,
+ Iterable<DexProgramClass> defaultValue) {
if (hasInheritanceClassName() && getInheritanceClassName().hasSpecificType()) {
DexType type = getInheritanceClassName().getSpecificType();
if (appView.verticallyMergedClasses() != null
@@ -92,9 +95,9 @@
assert clazz != null && clazz.isProgramClass();
return Iterables.concat(
ImmutableList.of(clazz.asProgramClass()),
- DexProgramClass.asProgramClasses(appView.appInfo().subtypes(type), appView));
+ DexProgramClass.asProgramClasses(subtypingInfo.subtypes(type), appView));
} else {
- return DexProgramClass.asProgramClasses(appView.appInfo().subtypes(type), appView);
+ return DexProgramClass.asProgramClasses(subtypingInfo.subtypes(type), appView);
}
}
return defaultValue;
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index d8c8b07..2065def 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -30,6 +30,7 @@
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AnnotationMatchResult.AnnotationsIgnoredMatchResult;
@@ -77,6 +78,7 @@
public class RootSetBuilder {
private final AppView<? extends AppInfoWithSubtyping> appView;
+ private final SubtypingInfo subtypingInfo;
private final DirectMappedDexApplication application;
private final Iterable<? extends ProguardConfigurationRule> rules;
private final Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking = new IdentityHashMap<>();
@@ -117,15 +119,11 @@
Iterable<? extends ProguardConfigurationRule> rules) {
this.appView = appView;
this.application = application.asDirect();
+ this.subtypingInfo = new SubtypingInfo(this.application.allClasses(), this.application);
this.rules = rules;
this.options = appView.options();
}
- public RootSetBuilder(
- AppView<? extends AppInfoWithSubtyping> appView, Collection<ProguardIfRule> ifRules) {
- this(appView, appView.appInfo().app(), ifRules);
- }
-
public RootSetBuilder(AppView<? extends AppInfoWithSubtyping> appView) {
this(appView, appView.appInfo().app(), null);
}
@@ -182,7 +180,7 @@
if (!allRulesSatisfied(memberKeepRules, clazz)) {
break;
}
- // fallthrough;
+ // fall through;
case KEEP:
markClass(clazz, rule, ifRule);
preconditionSupplier = new HashMap<>();
@@ -275,7 +273,7 @@
executorService.submit(
() -> {
for (DexProgramClass clazz :
- rule.relevantCandidatesForRule(appView, application.classes())) {
+ rule.relevantCandidatesForRule(appView, subtypingInfo, application.classes())) {
process(clazz, rule, ifRule);
}
if (rule.applyToNonProgramClasses()) {
@@ -306,12 +304,13 @@
application.timing.end();
}
if (!noSideEffects.isEmpty() || !assumedValues.isEmpty()) {
- BottomUpClassHierarchyTraversal.forAllClasses(appView)
+ BottomUpClassHierarchyTraversal.forAllClasses(appView, subtypingInfo)
.visit(appView.appInfo().classes(), this::propagateAssumeRules);
}
if (appView.options().protoShrinking().enableGeneratedMessageLiteBuilderShrinking) {
GeneratedMessageLiteBuilderShrinker.addInliningHeuristicsForBuilderInlining(
appView,
+ subtypingInfo,
alwaysClassInline,
neverMerge,
alwaysInline,
@@ -349,7 +348,7 @@
}
private void propagateAssumeRules(DexClass clazz) {
- Set<DexType> subTypes = appView.appInfo().allImmediateSubtypes(clazz.type);
+ Set<DexType> subTypes = subtypingInfo.allImmediateSubtypes(clazz.type);
if (subTypes.isEmpty()) {
return;
}
@@ -615,7 +614,7 @@
Set<DexType> visited = new HashSet<>();
Deque<DexType> worklist = new ArrayDeque<>();
// Intentionally skip the current `clazz`, assuming it's covered by markMatchingVisibleMethods.
- worklist.addAll(appInfoWithSubtyping.allImmediateSubtypes(clazz.type));
+ worklist.addAll(subtypingInfo.allImmediateSubtypes(clazz.type));
while (!worklist.isEmpty()) {
DexType currentType = worklist.poll();
@@ -636,7 +635,7 @@
DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
markMethod(method, memberKeepRules, null, rule, precondition, ifRule);
});
- worklist.addAll(appInfoWithSubtyping.allImmediateSubtypes(currentClazz.type));
+ worklist.addAll(subtypingInfo.allImmediateSubtypes(currentClazz.type));
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 2fb7435..95fa85d 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.ir.code.Invoke.Type.DIRECT;
import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
@@ -33,6 +34,7 @@
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
@@ -186,6 +188,7 @@
private final DexApplication application;
private final AppInfoWithLiveness appInfo;
private final AppView<AppInfoWithLiveness> appView;
+ private final SubtypingInfo subtypingInfo;
private final ExecutorService executorService;
private final MethodPoolCollection methodPoolCollection;
private final Timing timing;
@@ -222,8 +225,9 @@
this.application = application;
this.appInfo = appView.appInfo();
this.appView = appView;
+ this.subtypingInfo = appInfo.computeSubtypingInfo();
this.executorService = executorService;
- this.methodPoolCollection = new MethodPoolCollection(appView);
+ this.methodPoolCollection = new MethodPoolCollection(appView, subtypingInfo);
this.renamedMembersLense = new VerticalClassMergerGraphLense.Builder(appView.dexItemFactory());
this.timing = timing;
this.mainDexClasses = mainDexClasses;
@@ -239,7 +243,11 @@
private void initializeMergeCandidates(Iterable<DexProgramClass> classes) {
for (DexProgramClass sourceClass : classes) {
- DexProgramClass targetClass = appInfo.getSingleDirectSubtype(sourceClass);
+ DexType singleSubtype = subtypingInfo.getSingleDirectSubtype(sourceClass.type);
+ if (singleSubtype == null) {
+ continue;
+ }
+ DexProgramClass targetClass = asProgramClassOrNull(appView.definitionFor(singleSubtype));
if (targetClass == null) {
continue;
}
@@ -532,8 +540,8 @@
public OverloadedMethodSignaturesRetriever() {
for (DexProgramClass mergeCandidate : mergeCandidates) {
- DexProgramClass candidate = appInfo.getSingleDirectSubtype(mergeCandidate);
- mergeeCandidates.add(candidate.type);
+ DexType singleSubtype = subtypingInfo.getSingleDirectSubtype(mergeCandidate.type);
+ mergeeCandidates.add(singleSubtype);
}
}
@@ -775,7 +783,8 @@
return;
}
- DexProgramClass targetClass = appInfo.getSingleDirectSubtype(clazz);
+ DexType singleSubtype = subtypingInfo.getSingleDirectSubtype(clazz.type);
+ DexProgramClass targetClass = appView.definitionFor(singleSubtype).asProgramClass();
assert isMergeCandidate(clazz, targetClass, pinnedTypes);
assert !mergedClasses.containsKey(targetClass.type);
diff --git a/src/main/java/com/android/tools/r8/utils/FlagFile.java b/src/main/java/com/android/tools/r8/utils/FlagFile.java
index 319580d..df999e0 100644
--- a/src/main/java/com/android/tools/r8/utils/FlagFile.java
+++ b/src/main/java/com/android/tools/r8/utils/FlagFile.java
@@ -4,7 +4,7 @@
package com.android.tools.r8.utils;
-import com.android.tools.r8.BaseCommand;
+import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.origin.Origin;
import java.io.IOException;
import java.nio.file.Files;
@@ -12,6 +12,7 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
public class FlagFile {
@@ -29,7 +30,7 @@
}
}
- public static String[] expandFlagFiles(String[] args, BaseCommand.Builder builder) {
+ public static String[] expandFlagFiles(String[] args, Consumer<Diagnostic> errorConsumer) {
List<String> flags = new ArrayList<>(args.length);
for (String arg : args) {
if (arg.startsWith("@")) {
@@ -38,7 +39,7 @@
flags.addAll(Files.readAllLines(flagFilePath));
} catch (IOException e) {
Origin origin = new FlagFileOrigin(flagFilePath);
- builder.error(new ExceptionDiagnostic(e, origin));
+ errorConsumer.accept(new ExceptionDiagnostic(e, origin));
}
} else {
flags.add(arg);
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index b58e9d6..3e82b0d 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1131,6 +1131,7 @@
System.getProperty("com.android.tools.r8.trackDesugaredAPIConversions") != null;
public boolean forceLibBackportsInL8CfToCf = false;
public boolean enumUnboxingRewriteJavaCGeneratedMethod = false;
+ public boolean assertConsistentRenamingOfSignature = false;
// TODO(b/144781417): This is disabled by default as some test apps appear to have such classes.
public boolean allowNonAbstractClassesWithAbstractMethods = true;
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
index 42cf72a..ff795bc 100644
--- a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
+++ b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
@@ -51,6 +51,7 @@
private final KotlinTargetVersion targetVersion;
private final List<Path> sources = new ArrayList<>();
private final List<Path> classpath = new ArrayList<>();
+ private boolean useJvmAssertions;
private Path output = null;
private KotlinCompilerTool(
@@ -126,6 +127,11 @@
return this;
}
+ public KotlinCompilerTool setUseJvmAssertions(boolean useJvmAssertions) {
+ this.useJvmAssertions = useJvmAssertions;
+ return this;
+ }
+
private Path getOrCreateOutputPath() throws IOException {
return output != null ? output : state.getNewTempFolder().resolve("out.jar");
}
@@ -150,6 +156,9 @@
cmdline.add("-cp");
cmdline.add(compiler.getPath().toString());
cmdline.add(ToolHelper.K2JVMCompiler);
+ if (useJvmAssertions) {
+ cmdline.add("-Xassertions=jvm");
+ }
cmdline.add("-jdk-home");
cmdline.add(jdk.getJavaHome().toString());
cmdline.add("-jvm-target");
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 743a898..f172bb12f 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1449,6 +1449,27 @@
return buildOnDexRuntime(parameters, Arrays.asList(paths));
}
+ public Path buildOnDexRuntime(TestParameters parameters, Class<?>... classes)
+ throws IOException, CompilationFailedException {
+ if (parameters.isDexRuntime()) {
+ return testForD8()
+ .addProgramClasses(classes)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
+ }
+ Path path = temp.newFolder().toPath().resolve("classes.jar");
+ ArchiveConsumer consumer = new ArchiveConsumer(path);
+ for (Class clazz : classes) {
+ consumer.accept(
+ ByteDataView.of(ToolHelper.getClassAsBytes(clazz)),
+ DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName()),
+ null);
+ }
+ consumer.finished(null);
+ return path;
+ }
+
public static String binaryName(Class<?> clazz) {
return DescriptorUtils.getBinaryNameFromJavaType(typeName(clazz));
}
diff --git a/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java b/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
index a083cdd..2c7d700 100644
--- a/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
+++ b/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
@@ -7,6 +7,8 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.StringUtils;
@@ -62,7 +64,11 @@
.addProgramClassFileData(getDowngradedClass(TestClass.class))
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(TestClass.class)
- .addKeepClassAndMembersRules(Runner.class)
+ // We cannot keep class Runner, as that prohibits getClass optimization.
+ // Instead disable minification and inlining of the Runner class and method.
+ .noMinification()
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED)
.inspect(
@@ -82,7 +88,11 @@
.addProgramClasses(TestClass.class)
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(TestClass.class)
- .addKeepClassAndMembersRules(Runner.class)
+ // We cannot keep class Runner, as that prohibits getClass optimization.
+ // Instead disable minification and inlining of the Runner class and method.
+ .noMinification()
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED)
.inspect(
@@ -117,8 +127,10 @@
return transformer(clazz).setVersion(version).transform();
}
+ @NeverClassInline
static class Runner {
+ @NeverInline
public void run() {
System.out.println(getClass().getName());
}
diff --git a/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java b/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java
new file mode 100644
index 0000000..81f1d09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2020, the R8 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.
+package com.android.tools.r8.cf;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MissingClassJoinsToObjectTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("A::foo");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public MissingClassJoinsToObjectTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private List<Path> getRuntimeClasspath() throws Exception {
+ return buildOnDexRuntime(parameters, ToolHelper.getClassFileForTestClass(B.class));
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(TestClass.class, A.class, B.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ R8TestRunResult result =
+ testForR8(parameters.getBackend())
+ .enableInliningAnnotations()
+ .addProgramClasses(TestClass.class, A.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .addRunClasspathFiles(getRuntimeClasspath())
+ .run(parameters.getRuntime(), TestClass.class);
+ if (parameters.isCfRuntime()) {
+ // TODO(b/154792347): The analysis of types in the presence of undefined is incomplete.
+ result.assertFailureWithErrorThatThrows(VerifyError.class);
+ } else {
+ result.assertSuccessWithOutput(EXPECTED);
+ }
+ }
+
+ static class A {
+ @NeverInline
+ public void foo() {
+ System.out.println("A::foo");
+ }
+ }
+
+ // Missing at compile time.
+ static class B extends A {
+ // Intentionally empty.
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ // Due to the missing class B, the join is assigned Object.
+ A join = args.length == 0 ? new A() : new B();
+ // The call to Object::foo fails.
+ join.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
index 523a018..646dbf9 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -28,7 +28,7 @@
import org.junit.Test;
abstract class AbstractBackportTest extends TestBase {
- private final TestParameters parameters;
+ protected final TestParameters parameters;
private final Class<?> targetClass;
private final Class<?> testClass;
private final Path testJar;
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ListBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/ListBackportJava9Test.java
index 03843cc0..0d55065 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/ListBackportJava9Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ListBackportJava9Test.java
@@ -5,13 +5,16 @@
package com.android.tools.r8.desugar.backports;
import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static org.hamcrest.CoreMatchers.containsString;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
+import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@@ -29,9 +32,10 @@
private static final Path TEST_JAR =
Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR).resolve("backport" + JAR_EXTENSION);
+ private static final String TEST_CLASS = "backport.ListBackportJava9Main";
public ListBackportJava9Test(TestParameters parameters) {
- super(parameters, List.class, TEST_JAR, "backport.ListBackportJava9Main");
+ super(parameters, List.class, TEST_JAR, TEST_CLASS);
// Note: None of the methods in this test exist in the latest android.jar. If/when they ship in
// an actual API level, migrate these tests to ListBackportTest.
@@ -41,4 +45,20 @@
ignoreInvokes("set");
ignoreInvokes("size");
}
+
+ @Test
+ public void desugaringApiLevelR() throws Exception {
+ // TODO(154759404): This test should start to fail when testing on an Android R VM.
+ if (parameters.getRuntime().isDex()
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.Q)) {
+ testForD8()
+ .setMinApi(AndroidApiLevel.R)
+ .addProgramClasses(MiniAssert.class, IgnoreInvokes.class)
+ .addProgramFiles(TEST_JAR)
+ .setIncludeClassesChecksum(true)
+ .compile()
+ .run(parameters.getRuntime(), TEST_CLASS)
+ .assertFailureWithErrorThatMatches(containsString("java.lang.NoSuchMethodError"));
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/MapBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/MapBackportJava9Test.java
index fb39b49..27cb824 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/MapBackportJava9Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/MapBackportJava9Test.java
@@ -4,18 +4,21 @@
package com.android.tools.r8.desugar.backports;
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static org.hamcrest.CoreMatchers.containsString;
+
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
+import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-
@RunWith(Parameterized.class)
public class MapBackportJava9Test extends AbstractBackportTest {
@Parameters(name = "{0}")
@@ -29,9 +32,10 @@
private static final Path TEST_JAR =
Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR).resolve("backport" + JAR_EXTENSION);
+ private static final String TEST_CLASS = "backport.MapBackportJava9Main";
public MapBackportJava9Test(TestParameters parameters) {
- super(parameters, Map.class, TEST_JAR, "backport.MapBackportJava9Main");
+ super(parameters, Map.class, TEST_JAR, TEST_CLASS);
// Note: None of the methods in this test exist in the latest android.jar. If/when they ship in
// an actual API level, migrate these tests to MapBackportTest.
@@ -41,4 +45,20 @@
ignoreInvokes("put");
ignoreInvokes("size");
}
+
+ @Test
+ public void desugaringApiLevelR() throws Exception {
+ // TODO(154759404): This test should start to fail when testing on an Android R VM.
+ if (parameters.getRuntime().isDex()
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.Q)) {
+ testForD8()
+ .setMinApi(AndroidApiLevel.R)
+ .addProgramClasses(MiniAssert.class, IgnoreInvokes.class)
+ .addProgramFiles(TEST_JAR)
+ .setIncludeClassesChecksum(true)
+ .compile()
+ .run(parameters.getRuntime(), TEST_CLASS)
+ .assertFailureWithErrorThatMatches(containsString("java.lang.NoSuchMethodError"));
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava9Test.java
index ef8b990..09aef1e 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava9Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava9Test.java
@@ -4,17 +4,20 @@
package com.android.tools.r8.desugar.backports;
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static org.hamcrest.CoreMatchers.containsString;
+
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
import java.nio.file.Path;
import java.nio.file.Paths;
+import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-
@RunWith(Parameterized.class)
public final class ObjectsBackportJava9Test extends AbstractBackportTest {
@Parameters(name = "{0}")
@@ -28,10 +31,27 @@
private static final Path TEST_JAR =
Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR).resolve("backport" + JAR_EXTENSION);
+ private static final String TEST_CLASS = "backport.ObjectsBackportJava9Main";
public ObjectsBackportJava9Test(TestParameters parameters) {
- super(parameters, Short.class, TEST_JAR, "backport.ObjectsBackportJava9Main");
+ super(parameters, Short.class, TEST_JAR, TEST_CLASS);
// Note: None of the methods in this test exist in the latest android.jar. If/when they ship in
// an actual API level, migrate these tests to ObjectsBackportTest.
}
+
+ @Test
+ public void desugaringApiLevelR() throws Exception {
+ // TODO(154759404): This test should start to fail when testing on an Android R VM.
+ if (parameters.getRuntime().isDex()
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.Q)) {
+ testForD8()
+ .setMinApi(AndroidApiLevel.R)
+ .addProgramClasses(MiniAssert.class, IgnoreInvokes.class)
+ .addProgramFiles(TEST_JAR)
+ .setIncludeClassesChecksum(true)
+ .compile()
+ .run(parameters.getRuntime(), TEST_CLASS)
+ .assertFailureWithErrorThatMatches(containsString("java.lang.NoSuchMethodError"));
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/SetBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/SetBackportJava9Test.java
index 6947ff2..408b203 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/SetBackportJava9Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/SetBackportJava9Test.java
@@ -4,18 +4,21 @@
package com.android.tools.r8.desugar.backports;
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static org.hamcrest.CoreMatchers.containsString;
+
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Set;
+import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-
@RunWith(Parameterized.class)
public class SetBackportJava9Test extends AbstractBackportTest {
@Parameters(name = "{0}")
@@ -29,9 +32,10 @@
private static final Path TEST_JAR =
Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR).resolve("backport" + JAR_EXTENSION);
+ private static final String TEST_CLASS = "backport.SetBackportJava9Main";
public SetBackportJava9Test(TestParameters parameters) {
- super(parameters, Set.class, TEST_JAR, "backport.SetBackportJava9Main");
+ super(parameters, Set.class, TEST_JAR, TEST_CLASS);
// Note: None of the methods in this test exist in the latest android.jar. If/when they ship in
// an actual API level, migrate these tests to SetBackportTest.
@@ -40,4 +44,20 @@
ignoreInvokes("contains");
ignoreInvokes("size");
}
+
+ @Test
+ public void desugaringApiLevelR() throws Exception {
+ // TODO(154759404): This test should start to fail when testing on an Android R VM.
+ if (parameters.getRuntime().isDex()
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.Q)) {
+ testForD8()
+ .setMinApi(AndroidApiLevel.R)
+ .addProgramClasses(MiniAssert.class, IgnoreInvokes.class)
+ .addProgramFiles(TEST_JAR)
+ .setIncludeClassesChecksum(true)
+ .compile()
+ .run(parameters.getRuntime(), TEST_CLASS)
+ .assertFailureWithErrorThatMatches(containsString("java.lang.NoSuchMethodError"));
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
index e6fdf99..8096c47 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
@@ -35,9 +35,6 @@
System.out.println("Skipping check for " + apiLevel);
continue;
}
- if (apiLevel == AndroidApiLevel.R) {
- continue;
- }
// Check that the backported methods for each API level are are not present in the
// android.jar for that level.
CodeInspector inspector = new CodeInspector(ToolHelper.getAndroidJar(apiLevel));
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
new file mode 100644
index 0000000..7cc0930
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
@@ -0,0 +1,129 @@
+// Copyright (c) 2020, the R8 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.
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AnnotationEnumUnboxingTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final KeepRule enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public AnnotationEnumUnboxingTest(
+ TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ Assume.assumeFalse(
+ "The methods values and valueOf are required for reflection.",
+ enumKeepRules.toString().equals("none"));
+ testForR8(parameters.getBackend())
+ .addInnerClasses(AnnotationEnumUnboxingTest.class)
+ .addKeepMainRule(Main.class)
+ .addKeepRules(enumKeepRules.getKeepRule())
+ .addKeepRules(
+ "-keep @interface"
+ + " com.android.tools.r8.enumunboxing."
+ + "AnnotationEnumUnboxingTest$ClassAnnotationDefault"
+ + " { }",
+ "-keepattributes *Annotation*")
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> {
+ assertEnumIsBoxed(MyEnumDefault.class, MyEnumDefault.class.getSimpleName(), m);
+ assertEnumIsBoxed(MyEnum.class, MyEnum.class.getSimpleName(), m);
+ assertEnumIsBoxed(
+ MyEnumArrayDefault.class, MyEnumArrayDefault.class.getSimpleName(), m);
+ assertEnumIsBoxed(MyEnumArray.class, MyEnumArray.class.getSimpleName(), m);
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("print", "1", "1", "1", "1", "1", "0", "0", "0");
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface ClassAnnotationDefault {
+ MyEnumDefault myEnumDefault() default MyEnumDefault.A;
+
+ MyEnum myEnum();
+
+ MyEnumArrayDefault[] myEnumArrayDefault() default {MyEnumArrayDefault.A, MyEnumArrayDefault.B};
+
+ MyEnumArray[] myEnumArray();
+ }
+
+ enum MyEnumDefault {
+ A,
+ B
+ }
+
+ enum MyEnum {
+ A,
+ B
+ }
+
+ enum MyEnumArrayDefault {
+ A,
+ B,
+ C
+ }
+
+ enum MyEnumArray {
+ A,
+ B,
+ C
+ }
+
+ @NeverClassInline
+ @ClassAnnotationDefault(
+ myEnum = MyEnum.B,
+ myEnumArray = {MyEnumArray.A, MyEnumArray.B})
+ private static class ClassDefault {
+ @NeverInline
+ void print() {
+ System.out.println("print");
+ }
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ new ClassDefault().print();
+ System.out.println(MyEnum.B.ordinal());
+ System.out.println(MyEnumDefault.B.ordinal());
+ System.out.println(MyEnumArray.B.ordinal());
+ System.out.println(MyEnumArrayDefault.B.ordinal());
+ ClassAnnotationDefault annotation =
+ (ClassAnnotationDefault) ClassDefault.class.getDeclaredAnnotations()[0];
+ System.out.println(annotation.myEnum().ordinal());
+ System.out.println(annotation.myEnumArray()[0].ordinal());
+ System.out.println(annotation.myEnumArrayDefault()[0].ordinal());
+ System.out.println(annotation.myEnumDefault().ordinal());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingReturnNullTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingReturnNullTest.java
new file mode 100644
index 0000000..2a1be5f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingReturnNullTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2020, the R8 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.
+
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EnumUnboxingReturnNullTest extends EnumUnboxingTestBase {
+
+ private static final Class<?> ENUM_CLASS = MyEnum.class;
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "print1", "true", "print2", "true", "print2", "false", "0", "print3", "true");
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final KeepRule enumKeepRules;
+
+ @Parameterized.Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public EnumUnboxingReturnNullTest(
+ TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ Class<?> classToTest = ReturnNull.class;
+ testForR8(parameters.getBackend())
+ .addProgramClasses(classToTest, ENUM_CLASS)
+ .addKeepMainRule(classToTest)
+ .addKeepRules(enumKeepRules.getKeepRule())
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
+ .run(parameters.getRuntime(), classToTest)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @NeverClassInline
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ static class ReturnNull {
+
+ public static void main(String[] args) {
+ MyEnum myEnum1 = printAndReturnNull();
+ System.out.println(myEnum1 == null);
+ MyEnum myEnum2 = printAndReturnMaybeNull(true);
+ System.out.println(myEnum2 == null);
+ MyEnum myEnum3 = printAndReturnMaybeNull(false);
+ System.out.println(myEnum3 == null);
+ System.out.println(MyEnum.A.ordinal());
+ MyEnum[] myEnums = printAndReturnNullArray();
+ System.out.println(myEnums == null);
+ }
+
+ @NeverInline
+ static MyEnum printAndReturnNull() {
+ System.out.println("print1");
+ return null;
+ }
+
+ @NeverInline
+ static MyEnum printAndReturnMaybeNull(boolean bool) {
+ System.out.println("print2");
+ if (bool) {
+ return null;
+ } else {
+ return MyEnum.B;
+ }
+ }
+
+ @NeverInline
+ static MyEnum[] printAndReturnNullArray() {
+ System.out.println("print3");
+ return null;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index 20ed6dc..183a37e 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -23,6 +23,8 @@
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
@@ -32,6 +34,7 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
public class R8GMSCoreLookupTest extends TestBase {
@@ -39,6 +42,7 @@
private static final String APP_DIR = "third_party/gmscore/v5/";
private DirectMappedDexApplication program;
private AppView<? extends AppInfoWithSubtyping> appView;
+ private SubtypingInfo subtypingInfo;
@Before
public void readGMSCore() throws Exception {
@@ -58,6 +62,7 @@
InternalOptions options = new InternalOptions();
appView = AppView.createForR8(new AppInfoWithSubtyping(program), options);
appView.setAppServices(AppServices.builder(appView).build());
+ subtypingInfo = new SubtypingInfo(program.allClasses(), program);
}
private AppInfoWithSubtyping appInfo() {
@@ -71,9 +76,10 @@
// Check lookup targets with include method.
ResolutionResult resolutionResult = appInfo().resolveMethodOnClass(clazz, method.method);
+ AppInfoWithLiveness appInfo = null; // TODO(b/154881041): Remove or compute liveness.
LookupResult lookupResult =
resolutionResult.lookupVirtualDispatchTargets(
- clazz, appInfo(), appInfo(), dexReference -> false);
+ clazz, appInfo(), appInfo, dexReference -> false);
assertTrue(lookupResult.isLookupResultSuccess());
assertTrue(lookupResult.asLookupResultSuccess().contains(method));
}
@@ -87,14 +93,15 @@
}
private void testInterfaceLookup(DexProgramClass clazz, DexEncodedMethod method) {
+ AppInfoWithLiveness appInfo = null; // TODO(b/154881041): Remove or compute liveness.
LookupResultSuccess lookupResult =
appInfo()
.resolveMethodOnInterface(clazz, method.method)
- .lookupVirtualDispatchTargets(clazz, appInfo(), appInfo(), dexReference -> false)
+ .lookupVirtualDispatchTargets(clazz, appInfo(), appInfo, dexReference -> false)
.asLookupResultSuccess();
assertNotNull(lookupResult);
assertFalse(lookupResult.hasLambdaTargets());
- if (appInfo().subtypes(method.holder()).stream()
+ if (subtypingInfo.subtypes(method.holder()).stream()
.allMatch(t -> appInfo().definitionFor(t).isInterface())) {
Counter counter = new Counter();
lookupResult.forEach(
@@ -132,6 +139,7 @@
}
@Test
+ @Ignore("b/154881041: Does this test add value? If so it needs to compute a liveness app-info")
public void testLookup() {
program.classesWithDeterministicOrder().forEach(this::testLookup);
}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index 5a8a80e..5d2814c 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -534,7 +534,6 @@
@Test
public void testSelfOrderWithoutSubtypingInfo() {
DexType type = factory.createType("Lmy/Type;");
- appView.withSubtyping().appInfo().registerNewTypeForTesting(type, factory.objectType);
TypeElement nonNullType = fromDexType(type, Nullability.definitelyNotNull(), appView);
ReferenceTypeElement nullableType =
nonNullType.asReferenceType().getOrCreateVariant(Nullability.maybeNull());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicUpperBoundWithEffectivelyFinalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicUpperBoundWithEffectivelyFinalTest.java
new file mode 100644
index 0000000..5393409
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicUpperBoundWithEffectivelyFinalTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2020, the R8 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.
+
+package com.android.tools.r8.ir.optimize.dynamictype;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DynamicUpperBoundWithEffectivelyFinalTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public DynamicUpperBoundWithEffectivelyFinalTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws ExecutionException, CompilationFailedException, IOException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, Base.class, Final.class, Main.class)
+ .addKeepMainRule(Main.class)
+ .addKeepClassRules(Final.class)
+ .addKeepClassAndMembersRules(Base.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ @NeverClassInline
+ public static class A {
+
+ @Override
+ public String toString() {
+ return "Hello World!";
+ }
+ }
+
+ public abstract static class Base {
+
+ abstract A run();
+ }
+
+ @NeverClassInline
+ public static final class Final extends Base {
+
+ @Override
+ @NeverInline
+ A run() {
+ return new A();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(runFinal(new Final()));
+ }
+
+ @NeverInline
+ public static A runFinal(Final fin) {
+ return fin.run();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/singleton/SingletonFieldValuePropagationEnumTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/singleton/SingletonFieldValuePropagationEnumTest.java
new file mode 100644
index 0000000..1463a01
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/singleton/SingletonFieldValuePropagationEnumTest.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2020, the R8 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.
+
+package com.android.tools.r8.ir.optimize.membervaluepropagation.fields.singleton;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SingletonFieldValuePropagationEnumTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SingletonFieldValuePropagationEnumTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(SingletonFieldValuePropagationEnumTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("A", "B", "C");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject charactersClassSubject = inspector.clazz(Characters.class);
+ assertThat(charactersClassSubject, isPresent());
+ // TODO(b/150368955): Field value propagation should cause Characters.value to become dead.
+ assertEquals(1, charactersClassSubject.allInstanceFields().size());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println((char) Characters.A.get());
+ System.out.println((char) Characters.B.get());
+ System.out.println((char) Characters.C.get());
+ }
+ }
+
+ enum Characters {
+ A(65),
+ B(66),
+ C(67);
+
+ int value;
+
+ Characters(int value) {
+ this.value = value;
+ }
+
+ int get() {
+ return value;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/singleton/SingletonFieldValuePropagationEnumWithSubclassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/singleton/SingletonFieldValuePropagationEnumWithSubclassTest.java
new file mode 100644
index 0000000..3989c55
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/singleton/SingletonFieldValuePropagationEnumWithSubclassTest.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2020, the R8 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.
+
+package com.android.tools.r8.ir.optimize.membervaluepropagation.fields.singleton;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SingletonFieldValuePropagationEnumWithSubclassTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SingletonFieldValuePropagationEnumWithSubclassTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(SingletonFieldValuePropagationEnumWithSubclassTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("A", "B", "C");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject charactersClassSubject = inspector.clazz(Characters.class);
+ assertThat(charactersClassSubject, isPresent());
+ // TODO(b/150368955): Field value propagation should cause Characters.value to become dead.
+ assertEquals(1, charactersClassSubject.allInstanceFields().size());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println((char) Characters.A.get());
+ System.out.println((char) Characters.B.get());
+ System.out.println((char) Characters.C.get());
+ }
+ }
+
+ enum Characters {
+ A(65) {
+ @Override
+ int get() {
+ return value;
+ }
+ },
+ B(66) {
+ @Override
+ int get() {
+ return value;
+ }
+ },
+ C(67) {
+ @Override
+ int get() {
+ return value;
+ }
+ };
+
+ int value;
+
+ Characters(int value) {
+ this.value = value;
+ }
+
+ int get() {
+ throw new RuntimeException();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/singleton/SingletonFieldValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/singleton/SingletonFieldValuePropagationTest.java
new file mode 100644
index 0000000..371844e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/singleton/SingletonFieldValuePropagationTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2020, the R8 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.
+
+package com.android.tools.r8.ir.optimize.membervaluepropagation.fields.singleton;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SingletonFieldValuePropagationTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SingletonFieldValuePropagationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(SingletonFieldValuePropagationTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("A", "B", "C");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject charactersClassSubject = inspector.clazz(Characters.class);
+ assertThat(charactersClassSubject, isPresent());
+ // TODO(b/150368955): Field value propagation should cause Characters.value to become dead.
+ assertEquals(1, charactersClassSubject.allInstanceFields().size());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println((char) Characters.getA().get());
+ System.out.println((char) Characters.getB().get());
+ System.out.println((char) Characters.getC().get());
+ }
+ }
+
+ static class Characters {
+
+ static Characters INSTANCE_A = new Characters(65);
+ static Characters INSTANCE_B = new Characters(66);
+ static Characters INSTANCE_C = new Characters(67);
+
+ int value;
+
+ Characters(int value) {
+ this.value = value;
+ }
+
+ static Characters getA() {
+ return INSTANCE_A;
+ }
+
+ static Characters getB() {
+ return INSTANCE_B;
+ }
+
+ static Characters getC() {
+ return INSTANCE_C;
+ }
+
+ int get() {
+ return value;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassBaseAndSubTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassBaseAndSubTest.java
new file mode 100644
index 0000000..b5daa52
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassBaseAndSubTest.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2020, the R8 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.
+package com.android.tools.r8.ir.optimize.reflection;
+
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class GetClassBaseAndSubTest extends TestBase {
+
+ static final String EXPECTED =
+ StringUtils.lines("class " + Base.class.getTypeName(), "class " + Sub.class.getTypeName());
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public GetClassBaseAndSubTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(GetClassBaseAndSubTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .noMinification()
+ .addInnerClasses(GetClassBaseAndSubTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(
+ inspector ->
+ assertFalse(
+ inspector
+ .clazz(TestClass.class)
+ .mainMethod()
+ .streamInstructions()
+ .anyMatch(InstructionSubject::isConstClass)));
+ }
+
+ static class Base {}
+
+ static class Sub extends Base {}
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Base baseWithBase = System.currentTimeMillis() > 0 ? new Base() : new Sub();
+ // Cannot be rewritten to const-class.
+ System.out.println(baseWithBase.getClass());
+ Base baseWithSub = System.currentTimeMillis() > 0 ? new Sub() : new Base();
+ // Cannot be rewritten to const-class.
+ System.out.println(baseWithSub.getClass());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassOnKeptClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassOnKeptClassTest.java
new file mode 100644
index 0000000..6e0e9e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassOnKeptClassTest.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2020, the R8 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.
+package com.android.tools.r8.ir.optimize.reflection;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.concurrent.Callable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class GetClassOnKeptClassTest extends TestBase {
+
+ static final String EXPECTED =
+ StringUtils.lines(
+ "class " + KeptClass.class.getTypeName(),
+ "class " + KeptClass.class.getTypeName(),
+ "class " + UnknownClass.class.getTypeName(),
+ "class " + UnknownClass.class.getTypeName());
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public GetClassOnKeptClassTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(KeptClass.class, UnknownClass.class, TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .enableInliningAnnotations()
+ .addProgramClasses(KeptClass.class, TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepClassRules(KeptClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .addRunClasspathFiles(buildOnDexRuntime(parameters, UnknownClass.class))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ static class KeptClass implements Callable<Class<?>> {
+ @NeverInline
+ static Class<?> getClassMethod(KeptClass instance) {
+ // Nullable argument. Should not be rewritten to const-class to preserve NPE.
+ return instance.getClass();
+ }
+
+ @NeverInline
+ @Override
+ public Class<?> call() {
+ // Non-null `this` pointer.
+ return getClass();
+ }
+ }
+
+ static class UnknownClass extends KeptClass {
+ // Empty subtype of KeptClass.
+ }
+
+ static class TestClass {
+
+ static KeptClass getInstance(int i) throws Exception {
+ return i == 0
+ ? new KeptClass()
+ : (KeptClass)
+ Class.forName(TestClass.class.getName().replace("TestClass", "UnknownClass"))
+ .getDeclaredConstructor()
+ .newInstance();
+ }
+
+ public static void main(String[] args) throws Exception {
+ for (int i = 0; i < 2; i++) {
+ KeptClass instance = getInstance(args.length + i);
+ System.out.println(instance.call());
+ System.out.println(KeptClass.getClassMethod(instance));
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
index c2fa7fb..7807a9f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
@@ -9,30 +9,34 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
-import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.ForceInline;
import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
+import java.util.List;
import java.util.concurrent.Callable;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-class GetClassTestMain implements Callable<Class<?>> {
+@RunWith(Parameterized.class)
+public class GetClassTest extends ReflectionOptimizerTestBase {
+
static class Base {}
+
static class Sub extends Base {}
+
static class EffectivelyFinal {}
static class Reflection implements Callable<Class<?>> {
+
@ForceInline
@Override
public Class<?> call() {
@@ -40,96 +44,106 @@
}
}
- @NeverInline
- static Class<?> getMainClass(GetClassTestMain instance) {
- // Nullable argument. Should not be rewritten to const-class to preserve NPE.
- return instance.getClass();
+ static class GetClassTestMain implements Callable<Class<?>> {
+
+ @NeverInline
+ static Class<?> getMainClass(GetClassTestMain instance) {
+ // Nullable argument. Should not be rewritten to const-class to preserve NPE.
+ return instance.getClass();
+ }
+
+ @NeverInline
+ @Override
+ public Class<?> call() {
+ // Non-null `this` pointer.
+ return getClass();
+ }
}
- @NeverInline
- @Override
- public Class<?> call() {
- // Non-null `this` pointer.
- return getClass();
- }
+ static class Main {
- public static void main(String[] args) {
- {
- Base base = new Base();
- // Not applicable in debug mode.
- System.out.println(base.getClass());
- // Can be rewritten to const-class always.
- System.out.println(new Base().getClass());
- }
+ public static void main(String[] args) {
+ {
+ Base base = new Base();
+ // Not applicable in debug mode.
+ System.out.println(base.getClass());
+ // Can be rewritten to const-class always.
+ System.out.println(new Base().getClass());
+ }
- {
- Base sub = new Sub();
- // Not applicable in debug mode.
- System.out.println(sub.getClass());
- }
+ {
+ Base sub = new Sub();
+ // Not applicable in debug mode.
+ System.out.println(sub.getClass());
+ }
- {
- Base[] subs = new Sub[1];
- // Not applicable in debug mode.
- System.out.println(subs.getClass());
- }
+ {
+ Base[] subs = new Sub[1];
+ // Not applicable in debug mode.
+ System.out.println(subs.getClass());
+ }
- {
- EffectivelyFinal ef = new EffectivelyFinal();
- // Not applicable in debug mode.
- System.out.println(ef.getClass());
- }
+ {
+ EffectivelyFinal ef = new EffectivelyFinal();
+ // Not applicable in debug mode.
+ System.out.println(ef.getClass());
+ }
- try {
- // To not be recognized as un-instantiated class.
- GetClassTestMain instance = new GetClassTestMain();
- System.out.println(instance.call());
- System.out.println(getMainClass(instance));
-
- System.out.println(getMainClass(null));
- throw new AssertionError("Should preserve NPE.");
- } catch (NullPointerException e) {
- // Expected
- }
-
- {
- Reflection r = new Reflection();
- // Not applicable in debug mode.
- System.out.println(r.getClass());
try {
- // Can be rewritten to const-class after inlining.
- System.out.println(r.call());
- } catch (Throwable e) {
- throw new AssertionError("Not expected any exceptions.");
+ // To not be recognized as un-instantiated class.
+ GetClassTestMain instance = new GetClassTestMain();
+ System.out.println(instance.call());
+ System.out.println(GetClassTestMain.getMainClass(instance));
+
+ System.out.println(GetClassTestMain.getMainClass(null));
+ throw new AssertionError("Should preserve NPE.");
+ } catch (NullPointerException e) {
+ // Expected
+ }
+
+ {
+ Reflection r = new Reflection();
+ // Not applicable in debug mode.
+ System.out.println(r.getClass());
+ try {
+ // Can be rewritten to const-class after inlining.
+ System.out.println(r.call());
+ } catch (Throwable e) {
+ throw new AssertionError("Not expected any exceptions.");
+ }
}
}
}
-}
-@RunWith(Parameterized.class)
-public class GetClassTest extends ReflectionOptimizerTestBase {
- private static final String JAVA_OUTPUT = StringUtils.lines(
- "class com.android.tools.r8.ir.optimize.reflection.GetClassTestMain$Base",
- "class com.android.tools.r8.ir.optimize.reflection.GetClassTestMain$Base",
- "class com.android.tools.r8.ir.optimize.reflection.GetClassTestMain$Sub",
- "class [Lcom.android.tools.r8.ir.optimize.reflection.GetClassTestMain$Sub;",
- "class com.android.tools.r8.ir.optimize.reflection.GetClassTestMain$EffectivelyFinal",
- "class com.android.tools.r8.ir.optimize.reflection.GetClassTestMain",
- "class com.android.tools.r8.ir.optimize.reflection.GetClassTestMain",
- "class com.android.tools.r8.ir.optimize.reflection.GetClassTestMain$Reflection",
- "class com.android.tools.r8.ir.optimize.reflection.GetClassTestMain$Reflection"
- );
- private static final Class<?> MAIN = GetClassTestMain.class;
+ private static final String JAVA_OUTPUT =
+ StringUtils.lines(
+ ListUtils.map(
+ ImmutableList.of(
+ Base.class.getTypeName(),
+ Base.class.getTypeName(),
+ Sub.class.getTypeName(),
+ "[L" + Sub.class.getTypeName() + ";",
+ EffectivelyFinal.class.getTypeName(),
+ GetClassTestMain.class.getTypeName(),
+ GetClassTestMain.class.getTypeName(),
+ Reflection.class.getTypeName(),
+ Reflection.class.getTypeName()),
+ l -> "class " + l));
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ private static final Class<?> MAIN = Main.class;
+
+ @Parameterized.Parameters(name = "{0}, mode:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), CompilationMode.values());
}
private final TestParameters parameters;
+ private final CompilationMode mode;
- public GetClassTest(TestParameters parameters) {
+ public GetClassTest(TestParameters parameters, CompilationMode mode) {
this.parameters = parameters;
+ this.mode = mode;
}
private void configure(InternalOptions options) {
@@ -140,109 +154,75 @@
}
@Test
- public void testJVMOutput() throws Exception {
- assumeTrue("Only run JVM reference on CF runtimes", parameters.isCfRuntime());
+ public void testJVM() throws Exception {
+ assumeTrue(
+ "Only run JVM reference on CF runtimes",
+ parameters.isCfRuntime() && mode == CompilationMode.DEBUG);
testForJvm()
- .addTestClasspath()
+ .addInnerClasses(GetClassTest.class)
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
}
private void test(
- TestRunResult<?> result, boolean isR8, boolean isRelease) throws Exception {
- CodeInspector codeInspector = result.inspector();
+ CodeInspector codeInspector,
+ boolean expectCallPresent,
+ int expectedGetClassCount,
+ int expectedConstClassCount) {
ClassSubject mainClass = codeInspector.clazz(MAIN);
MethodSubject mainMethod = mainClass.mainMethod();
assertThat(mainMethod, isPresent());
- int expectedCount = isR8 ? (isRelease ? 0 : 5) : 6;
- assertEquals(expectedCount, countGetClass(mainMethod));
- expectedCount = isR8 ? (isRelease ? (parameters.isCfRuntime() ? 8 : 6) : 1) : 0;
- assertEquals(expectedCount, countConstClass(mainMethod));
+ assertEquals(expectedGetClassCount, countGetClass(mainMethod));
+ assertEquals(expectedConstClassCount, countConstClass(mainMethod));
- boolean expectToBeOptimized = isR8 && isRelease;
-
- MethodSubject getMainClass = mainClass.uniqueMethodWithName("getMainClass");
+ ClassSubject getterClass = codeInspector.clazz(GetClassTestMain.class);
+ MethodSubject getMainClass = getterClass.uniqueMethodWithName("getMainClass");
assertThat(getMainClass, isPresent());
// Because of nullable argument, getClass() should remain.
assertEquals(1, countGetClass(getMainClass));
assertEquals(0, countConstClass(getMainClass));
- MethodSubject call = mainClass.method("java.lang.Class", "call", ImmutableList.of());
- if (isR8 && isRelease) {
+ MethodSubject call = getterClass.method("java.lang.Class", "call", ImmutableList.of());
+ if (!expectCallPresent) {
assertThat(call, not(isPresent()));
} else {
assertThat(call, isPresent());
// Because of local, only R8 release mode can rewrite getClass() to const-class.
- assertEquals(expectToBeOptimized ? 0 : 1, countGetClass(call));
- assertEquals(expectToBeOptimized ? 1 : 0, countConstClass(call));
+ assertEquals(1, countGetClass(call));
+ assertEquals(0, countConstClass(call));
}
}
@Test
public void testD8() throws Exception {
assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
-
- // D8 debug.
- D8TestRunResult result =
- testForD8()
- .debug()
- .addProgramClassesAndInnerClasses(MAIN)
- .addOptionsModification(this::configure)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), MAIN)
- .assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, false, false);
-
- // D8 release.
- result =
- testForD8()
- .release()
- .addProgramClassesAndInnerClasses(MAIN)
- .addOptionsModification(this::configure)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), MAIN)
- .assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, false, true);
+ testForD8()
+ .setMode(mode)
+ .addInnerClasses(GetClassTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT)
+ .inspect(inspector -> test(inspector, true, 6, 0));
}
@Test
public void testR8() throws Exception {
- // R8 debug, no minification.
- R8TestRunResult result =
- testForR8(parameters.getBackend())
- .debug()
- .addProgramClassesAndInnerClasses(MAIN)
- .enableInliningAnnotations()
- .addKeepMainRule(MAIN)
- .noMinification()
- .addOptionsModification(this::configure)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), MAIN);
- test(result, true, false);
-
- // R8 release, no minification.
- result =
- testForR8(parameters.getBackend())
- .addProgramClassesAndInnerClasses(MAIN)
- .enableInliningAnnotations()
- .addKeepMainRule(MAIN)
- .noMinification()
- .addOptionsModification(this::configure)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), MAIN)
- .assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, true, true);
-
- // R8 release, minification.
- result =
- testForR8(parameters.getBackend())
- .addProgramClassesAndInnerClasses(MAIN)
- .enableInliningAnnotations()
- .addKeepMainRule(MAIN)
- .addOptionsModification(this::configure)
- .setMinApi(parameters.getApiLevel())
- // We are not checking output because it can't be matched due to minification. Just run.
- .run(parameters.getRuntime(), MAIN);
- test(result, true, true);
+ boolean isRelease = mode == CompilationMode.RELEASE;
+ boolean expectCallPresent = !isRelease;
+ int expectedGetClassCount = isRelease ? 0 : 5;
+ int expectedConstClassCount = isRelease ? (parameters.isCfRuntime() ? 8 : 6) : 1;
+ testForR8(parameters.getBackend())
+ .setMode(mode)
+ .addInnerClasses(GetClassTest.class)
+ .enableInliningAnnotations()
+ .addKeepMainRule(MAIN)
+ .noMinification()
+ .addOptionsModification(this::configure)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT)
+ .inspect(
+ inspector ->
+ test(inspector, expectCallPresent, expectedGetClassCount, expectedConstClassCount));
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
index 6d9c2d7..848bf1d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
@@ -21,6 +21,7 @@
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.Collection;
+import org.junit.Ignore;
import org.junit.Test;
class GetName0Class {
@@ -262,6 +263,7 @@
}
@Test
+ @Ignore("b/154813140: Invalidly assumes that getClass on kept classes can be optimized")
public void testR8_pinning() throws Exception {
// Pinning the test class.
R8TestRunResult result =
@@ -280,6 +282,7 @@
}
@Test
+ @Ignore("b/154813140: Invalidly assumes that getClass on kept classes can be optimized")
public void testR8_shallow_pinning() throws Exception {
// Shallow pinning the test class.
R8TestRunResult result =
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapWithMissingFieldTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapWithMissingFieldTest.java
index 796b3d9..707eb10 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapWithMissingFieldTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapWithMissingFieldTest.java
@@ -11,6 +11,8 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import org.junit.Test;
@@ -58,10 +60,11 @@
}
private static Path getSwitchMapProgramFile() throws IOException {
+ String switchMapFileName =
+ StringUtils.join(File.separator, getSwitchMapClassReference().getBinaryName().split("/"))
+ + ".class";
return getClassFilesForInnerClasses(SwitchMapWithMissingFieldTest.class).stream()
- .filter(
- file ->
- file.toString().endsWith(getSwitchMapClassReference().getBinaryName() + ".class"))
+ .filter(file -> file.toString().endsWith(switchMapFileName))
.findFirst()
.get();
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapWithUnexpectedFieldTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapWithUnexpectedFieldTest.java
index 0679ef6..167cd47 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapWithUnexpectedFieldTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapWithUnexpectedFieldTest.java
@@ -11,6 +11,8 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import org.junit.Test;
@@ -64,10 +66,11 @@
}
private static Path getSwitchMapProgramFile() throws IOException {
+ String switchMapFileName =
+ StringUtils.join(File.separator, getSwitchMapClassReference().getBinaryName().split("/"))
+ + ".class";
return getClassFilesForInnerClasses(SwitchMapWithUnexpectedFieldTest.class).stream()
- .filter(
- file ->
- file.toString().endsWith(getSwitchMapClassReference().getBinaryName() + ".class"))
+ .filter(file -> file.toString().endsWith(switchMapFileName))
.findFirst()
.get();
}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java b/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java
index 892b7d2..adf7ba7 100644
--- a/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java
@@ -4,59 +4,87 @@
package com.android.tools.r8.naming.signature;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+@RunWith(Parameterized.class)
public class GenericSignatureRenamingTest extends TestBase {
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public GenericSignatureRenamingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
@Test
public void testJVM() throws Exception {
- testForJvm().addTestClasspath().run(Main.class).assertSuccess();
- }
-
- @Test
- public void testR8Dex() throws Exception {
- test(testForR8(Backend.DEX));
- }
-
- @Test
- public void testR8CompatDex() throws Exception {
- test(testForR8Compat(Backend.DEX));
- }
-
- @Test
- public void testR8DexNoMinify() throws Exception {
- test(testForR8(Backend.DEX).addKeepRules("-dontobfuscate"));
- }
-
- @Test
- public void testR8Cf() throws Exception {
- test(testForR8(Backend.CF));
- }
-
- @Test
- public void testR8CfNoMinify() throws Exception {
- test(testForR8(Backend.CF).addKeepRules("-dontobfuscate"));
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm().addTestClasspath().run(parameters.getRuntime(), Main.class).assertSuccess();
}
@Test
public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
testForD8()
.addProgramClasses(Main.class)
.addProgramClassesAndInnerClasses(A.class, B.class, CY.class, CYY.class)
.setMode(CompilationMode.RELEASE)
+ .setMinApi(parameters.getApiLevel())
.compile()
.assertNoMessages()
- .run(Main.class)
+ .run(parameters.getRuntime(), Main.class)
.assertSuccess();
}
+ @Test
+ public void testR8() throws Exception {
+ test(testForR8(parameters.getBackend()));
+ }
+
+ @Test
+ public void testR8Compat() throws Exception {
+ test(testForR8Compat(parameters.getBackend()));
+ }
+
+ @Test
+ public void testR8NoMinify() throws Exception {
+ test(testForR8(parameters.getBackend()).addKeepRules("-dontobfuscate"));
+ }
+
+ @Test
+ public void testR8WithAssertEnabled() {
+ // TODO(b/154793333): Enable assertions always when resolved.
+ assertThrows(
+ AssertionError.class,
+ () -> {
+ test(
+ testForR8(parameters.getBackend())
+ .addKeepRules("-dontobfuscate")
+ .addOptionsModification(
+ internalOptions ->
+ internalOptions.testing.assertConsistentRenamingOfSignature = true));
+ });
+ }
+
private void test(R8TestBuilder<?> builder) throws Exception {
builder
.addKeepRules("-dontoptimize")
@@ -67,9 +95,10 @@
.addProgramClasses(Main.class)
.addProgramClassesAndInnerClasses(A.class, B.class, CY.class, CYY.class)
.setMode(CompilationMode.RELEASE)
+ .setMinApi(parameters.getApiLevel())
.compile()
.assertNoMessages()
- .run(Main.class)
+ .run(parameters.getRuntime(), Main.class)
.assertSuccess();
}
}
@@ -92,6 +121,15 @@
}
}
+ class GenericInner<S extends T> {
+
+ private S s;
+
+ public GenericInner(S s) {
+ this.s = s;
+ }
+ }
+
class Z extends Y {}
static class S {}
@@ -107,6 +145,10 @@
Y.ZZ newZZ() {
return new Y().zz();
}
+
+ public <S extends T> GenericInner<S> create(S s) {
+ return new GenericInner<>(s);
+ }
}
class B<T extends A<T>> {}
@@ -133,8 +175,11 @@
CYY cyy = new CYY();
A.S s = new A.S();
+ A<Object>.GenericInner<String> foo = new A<Object>().create("Foo");
+ Class<? extends A.GenericInner> aClass = foo.getClass();
+
// Check if names of Z and ZZ shows A as a superclass.
- Class classA = new A().getClass();
+ Class classA = A.class;
String nameA = classA.getName();
TypeVariable[] v = classA.getTypeParameters();
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
index c2fdaa1..873788c 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
@@ -26,12 +26,14 @@
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -45,41 +47,78 @@
@RunWith(Parameterized.class)
public class AssertionConfigurationKotlinTest extends KotlinTestBase implements Opcodes {
+ private static class KotlinCompilationKey {
+ KotlinTargetVersion targetVersion;
+ boolean useJvmAssertions;
+
+ private KotlinCompilationKey(KotlinTargetVersion targetVersion, boolean useJvmAssertions) {
+ this.targetVersion = targetVersion;
+ this.useJvmAssertions = useJvmAssertions;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(targetVersion, useJvmAssertions);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) {
+ return false;
+ }
+ if (getClass() != other.getClass()) {
+ return false;
+ }
+ KotlinCompilationKey kotlinCompilationKey = (KotlinCompilationKey) other;
+ return targetVersion == kotlinCompilationKey.targetVersion
+ && useJvmAssertions == kotlinCompilationKey.useJvmAssertions;
+ }
+ }
+
private static final Package pkg = AssertionConfigurationKotlinTest.class.getPackage();
private static final String kotlintestclasesPackage = pkg.getName() + ".kotlintestclasses";
private static final String testClassKt = kotlintestclasesPackage + ".TestClassKt";
private static final String class1 = kotlintestclasesPackage + ".Class1";
private static final String class2 = kotlintestclasesPackage + ".Class2";
- private static final Map<KotlinTargetVersion, Path> kotlinClasses = new HashMap<>();
+ private static final Map<KotlinCompilationKey, Path> kotlinClasses = new HashMap<>();
private final TestParameters parameters;
private final boolean kotlinStdlibAsLibrary;
+ private final boolean useJvmAssertions;
+ private final KotlinCompilationKey kotlinCompilationKey;
- @Parameterized.Parameters(name = "{0}, {1}, kotlin-stdlib as library: {2}")
+ @Parameterized.Parameters(name = "{0}, {1}, kotlin-stdlib as library: {2}, -Xassertions=jvm: {3}")
public static Collection<Object[]> data() {
return buildParameters(
getTestParameters().withAllRuntimesAndApiLevels().build(),
KotlinTargetVersion.values(),
+ BooleanUtils.values(),
BooleanUtils.values());
}
public AssertionConfigurationKotlinTest(
TestParameters parameters,
KotlinTargetVersion targetVersion,
- boolean kotlinStdlibAsClasspath) {
+ boolean kotlinStdlibAsClasspath,
+ boolean useJvmAssertions) {
super(targetVersion);
this.parameters = parameters;
this.kotlinStdlibAsLibrary = kotlinStdlibAsClasspath;
+ this.useJvmAssertions = useJvmAssertions;
+ this.kotlinCompilationKey = new KotlinCompilationKey(targetVersion, useJvmAssertions);
}
@BeforeClass
public static void compileKotlin() throws Exception {
for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
- Path ktClasses =
- kotlinc(KOTLINC, targetVersion)
- .addSourceFiles(getKotlinFilesInTestPackage(pkg))
- .compile();
- kotlinClasses.put(targetVersion, ktClasses);
+ for (boolean useJvmAssertions : BooleanUtils.values()) {
+ Path ktClasses =
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(getKotlinFilesInTestPackage(pkg))
+ .setUseJvmAssertions(useJvmAssertions)
+ .compile();
+ kotlinClasses.put(new KotlinCompilationKey(targetVersion, useJvmAssertions), ktClasses);
+ }
}
}
@@ -106,7 +145,7 @@
if (kotlinStdlibAsLibrary) {
testForD8()
.addClasspathFiles(ToolHelper.getKotlinStdlibJar())
- .addProgramFiles(kotlinClasses.get(targetVersion))
+ .addProgramFiles(kotlinClasses.get(kotlinCompilationKey))
.setMinApi(parameters.getApiLevel())
.apply(builderConsumer)
.addRunClasspathFiles(kotlinStdlibLibraryForRuntime())
@@ -118,7 +157,7 @@
} else {
testForD8()
.addProgramFiles(ToolHelper.getKotlinStdlibJar())
- .addProgramFiles(kotlinClasses.get(targetVersion))
+ .addProgramFiles(kotlinClasses.get(kotlinCompilationKey))
.setMinApi(parameters.getApiLevel())
.apply(builderConsumer)
.run(
@@ -147,7 +186,7 @@
if (kotlinStdlibAsLibrary) {
testForR8(parameters.getBackend())
.addClasspathFiles(ToolHelper.getKotlinStdlibJar())
- .addProgramFiles(kotlinClasses.get(targetVersion))
+ .addProgramFiles(kotlinClasses.get(kotlinCompilationKey))
.addKeepMainRule(testClassKt)
.addKeepClassAndMembersRules(class1, class2)
.setMinApi(parameters.getApiLevel())
@@ -162,7 +201,7 @@
} else {
testForR8(parameters.getBackend())
.addProgramFiles(ToolHelper.getKotlinStdlibJar())
- .addProgramFiles(kotlinClasses.get(targetVersion))
+ .addProgramFiles(kotlinClasses.get(kotlinCompilationKey))
.addKeepMainRule(testClassKt)
.addKeepClassAndMembersRules(class1, class2)
.setMinApi(parameters.getApiLevel())
@@ -225,7 +264,7 @@
assert !kotlinStdlibAsLibrary;
// With R8 the static-put of the kotlin._Assertions.INSTANCE field is removed as is not used.
assertEquals(
- (isR8 ? 1 : 2),
+ isR8 ? 1 : 2,
subject
.uniqueMethodWithName("<clinit>")
.streamInstructions()
@@ -252,12 +291,17 @@
private void checkAssertionCodeLeft(CodeInspector inspector, String clazz, boolean isR8) {
ClassSubject subject = inspector.clazz(clazz);
- assertThat(subject, isPresent());
- if (subject.getOriginalName().equals("kotlin._Assertions")) {
+ if (clazz.equals("kotlin._Assertions")) {
assert !kotlinStdlibAsLibrary;
+ if (isR8 && useJvmAssertions) {
+ // When JVM assertions are used the class kotlin._Assertions is unused.
+ assertThat(subject, not(isPresent()));
+ return;
+ }
+ assertThat(subject, isPresent());
// With R8 the static-put of the kotlin._Assertions.INSTANCE field is removed as is not used.
assertEquals(
- (isR8 ? 1 : 2),
+ isR8 ? 1 : 2,
subject
.uniqueMethodWithName("<clinit>")
.streamInstructions()
@@ -269,6 +313,13 @@
.streamInstructions()
.anyMatch(InstructionSubject::isConstNumber));
} else {
+ assertThat(subject, isPresent());
+ MethodSubject clinit = subject.uniqueMethodWithName("<clinit>");
+ if (useJvmAssertions) {
+ assertTrue(clinit.streamInstructions().anyMatch(InstructionSubject::isStaticPut));
+ } else {
+ assertThat(clinit, not(isPresent()));
+ }
assertTrue(
subject
.uniqueMethodWithName("m")
@@ -346,32 +397,90 @@
AssertionsConfiguration.Builder::enableAllAssertions),
inspector -> checkAssertionCodeEnabled(inspector, true),
allAssertionsExpectedLines());
- // Enabling for the "kotlin._Assertions" class should enable all.
- runD8Test(
- builder ->
- builder.addAssertionsConfiguration(
- b -> b.setEnable().setScopeClass("kotlin._Assertions").build()),
- inspector -> checkAssertionCodeEnabled(inspector, false),
- allAssertionsExpectedLines());
- runR8Test(
- builder ->
- builder.addAssertionsConfiguration(
- b -> b.setEnable().setScopeClass("kotlin._Assertions").build()),
- inspector -> checkAssertionCodeEnabled(inspector, true),
- allAssertionsExpectedLines());
- // Enabling for the "kotlin" package should enable all.
- runD8Test(
- builder ->
- builder.addAssertionsConfiguration(
- b -> b.setEnable().setScopePackage("kotlin").build()),
- inspector -> checkAssertionCodeEnabled(inspector, false),
- allAssertionsExpectedLines());
- runR8Test(
- builder ->
- builder.addAssertionsConfiguration(
- b -> b.setEnable().setScopePackage("kotlin").build()),
- inspector -> checkAssertionCodeEnabled(inspector, true),
- allAssertionsExpectedLines());
+ if (useJvmAssertions) {
+ // Enabling for the kotlin generated Java classes should enable all.
+ runD8Test(
+ builder ->
+ builder
+ .addAssertionsConfiguration(b -> b.setEnable().setScopeClass(class1).build())
+ .addAssertionsConfiguration(b -> b.setEnable().setScopeClass(class2).build()),
+ inspector -> {
+ // The default is applied to kotlin._Assertions (which for DEX is remove).
+ if (!kotlinStdlibAsLibrary) {
+ checkAssertionCodeRemoved(inspector, "kotlin._Assertions", false);
+ }
+ checkAssertionCodeEnabled(inspector, class1, false);
+ checkAssertionCodeEnabled(inspector, class2, false);
+ },
+ allAssertionsExpectedLines());
+ } else {
+ // Enabling for the class kotlin._Assertions should enable all.
+ runD8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ b -> b.setEnable().setScopeClass("kotlin._Assertions").build()),
+ inspector -> checkAssertionCodeEnabled(inspector, false),
+ allAssertionsExpectedLines());
+ }
+ if (useJvmAssertions) {
+ // Enabling for the kotlin generated Java classes should enable all.
+ runR8Test(
+ builder ->
+ builder
+ .addAssertionsConfiguration(b -> b.setEnable().setScopeClass(class1).build())
+ .addAssertionsConfiguration(b -> b.setEnable().setScopeClass(class2).build()),
+ inspector -> checkAssertionCodeEnabled(inspector, true),
+ allAssertionsExpectedLines());
+ } else {
+ // Enabling for the class kotlin._Assertions should enable all.
+ runR8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ b -> b.setEnable().setScopeClass("kotlin._Assertions").build()),
+ inspector -> checkAssertionCodeEnabled(inspector, true),
+ allAssertionsExpectedLines());
+ }
+ if (useJvmAssertions) {
+ // Enabling for the Java package for the kotlin test classes package should enable all.
+ runD8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ b -> b.setEnable().setScopePackage(kotlintestclasesPackage).build()),
+ inspector -> {
+ // The default is applied to kotlin._Assertions (which for DEX is remove).
+ if (!kotlinStdlibAsLibrary) {
+ checkAssertionCodeRemoved(inspector, "kotlin._Assertions", false);
+ }
+ checkAssertionCodeEnabled(inspector, class1, false);
+ checkAssertionCodeEnabled(inspector, class2, false);
+ },
+ allAssertionsExpectedLines());
+ } else {
+ // Enabling for the kotlin package (containing kotlin._Assertions) should enable all.
+ runD8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ b -> b.setEnable().setScopePackage("kotlin").build()),
+ inspector -> checkAssertionCodeEnabled(inspector, false),
+ allAssertionsExpectedLines());
+ }
+ if (useJvmAssertions) {
+ // Enabling for the Java package for the kotlin test classes package should enable all.
+ runR8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ b -> b.setEnable().setScopePackage(kotlintestclasesPackage).build()),
+ inspector -> checkAssertionCodeEnabled(inspector, true),
+ allAssertionsExpectedLines());
+ } else {
+ // Enabling for the kotlin package (containing kotlin._Assertions) should enable all.
+ runR8Test(
+ builder ->
+ builder.addAssertionsConfiguration(
+ b -> b.setEnable().setScopePackage("kotlin").build()),
+ inspector -> checkAssertionCodeEnabled(inspector, true),
+ allAssertionsExpectedLines());
+ }
}
@Test
@@ -425,7 +534,7 @@
Assume.assumeTrue(parameters.isDexRuntime());
testForD8()
.addProgramClassFileData(dumpModifiedKotlinAssertions())
- .addProgramFiles(kotlinClasses.get(targetVersion))
+ .addProgramFiles(kotlinClasses.get(kotlinCompilationKey))
.setMinApi(parameters.getApiLevel())
.addAssertionsConfiguration(AssertionsConfiguration.Builder::passthroughAllAssertions)
.run(
@@ -434,7 +543,7 @@
.assertSuccessWithOutputLines(noAllAssertionsExpectedLines());
testForD8()
.addProgramClassFileData(dumpModifiedKotlinAssertions())
- .addProgramFiles(kotlinClasses.get(targetVersion))
+ .addProgramFiles(kotlinClasses.get(kotlinCompilationKey))
.setMinApi(parameters.getApiLevel())
.addAssertionsConfiguration(AssertionsConfiguration.Builder::enableAllAssertions)
.run(
@@ -443,7 +552,7 @@
.assertSuccessWithOutputLines(allAssertionsExpectedLines());
testForD8()
.addProgramClassFileData(dumpModifiedKotlinAssertions())
- .addProgramFiles(kotlinClasses.get(targetVersion))
+ .addProgramFiles(kotlinClasses.get(kotlinCompilationKey))
.setMinApi(parameters.getApiLevel())
.addAssertionsConfiguration(AssertionsConfiguration.Builder::disableAllAssertions)
.run(
diff --git a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
index e64933b..35f67b2 100644
--- a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
@@ -8,10 +8,11 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
@@ -23,25 +24,27 @@
static final String APP_FILE_NAME = ToolHelper.EXAMPLES_BUILD_DIR + "shaking2/classes.dex";
private DirectMappedDexApplication program;
private DexItemFactory dexItemFactory;
- private AppInfoWithSubtyping appInfo;
+ private AppInfoWithClassHierarchy appInfo;
+ private SubtypingInfo subtypingInfo;
@Before
public void readApp() throws IOException, ExecutionException {
program = ToolHelper.buildApplication(ImmutableList.of(APP_FILE_NAME));
dexItemFactory = program.dexItemFactory;
- appInfo = new AppInfoWithSubtyping(program);
+ appInfo = new AppInfoWithClassHierarchy(program);
+ subtypingInfo = new SubtypingInfo(program.allClasses(), program);
}
private void validateSubtype(DexType super_type, DexType sub_type) {
assertFalse(super_type.equals(sub_type));
- assertTrue(appInfo.subtypes(super_type).contains(sub_type));
+ assertTrue(subtypingInfo.subtypes(super_type).contains(sub_type));
assertTrue(appInfo.isSubtype(sub_type, super_type));
- assertFalse(appInfo.subtypes(sub_type).contains(super_type));
+ assertFalse(subtypingInfo.subtypes(sub_type).contains(super_type));
assertFalse(appInfo.isSubtype(super_type, sub_type));
}
private void validateSubtypeSize(DexType type, int size) {
- assertEquals(size, appInfo.subtypes(type).size());
+ assertEquals(size, subtypingInfo.subtypes(type).size());
}
@Test
diff --git a/third_party/tachiyomi.tar.gz.sha1 b/third_party/tachiyomi.tar.gz.sha1
new file mode 100644
index 0000000..cf45571
--- /dev/null
+++ b/third_party/tachiyomi.tar.gz.sha1
@@ -0,0 +1 @@
+8ef393ca5ac25fb182071a274bf8f266753824b8
\ No newline at end of file
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 1687797..ad9f63f 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -22,9 +22,11 @@
import chrome_data
import r8_data
import iosched_data
+import tachiyomi_data
TYPES = ['dex', 'deploy', 'proguarded']
-APPS = ['gmscore', 'nest', 'youtube', 'gmail', 'chrome', 'r8', 'iosched']
+APPS = [
+ 'gmscore', 'nest', 'youtube', 'gmail', 'chrome', 'r8', 'iosched', 'tachiyomi']
COMPILERS = ['d8', 'r8']
COMPILER_BUILDS = ['full', 'lib']
@@ -207,6 +209,7 @@
'gmail': gmail_data,
'r8': r8_data,
'iosched': iosched_data,
+ 'tachiyomi': tachiyomi_data
}
# Check to ensure that we add all variants here.
assert len(APPS) == len(data_providers)
@@ -388,6 +391,9 @@
elif options.app == 'iosched':
version = options.version or '2019'
data = iosched_data
+ elif options.app == 'tachiyomi':
+ version = options.version or 'b15d2fe16864645055af6a745a62cc5566629798'
+ data = tachiyomi_data
else:
raise Exception("You need to specify '--app={}'".format('|'.join(APPS)))
return version, data
@@ -480,7 +486,8 @@
or options.app == 'chrome'
or options.app == 'nest'
or options.app == 'r8'
- or options.app == 'iosched'):
+ or options.app == 'iosched'
+ or options.app == 'tachiyomi'):
inputs = values['inputs']
args.extend(['--output', outdir])
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 6d7905e..94ef0dc 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -704,7 +704,15 @@
app.name,
shrinker,
' for recompilation' if keepRuleSynthesisForRecompilation else ''))
-
+ print('To compile locally: '
+ 'tools/run_on_as_app.py --shrinker {} --r8-compilation-steps {} '
+ '--app {} {}'.format(
+ shrinker,
+ options.r8_compilation_steps,
+ app.name,
+ '--r8-compilation-steps-only'
+ if options.r8_compilation_steps_only else ''))
+ print('HINT: use --shrinker r8-nolib --no-build if you have a local R8.jar')
# Add settings.gradle file if it is not present to prevent gradle from finding
# the settings.gradle file in the r8 root when apps are placed under
# $R8/build.
diff --git a/tools/tachiyomi_data.py b/tools/tachiyomi_data.py
new file mode 100644
index 0000000..5518cea
--- /dev/null
+++ b/tools/tachiyomi_data.py
@@ -0,0 +1,18 @@
+# Copyright (c) 2020, the R8 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 os
+import utils
+
+BASE = os.path.join(utils.THIRD_PARTY, 'tachiyomi')
+
+VERSIONS = {
+ 'b15d2fe16864645055af6a745a62cc5566629798': {
+ 'deploy' : {
+ 'inputs': [os.path.join(BASE, 'program.jar')],
+ 'pgconf': [os.path.join(BASE, 'proguard.config')],
+ 'libraries': [os.path.join(BASE, 'library.jar')],
+ },
+ },
+}