Merge commit '42f9d5566fd9a449f3207f5f288394f3f0904267' into dev-release
diff --git a/.gitignore b/.gitignore
index 424a67a..080826a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -131,6 +131,12 @@
third_party/openjdk/desugar_jdk_libs_releases/1.1.1.tar.gz
third_party/openjdk/desugar_jdk_libs_releases/1.1.5
third_party/openjdk/desugar_jdk_libs_releases/1.1.5.tar.gz
+third_party/openjdk/jdk-17/linux
+third_party/openjdk/jdk-17/linux.tar.gz
+third_party/openjdk/jdk-17/osx
+third_party/openjdk/jdk-17/osx.tar.gz
+third_party/openjdk/jdk-17/windows
+third_party/openjdk/jdk-17/windows.tar.gz
third_party/openjdk/jdk-16/linux
third_party/openjdk/jdk-16/linux.tar.gz
third_party/openjdk/jdk-16/osx
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index c3902b8..8fbb76e 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -3,6 +3,7 @@
# BSD-style license that can be found in the LICENSE file.
from os import path
+import datetime
from subprocess import check_output, Popen, PIPE, STDOUT
FMT_CMD = path.join(
@@ -78,14 +79,15 @@
continue
if not CopyRightInContents(f, contents):
results.append(
- output_api.PresubmitError('Could not find Copyright in file: %s' % f))
+ output_api.PresubmitError('Could not find correctly formatted '
+ 'copyright in file: %s' % f))
return results
def CopyRightInContents(f, contents):
expected = '//'
if f.LocalPath().endswith('.py') or f.LocalPath().endswith('.sh'):
expected = '#'
- expected = expected + ' Copyright'
+ expected = expected + ' Copyright (c) ' + str(datetime.datetime.now().year)
for content_line in contents:
if expected in content_line:
return True
diff --git a/build.gradle b/build.gradle
index 3321e41..e83113a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -131,9 +131,9 @@
srcDirs = ['src/test/examplesJava11']
}
}
- examplesJava16 {
+ examplesJava17 {
java {
- srcDirs = ['src/test/examplesJava16']
+ srcDirs = ['src/test/examplesJava17']
}
}
jdk11TimeTests {
@@ -374,18 +374,18 @@
"third_party": ["openjdk/openjdk-9.0.4/linux",
"openjdk/jdk8/linux-x86",
"openjdk/jdk-11/linux",
- "openjdk/jdk-16/linux"],
+ "openjdk/jdk-17/linux"],
],
osx: [
"third_party": ["openjdk/openjdk-9.0.4/osx",
"openjdk/jdk8/darwin-x86",
"openjdk/jdk-11/osx",
- "openjdk/jdk-16/osx"],
+ "openjdk/jdk-17/osx"],
],
windows: [
"third_party": ["openjdk/openjdk-9.0.4/windows",
"openjdk/jdk-11/windows",
- "openjdk/jdk-16/windows"],
+ "openjdk/jdk-17/windows"],
],
]
@@ -624,10 +624,10 @@
JavaVersion.VERSION_11,
false)
setJdkCompilationWithCompatibility(
- sourceSets.examplesJava16.compileJavaTaskName,
- 'jdk-16',
- JavaVersion.VERSION_16,
- true)
+ sourceSets.examplesJava17.compileJavaTaskName,
+ 'jdk-17',
+ JavaVersion.VERSION_17,
+ false)
task compileMainWithJava11 (type: JavaCompile) {
dependsOn downloadDeps
@@ -1618,7 +1618,7 @@
buildExampleJarsCreateTask("Java9", sourceSets.examplesJava9)
buildExampleJarsCreateTask("Java10", sourceSets.examplesJava10)
buildExampleJarsCreateTask("Java11", sourceSets.examplesJava11)
-buildExampleJarsCreateTask("Java16", sourceSets.examplesJava16)
+buildExampleJarsCreateTask("Java17", sourceSets.examplesJava17)
task provideArtFrameworksDependencies {
cloudDependencies.tools.forEach({ art ->
@@ -1701,7 +1701,7 @@
dependsOn buildExampleJava9Jars
dependsOn buildExampleJava10Jars
dependsOn buildExampleJava11Jars
- dependsOn buildExampleJava16Jars
+ dependsOn buildExampleJava17Jars
dependsOn buildExampleAndroidApi
def examplesDir = file("src/test/examples")
def noDexTests = [
diff --git a/scripts/add-openjdk.sh b/scripts/add-openjdk.sh
old mode 100644
new mode 100755
index 8587b93..6f8bada
--- a/scripts/add-openjdk.sh
+++ b/scripts/add-openjdk.sh
@@ -16,9 +16,9 @@
# Prepare README.google
# Update JDK_VERSION below
-# Now run script wit fingers crossed!
+# Now run script with fingers crossed!
-JDK_VERSION=16.0.2
+JDK_VERSION=17
tar xf ~/Downloads/openjdk-${JDK_VERSION}_linux-x64_bin.tar.gz
cp -rL jdk-${JDK_VERSION} linux
@@ -28,7 +28,7 @@
rm -rf linux
rm linux.tar.gz
-tar xf ~/Downloads/openjdk-${JDK_VERSION}_osx-x64_bin.tar.gz
+tar xf ~/Downloads/openjdk-${JDK_VERSION}_macos-x64_bin.tar.gz
cp -rL jdk-${JDK_VERSION}.jdk osx
cp README.google osx
upload_to_google_storage.py -a --bucket r8-deps osx
@@ -43,3 +43,7 @@
rm -rf windows
rm -rf jdk-${JDK_VERSION}
rm windows.tar.gz
+
+git add *.sha1
+
+echo "Update additional files, see https://r8-review.googlesource.com/c/r8/+/61909"
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabase.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabase.java
new file mode 100644
index 0000000..5dda285
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabase.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2021, 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.androidapi;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+public interface AndroidApiLevelDatabase {
+
+ AndroidApiLevel getTypeApiLevel(DexType type);
+
+ AndroidApiLevel getMethodApiLevel(DexMethod method);
+
+ AndroidApiLevel getFieldApiLevel(DexField field);
+}
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseImpl.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseImpl.java
new file mode 100644
index 0000000..d98013e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseImpl.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2021, 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.androidapi;
+
+import com.android.tools.r8.apimodel.AndroidApiDatabaseBuilder;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.HashMap;
+import java.util.function.BiFunction;
+
+public class AndroidApiLevelDatabaseImpl implements AndroidApiLevelDatabase {
+
+ private final HashMap<DexType, AndroidApiClass> predefinedApiTypeLookup;
+
+ private final AndroidApiClass SENTINEL =
+ new AndroidApiClass(null) {
+
+ @Override
+ public AndroidApiLevel getApiLevel() {
+ return null;
+ }
+
+ @Override
+ public int getMemberCount() {
+ return 0;
+ }
+
+ @Override
+ protected TraversalContinuation visitFields(
+ BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor,
+ ClassReference holder,
+ int minApiClass) {
+ return null;
+ }
+
+ @Override
+ protected TraversalContinuation visitMethods(
+ BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor,
+ ClassReference holder,
+ int minApiClass) {
+ return null;
+ }
+ };
+
+ public AndroidApiLevelDatabaseImpl(HashMap<DexType, AndroidApiClass> predefinedApiTypeLookup) {
+ this.predefinedApiTypeLookup = predefinedApiTypeLookup;
+ }
+
+ @Override
+ public AndroidApiLevel getTypeApiLevel(DexType type) {
+ return lookupDefinedApiLevel(type);
+ }
+
+ @Override
+ public AndroidApiLevel getMethodApiLevel(DexMethod method) {
+ return lookupDefinedApiLevel(method);
+ }
+
+ @Override
+ public AndroidApiLevel getFieldApiLevel(DexField field) {
+ return lookupDefinedApiLevel(field);
+ }
+
+ private AndroidApiLevel lookupDefinedApiLevel(DexReference reference) {
+ AndroidApiClass foundClass =
+ predefinedApiTypeLookup.getOrDefault(reference.getContextType(), SENTINEL);
+ if (foundClass == null) {
+ return AndroidApiLevel.UNKNOWN;
+ }
+ AndroidApiClass androidApiClass;
+ if (foundClass == SENTINEL) {
+ androidApiClass =
+ AndroidApiDatabaseBuilder.buildClass(reference.getContextType().asClassReference());
+ if (androidApiClass == null) {
+ predefinedApiTypeLookup.put(reference.getContextType(), null);
+ return AndroidApiLevel.UNKNOWN;
+ }
+ } else {
+ androidApiClass = foundClass;
+ }
+ return reference.apply(
+ type -> androidApiClass.getApiLevel(),
+ field -> {
+ FieldReference fieldReference = field.asFieldReference();
+ Box<AndroidApiLevel> apiLevelBox = new Box<>(AndroidApiLevel.UNKNOWN);
+ androidApiClass.visitFields(
+ (fieldRef, apiLevel) -> {
+ if (fieldReference.equals(fieldRef)) {
+ apiLevelBox.set(apiLevel);
+ return TraversalContinuation.BREAK;
+ }
+ return TraversalContinuation.CONTINUE;
+ });
+ return apiLevelBox.get();
+ },
+ method -> {
+ MethodReference methodReference = method.asMethodReference();
+ Box<AndroidApiLevel> apiLevelBox = new Box<>(AndroidApiLevel.UNKNOWN);
+ androidApiClass.visitMethods(
+ (methodRef, apiLevel) -> {
+ if (methodReference.equals(methodRef)) {
+ apiLevelBox.set(apiLevel);
+ return TraversalContinuation.BREAK;
+ }
+ return TraversalContinuation.CONTINUE;
+ });
+ return apiLevelBox.get();
+ });
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
index e3fa030..8ca1b59 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
@@ -4,37 +4,28 @@
package com.android.tools.r8.androidapi;
-import com.android.tools.r8.apimodel.AndroidApiDatabaseBuilder;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.Box;
-import com.android.tools.r8.utils.TraversalContinuation;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.HashMap;
public class AndroidApiReferenceLevelCache {
- private static final int BUILD_CACHE_TRESHOLD = 20;
-
- private final ConcurrentHashMap<DexType, AndroidApiClass> apiTypeLookup;
- private final ConcurrentHashMap<DexReference, AndroidApiLevel> apiMemberLookup =
- new ConcurrentHashMap<>();
private final DesugaredLibraryConfiguration desugaredLibraryConfiguration;
+ private final AndroidApiLevelDatabase androidApiLevelDatabase;
private final AppView<?> appView;
private AndroidApiReferenceLevelCache(AppView<?> appView) {
- this(appView, new ConcurrentHashMap<>());
+ this(appView, new HashMap<>());
}
private AndroidApiReferenceLevelCache(
- AppView<?> appView, ConcurrentHashMap<DexType, AndroidApiClass> apiTypeLookup) {
+ AppView<?> appView, HashMap<DexType, AndroidApiClass> predefinedApiTypeLookup) {
this.appView = appView;
- this.apiTypeLookup = apiTypeLookup;
+ androidApiLevelDatabase = new AndroidApiLevelDatabaseImpl(predefinedApiTypeLookup);
desugaredLibraryConfiguration = appView.options().desugaredLibraryConfiguration;
}
@@ -51,16 +42,16 @@
}
// The apiTypeLookup is build lazily except for the mocked api types that we define in tests
// externally.
- ConcurrentHashMap<DexType, AndroidApiClass> apiTypeLookup = new ConcurrentHashMap<>();
+ HashMap<DexType, AndroidApiClass> predefinedApiTypeLookup = new HashMap<>();
appView
.options()
.apiModelingOptions()
.visitMockedApiReferences(
(classReference, androidApiClass) ->
- apiTypeLookup.put(
+ predefinedApiTypeLookup.put(
appView.dexItemFactory().createType(classReference.getDescriptor()),
androidApiClass));
- return new AndroidApiReferenceLevelCache(appView, apiTypeLookup);
+ return new AndroidApiReferenceLevelCache(appView, predefinedApiTypeLookup);
}
public AndroidApiLevel lookupMax(DexReference reference, AndroidApiLevel minApiLevel) {
@@ -87,73 +78,9 @@
// of the program.
return appView.options().minApiLevel;
}
- AndroidApiClass androidApiClass =
- apiTypeLookup.computeIfAbsent(
- contextType, type -> AndroidApiDatabaseBuilder.buildClass(type.asClassReference()));
- if (androidApiClass == null) {
- // This is a library class but we have no api model for it. This happens if using an older
- // version of R8 to compile a new target. We simply have to disallow inlining of methods
- // that has such references.
- return AndroidApiLevel.UNKNOWN;
- }
- if (reference.isDexType()) {
- return androidApiClass.getApiLevel();
- }
- return androidApiClass.getMemberCount() > BUILD_CACHE_TRESHOLD
- ? findMemberByCaching(reference, androidApiClass)
- : findMemberByIteration(reference.asDexMember(), androidApiClass);
- }
-
- private AndroidApiLevel findMemberByIteration(
- DexMember<?, ?> reference, AndroidApiClass apiClass) {
- DexItemFactory factory = appView.dexItemFactory();
- // Similar to the case for api classes we are unable to find, if the member
- // is unknown we have to be conservative.
- Box<AndroidApiLevel> apiLevelBox = new Box<>(AndroidApiLevel.UNKNOWN);
- reference.apply(
- field ->
- apiClass.visitFields(
- (fieldReference, apiLevel) -> {
- if (factory.createField(fieldReference) == field) {
- apiLevelBox.set(apiLevel);
- return TraversalContinuation.BREAK;
- }
- return TraversalContinuation.CONTINUE;
- }),
- method ->
- apiClass.visitMethods(
- (methodReference, apiLevel) -> {
- if (factory.createMethod(methodReference) == method) {
- apiLevelBox.set(apiLevel);
- return TraversalContinuation.BREAK;
- }
- return TraversalContinuation.CONTINUE;
- }));
- return apiLevelBox.get();
- }
-
- private AndroidApiLevel findMemberByCaching(DexReference reference, AndroidApiClass apiClass) {
- buildCacheForMembers(reference.getContextType(), apiClass);
- return apiMemberLookup.getOrDefault(reference, AndroidApiLevel.UNKNOWN);
- }
-
- private void buildCacheForMembers(DexType context, AndroidApiClass apiClass) {
- assert apiClass.getMemberCount() > BUILD_CACHE_TRESHOLD;
- // Use the context type as a token for us having build a cache for it.
- if (apiMemberLookup.containsKey(context)) {
- return;
- }
- DexItemFactory factory = appView.dexItemFactory();
- apiClass.visitFields(
- (fieldReference, apiLevel) -> {
- apiMemberLookup.put(factory.createField(fieldReference), apiLevel);
- return TraversalContinuation.CONTINUE;
- });
- apiClass.visitMethods(
- (methodReference, apiLevel) -> {
- apiMemberLookup.put(factory.createMethod(methodReference), apiLevel);
- return TraversalContinuation.CONTINUE;
- });
- apiMemberLookup.put(context, AndroidApiLevel.UNKNOWN);
+ return reference.apply(
+ androidApiLevelDatabase::getTypeApiLevel,
+ androidApiLevelDatabase::getFieldApiLevel,
+ androidApiLevelDatabase::getMethodApiLevel);
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/CfVersion.java b/src/main/java/com/android/tools/r8/cf/CfVersion.java
index 5b877b8..b23d44d 100644
--- a/src/main/java/com/android/tools/r8/cf/CfVersion.java
+++ b/src/main/java/com/android/tools/r8/cf/CfVersion.java
@@ -36,6 +36,8 @@
public static final CfVersion V15_PREVIEW = new CfVersion(Opcodes.V15 | Opcodes.V_PREVIEW);
public static final CfVersion V16 = new CfVersion(Opcodes.V16);
public static final CfVersion V16_PREVIEW = new CfVersion(Opcodes.V16 | Opcodes.V_PREVIEW);
+ public static final CfVersion V17 = new CfVersion(Opcodes.V17);
+ public static final CfVersion V17_PREVIEW = new CfVersion(Opcodes.V17 | Opcodes.V_PREVIEW);
private final int version;
@@ -55,7 +57,8 @@
CfVersion.V13,
CfVersion.V14,
CfVersion.V15,
- CfVersion.V16
+ CfVersion.V16,
+ CfVersion.V17
};
// Private constructor in case we want to canonicalize versions.
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 4050961..08e18ca4 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -331,7 +331,6 @@
// as there was a dex resource.
private boolean hasReadProgramResourceFromCf = false;
private boolean hasReadProgramResourceFromDex = false;
- private boolean hasReadProgramRecord = false;
ClassReader(ExecutorService executorService, List<Future<?>> futures) {
this.executorService = executorService;
@@ -340,7 +339,9 @@
public DexApplicationReadFlags getDexApplicationReadFlags() {
return new DexApplicationReadFlags(
- hasReadProgramResourceFromDex, hasReadProgramResourceFromCf, hasReadProgramRecord);
+ hasReadProgramResourceFromDex,
+ hasReadProgramResourceFromCf,
+ application.hasReadRecordReferenceFromProgramClass());
}
private void readDexSources(List<ProgramResource> dexSources, Queue<DexProgramClass> classes)
@@ -382,15 +383,7 @@
}
hasReadProgramResourceFromCf = true;
JarClassFileReader<DexProgramClass> reader =
- new JarClassFileReader<>(
- application,
- clazz -> {
- classes.add(clazz);
- if (clazz.isRecord()) {
- hasReadProgramRecord = true;
- }
- },
- PROGRAM);
+ new JarClassFileReader<>(application, classes::add, PROGRAM);
// Read classes in parallel.
for (ProgramResource input : classSources) {
futures.add(
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index dc8e0bb..12a5e8d 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -794,15 +794,23 @@
return builder.resolve(clazz);
}
- // Non-private lookup (ie, not resolution) to find interface targets.
- DexClassAndMethod lookupMaximallySpecificTarget(DexClass clazz, DexMethod method) {
- MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
- resolveMethodStep3Helper(method.getProto(), method.getName(), clazz, builder);
- return builder.lookup();
+ MethodResolutionResult resolveMaximallySpecificTarget(DexClass clazz, DexMethod method) {
+ return resolveMaximallySpecificTargetHelper(clazz, method).resolve(clazz);
}
- // Non-private lookup (ie, not resolution) to find interface targets.
- DexClassAndMethod lookupMaximallySpecificTarget(LambdaDescriptor lambda, DexMethod method) {
+ private MaximallySpecificMethodsBuilder resolveMaximallySpecificTargetHelper(
+ DexClass clazz, DexMethod method) {
+ MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
+ resolveMethodStep3Helper(method.getProto(), method.getName(), clazz, builder);
+ return builder;
+ }
+
+ MethodResolutionResult resolveMaximallySpecificTarget(LambdaDescriptor lambda, DexMethod method) {
+ return resolveMaximallySpecificTargetHelper(lambda, method).internalResolve(null);
+ }
+
+ private MaximallySpecificMethodsBuilder resolveMaximallySpecificTargetHelper(
+ LambdaDescriptor lambda, DexMethod method) {
MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
resolveMethodStep3Helper(
method.getProto(),
@@ -810,7 +818,17 @@
dexItemFactory().objectType,
lambda.interfaces,
builder);
- return builder.lookup();
+ return builder;
+ }
+
+ // Non-private lookup (ie, not resolution) to find interface targets.
+ DexClassAndMethod lookupMaximallySpecificTarget(DexClass clazz, DexMethod method) {
+ return resolveMaximallySpecificTargetHelper(clazz, method).lookup();
+ }
+
+ // Non-private lookup (ie, not resolution) to find interface targets.
+ DexClassAndMethod lookupMaximallySpecificTarget(LambdaDescriptor lambda, DexMethod method) {
+ return resolveMaximallySpecificTargetHelper(lambda, method).lookup();
}
/** Helper method that builds the set of maximally specific methods. */
@@ -1059,10 +1077,7 @@
}
DexClassAndMethod lookup() {
- SingleResolutionResult result = internalResolve(null).asSingleResolution();
- return result != null
- ? DexClassAndMethod.create(result.getResolvedHolder(), result.getResolvedMethod())
- : null;
+ return internalResolve(null).getResolutionPair();
}
MethodResolutionResult resolve(DexClass initialResolutionHolder) {
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 72cddc2..0225c1d 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -97,7 +97,7 @@
private InitializedClassesInInstanceMethods initializedClassesInInstanceMethods;
private HorizontallyMergedClasses horizontallyMergedClasses = HorizontallyMergedClasses.empty();
private VerticallyMergedClasses verticallyMergedClasses;
- private EnumDataMap unboxedEnums = EnumDataMap.empty();
+ private EnumDataMap unboxedEnums = null;
// TODO(b/169115389): Remove
private Set<DexMethod> cfByteCodePassThrough = ImmutableSet.of();
private Map<DexType, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
@@ -575,12 +575,16 @@
testing().verticallyMergedClassesConsumer.accept(dexItemFactory(), verticallyMergedClasses);
}
+ public boolean hasUnboxedEnums() {
+ return unboxedEnums != null;
+ }
+
public EnumDataMap unboxedEnums() {
- return unboxedEnums;
+ return hasUnboxedEnums() ? unboxedEnums : EnumDataMap.empty();
}
public void setUnboxedEnums(EnumDataMap unboxedEnums) {
- assert this.unboxedEnums.isEmpty();
+ assert !hasUnboxedEnums();
this.unboxedEnums = unboxedEnums;
testing().unboxedEnumsConsumer.accept(dexItemFactory(), unboxedEnums);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java b/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java
index d5089a7..bb3bcf8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java
@@ -9,15 +9,15 @@
private final boolean hasReadProgramClassFromDex;
private final boolean hasReadProgramClassFromCf;
- private final boolean hasReadProgramRecord;
+ private final boolean hasReadRecordReferenceFromProgramClass;
public DexApplicationReadFlags(
boolean hasReadProgramClassFromDex,
boolean hasReadProgramClassFromCf,
- boolean hasReadProgramRecord) {
+ boolean hasReadRecordReferenceFromProgramClass) {
this.hasReadProgramClassFromDex = hasReadProgramClassFromDex;
this.hasReadProgramClassFromCf = hasReadProgramClassFromCf;
- this.hasReadProgramRecord = hasReadProgramRecord;
+ this.hasReadRecordReferenceFromProgramClass = hasReadRecordReferenceFromProgramClass;
}
public boolean hasReadProgramClassFromCf() {
@@ -28,7 +28,7 @@
return hasReadProgramClassFromDex;
}
- public boolean hasReadProgramRecord() {
- return hasReadProgramRecord;
+ public boolean hasReadRecordReferenceFromProgramClass() {
+ return hasReadRecordReferenceFromProgramClass;
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index b4a382f..82f09f8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1675,6 +1675,11 @@
return this;
}
+ public Builder setOptimizationInfo(MethodOptimizationInfo optimizationInfo) {
+ this.optimizationInfo = optimizationInfo;
+ return this;
+ }
+
public Builder modifyOptimizationInfo(
BiConsumer<DexEncodedMethod, MutableMethodOptimizationInfo> consumer) {
return addBuildConsumer(
diff --git a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
index 67e7b5b..047be58 100644
--- a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
+import com.android.tools.r8.ir.desugar.records.RecordRewriter;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import java.util.List;
@@ -26,6 +27,8 @@
private final ConcurrentHashMap<String, DexString> stringCache = new ConcurrentHashMap<>();
private final Map<String, String> typeDescriptorMap;
+ private boolean hasReadRecordReferenceFromProgramClass = false;
+
public JarApplicationReader(InternalOptions options) {
this.options = options;
typeDescriptorMap = ApplicationReaderMap.getDescriptorMap(options);
@@ -149,4 +152,24 @@
public Type getReturnType(final String methodDescriptor) {
return getAsmType(DescriptorUtils.getReturnTypeDescriptor(methodDescriptor));
}
+
+ public void setHasReadRecordReferenceFromProgramClass() {
+ hasReadRecordReferenceFromProgramClass = true;
+ }
+
+ public boolean hasReadRecordReferenceFromProgramClass() {
+ return hasReadRecordReferenceFromProgramClass;
+ }
+
+ public void checkFieldForRecord(DexField dexField) {
+ if (options.shouldDesugarRecords() && RecordRewriter.refersToRecord(dexField, getFactory())) {
+ setHasReadRecordReferenceFromProgramClass();
+ }
+ }
+
+ public void checkMethodForRecord(DexMethod dexMethod) {
+ if (options.shouldDesugarRecords() && RecordRewriter.refersToRecord(dexMethod, getFactory())) {
+ setHasReadRecordReferenceFromProgramClass();
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 4c6c165..296f792 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -512,9 +512,13 @@
}
private void checkRecord() {
+ if (!application.options.shouldDesugarRecords()) {
+ return;
+ }
if (!accessFlags.isRecord()) {
return;
}
+ application.setHasReadRecordReferenceFromProgramClass();
// TODO(b/169645628): Change this logic if we start stripping the record components.
// Another approach would be to mark a bit in fields that are record components instead.
String message = "Records are expected to have one record component per instance field.";
@@ -661,6 +665,7 @@
public void visitEnd() {
FieldAccessFlags flags = createFieldAccessFlags(access);
DexField dexField = parent.application.getField(parent.type, name, desc);
+ parent.application.checkFieldForRecord(dexField);
Wrapper<DexField> signature = FieldSignatureEquivalence.get().wrap(dexField);
if (parent.fieldSignatures.add(signature)) {
DexAnnotationSet annotationSet =
@@ -878,6 +883,7 @@
@Override
public void visitEnd() {
InternalOptions options = parent.application.options;
+ parent.application.checkMethodForRecord(method);
if (!flags.isAbstract() && !flags.isNative() && classRequiresCode()) {
code = new LazyCfCode(method, parent.origin, parent.context, parent.application);
}
diff --git a/src/main/java/com/android/tools/r8/graph/LookupResult.java b/src/main/java/com/android/tools/r8/graph/LookupResult.java
index 17d7f55..bc85814 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupResult.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupResult.java
@@ -5,9 +5,10 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.graph.LookupResult.LookupResultSuccess.LookupResultCollectionState;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import java.util.function.Consumer;
public abstract class LookupResult {
@@ -28,18 +29,23 @@
return null;
}
- public final void forEach(Consumer<LookupTarget> onTarget) {
- forEach(onTarget::accept, onTarget::accept);
+ public final void forEach(Consumer<? super LookupTarget> onTarget) {
+ forEach(onTarget, onTarget);
}
public abstract void forEach(
- Consumer<DexClassAndMethod> onMethodTarget, Consumer<LookupLambdaTarget> onLambdaTarget);
+ Consumer<? super DexClassAndMethod> onMethodTarget,
+ Consumer<? super LookupLambdaTarget> onLambdaTarget);
+
+ public abstract void forEachFailureDependency(
+ Consumer<? super DexEncodedMethod> methodCausingFailureConsumer);
public static LookupResultSuccess createResult(
- Map<DexEncodedMethod, DexClassAndMethod> methodTargets,
+ DexClassAndMethodSet methodTargets,
List<LookupLambdaTarget> lambdaTargets,
+ List<DexEncodedMethod> methodsCausingFailure,
LookupResultCollectionState state) {
- return new LookupResultSuccess(methodTargets, lambdaTargets, state);
+ return new LookupResultSuccess(methodTargets, lambdaTargets, methodsCausingFailure, state);
}
public static LookupResultFailure createFailedResult() {
@@ -54,23 +60,31 @@
private static final LookupResultSuccess EMPTY_INSTANCE =
new LookupResultSuccess(
- Collections.emptyMap(),
+ DexClassAndMethodSet.empty(),
+ Collections.emptyList(),
Collections.emptyList(),
LookupResultCollectionState.Incomplete);
- private final Map<DexEncodedMethod, DexClassAndMethod> methodTargets;
+ private final DexClassAndMethodSet methodTargets;
private final List<LookupLambdaTarget> lambdaTargets;
+ private final List<DexEncodedMethod> methodsCausingFailure;
private LookupResultCollectionState state;
private LookupResultSuccess(
- Map<DexEncodedMethod, DexClassAndMethod> methodTargets,
+ DexClassAndMethodSet methodTargets,
List<LookupLambdaTarget> lambdaTargets,
+ List<DexEncodedMethod> methodsCausingFailure,
LookupResultCollectionState state) {
this.methodTargets = methodTargets;
this.lambdaTargets = lambdaTargets;
+ this.methodsCausingFailure = methodsCausingFailure;
this.state = state;
}
+ public static Builder builder() {
+ return new Builder();
+ }
+
public boolean isEmpty() {
return methodTargets.isEmpty() && lambdaTargets.isEmpty();
}
@@ -85,14 +99,21 @@
@Override
public void forEach(
- Consumer<DexClassAndMethod> onMethodTarget, Consumer<LookupLambdaTarget> onLambdaTarget) {
- methodTargets.forEach((ignore, method) -> onMethodTarget.accept(method));
+ Consumer<? super DexClassAndMethod> onMethodTarget,
+ Consumer<? super LookupLambdaTarget> onLambdaTarget) {
+ methodTargets.forEach(onMethodTarget);
lambdaTargets.forEach(onLambdaTarget);
}
+ @Override
+ public void forEachFailureDependency(
+ Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
+ methodsCausingFailure.forEach(methodCausingFailureConsumer);
+ }
+
public boolean contains(DexEncodedMethod method) {
// Containment of a method in the lookup results only pertains to the method targets.
- return methodTargets.containsKey(method);
+ return methodTargets.contains(method);
}
@Override
@@ -124,7 +145,7 @@
}
// TODO(b/150932978): Check lambda targets implementation methods.
if (methodTargets.size() == 1) {
- return methodTargets.values().iterator().next();
+ return methodTargets.iterator().next();
} else if (lambdaTargets.size() == 1) {
return lambdaTargets.get(0);
}
@@ -135,6 +156,38 @@
Complete,
Incomplete,
}
+
+ public static class Builder {
+
+ private final DexClassAndMethodSet methodTargets = DexClassAndMethodSet.create();
+ private final List<LookupLambdaTarget> lambdaTargets = new ArrayList<>();
+ private final List<DexEncodedMethod> methodsCausingFailure = new ArrayList<>();
+ private LookupResultCollectionState state;
+
+ public Builder addMethodTarget(DexClassAndMethod methodTarget) {
+ methodTargets.add(methodTarget);
+ return this;
+ }
+
+ public Builder addLambdaTarget(LookupLambdaTarget lambdaTarget) {
+ lambdaTargets.add(lambdaTarget);
+ return this;
+ }
+
+ public Builder addMethodCausingFailure(DexEncodedMethod methodCausingFailure) {
+ methodsCausingFailure.add(methodCausingFailure);
+ return this;
+ }
+
+ public Builder setState(LookupResultCollectionState state) {
+ this.state = state;
+ return this;
+ }
+
+ public LookupResultSuccess build() {
+ return new LookupResultSuccess(methodTargets, lambdaTargets, methodsCausingFailure, state);
+ }
+ }
}
public static class LookupResultFailure extends LookupResult {
@@ -157,8 +210,15 @@
@Override
public void forEach(
- Consumer<DexClassAndMethod> onMethodTarget, Consumer<LookupLambdaTarget> onLambdaTarget) {
+ Consumer<? super DexClassAndMethod> onMethodTarget,
+ Consumer<? super LookupLambdaTarget> onLambdaTarget) {
// Nothing to iterate for a failed lookup.
}
+
+ @Override
+ public void forEachFailureDependency(
+ Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
+ // TODO: record and emit failure dependencies.
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
index c2cb1d4..c1ff02b 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -3,6 +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.utils.ConsumerUtils.emptyConsumer;
+
+import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
import com.android.tools.r8.graph.LookupResult.LookupResultSuccess.LookupResultCollectionState;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -10,12 +13,9 @@
import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.OptionalBool;
-import java.util.ArrayList;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
import java.util.Collection;
import java.util.Collections;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
@@ -84,6 +84,10 @@
return null;
}
+ public DexEncodedMethod getResolvedMethod() {
+ return null;
+ }
+
/** Short-hand to get the single resolution method if resolution finds it, null otherwise. */
public final DexEncodedMethod getSingleTarget() {
return isSingleResolution() ? asSingleResolution().getResolvedMethod() : null;
@@ -148,7 +152,9 @@
DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo);
public abstract LookupTarget lookupVirtualDispatchTarget(
- LambdaDescriptor lambdaInstance, AppInfoWithClassHierarchy appInfo);
+ LambdaDescriptor lambdaInstance,
+ AppInfoWithClassHierarchy appInfo,
+ Consumer<? super DexEncodedMethod> methodCausingFailureConsumer);
/** Result for a resolution that succeeds with a known declaration/definition. */
public static class SingleResolutionResult extends MethodResolutionResult
@@ -187,6 +193,7 @@
return resolvedMethod;
}
+ @Override
public DexEncodedMethod getResolvedMethod() {
return resolvedMethod;
}
@@ -431,46 +438,47 @@
boolean isIncomplete =
pinnedPredicate.isPinned(resolvedHolder) && pinnedPredicate.isPinned(resolvedMethod);
return LookupResult.createResult(
- Collections.singletonMap(
- resolvedMethod, DexClassAndMethod.create(resolvedHolder, resolvedMethod)),
+ DexClassAndMethodSet.create(getResolutionPair()),
+ Collections.emptyList(),
Collections.emptyList(),
isIncomplete
? LookupResultCollectionState.Incomplete
: LookupResultCollectionState.Complete);
}
assert resolvedMethod.isNonPrivateVirtualMethod();
- Map<DexEncodedMethod, DexClassAndMethod> methodTargets = new IdentityHashMap<>();
- List<LookupLambdaTarget> lambdaTargets = new ArrayList<>();
+ LookupResultSuccess.Builder resultBuilder = LookupResultSuccess.builder();
LookupCompletenessHelper incompleteness = new LookupCompletenessHelper(pinnedPredicate);
instantiatedInfo.forEachInstantiatedSubType(
initialResolutionHolder.type,
subClass -> {
incompleteness.checkClass(subClass);
DexClassAndMethod dexClassAndMethod =
- lookupVirtualDispatchTarget(subClass, appInfo, resolvedHolder.type);
+ lookupVirtualDispatchTarget(
+ subClass, appInfo, resolvedHolder.type, resultBuilder::addMethodCausingFailure);
if (dexClassAndMethod != null) {
incompleteness.checkDexClassAndMethod(dexClassAndMethod);
addVirtualDispatchTarget(
- dexClassAndMethod, resolvedHolder.isInterface(), methodTargets);
+ dexClassAndMethod, resolvedHolder.isInterface(), resultBuilder);
}
},
lambda -> {
assert resolvedHolder.isInterface()
|| resolvedHolder.type == appInfo.dexItemFactory().objectType;
- LookupTarget target = lookupVirtualDispatchTarget(lambda, appInfo);
+ LookupTarget target =
+ lookupVirtualDispatchTarget(
+ lambda, appInfo, resultBuilder::addMethodCausingFailure);
if (target != null) {
if (target.isLambdaTarget()) {
- lambdaTargets.add(target.asLambdaTarget());
+ resultBuilder.addLambdaTarget(target.asLambdaTarget());
} else {
addVirtualDispatchTarget(
- target.asMethodTarget(), resolvedHolder.isInterface(), methodTargets);
+ target.asMethodTarget(), resolvedHolder.isInterface(), resultBuilder);
}
}
});
- return LookupResult.createResult(
- methodTargets,
- lambdaTargets,
- incompleteness.computeCollectionState(resolvedMethod.getReference(), appInfo));
+ return resultBuilder
+ .setState(incompleteness.computeCollectionState(resolvedMethod.getReference(), appInfo))
+ .build();
}
@Override
@@ -532,7 +540,7 @@
private static void addVirtualDispatchTarget(
DexClassAndMethod target,
boolean holderIsInterface,
- Map<DexEncodedMethod, DexClassAndMethod> result) {
+ LookupResultSuccess.Builder resultBuilder) {
DexEncodedMethod targetMethod = target.getDefinition();
assert !targetMethod.isPrivateMethod();
if (holderIsInterface) {
@@ -559,17 +567,17 @@
// }
//
if (targetMethod.isDefaultMethod()) {
- result.putIfAbsent(targetMethod, target);
+ resultBuilder.addMethodTarget(target);
}
// Default methods are looked up when looking at a specific subtype that does not override
// them. Otherwise, we would look up default methods that are actually never used.
// However, we have to add bridge methods, otherwise we can remove a bridge that will be
// used.
if (!targetMethod.accessFlags.isAbstract() && targetMethod.accessFlags.isBridge()) {
- result.putIfAbsent(targetMethod, target);
+ resultBuilder.addMethodTarget(target);
}
} else {
- result.putIfAbsent(targetMethod, target);
+ resultBuilder.addMethodTarget(target);
}
}
@@ -583,18 +591,21 @@
InstantiatedObject instance, AppInfoWithClassHierarchy appInfo) {
return instance.isClass()
? lookupVirtualDispatchTarget(instance.asClass(), appInfo)
- : lookupVirtualDispatchTarget(instance.asLambda(), appInfo);
+ : lookupVirtualDispatchTarget(instance.asLambda(), appInfo, emptyConsumer());
}
@Override
public DexClassAndMethod lookupVirtualDispatchTarget(
DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
- return lookupVirtualDispatchTarget(dynamicInstance, appInfo, initialResolutionHolder.type);
+ return lookupVirtualDispatchTarget(
+ dynamicInstance, appInfo, initialResolutionHolder.type, emptyConsumer());
}
@Override
public LookupTarget lookupVirtualDispatchTarget(
- LambdaDescriptor lambdaInstance, AppInfoWithClassHierarchy appInfo) {
+ LambdaDescriptor lambdaInstance,
+ AppInfoWithClassHierarchy appInfo,
+ Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
if (lambdaInstance.getMainMethod().match(resolvedMethod)) {
DexMethod methodReference = lambdaInstance.implHandle.asMethod();
DexClass holder = appInfo.definitionForHolder(methodReference);
@@ -605,11 +616,15 @@
}
return new LookupLambdaTarget(lambdaInstance, method);
}
- return lookupMaximallySpecificDispatchTarget(lambdaInstance, appInfo);
+ return lookupMaximallySpecificDispatchTarget(
+ lambdaInstance, appInfo, methodCausingFailureConsumer);
}
private DexClassAndMethod lookupVirtualDispatchTarget(
- DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo, DexType resolutionHolder) {
+ DexClass dynamicInstance,
+ AppInfoWithClassHierarchy appInfo,
+ DexType resolutionHolder,
+ Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
assert appInfo.isSubtype(dynamicInstance.type, resolutionHolder)
: dynamicInstance.type + " is not a subtype of " + resolutionHolder;
// TODO(b/148591377): Enable this assertion.
@@ -618,7 +633,7 @@
if (resolvedMethod.isPrivateMethod()) {
// If the resolved reference is private there is no dispatch.
// This is assuming that the method is accessible, which implies self/nest access.
- return DexClassAndMethod.create(resolvedHolder, resolvedMethod);
+ return getResolutionPair();
}
boolean allowPackageBlocked = resolvedMethod.accessFlags.isPackagePrivate();
DexClass current = dynamicInstance;
@@ -645,17 +660,46 @@
if (!resolvedHolder.isInterface()) {
return null;
}
- return lookupMaximallySpecificDispatchTarget(dynamicInstance, appInfo);
+ return lookupMaximallySpecificDispatchTarget(
+ dynamicInstance, appInfo, methodCausingFailureConsumer);
}
private DexClassAndMethod lookupMaximallySpecificDispatchTarget(
- DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
- return appInfo.lookupMaximallySpecificMethod(dynamicInstance, resolvedMethod.getReference());
+ DexClass dynamicInstance,
+ AppInfoWithClassHierarchy appInfo,
+ Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
+ MethodResolutionResult maximallySpecificResolutionResult =
+ appInfo.resolveMaximallySpecificTarget(dynamicInstance, resolvedMethod.getReference());
+ if (maximallySpecificResolutionResult.isSingleResolution()) {
+ return maximallySpecificResolutionResult.getResolutionPair();
+ }
+ if (maximallySpecificResolutionResult.isFailedResolution()) {
+ maximallySpecificResolutionResult
+ .asFailedResolution()
+ .forEachFailureDependency(methodCausingFailureConsumer);
+ return null;
+ }
+ assert maximallySpecificResolutionResult.isArrayCloneMethodResult();
+ return null;
}
private DexClassAndMethod lookupMaximallySpecificDispatchTarget(
- LambdaDescriptor lambdaDescriptor, AppInfoWithClassHierarchy appInfo) {
- return appInfo.lookupMaximallySpecificMethod(lambdaDescriptor, resolvedMethod.getReference());
+ LambdaDescriptor lambdaDescriptor,
+ AppInfoWithClassHierarchy appInfo,
+ Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
+ MethodResolutionResult maximallySpecificResolutionResult =
+ appInfo.resolveMaximallySpecificTarget(lambdaDescriptor, resolvedMethod.getReference());
+ if (maximallySpecificResolutionResult.isSingleResolution()) {
+ return maximallySpecificResolutionResult.getResolutionPair();
+ }
+ if (maximallySpecificResolutionResult.isFailedResolution()) {
+ maximallySpecificResolutionResult
+ .asFailedResolution()
+ .forEachFailureDependency(methodCausingFailureConsumer);
+ return null;
+ }
+ assert maximallySpecificResolutionResult.isArrayCloneMethodResult();
+ return null;
}
/**
@@ -773,7 +817,9 @@
@Override
public DexClassAndMethod lookupVirtualDispatchTarget(
- LambdaDescriptor lambdaInstance, AppInfoWithClassHierarchy appInfo) {
+ LambdaDescriptor lambdaInstance,
+ AppInfoWithClassHierarchy appInfo,
+ Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
return null;
}
}
@@ -823,7 +869,8 @@
return this;
}
- public void forEachFailureDependency(Consumer<DexEncodedMethod> methodCausingFailureConsumer) {
+ public void forEachFailureDependency(
+ Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
// Default failure has no dependencies.
}
@@ -871,7 +918,8 @@
}
@Override
- public void forEachFailureDependency(Consumer<DexEncodedMethod> methodCausingFailureConsumer) {
+ public void forEachFailureDependency(
+ Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
this.methodsCausingError.forEach(methodCausingFailureConsumer);
}
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java
index 265d050..4c869ef 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java
@@ -107,7 +107,7 @@
*/
@Override
public BitSet fixupNonNullParamOnNormalExits(BitSet nonNullParamOnNormalExits) {
- return fixupNonNullParamInfo(nonNullParamOnNormalExits);
+ return fixupArgumentInfo(nonNullParamOnNormalExits);
}
/**
@@ -116,27 +116,7 @@
*/
@Override
public BitSet fixupNonNullParamOrThrow(BitSet nonNullParamOrThrow) {
- return fixupNonNullParamInfo(nonNullParamOrThrow);
- }
-
- private BitSet fixupNonNullParamInfo(BitSet nonNullParamInfo) {
- if (getArgumentInfoCollection().isEmpty() || nonNullParamInfo == null) {
- return nonNullParamInfo;
- }
- int n = nonNullParamInfo.length();
- BitSet rewrittenNonNullParamOnNormalExits = new BitSet(n);
- for (int argumentIndex = 0; argumentIndex < n; argumentIndex++) {
- if (!nonNullParamInfo.get(argumentIndex)) {
- continue;
- }
- ArgumentInfo argumentInfo = getArgumentInfoCollection().getArgumentInfo(argumentIndex);
- if (argumentInfo.isRemovedArgumentInfo() || argumentInfo.isRewrittenTypeInfo()) {
- continue;
- }
- rewrittenNonNullParamOnNormalExits.set(
- getArgumentInfoCollection().getNewArgumentIndex(argumentIndex));
- }
- return rewrittenNonNullParamOnNormalExits.isEmpty() ? null : rewrittenNonNullParamOnNormalExits;
+ return fixupArgumentInfo(nonNullParamOrThrow);
}
/**
@@ -167,4 +147,33 @@
}
return constraint.fixupAfterParametersChanged(appView, getArgumentInfoCollection(), factory);
}
+
+ /**
+ * Function for rewriting the unused arguments on a piece of method optimization info after
+ * prototype changes were made.
+ */
+ @Override
+ public BitSet fixupUnusedArguments(BitSet unusedArguments) {
+ return fixupArgumentInfo(unusedArguments);
+ }
+
+ private BitSet fixupArgumentInfo(BitSet bitSet) {
+ if (getArgumentInfoCollection().isEmpty() || bitSet == null) {
+ return bitSet;
+ }
+ int n = bitSet.length();
+ BitSet rewrittenNonNullParamOnNormalExits = new BitSet(n);
+ for (int argumentIndex = 0; argumentIndex < n; argumentIndex++) {
+ if (!bitSet.get(argumentIndex)) {
+ continue;
+ }
+ ArgumentInfo argumentInfo = getArgumentInfoCollection().getArgumentInfo(argumentIndex);
+ if (argumentInfo.isRemovedArgumentInfo() || argumentInfo.isRewrittenTypeInfo()) {
+ continue;
+ }
+ rewrittenNonNullParamOnNormalExits.set(
+ getArgumentInfoCollection().getNewArgumentIndex(argumentIndex));
+ }
+ return rewrittenNonNullParamOnNormalExits.isEmpty() ? null : rewrittenNonNullParamOnNormalExits;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index 58a44a3..190e1b5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -37,6 +37,10 @@
return createSingleNumberValue(0);
}
+ public SingleNumberValue createZeroValue() {
+ return createSingleNumberValue(0);
+ }
+
public SingleStringValue createSingleStringValue(DexString string) {
return singleStringValues.computeIfAbsent(string, SingleStringValue::new);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
index 8230766..33829cd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
@@ -18,9 +18,11 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxerImpl;
/**
- * Special instruction used by {@link com.android.tools.r8.ir.optimize.enums.EnumUnboxer}.
+ * Special instruction used by {@link EnumUnboxerImpl}.
*
* <p>When applying the enum unboxer to the application, we move the class initializer of each
* unboxed enum to its utility class, and change each {@link NewInstance} instruction that
@@ -32,11 +34,10 @@
* code to type check until lens code rewriting, which replaces the {@link NewUnboxedEnumInstance}
* instructions by {@link ConstNumber} instructions.
*
- * <p>Note: The {@link NewUnboxedEnumInstance} is only used from {@link
- * com.android.tools.r8.ir.optimize.enums.EnumUnboxer#unboxEnums} until the execution of the {@link
- * com.android.tools.r8.ir.conversion.PostMethodProcessor}. There should be no instances of {@link
- * NewUnboxedEnumInstance} (nor {@link CfNewUnboxedEnum}, {@link DexNewUnboxedEnumInstance}) after
- * IR processing has finished.
+ * <p>Note: The {@link NewUnboxedEnumInstance} is only used from {@link EnumUnboxer#unboxEnums}
+ * until the execution of the {@link com.android.tools.r8.ir.conversion.PostMethodProcessor}. There
+ * should be no instances of {@link NewUnboxedEnumInstance} (nor {@link CfNewUnboxedEnum}, {@link
+ * DexNewUnboxedEnumInstance}) after IR processing has finished.
*/
public class NewUnboxedEnumInstance extends Instruction {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 5bde78e..a165209 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -78,7 +78,6 @@
import com.android.tools.r8.ir.optimize.ReflectionOptimizer;
import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
-import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoCollector;
@@ -239,7 +238,7 @@
this.serviceLoaderRewriter = null;
this.methodOptimizationInfoCollector = null;
this.enumValueOptimizer = null;
- this.enumUnboxer = null;
+ this.enumUnboxer = EnumUnboxer.empty();
this.assumeInserter = null;
return;
}
@@ -267,7 +266,7 @@
options.enableTreeShakingOfLibraryMethodOverrides
? new LibraryMethodOverrideAnalysis(appViewWithLiveness)
: null;
- this.enumUnboxer = options.enableEnumUnboxing ? new EnumUnboxer(appViewWithLiveness) : null;
+ this.enumUnboxer = EnumUnboxer.create(appViewWithLiveness);
this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness, enumUnboxer);
this.inliner = new Inliner(appViewWithLiveness, lensCodeRewriter);
this.outliner = Outliner.create(appViewWithLiveness);
@@ -306,7 +305,7 @@
this.serviceLoaderRewriter = null;
this.methodOptimizationInfoCollector = null;
this.enumValueOptimizer = null;
- this.enumUnboxer = null;
+ this.enumUnboxer = EnumUnboxer.empty();
}
this.stringSwitchRemover =
options.isStringSwitchConversionEnabled()
@@ -640,10 +639,7 @@
optimization.abandonCallSitePropagationForPinnedMethodsAndOverrides(
executorService, timing);
});
- ConsumerUtils.acceptIfNotNull(
- enumUnboxer,
- enumUnboxer ->
- enumUnboxer.initializeEnumUnboxingCandidates(graphLensForPrimaryOptimizationPass));
+ enumUnboxer.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass);
ConsumerUtils.acceptIfNotNull(
classStaticizer,
classStaticizer ->
@@ -724,12 +720,8 @@
.run(executorService, feedback, timing);
}
- if (enumUnboxer != null) {
- outliner.rewriteWithLens();
- enumUnboxer.unboxEnums(this, postMethodProcessorBuilder, executorService, feedback);
- } else {
- appView.setUnboxedEnums(EnumDataMap.empty());
- }
+ outliner.rewriteWithLens();
+ enumUnboxer.unboxEnums(appView, this, postMethodProcessorBuilder, executorService, feedback);
GraphLens graphLensForSecondaryOptimizationPass = appView.graphLens();
@@ -757,9 +749,7 @@
}
timing.end();
- if (enumUnboxer != null) {
- enumUnboxer.unsetRewriter();
- }
+ enumUnboxer.unsetRewriter();
// All the code that should be impacted by the lenses inserted between phase 1 and phase 2
// have now been processed and rewritten, we clear code lens rewriting so that the class
@@ -848,9 +838,7 @@
if (appView.options().protoShrinking().enableRemoveProtoEnumSwitchMap()) {
appView.protoShrinker().protoEnumSwitchMapRemover.updateVisibleStaticFieldValues();
}
- if (enumUnboxer != null) {
- enumUnboxer.updateEnumUnboxingCandidatesInfo();
- }
+ enumUnboxer.updateEnumUnboxingCandidatesInfo();
assert delayedOptimizationFeedback.noUpdatesLeft();
onWaveDoneActions.forEach(com.android.tools.r8.utils.Action::execute);
onWaveDoneActions = null;
@@ -1548,7 +1536,7 @@
appView.withArgumentPropagator(
argumentPropagator -> argumentPropagator.scan(method, code, methodProcessor, timing));
- if (enumUnboxer != null && methodProcessor.isPrimaryMethodProcessor()) {
+ if (methodProcessor.isPrimaryMethodProcessor()) {
enumUnboxer.analyzeEnums(code, conversionOptions);
}
@@ -1593,9 +1581,7 @@
appView, code, classInitializerDefaultsResult, feedback, timing);
}
}
- if (enumUnboxer != null) {
- enumUnboxer.recordEnumState(method.getHolder(), staticFieldValues);
- }
+ enumUnboxer.recordEnumState(method.getHolder(), staticFieldValues);
if (appView.options().protoShrinking().enableRemoveProtoEnumSwitchMap()) {
appView
.protoShrinker()
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 13b7140..5219a50 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -66,6 +66,8 @@
void setEnumUnboxerMethodClassification(
ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification);
+ void unsetEnumUnboxerMethodClassification(ProgramMethod method);
+
void setInstanceInitializerInfoCollection(
DexEncodedMethod method, InstanceInitializerInfoCollection instanceInitializerInfoCollection);
@@ -78,4 +80,6 @@
void setSimpleInliningConstraint(ProgramMethod method, SimpleInliningConstraint constraint);
void classInitializerMayBePostponed(DexEncodedMethod method);
+
+ void setUnusedArguments(ProgramMethod method, BitSet unusedArguments);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
index f07a802..8236617 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
@@ -4,20 +4,25 @@
package com.android.tools.r8.ir.desugar;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
// Source code representing synthesized accessor method.
public class AccessorMethodSourceCode {
- public static CfCode build(LambdaClass lambda, DexMethod accessor) {
- DexMethod target = lambda.descriptor.implHandle.asMethod();
+ public static CfCode build(
+ DexMethod target,
+ boolean isInterface,
+ MethodHandleType type,
+ DexMethod accessor,
+ AppView<?> appView) {
ForwardMethodBuilder forwardMethodBuilder =
- ForwardMethodBuilder.builder(lambda.appView.dexItemFactory()).setStaticSource(accessor);
- boolean isInterface = lambda.descriptor.implHandle.isInterface;
- switch (lambda.descriptor.implHandle.type) {
+ ForwardMethodBuilder.builder(appView.dexItemFactory()).setStaticSource(accessor);
+ switch (type) {
case INVOKE_INSTANCE:
{
forwardMethodBuilder.setVirtualTarget(target, isInterface);
@@ -35,7 +40,7 @@
}
case INVOKE_CONSTRUCTOR:
{
- forwardMethodBuilder.setConstructorTarget(target, lambda.appView.dexItemFactory());
+ forwardMethodBuilder.setConstructorTarget(target, appView.dexItemFactory());
break;
}
case INVOKE_INTERFACE:
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 2493dd3..929b5a8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -343,12 +343,13 @@
for (ConstantDynamicClass constantDynamicClass : synthesizedConstantDynamicClasses) {
constantDynamicClass.getConstantDynamicProgramClass().forEachProgramMethod(needsProcessing);
}
- synthesizedLambdaClasses.clear();
+ synthesizedConstantDynamicClasses.clear();
}
public boolean verifyNothingToFinalize() {
assert pendingInvokeSpecialBridges.isEmpty();
assert synthesizedLambdaClasses.isEmpty();
+ assert synthesizedConstantDynamicClasses.isEmpty();
return true;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index c2d95ca..afedd24 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -312,14 +312,18 @@
appView.appInfoForDesugaring().resolveMethod(implMethod, implHandle.isInterface);
if (resolution.isFailedResolution()) {
return new InvalidLambdaImplTarget(
- implMethod, Type.STATIC, appView.dexItemFactory().icceType);
+ implMethod,
+ Type.STATIC,
+ appView.dexItemFactory().icceType,
+ descriptor.implHandle.isInterface);
}
SingleResolutionResult result = resolution.asSingleResolution();
assert result.getResolvedMethod().isStatic();
assert result.getResolvedHolder().isProgramClass();
return new StaticLambdaImplTarget(
new ProgramMethod(
- result.getResolvedHolder().asProgramClass(), result.getResolvedMethod()));
+ result.getResolvedHolder().asProgramClass(), result.getResolvedMethod()),
+ descriptor.implHandle.isInterface);
}
assert implHandle.type.isInvokeDirect();
@@ -334,18 +338,24 @@
DexProto newProto = appView.dexItemFactory().createProto(implProto.returnType, newParams);
return new InterfaceLambdaImplTarget(
- appView.dexItemFactory().createMethod(implMethod.holder, newProto, implMethod.name));
+ descriptor.implHandle.asMethod(),
+ descriptor.implHandle.isInterface,
+ appView.dexItemFactory().createMethod(implMethod.holder, newProto, implMethod.name),
+ appView);
} else {
// Otherwise we need to ensure the method can be reached publicly by virtual dispatch.
// To avoid potential conflicts on the name of the lambda method once dispatch becomes virtual
// we add the fully qualified method-holder name as suffix to the lambda-method name.
return new InstanceLambdaImplTarget(
+ descriptor.implHandle.asMethod(),
+ descriptor.implHandle.isInterface,
appView
.dexItemFactory()
.createMethod(
implMethod.holder,
implMethod.proto,
- appendFullyQualifiedHolderToMethodName(implMethod, appView.dexItemFactory())));
+ appendFullyQualifiedHolderToMethodName(implMethod, appView.dexItemFactory())),
+ appView);
}
}
@@ -356,7 +366,8 @@
descriptor.implHandle.type.isInvokeDirect();
if (doesNotNeedAccessor(accessedFrom)) {
- return new NoAccessorMethodTarget(Invoke.Type.VIRTUAL);
+ return new NoAccessorMethodTarget(
+ descriptor.implHandle.asMethod(), Type.VIRTUAL, descriptor.implHandle.isInterface);
}
// We need to generate an accessor method in `accessedFrom` class/interface
// for accessing the original instance impl-method. Note that impl-method's
@@ -379,7 +390,12 @@
.createMethod(
accessedFrom.getHolderType(), accessorProto, generateUniqueLambdaMethodName());
- return new ClassMethodWithAccessorTarget(accessorMethod);
+ return new ClassMethodWithAccessorTarget(
+ descriptor.implHandle.asMethod(),
+ descriptor.implHandle.isInterface,
+ descriptor.implHandle.type,
+ accessorMethod,
+ appView);
}
// Create targets for static method referenced directly without
@@ -388,7 +404,8 @@
assert descriptor.implHandle.type.isInvokeStatic();
if (doesNotNeedAccessor(accessedFrom)) {
- return new NoAccessorMethodTarget(Invoke.Type.STATIC);
+ return new NoAccessorMethodTarget(
+ descriptor.implHandle.asMethod(), Type.STATIC, descriptor.implHandle.isInterface);
}
// We need to generate an accessor method in `accessedFrom` class/interface
@@ -401,7 +418,12 @@
accessedFrom.getHolderType(),
descriptor.implHandle.asMethod().proto,
generateUniqueLambdaMethodName());
- return new ClassMethodWithAccessorTarget(accessorMethod);
+ return new ClassMethodWithAccessorTarget(
+ descriptor.implHandle.asMethod(),
+ descriptor.implHandle.isInterface,
+ descriptor.implHandle.type,
+ accessorMethod,
+ appView);
}
// Create targets for constructor referenced directly without lambda$ methods.
@@ -412,7 +434,8 @@
assert implHandle.type.isInvokeConstructor();
if (doesNotNeedAccessor(accessedFrom)) {
- return new NoAccessorMethodTarget(Invoke.Type.DIRECT);
+ return new NoAccessorMethodTarget(
+ descriptor.implHandle.asMethod(), Type.DIRECT, descriptor.implHandle.isInterface);
}
// We need to generate an accessor method in `accessedFrom` class/interface for
@@ -428,14 +451,20 @@
.dexItemFactory()
.createMethod(
accessedFrom.getHolderType(), accessorProto, generateUniqueLambdaMethodName());
- return new ClassMethodWithAccessorTarget(accessorMethod);
+ return new ClassMethodWithAccessorTarget(
+ descriptor.implHandle.asMethod(),
+ descriptor.implHandle.isInterface,
+ descriptor.implHandle.type,
+ accessorMethod,
+ appView);
}
// Create targets for interface methods.
private Target createInterfaceMethodTarget(ProgramMethod accessedFrom) {
assert descriptor.implHandle.type.isInvokeInterface();
assert doesNotNeedAccessor(accessedFrom);
- return new NoAccessorMethodTarget(Invoke.Type.INTERFACE);
+ return new NoAccessorMethodTarget(
+ descriptor.implHandle.asMethod(), Type.INTERFACE, descriptor.implHandle.isInterface);
}
private DexString generateUniqueLambdaMethodName() {
@@ -447,19 +476,20 @@
// Represents information about the method lambda class need to delegate the call to. It may
// be the same method as specified in lambda descriptor or a newly synthesized accessor.
// Also provides action for ensuring accessibility of the referenced symbols.
- public abstract class Target {
+ public abstract static class Target {
final DexMethod callTarget;
final Invoke.Type invokeType;
+ final boolean isInterface;
private boolean hasEnsuredAccessibility;
- private ProgramMethod accessibilityBridge;
- Target(DexMethod callTarget, Invoke.Type invokeType) {
+ Target(DexMethod callTarget, Type invokeType, boolean isInterface) {
assert callTarget != null;
assert invokeType != null;
this.callTarget = callTarget;
this.invokeType = invokeType;
+ this.isInterface = isInterface;
}
// Ensure access of the referenced symbol(s).
@@ -476,29 +506,27 @@
ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
Consumer<ProgramMethod> needsProcessingConsumer) {
if (!hasEnsuredAccessibility) {
- accessibilityBridge =
- ensureAccessibility(forcefullyMovedLambdaMethodConsumer, needsProcessingConsumer);
+ ensureAccessibility(forcefullyMovedLambdaMethodConsumer, needsProcessingConsumer);
hasEnsuredAccessibility = true;
}
}
boolean isInterface() {
- return descriptor.implHandle.isInterface;
+ return isInterface;
}
}
- public abstract class D8SpecificTarget extends Target {
- D8SpecificTarget(DexMethod callTarget, Type invokeType) {
- super(callTarget, invokeType);
- assert !appView.enableWholeProgramOptimizations();
+ public abstract static class D8SpecificTarget extends Target {
+ D8SpecificTarget(DexMethod callTarget, Type invokeType, boolean isInterface) {
+ super(callTarget, invokeType, isInterface);
}
}
// Used for targeting methods referenced directly without creating accessors.
- private final class NoAccessorMethodTarget extends Target {
+ private static final class NoAccessorMethodTarget extends Target {
- NoAccessorMethodTarget(Invoke.Type invokeType) {
- super(descriptor.implHandle.asMethod(), invokeType);
+ NoAccessorMethodTarget(DexMethod method, Type invokeType, boolean isInterface) {
+ super(method, invokeType, isInterface);
}
@Override
@@ -510,12 +538,12 @@
}
// Used for static private lambda$ methods. Only needs access relaxation.
- private final class StaticLambdaImplTarget extends D8SpecificTarget {
+ private static final class StaticLambdaImplTarget extends D8SpecificTarget {
final ProgramMethod target;
- StaticLambdaImplTarget(ProgramMethod target) {
- super(descriptor.implHandle.asMethod(), Invoke.Type.STATIC);
+ StaticLambdaImplTarget(ProgramMethod target, boolean isInterface) {
+ super(target.getReference(), Invoke.Type.STATIC, isInterface);
this.target = target;
}
@@ -535,10 +563,16 @@
// Used for instance private lambda$ methods on interfaces which need to be converted to public
// static methods. They can't remain instance methods as they will end up on the companion class.
- private class InterfaceLambdaImplTarget extends D8SpecificTarget {
+ private static final class InterfaceLambdaImplTarget extends D8SpecificTarget {
- InterfaceLambdaImplTarget(DexMethod staticMethod) {
- super(staticMethod, Type.STATIC);
+ private final AppView<?> appView;
+ private final DexMethod implMethod;
+
+ InterfaceLambdaImplTarget(
+ DexMethod implMethod, boolean isInterface, DexMethod staticMethod, AppView<?> appView) {
+ super(staticMethod, Type.STATIC, isInterface);
+ this.implMethod = implMethod;
+ this.appView = appView;
}
@Override
@@ -547,7 +581,6 @@
Consumer<ProgramMethod> needsProcessingConsumer) {
// For all instantiation points for which the compiler creates lambda$
// methods, it creates these methods in the same class/interface.
- DexMethod implMethod = descriptor.implHandle.asMethod();
DexProgramClass implMethodHolder = appView.definitionFor(implMethod.holder).asProgramClass();
DexEncodedMethod replacement =
@@ -603,12 +636,13 @@
}
}
- class InvalidLambdaImplTarget extends Target {
+ static final class InvalidLambdaImplTarget extends Target {
final DexType exceptionType;
- public InvalidLambdaImplTarget(DexMethod callTarget, Type invokeType, DexType exceptionType) {
- super(callTarget, invokeType);
+ public InvalidLambdaImplTarget(
+ DexMethod callTarget, Type invokeType, DexType exceptionType, boolean isInterface) {
+ super(callTarget, invokeType, isInterface);
this.exceptionType = exceptionType;
}
@@ -621,10 +655,16 @@
}
// Used for instance private lambda$ methods which need to be converted to public methods.
- private class InstanceLambdaImplTarget extends D8SpecificTarget {
+ private static final class InstanceLambdaImplTarget extends D8SpecificTarget {
- InstanceLambdaImplTarget(DexMethod staticMethod) {
- super(staticMethod, Type.VIRTUAL);
+ private final DexMethod implMethod;
+ private final AppView<?> appView;
+
+ InstanceLambdaImplTarget(
+ DexMethod implMethod, boolean isInterface, DexMethod staticMethod, AppView<?> appView) {
+ super(staticMethod, Type.VIRTUAL, isInterface);
+ this.implMethod = implMethod;
+ this.appView = appView;
}
@Override
@@ -634,7 +674,6 @@
// When compiling with whole program optimization, check that we are not inplace modifying.
// For all instantiation points for which the compiler creates lambda$
// methods, it creates these methods in the same class/interface.
- DexMethod implMethod = descriptor.implHandle.asMethod();
DexProgramClass implMethodHolder = appView.definitionFor(implMethod.holder).asProgramClass();
DexEncodedMethod replacement =
@@ -687,12 +726,25 @@
// Used for instance/static methods or constructors accessed via
// synthesized accessor method. Needs accessor method to be created.
- private class ClassMethodWithAccessorTarget extends Target {
+ private static class ClassMethodWithAccessorTarget extends Target {
- ClassMethodWithAccessorTarget(DexMethod accessorMethod) {
- super(accessorMethod, Invoke.Type.STATIC);
+ private final AppView<?> appView;
+ private final DexMethod implMethod;
+ private final MethodHandleType type;
+
+ ClassMethodWithAccessorTarget(
+ DexMethod implMethod,
+ boolean isInterface,
+ MethodHandleType type,
+ DexMethod accessorMethod,
+ AppView<?> appView) {
+ super(accessorMethod, Invoke.Type.STATIC, isInterface);
+ this.appView = appView;
+ this.implMethod = implMethod;
+ this.type = type;
}
+
@Override
ProgramMethod ensureAccessibility(
ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
@@ -718,7 +770,9 @@
DexEncodedMethod.syntheticBuilder()
.setMethod(callTarget)
.setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
- .setCode(AccessorMethodSourceCode.build(LambdaClass.this, callTarget))
+ .setCode(
+ AccessorMethodSourceCode.build(
+ implMethod, isInterface, type, callTarget, appView))
// The api level is computed when tracing.
.disableAndroidApiLevelCheck()
.build());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index b4da192..da0a96a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
+import com.android.tools.r8.ir.desugar.icce.AlwaysThrowingInstructionDesugaring;
import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaring;
import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
@@ -29,8 +30,10 @@
import com.android.tools.r8.ir.desugar.twr.TwrInstructionDesugaring;
import com.android.tools.r8.utils.IntBox;
import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThrowingConsumer;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collection;
@@ -55,6 +58,13 @@
AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
this.appView = appView;
this.apiLevelCompute = apiLevelCompute;
+ AlwaysThrowingInstructionDesugaring alwaysThrowingInstructionDesugaring =
+ appView.enableWholeProgramOptimizations()
+ ? new AlwaysThrowingInstructionDesugaring(appView.withClassHierarchy())
+ : null;
+ if (alwaysThrowingInstructionDesugaring != null) {
+ desugarings.add(alwaysThrowingInstructionDesugaring);
+ }
if (appView.options().desugarState.isOff()) {
this.nestBasedAccessDesugaring = null;
this.recordRewriter = null;
@@ -75,14 +85,17 @@
if (appView.options().enableBackportedMethodRewriting()) {
backportedMethodRewriter = new BackportedMethodRewriter(appView);
}
- // Place TWR before Interface desugaring to eliminate potential $closeResource interface calls.
if (appView.options().enableTryWithResourcesDesugaring()) {
desugarings.add(new TwrInstructionDesugaring(appView));
}
if (appView.options().isInterfaceMethodDesugaringEnabled()) {
interfaceMethodRewriter =
new InterfaceMethodRewriter(
- appView, backportedMethodRewriter, desugaredLibraryRetargeter);
+ appView,
+ SetUtils.newImmutableSetExcludingNullItems(
+ alwaysThrowingInstructionDesugaring,
+ backportedMethodRewriter,
+ desugaredLibraryRetargeter));
desugarings.add(interfaceMethodRewriter);
} else {
interfaceMethodRewriter = null;
@@ -91,9 +104,11 @@
appView.rewritePrefix.isRewriting()
? new DesugaredLibraryAPIConverter(
appView,
- interfaceMethodRewriter,
- desugaredLibraryRetargeter,
- backportedMethodRewriter)
+ SetUtils.newImmutableSetExcludingNullItems(
+ interfaceMethodRewriter, desugaredLibraryRetargeter, backportedMethodRewriter),
+ interfaceMethodRewriter != null
+ ? interfaceMethodRewriter.getEmulatedMethods()
+ : ImmutableSet.of())
: null;
if (desugaredLibraryAPIConverter != null) {
desugarings.add(desugaredLibraryAPIConverter);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
index 828e70c..4634977 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
@@ -23,22 +23,22 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.MemberType;
import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.FreshLocalProvider;
import com.android.tools.r8.ir.desugar.LocalStackAllocator;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer;
-import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConversionCfCodeProvider;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
@@ -67,31 +67,24 @@
private final AppView<?> appView;
private final DexItemFactory factory;
- // This is used to filter out double desugaring on backported methods.
- private final BackportedMethodRewriter backportedMethodRewriter;
- private final InterfaceMethodRewriter interfaceMethodRewriter;
- private final DesugaredLibraryRetargeter retargeter;
+ private final Set<CfInstructionDesugaring> precedingDesugarings;
+ private final Set<DexString> emulatedMethods;
private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
- private final Set<DexMethod> trackedCallBackAPIs;
private final Set<DexMethod> trackedAPIs;
public DesugaredLibraryAPIConverter(
AppView<?> appView,
- InterfaceMethodRewriter interfaceMethodRewriter,
- DesugaredLibraryRetargeter retargeter,
- BackportedMethodRewriter backportedMethodRewriter) {
+ Set<CfInstructionDesugaring> precedingDesugarings,
+ Set<DexString> emulatedMethods) {
this.appView = appView;
this.factory = appView.dexItemFactory();
- this.interfaceMethodRewriter = interfaceMethodRewriter;
- this.retargeter = retargeter;
- this.backportedMethodRewriter = backportedMethodRewriter;
+ this.precedingDesugarings = precedingDesugarings;
+ this.emulatedMethods = emulatedMethods;
this.wrapperSynthesizor = new DesugaredLibraryWrapperSynthesizer(appView);
if (appView.options().testing.trackDesugaredAPIConversions) {
- trackedCallBackAPIs = Sets.newConcurrentHashSet();
trackedAPIs = Sets.newConcurrentHashSet();
} else {
- trackedCallBackAPIs = null;
trackedAPIs = null;
}
}
@@ -176,10 +169,7 @@
// The problem is that a method can resolve into a library method which is not present at runtime,
// the code relies in that case on emulated interface dispatch. We should not convert such API.
private boolean isEmulatedInterfaceOverride(DexClassAndMethod invokedMethod) {
- if (interfaceMethodRewriter == null) {
- return false;
- }
- if (!interfaceMethodRewriter.getEmulatedMethods().contains(invokedMethod.getName())) {
+ if (!emulatedMethods.contains(invokedMethod.getName())) {
return false;
}
DexClassAndMethod interfaceResult =
@@ -195,18 +185,8 @@
}
private boolean isAlreadyDesugared(CfInvoke invoke, ProgramMethod context) {
- if (interfaceMethodRewriter != null
- && interfaceMethodRewriter.needsDesugaring(invoke, context)) {
- return true;
- }
- if (retargeter != null && retargeter.needsDesugaring(invoke, context)) {
- return true;
- }
- if (backportedMethodRewriter != null
- && backportedMethodRewriter.needsDesugaring(invoke, context)) {
- return true;
- }
- return false;
+ return Iterables.any(
+ precedingDesugarings, desugaring -> desugaring.needsDesugaring(invoke, context));
}
public static DexMethod methodWithVivifiedTypeInSignature(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
new file mode 100644
index 0000000..28b6981
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
@@ -0,0 +1,198 @@
+// Copyright (c) 2021, 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.desugar.icce;
+
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.DesugarDescription;
+import com.android.tools.r8.ir.desugar.FreshLocalProvider;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.MethodSynthesizerConsumer;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class AlwaysThrowingInstructionDesugaring implements CfInstructionDesugaring {
+
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
+
+ public AlwaysThrowingInstructionDesugaring(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public Collection<CfInstruction> desugarInstruction(
+ CfInstruction instruction,
+ FreshLocalProvider freshLocalProvider,
+ LocalStackAllocator localStackAllocator,
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ ProgramMethod context,
+ MethodProcessingContext methodProcessingContext,
+ DexItemFactory dexItemFactory) {
+ return computeDesugarDescription(instruction)
+ .desugarInstruction(
+ freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ dexItemFactory);
+ }
+
+ @Override
+ public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+ return computeDesugarDescription(instruction).needsDesugaring();
+ }
+
+ private DesugarDescription computeDesugarDescription(CfInstruction instruction) {
+ if (instruction.isInvoke()) {
+ CfInvoke invoke = instruction.asInvoke();
+ DexMethod invokedMethod = invoke.getMethod();
+ MethodResolutionResult resolutionResult =
+ appView.appInfo().resolveMethod(invokedMethod, invoke.isInterface());
+ if (shouldRewriteInvokeToThrow(invoke, resolutionResult)) {
+ return computeInvokeAsThrowRewrite(appView, invoke, resolutionResult);
+ }
+ }
+ return DesugarDescription.nothing();
+ }
+
+ private boolean shouldRewriteInvokeToThrow(
+ CfInvoke invoke, MethodResolutionResult resolutionResult) {
+ if (resolutionResult.isArrayCloneMethodResult()) {
+ return false;
+ }
+ if (resolutionResult.isFailedResolution()) {
+ // For now don't materialize NSMEs from failed resolutions.
+ return resolutionResult.asFailedResolution().hasMethodsCausingError();
+ }
+ assert resolutionResult.isSingleResolution();
+ return resolutionResult.getResolvedMethod().isStatic() != invoke.isInvokeStatic();
+ }
+
+ public static DesugarDescription computeInvokeAsThrowRewrite(
+ AppView<?> appView, CfInvoke invoke, MethodResolutionResult resolutionResult) {
+ return DesugarDescription.builder()
+ .setDesugarRewrite(
+ (freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ dexItemFactory) ->
+ getThrowInstructions(
+ appView,
+ invoke,
+ resolutionResult,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext))
+ .build();
+ }
+
+ private static Collection<CfInstruction> getThrowInstructions(
+ AppView<?> appView,
+ CfInvoke invoke,
+ MethodResolutionResult resolutionResult,
+ LocalStackAllocator localStackAllocator,
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ ProgramMethod context,
+ MethodProcessingContext methodProcessingContext) {
+ MethodSynthesizerConsumer methodSynthesizerConsumer = null;
+ if (resolutionResult == null) {
+ methodSynthesizerConsumer =
+ UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
+ } else if (resolutionResult.isSingleResolution()) {
+ if (resolutionResult.getResolvedMethod().isStatic() != invoke.isInvokeStatic()) {
+ methodSynthesizerConsumer =
+ UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod;
+ }
+ } else if (resolutionResult.isFailedResolution()) {
+ FailedResolutionResult failedResolutionResult = resolutionResult.asFailedResolution();
+ AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
+ if (failedResolutionResult.isIllegalAccessErrorResult(context.getHolder(), appInfo)) {
+ methodSynthesizerConsumer =
+ UtilityMethodsForCodeOptimizations::synthesizeThrowIllegalAccessErrorMethod;
+ } else if (failedResolutionResult.isNoSuchMethodErrorResult(context.getHolder(), appInfo)) {
+ methodSynthesizerConsumer =
+ UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
+ } else if (failedResolutionResult.isIncompatibleClassChangeErrorResult()) {
+ methodSynthesizerConsumer =
+ UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod;
+ }
+ }
+
+ if (methodSynthesizerConsumer == null) {
+ assert false;
+ return null;
+ }
+
+ // Replace the entire effect of the invoke by by call to the throwing helper:
+ // ...
+ // invoke <method> [receiver] args*
+ // =>
+ // ...
+ // (pop arg)*
+ // [pop receiver]
+ // invoke <throwing-method>
+ // pop exception result
+ // [push fake result for <method>]
+ UtilityMethodForCodeOptimizations throwMethod =
+ methodSynthesizerConsumer.synthesizeMethod(appView, methodProcessingContext);
+ ProgramMethod throwProgramMethod = throwMethod.uncheckedGetMethod();
+ eventConsumer.acceptThrowMethod(throwProgramMethod, context);
+
+ ArrayList<CfInstruction> replacement = new ArrayList<>();
+ DexTypeList parameters = invoke.getMethod().getParameters();
+ for (int i = parameters.values.length - 1; i >= 0; i--) {
+ replacement.add(
+ new CfStackInstruction(
+ parameters.get(i).isWideType()
+ ? CfStackInstruction.Opcode.Pop2
+ : CfStackInstruction.Opcode.Pop));
+ }
+ if (!invoke.isInvokeStatic()) {
+ replacement.add(new CfStackInstruction(CfStackInstruction.Opcode.Pop));
+ }
+
+ CfInvoke throwInvoke =
+ new CfInvoke(
+ org.objectweb.asm.Opcodes.INVOKESTATIC, throwProgramMethod.getReference(), false);
+ assert throwInvoke.getMethod().getReturnType().isClassType();
+ replacement.add(throwInvoke);
+ replacement.add(new CfStackInstruction(CfStackInstruction.Opcode.Pop));
+
+ DexType returnType = invoke.getMethod().getReturnType();
+ if (!returnType.isVoidType()) {
+ replacement.add(
+ returnType.isPrimitiveType()
+ ? new CfConstNumber(0, ValueType.fromDexType(returnType))
+ : new CfConstNull());
+ } else {
+ // If the return type is void, the stack may need an extra slot to fit the return type of
+ // the call to the throwing method.
+ localStackAllocator.allocateLocalStack(1);
+ }
+ return replacement;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 9ee3f54..87e0d65 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -6,11 +6,8 @@
import com.android.tools.r8.DesugarGraphConsumer;
import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.cf.code.CfConstNull;
-import com.android.tools.r8.cf.code.CfConstNumber;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfStackInstruction;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unimplemented;
@@ -27,27 +24,20 @@
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.MethodResolutionResult;
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.DesugarDescription;
import com.android.tools.r8.ir.desugar.FreshLocalProvider;
import com.android.tools.r8.ir.desugar.LocalStackAllocator;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeter;
+import com.android.tools.r8.ir.desugar.icce.AlwaysThrowingInstructionDesugaring;
import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
import com.android.tools.r8.ir.desugar.stringconcat.StringConcatInstructionDesugaring;
-import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
-import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.MethodSynthesizerConsumer;
-import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.MethodPosition;
@@ -56,8 +46,8 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.android.tools.r8.utils.structural.Ordered;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
@@ -108,8 +98,7 @@
private final Map<DexType, DefaultMethodsHelper.Collection> cache = new ConcurrentHashMap<>();
// This is used to filter out double desugaring on backported methods.
- private final BackportedMethodRewriter backportedMethodRewriter;
- private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
+ private final Set<CfInstructionDesugaring> precedingDesugarings;
/** Defines a minor variation in desugaring. */
public enum Flavor {
@@ -119,26 +108,10 @@
ExcludeDexResources
}
- // Constructor for cf to cf desugaring.
public InterfaceMethodRewriter(
- AppView<?> appView,
- BackportedMethodRewriter rewriter,
- DesugaredLibraryRetargeter desugaredLibraryRetargeter) {
+ AppView<?> appView, Set<CfInstructionDesugaring> precedingDesugarings) {
this.appView = appView;
- this.backportedMethodRewriter = rewriter;
- this.desugaredLibraryRetargeter = desugaredLibraryRetargeter;
- this.options = appView.options();
- this.factory = appView.dexItemFactory();
- this.helper = new InterfaceDesugaringSyntheticHelper(appView);
- initializeEmulatedInterfaceVariables();
- }
-
- // Constructor for IR desugaring.
- public InterfaceMethodRewriter(AppView<?> appView, IRConverter converter) {
- assert converter != null;
- this.appView = appView;
- this.backportedMethodRewriter = null;
- this.desugaredLibraryRetargeter = null;
+ this.precedingDesugarings = precedingDesugarings;
this.options = appView.options();
this.factory = appView.dexItemFactory();
this.helper = new InterfaceDesugaringSyntheticHelper(appView);
@@ -228,13 +201,8 @@
}
private boolean isAlreadyDesugared(CfInvoke invoke, ProgramMethod context) {
- // In Cf to Cf it is forbidden to desugar twice the same instruction, if the backported
- // method rewriter or the desugared library retargeter already desugar the instruction, they
- // take precedence and nothing has to be done here.
- return (backportedMethodRewriter != null
- && backportedMethodRewriter.needsDesugaring(invoke, context))
- || (desugaredLibraryRetargeter != null
- && desugaredLibraryRetargeter.needsDesugaring(invoke, context));
+ return Iterables.any(
+ precedingDesugarings, desugaring -> desugaring.needsDesugaring(invoke, context));
}
@Override
@@ -375,7 +343,7 @@
if (target != null && target.isDefaultMethod()) {
// Rewrite the invoke to a throw ICCE as the default method forward would otherwise hide the
// static / virtual mismatch.
- return computeInvokeAsThrowRewrite(invoke, resolution.asSingleResolution());
+ return computeInvokeAsThrowRewrite(invoke, resolution.asSingleResolution(), context);
}
return DesugarDescription.nothing();
}
@@ -469,7 +437,7 @@
.resolveMethodOnInterface(holder, invoke.getMethod())
.asSingleResolution();
if (holder.isInterface() && shouldRewriteToInvokeToThrow(resolutionResult, true)) {
- return computeInvokeAsThrowRewrite(invoke, resolutionResult);
+ return computeInvokeAsThrowRewrite(invoke, resolutionResult, context);
}
assert resolutionResult != null;
@@ -504,7 +472,7 @@
return computeInvokeDirect(holder, invoke, context);
}
if (resolution != null && resolution.getResolvedMethod().isStatic()) {
- return computeInvokeAsThrowRewrite(invoke, resolution);
+ return computeInvokeAsThrowRewrite(invoke, resolution, context);
}
DesugarDescription description = computeEmulatedInterfaceVirtualDispatchOrNull(invoke);
return description != null ? description : DesugarDescription.nothing();
@@ -549,7 +517,7 @@
MethodResolutionResult resolution =
appView.appInfoForDesugaring().resolveMethod(invokedMethod, invoke.isInterface());
if (resolution.isFailedResolution()) {
- return computeInvokeAsThrowRewrite(invoke, null);
+ return computeInvokeAsThrowRewrite(invoke, null, context);
}
SingleResolutionResult singleResolution = resolution.asSingleResolution();
@@ -628,23 +596,10 @@
}
private DesugarDescription computeInvokeAsThrowRewrite(
- CfInvoke invoke, SingleResolutionResult resolution) {
- return DesugarDescription.builder()
- .setDesugarRewrite(
- (freshLocalProvider,
- localStackAllocator,
- eventConsumer,
- context,
- methodProcessingContext,
- dexItemFactory) ->
- getThrowInstructions(
- invoke,
- resolution,
- localStackAllocator,
- eventConsumer,
- context,
- methodProcessingContext))
- .build();
+ CfInvoke invoke, SingleResolutionResult resolution, ProgramMethod context) {
+ assert !isAlreadyDesugared(invoke, context);
+ return AlwaysThrowingInstructionDesugaring.computeInvokeAsThrowRewrite(
+ appView, invoke, resolution);
}
private Collection<CfInstruction> getInvokeStaticInstructions(DexMethod newTarget) {
@@ -652,76 +607,6 @@
new CfInvoke(org.objectweb.asm.Opcodes.INVOKESTATIC, newTarget, false));
}
- private Collection<CfInstruction> getThrowInstructions(
- CfInvoke invoke,
- SingleResolutionResult resolutionResult,
- LocalStackAllocator localStackAllocator,
- CfInstructionDesugaringEventConsumer eventConsumer,
- ProgramMethod context,
- MethodProcessingContext methodProcessingContext) {
- assert !isAlreadyDesugared(invoke, context);
-
- MethodSynthesizerConsumer methodSynthesizerConsumer;
- if (resolutionResult == null) {
- methodSynthesizerConsumer =
- UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
- } else if (resolutionResult.getResolvedMethod().isStatic() != invoke.isInvokeStatic()) {
- methodSynthesizerConsumer =
- UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod;
- } else {
- assert false;
- return null;
- }
-
- // Replace the entire effect of the invoke by by call to the throwing helper:
- // ...
- // invoke <method> [receiver] args*
- // =>
- // ...
- // (pop arg)*
- // [pop receiver]
- // invoke <throwing-method>
- // pop exception result
- // [push fake result for <method>]
- UtilityMethodForCodeOptimizations throwMethod =
- methodSynthesizerConsumer.synthesizeMethod(appView, methodProcessingContext);
- ProgramMethod throwProgramMethod = throwMethod.uncheckedGetMethod();
- eventConsumer.acceptThrowMethod(throwProgramMethod, context);
-
- ArrayList<CfInstruction> replacement = new ArrayList<>();
- DexTypeList parameters = invoke.getMethod().getParameters();
- for (int i = parameters.values.length - 1; i >= 0; i--) {
- replacement.add(
- new CfStackInstruction(
- parameters.get(i).isWideType()
- ? CfStackInstruction.Opcode.Pop2
- : CfStackInstruction.Opcode.Pop));
- }
- if (!invoke.isInvokeStatic()) {
- replacement.add(new CfStackInstruction(CfStackInstruction.Opcode.Pop));
- }
-
- CfInvoke throwInvoke =
- new CfInvoke(
- org.objectweb.asm.Opcodes.INVOKESTATIC, throwProgramMethod.getReference(), false);
- assert throwInvoke.getMethod().getReturnType().isClassType();
- replacement.add(throwInvoke);
- replacement.add(new CfStackInstruction(CfStackInstruction.Opcode.Pop));
-
- DexType returnType = invoke.getMethod().getReturnType();
- if (returnType != factory.voidType) {
- replacement.add(
- returnType.isPrimitiveType()
- ? new CfConstNumber(0, ValueType.fromDexType(returnType))
- : new CfConstNull());
- } else {
- // If the return type is void, the stack may need an extra slot to fit the return type of
- // the call to the throwing method.
- localStackAllocator.allocateLocalStack(1);
- }
- return replacement;
- }
-
private void leavingStaticInvokeToInterface(ProgramMethod method) {
// When leaving static interface method invokes possibly upgrade the class file
// version, but don't go above the initial class file version. If the input was
@@ -778,7 +663,7 @@
SingleResolutionResult resolutionResult =
appView.appInfoForDesugaring().resolveMethodOn(clazz, invokedMethod).asSingleResolution();
if (clazz.isInterface() && shouldRewriteToInvokeToThrow(resolutionResult, false)) {
- return computeInvokeAsThrowRewrite(invoke, resolutionResult);
+ return computeInvokeAsThrowRewrite(invoke, resolutionResult, context);
}
if (clazz.isInterface() && !clazz.isLibraryClass()) {
@@ -794,7 +679,7 @@
if (resolutionResult.getResolvedMethod().isPrivateMethod()) {
if (resolutionResult.isAccessibleFrom(context, appView.appInfoForDesugaring()).isFalse()) {
// TODO(b/145775365): This should throw IAE.
- return computeInvokeAsThrowRewrite(invoke, null);
+ return computeInvokeAsThrowRewrite(invoke, null, context);
}
return DesugarDescription.builder()
.setDesugarRewrite(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
index 7fa0410..1f16c1b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
@@ -137,21 +137,21 @@
assert !instruction.isInitClass();
if (instruction.isInvoke()) {
CfInvoke cfInvoke = instruction.asInvoke();
- if (refersToRecord(cfInvoke.getMethod())) {
+ if (refersToRecord(cfInvoke.getMethod(), factory)) {
ensureRecordClass(eventConsumer);
}
return;
}
if (instruction.isFieldInstruction()) {
CfFieldInstruction fieldInstruction = instruction.asFieldInstruction();
- if (refersToRecord(fieldInstruction.getField())) {
+ if (refersToRecord(fieldInstruction.getField(), factory)) {
ensureRecordClass(eventConsumer);
}
return;
}
if (instruction.isTypeInstruction()) {
CfTypeInstruction typeInstruction = instruction.asTypeInstruction();
- if (refersToRecord(typeInstruction.getType())) {
+ if (refersToRecord(typeInstruction.getType(), factory)) {
ensureRecordClass(eventConsumer);
}
return;
@@ -452,35 +452,35 @@
}
}
- private boolean refersToRecord(DexField field) {
- assert !refersToRecord(field.holder) : "The java.lang.Record class has no fields.";
- return refersToRecord(field.type);
+ public static boolean refersToRecord(DexField field, DexItemFactory factory) {
+ assert !refersToRecord(field.holder, factory) : "The java.lang.Record class has no fields.";
+ return refersToRecord(field.type, factory);
}
- private boolean refersToRecord(DexMethod method) {
- if (refersToRecord(method.holder)) {
+ public static boolean refersToRecord(DexMethod method, DexItemFactory factory) {
+ if (refersToRecord(method.holder, factory)) {
return true;
}
- return refersToRecord(method.proto);
+ return refersToRecord(method.proto, factory);
}
- private boolean refersToRecord(DexProto proto) {
- if (refersToRecord(proto.returnType)) {
+ private static boolean refersToRecord(DexProto proto, DexItemFactory factory) {
+ if (refersToRecord(proto.returnType, factory)) {
return true;
}
- return refersToRecord(proto.parameters.values);
+ return refersToRecord(proto.parameters.values, factory);
}
- private boolean refersToRecord(DexType[] types) {
+ private static boolean refersToRecord(DexType[] types, DexItemFactory factory) {
for (DexType type : types) {
- if (refersToRecord(type)) {
+ if (refersToRecord(type, factory)) {
return true;
}
}
return false;
}
- private boolean refersToRecord(DexType type) {
+ private static boolean refersToRecord(DexType type, DexItemFactory factory) {
return type == factory.recordType;
}
@@ -600,7 +600,7 @@
@Override
public void synthesizeClasses(CfClassSynthesizerDesugaringEventConsumer eventConsumer) {
- if (appView.appInfo().app().getFlags().hasReadProgramRecord()) {
+ if (appView.appInfo().app().getFlags().hasReadRecordReferenceFromProgramClass()) {
ensureRecordClass(eventConsumer);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
new file mode 100644
index 0000000..2fb3b5a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2021, 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.enums;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.PostMethodProcessor.Builder;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+public class EmptyEnumUnboxer extends EnumUnboxer {
+
+ private static final EmptyEnumUnboxer INSTANCE = new EmptyEnumUnboxer();
+
+ private EmptyEnumUnboxer() {}
+
+ static EmptyEnumUnboxer get() {
+ return INSTANCE;
+ }
+
+ @Override
+ public void prepareForPrimaryOptimizationPass(GraphLens graphLensForPrimaryOptimizationPass) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void analyzeEnums(IRCode code, MutableMethodConversionOptions conversionOptions) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void recordEnumState(DexProgramClass clazz, StaticFieldValues staticFieldValues) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public Set<Phi> rewriteCode(IRCode code, MethodProcessor methodProcessor) {
+ // Intentionally empty.
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void unboxEnums(
+ AppView<AppInfoWithLiveness> appView,
+ IRConverter converter,
+ Builder postMethodProcessorBuilder,
+ ExecutorService executorService,
+ OptimizationFeedbackDelayed feedback) {
+ appView.setUnboxedEnums(EnumDataMap.empty());
+ }
+
+ @Override
+ public void unsetRewriter() {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void updateEnumUnboxingCandidatesInfo() {
+ // Intentionally empty.
+ }
+}
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 705ce93..2cdf22f 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
@@ -1,1575 +1,53 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2021, 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.enums;
-import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-import static com.android.tools.r8.ir.code.Opcodes.ARRAY_GET;
-import static com.android.tools.r8.ir.code.Opcodes.ARRAY_LENGTH;
-import static com.android.tools.r8.ir.code.Opcodes.ARRAY_PUT;
-import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
-import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
-import static com.android.tools.r8.ir.code.Opcodes.IF;
-import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
-import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
-import static com.android.tools.r8.ir.code.Opcodes.RETURN;
-import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
-import static com.android.tools.r8.utils.MapUtils.ignoreKey;
-
-import com.android.tools.r8.graph.AccessFlags;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClassAndField;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMember;
-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.DexMethod;
-import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.FieldResolutionResult;
import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.MethodResolutionResult;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
-import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues.EnumStaticFieldValues;
-import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
-import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.EnumValuesObjectState;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
-import com.android.tools.r8.ir.code.ArrayGet;
-import com.android.tools.r8.ir.code.ArrayLength;
-import com.android.tools.r8.ir.code.ArrayPut;
-import com.android.tools.r8.ir.code.Assume;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.CheckCast;
-import com.android.tools.r8.ir.code.ConstClass;
-import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.If;
-import com.android.tools.r8.ir.code.InstanceGet;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeCustom;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.MemberType;
-import com.android.tools.r8.ir.code.Opcodes;
import com.android.tools.r8.ir.code.Phi;
-import com.android.tools.r8.ir.code.Return;
-import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
import com.android.tools.r8.ir.conversion.MethodProcessor;
-import com.android.tools.r8.ir.conversion.PostMethodProcessor;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
-import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
-import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldMappingData;
-import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldOrdinalData;
-import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldUnknownData;
-import com.android.tools.r8.ir.optimize.enums.classification.CheckNotNullEnumUnboxerMethodClassification;
-import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
-import com.android.tools.r8.ir.optimize.enums.eligibility.Reason;
-import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.IllegalInvokeWithImpreciseParameterTypeReason;
-import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.MissingContentsForEnumValuesArrayReason;
-import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.MissingEnumStaticFieldValuesReason;
-import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.MissingInstanceFieldValueForEnumInstanceReason;
-import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.MissingObjectStateForEnumInstanceReason;
-import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.UnsupportedInstanceFieldValueForEnumInstanceReason;
-import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.UnsupportedLibraryInvokeReason;
-import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.MutableMethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
+import com.android.tools.r8.ir.conversion.PostMethodProcessor.Builder;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.KeepInfoCollection;
-import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
-import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
-import com.android.tools.r8.utils.collections.ProgramMethodMap;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.google.common.collect.HashMultiset;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
-import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.OptionalInt;
import java.util.Set;
-import java.util.TreeMap;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Predicate;
-public class EnumUnboxer {
+public abstract class EnumUnboxer {
- private final AppView<AppInfoWithLiveness> appView;
- private final DexItemFactory factory;
- // Map the enum candidates with their dependencies, i.e., the methods to reprocess for the given
- // enum if the optimization eventually decides to unbox it.
- private EnumUnboxingCandidateInfoCollection enumUnboxingCandidatesInfo;
- private final Set<DexProgramClass> candidatesToRemoveInWave = Sets.newConcurrentHashSet();
- private final Map<DexType, EnumStaticFieldValues> staticFieldValuesMap =
- new ConcurrentHashMap<>();
-
- // Methods depending on library modelisation need to be reprocessed so they are peephole
- // optimized.
- private LongLivedProgramMethodSetBuilder<ProgramMethodSet> methodsDependingOnLibraryModelisation;
-
- // Map from checkNotNull() methods to the enums that use the given method.
- private final ProgramMethodMap<Set<DexProgramClass>> checkNotNullMethods =
- ProgramMethodMap.createConcurrent();
-
- private final DexClassAndField ordinalField;
-
- private EnumUnboxingRewriter enumUnboxerRewriter;
-
- private final boolean debugLogEnabled;
- private final Map<DexType, List<Reason>> debugLogs;
-
- public EnumUnboxer(AppView<AppInfoWithLiveness> appView) {
- this.appView = appView;
- this.factory = appView.dexItemFactory();
- if (appView.options().testing.enableEnumUnboxingDebugLogs) {
- debugLogEnabled = true;
- debugLogs = new ConcurrentHashMap<>();
- } else {
- debugLogEnabled = false;
- debugLogs = null;
- }
- assert !appView.options().debug;
- ordinalField =
- appView.appInfo().resolveField(factory.enumMembers.ordinalField).getResolutionPair();
+ public static EnumUnboxer create(AppView<AppInfoWithLiveness> appView) {
+ return appView.options().enableEnumUnboxing ? new EnumUnboxerImpl(appView) : empty();
}
- public static int ordinalToUnboxedInt(int ordinal) {
- return ordinal + 1;
+ public static EmptyEnumUnboxer empty() {
+ return EmptyEnumUnboxer.get();
}
- public DexClassAndField getOrdinalField() {
- return ordinalField;
- }
+ public abstract void prepareForPrimaryOptimizationPass(
+ GraphLens graphLensForPrimaryOptimizationPass);
- public void updateEnumUnboxingCandidatesInfo() {
- for (DexProgramClass candidate : candidatesToRemoveInWave) {
- enumUnboxingCandidatesInfo.removeCandidate(candidate);
- }
- candidatesToRemoveInWave.clear();
- }
+ public abstract void analyzeEnums(IRCode code, MutableMethodConversionOptions conversionOptions);
- /**
- * Returns true if {@param enumClass} was marked as being unboxable.
- *
- * <p>Note that, if debug logging is enabled, {@param enumClass} is not marked unboxable until the
- * enum unboxing analysis has finished. This is to ensure completeness of the reason reporting.
- */
- private boolean markEnumAsUnboxable(Reason reason, DexProgramClass enumClass) {
- assert enumClass.isEnum();
- if (!reportFailure(enumClass, reason)) {
- // The failure was not reported, meaning debug logging is disabled.
- candidatesToRemoveInWave.add(enumClass);
- return true;
- }
- return false;
- }
+ public abstract void recordEnumState(DexProgramClass clazz, StaticFieldValues staticFieldValues);
- private void markMethodDependsOnLibraryModelisation(ProgramMethod method) {
- methodsDependingOnLibraryModelisation.add(method, appView.graphLens());
- }
+ public abstract Set<Phi> rewriteCode(IRCode code, MethodProcessor methodProcessor);
- private DexProgramClass getEnumUnboxingCandidateOrNull(TypeElement lattice) {
- if (lattice.isClassType()) {
- DexType classType = lattice.asClassType().getClassType();
- return getEnumUnboxingCandidateOrNull(classType);
- }
- if (lattice.isArrayType()) {
- ArrayTypeElement arrayType = lattice.asArrayType();
- if (arrayType.getBaseType().isClassType()) {
- return getEnumUnboxingCandidateOrNull(arrayType.getBaseType());
- }
- }
- return null;
- }
-
- private DexProgramClass getEnumUnboxingCandidateOrNull(DexType type) {
- if (type.isArrayType()) {
- return getEnumUnboxingCandidateOrNull(type.toBaseType(appView.dexItemFactory()));
- }
- if (type.isPrimitiveType() || type.isVoidType()) {
- return null;
- }
- assert type.isClassType();
- return enumUnboxingCandidatesInfo.getCandidateClassOrNull(type);
- }
-
- public void analyzeEnums(IRCode code, MutableMethodConversionOptions conversionOptions) {
- Set<DexType> eligibleEnums = Sets.newIdentityHashSet();
- for (BasicBlock block : code.blocks) {
- for (Instruction instruction : block.getInstructions()) {
- Value outValue = instruction.outValue();
- if (outValue != null) {
- DexProgramClass enumClass =
- getEnumUnboxingCandidateOrNull(outValue.getDynamicUpperBoundType(appView));
- if (enumClass != null) {
- Reason reason = validateEnumUsages(code, outValue, enumClass);
- if (reason == Reason.ELIGIBLE) {
- eligibleEnums.add(enumClass.type);
- }
- }
- if (outValue.getType().isNullType()) {
- addNullDependencies(code, outValue.uniqueUsers(), eligibleEnums);
- }
- } else {
- if (instruction.isInvokeMethod()) {
- DexProgramClass enumClass =
- getEnumUnboxingCandidateOrNull(instruction.asInvokeMethod().getReturnType());
- if (enumClass != null) {
- eligibleEnums.add(enumClass.type);
- }
- }
- }
- switch (instruction.opcode()) {
- case Opcodes.CONST_CLASS:
- analyzeConstClass(instruction.asConstClass(), eligibleEnums, code.context());
- break;
- case Opcodes.CHECK_CAST:
- analyzeCheckCast(instruction.asCheckCast(), eligibleEnums);
- break;
- case Opcodes.INVOKE_CUSTOM:
- analyzeInvokeCustom(instruction.asInvokeCustom(), eligibleEnums);
- break;
- case INVOKE_STATIC:
- analyzeInvokeStatic(instruction.asInvokeStatic(), eligibleEnums, code.context());
- break;
- case Opcodes.STATIC_GET:
- case Opcodes.INSTANCE_GET:
- case Opcodes.STATIC_PUT:
- case INSTANCE_PUT:
- analyzeFieldInstruction(
- instruction.asFieldInstruction(), eligibleEnums, code.context());
- break;
- default: // Nothing to do for other instructions.
- }
- }
- for (Phi phi : block.getPhis()) {
- DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(phi.getType());
- if (enumClass != null) {
- Reason reason = validateEnumUsages(code, phi, enumClass);
- if (reason == Reason.ELIGIBLE) {
- eligibleEnums.add(enumClass.type);
- }
- }
- if (phi.getType().isNullType()) {
- addNullDependencies(code, phi.uniqueUsers(), eligibleEnums);
- }
- }
- }
- if (!eligibleEnums.isEmpty()) {
- for (DexType eligibleEnum : eligibleEnums) {
- enumUnboxingCandidatesInfo.addMethodDependency(eligibleEnum, code.context());
- }
- }
- if (methodsDependingOnLibraryModelisation.contains(code.context(), appView.graphLens())) {
- conversionOptions.disablePeepholeOptimizations();
- }
- }
-
- private void analyzeInvokeCustom(InvokeCustom invoke, Set<DexType> eligibleEnums) {
- Consumer<DexType> typeReferenceConsumer =
- type -> {
- DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(type);
- if (enumClass != null) {
- eligibleEnums.add(enumClass.getType());
- }
- };
- invoke.getCallSite().getMethodProto().forEachType(typeReferenceConsumer);
- invoke
- .getCallSite()
- .getBootstrapArgs()
- .forEach(
- bootstrapArgument -> {
- if (bootstrapArgument.isDexValueMethodHandle()) {
- DexMethodHandle methodHandle =
- bootstrapArgument.asDexValueMethodHandle().getValue();
- if (methodHandle.isMethodHandle()) {
- DexMethod method = methodHandle.asMethod();
- DexProgramClass enumClass =
- getEnumUnboxingCandidateOrNull(method.getHolderType());
- if (enumClass != null) {
- markEnumAsUnboxable(Reason.INVALID_INVOKE_CUSTOM, enumClass);
- } else {
- method.getProto().forEachType(typeReferenceConsumer);
- }
- } else {
- assert methodHandle.isFieldHandle();
- DexField field = methodHandle.asField();
- DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(field.getHolderType());
- if (enumClass != null) {
- markEnumAsUnboxable(Reason.INVALID_INVOKE_CUSTOM, enumClass);
- } else {
- typeReferenceConsumer.accept(field.getType());
- }
- }
- } else if (bootstrapArgument.isDexValueMethodType()) {
- DexProto proto = bootstrapArgument.asDexValueMethodType().getValue();
- proto.forEachType(typeReferenceConsumer);
- }
- });
- }
-
- private void analyzeFieldInstruction(
- FieldInstruction fieldInstruction, Set<DexType> eligibleEnums, ProgramMethod context) {
- DexField field = fieldInstruction.getField();
- DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(field.holder);
- if (enumClass != null) {
- FieldResolutionResult resolutionResult = appView.appInfo().resolveField(field, context);
- if (resolutionResult.isSuccessfulResolution()) {
- eligibleEnums.add(enumClass.getType());
- } else {
- markEnumAsUnboxable(Reason.UNRESOLVABLE_FIELD, enumClass);
- }
- }
- }
-
- private void analyzeInvokeStatic(
- InvokeStatic invokeStatic, Set<DexType> eligibleEnums, ProgramMethod context) {
- DexMethod invokedMethod = invokeStatic.getInvokedMethod();
- DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder);
- if (enumClass != null) {
- DexClassAndMethod method = invokeStatic.lookupSingleTarget(appView, context);
- if (method != null) {
- eligibleEnums.add(enumClass.type);
- } else {
- markEnumAsUnboxable(Reason.INVALID_INVOKE, enumClass);
- }
- }
- }
-
- private void analyzeCheckCast(CheckCast checkCast, Set<DexType> eligibleEnums) {
- // Casts to enum array types are fine as long all enum array creations are valid and have valid
- // usages. Since creations of enum arrays are rewritten to primitive int arrays, enum array
- // casts will continue to work after rewriting to int[] casts. Casts that failed with
- // ClassCastException: "T[] cannot be cast to MyEnum[]" will continue to fail, but with "T[]
- // cannot be cast to int[]".
- //
- // Note that strictly speaking, the rewriting from MyEnum[] to int[] could change the semantics
- // of code that would fail with "int[] cannot be cast to MyEnum[]" in the input. However, javac
- // does not allow such code ("incompatible types"), so we should generally not see such code.
- if (checkCast.getType().isArrayType()) {
- return;
- }
-
- // We are doing a type check, which typically means the in-value is of an upper
- // type and cannot be dealt with.
- // If the cast is on a dynamically typed object, the checkCast can be simply removed.
- // This allows enum array clone and valueOf to work correctly.
- DexProgramClass enumClass =
- getEnumUnboxingCandidateOrNull(checkCast.getType().toBaseType(factory));
- if (enumClass == null) {
- return;
- }
- if (allowCheckCast(checkCast)) {
- eligibleEnums.add(enumClass.type);
- return;
- }
- markEnumAsUnboxable(Reason.DOWN_CAST, enumClass);
- }
-
- private boolean allowCheckCast(CheckCast checkCast) {
- TypeElement objectType = checkCast.object().getDynamicUpperBoundType(appView);
- return objectType.equalUpToNullability(
- TypeElement.fromDexType(checkCast.getType(), definitelyNotNull(), appView));
- }
-
- private void analyzeConstClass(
- ConstClass constClass, Set<DexType> eligibleEnums, ProgramMethod context) {
- // We are using the ConstClass of an enum, which typically means the enum cannot be unboxed.
- // We however allow unboxing if the ConstClass is used only:
- // - as an argument to java.lang.reflect.Array#newInstance(java.lang.Class, int[]), to allow
- // unboxing of:
- // MyEnum[][] a = new MyEnum[x][y];
- // - as an argument to Enum#valueOf, to allow unboxing of:
- // MyEnum a = Enum.valueOf(MyEnum.class, "A");
- // - as a receiver for a name method, to allow unboxing of:
- // MyEnum.class.getName();
- DexType enumType = constClass.getValue();
- if (!enumUnboxingCandidatesInfo.isCandidate(enumType)) {
- return;
- }
- if (constClass.outValue() == null) {
- eligibleEnums.add(enumType);
- return;
- }
- DexProgramClass enumClass = appView.definitionFor(enumType).asProgramClass();
- if (constClass.outValue().hasPhiUsers()) {
- markEnumAsUnboxable(Reason.CONST_CLASS, enumClass);
- return;
- }
- for (Instruction user : constClass.outValue().aliasedUsers()) {
- if (!isLegitimateConstClassUser(user, context, enumClass)) {
- markEnumAsUnboxable(Reason.CONST_CLASS, enumClass);
- return;
- }
- }
- eligibleEnums.add(enumType);
- }
-
- private boolean isLegitimateConstClassUser(
- Instruction user, ProgramMethod context, DexProgramClass enumClass) {
- if (user.isAssume()) {
- if (user.outValue().hasPhiUsers()) {
- return false;
- }
- return true;
- }
-
- if (user.isInvokeStatic()) {
- DexClassAndMethod singleTarget = user.asInvokeStatic().lookupSingleTarget(appView, context);
- if (singleTarget == null) {
- return false;
- }
- if (singleTarget.getReference() == factory.enumMembers.valueOf) {
- // The name data is required for the correct mapping from the enum name to the ordinal
- // in the valueOf utility method.
- addRequiredNameData(enumClass);
- markMethodDependsOnLibraryModelisation(context);
- return true;
- }
- if (singleTarget.getReference()
- == factory.javaLangReflectArrayMembers.newInstanceMethodWithDimensions) {
- markMethodDependsOnLibraryModelisation(context);
- return true;
- }
- }
-
- if (user.isInvokeVirtual()) {
- InvokeVirtual invoke = user.asInvokeVirtual();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (invokedMethod == factory.classMethods.desiredAssertionStatus) {
- // Only valid in the enum's class initializer, since the class constant must be rewritten
- // to LocalEnumUtility.class instead of int.class.
- return context.getDefinition().isClassInitializer() && context.getHolder() == enumClass;
- }
- if (isUnboxableNameMethod(invokedMethod)) {
- return true;
- }
- }
-
- return false;
- }
-
- private void addRequiredNameData(DexProgramClass enumClass) {
- enumUnboxingCandidatesInfo.addRequiredEnumInstanceFieldData(
- enumClass, factory.enumMembers.nameField);
- }
-
- private boolean isUnboxableNameMethod(DexMethod method) {
- return method == factory.classMethods.getName
- || method == factory.classMethods.getCanonicalName
- || method == factory.classMethods.getSimpleName;
- }
-
- private void addNullDependencies(IRCode code, Set<Instruction> uses, Set<DexType> eligibleEnums) {
- for (Instruction use : uses) {
- if (use.isInvokeMethod()) {
- InvokeMethod invokeMethod = use.asInvokeMethod();
- DexMethod invokedMethod = invokeMethod.getInvokedMethod();
- for (DexType paramType : invokedMethod.proto.parameters.values) {
- if (enumUnboxingCandidatesInfo.isCandidate(paramType)) {
- eligibleEnums.add(paramType);
- }
- }
- if (invokeMethod.isInvokeMethodWithReceiver()) {
- DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder);
- if (enumClass != null) {
- markEnumAsUnboxable(Reason.ENUM_METHOD_CALLED_WITH_NULL_RECEIVER, enumClass);
- }
- }
- } else if (use.isFieldPut()) {
- DexType type = use.asFieldInstruction().getField().type;
- if (enumUnboxingCandidatesInfo.isCandidate(type)) {
- eligibleEnums.add(type);
- }
- } else if (use.isReturn()) {
- DexType returnType = code.method().getReference().proto.returnType;
- if (enumUnboxingCandidatesInfo.isCandidate(returnType)) {
- eligibleEnums.add(returnType);
- }
- }
- }
- }
-
- private Reason validateEnumUsages(IRCode code, Value value, DexProgramClass enumClass) {
- Reason result = Reason.ELIGIBLE;
- for (Instruction user : value.uniqueUsers()) {
- Reason reason = instructionAllowEnumUnboxing(user, code, enumClass, value);
- if (reason != Reason.ELIGIBLE) {
- if (markEnumAsUnboxable(reason, enumClass)) {
- return reason;
- }
- // Record that the enum is ineligible, and continue analysis to collect all reasons for
- // debugging.
- result = reason;
- }
- }
- for (Phi phi : value.uniquePhiUsers()) {
- for (Value operand : phi.getOperands()) {
- if (!operand.getType().isNullType()
- && getEnumUnboxingCandidateOrNull(operand.getType()) != enumClass) {
- // All reported reasons from here will be the same (INVALID_PHI), so just return
- // immediately.
- markEnumAsUnboxable(Reason.INVALID_PHI, enumClass);
- return Reason.INVALID_PHI;
- }
- }
- }
- return result;
- }
-
- public void initializeEnumUnboxingCandidates(GraphLens graphLensForPrimaryOptimizationPass) {
- assert enumUnboxingCandidatesInfo == null;
- enumUnboxingCandidatesInfo =
- new EnumUnboxingCandidateAnalysis(appView, this)
- .findCandidates(graphLensForPrimaryOptimizationPass);
- methodsDependingOnLibraryModelisation =
- LongLivedProgramMethodSetBuilder.createConcurrentForIdentitySet(
- graphLensForPrimaryOptimizationPass);
- }
-
- public void unboxEnums(
+ public abstract void unboxEnums(
+ AppView<AppInfoWithLiveness> appView,
IRConverter converter,
- PostMethodProcessor.Builder postBuilder,
+ Builder postMethodProcessorBuilder,
ExecutorService executorService,
OptimizationFeedbackDelayed feedback)
- throws ExecutionException {
- assert candidatesToRemoveInWave.isEmpty();
- EnumDataMap enumDataMap = finishAnalysis();
- assert candidatesToRemoveInWave.isEmpty();
+ throws ExecutionException;
- // At this point the enum unboxing candidates are no longer candidates, they will all be
- // unboxed. We extract the now immutable enums to unbox information and clear the candidate
- // info.
- if (enumUnboxingCandidatesInfo.isEmpty()) {
- assert enumDataMap.isEmpty();
- appView.setUnboxedEnums(enumDataMap);
- return;
- }
+ public abstract void unsetRewriter();
- ImmutableSet<DexType> enumsToUnbox = enumUnboxingCandidatesInfo.candidates();
- ImmutableSet<DexProgramClass> enumClassesToUnbox =
- enumUnboxingCandidatesInfo.candidateClasses();
- LongLivedProgramMethodSetBuilder<ProgramMethodSet> dependencies =
- enumUnboxingCandidatesInfo.allMethodDependencies();
- enumUnboxingCandidatesInfo.clear();
- // Update keep info on any of the enum methods of the removed classes.
- updateKeepInfo(enumsToUnbox);
-
- EnumUnboxingUtilityClasses utilityClasses =
- EnumUnboxingUtilityClasses.builder(appView)
- .synthesizeEnumUnboxingUtilityClasses(enumClassesToUnbox, enumDataMap)
- .build(converter, executorService);
-
- // Fixup the application.
- EnumUnboxingTreeFixer.Result treeFixerResult =
- new EnumUnboxingTreeFixer(
- appView, checkNotNullMethods, enumDataMap, enumClassesToUnbox, utilityClasses)
- .fixupTypeReferences(converter, executorService);
- EnumUnboxingLens enumUnboxingLens = treeFixerResult.getLens();
- appView.setUnboxedEnums(enumDataMap);
-
- // Update the graph lens.
- appView.rewriteWithLens(enumUnboxingLens);
-
- // Enqueue the (lens rewritten) methods that require reprocessing.
- //
- // Note that the reprocessing set must be rewritten to the new enum unboxing lens before pruning
- // the builders with the methods removed by the tree fixer (since these methods references are
- // already fully lens rewritten).
- postBuilder
- .getMethodsToReprocessBuilder()
- .rewrittenWithLens(appView)
- .merge(dependencies)
- .merge(methodsDependingOnLibraryModelisation)
- .removeAll(treeFixerResult.getPrunedItems().getRemovedMethods());
- methodsDependingOnLibraryModelisation.clear();
-
- updateOptimizationInfos(executorService, feedback, treeFixerResult.getPrunedItems());
-
- enumUnboxerRewriter =
- new EnumUnboxingRewriter(
- appView,
- treeFixerResult.getCheckNotNullToCheckNotZeroMapping(),
- converter,
- enumUnboxingLens,
- enumDataMap,
- utilityClasses);
- }
-
- private void updateOptimizationInfos(
- ExecutorService executorService,
- OptimizationFeedbackDelayed feedback,
- PrunedItems prunedItems)
- throws ExecutionException {
- feedback.fixupOptimizationInfos(
- appView,
- executorService,
- new OptimizationInfoFixer() {
- @Override
- public void fixup(DexEncodedField field, MutableFieldOptimizationInfo optimizationInfo) {
- optimizationInfo
- .fixupClassTypeReferences(appView, appView.graphLens())
- .fixupAbstractValue(appView, appView.graphLens());
- }
-
- @Override
- public void fixup(
- DexEncodedMethod method, MutableMethodOptimizationInfo optimizationInfo) {
- optimizationInfo
- .fixupClassTypeReferences(appView, appView.graphLens())
- .fixupAbstractReturnValue(appView, appView.graphLens())
- .fixupInstanceInitializerInfo(appView, appView.graphLens(), prunedItems);
- }
- });
- }
-
- private void updateKeepInfo(Set<DexType> enumsToUnbox) {
- KeepInfoCollection keepInfo = appView.appInfo().getKeepInfo();
- keepInfo.mutate(mutator -> mutator.removeKeepInfoForPrunedItems(enumsToUnbox));
- }
-
- public EnumDataMap finishAnalysis() {
- analyzeInitializers();
- updateEnumUnboxingCandidatesInfo();
- EnumDataMap enumDataMap = analyzeEnumInstances();
- if (debugLogEnabled) {
- // Remove all enums that have been reported as being unboxable.
- debugLogs.keySet().forEach(enumUnboxingCandidatesInfo::removeCandidate);
- reportEnumsAnalysis();
- }
- assert enumDataMap.getUnboxedEnums().size() == enumUnboxingCandidatesInfo.candidates().size();
- return enumDataMap;
- }
-
- private EnumDataMap analyzeEnumInstances() {
- ImmutableMap.Builder<DexType, EnumData> builder = ImmutableMap.builder();
- enumUnboxingCandidatesInfo.forEachCandidateAndRequiredInstanceFieldData(
- (enumClass, instanceFields) -> {
- EnumData data = buildData(enumClass, instanceFields);
- if (data == null) {
- // Reason is already reported at this point.
- enumUnboxingCandidatesInfo.removeCandidate(enumClass);
- return;
- }
- if (!debugLogEnabled || !debugLogs.containsKey(enumClass.getType())) {
- builder.put(enumClass.type, data);
- }
- });
- staticFieldValuesMap.clear();
- return new EnumDataMap(builder.build());
- }
-
- private EnumData buildData(DexProgramClass enumClass, Set<DexField> instanceFields) {
- if (!enumClass.hasStaticFields()) {
- return new EnumData(ImmutableMap.of(), ImmutableMap.of(), ImmutableSet.of(), -1);
- }
-
- // This map holds all the accessible fields to their unboxed value, so we can remap the field
- // read to the unboxed value.
- ImmutableMap.Builder<DexField, Integer> unboxedValues = ImmutableMap.builder();
- // This maps the ordinal to the object state, note that some fields may have been removed,
- // hence the entry is in this map but not the enumToOrdinalMap.
- Int2ReferenceMap<ObjectState> ordinalToObjectState = new Int2ReferenceArrayMap<>();
- // Any fields matching the expected $VALUES content can be recorded here, they have however
- // all the same content.
- ImmutableSet.Builder<DexField> valuesField = ImmutableSet.builder();
- EnumValuesObjectState valuesContents = null;
-
- EnumStaticFieldValues enumStaticFieldValues = staticFieldValuesMap.get(enumClass.type);
- if (enumStaticFieldValues == null) {
- reportFailure(enumClass, new MissingEnumStaticFieldValuesReason());
- return null;
- }
-
- // Step 1: We iterate over the field to find direct enum instance information and the values
- // fields.
- for (DexEncodedField staticField : enumClass.staticFields()) {
- if (factory.enumMembers.isEnumField(staticField, enumClass.type)) {
- ObjectState enumState =
- enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.getReference());
- if (enumState == null) {
- if (staticField.getOptimizationInfo().isDead()) {
- // We don't care about unused field data.
- continue;
- }
- // We could not track the content of that field. We bail out.
- reportFailure(
- enumClass, new MissingObjectStateForEnumInstanceReason(staticField.getReference()));
- return null;
- }
- OptionalInt optionalOrdinal = getOrdinal(enumState);
- if (!optionalOrdinal.isPresent()) {
- reportFailure(
- enumClass,
- new MissingInstanceFieldValueForEnumInstanceReason(
- staticField.getReference(), factory.enumMembers.ordinalField));
- return null;
- }
- int ordinal = optionalOrdinal.getAsInt();
- unboxedValues.put(staticField.getReference(), ordinalToUnboxedInt(ordinal));
- ordinalToObjectState.put(ordinal, enumState);
- } else if (factory.enumMembers.isValuesFieldCandidate(staticField, enumClass.type)) {
- ObjectState valuesState =
- enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.getReference());
- if (valuesState == null) {
- if (staticField.getOptimizationInfo().isDead()) {
- // We don't care about unused field data.
- continue;
- }
- // We could not track the content of that field. We bail out.
- // We could not track the content of that field, and the field could be a values field.
- // We conservatively bail out.
- reportFailure(
- enumClass, new MissingContentsForEnumValuesArrayReason(staticField.getReference()));
- return null;
- }
- assert valuesState.isEnumValuesObjectState();
- assert valuesContents == null
- || valuesContents.equals(valuesState.asEnumValuesObjectState());
- valuesContents = valuesState.asEnumValuesObjectState();
- valuesField.add(staticField.getReference());
- }
- }
-
- // Step 2: We complete the information based on the values content, since some enum instances
- // may be reachable only though the $VALUES field.
- if (valuesContents != null) {
- for (int ordinal = 0; ordinal < valuesContents.getEnumValuesSize(); ordinal++) {
- if (!ordinalToObjectState.containsKey(ordinal)) {
- ObjectState enumState = valuesContents.getObjectStateForOrdinal(ordinal);
- if (enumState.isEmpty()) {
- // If $VALUES is used, we need data for all enums, at least the ordinal.
- return null;
- }
- assert getOrdinal(enumState).isPresent();
- assert getOrdinal(enumState).getAsInt() == ordinal;
- ordinalToObjectState.put(ordinal, enumState);
- }
- }
- }
-
- // The ordinalToObjectState map may have holes at this point, if some enum instances are never
- // used ($VALUES unused or removed, and enum instance field unused or removed), it contains
- // only data for reachable enum instance, that is what we're interested in.
- ImmutableMap<DexField, EnumInstanceFieldKnownData> instanceFieldsData =
- computeRequiredEnumInstanceFieldsData(enumClass, instanceFields, ordinalToObjectState);
- if (instanceFieldsData == null) {
- return null;
- }
-
- return new EnumData(
- instanceFieldsData,
- unboxedValues.build(),
- valuesField.build(),
- valuesContents == null ? EnumData.INVALID_VALUES_SIZE : valuesContents.getEnumValuesSize());
- }
-
- private ImmutableMap<DexField, EnumInstanceFieldKnownData> computeRequiredEnumInstanceFieldsData(
- DexProgramClass enumClass,
- Set<DexField> instanceFields,
- Int2ReferenceMap<ObjectState> ordinalToObjectState) {
- ImmutableMap.Builder<DexField, EnumInstanceFieldKnownData> builder = ImmutableMap.builder();
- for (DexField instanceField : instanceFields) {
- EnumInstanceFieldData fieldData =
- computeRequiredEnumInstanceFieldData(instanceField, enumClass, ordinalToObjectState);
- if (fieldData.isUnknown()) {
- if (!debugLogEnabled) {
- return null;
- }
- builder = null;
- }
- if (builder != null) {
- builder.put(instanceField, fieldData.asEnumFieldKnownData());
- }
- }
- return builder != null ? builder.build() : null;
- }
-
- private EnumInstanceFieldData computeRequiredEnumInstanceFieldData(
- DexField instanceField,
- DexProgramClass enumClass,
- Int2ReferenceMap<ObjectState> ordinalToObjectState) {
- DexEncodedField encodedInstanceField =
- appView.appInfo().resolveFieldOn(enumClass, instanceField).getResolvedField();
- assert encodedInstanceField != null;
- boolean canBeOrdinal = instanceField.type.isIntType();
- ImmutableInt2ReferenceSortedMap.Builder<AbstractValue> data =
- ImmutableInt2ReferenceSortedMap.builder();
- for (Integer ordinal : ordinalToObjectState.keySet()) {
- ObjectState state = ordinalToObjectState.get(ordinal);
- AbstractValue fieldValue = state.getAbstractFieldValue(encodedInstanceField);
- if (!fieldValue.isSingleValue()) {
- reportFailure(
- enumClass, new MissingInstanceFieldValueForEnumInstanceReason(ordinal, instanceField));
- return EnumInstanceFieldUnknownData.getInstance();
- }
- if (!(fieldValue.isSingleNumberValue() || fieldValue.isSingleStringValue())) {
- reportFailure(
- enumClass,
- new UnsupportedInstanceFieldValueForEnumInstanceReason(ordinal, instanceField));
- return EnumInstanceFieldUnknownData.getInstance();
- }
- data.put(ordinalToUnboxedInt(ordinal), fieldValue);
- if (canBeOrdinal) {
- assert fieldValue.isSingleNumberValue();
- int computedValue = fieldValue.asSingleNumberValue().getIntValue();
- if (computedValue != ordinal) {
- canBeOrdinal = false;
- }
- }
- }
- if (canBeOrdinal) {
- return new EnumInstanceFieldOrdinalData();
- }
- return new EnumInstanceFieldMappingData(data.build());
- }
-
- private OptionalInt getOrdinal(ObjectState state) {
- AbstractValue field = state.getAbstractFieldValue(getOrdinalField().getDefinition());
- if (field.isSingleNumberValue()) {
- return OptionalInt.of(field.asSingleNumberValue().getIntValue());
- }
- return OptionalInt.empty();
- }
-
- public void recordEnumState(DexProgramClass clazz, StaticFieldValues staticFieldValues) {
- if (staticFieldValues == null || !staticFieldValues.isEnumStaticFieldValues()) {
- return;
- }
- assert clazz.isEnum();
- EnumStaticFieldValues enumStaticFieldValues = staticFieldValues.asEnumStaticFieldValues();
- if (getEnumUnboxingCandidateOrNull(clazz.type) != null) {
- staticFieldValuesMap.put(clazz.type, enumStaticFieldValues);
- }
- }
-
- private class EnumAccessibilityUseRegistry extends UseRegistry {
-
- private ProgramMethod context;
- private Constraint constraint;
-
- public EnumAccessibilityUseRegistry(DexItemFactory factory) {
- super(factory);
- }
-
- public Constraint computeConstraint(ProgramMethod method) {
- constraint = Constraint.ALWAYS;
- context = method;
- method.registerCodeReferences(this);
- return constraint;
- }
-
- public Constraint deriveConstraint(DexType targetHolder, AccessFlags<?> flags) {
- DexProgramClass contextHolder = context.getHolder();
- if (targetHolder == contextHolder.type) {
- return Constraint.ALWAYS;
- }
- if (flags.isPublic()) {
- return Constraint.ALWAYS;
- }
- if (flags.isPrivate()) {
- // Enum unboxing is currently happening only cf to dex, and no class should be in a nest
- // at this point. If that is the case, we just don't unbox the enum, or we would need to
- // support Constraint.SAMENEST in the enum unboxer.
- assert !contextHolder.isInANest();
- // Only accesses within the enum are allowed since all enum methods and fields will be
- // moved to the same class, and the enum itself becomes an integer, which is
- // accessible everywhere.
- return Constraint.NEVER;
- }
- assert flags.isProtected() || flags.isPackagePrivate();
- // Protected is in practice equivalent to package private in this analysis since we are
- // accessing the member from an enum context where subclassing is limited.
- // At this point we don't support unboxing enums with subclasses, so we assume either
- // same package access, or we just don't unbox.
- // The only protected methods in java.lang.Enum are clone, finalize and the constructor.
- // Besides calls to the constructor in the instance initializer, Enums with calls to such
- // methods cannot be unboxed.
- return targetHolder.isSamePackage(contextHolder.type) ? Constraint.PACKAGE : Constraint.NEVER;
- }
-
- @Override
- public void registerTypeReference(DexType type) {
- if (type.isArrayType()) {
- registerTypeReference(type.toBaseType(factory));
- return;
- }
-
- if (type.isPrimitiveType()) {
- return;
- }
-
- DexClass definition = appView.definitionFor(type);
- if (definition == null) {
- constraint = Constraint.NEVER;
- return;
- }
- constraint = constraint.meet(deriveConstraint(type, definition.accessFlags));
- }
-
- @Override
- public void registerInitClass(DexType type) {
- registerTypeReference(type);
- }
-
- @Override
- public void registerInstanceOf(DexType type) {
- registerTypeReference(type);
- }
-
- @Override
- public void registerNewInstance(DexType type) {
- registerTypeReference(type);
- }
-
- @Override
- public void registerInvokeVirtual(DexMethod method) {
- registerVirtualInvoke(method, false);
- }
-
- @Override
- public void registerInvokeInterface(DexMethod method) {
- registerVirtualInvoke(method, true);
- }
-
- private void registerVirtualInvoke(DexMethod method, boolean isInterface) {
- if (method.holder.isArrayType()) {
- return;
- }
- // Perform resolution and derive unboxing constraints based on the accessibility of the
- // resolution result.
- MethodResolutionResult resolutionResult =
- appView.appInfo().resolveMethod(method, isInterface);
- if (!resolutionResult.isVirtualTarget()) {
- constraint = Constraint.NEVER;
- return;
- }
- registerTarget(
- resolutionResult.getInitialResolutionHolder(), resolutionResult.getSingleTarget());
- }
-
- private void registerTarget(DexClass initialResolutionHolder, DexEncodedMember<?, ?> target) {
- if (target == null) {
- // This will fail at runtime.
- constraint = Constraint.NEVER;
- return;
- }
- DexType resolvedHolder = target.getHolderType();
- if (initialResolutionHolder == null) {
- constraint = Constraint.NEVER;
- return;
- }
- Constraint memberConstraint = deriveConstraint(resolvedHolder, target.getAccessFlags());
- // We also have to take the constraint of the initial resolution holder into account.
- Constraint classConstraint =
- deriveConstraint(initialResolutionHolder.type, initialResolutionHolder.accessFlags);
- Constraint instructionConstraint = memberConstraint.meet(classConstraint);
- constraint = instructionConstraint.meet(constraint);
- }
-
- @Override
- public void registerInvokeDirect(DexMethod method) {
- registerSingleTargetInvoke(method, DexEncodedMethod::isDirectMethod);
- }
-
- @Override
- public void registerInvokeStatic(DexMethod method) {
- registerSingleTargetInvoke(method, DexEncodedMethod::isStatic);
- }
-
- private void registerSingleTargetInvoke(
- DexMethod method, Predicate<DexEncodedMethod> methodValidator) {
- if (method.holder.isArrayType()) {
- return;
- }
- MethodResolutionResult resolutionResult =
- appView.appInfo().unsafeResolveMethodDueToDexFormat(method);
- DexEncodedMethod target = resolutionResult.getSingleTarget();
- if (target == null || !methodValidator.test(target)) {
- constraint = Constraint.NEVER;
- return;
- }
- registerTarget(resolutionResult.getInitialResolutionHolder(), target);
- }
-
- @Override
- public void registerInvokeSuper(DexMethod method) {
- // Invoke-super can only target java.lang.Enum methods since we do not unbox enums with
- // subclasses. Calls to java.lang.Object methods would have resulted in the enum to be marked
- // as unboxable. The methods of java.lang.Enum called are already analyzed in the enum
- // unboxer analysis, so invoke-super is always valid.
- assert method.holder == factory.enumType;
- }
-
- @Override
- public void registerCallSite(DexCallSite callSite) {
- // This is reached after lambda desugaring, so this should not be a lambda call site.
- // We do not unbox enums with invoke custom since it's not clear the accessibility
- // constraints would be correct if the method holding the invoke custom is moved to
- // another class.
- assert appView.options().isGeneratingClassFiles()
- || !factory.isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
- constraint = Constraint.NEVER;
- }
-
- private void registerFieldInstruction(DexField field) {
- FieldResolutionResult fieldResolutionResult = appView.appInfo().resolveField(field, context);
- registerTarget(
- fieldResolutionResult.getInitialResolutionHolder(),
- fieldResolutionResult.getResolvedField());
- }
-
- @Override
- public void registerInstanceFieldRead(DexField field) {
- registerFieldInstruction(field);
- }
-
- @Override
- public void registerInstanceFieldWrite(DexField field) {
- registerFieldInstruction(field);
- }
-
- @Override
- public void registerStaticFieldRead(DexField field) {
- registerFieldInstruction(field);
- }
-
- @Override
- public void registerStaticFieldWrite(DexField field) {
- registerFieldInstruction(field);
- }
- }
-
- private void analyzeInitializers() {
- enumUnboxingCandidatesInfo.forEachCandidate(
- enumClass -> {
- for (DexEncodedMethod directMethod : enumClass.directMethods()) {
- if (directMethod.isInstanceInitializer()) {
- if (directMethod
- .getOptimizationInfo()
- .getContextInsensitiveInstanceInitializerInfo()
- .mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
- if (markEnumAsUnboxable(Reason.INVALID_INIT, enumClass)) {
- break;
- }
- }
- }
- }
- if (enumClass.classInitializationMayHaveSideEffects(appView)) {
- markEnumAsUnboxable(Reason.INVALID_CLINIT, enumClass);
- }
- });
- }
-
- private Reason instructionAllowEnumUnboxing(
- Instruction instruction, IRCode code, DexProgramClass enumClass, Value enumValue) {
- ProgramMethod context = code.context();
- switch (instruction.opcode()) {
- case ASSUME:
- return analyzeAssumeUser(instruction.asAssume(), code, context, enumClass, enumValue);
- case ARRAY_GET:
- return analyzeArrayGetUser(instruction.asArrayGet(), code, context, enumClass, enumValue);
- case ARRAY_LENGTH:
- return analyzeArrayLengthUser(
- instruction.asArrayLength(), code, context, enumClass, enumValue);
- case ARRAY_PUT:
- return analyzeArrayPutUser(instruction.asArrayPut(), code, context, enumClass, enumValue);
- case CHECK_CAST:
- return analyzeCheckCastUser(instruction.asCheckCast(), code, context, enumClass, enumValue);
- case IF:
- return analyzeIfUser(instruction.asIf(), code, context, enumClass, enumValue);
- case INSTANCE_GET:
- return analyzeInstanceGetUser(
- instruction.asInstanceGet(), code, context, enumClass, enumValue);
- case INSTANCE_PUT:
- return analyzeFieldPutUser(
- instruction.asInstancePut(), code, context, enumClass, enumValue);
- case INVOKE_DIRECT:
- case INVOKE_INTERFACE:
- case INVOKE_STATIC:
- case INVOKE_SUPER:
- case INVOKE_VIRTUAL:
- return analyzeInvokeUser(instruction.asInvokeMethod(), code, context, enumClass, enumValue);
- case RETURN:
- return analyzeReturnUser(instruction.asReturn(), code, context, enumClass, enumValue);
- case STATIC_PUT:
- return analyzeFieldPutUser(instruction.asStaticPut(), code, context, enumClass, enumValue);
- default:
- return Reason.OTHER_UNSUPPORTED_INSTRUCTION;
- }
- }
-
- private Reason analyzeAssumeUser(
- Assume assume,
- IRCode code,
- ProgramMethod context,
- DexProgramClass enumClass,
- Value enumValue) {
- return validateEnumUsages(code, assume.outValue(), enumClass);
- }
-
- private Reason analyzeArrayGetUser(
- ArrayGet arrayGet,
- IRCode code,
- ProgramMethod context,
- DexProgramClass enumClass,
- Value enumValue) {
- // MyEnum[] array = ...; array[0]; is valid.
- return Reason.ELIGIBLE;
- }
-
- private Reason analyzeArrayLengthUser(
- ArrayLength arrayLength,
- IRCode code,
- ProgramMethod context,
- DexProgramClass enumClass,
- Value enumValue) {
- // MyEnum[] array = ...; array.length; is valid.
- return Reason.ELIGIBLE;
- }
-
- private Reason analyzeArrayPutUser(
- ArrayPut arrayPut,
- IRCode code,
- ProgramMethod context,
- DexProgramClass enumClass,
- Value enumValue) {
- // MyEnum[] array; array[0] = MyEnum.A; is valid.
- // MyEnum[][] array2d; MyEnum[] array; array2d[0] = array; is valid.
- // MyEnum[]^N array; MyEnum[]^(N-1) element; array[0] = element; is valid.
- // We need to prove that the value to put in and the array have correct types.
- assert arrayPut.getMemberType() == MemberType.OBJECT;
- TypeElement arrayType = arrayPut.array().getType();
- assert arrayType.isArrayType();
- assert arrayType.asArrayType().getBaseType().isClassType();
- ClassTypeElement arrayBaseType = arrayType.asArrayType().getBaseType().asClassType();
- TypeElement valueBaseType = arrayPut.value().getType();
- if (valueBaseType.isArrayType()) {
- assert valueBaseType.asArrayType().getBaseType().isClassType();
- assert valueBaseType.asArrayType().getNesting() == arrayType.asArrayType().getNesting() - 1;
- valueBaseType = valueBaseType.asArrayType().getBaseType();
- }
- if (arrayBaseType.equalUpToNullability(valueBaseType)
- && arrayBaseType.getClassType() == enumClass.type) {
- return Reason.ELIGIBLE;
- }
- return Reason.INVALID_ARRAY_PUT;
- }
-
- private Reason analyzeCheckCastUser(
- CheckCast checkCast,
- IRCode code,
- ProgramMethod context,
- DexProgramClass enumClass,
- Value enumValue) {
- if (allowCheckCast(checkCast)) {
- return Reason.ELIGIBLE;
- }
- return Reason.DOWN_CAST;
- }
-
- // A field put is valid only if the field is not on an enum, and the field type and the valuePut
- // have identical enum type.
- private Reason analyzeFieldPutUser(
- FieldInstruction fieldPut,
- IRCode code,
- ProgramMethod context,
- DexProgramClass enumClass,
- Value enumValue) {
- assert fieldPut.isInstancePut() || fieldPut.isStaticPut();
- DexEncodedField field = appView.appInfo().resolveField(fieldPut.getField()).getResolvedField();
- if (field == null) {
- return Reason.INVALID_FIELD_PUT;
- }
- DexProgramClass dexClass = appView.programDefinitionFor(field.getHolderType(), code.context());
- if (dexClass == null) {
- return Reason.INVALID_FIELD_PUT;
- }
- if (fieldPut.isInstancePut() && fieldPut.asInstancePut().object() == enumValue) {
- return Reason.ELIGIBLE;
- }
- // The put value has to be of the field type.
- if (field.getReference().type.toBaseType(factory) != enumClass.type) {
- return Reason.TYPE_MISMATCH_FIELD_PUT;
- }
- return Reason.ELIGIBLE;
- }
-
- // An If using enum as inValue is valid if it matches e == null
- // or e == X with X of same enum type as e. Ex: if (e == MyEnum.A).
- private Reason analyzeIfUser(
- If theIf, IRCode code, ProgramMethod context, DexProgramClass enumClass, Value enumValue) {
- assert (theIf.getType() == If.Type.EQ || theIf.getType() == If.Type.NE)
- : "Comparing a reference with " + theIf.getType().toString();
- // e == null.
- if (theIf.isZeroTest()) {
- return Reason.ELIGIBLE;
- }
- // e == MyEnum.X
- TypeElement leftType = theIf.lhs().getType();
- TypeElement rightType = theIf.rhs().getType();
- if (leftType.equalUpToNullability(rightType)) {
- assert leftType.isClassType();
- assert leftType.asClassType().getClassType() == enumClass.type;
- return Reason.ELIGIBLE;
- }
- return Reason.INVALID_IF_TYPES;
- }
-
- private Reason analyzeInstanceGetUser(
- InstanceGet instanceGet,
- IRCode code,
- ProgramMethod context,
- DexProgramClass enumClass,
- Value enumValue) {
- assert instanceGet.getField().holder == enumClass.type;
- DexField field = instanceGet.getField();
- enumUnboxingCandidatesInfo.addRequiredEnumInstanceFieldData(enumClass, field);
- return Reason.ELIGIBLE;
- }
-
- // All invokes in the library are invalid, besides a few cherry picked cases such as ordinal().
- private Reason analyzeInvokeUser(
- InvokeMethod invoke,
- IRCode code,
- ProgramMethod context,
- DexProgramClass enumClass,
- Value enumValue) {
- if (invoke.getInvokedMethod().holder.isArrayType()) {
- // The only valid methods is clone for values() to be correct.
- if (invoke.getInvokedMethod().name == factory.cloneMethodName) {
- return Reason.ELIGIBLE;
- }
- return Reason.INVALID_INVOKE_ON_ARRAY;
- }
- DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
- if (singleTarget == null) {
- return Reason.INVALID_INVOKE;
- }
- DexMethod singleTargetReference = singleTarget.getReference();
- DexClass targetHolder = singleTarget.getHolder();
- if (targetHolder.isProgramClass()) {
- if (targetHolder.isEnum() && singleTarget.getDefinition().isInstanceInitializer()) {
- if (code.context().getHolder() == targetHolder && code.method().isClassInitializer()) {
- // The enum instance initializer is allowed to be called only from the enum clinit.
- return Reason.ELIGIBLE;
- } else {
- return Reason.INVALID_INIT;
- }
- }
-
- // Check if this is a checkNotNull() user. In this case, we can create a copy of the method
- // that takes an int instead of java.lang.Object and call that method instead.
- EnumUnboxerMethodClassification classification =
- singleTarget.getOptimizationInfo().getEnumUnboxerMethodClassification();
- if (classification.isCheckNotNullClassification()) {
- CheckNotNullEnumUnboxerMethodClassification checkNotNullClassification =
- classification.asCheckNotNullClassification();
- if (checkNotNullClassification.isUseEligibleForUnboxing(
- invoke.asInvokeStatic(), enumValue)) {
- checkNotNullMethods
- .computeIfAbsent(
- singleTarget.asProgramMethod(), ignoreKey(Sets::newConcurrentHashSet))
- .add(enumClass);
- return Reason.ELIGIBLE;
- }
- }
-
- // Check that the enum-value only flows into parameters whose type exactly matches the
- // enum's type.
- for (int i = 0; i < singleTarget.getParameters().size(); i++) {
- if (invoke.getArgumentForParameter(i) == enumValue
- && singleTarget.getParameter(i).toBaseType(factory) != enumClass.getType()) {
- return new IllegalInvokeWithImpreciseParameterTypeReason(singleTargetReference);
- }
- }
- if (invoke.isInvokeMethodWithReceiver()) {
- Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver();
- if (receiver == enumValue && targetHolder.isInterface()) {
- return Reason.DEFAULT_METHOD_INVOKE;
- }
- }
- return Reason.ELIGIBLE;
- }
-
- if (targetHolder.isClasspathClass()) {
- return Reason.INVALID_INVOKE_CLASSPATH;
- }
-
- assert targetHolder.isLibraryClass();
-
- Reason reason =
- analyzeLibraryInvoke(
- invoke, code, context, enumClass, enumValue, singleTargetReference, targetHolder);
-
- if (reason == Reason.ELIGIBLE) {
- markMethodDependsOnLibraryModelisation(context);
- }
-
- return reason;
- }
-
- private Reason analyzeLibraryInvoke(
- InvokeMethod invoke,
- IRCode code,
- ProgramMethod context,
- DexProgramClass enumClass,
- Value enumValue,
- DexMethod singleTargetReference,
- DexClass targetHolder) {
- // Calls to java.lang.Enum.
- if (targetHolder.getType() == factory.enumType) {
- // TODO(b/147860220): EnumSet and EnumMap may be interesting to model.
- if (singleTargetReference == factory.enumMembers.compareTo
- || singleTargetReference == factory.enumMembers.compareToWithObject) {
- DexProgramClass otherEnumClass =
- getEnumUnboxingCandidateOrNull(invoke.getLastArgument().getType());
- if (otherEnumClass == enumClass || invoke.getLastArgument().getType().isNullType()) {
- return Reason.ELIGIBLE;
- }
- } else if (singleTargetReference == factory.enumMembers.equals) {
- return Reason.ELIGIBLE;
- } else if (singleTargetReference == factory.enumMembers.nameMethod
- || singleTargetReference == factory.enumMembers.toString) {
- assert invoke.asInvokeMethodWithReceiver().getReceiver() == enumValue;
- addRequiredNameData(enumClass);
- return Reason.ELIGIBLE;
- } else if (singleTargetReference == factory.enumMembers.ordinalMethod) {
- return Reason.ELIGIBLE;
- } else if (singleTargetReference == factory.enumMembers.hashCode) {
- return Reason.ELIGIBLE;
- } else if (singleTargetReference == factory.enumMembers.constructor) {
- // Enum constructor call is allowed only if called from an enum initializer.
- if (code.method().isInstanceInitializer() && code.context().getHolder() == enumClass) {
- return Reason.ELIGIBLE;
- }
- }
- return new UnsupportedLibraryInvokeReason(singleTargetReference);
- }
-
- // Calls to java.lang.Object.
- if (targetHolder.getType() == factory.objectType) {
- // Object#getClass without outValue is important since R8 rewrites explicit null checks to
- // such instructions.
- if (singleTargetReference == factory.objectMembers.getClass && invoke.hasUnusedOutValue()) {
- // This is a hidden null check.
- return Reason.ELIGIBLE;
- }
- return new UnsupportedLibraryInvokeReason(singleTargetReference);
- }
-
- // Calls to java.lang.Objects.
- if (targetHolder.getType() == factory.objectsType) {
- // Objects#requireNonNull is important since R8 rewrites explicit null checks to such
- // instructions.
- if (singleTargetReference == factory.objectsMethods.requireNonNull
- || singleTargetReference == factory.objectsMethods.requireNonNullWithMessage) {
- return Reason.ELIGIBLE;
- }
- return new UnsupportedLibraryInvokeReason(singleTargetReference);
- }
-
- // Calls to java.lang.String.
- if (targetHolder.getType() == factory.stringType) {
- if (singleTargetReference == factory.stringMembers.valueOf) {
- addRequiredNameData(enumClass);
- return Reason.ELIGIBLE;
- }
- return new UnsupportedLibraryInvokeReason(singleTargetReference);
- }
-
- // Calls to java.lang.StringBuilder and java.lang.StringBuffer.
- if (targetHolder.getType() == factory.stringBuilderType
- || targetHolder.getType() == factory.stringBufferType) {
- if (singleTargetReference == factory.stringBuilderMethods.appendObject
- || singleTargetReference == factory.stringBufferMethods.appendObject) {
- addRequiredNameData(enumClass);
- return Reason.ELIGIBLE;
- }
- return new UnsupportedLibraryInvokeReason(singleTargetReference);
- }
-
- // Calls to java.lang.System.
- if (targetHolder.getType() == factory.javaLangSystemType) {
- if (singleTargetReference == factory.javaLangSystemMethods.arraycopy) {
- // Important for Kotlin 1.5 enums, which use arraycopy to create a copy of $VALUES instead
- // of int[].clone().
- return Reason.ELIGIBLE;
- }
- if (singleTargetReference == factory.javaLangSystemMethods.identityHashCode) {
- // Important for proto enum unboxing.
- return Reason.ELIGIBLE;
- }
- return new UnsupportedLibraryInvokeReason(singleTargetReference);
- }
-
- // Unsupported holder.
- return new UnsupportedLibraryInvokeReason(singleTargetReference);
- }
-
- // Return is used for valueOf methods.
- private Reason analyzeReturnUser(
- Return theReturn,
- IRCode code,
- ProgramMethod context,
- DexProgramClass enumClass,
- Value enumValue) {
- DexType returnType = context.getReturnType();
- if (returnType != enumClass.type && returnType.toBaseType(factory) != enumClass.type) {
- return Reason.IMPLICIT_UP_CAST_IN_RETURN;
- }
- return Reason.ELIGIBLE;
- }
-
- private void reportEnumsAnalysis() {
- assert debugLogEnabled;
- Reporter reporter = appView.reporter();
- Set<DexType> candidates = enumUnboxingCandidatesInfo.candidates();
- reporter.info(
- new StringDiagnostic(
- "Unboxed " + candidates.size() + " enums: " + Arrays.toString(candidates.toArray())));
-
- StringBuilder sb =
- new StringBuilder("Unable to unbox ")
- .append(debugLogs.size())
- .append(" enums.")
- .append(System.lineSeparator())
- .append(System.lineSeparator());
-
- // Sort by the number of reasons that prevent enum unboxing.
- TreeMap<DexType, List<Reason>> sortedDebugLogs =
- new TreeMap<>(
- Comparator.<DexType>comparingInt(x -> debugLogs.get(x).size())
- .thenComparing(Function.identity()));
- sortedDebugLogs.putAll(debugLogs);
-
- // Print the pinned enums and remove them from further reporting.
- List<DexType> pinned = new ArrayList<>();
- Iterator<Entry<DexType, List<Reason>>> sortedDebugLogIterator =
- sortedDebugLogs.entrySet().iterator();
- while (sortedDebugLogIterator.hasNext()) {
- Entry<DexType, List<Reason>> entry = sortedDebugLogIterator.next();
- List<Reason> reasons = entry.getValue();
- if (reasons.size() > 1) {
- break;
- }
- if (reasons.get(0) == Reason.PINNED) {
- pinned.add(entry.getKey());
- sortedDebugLogIterator.remove();
- }
- }
- if (!pinned.isEmpty()) {
- sb.append("Pinned: ").append(Arrays.toString(pinned.toArray()));
- }
-
- // Print the reasons for each unboxable enum.
- sortedDebugLogs.forEach(
- (type, reasons) -> {
- sb.append(type).append(" (").append(reasons.size()).append(" reasons):");
- HashMultiset.create(reasons)
- .forEachEntry(
- (reason, count) ->
- sb.append(System.lineSeparator())
- .append(" - ")
- .append(reason)
- .append(" (")
- .append(count)
- .append(")"));
- sb.append(System.lineSeparator());
- });
-
- sb.append(System.lineSeparator());
-
- // Print information about how often a given Reason kind prevents enum unboxing.
- Object2IntMap<Object> reasonKindCount = new Object2IntOpenHashMap<>();
- debugLogs.forEach(
- (type, reasons) ->
- reasons.forEach(
- reason ->
- reasonKindCount.put(reason.getKind(), reasonKindCount.getInt(reason) + 1)));
- List<Object> differentReasonKinds = new ArrayList<>(reasonKindCount.keySet());
- differentReasonKinds.sort(
- (reasonKind, other) -> {
- int freq = reasonKindCount.getInt(reasonKind) - reasonKindCount.getInt(other);
- return freq != 0
- ? freq
- : System.identityHashCode(reasonKind) - System.identityHashCode(other);
- });
- differentReasonKinds.forEach(
- reasonKind ->
- sb.append(reasonKind)
- .append(" (")
- .append(reasonKindCount.getInt(reasonKind))
- .append(")")
- .append(System.lineSeparator()));
-
- reporter.info(new StringDiagnostic(sb.toString()));
- }
-
- boolean reportFailure(DexProgramClass enumClass, Reason reason) {
- return reportFailure(enumClass.getType(), reason);
- }
-
- /** Returns true if the failure was reported. */
- boolean reportFailure(DexType enumType, Reason reason) {
- if (debugLogEnabled) {
- debugLogs
- .computeIfAbsent(enumType, ignore -> Collections.synchronizedList(new ArrayList<>()))
- .add(reason);
- return true;
- }
- return false;
- }
-
- public Set<Phi> rewriteCode(IRCode code, MethodProcessor methodProcessor) {
- // This has no effect during primary processing since the enumUnboxerRewriter is set
- // in between primary and post processing.
- if (enumUnboxerRewriter != null) {
- return enumUnboxerRewriter.rewriteCode(code, methodProcessor);
- }
- return Sets.newIdentityHashSet();
- }
-
- public void unsetRewriter() {
- enumUnboxerRewriter = null;
- }
+ public abstract void updateEnumUnboxingCandidatesInfo();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
new file mode 100644
index 0000000..e4229c2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -0,0 +1,1620 @@
+// Copyright (c) 2021, 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.enums;
+
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.ir.code.Opcodes.ARRAY_GET;
+import static com.android.tools.r8.ir.code.Opcodes.ARRAY_LENGTH;
+import static com.android.tools.r8.ir.code.Opcodes.ARRAY_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
+import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS;
+import static com.android.tools.r8.ir.code.Opcodes.IF;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_CUSTOM;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+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;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMember;
+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.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues.EnumStaticFieldValues;
+import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.EnumValuesObjectState;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.code.ArrayGet;
+import com.android.tools.r8.ir.code.ArrayLength;
+import com.android.tools.r8.ir.code.ArrayPut;
+import com.android.tools.r8.ir.code.Assume;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.ConstClass;
+import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeCustom;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.PostMethodProcessor.Builder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
+import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
+import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldMappingData;
+import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldOrdinalData;
+import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldUnknownData;
+import com.android.tools.r8.ir.optimize.enums.classification.CheckNotNullEnumUnboxerMethodClassification;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.IllegalInvokeWithImpreciseParameterTypeReason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.MissingContentsForEnumValuesArrayReason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.MissingEnumStaticFieldValuesReason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.MissingInstanceFieldValueForEnumInstanceReason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.MissingObjectStateForEnumInstanceReason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.UnsupportedInstanceFieldValueForEnumInstanceReason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.UnsupportedLibraryInvokeReason;
+import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.MutableMethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepInfoCollection;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
+import com.android.tools.r8.utils.collections.LongLivedClassSetBuilder;
+import com.android.tools.r8.utils.collections.LongLivedProgramMethodMapBuilder;
+import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
+import com.android.tools.r8.utils.collections.ProgramMethodMap;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.OptionalInt;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+public class EnumUnboxerImpl extends EnumUnboxer {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final DexItemFactory factory;
+ // Map the enum candidates with their dependencies, i.e., the methods to reprocess for the given
+ // enum if the optimization eventually decides to unbox it.
+ private EnumUnboxingCandidateInfoCollection enumUnboxingCandidatesInfo;
+ private final Set<DexProgramClass> candidatesToRemoveInWave = Sets.newConcurrentHashSet();
+ private final Map<DexType, EnumStaticFieldValues> staticFieldValuesMap =
+ new ConcurrentHashMap<>();
+
+ // Methods depending on library modelisation need to be reprocessed so they are peephole
+ // optimized.
+ private LongLivedProgramMethodSetBuilder<ProgramMethodSet> methodsDependingOnLibraryModelisation;
+
+ // Map from checkNotNull() methods to the enums that use the given method.
+ private LongLivedProgramMethodMapBuilder<LongLivedClassSetBuilder<DexProgramClass>>
+ checkNotNullMethodsBuilder;
+
+ private final DexClassAndField ordinalField;
+
+ private EnumUnboxingRewriter enumUnboxerRewriter;
+
+ private final boolean debugLogEnabled;
+ private final Map<DexType, List<Reason>> debugLogs;
+
+ EnumUnboxerImpl(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ this.factory = appView.dexItemFactory();
+ if (appView.options().testing.enableEnumUnboxingDebugLogs) {
+ debugLogEnabled = true;
+ debugLogs = new ConcurrentHashMap<>();
+ } else {
+ debugLogEnabled = false;
+ debugLogs = null;
+ }
+ assert !appView.options().debug;
+ ordinalField =
+ appView.appInfo().resolveField(factory.enumMembers.ordinalField).getResolutionPair();
+ }
+
+ public static int ordinalToUnboxedInt(int ordinal) {
+ return ordinal + 1;
+ }
+
+ public DexClassAndField getOrdinalField() {
+ return ordinalField;
+ }
+
+ @Override
+ public void updateEnumUnboxingCandidatesInfo() {
+ for (DexProgramClass candidate : candidatesToRemoveInWave) {
+ enumUnboxingCandidatesInfo.removeCandidate(candidate);
+ }
+ candidatesToRemoveInWave.clear();
+ }
+
+ /**
+ * Returns true if {@param enumClass} was marked as being unboxable.
+ *
+ * <p>Note that, if debug logging is enabled, {@param enumClass} is not marked unboxable until the
+ * enum unboxing analysis has finished. This is to ensure completeness of the reason reporting.
+ */
+ private boolean markEnumAsUnboxable(Reason reason, DexProgramClass enumClass) {
+ assert enumClass.isEnum();
+ if (!reportFailure(enumClass, reason)) {
+ // The failure was not reported, meaning debug logging is disabled.
+ candidatesToRemoveInWave.add(enumClass);
+ return true;
+ }
+ return false;
+ }
+
+ private void markMethodDependsOnLibraryModelisation(ProgramMethod method) {
+ methodsDependingOnLibraryModelisation.add(method, appView.graphLens());
+ }
+
+ private DexProgramClass getEnumUnboxingCandidateOrNull(TypeElement lattice) {
+ if (lattice.isClassType()) {
+ DexType classType = lattice.asClassType().getClassType();
+ return getEnumUnboxingCandidateOrNull(classType);
+ }
+ if (lattice.isArrayType()) {
+ ArrayTypeElement arrayType = lattice.asArrayType();
+ if (arrayType.getBaseType().isClassType()) {
+ return getEnumUnboxingCandidateOrNull(arrayType.getBaseType());
+ }
+ }
+ return null;
+ }
+
+ private DexProgramClass getEnumUnboxingCandidateOrNull(DexType type) {
+ if (type.isArrayType()) {
+ return getEnumUnboxingCandidateOrNull(type.toBaseType(appView.dexItemFactory()));
+ }
+ if (type.isPrimitiveType() || type.isVoidType()) {
+ return null;
+ }
+ assert type.isClassType();
+ return enumUnboxingCandidatesInfo.getCandidateClassOrNull(type);
+ }
+
+ @Override
+ public void analyzeEnums(IRCode code, MutableMethodConversionOptions conversionOptions) {
+ Set<DexType> eligibleEnums = Sets.newIdentityHashSet();
+ for (BasicBlock block : code.blocks) {
+ for (Instruction instruction : block.getInstructions()) {
+ Value outValue = instruction.outValue();
+ if (outValue != null) {
+ DexProgramClass enumClass =
+ getEnumUnboxingCandidateOrNull(outValue.getDynamicUpperBoundType(appView));
+ if (enumClass != null) {
+ Reason reason = validateEnumUsages(code, outValue, enumClass);
+ if (reason == Reason.ELIGIBLE) {
+ eligibleEnums.add(enumClass.type);
+ }
+ }
+ if (outValue.getType().isNullType()) {
+ addNullDependencies(code, outValue.uniqueUsers(), eligibleEnums);
+ }
+ } else {
+ if (instruction.isInvokeMethod()) {
+ DexProgramClass enumClass =
+ getEnumUnboxingCandidateOrNull(instruction.asInvokeMethod().getReturnType());
+ if (enumClass != null) {
+ eligibleEnums.add(enumClass.type);
+ }
+ }
+ }
+ switch (instruction.opcode()) {
+ case CONST_CLASS:
+ analyzeConstClass(instruction.asConstClass(), eligibleEnums, code.context());
+ break;
+ case CHECK_CAST:
+ analyzeCheckCast(instruction.asCheckCast(), eligibleEnums);
+ break;
+ case INVOKE_CUSTOM:
+ analyzeInvokeCustom(instruction.asInvokeCustom(), eligibleEnums);
+ break;
+ case INVOKE_STATIC:
+ analyzeInvokeStatic(instruction.asInvokeStatic(), eligibleEnums, code.context());
+ break;
+ case STATIC_GET:
+ case INSTANCE_GET:
+ case STATIC_PUT:
+ case INSTANCE_PUT:
+ analyzeFieldInstruction(
+ instruction.asFieldInstruction(), eligibleEnums, code.context());
+ break;
+ default: // Nothing to do for other instructions.
+ }
+ }
+ for (Phi phi : block.getPhis()) {
+ DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(phi.getType());
+ if (enumClass != null) {
+ Reason reason = validateEnumUsages(code, phi, enumClass);
+ if (reason == Reason.ELIGIBLE) {
+ eligibleEnums.add(enumClass.type);
+ }
+ }
+ if (phi.getType().isNullType()) {
+ addNullDependencies(code, phi.uniqueUsers(), eligibleEnums);
+ }
+ }
+ }
+ if (!eligibleEnums.isEmpty()) {
+ for (DexType eligibleEnum : eligibleEnums) {
+ enumUnboxingCandidatesInfo.addMethodDependency(eligibleEnum, code.context());
+ }
+ }
+ if (methodsDependingOnLibraryModelisation.contains(code.context(), appView.graphLens())) {
+ conversionOptions.disablePeepholeOptimizations();
+ }
+ }
+
+ private void analyzeInvokeCustom(InvokeCustom invoke, Set<DexType> eligibleEnums) {
+ Consumer<DexType> typeReferenceConsumer =
+ type -> {
+ DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(type);
+ if (enumClass != null) {
+ eligibleEnums.add(enumClass.getType());
+ }
+ };
+ invoke.getCallSite().getMethodProto().forEachType(typeReferenceConsumer);
+ invoke
+ .getCallSite()
+ .getBootstrapArgs()
+ .forEach(
+ bootstrapArgument -> {
+ if (bootstrapArgument.isDexValueMethodHandle()) {
+ DexMethodHandle methodHandle =
+ bootstrapArgument.asDexValueMethodHandle().getValue();
+ if (methodHandle.isMethodHandle()) {
+ DexMethod method = methodHandle.asMethod();
+ DexProgramClass enumClass =
+ getEnumUnboxingCandidateOrNull(method.getHolderType());
+ if (enumClass != null) {
+ markEnumAsUnboxable(Reason.INVALID_INVOKE_CUSTOM, enumClass);
+ } else {
+ method.getProto().forEachType(typeReferenceConsumer);
+ }
+ } else {
+ assert methodHandle.isFieldHandle();
+ DexField field = methodHandle.asField();
+ DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(field.getHolderType());
+ if (enumClass != null) {
+ markEnumAsUnboxable(Reason.INVALID_INVOKE_CUSTOM, enumClass);
+ } else {
+ typeReferenceConsumer.accept(field.getType());
+ }
+ }
+ } else if (bootstrapArgument.isDexValueMethodType()) {
+ DexProto proto = bootstrapArgument.asDexValueMethodType().getValue();
+ proto.forEachType(typeReferenceConsumer);
+ }
+ });
+ }
+
+ private void analyzeFieldInstruction(
+ FieldInstruction fieldInstruction, Set<DexType> eligibleEnums, ProgramMethod context) {
+ DexField field = fieldInstruction.getField();
+ DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(field.holder);
+ if (enumClass != null) {
+ FieldResolutionResult resolutionResult = appView.appInfo().resolveField(field, context);
+ if (resolutionResult.isSuccessfulResolution()) {
+ eligibleEnums.add(enumClass.getType());
+ } else {
+ markEnumAsUnboxable(Reason.UNRESOLVABLE_FIELD, enumClass);
+ }
+ }
+ }
+
+ private void analyzeInvokeStatic(
+ InvokeStatic invokeStatic, Set<DexType> eligibleEnums, ProgramMethod context) {
+ DexMethod invokedMethod = invokeStatic.getInvokedMethod();
+ DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder);
+ if (enumClass != null) {
+ DexClassAndMethod method = invokeStatic.lookupSingleTarget(appView, context);
+ if (method != null) {
+ eligibleEnums.add(enumClass.type);
+ } else {
+ markEnumAsUnboxable(Reason.INVALID_INVOKE, enumClass);
+ }
+ }
+ }
+
+ private void analyzeCheckCast(CheckCast checkCast, Set<DexType> eligibleEnums) {
+ // Casts to enum array types are fine as long all enum array creations are valid and have valid
+ // usages. Since creations of enum arrays are rewritten to primitive int arrays, enum array
+ // casts will continue to work after rewriting to int[] casts. Casts that failed with
+ // ClassCastException: "T[] cannot be cast to MyEnum[]" will continue to fail, but with "T[]
+ // cannot be cast to int[]".
+ //
+ // Note that strictly speaking, the rewriting from MyEnum[] to int[] could change the semantics
+ // of code that would fail with "int[] cannot be cast to MyEnum[]" in the input. However, javac
+ // does not allow such code ("incompatible types"), so we should generally not see such code.
+ if (checkCast.getType().isArrayType()) {
+ return;
+ }
+
+ // We are doing a type check, which typically means the in-value is of an upper
+ // type and cannot be dealt with.
+ // If the cast is on a dynamically typed object, the checkCast can be simply removed.
+ // This allows enum array clone and valueOf to work correctly.
+ DexProgramClass enumClass =
+ getEnumUnboxingCandidateOrNull(checkCast.getType().toBaseType(factory));
+ if (enumClass == null) {
+ return;
+ }
+ if (allowCheckCast(checkCast)) {
+ eligibleEnums.add(enumClass.type);
+ return;
+ }
+ markEnumAsUnboxable(Reason.DOWN_CAST, enumClass);
+ }
+
+ private boolean allowCheckCast(CheckCast checkCast) {
+ TypeElement objectType = checkCast.object().getDynamicUpperBoundType(appView);
+ return objectType.equalUpToNullability(
+ TypeElement.fromDexType(checkCast.getType(), definitelyNotNull(), appView));
+ }
+
+ private void analyzeConstClass(
+ ConstClass constClass, Set<DexType> eligibleEnums, ProgramMethod context) {
+ // We are using the ConstClass of an enum, which typically means the enum cannot be unboxed.
+ // We however allow unboxing if the ConstClass is used only:
+ // - as an argument to java.lang.reflect.Array#newInstance(java.lang.Class, int[]), to allow
+ // unboxing of:
+ // MyEnum[][] a = new MyEnum[x][y];
+ // - as an argument to Enum#valueOf, to allow unboxing of:
+ // MyEnum a = Enum.valueOf(MyEnum.class, "A");
+ // - as a receiver for a name method, to allow unboxing of:
+ // MyEnum.class.getName();
+ DexType enumType = constClass.getValue();
+ if (!enumUnboxingCandidatesInfo.isCandidate(enumType)) {
+ return;
+ }
+ if (constClass.outValue() == null) {
+ eligibleEnums.add(enumType);
+ return;
+ }
+ DexProgramClass enumClass = appView.definitionFor(enumType).asProgramClass();
+ if (constClass.outValue().hasPhiUsers()) {
+ markEnumAsUnboxable(Reason.CONST_CLASS, enumClass);
+ return;
+ }
+ for (Instruction user : constClass.outValue().aliasedUsers()) {
+ if (!isLegitimateConstClassUser(user, context, enumClass)) {
+ markEnumAsUnboxable(Reason.CONST_CLASS, enumClass);
+ return;
+ }
+ }
+ eligibleEnums.add(enumType);
+ }
+
+ private boolean isLegitimateConstClassUser(
+ Instruction user, ProgramMethod context, DexProgramClass enumClass) {
+ if (user.isAssume()) {
+ if (user.outValue().hasPhiUsers()) {
+ return false;
+ }
+ return true;
+ }
+
+ if (user.isInvokeStatic()) {
+ DexClassAndMethod singleTarget = user.asInvokeStatic().lookupSingleTarget(appView, context);
+ if (singleTarget == null) {
+ return false;
+ }
+ if (singleTarget.getReference() == factory.enumMembers.valueOf) {
+ // The name data is required for the correct mapping from the enum name to the ordinal
+ // in the valueOf utility method.
+ addRequiredNameData(enumClass);
+ markMethodDependsOnLibraryModelisation(context);
+ return true;
+ }
+ if (singleTarget.getReference()
+ == factory.javaLangReflectArrayMembers.newInstanceMethodWithDimensions) {
+ markMethodDependsOnLibraryModelisation(context);
+ return true;
+ }
+ }
+
+ if (user.isInvokeVirtual()) {
+ InvokeVirtual invoke = user.asInvokeVirtual();
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ if (invokedMethod == factory.classMethods.desiredAssertionStatus) {
+ // Only valid in the enum's class initializer, since the class constant must be rewritten
+ // to LocalEnumUtility.class instead of int.class.
+ return context.getDefinition().isClassInitializer() && context.getHolder() == enumClass;
+ }
+ if (isUnboxableNameMethod(invokedMethod)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void addRequiredNameData(DexProgramClass enumClass) {
+ enumUnboxingCandidatesInfo.addRequiredEnumInstanceFieldData(
+ enumClass, factory.enumMembers.nameField);
+ }
+
+ private boolean isUnboxableNameMethod(DexMethod method) {
+ return method == factory.classMethods.getName
+ || method == factory.classMethods.getCanonicalName
+ || method == factory.classMethods.getSimpleName;
+ }
+
+ private void addNullDependencies(IRCode code, Set<Instruction> uses, Set<DexType> eligibleEnums) {
+ for (Instruction use : uses) {
+ if (use.isInvokeMethod()) {
+ InvokeMethod invokeMethod = use.asInvokeMethod();
+ DexMethod invokedMethod = invokeMethod.getInvokedMethod();
+ for (DexType paramType : invokedMethod.proto.parameters.values) {
+ if (enumUnboxingCandidatesInfo.isCandidate(paramType)) {
+ eligibleEnums.add(paramType);
+ }
+ }
+ if (invokeMethod.isInvokeMethodWithReceiver()) {
+ DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder);
+ if (enumClass != null) {
+ markEnumAsUnboxable(Reason.ENUM_METHOD_CALLED_WITH_NULL_RECEIVER, enumClass);
+ }
+ }
+ } else if (use.isFieldPut()) {
+ DexType type = use.asFieldInstruction().getField().type;
+ if (enumUnboxingCandidatesInfo.isCandidate(type)) {
+ eligibleEnums.add(type);
+ }
+ } else if (use.isReturn()) {
+ DexType returnType = code.method().getReference().proto.returnType;
+ if (enumUnboxingCandidatesInfo.isCandidate(returnType)) {
+ eligibleEnums.add(returnType);
+ }
+ }
+ }
+ }
+
+ private Reason validateEnumUsages(IRCode code, Value value, DexProgramClass enumClass) {
+ Reason result = Reason.ELIGIBLE;
+ for (Instruction user : value.uniqueUsers()) {
+ Reason reason = instructionAllowEnumUnboxing(user, code, enumClass, value);
+ if (reason != Reason.ELIGIBLE) {
+ if (markEnumAsUnboxable(reason, enumClass)) {
+ return reason;
+ }
+ // Record that the enum is ineligible, and continue analysis to collect all reasons for
+ // debugging.
+ result = reason;
+ }
+ }
+ for (Phi phi : value.uniquePhiUsers()) {
+ for (Value operand : phi.getOperands()) {
+ if (!operand.getType().isNullType()
+ && getEnumUnboxingCandidateOrNull(operand.getType()) != enumClass) {
+ // All reported reasons from here will be the same (INVALID_PHI), so just return
+ // immediately.
+ markEnumAsUnboxable(Reason.INVALID_PHI, enumClass);
+ return Reason.INVALID_PHI;
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public void prepareForPrimaryOptimizationPass(GraphLens graphLensForPrimaryOptimizationPass) {
+ assert appView.graphLens() == graphLensForPrimaryOptimizationPass;
+ initializeCheckNotNullMethods(graphLensForPrimaryOptimizationPass);
+ initializeEnumUnboxingCandidates(graphLensForPrimaryOptimizationPass);
+ }
+
+ private void initializeCheckNotNullMethods(GraphLens graphLensForPrimaryOptimizationPass) {
+ assert checkNotNullMethodsBuilder == null;
+ checkNotNullMethodsBuilder =
+ LongLivedProgramMethodMapBuilder.createConcurrentBuilderForNonConcurrentMap(
+ graphLensForPrimaryOptimizationPass);
+ }
+
+ private void initializeEnumUnboxingCandidates(GraphLens graphLensForPrimaryOptimizationPass) {
+ assert enumUnboxingCandidatesInfo == null;
+ enumUnboxingCandidatesInfo =
+ new EnumUnboxingCandidateAnalysis(appView, this)
+ .findCandidates(graphLensForPrimaryOptimizationPass);
+ methodsDependingOnLibraryModelisation =
+ LongLivedProgramMethodSetBuilder.createConcurrentForIdentitySet(
+ graphLensForPrimaryOptimizationPass);
+ }
+
+ @Override
+ public void unboxEnums(
+ AppView<AppInfoWithLiveness> appView,
+ IRConverter converter,
+ Builder postMethodProcessorBuilder,
+ ExecutorService executorService,
+ OptimizationFeedbackDelayed feedback)
+ throws ExecutionException {
+ assert feedback.noUpdatesLeft();
+
+ assert candidatesToRemoveInWave.isEmpty();
+ EnumDataMap enumDataMap = finishAnalysis();
+ assert candidatesToRemoveInWave.isEmpty();
+
+ // At this point the enum unboxing candidates are no longer candidates, they will all be
+ // unboxed. We extract the now immutable enums to unbox information and clear the candidate
+ // info.
+ appView.setUnboxedEnums(enumDataMap);
+
+ if (enumUnboxingCandidatesInfo.isEmpty()) {
+ assert enumDataMap.isEmpty();
+ return;
+ }
+
+ ImmutableSet<DexType> enumsToUnbox = enumUnboxingCandidatesInfo.candidates();
+ ImmutableSet<DexProgramClass> enumClassesToUnbox =
+ enumUnboxingCandidatesInfo.candidateClasses();
+ LongLivedProgramMethodSetBuilder<ProgramMethodSet> dependencies =
+ enumUnboxingCandidatesInfo.allMethodDependencies();
+ enumUnboxingCandidatesInfo.clear();
+ // Update keep info on any of the enum methods of the removed classes.
+ updateKeepInfo(enumsToUnbox);
+
+ EnumUnboxingUtilityClasses utilityClasses =
+ EnumUnboxingUtilityClasses.builder(appView)
+ .synthesizeEnumUnboxingUtilityClasses(enumClassesToUnbox, enumDataMap)
+ .build(converter, executorService);
+
+ // Fixup the application.
+ ProgramMethodMap<Set<DexProgramClass>> checkNotNullMethods =
+ checkNotNullMethodsBuilder
+ .rewrittenWithLens(appView, (enumClasses, appliedGraphLens) -> enumClasses)
+ .build(appView, builder -> builder.build(appView));
+ EnumUnboxingTreeFixer.Result treeFixerResult =
+ new EnumUnboxingTreeFixer(
+ appView, checkNotNullMethods, enumDataMap, enumClassesToUnbox, utilityClasses)
+ .fixupTypeReferences(converter, executorService);
+ EnumUnboxingLens enumUnboxingLens = treeFixerResult.getLens();
+
+ // Update the graph lens.
+ appView.rewriteWithLens(enumUnboxingLens);
+
+ // Enqueue the (lens rewritten) methods that require reprocessing.
+ //
+ // Note that the reprocessing set must be rewritten to the new enum unboxing lens before pruning
+ // the builders with the methods removed by the tree fixer (since these methods references are
+ // already fully lens rewritten).
+ postMethodProcessorBuilder
+ .getMethodsToReprocessBuilder()
+ .rewrittenWithLens(appView)
+ .merge(dependencies)
+ .merge(methodsDependingOnLibraryModelisation)
+ .removeAll(treeFixerResult.getPrunedItems().getRemovedMethods());
+ methodsDependingOnLibraryModelisation.clear();
+
+ updateOptimizationInfos(executorService, feedback, treeFixerResult);
+
+ enumUnboxerRewriter =
+ new EnumUnboxingRewriter(
+ appView,
+ treeFixerResult.getCheckNotNullToCheckNotZeroMapping(),
+ converter,
+ enumUnboxingLens,
+ enumDataMap,
+ utilityClasses);
+ }
+
+ private void updateOptimizationInfos(
+ ExecutorService executorService,
+ OptimizationFeedbackDelayed feedback,
+ EnumUnboxingTreeFixer.Result treeFixerResult)
+ throws ExecutionException {
+ feedback.fixupOptimizationInfos(
+ appView,
+ executorService,
+ new OptimizationInfoFixer() {
+ @Override
+ public void fixup(DexEncodedField field, MutableFieldOptimizationInfo optimizationInfo) {
+ optimizationInfo
+ .fixupClassTypeReferences(appView, appView.graphLens())
+ .fixupAbstractValue(appView, appView.graphLens());
+ }
+
+ @Override
+ public void fixup(
+ DexEncodedMethod method, MutableMethodOptimizationInfo optimizationInfo) {
+ optimizationInfo
+ .fixupClassTypeReferences(appView, appView.graphLens())
+ .fixupAbstractReturnValue(appView, appView.graphLens())
+ .fixupInstanceInitializerInfo(
+ appView, appView.graphLens(), treeFixerResult.getPrunedItems());
+
+ // Clear the enum unboxer method classification for check-not-null methods (these
+ // classifications are transferred to the synthesized check-not-zero methods by now).
+ if (!treeFixerResult
+ .getCheckNotNullToCheckNotZeroMapping()
+ .containsValue(method.getReference())) {
+ optimizationInfo.unsetEnumUnboxerMethodClassification();
+ }
+ }
+ });
+ }
+
+ private void updateKeepInfo(Set<DexType> enumsToUnbox) {
+ KeepInfoCollection keepInfo = appView.appInfo().getKeepInfo();
+ keepInfo.mutate(mutator -> mutator.removeKeepInfoForPrunedItems(enumsToUnbox));
+ }
+
+ public EnumDataMap finishAnalysis() {
+ analyzeInitializers();
+ updateEnumUnboxingCandidatesInfo();
+ EnumDataMap enumDataMap = analyzeEnumInstances();
+ if (debugLogEnabled) {
+ // Remove all enums that have been reported as being unboxable.
+ debugLogs.keySet().forEach(enumUnboxingCandidatesInfo::removeCandidate);
+ reportEnumsAnalysis();
+ }
+ assert enumDataMap.getUnboxedEnums().size() == enumUnboxingCandidatesInfo.candidates().size();
+ return enumDataMap;
+ }
+
+ private EnumDataMap analyzeEnumInstances() {
+ ImmutableMap.Builder<DexType, EnumData> builder = ImmutableMap.builder();
+ enumUnboxingCandidatesInfo.forEachCandidateAndRequiredInstanceFieldData(
+ (enumClass, instanceFields) -> {
+ EnumData data = buildData(enumClass, instanceFields);
+ if (data == null) {
+ // Reason is already reported at this point.
+ enumUnboxingCandidatesInfo.removeCandidate(enumClass);
+ return;
+ }
+ if (!debugLogEnabled || !debugLogs.containsKey(enumClass.getType())) {
+ builder.put(enumClass.type, data);
+ }
+ });
+ staticFieldValuesMap.clear();
+ return new EnumDataMap(builder.build());
+ }
+
+ private EnumData buildData(DexProgramClass enumClass, Set<DexField> instanceFields) {
+ if (!enumClass.hasStaticFields()) {
+ return new EnumData(ImmutableMap.of(), ImmutableMap.of(), ImmutableSet.of(), -1);
+ }
+
+ // This map holds all the accessible fields to their unboxed value, so we can remap the field
+ // read to the unboxed value.
+ ImmutableMap.Builder<DexField, Integer> unboxedValues = ImmutableMap.builder();
+ // This maps the ordinal to the object state, note that some fields may have been removed,
+ // hence the entry is in this map but not the enumToOrdinalMap.
+ Int2ReferenceMap<ObjectState> ordinalToObjectState = new Int2ReferenceArrayMap<>();
+ // Any fields matching the expected $VALUES content can be recorded here, they have however
+ // all the same content.
+ ImmutableSet.Builder<DexField> valuesField = ImmutableSet.builder();
+ EnumValuesObjectState valuesContents = null;
+
+ EnumStaticFieldValues enumStaticFieldValues = staticFieldValuesMap.get(enumClass.type);
+ if (enumStaticFieldValues == null) {
+ reportFailure(enumClass, new MissingEnumStaticFieldValuesReason());
+ return null;
+ }
+
+ // Step 1: We iterate over the field to find direct enum instance information and the values
+ // fields.
+ for (DexEncodedField staticField : enumClass.staticFields()) {
+ if (factory.enumMembers.isEnumField(staticField, enumClass.type)) {
+ ObjectState enumState =
+ enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.getReference());
+ if (enumState == null) {
+ if (staticField.getOptimizationInfo().isDead()) {
+ // We don't care about unused field data.
+ continue;
+ }
+ // We could not track the content of that field. We bail out.
+ reportFailure(
+ enumClass, new MissingObjectStateForEnumInstanceReason(staticField.getReference()));
+ return null;
+ }
+ OptionalInt optionalOrdinal = getOrdinal(enumState);
+ if (!optionalOrdinal.isPresent()) {
+ reportFailure(
+ enumClass,
+ new MissingInstanceFieldValueForEnumInstanceReason(
+ staticField.getReference(), factory.enumMembers.ordinalField));
+ return null;
+ }
+ int ordinal = optionalOrdinal.getAsInt();
+ unboxedValues.put(staticField.getReference(), ordinalToUnboxedInt(ordinal));
+ ordinalToObjectState.put(ordinal, enumState);
+ } else if (factory.enumMembers.isValuesFieldCandidate(staticField, enumClass.type)) {
+ ObjectState valuesState =
+ enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.getReference());
+ if (valuesState == null) {
+ if (staticField.getOptimizationInfo().isDead()) {
+ // We don't care about unused field data.
+ continue;
+ }
+ // We could not track the content of that field. We bail out.
+ // We could not track the content of that field, and the field could be a values field.
+ // We conservatively bail out.
+ reportFailure(
+ enumClass, new MissingContentsForEnumValuesArrayReason(staticField.getReference()));
+ return null;
+ }
+ assert valuesState.isEnumValuesObjectState();
+ assert valuesContents == null
+ || valuesContents.equals(valuesState.asEnumValuesObjectState());
+ valuesContents = valuesState.asEnumValuesObjectState();
+ valuesField.add(staticField.getReference());
+ }
+ }
+
+ // Step 2: We complete the information based on the values content, since some enum instances
+ // may be reachable only though the $VALUES field.
+ if (valuesContents != null) {
+ for (int ordinal = 0; ordinal < valuesContents.getEnumValuesSize(); ordinal++) {
+ if (!ordinalToObjectState.containsKey(ordinal)) {
+ ObjectState enumState = valuesContents.getObjectStateForOrdinal(ordinal);
+ if (enumState.isEmpty()) {
+ // If $VALUES is used, we need data for all enums, at least the ordinal.
+ return null;
+ }
+ assert getOrdinal(enumState).isPresent();
+ assert getOrdinal(enumState).getAsInt() == ordinal;
+ ordinalToObjectState.put(ordinal, enumState);
+ }
+ }
+ }
+
+ // The ordinalToObjectState map may have holes at this point, if some enum instances are never
+ // used ($VALUES unused or removed, and enum instance field unused or removed), it contains
+ // only data for reachable enum instance, that is what we're interested in.
+ ImmutableMap<DexField, EnumInstanceFieldKnownData> instanceFieldsData =
+ computeRequiredEnumInstanceFieldsData(enumClass, instanceFields, ordinalToObjectState);
+ if (instanceFieldsData == null) {
+ return null;
+ }
+
+ return new EnumData(
+ instanceFieldsData,
+ unboxedValues.build(),
+ valuesField.build(),
+ valuesContents == null ? EnumData.INVALID_VALUES_SIZE : valuesContents.getEnumValuesSize());
+ }
+
+ private ImmutableMap<DexField, EnumInstanceFieldKnownData> computeRequiredEnumInstanceFieldsData(
+ DexProgramClass enumClass,
+ Set<DexField> instanceFields,
+ Int2ReferenceMap<ObjectState> ordinalToObjectState) {
+ ImmutableMap.Builder<DexField, EnumInstanceFieldKnownData> builder = ImmutableMap.builder();
+ for (DexField instanceField : instanceFields) {
+ EnumInstanceFieldData fieldData =
+ computeRequiredEnumInstanceFieldData(instanceField, enumClass, ordinalToObjectState);
+ if (fieldData.isUnknown()) {
+ if (!debugLogEnabled) {
+ return null;
+ }
+ builder = null;
+ }
+ if (builder != null) {
+ builder.put(instanceField, fieldData.asEnumFieldKnownData());
+ }
+ }
+ return builder != null ? builder.build() : null;
+ }
+
+ private EnumInstanceFieldData computeRequiredEnumInstanceFieldData(
+ DexField instanceField,
+ DexProgramClass enumClass,
+ Int2ReferenceMap<ObjectState> ordinalToObjectState) {
+ DexEncodedField encodedInstanceField =
+ appView.appInfo().resolveFieldOn(enumClass, instanceField).getResolvedField();
+ assert encodedInstanceField != null;
+ boolean canBeOrdinal = instanceField.type.isIntType();
+ ImmutableInt2ReferenceSortedMap.Builder<AbstractValue> data =
+ ImmutableInt2ReferenceSortedMap.builder();
+ for (Integer ordinal : ordinalToObjectState.keySet()) {
+ ObjectState state = ordinalToObjectState.get(ordinal);
+ AbstractValue fieldValue = state.getAbstractFieldValue(encodedInstanceField);
+ if (!fieldValue.isSingleValue()) {
+ reportFailure(
+ enumClass, new MissingInstanceFieldValueForEnumInstanceReason(ordinal, instanceField));
+ return EnumInstanceFieldUnknownData.getInstance();
+ }
+ if (!(fieldValue.isSingleNumberValue() || fieldValue.isSingleStringValue())) {
+ reportFailure(
+ enumClass,
+ new UnsupportedInstanceFieldValueForEnumInstanceReason(ordinal, instanceField));
+ return EnumInstanceFieldUnknownData.getInstance();
+ }
+ data.put(ordinalToUnboxedInt(ordinal), fieldValue);
+ if (canBeOrdinal) {
+ assert fieldValue.isSingleNumberValue();
+ int computedValue = fieldValue.asSingleNumberValue().getIntValue();
+ if (computedValue != ordinal) {
+ canBeOrdinal = false;
+ }
+ }
+ }
+ if (canBeOrdinal) {
+ return new EnumInstanceFieldOrdinalData();
+ }
+ return new EnumInstanceFieldMappingData(data.build());
+ }
+
+ private OptionalInt getOrdinal(ObjectState state) {
+ AbstractValue field = state.getAbstractFieldValue(getOrdinalField().getDefinition());
+ if (field.isSingleNumberValue()) {
+ return OptionalInt.of(field.asSingleNumberValue().getIntValue());
+ }
+ return OptionalInt.empty();
+ }
+
+ @Override
+ public void recordEnumState(DexProgramClass clazz, StaticFieldValues staticFieldValues) {
+ if (staticFieldValues == null || !staticFieldValues.isEnumStaticFieldValues()) {
+ return;
+ }
+ assert clazz.isEnum();
+ EnumStaticFieldValues enumStaticFieldValues = staticFieldValues.asEnumStaticFieldValues();
+ if (getEnumUnboxingCandidateOrNull(clazz.type) != null) {
+ staticFieldValuesMap.put(clazz.type, enumStaticFieldValues);
+ }
+ }
+
+ private class EnumAccessibilityUseRegistry extends UseRegistry {
+
+ private ProgramMethod context;
+ private Constraint constraint;
+
+ public EnumAccessibilityUseRegistry(DexItemFactory factory) {
+ super(factory);
+ }
+
+ public Constraint computeConstraint(ProgramMethod method) {
+ constraint = Constraint.ALWAYS;
+ context = method;
+ method.registerCodeReferences(this);
+ return constraint;
+ }
+
+ public Constraint deriveConstraint(DexType targetHolder, AccessFlags<?> flags) {
+ DexProgramClass contextHolder = context.getHolder();
+ if (targetHolder == contextHolder.type) {
+ return Constraint.ALWAYS;
+ }
+ if (flags.isPublic()) {
+ return Constraint.ALWAYS;
+ }
+ if (flags.isPrivate()) {
+ // Enum unboxing is currently happening only cf to dex, and no class should be in a nest
+ // at this point. If that is the case, we just don't unbox the enum, or we would need to
+ // support Constraint.SAMENEST in the enum unboxer.
+ assert !contextHolder.isInANest();
+ // Only accesses within the enum are allowed since all enum methods and fields will be
+ // moved to the same class, and the enum itself becomes an integer, which is
+ // accessible everywhere.
+ return Constraint.NEVER;
+ }
+ assert flags.isProtected() || flags.isPackagePrivate();
+ // Protected is in practice equivalent to package private in this analysis since we are
+ // accessing the member from an enum context where subclassing is limited.
+ // At this point we don't support unboxing enums with subclasses, so we assume either
+ // same package access, or we just don't unbox.
+ // The only protected methods in java.lang.Enum are clone, finalize and the constructor.
+ // Besides calls to the constructor in the instance initializer, Enums with calls to such
+ // methods cannot be unboxed.
+ return targetHolder.isSamePackage(contextHolder.type) ? Constraint.PACKAGE : Constraint.NEVER;
+ }
+
+ @Override
+ public void registerTypeReference(DexType type) {
+ if (type.isArrayType()) {
+ registerTypeReference(type.toBaseType(factory));
+ return;
+ }
+
+ if (type.isPrimitiveType()) {
+ return;
+ }
+
+ DexClass definition = appView.definitionFor(type);
+ if (definition == null) {
+ constraint = Constraint.NEVER;
+ return;
+ }
+ constraint = constraint.meet(deriveConstraint(type, definition.accessFlags));
+ }
+
+ @Override
+ public void registerInitClass(DexType type) {
+ registerTypeReference(type);
+ }
+
+ @Override
+ public void registerInstanceOf(DexType type) {
+ registerTypeReference(type);
+ }
+
+ @Override
+ public void registerNewInstance(DexType type) {
+ registerTypeReference(type);
+ }
+
+ @Override
+ public void registerInvokeVirtual(DexMethod method) {
+ registerVirtualInvoke(method, false);
+ }
+
+ @Override
+ public void registerInvokeInterface(DexMethod method) {
+ registerVirtualInvoke(method, true);
+ }
+
+ private void registerVirtualInvoke(DexMethod method, boolean isInterface) {
+ if (method.holder.isArrayType()) {
+ return;
+ }
+ // Perform resolution and derive unboxing constraints based on the accessibility of the
+ // resolution result.
+ MethodResolutionResult resolutionResult =
+ appView.appInfo().resolveMethod(method, isInterface);
+ if (!resolutionResult.isVirtualTarget()) {
+ constraint = Constraint.NEVER;
+ return;
+ }
+ registerTarget(
+ resolutionResult.getInitialResolutionHolder(), resolutionResult.getSingleTarget());
+ }
+
+ private void registerTarget(DexClass initialResolutionHolder, DexEncodedMember<?, ?> target) {
+ if (target == null) {
+ // This will fail at runtime.
+ constraint = Constraint.NEVER;
+ return;
+ }
+ DexType resolvedHolder = target.getHolderType();
+ if (initialResolutionHolder == null) {
+ constraint = Constraint.NEVER;
+ return;
+ }
+ Constraint memberConstraint = deriveConstraint(resolvedHolder, target.getAccessFlags());
+ // We also have to take the constraint of the initial resolution holder into account.
+ Constraint classConstraint =
+ deriveConstraint(initialResolutionHolder.type, initialResolutionHolder.accessFlags);
+ Constraint instructionConstraint = memberConstraint.meet(classConstraint);
+ constraint = instructionConstraint.meet(constraint);
+ }
+
+ @Override
+ public void registerInvokeDirect(DexMethod method) {
+ registerSingleTargetInvoke(method, DexEncodedMethod::isDirectMethod);
+ }
+
+ @Override
+ public void registerInvokeStatic(DexMethod method) {
+ registerSingleTargetInvoke(method, DexEncodedMethod::isStatic);
+ }
+
+ private void registerSingleTargetInvoke(
+ DexMethod method, Predicate<DexEncodedMethod> methodValidator) {
+ if (method.holder.isArrayType()) {
+ return;
+ }
+ MethodResolutionResult resolutionResult =
+ appView.appInfo().unsafeResolveMethodDueToDexFormat(method);
+ DexEncodedMethod target = resolutionResult.getSingleTarget();
+ if (target == null || !methodValidator.test(target)) {
+ constraint = Constraint.NEVER;
+ return;
+ }
+ registerTarget(resolutionResult.getInitialResolutionHolder(), target);
+ }
+
+ @Override
+ public void registerInvokeSuper(DexMethod method) {
+ // Invoke-super can only target java.lang.Enum methods since we do not unbox enums with
+ // subclasses. Calls to java.lang.Object methods would have resulted in the enum to be marked
+ // as unboxable. The methods of java.lang.Enum called are already analyzed in the enum
+ // unboxer analysis, so invoke-super is always valid.
+ assert method.holder == factory.enumType;
+ }
+
+ @Override
+ public void registerCallSite(DexCallSite callSite) {
+ // This is reached after lambda desugaring, so this should not be a lambda call site.
+ // We do not unbox enums with invoke custom since it's not clear the accessibility
+ // constraints would be correct if the method holding the invoke custom is moved to
+ // another class.
+ assert appView.options().isGeneratingClassFiles()
+ || !factory.isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
+ constraint = Constraint.NEVER;
+ }
+
+ private void registerFieldInstruction(DexField field) {
+ FieldResolutionResult fieldResolutionResult = appView.appInfo().resolveField(field, context);
+ registerTarget(
+ fieldResolutionResult.getInitialResolutionHolder(),
+ fieldResolutionResult.getResolvedField());
+ }
+
+ @Override
+ public void registerInstanceFieldRead(DexField field) {
+ registerFieldInstruction(field);
+ }
+
+ @Override
+ public void registerInstanceFieldWrite(DexField field) {
+ registerFieldInstruction(field);
+ }
+
+ @Override
+ public void registerStaticFieldRead(DexField field) {
+ registerFieldInstruction(field);
+ }
+
+ @Override
+ public void registerStaticFieldWrite(DexField field) {
+ registerFieldInstruction(field);
+ }
+ }
+
+ private void analyzeInitializers() {
+ enumUnboxingCandidatesInfo.forEachCandidate(
+ enumClass -> {
+ for (DexEncodedMethod directMethod : enumClass.directMethods()) {
+ if (directMethod.isInstanceInitializer()) {
+ if (directMethod
+ .getOptimizationInfo()
+ .getContextInsensitiveInstanceInitializerInfo()
+ .mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
+ if (markEnumAsUnboxable(Reason.INVALID_INIT, enumClass)) {
+ break;
+ }
+ }
+ }
+ }
+ if (enumClass.classInitializationMayHaveSideEffects(appView)) {
+ markEnumAsUnboxable(Reason.INVALID_CLINIT, enumClass);
+ }
+ });
+ }
+
+ private Reason instructionAllowEnumUnboxing(
+ Instruction instruction, IRCode code, DexProgramClass enumClass, Value enumValue) {
+ ProgramMethod context = code.context();
+ switch (instruction.opcode()) {
+ case ASSUME:
+ return analyzeAssumeUser(instruction.asAssume(), code, context, enumClass, enumValue);
+ case ARRAY_GET:
+ return analyzeArrayGetUser(instruction.asArrayGet(), code, context, enumClass, enumValue);
+ case ARRAY_LENGTH:
+ return analyzeArrayLengthUser(
+ instruction.asArrayLength(), code, context, enumClass, enumValue);
+ case ARRAY_PUT:
+ return analyzeArrayPutUser(instruction.asArrayPut(), code, context, enumClass, enumValue);
+ case CHECK_CAST:
+ return analyzeCheckCastUser(instruction.asCheckCast(), code, context, enumClass, enumValue);
+ case IF:
+ return analyzeIfUser(instruction.asIf(), code, context, enumClass, enumValue);
+ case INSTANCE_GET:
+ return analyzeInstanceGetUser(
+ instruction.asInstanceGet(), code, context, enumClass, enumValue);
+ case INSTANCE_PUT:
+ return analyzeFieldPutUser(
+ instruction.asInstancePut(), code, context, enumClass, enumValue);
+ case INVOKE_DIRECT:
+ case INVOKE_INTERFACE:
+ case INVOKE_STATIC:
+ case INVOKE_SUPER:
+ case INVOKE_VIRTUAL:
+ return analyzeInvokeUser(instruction.asInvokeMethod(), code, context, enumClass, enumValue);
+ case RETURN:
+ return analyzeReturnUser(instruction.asReturn(), code, context, enumClass, enumValue);
+ case STATIC_PUT:
+ return analyzeFieldPutUser(instruction.asStaticPut(), code, context, enumClass, enumValue);
+ default:
+ return Reason.OTHER_UNSUPPORTED_INSTRUCTION;
+ }
+ }
+
+ private Reason analyzeAssumeUser(
+ Assume assume,
+ IRCode code,
+ ProgramMethod context,
+ DexProgramClass enumClass,
+ Value enumValue) {
+ return validateEnumUsages(code, assume.outValue(), enumClass);
+ }
+
+ private Reason analyzeArrayGetUser(
+ ArrayGet arrayGet,
+ IRCode code,
+ ProgramMethod context,
+ DexProgramClass enumClass,
+ Value enumValue) {
+ // MyEnum[] array = ...; array[0]; is valid.
+ return Reason.ELIGIBLE;
+ }
+
+ private Reason analyzeArrayLengthUser(
+ ArrayLength arrayLength,
+ IRCode code,
+ ProgramMethod context,
+ DexProgramClass enumClass,
+ Value enumValue) {
+ // MyEnum[] array = ...; array.length; is valid.
+ return Reason.ELIGIBLE;
+ }
+
+ private Reason analyzeArrayPutUser(
+ ArrayPut arrayPut,
+ IRCode code,
+ ProgramMethod context,
+ DexProgramClass enumClass,
+ Value enumValue) {
+ // MyEnum[] array; array[0] = MyEnum.A; is valid.
+ // MyEnum[][] array2d; MyEnum[] array; array2d[0] = array; is valid.
+ // MyEnum[]^N array; MyEnum[]^(N-1) element; array[0] = element; is valid.
+ // We need to prove that the value to put in and the array have correct types.
+ assert arrayPut.getMemberType() == MemberType.OBJECT;
+ TypeElement arrayType = arrayPut.array().getType();
+ assert arrayType.isArrayType();
+ assert arrayType.asArrayType().getBaseType().isClassType();
+ ClassTypeElement arrayBaseType = arrayType.asArrayType().getBaseType().asClassType();
+ TypeElement valueBaseType = arrayPut.value().getType();
+ if (valueBaseType.isArrayType()) {
+ assert valueBaseType.asArrayType().getBaseType().isClassType();
+ assert valueBaseType.asArrayType().getNesting() == arrayType.asArrayType().getNesting() - 1;
+ valueBaseType = valueBaseType.asArrayType().getBaseType();
+ }
+ if (arrayBaseType.equalUpToNullability(valueBaseType)
+ && arrayBaseType.getClassType() == enumClass.type) {
+ return Reason.ELIGIBLE;
+ }
+ return Reason.INVALID_ARRAY_PUT;
+ }
+
+ private Reason analyzeCheckCastUser(
+ CheckCast checkCast,
+ IRCode code,
+ ProgramMethod context,
+ DexProgramClass enumClass,
+ Value enumValue) {
+ if (allowCheckCast(checkCast)) {
+ return Reason.ELIGIBLE;
+ }
+ return Reason.DOWN_CAST;
+ }
+
+ // A field put is valid only if the field is not on an enum, and the field type and the valuePut
+ // have identical enum type.
+ private Reason analyzeFieldPutUser(
+ FieldInstruction fieldPut,
+ IRCode code,
+ ProgramMethod context,
+ DexProgramClass enumClass,
+ Value enumValue) {
+ assert fieldPut.isInstancePut() || fieldPut.isStaticPut();
+ DexEncodedField field = appView.appInfo().resolveField(fieldPut.getField()).getResolvedField();
+ if (field == null) {
+ return Reason.INVALID_FIELD_PUT;
+ }
+ DexProgramClass dexClass = appView.programDefinitionFor(field.getHolderType(), code.context());
+ if (dexClass == null) {
+ return Reason.INVALID_FIELD_PUT;
+ }
+ if (fieldPut.isInstancePut() && fieldPut.asInstancePut().object() == enumValue) {
+ return Reason.ELIGIBLE;
+ }
+ // The put value has to be of the field type.
+ if (field.getReference().type.toBaseType(factory) != enumClass.type) {
+ return Reason.TYPE_MISMATCH_FIELD_PUT;
+ }
+ return Reason.ELIGIBLE;
+ }
+
+ // An If using enum as inValue is valid if it matches e == null
+ // or e == X with X of same enum type as e. Ex: if (e == MyEnum.A).
+ private Reason analyzeIfUser(
+ If theIf, IRCode code, ProgramMethod context, DexProgramClass enumClass, Value enumValue) {
+ assert (theIf.getType() == If.Type.EQ || theIf.getType() == If.Type.NE)
+ : "Comparing a reference with " + theIf.getType().toString();
+ // e == null.
+ if (theIf.isZeroTest()) {
+ return Reason.ELIGIBLE;
+ }
+ // e == MyEnum.X
+ TypeElement leftType = theIf.lhs().getType();
+ TypeElement rightType = theIf.rhs().getType();
+ if (leftType.equalUpToNullability(rightType)) {
+ assert leftType.isClassType();
+ assert leftType.asClassType().getClassType() == enumClass.type;
+ return Reason.ELIGIBLE;
+ }
+ return Reason.INVALID_IF_TYPES;
+ }
+
+ private Reason analyzeInstanceGetUser(
+ InstanceGet instanceGet,
+ IRCode code,
+ ProgramMethod context,
+ DexProgramClass enumClass,
+ Value enumValue) {
+ assert instanceGet.getField().holder == enumClass.type;
+ DexField field = instanceGet.getField();
+ enumUnboxingCandidatesInfo.addRequiredEnumInstanceFieldData(enumClass, field);
+ return Reason.ELIGIBLE;
+ }
+
+ // All invokes in the library are invalid, besides a few cherry picked cases such as ordinal().
+ private Reason analyzeInvokeUser(
+ InvokeMethod invoke,
+ IRCode code,
+ ProgramMethod context,
+ DexProgramClass enumClass,
+ Value enumValue) {
+ if (invoke.getInvokedMethod().holder.isArrayType()) {
+ // The only valid methods is clone for values() to be correct.
+ if (invoke.getInvokedMethod().name == factory.cloneMethodName) {
+ return Reason.ELIGIBLE;
+ }
+ return Reason.INVALID_INVOKE_ON_ARRAY;
+ }
+ DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
+ if (singleTarget == null) {
+ return Reason.INVALID_INVOKE;
+ }
+ DexMethod singleTargetReference = singleTarget.getReference();
+ DexClass targetHolder = singleTarget.getHolder();
+ if (targetHolder.isProgramClass()) {
+ if (targetHolder.isEnum() && singleTarget.getDefinition().isInstanceInitializer()) {
+ if (code.context().getHolder() == targetHolder && code.method().isClassInitializer()) {
+ // The enum instance initializer is allowed to be called only from the enum clinit.
+ return Reason.ELIGIBLE;
+ } else {
+ return Reason.INVALID_INIT;
+ }
+ }
+
+ // Check if this is a checkNotNull() user. In this case, we can create a copy of the method
+ // that takes an int instead of java.lang.Object and call that method instead.
+ EnumUnboxerMethodClassification classification =
+ singleTarget.getOptimizationInfo().getEnumUnboxerMethodClassification();
+ if (classification.isCheckNotNullClassification()) {
+ CheckNotNullEnumUnboxerMethodClassification checkNotNullClassification =
+ classification.asCheckNotNullClassification();
+ if (checkNotNullClassification.isUseEligibleForUnboxing(
+ invoke.asInvokeStatic(), enumValue)) {
+ GraphLens graphLens = appView.graphLens();
+ checkNotNullMethodsBuilder
+ .computeIfAbsent(
+ singleTarget.asProgramMethod(),
+ ignoreKey(
+ () ->
+ LongLivedClassSetBuilder.createConcurrentBuilderForIdentitySet(
+ graphLens)),
+ graphLens)
+ .add(enumClass, graphLens);
+ return Reason.ELIGIBLE;
+ }
+ }
+
+ // Check that the enum-value only flows into parameters whose type exactly matches the
+ // enum's type.
+ for (int i = 0; i < singleTarget.getParameters().size(); i++) {
+ if (invoke.getArgumentForParameter(i) == enumValue
+ && singleTarget.getParameter(i).toBaseType(factory) != enumClass.getType()) {
+ return new IllegalInvokeWithImpreciseParameterTypeReason(singleTargetReference);
+ }
+ }
+ if (invoke.isInvokeMethodWithReceiver()) {
+ Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver();
+ if (receiver == enumValue && targetHolder.isInterface()) {
+ return Reason.DEFAULT_METHOD_INVOKE;
+ }
+ }
+ return Reason.ELIGIBLE;
+ }
+
+ if (targetHolder.isClasspathClass()) {
+ return Reason.INVALID_INVOKE_CLASSPATH;
+ }
+
+ assert targetHolder.isLibraryClass();
+
+ Reason reason =
+ analyzeLibraryInvoke(
+ invoke, code, context, enumClass, enumValue, singleTargetReference, targetHolder);
+
+ if (reason == Reason.ELIGIBLE) {
+ markMethodDependsOnLibraryModelisation(context);
+ }
+
+ return reason;
+ }
+
+ private Reason analyzeLibraryInvoke(
+ InvokeMethod invoke,
+ IRCode code,
+ ProgramMethod context,
+ DexProgramClass enumClass,
+ Value enumValue,
+ DexMethod singleTargetReference,
+ DexClass targetHolder) {
+ // Calls to java.lang.Enum.
+ if (targetHolder.getType() == factory.enumType) {
+ // TODO(b/147860220): EnumSet and EnumMap may be interesting to model.
+ if (singleTargetReference == factory.enumMembers.compareTo
+ || singleTargetReference == factory.enumMembers.compareToWithObject) {
+ DexProgramClass otherEnumClass =
+ getEnumUnboxingCandidateOrNull(invoke.getLastArgument().getType());
+ if (otherEnumClass == enumClass || invoke.getLastArgument().getType().isNullType()) {
+ return Reason.ELIGIBLE;
+ }
+ } else if (singleTargetReference == factory.enumMembers.equals) {
+ return Reason.ELIGIBLE;
+ } else if (singleTargetReference == factory.enumMembers.nameMethod
+ || singleTargetReference == factory.enumMembers.toString) {
+ assert invoke.asInvokeMethodWithReceiver().getReceiver() == enumValue;
+ addRequiredNameData(enumClass);
+ return Reason.ELIGIBLE;
+ } else if (singleTargetReference == factory.enumMembers.ordinalMethod) {
+ return Reason.ELIGIBLE;
+ } else if (singleTargetReference == factory.enumMembers.hashCode) {
+ return Reason.ELIGIBLE;
+ } else if (singleTargetReference == factory.enumMembers.constructor) {
+ // Enum constructor call is allowed only if called from an enum initializer.
+ if (code.method().isInstanceInitializer() && code.context().getHolder() == enumClass) {
+ return Reason.ELIGIBLE;
+ }
+ }
+ return new UnsupportedLibraryInvokeReason(singleTargetReference);
+ }
+
+ // Calls to java.lang.Object.
+ if (targetHolder.getType() == factory.objectType) {
+ // Object#getClass without outValue is important since R8 rewrites explicit null checks to
+ // such instructions.
+ if (singleTargetReference == factory.objectMembers.getClass && invoke.hasUnusedOutValue()) {
+ // This is a hidden null check.
+ return Reason.ELIGIBLE;
+ }
+ return new UnsupportedLibraryInvokeReason(singleTargetReference);
+ }
+
+ // Calls to java.lang.Objects.
+ if (targetHolder.getType() == factory.objectsType) {
+ // Objects#requireNonNull is important since R8 rewrites explicit null checks to such
+ // instructions.
+ if (singleTargetReference == factory.objectsMethods.requireNonNull
+ || singleTargetReference == factory.objectsMethods.requireNonNullWithMessage) {
+ return Reason.ELIGIBLE;
+ }
+ return new UnsupportedLibraryInvokeReason(singleTargetReference);
+ }
+
+ // Calls to java.lang.String.
+ if (targetHolder.getType() == factory.stringType) {
+ if (singleTargetReference == factory.stringMembers.valueOf) {
+ addRequiredNameData(enumClass);
+ return Reason.ELIGIBLE;
+ }
+ return new UnsupportedLibraryInvokeReason(singleTargetReference);
+ }
+
+ // Calls to java.lang.StringBuilder and java.lang.StringBuffer.
+ if (targetHolder.getType() == factory.stringBuilderType
+ || targetHolder.getType() == factory.stringBufferType) {
+ if (singleTargetReference == factory.stringBuilderMethods.appendObject
+ || singleTargetReference == factory.stringBufferMethods.appendObject) {
+ addRequiredNameData(enumClass);
+ return Reason.ELIGIBLE;
+ }
+ return new UnsupportedLibraryInvokeReason(singleTargetReference);
+ }
+
+ // Calls to java.lang.System.
+ if (targetHolder.getType() == factory.javaLangSystemType) {
+ if (singleTargetReference == factory.javaLangSystemMethods.arraycopy) {
+ // Important for Kotlin 1.5 enums, which use arraycopy to create a copy of $VALUES instead
+ // of int[].clone().
+ return Reason.ELIGIBLE;
+ }
+ if (singleTargetReference == factory.javaLangSystemMethods.identityHashCode) {
+ // Important for proto enum unboxing.
+ return Reason.ELIGIBLE;
+ }
+ return new UnsupportedLibraryInvokeReason(singleTargetReference);
+ }
+
+ // Unsupported holder.
+ return new UnsupportedLibraryInvokeReason(singleTargetReference);
+ }
+
+ // Return is used for valueOf methods.
+ private Reason analyzeReturnUser(
+ Return theReturn,
+ IRCode code,
+ ProgramMethod context,
+ DexProgramClass enumClass,
+ Value enumValue) {
+ DexType returnType = context.getReturnType();
+ if (returnType != enumClass.type && returnType.toBaseType(factory) != enumClass.type) {
+ return Reason.IMPLICIT_UP_CAST_IN_RETURN;
+ }
+ return Reason.ELIGIBLE;
+ }
+
+ private void reportEnumsAnalysis() {
+ assert debugLogEnabled;
+ Reporter reporter = appView.reporter();
+ Set<DexType> candidates = enumUnboxingCandidatesInfo.candidates();
+ reporter.info(
+ new StringDiagnostic(
+ "Unboxed " + candidates.size() + " enums: " + Arrays.toString(candidates.toArray())));
+
+ StringBuilder sb =
+ new StringBuilder("Unable to unbox ")
+ .append(debugLogs.size())
+ .append(" enums.")
+ .append(System.lineSeparator())
+ .append(System.lineSeparator());
+
+ // Sort by the number of reasons that prevent enum unboxing.
+ TreeMap<DexType, List<Reason>> sortedDebugLogs =
+ new TreeMap<>(
+ Comparator.<DexType>comparingInt(x -> debugLogs.get(x).size())
+ .thenComparing(Function.identity()));
+ sortedDebugLogs.putAll(debugLogs);
+
+ // Print the pinned enums and remove them from further reporting.
+ List<DexType> pinned = new ArrayList<>();
+ Iterator<Entry<DexType, List<Reason>>> sortedDebugLogIterator =
+ sortedDebugLogs.entrySet().iterator();
+ while (sortedDebugLogIterator.hasNext()) {
+ Entry<DexType, List<Reason>> entry = sortedDebugLogIterator.next();
+ List<Reason> reasons = entry.getValue();
+ if (reasons.size() > 1) {
+ break;
+ }
+ if (reasons.get(0) == Reason.PINNED) {
+ pinned.add(entry.getKey());
+ sortedDebugLogIterator.remove();
+ }
+ }
+ if (!pinned.isEmpty()) {
+ sb.append("Pinned: ").append(Arrays.toString(pinned.toArray()));
+ }
+
+ // Print the reasons for each unboxable enum.
+ sortedDebugLogs.forEach(
+ (type, reasons) -> {
+ sb.append(type).append(" (").append(reasons.size()).append(" reasons):");
+ HashMultiset.create(reasons)
+ .forEachEntry(
+ (reason, count) ->
+ sb.append(System.lineSeparator())
+ .append(" - ")
+ .append(reason)
+ .append(" (")
+ .append(count)
+ .append(")"));
+ sb.append(System.lineSeparator());
+ });
+
+ sb.append(System.lineSeparator());
+
+ // Print information about how often a given Reason kind prevents enum unboxing.
+ Object2IntMap<Object> reasonKindCount = new Object2IntOpenHashMap<>();
+ debugLogs.forEach(
+ (type, reasons) ->
+ reasons.forEach(
+ reason ->
+ reasonKindCount.put(reason.getKind(), reasonKindCount.getInt(reason) + 1)));
+ List<Object> differentReasonKinds = new ArrayList<>(reasonKindCount.keySet());
+ differentReasonKinds.sort(
+ (reasonKind, other) -> {
+ int freq = reasonKindCount.getInt(reasonKind) - reasonKindCount.getInt(other);
+ return freq != 0
+ ? freq
+ : System.identityHashCode(reasonKind) - System.identityHashCode(other);
+ });
+ differentReasonKinds.forEach(
+ reasonKind ->
+ sb.append(reasonKind)
+ .append(" (")
+ .append(reasonKindCount.getInt(reasonKind))
+ .append(")")
+ .append(System.lineSeparator()));
+
+ reporter.info(new StringDiagnostic(sb.toString()));
+ }
+
+ boolean reportFailure(DexProgramClass enumClass, Reason reason) {
+ return reportFailure(enumClass.getType(), reason);
+ }
+
+ /** Returns true if the failure was reported. */
+ boolean reportFailure(DexType enumType, Reason reason) {
+ if (debugLogEnabled) {
+ debugLogs
+ .computeIfAbsent(enumType, ignore -> Collections.synchronizedList(new ArrayList<>()))
+ .add(reason);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Set<Phi> rewriteCode(IRCode code, MethodProcessor methodProcessor) {
+ // This has no effect during primary processing since the enumUnboxerRewriter is set
+ // in between primary and post processing.
+ if (enumUnboxerRewriter != null) {
+ return enumUnboxerRewriter.rewriteCode(code, methodProcessor);
+ }
+ return Sets.newIdentityHashSet();
+ }
+
+ @Override
+ public void unsetRewriter() {
+ enumUnboxerRewriter = null;
+ }
+}
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 4225447..51e4191 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
@@ -25,12 +25,12 @@
private static final int MAX_INSTANCE_FIELDS_FOR_UNBOXING = 7;
private final AppView<AppInfoWithLiveness> appView;
- private final EnumUnboxer enumUnboxer;
+ private final EnumUnboxerImpl enumUnboxer;
private final DexItemFactory factory;
private EnumUnboxingCandidateInfoCollection enumToUnboxCandidates =
new EnumUnboxingCandidateInfoCollection();
- EnumUnboxingCandidateAnalysis(AppView<AppInfoWithLiveness> appView, EnumUnboxer enumUnboxer) {
+ EnumUnboxingCandidateAnalysis(AppView<AppInfoWithLiveness> appView, EnumUnboxerImpl enumUnboxer) {
this.appView = appView;
this.enumUnboxer = enumUnboxer;
factory = appView.dexItemFactory();
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 287a4d6..5fc743e 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
@@ -288,7 +288,7 @@
assert unboxedEnumsData.isUnboxedEnum(newUnboxedEnumInstance.getType());
iterator.replaceCurrentInstruction(
code.createIntConstant(
- EnumUnboxer.ordinalToUnboxedInt(newUnboxedEnumInstance.getOrdinal())));
+ EnumUnboxerImpl.ordinalToUnboxedInt(newUnboxedEnumInstance.getOrdinal())));
}
}
}
@@ -398,26 +398,38 @@
return;
}
- if (singleTarget.isProgramMethod()) {
- EnumUnboxerMethodClassification classification =
- singleTarget.getOptimizationInfo().getEnumUnboxerMethodClassification();
- if (classification.isCheckNotNullClassification()) {
- CheckNotNullEnumUnboxerMethodClassification checkNotNullClassification =
- classification.asCheckNotNullClassification();
- Value argument = invoke.getArgument(checkNotNullClassification.getArgumentIndex());
- DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
- if (enumType != null) {
- InvokeStatic replacement =
- InvokeStatic.builder()
- .setMethod(checkNotNullToCheckNotZeroMapping.get(singleTarget.getReference()))
- .setArguments(invoke.arguments())
- .setPosition(invoke.getPosition())
- .build();
- instructionIterator.replaceCurrentInstruction(replacement);
- convertedEnums.put(replacement, enumType);
+ if (singleTarget.isProgramMethod()
+ && checkNotNullToCheckNotZeroMapping.containsKey(singleTarget.getReference())) {
+ DexMethod checkNotZeroMethodReference =
+ checkNotNullToCheckNotZeroMapping.get(singleTarget.getReference());
+ ProgramMethod checkNotZeroMethod =
+ appView
+ .appInfo()
+ .resolveMethodOnClass(checkNotZeroMethodReference)
+ .getResolvedProgramMethod();
+ if (checkNotZeroMethod != null) {
+ EnumUnboxerMethodClassification classification =
+ checkNotZeroMethod.getOptimizationInfo().getEnumUnboxerMethodClassification();
+ if (classification.isCheckNotNullClassification()) {
+ CheckNotNullEnumUnboxerMethodClassification checkNotNullClassification =
+ classification.asCheckNotNullClassification();
+ Value argument = invoke.getArgument(checkNotNullClassification.getArgumentIndex());
+ DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
+ if (enumType != null) {
+ InvokeStatic replacement =
+ InvokeStatic.builder()
+ .setMethod(checkNotZeroMethod)
+ .setArguments(invoke.arguments())
+ .setPosition(invoke.getPosition())
+ .build();
+ instructionIterator.replaceCurrentInstruction(replacement);
+ convertedEnums.put(replacement, enumType);
+ }
+ } else {
+ assert false;
}
} else {
- assert !checkNotNullToCheckNotZeroMapping.containsKey(singleTarget.getReference());
+ assert false;
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index c7bcc37..8a97aef 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -54,10 +54,11 @@
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.ProgramMethodMap;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
@@ -130,16 +131,16 @@
}
// Create mapping from checkNotNull() to checkNotZero() methods.
- Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping =
+ BiMap<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping =
duplicateCheckNotNullMethods(converter, executorService);
return new Result(
checkNotNullToCheckNotZeroMapping, lensBuilder.build(appView), prunedItemsBuilder.build());
}
- private Map<DexMethod, DexMethod> duplicateCheckNotNullMethods(
+ private BiMap<DexMethod, DexMethod> duplicateCheckNotNullMethods(
IRConverter converter, ExecutorService executorService) throws ExecutionException {
- Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping = new IdentityHashMap<>();
+ BiMap<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping = HashBiMap.create();
ProcessorContext processorContext = appView.createProcessorContext();
OneTimeMethodProcessor.Builder methodProcessorBuilder =
OneTimeMethodProcessor.builder(processorContext);
@@ -186,6 +187,11 @@
.setApiLevelForDefinition(minApiLevelIfEnabledOrUnknown(appView))
.setApiLevelForCode(minApiLevelIfEnabledOrUnknown(appView))
.setCode(method -> new CheckNotZeroCode(checkNotNullMethod))
+ .setOptimizationInfo(
+ checkNotNullMethod
+ .getOptimizationInfo()
+ .asMutableMethodOptimizationInfo()
+ .mutableCopy())
.setProto(newProto));
checkNotNullToCheckNotZeroMapping.put(
checkNotNullMethod.getReference(), checkNotZeroMethod.getReference());
@@ -603,12 +609,12 @@
public static class Result {
- private final Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping;
+ private final BiMap<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping;
private final EnumUnboxingLens lens;
private final PrunedItems prunedItems;
Result(
- Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping,
+ BiMap<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping,
EnumUnboxingLens lens,
PrunedItems prunedItems) {
this.checkNotNullToCheckNotZeroMapping = checkNotNullToCheckNotZeroMapping;
@@ -616,7 +622,7 @@
this.prunedItems = prunedItems;
}
- Map<DexMethod, DexMethod> getCheckNotNullToCheckNotZeroMapping() {
+ BiMap<DexMethod, DexMethod> getCheckNotNullToCheckNotZeroMapping() {
return checkNotNullToCheckNotZeroMapping;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassificationAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassificationAnalysis.java
index f01abfc..3d8bccc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassificationAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassificationAnalysis.java
@@ -46,13 +46,25 @@
}
// Look for an argument with a single if-zero user.
+ EnumUnboxerMethodClassification currentClassification =
+ method.getOptimizationInfo().getEnumUnboxerMethodClassification();
DexItemFactory dexItemFactory = appView.dexItemFactory();
InstructionIterator entryIterator = code.entryBlock().iterator();
for (int index = 0; index < method.getParameters().size(); index++) {
Argument argument = entryIterator.next().asArgument();
DexType parameter = method.getParameter(index);
- if (parameter != dexItemFactory.objectType) {
- continue;
+ // Before enum unboxing, we classify methods with `object != null` as check-not-null methods.
+ // After enum unboxing, we check correctness of the classification for check-not-zero methods.
+ if (appView.hasUnboxedEnums()) {
+ if (parameter != dexItemFactory.intType
+ || !currentClassification.isCheckNotNullClassification()
+ || currentClassification.asCheckNotNullClassification().getArgumentIndex() != index) {
+ continue;
+ }
+ } else {
+ if (parameter != dexItemFactory.objectType) {
+ continue;
+ }
}
if (onlyHasCheckNotNullUsers(argument, methodProcessor)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
index c2c5427..c2cbec9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxerImpl;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.IteratorUtils;
@@ -28,8 +29,7 @@
*
* <p>Instances of {@link CheckNotZeroCode} are converted to {@link
* com.android.tools.r8.graph.CfCode} or {@link com.android.tools.r8.graph.DexCode} immediately, and
- * thus should never be seen outside of the {@link
- * com.android.tools.r8.ir.optimize.enums.EnumUnboxer}.
+ * thus should never be seen outside of the {@link EnumUnboxerImpl}.
*/
public class CheckNotZeroCode extends Code {
@@ -49,7 +49,7 @@
// Start iterating at the argument instruction for the checked argument.
IteratorUtils.skip(
instructionIterator,
- checkNotNullMethod
+ checkNotZeroMethod
.getOptimizationInfo()
.getEnumUnboxerMethodClassification()
.asCheckNotNullClassification()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 24aeb3d..c085286 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -150,6 +150,11 @@
}
@Override
+ public BitSet getUnusedArguments() {
+ return null;
+ }
+
+ @Override
public boolean isInitializerEnablingJavaVmAssertions() {
return UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index aab9aa6..aff19ef 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -83,6 +83,13 @@
public abstract SimpleInliningConstraint getSimpleInliningConstraint();
+ public final boolean hasUnusedArguments() {
+ assert getUnusedArguments() == null || !getUnusedArguments().isEmpty();
+ return getUnusedArguments() != null;
+ }
+
+ public abstract BitSet getUnusedArguments();
+
public abstract boolean forceInline();
public abstract boolean neverInline();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 6abfb55..9836791 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -65,6 +65,7 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.code.AliasedValueConfiguration;
+import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.DominatorTree;
@@ -155,6 +156,7 @@
computeReturnValueOnlyDependsOnArguments(feedback, definition, code, timing);
BitSet nonNullParamOrThrow = computeNonNullParamOrThrow(feedback, definition, code, timing);
computeNonNullParamOnNormalExits(feedback, code, nonNullParamOrThrow, timing);
+ computeUnusedArguments(method, code, feedback, timing);
}
private void identifyBridgeInfo(
@@ -808,9 +810,33 @@
IRCode code,
OptimizationFeedback feedback,
MethodProcessor methodProcessor) {
- EnumUnboxerMethodClassification enumUnboxerMethodClassification =
- EnumUnboxerMethodClassificationAnalysis.analyze(appView, method, code, methodProcessor);
- feedback.setEnumUnboxerMethodClassification(method, enumUnboxerMethodClassification);
+ if (appView.hasUnboxedEnums()) {
+ if (appView.unboxedEnums().isEmpty()) {
+ feedback.unsetEnumUnboxerMethodClassification(method);
+ } else {
+ assert verifyEnumUnboxerMethodClassificationCorrect(method, code, methodProcessor);
+ }
+ } else {
+ EnumUnboxerMethodClassification enumUnboxerMethodClassification =
+ EnumUnboxerMethodClassificationAnalysis.analyze(appView, method, code, methodProcessor);
+ feedback.setEnumUnboxerMethodClassification(method, enumUnboxerMethodClassification);
+ }
+ }
+
+ private boolean verifyEnumUnboxerMethodClassificationCorrect(
+ ProgramMethod method, IRCode code, MethodProcessor methodProcessor) {
+ EnumUnboxerMethodClassification existingClassification =
+ method.getOptimizationInfo().getEnumUnboxerMethodClassification();
+ if (existingClassification.isCheckNotNullClassification()) {
+ EnumUnboxerMethodClassification computedClassification =
+ EnumUnboxerMethodClassificationAnalysis.analyze(appView, method, code, methodProcessor);
+ assert computedClassification.isCheckNotNullClassification();
+ assert computedClassification.asCheckNotNullClassification().getArgumentIndex()
+ == existingClassification.asCheckNotNullClassification().getArgumentIndex();
+ } else {
+ assert existingClassification.isUnknownClassification();
+ }
+ return true;
}
private void computeSimpleInliningConstraint(
@@ -1195,4 +1221,25 @@
assert uncoveredPaths.isEmpty();
return true;
}
+
+ private void computeUnusedArguments(
+ ProgramMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+ timing.begin("Compute unused arguments");
+ computeUnusedArguments(method, code, feedback);
+ timing.end();
+ }
+
+ private void computeUnusedArguments(
+ ProgramMethod method, IRCode code, OptimizationFeedback feedback) {
+ BitSet unusedArguments = new BitSet(method.getDefinition().getNumberOfArguments());
+ InstructionIterator instructionIterator = code.entryBlock().iterator();
+ Argument argument = instructionIterator.next().asArgument();
+ while (argument != null) {
+ if (!argument.outValue().hasAnyUsers()) {
+ unusedArguments.set(argument.getIndex());
+ }
+ argument = instructionIterator.next().asArgument();
+ }
+ feedback.setUnusedArguments(method, unusedArguments);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java
index 58c0f9b..12c5b37 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java
@@ -43,4 +43,6 @@
AppView<AppInfoWithLiveness> appView,
SimpleInliningConstraint constraint,
SimpleInliningConstraintFactory factory);
+
+ public abstract BitSet fixupUnusedArguments(BitSet unusedArguments);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index c4a08f4..dd6f917 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -25,6 +25,7 @@
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.BitSetUtils;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions;
import java.util.BitSet;
@@ -73,6 +74,8 @@
private SimpleInliningConstraint simpleInliningConstraint =
NeverSimpleInliningConstraint.getInstance();
+ private BitSet unusedArguments = null;
+
// To reduce the memory footprint of UpdatableMethodOptimizationInfo, all the boolean fields are
// merged into a flag int field. The various static final FLAG fields indicate which bit is
// used by each boolean. DEFAULT_FLAGS encodes the default value for efficient instantiation and
@@ -165,7 +168,8 @@
.fixupNonNullParamOnNormalExits(fixer)
.fixupNonNullParamOrThrow(fixer)
.fixupReturnedArgumentIndex(fixer)
- .fixupSimpleInliningConstraint(appView, fixer);
+ .fixupSimpleInliningConstraint(appView, fixer)
+ .fixupUnusedArguments(fixer);
}
public MutableMethodOptimizationInfo fixupClassTypeReferences(
@@ -280,6 +284,10 @@
this.enumUnboxerMethodClassification = enumUnboxerMethodClassification;
}
+ public void unsetEnumUnboxerMethodClassification() {
+ this.enumUnboxerMethodClassification = EnumUnboxerMethodClassification.unknown();
+ }
+
public MutableMethodOptimizationInfo fixupEnumUnboxerMethodClassification(
MethodOptimizationInfoFixer fixer) {
enumUnboxerMethodClassification =
@@ -419,6 +427,25 @@
}
@Override
+ public BitSet getUnusedArguments() {
+ return unusedArguments;
+ }
+
+ public MutableMethodOptimizationInfo fixupUnusedArguments(MethodOptimizationInfoFixer fixer) {
+ unusedArguments = fixer.fixupUnusedArguments(unusedArguments);
+ return this;
+ }
+
+ void setUnusedArguments(BitSet unusedArguments) {
+ // Verify monotonicity (i.e., unused arguments should never become used).
+ assert !hasUnusedArguments() || unusedArguments != null;
+ assert !hasUnusedArguments()
+ || BitSetUtils.verifyLessThanOrEqualTo(getUnusedArguments(), unusedArguments);
+ this.unusedArguments =
+ unusedArguments != null && !unusedArguments.isEmpty() ? unusedArguments : null;
+ }
+
+ @Override
public boolean isInitializerEnablingJavaVmAssertions() {
return isFlagSet(INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index c9390d7..472a65b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -272,6 +272,11 @@
}
@Override
+ public void unsetEnumUnboxerMethodClassification(ProgramMethod method) {
+ getMethodOptimizationInfoForUpdating(method).unsetEnumUnboxerMethodClassification();
+ }
+
+ @Override
public synchronized void setInstanceInitializerInfoCollection(
DexEncodedMethod method,
InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
@@ -304,4 +309,9 @@
public synchronized void classInitializerMayBePostponed(DexEncodedMethod method) {
getMethodOptimizationInfoForUpdating(method).markClassInitializerMayBePostponed();
}
+
+ @Override
+ public synchronized void setUnusedArguments(ProgramMethod method, BitSet unusedArguments) {
+ getMethodOptimizationInfoForUpdating(method).setUnusedArguments(unusedArguments);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 623d8d2..366a1ac 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -122,6 +122,9 @@
ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {}
@Override
+ public void unsetEnumUnboxerMethodClassification(ProgramMethod method) {}
+
+ @Override
public void setInstanceInitializerInfoCollection(
DexEncodedMethod method,
InstanceInitializerInfoCollection instanceInitializerInfoCollection) {}
@@ -141,4 +144,7 @@
@Override
public void classInitializerMayBePostponed(DexEncodedMethod method) {}
+
+ @Override
+ public void setUnusedArguments(ProgramMethod method, BitSet unusedArguments) {}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 47f1b8e..f33bf52 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -194,6 +194,16 @@
}
@Override
+ public void unsetEnumUnboxerMethodClassification(ProgramMethod method) {
+ if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
+ method
+ .getOptimizationInfo()
+ .asMutableMethodOptimizationInfo()
+ .unsetEnumUnboxerMethodClassification();
+ }
+ }
+
+ @Override
public void setInstanceInitializerInfoCollection(
DexEncodedMethod method,
InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
@@ -227,4 +237,9 @@
public void classInitializerMayBePostponed(DexEncodedMethod method) {
method.getMutableOptimizationInfo().markClassInitializerMayBePostponed();
}
+
+ @Override
+ public void setUnusedArguments(ProgramMethod method, BitSet unusedArguments) {
+ method.getDefinition().getMutableOptimizationInfo().setUnusedArguments(unusedArguments);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 36719be..cabc28f 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -4,8 +4,6 @@
package com.android.tools.r8.optimize.argumentpropagation;
-import static com.android.tools.r8.optimize.argumentpropagation.utils.StronglyConnectedProgramClasses.computeStronglyConnectedProgramClasses;
-
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
@@ -18,6 +16,7 @@
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.VirtualRootMethodsAnalysis;
import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.ArgumentPropagatorReprocessingCriteriaCollection;
+import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
@@ -74,7 +73,8 @@
ImmediateProgramSubtypingInfo immediateSubtypingInfo =
ImmediateProgramSubtypingInfo.create(appView);
List<Set<DexProgramClass>> stronglyConnectedProgramClasses =
- computeStronglyConnectedProgramClasses(appView, immediateSubtypingInfo);
+ new ProgramClassesBidirectedGraph(appView, immediateSubtypingInfo)
+ .computeStronglyConnectedComponents();
ThreadUtils.processItems(
stronglyConnectedProgramClasses,
classes -> {
@@ -132,7 +132,8 @@
ImmediateProgramSubtypingInfo immediateSubtypingInfo =
ImmediateProgramSubtypingInfo.create(appView);
List<Set<DexProgramClass>> stronglyConnectedProgramComponents =
- computeStronglyConnectedProgramClasses(appView, immediateSubtypingInfo);
+ new ProgramClassesBidirectedGraph(appView, immediateSubtypingInfo)
+ .computeStronglyConnectedComponents();
timing.end();
// Set the optimization info on each method.
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
index 6288a63..a61d0b4 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
@@ -35,6 +35,7 @@
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeParameterState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteReceiverParameterState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameterFactory;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ParameterState;
@@ -63,6 +64,8 @@
private final AppView<AppInfoWithLiveness> appView;
+ private final MethodParameterFactory methodParameterFactory = new MethodParameterFactory();
+
private final Set<DexMethod> monomorphicVirtualMethods = Sets.newIdentityHashSet();
/**
@@ -99,12 +102,34 @@
return virtualRootMethods.get(method.getReference());
}
+ boolean isMethodParameterAlreadyUnknown(MethodParameter methodParameter, ProgramMethod method) {
+ MethodState methodState =
+ methodStates.get(
+ method.getDefinition().belongsToDirectPool() || isMonomorphicVirtualMethod(method)
+ ? method.getReference()
+ : getVirtualRootMethod(method));
+ if (methodState.isPolymorphic()) {
+ methodState = methodState.asPolymorphic().getMethodStateForBounds(DynamicType.unknown());
+ }
+ if (methodState.isMonomorphic()) {
+ ParameterState parameterState =
+ methodState.asMonomorphic().getParameterState(methodParameter.getIndex());
+ return parameterState.isUnknown();
+ }
+ assert methodState.isBottom() || methodState.isUnknown();
+ return methodState.isUnknown();
+ }
+
boolean isMonomorphicVirtualMethod(ProgramMethod method) {
- boolean isMonomorphicVirtualMethod = monomorphicVirtualMethods.contains(method.getReference());
+ boolean isMonomorphicVirtualMethod = isMonomorphicVirtualMethod(method.getReference());
assert method.getDefinition().belongsToVirtualPool() || !isMonomorphicVirtualMethod;
return isMonomorphicVirtualMethod;
}
+ boolean isMonomorphicVirtualMethod(DexMethod method) {
+ return monomorphicVirtualMethods.contains(method);
+ }
+
void scan(ProgramMethod method, IRCode code, Timing timing) {
timing.begin("Argument propagation scanner");
for (Invoke invoke : code.<Invoke>instructions(Instruction::isInvoke)) {
@@ -197,14 +222,11 @@
// possible dispatch targets and propagate the information to these methods (this is expensive).
// Instead we record the information in one place and then later propagate the information to
// all dispatch targets.
- DexMethod representativeMethodReference =
- getRepresentativeForPolymorphicInvokeOrElse(
- invoke, resolvedMethod, resolvedMethod.getReference());
ProgramMethod finalResolvedMethod = resolvedMethod;
timing.begin("Add method state");
methodStates.addTemporaryMethodState(
appView,
- representativeMethodReference,
+ getRepresentative(invoke, resolvedMethod),
existingMethodState ->
computeMethodState(invoke, finalResolvedMethod, context, existingMethodState, timing),
timing);
@@ -224,10 +246,8 @@
// compute a polymorphic method state, which includes information about the receiver's dynamic
// type bounds.
timing.begin("Compute method state for invoke");
- boolean isPolymorphicInvoke =
- getRepresentativeForPolymorphicInvokeOrElse(invoke, resolvedMethod, null) != null;
MethodState result;
- if (isPolymorphicInvoke) {
+ if (shouldUsePolymorphicMethodState(invoke, resolvedMethod)) {
assert existingMethodState.isBottom() || existingMethodState.isPolymorphic();
result =
computePolymorphicMethodState(
@@ -420,8 +440,11 @@
// potentially called from this invoke instruction.
if (argumentRoot.isArgument()) {
MethodParameter forwardedParameter =
- new MethodParameter(
- context.getReference(), argumentRoot.getDefinition().asArgument().getIndex());
+ methodParameterFactory.create(
+ context, argumentRoot.getDefinition().asArgument().getIndex());
+ if (isMethodParameterAlreadyUnknown(forwardedParameter, context)) {
+ return ParameterState.unknown();
+ }
if (parameterTypeElement.isClassType()) {
return new ConcreteClassTypeParameterState(forwardedParameter);
} else if (parameterTypeElement.isArrayType()) {
@@ -461,10 +484,9 @@
: new ConcretePrimitiveTypeParameterState(abstractValue);
}
- private DexMethod getRepresentativeForPolymorphicInvokeOrElse(
- InvokeMethod invoke, ProgramMethod resolvedMethod, DexMethod defaultValue) {
+ private DexMethod getRepresentative(InvokeMethod invoke, ProgramMethod resolvedMethod) {
if (resolvedMethod.getDefinition().belongsToDirectPool()) {
- return defaultValue;
+ return resolvedMethod.getReference();
}
if (invoke.isInvokeInterface()) {
@@ -475,14 +497,22 @@
assert invoke.isInvokeSuper() || invoke.isInvokeVirtual();
if (isMonomorphicVirtualMethod(resolvedMethod)) {
- return defaultValue;
+ return resolvedMethod.getReference();
}
DexMethod rootMethod = getVirtualRootMethod(resolvedMethod);
assert rootMethod != null;
+ assert !isMonomorphicVirtualMethod(resolvedMethod)
+ || rootMethod == resolvedMethod.getReference();
return rootMethod;
}
+ private boolean shouldUsePolymorphicMethodState(
+ InvokeMethod invoke, ProgramMethod resolvedMethod) {
+ return !resolvedMethod.getDefinition().belongsToDirectPool()
+ && !isMonomorphicVirtualMethod(getRepresentative(invoke, resolvedMethod));
+ }
+
private void scan(InvokeCustom invoke, ProgramMethod context) {
// If the bootstrap method is program declared it will be called. The call is with runtime
// provided arguments so ensure that the argument information is unknown.
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
index 69f9e86..6f97143 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
@@ -12,10 +12,14 @@
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteArrayTypeParameterState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeParameterState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteParameterState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeParameterState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ParameterState;
@@ -26,8 +30,11 @@
import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.MethodReprocessingCriteria;
import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Iterables;
+import java.util.BitSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -152,6 +159,8 @@
return;
}
+ methodState = getMethodStateAfterUnusedParameterRemoval(method, methodState);
+
if (methodState.isUnknown()) {
// Nothing is known about the arguments to this method.
return;
@@ -214,4 +223,51 @@
ConcreteCallSiteOptimizationInfo.fromMethodState(
appView, method, monomorphicMethodState));
}
+
+ private MethodState getMethodStateAfterUnusedParameterRemoval(
+ ProgramMethod method, MethodState methodState) {
+ assert methodState.isMonomorphic() || methodState.isUnknown();
+ if (!method.getOptimizationInfo().hasUnusedArguments()
+ || appView.appInfo().isKeepUnusedArgumentsMethod(method)) {
+ return methodState;
+ }
+
+ int numberOfArguments = method.getDefinition().getNumberOfArguments();
+ List<ParameterState> parameterStates =
+ methodState.isMonomorphic()
+ ? methodState.asMonomorphic().getParameterStates()
+ : ListUtils.newInitializedArrayList(numberOfArguments, ParameterState.unknown());
+
+ BitSet unusedArguments = method.getOptimizationInfo().getUnusedArguments();
+ for (int argumentIndex = method.getDefinition().getFirstNonReceiverArgumentIndex();
+ argumentIndex < numberOfArguments;
+ argumentIndex++) {
+ boolean isUnused = unusedArguments.get(argumentIndex);
+ if (isUnused) {
+ DexType argumentType = method.getArgumentType(argumentIndex);
+ parameterStates.set(argumentIndex, getUnusedParameterState(argumentType));
+ }
+ }
+
+ if (methodState.isUnknown()) {
+ if (!unusedArguments.get(0) || Iterables.any(parameterStates, ParameterState::isConcrete)) {
+ assert parameterStates.stream().anyMatch(ParameterState::isConcrete);
+ return new ConcreteMonomorphicMethodState(parameterStates);
+ }
+ }
+ return methodState;
+ }
+
+ private ParameterState getUnusedParameterState(DexType argumentType) {
+ if (argumentType.isArrayType()) {
+ return new ConcreteArrayTypeParameterState(Nullability.definitelyNull());
+ } else if (argumentType.isClassType()) {
+ return new ConcreteClassTypeParameterState(
+ appView.abstractValueFactory().createNullValue(), DynamicType.definitelyNull());
+ } else {
+ assert argumentType.isPrimitiveType();
+ return new ConcretePrimitiveTypeParameterState(
+ appView.abstractValueFactory().createZeroValue());
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index 76845cc..5f14c9d 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -265,7 +265,8 @@
private boolean isParameterRemovalAllowed(ProgramMethod method) {
return appView.getKeepInfo(method).isParameterRemovalAllowed(options)
- && !method.getDefinition().isLibraryMethodOverride().isPossiblyTrue();
+ && !method.getDefinition().isLibraryMethodOverride().isPossiblyTrue()
+ && !appView.appInfo().isMethodTargetedByInvokeDynamic(method);
}
private boolean canRemoveParameterFromVirtualMethods(
@@ -357,8 +358,7 @@
// We need to find a new name for this method, since the signature is already occupied.
// TODO(b/190154391): Instead of generating a new name, we could also try permuting the order
- // of
- // parameters.
+ // of parameters.
DexMethod newMethod =
dexItemFactory.createFreshMethodNameWithoutHolder(
method.getName().toString(),
@@ -390,8 +390,14 @@
assert method.getDefinition().belongsToDirectPool();
// TODO(b/190154391): Allow parameter removal from initializers. We need to guarantee absence
// of collisions since initializers can't be renamed.
- if (!appView.getKeepInfo(method).isParameterRemovalAllowed(options)
- || method.getDefinition().isInstanceInitializer()) {
+ if (!isParameterRemovalAllowed(method) || method.getDefinition().isInstanceInitializer()) {
+ return ArgumentInfoCollection.empty();
+ }
+ // TODO(b/199864962): Allow parameter removal from check-not-null classified methods.
+ if (method
+ .getOptimizationInfo()
+ .getEnumUnboxerMethodClassification()
+ .isCheckNotNullClassification()) {
return ArgumentInfoCollection.empty();
}
return computeRemovableParametersFromMethod(method);
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeParameterState.java
index 666c87b..3ba87ce 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeParameterState.java
@@ -25,6 +25,7 @@
AppView<AppInfoWithLiveness> appView,
ParameterState parameterState,
DexType parameterType,
+ StateCloner cloner,
Action onChangedAction) {
if (parameterState.isBottom()) {
return this;
@@ -36,6 +37,9 @@
assert parameterState.asConcrete().isReferenceParameter();
ConcreteReferenceTypeParameterState concreteParameterState =
parameterState.asConcrete().asReferenceParameter();
+ if (concreteParameterState.isArrayParameter()) {
+ return cloner.mutableCopy(concreteParameterState);
+ }
Nullability nullability = concreteParameterState.getNullability();
if (nullability.isMaybeNull()) {
return unknown();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeParameterState.java
index d1df0ac..2ae85ed 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeParameterState.java
@@ -27,6 +27,7 @@
AppView<AppInfoWithLiveness> appView,
ParameterState parameterState,
DexType parameterType,
+ StateCloner cloner,
Action onChangedAction) {
if (parameterState.isBottom()) {
return this;
@@ -42,6 +43,9 @@
DynamicType dynamicType = concreteParameterState.getDynamicType();
DynamicType widenedDynamicType =
WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, parameterType);
+ if (concreteParameterState.isClassParameter() && !widenedDynamicType.isUnknown()) {
+ return cloner.mutableCopy(concreteParameterState);
+ }
return abstractValue.isUnknown() && widenedDynamicType.isUnknown()
? unknown()
: new ConcreteClassTypeParameterState(
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomMethodState.java
index f5f117e..b7564eb 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomMethodState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomMethodState.java
@@ -44,7 +44,8 @@
public MethodState mutableJoin(
AppView<AppInfoWithLiveness> appView,
DexMethodSignature methodSignature,
- MethodState methodState) {
+ MethodState methodState,
+ StateCloner cloner) {
return methodState.mutableCopy();
}
@@ -52,7 +53,8 @@
public MethodState mutableJoin(
AppView<AppInfoWithLiveness> appView,
DexMethodSignature methodSignature,
- Function<MethodState, MethodState> methodStateSupplier) {
- return mutableJoin(appView, methodSignature, methodStateSupplier.apply(this));
+ Function<MethodState, MethodState> methodStateSupplier,
+ StateCloner cloner) {
+ return mutableJoin(appView, methodSignature, methodStateSupplier.apply(this), cloner);
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeParameterState.java
index fc88145..729938d 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeParameterState.java
@@ -25,6 +25,7 @@
AppView<AppInfoWithLiveness> appView,
ParameterState parameterState,
DexType parameterType,
+ StateCloner cloner,
Action onChangedAction) {
if (parameterState.isBottom()) {
assert parameterState == bottomPrimitiveTypeParameter();
@@ -35,6 +36,6 @@
}
assert parameterState.isConcrete();
assert parameterState.asConcrete().isPrimitiveParameter();
- return parameterState.mutableCopy();
+ return cloner.mutableCopy(parameterState);
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomReceiverParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomReceiverParameterState.java
index 3a94858..aa331f8 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomReceiverParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomReceiverParameterState.java
@@ -25,6 +25,7 @@
AppView<AppInfoWithLiveness> appView,
ParameterState parameterState,
DexType parameterType,
+ StateCloner cloner,
Action onChangedAction) {
if (parameterState.isBottom()) {
return this;
@@ -36,6 +37,9 @@
assert parameterState.asConcrete().isReferenceParameter();
ConcreteReferenceTypeParameterState concreteParameterState =
parameterState.asConcrete().asReferenceParameter();
+ if (concreteParameterState.isReceiverParameter()) {
+ return cloner.mutableCopy(concreteParameterState);
+ }
DynamicType dynamicType = concreteParameterState.getDynamicType();
if (dynamicType.isUnknown()) {
return unknown();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java
index eb75ac9..9a9de09 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java
@@ -106,7 +106,7 @@
return unknown();
}
boolean inParametersChanged = mutableJoinInParameters(parameterState);
- if (widenInParameters()) {
+ if (widenInParameters(appView)) {
return unknown();
}
if (inParametersChanged) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
index 02db24d..9af5fb3 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
@@ -123,7 +123,7 @@
return unknown();
}
boolean inParametersChanged = mutableJoinInParameters(parameterState);
- if (widenInParameters()) {
+ if (widenInParameters(appView)) {
return unknown();
}
if (abstractValue != oldAbstractValue
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMethodState.java
index ecd3e08..613d864 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMethodState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMethodState.java
@@ -25,33 +25,38 @@
public MethodState mutableJoin(
AppView<AppInfoWithLiveness> appView,
DexMethodSignature methodSignature,
- MethodState methodState) {
+ MethodState methodState,
+ StateCloner cloner) {
if (methodState.isBottom()) {
return this;
}
if (methodState.isUnknown()) {
return methodState;
}
- return mutableJoin(appView, methodSignature, methodState.asConcrete());
+ return mutableJoin(appView, methodSignature, methodState.asConcrete(), cloner);
}
@Override
public MethodState mutableJoin(
AppView<AppInfoWithLiveness> appView,
DexMethodSignature methodSignature,
- Function<MethodState, MethodState> methodStateSupplier) {
- return mutableJoin(appView, methodSignature, methodStateSupplier.apply(this));
+ Function<MethodState, MethodState> methodStateSupplier,
+ StateCloner cloner) {
+ return mutableJoin(appView, methodSignature, methodStateSupplier.apply(this), cloner);
}
private MethodState mutableJoin(
AppView<AppInfoWithLiveness> appView,
DexMethodSignature methodSignature,
- ConcreteMethodState methodState) {
+ ConcreteMethodState methodState,
+ StateCloner cloner) {
if (isMonomorphic() && methodState.isMonomorphic()) {
- return asMonomorphic().mutableJoin(appView, methodSignature, methodState.asMonomorphic());
+ return asMonomorphic()
+ .mutableJoin(appView, methodSignature, methodState.asMonomorphic(), cloner);
}
if (isPolymorphic() && methodState.isPolymorphic()) {
- return asPolymorphic().mutableJoin(appView, methodSignature, methodState.asPolymorphic());
+ return asPolymorphic()
+ .mutableJoin(appView, methodSignature, methodState.asPolymorphic(), cloner);
}
assert false;
return unknown();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java
index b49e48f..c98c383 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java
@@ -46,7 +46,8 @@
public ConcreteMonomorphicMethodStateOrUnknown mutableJoin(
AppView<AppInfoWithLiveness> appView,
DexMethodSignature methodSignature,
- ConcreteMonomorphicMethodState methodState) {
+ ConcreteMonomorphicMethodState methodState,
+ StateCloner cloner) {
if (size() != methodState.size()) {
assert false;
return unknown();
@@ -59,7 +60,7 @@
ParameterState otherParameterState = methodState.parameterStates.get(0);
DexType parameterType = null;
parameterStates.set(
- 0, parameterState.mutableJoin(appView, otherParameterState, parameterType));
+ 0, parameterState.mutableJoin(appView, otherParameterState, parameterType, cloner));
argumentIndex++;
}
@@ -68,7 +69,8 @@
ParameterState otherParameterState = methodState.parameterStates.get(argumentIndex);
DexType parameterType = methodSignature.getParameter(parameterIndex);
parameterStates.set(
- argumentIndex, parameterState.mutableJoin(appView, otherParameterState, parameterType));
+ argumentIndex,
+ parameterState.mutableJoin(appView, otherParameterState, parameterType, cloner));
assert !parameterStates.get(argumentIndex).isConcrete()
|| !parameterStates.get(argumentIndex).asConcrete().isReceiverParameter();
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteParameterState.java
index 3b9da36..ed3873f 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteParameterState.java
@@ -107,6 +107,7 @@
AppView<AppInfoWithLiveness> appView,
ParameterState parameterState,
DexType parameterType,
+ StateCloner cloner,
Action onChangedAction) {
if (parameterState.isBottom()) {
return this;
@@ -140,8 +141,13 @@
return inParameters.addAll(parameterState.inParameters);
}
- boolean widenInParameters() {
- // TODO(b/190154391): Widen to unknown when the size of the collection exceeds a threshold.
- return false;
+ /**
+ * Returns true if the in-parameters set should be widened to unknown, in which case the entire
+ * parameter state must be widened to unknown.
+ */
+ boolean widenInParameters(AppView<AppInfoWithLiveness> appView) {
+ return inParameters != null
+ && inParameters.size()
+ > appView.options().callSiteOptimizationOptions().getMaxNumberOfInParameters();
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodState.java
index c4a1feb..d17ff72 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodState.java
@@ -45,7 +45,8 @@
AppView<AppInfoWithLiveness> appView,
DexMethodSignature methodSignature,
DynamicType bounds,
- ConcreteMonomorphicMethodStateOrUnknown methodState) {
+ ConcreteMonomorphicMethodStateOrUnknown methodState,
+ StateCloner cloner) {
assert !isEffectivelyBottom();
assert !isEffectivelyUnknown();
if (methodState.isUnknown()) {
@@ -58,7 +59,8 @@
} else {
assert methodState.isMonomorphic();
ConcreteMonomorphicMethodStateOrUnknown newMethodStateForBounds =
- joinInner(appView, methodSignature, receiverBoundsToState.get(bounds), methodState);
+ joinInner(
+ appView, methodSignature, receiverBoundsToState.get(bounds), methodState, cloner);
if (bounds.isUnknown() && newMethodStateForBounds.isUnknown()) {
return unknown();
} else {
@@ -68,19 +70,23 @@
}
}
+ @SuppressWarnings("unchecked")
private static ConcreteMonomorphicMethodStateOrUnknown joinInner(
AppView<AppInfoWithLiveness> appView,
DexMethodSignature methodSignature,
ConcreteMonomorphicMethodStateOrUnknown methodState,
- ConcreteMonomorphicMethodStateOrUnknown other) {
+ ConcreteMonomorphicMethodStateOrUnknown other,
+ StateCloner cloner) {
if (methodState == null) {
- return other.mutableCopy();
+ return (ConcreteMonomorphicMethodStateOrUnknown) cloner.mutableCopy(other);
}
if (methodState.isUnknown() || other.isUnknown()) {
return unknown();
}
assert methodState.isMonomorphic();
- return methodState.asMonomorphic().mutableJoin(appView, methodSignature, other.asMonomorphic());
+ return methodState
+ .asMonomorphic()
+ .mutableJoin(appView, methodSignature, other.asMonomorphic(), cloner);
}
public void forEach(
@@ -118,7 +124,8 @@
public MethodState mutableCopyWithRewrittenBounds(
AppView<AppInfoWithLiveness> appView,
Function<DynamicType, DynamicType> boundsRewriter,
- DexMethodSignature methodSignature) {
+ DexMethodSignature methodSignature,
+ StateCloner cloner) {
assert !isEffectivelyBottom();
assert !isEffectivelyUnknown();
Map<DynamicType, ConcreteMonomorphicMethodStateOrUnknown> rewrittenReceiverBoundsToState =
@@ -132,7 +139,8 @@
ConcreteMonomorphicMethodStateOrUnknown existingMethodStateForBounds =
rewrittenReceiverBoundsToState.get(rewrittenBounds);
ConcreteMonomorphicMethodStateOrUnknown newMethodStateForBounds =
- joinInner(appView, methodSignature, existingMethodStateForBounds, entry.getValue());
+ joinInner(
+ appView, methodSignature, existingMethodStateForBounds, entry.getValue(), cloner);
if (rewrittenBounds.isUnknown() && newMethodStateForBounds.isUnknown()) {
return unknown();
}
@@ -146,7 +154,8 @@
public MethodState mutableJoin(
AppView<AppInfoWithLiveness> appView,
DexMethodSignature methodSignature,
- ConcretePolymorphicMethodState methodState) {
+ ConcretePolymorphicMethodState methodState,
+ StateCloner cloner) {
assert !isEffectivelyBottom();
assert !isEffectivelyUnknown();
assert !methodState.isEffectivelyBottom();
@@ -154,7 +163,7 @@
for (Entry<DynamicType, ConcreteMonomorphicMethodStateOrUnknown> entry :
methodState.receiverBoundsToState.entrySet()) {
ConcretePolymorphicMethodStateOrUnknown result =
- add(appView, methodSignature, entry.getKey(), entry.getValue());
+ add(appView, methodSignature, entry.getKey(), entry.getValue(), cloner);
if (result.isUnknown()) {
return result;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java
index 3f1baff..429e514 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java
@@ -69,7 +69,7 @@
return unknown();
}
boolean inParametersChanged = mutableJoinInParameters(parameterState);
- if (widenInParameters()) {
+ if (widenInParameters(appView)) {
return unknown();
}
if (abstractValue != oldAbstractValue || inParametersChanged) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverParameterState.java
index d7f7bbc..74c9107 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverParameterState.java
@@ -100,7 +100,7 @@
return unknown();
}
boolean inParametersChanged = mutableJoinInParameters(parameterState);
- if (widenInParameters()) {
+ if (widenInParameters(appView)) {
return unknown();
}
if (!dynamicType.equals(oldDynamicType) || inParametersChanged) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameterFactory.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameterFactory.java
new file mode 100644
index 0000000..d1501e6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameterFactory.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2021, 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.optimize.argumentpropagation.codescanner;
+
+import com.android.tools.r8.graph.ProgramMethod;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+
+public class MethodParameterFactory {
+
+ private final Map<MethodParameter, MethodParameter> methodParameters = new ConcurrentHashMap<>();
+
+ public MethodParameter create(ProgramMethod method, int argumentIndex) {
+ return methodParameters.computeIfAbsent(
+ new MethodParameter(method.getReference(), argumentIndex), Function.identity());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodState.java
index 0de2d3e..b9df609 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodState.java
@@ -44,10 +44,12 @@
MethodState mutableJoin(
AppView<AppInfoWithLiveness> appView,
DexMethodSignature methodSignature,
- MethodState methodState);
+ MethodState methodState,
+ StateCloner cloner);
MethodState mutableJoin(
AppView<AppInfoWithLiveness> appView,
DexMethodSignature methodSignature,
- Function<MethodState, MethodState> methodStateSupplier);
+ Function<MethodState, MethodState> methodStateSupplier,
+ StateCloner cloner);
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java
index 50f02c9..856ff9d 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java
@@ -45,7 +45,8 @@
newMethodState = methodState.mutableCopy();
} else {
newMethodState =
- existingMethodState.mutableJoin(appView, getSignature(method), methodState);
+ existingMethodState.mutableJoin(
+ appView, getSignature(method), methodState, StateCloner.getCloner());
}
assert !newMethodState.isBottom();
return newMethodState;
@@ -73,7 +74,8 @@
assert !existingMethodState.isBottom();
timing.begin("Join temporary method state");
MethodState joinResult =
- existingMethodState.mutableJoin(appView, getSignature(method), methodStateSupplier);
+ existingMethodState.mutableJoin(
+ appView, getSignature(method), methodStateSupplier, StateCloner.getIdentity());
assert !joinResult.isBottom();
timing.end();
return joinResult;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ParameterState.java
index ad7b97c..d70fdfd 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ParameterState.java
@@ -57,13 +57,17 @@
public abstract ParameterState mutableCopy();
public final ParameterState mutableJoin(
- AppView<AppInfoWithLiveness> appView, ParameterState parameterState, DexType parameterType) {
- return mutableJoin(appView, parameterState, parameterType, Action.empty());
+ AppView<AppInfoWithLiveness> appView,
+ ParameterState parameterState,
+ DexType parameterType,
+ StateCloner cloner) {
+ return mutableJoin(appView, parameterState, parameterType, cloner, Action.empty());
}
public abstract ParameterState mutableJoin(
AppView<AppInfoWithLiveness> appView,
ParameterState parameterState,
DexType parameterType,
+ StateCloner cloner,
Action onChangedAction);
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/StateCloner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/StateCloner.java
new file mode 100644
index 0000000..6a2e941
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/StateCloner.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2021, 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.optimize.argumentpropagation.codescanner;
+
+/**
+ * A strategy for cloning method and parameter states.
+ *
+ * <p>During the primary optimization pass, for each invoke we compute a fresh method state and join
+ * the state into the existing method state for the resolved method. Since the added method state is
+ * completely fresh and not stored anywhere else, we can avoid copying the method state when we join
+ * it into the existing method state. This is achieved by using the {@link #getIdentity()} cloner
+ * below.
+ *
+ * <p>When we later propagate argument information for virtual methods to their overrides, we join
+ * method states from one virtual method into the state for another virtual method. Therefore, it is
+ * important to copy the method state during the join, which is achieved using the {@link
+ * #getCloner()} cloner.
+ */
+public abstract class StateCloner {
+
+ private static StateCloner CLONER =
+ new StateCloner() {
+ @Override
+ public MethodState mutableCopy(MethodState methodState) {
+ return methodState.mutableCopy();
+ }
+
+ @Override
+ public ParameterState mutableCopy(ParameterState parameterState) {
+ return parameterState.mutableCopy();
+ }
+ };
+
+ private static StateCloner IDENTITY =
+ new StateCloner() {
+ @Override
+ public MethodState mutableCopy(MethodState methodState) {
+ return methodState;
+ }
+
+ @Override
+ public ParameterState mutableCopy(ParameterState parameterState) {
+ return parameterState;
+ }
+ };
+
+ public static StateCloner getCloner() {
+ return CLONER;
+ }
+
+ public static StateCloner getIdentity() {
+ return IDENTITY;
+ }
+
+ public abstract MethodState mutableCopy(MethodState methodState);
+
+ public abstract ParameterState mutableCopy(ParameterState parameterState);
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownMethodState.java
index b4bb41f..dd822b8 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownMethodState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownMethodState.java
@@ -35,7 +35,8 @@
public MethodState mutableJoin(
AppView<AppInfoWithLiveness> appView,
DexMethodSignature methodSignature,
- MethodState methodState) {
+ MethodState methodState,
+ StateCloner cloner) {
return this;
}
@@ -43,7 +44,8 @@
public MethodState mutableJoin(
AppView<AppInfoWithLiveness> appView,
DexMethodSignature methodSignature,
- Function<MethodState, MethodState> methodStateSupplier) {
+ Function<MethodState, MethodState> methodStateSupplier,
+ StateCloner cloner) {
return this;
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownParameterState.java
index bcb5317..f70ae3d 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownParameterState.java
@@ -40,6 +40,7 @@
AppView<AppInfoWithLiveness> appView,
ParameterState parameterState,
DexType parameterType,
+ StateCloner cloner,
Action onChangedAction) {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
index 5d5399d..29a5e40 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
@@ -45,6 +45,22 @@
return root;
}
+ ProgramMethod getSingleNonAbstractMethod() {
+ ProgramMethod singleNonAbstractMethod = root.getAccessFlags().isAbstract() ? null : root;
+ for (ProgramMethod override : overrides) {
+ if (!override.getAccessFlags().isAbstract()) {
+ if (singleNonAbstractMethod != null) {
+ // Not a single non-abstract method.
+ return null;
+ }
+ singleNonAbstractMethod = override;
+ }
+ }
+ assert singleNonAbstractMethod == null
+ || !singleNonAbstractMethod.getAccessFlags().isAbstract();
+ return singleNonAbstractMethod;
+ }
+
void forEach(Consumer<ProgramMethod> consumer) {
consumer.accept(root);
overrides.forEach(consumer);
@@ -53,6 +69,12 @@
boolean hasOverrides() {
return !overrides.isEmpty();
}
+
+ boolean isInterfaceMethodWithSiblings() {
+ // TODO(b/190154391): Conservatively returns true for all interface methods, but should only
+ // return true for those with siblings.
+ return root.getHolder().isInterface();
+ }
}
private final Map<DexProgramClass, Map<DexMethodSignature, VirtualRootMethod>>
@@ -125,9 +147,25 @@
if (isMonomorphicVirtualMethod) {
monomorphicVirtualMethods.add(rootCandidate.getReference());
} else {
- virtualRootMethod.forEach(
- method ->
- virtualRootMethods.put(method.getReference(), rootCandidate.getReference()));
+ ProgramMethod singleNonAbstractMethod = virtualRootMethod.getSingleNonAbstractMethod();
+ if (singleNonAbstractMethod != null
+ && !virtualRootMethod.isInterfaceMethodWithSiblings()) {
+ virtualRootMethod.forEach(
+ method -> {
+ // Interface methods can have siblings and can therefore not be mapped to their
+ // unique non-abstract implementation, unless the interface method does not have
+ // any siblings.
+ virtualRootMethods.put(
+ method.getReference(), singleNonAbstractMethod.getReference());
+ });
+ if (!singleNonAbstractMethod.getHolder().isInterface()) {
+ monomorphicVirtualMethods.add(singleNonAbstractMethod.getReference());
+ }
+ } else {
+ virtualRootMethod.forEach(
+ method ->
+ virtualRootMethods.put(method.getReference(), rootCandidate.getReference()));
+ }
}
});
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java
index b551b68..9b583c7 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java
@@ -20,6 +20,8 @@
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyParameterState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ParameterState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.StateCloner;
+import com.android.tools.r8.optimize.argumentpropagation.utils.BidirectedGraph;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Action;
import com.android.tools.r8.utils.ThreadUtils;
@@ -27,6 +29,7 @@
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.List;
@@ -52,23 +55,26 @@
// must be included in the argument information for p'.
FlowGraph flowGraph = new FlowGraph(appView.appInfo().classes());
+ List<Set<ParameterNode>> stronglyConnectedComponents =
+ flowGraph.computeStronglyConnectedComponents();
+ ThreadUtils.processItems(stronglyConnectedComponents, this::process, executorService);
+
+ // The algorithm only changes the parameter states of each monomorphic method state. In case any
+ // of these method states have effectively become unknown, we replace them by the canonicalized
+ // unknown method state.
+ postProcessMethodStates(executorService);
+ }
+
+ private void process(Set<ParameterNode> stronglyConnectedComponent) {
// Build a worklist containing all the parameter nodes.
- Deque<ParameterNode> worklist = new ArrayDeque<>();
- flowGraph.forEachNode(worklist::add);
+ Deque<ParameterNode> worklist = new ArrayDeque<>(stronglyConnectedComponent);
// Repeatedly propagate argument information through edges in the flow graph until there are no
// more changes.
- // TODO(b/190154391): Consider parallelizing the flow propagation. There are a few scenarios
- // that need to be covered, such as (i) two threads could race to update the same parameter
- // state, (ii) a thread may try to propagate a parameter state to its successors while
- // another thread is trying to update the state of the parameter itself.
// TODO(b/190154391): Consider a path p1 -> p2 -> p3 in the graph. If we process p2 first, then
// p3, and then p1, then the processing of p1 could cause p2 to change, which means that we
// need to reprocess p2 and then p3. If we always process leaves in the graph first, we would
// process p1, then p2, then p3, and then be done.
- // TODO(b/190154391): Prune the graph on-the-fly. If the argument information for a parameter
- // becomes unknown, we could consider clearing its predecessors since none of the predecessors
- // could contribute any information even if they change.
while (!worklist.isEmpty()) {
ParameterNode parameterNode = worklist.removeLast();
parameterNode.unsetPending();
@@ -83,11 +89,6 @@
}
});
}
-
- // The algorithm only changes the parameter states of each monomorphic method state. In case any
- // of these method states have effectively become unknown, we replace them by the canonicalized
- // unknown method state.
- postProcessMethodStates(executorService);
}
private void propagate(
@@ -96,9 +97,19 @@
if (parameterState.isBottom()) {
return;
}
+ List<ParameterNode> newlyUnknownParameterNodes = new ArrayList<>();
for (ParameterNode successorNode : parameterNode.getSuccessors()) {
- successorNode.addState(
- appView, parameterState.asNonEmpty(), () -> affectedNodeConsumer.accept(successorNode));
+ ParameterState newParameterState =
+ successorNode.addState(
+ appView,
+ parameterState.asNonEmpty(),
+ () -> affectedNodeConsumer.accept(successorNode));
+ if (newParameterState.isUnknown()) {
+ newlyUnknownParameterNodes.add(successorNode);
+ }
+ }
+ for (ParameterNode newlyUnknownParameterNode : newlyUnknownParameterNodes) {
+ newlyUnknownParameterNode.clearPredecessors();
}
}
@@ -133,7 +144,7 @@
}
}
- private class FlowGraph {
+ public class FlowGraph extends BidirectedGraph<ParameterNode> {
private final Map<DexMethod, Int2ReferenceMap<ParameterNode>> nodes = new IdentityHashMap<>();
@@ -141,7 +152,14 @@
classes.forEach(this::add);
}
- void forEachNode(Consumer<? super ParameterNode> consumer) {
+ @Override
+ public void forEachNeighbor(ParameterNode node, Consumer<? super ParameterNode> consumer) {
+ node.getPredecessors().forEach(consumer);
+ node.getSuccessors().forEach(consumer);
+ }
+
+ @Override
+ public void forEachNode(Consumer<? super ParameterNode> consumer) {
nodes.values().forEach(nodesForMethod -> nodesForMethod.values().forEach(consumer));
}
@@ -278,6 +296,10 @@
predecessors.clear();
}
+ Set<ParameterNode> getPredecessors() {
+ return predecessors;
+ }
+
ParameterState getState() {
return methodState.getParameterState(parameterIndex);
}
@@ -294,18 +316,23 @@
return pending;
}
- void addState(
+ ParameterState addState(
AppView<AppInfoWithLiveness> appView,
NonEmptyParameterState parameterStateToAdd,
Action onChangedAction) {
ParameterState oldParameterState = getState();
ParameterState newParameterState =
oldParameterState.mutableJoin(
- appView, parameterStateToAdd, parameterType, onChangedAction);
+ appView,
+ parameterStateToAdd,
+ parameterType,
+ StateCloner.getCloner(),
+ onChangedAction);
if (newParameterState != oldParameterState) {
setState(newParameterState);
onChangedAction.execute();
}
+ return newParameterState;
}
void setPending() {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
index 72990cb..e1e5f63 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionBySignature;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.StateCloner;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.Collection;
import java.util.IdentityHashMap;
@@ -130,7 +131,7 @@
if (resolutionResult.isFailedResolution()) {
// TODO(b/190154391): Do we need to propagate argument information to the first
// virtual method above the inaccessible method in the class hierarchy?
- assert resolutionResult.isIllegalAccessErrorResult(subclass, appView.appInfo());
+ assert resolutionResult.asFailedResolution().hasMethodsCausingError();
return;
}
@@ -185,7 +186,8 @@
}
return null;
},
- resolvedMethod.getMethodSignature());
+ resolvedMethod.getMethodSignature(),
+ StateCloner.getCloner());
// If the resolved method is a virtual method that does not override any methods and are not
// overridden by any methods, then we use a monomorphic method state for it. Therefore, we
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
index 41cb0af..36525f5 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
@@ -23,6 +23,7 @@
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionBySignature;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.StateCloner;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.UnknownMethodState;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.Collection;
@@ -150,7 +151,9 @@
if (!activeUntilLowerBound.isEmpty()) {
DexMethodSignature methodSignature = method.getMethodSignature();
for (MethodStateCollectionBySignature methodStates : activeUntilLowerBound.values()) {
- methodState = methodState.mutableJoin(appView, methodSignature, methodStates.get(method));
+ methodState =
+ methodState.mutableJoin(
+ appView, methodSignature, methodStates.get(method), StateCloner.getCloner());
}
}
return methodState;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/BidirectedGraph.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/BidirectedGraph.java
new file mode 100644
index 0000000..8e76917
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/BidirectedGraph.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2021, 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.optimize.argumentpropagation.utils;
+
+import com.android.tools.r8.utils.WorkList;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public abstract class BidirectedGraph<T> {
+
+ public abstract void forEachNeighbor(T node, Consumer<? super T> consumer);
+
+ public abstract void forEachNode(Consumer<? super T> consumer);
+
+ /**
+ * Computes the strongly connected components in the current bidirectional graph (i.e., each
+ * strongly connected component can be found using a breadth first search).
+ */
+ public List<Set<T>> computeStronglyConnectedComponents() {
+ Set<T> seen = new HashSet<>();
+ List<Set<T>> stronglyConnectedComponents = new ArrayList<>();
+ forEachNode(
+ node -> {
+ if (seen.contains(node)) {
+ return;
+ }
+ Set<T> stronglyConnectedComponent = internalComputeStronglyConnectedProgramClasses(node);
+ stronglyConnectedComponents.add(stronglyConnectedComponent);
+ seen.addAll(stronglyConnectedComponent);
+ });
+ return stronglyConnectedComponents;
+ }
+
+ private Set<T> internalComputeStronglyConnectedProgramClasses(T node) {
+ WorkList<T> worklist = WorkList.newEqualityWorkList(node);
+ while (worklist.hasNext()) {
+ T current = worklist.next();
+ forEachNeighbor(current, worklist::addIfNotSeen);
+ }
+ return worklist.getSeenSet();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ProgramClassesBidirectedGraph.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ProgramClassesBidirectedGraph.java
new file mode 100644
index 0000000..474c445
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ProgramClassesBidirectedGraph.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2021, 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.optimize.argumentpropagation.utils;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.function.Consumer;
+
+public class ProgramClassesBidirectedGraph extends BidirectedGraph<DexProgramClass> {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
+
+ public ProgramClassesBidirectedGraph(
+ AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
+ this.appView = appView;
+ this.immediateSubtypingInfo = immediateSubtypingInfo;
+ }
+
+ @Override
+ public void forEachNeighbor(DexProgramClass node, Consumer<? super DexProgramClass> consumer) {
+ immediateSubtypingInfo.forEachImmediateProgramSuperClass(node, consumer);
+ immediateSubtypingInfo.getSubclasses(node).forEach(consumer);
+ }
+
+ @Override
+ public void forEachNode(Consumer<? super DexProgramClass> consumer) {
+ appView.appInfo().classes().forEach(consumer);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/StronglyConnectedProgramClasses.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/StronglyConnectedProgramClasses.java
deleted file mode 100644
index 25d28cb..0000000
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/StronglyConnectedProgramClasses.java
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) 2021, 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.optimize.argumentpropagation.utils;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.WorkList;
-import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-public class StronglyConnectedProgramClasses {
-
- /**
- * Computes the strongly connected components in the program class hierarchy (where extends and
- * implements edges are treated as bidirectional).
- */
- public static List<Set<DexProgramClass>> computeStronglyConnectedProgramClasses(
- AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
- Set<DexProgramClass> seen = Sets.newIdentityHashSet();
- List<Set<DexProgramClass>> stronglyConnectedComponents = new ArrayList<>();
- for (DexProgramClass clazz : appView.appInfo().classes()) {
- if (seen.contains(clazz)) {
- continue;
- }
- Set<DexProgramClass> stronglyConnectedComponent =
- internalComputeStronglyConnectedProgramClasses(clazz, immediateSubtypingInfo);
- stronglyConnectedComponents.add(stronglyConnectedComponent);
- seen.addAll(stronglyConnectedComponent);
- }
- return stronglyConnectedComponents;
- }
-
- private static Set<DexProgramClass> internalComputeStronglyConnectedProgramClasses(
- DexProgramClass clazz, ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
- WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList(clazz);
- while (worklist.hasNext()) {
- DexProgramClass current = worklist.next();
- immediateSubtypingInfo.forEachImmediateProgramSuperClass(current, worklist::addIfNotSeen);
- worklist.addIfNotSeen(immediateSubtypingInfo.getSubclasses(current));
- }
- return worklist.getSeenSet();
- }
-}
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index 62c04bb..11dcc13 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.OptionsParsing;
import com.android.tools.r8.utils.OptionsParsing.ParseContext;
+import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.Timing;
@@ -36,7 +37,6 @@
import java.util.List;
import java.util.Map;
import java.util.Scanner;
-import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
@@ -139,7 +139,7 @@
private final StackTraceLineParser<T, ST> stackTraceLineParser;
private final StackTraceElementProxyRetracer<T, ST> proxyRetracer;
private final DiagnosticsHandler diagnosticsHandler;
- private final boolean isVerbose;
+ protected final boolean isVerbose;
Retrace(
StackTraceLineParser<T, ST> stackTraceLineParser,
@@ -153,12 +153,12 @@
}
/**
- * Retraces a stack frame and calls the consumer for each retraced line
+ * Retraces a complete stack frame and returns a list of retraced stack traces.
*
* @param stackTrace the stack trace to be retrace
- * @param retracedFrameConsumer the consumer to accept the retraced stack trace.
+ * @return list of potentially ambiguous stack traces.
*/
- public void retraceStackTrace(List<T> stackTrace, Consumer<List<List<T>>> retracedFrameConsumer) {
+ public List<List<T>> retraceStackTrace(List<T> stackTrace) {
ListUtils.forEachWithIndex(
stackTrace,
(line, lineNumber) -> {
@@ -168,7 +168,57 @@
throw new RetraceAbortException();
}
});
- stackTrace.forEach(line -> retracedFrameConsumer.accept(retraceFrame(line)));
+ List<Pair<List<T>, RetraceStackTraceContext>> retracedStackTraces = new ArrayList<>();
+ retracedStackTraces.add(
+ new Pair<>(new ArrayList<>(), RetraceStackTraceContext.getInitialContext()));
+ retracedStackTraces =
+ ListUtils.fold(
+ stackTrace,
+ retracedStackTraces,
+ (acc, stackTraceLine) -> {
+ ST parsedLine = stackTraceLineParser.parse(stackTraceLine);
+ List<Pair<List<T>, RetraceStackTraceContext>> newRetracedStackTraces =
+ new ArrayList<>();
+ for (Pair<List<T>, RetraceStackTraceContext> retracedStackTrace : acc) {
+ Map<
+ RetraceStackTraceElementProxy<T, ST>,
+ List<RetraceStackTraceElementProxy<T, ST>>>
+ ambiguousBlocks = new HashMap<>();
+ List<RetraceStackTraceElementProxy<T, ST>> ambiguousKeys = new ArrayList<>();
+ proxyRetracer
+ .retrace(parsedLine, retracedStackTrace.getSecond())
+ .forEach(
+ retracedElement -> {
+ if (retracedElement.isTopFrame() || !retracedElement.hasRetracedClass()) {
+ ambiguousKeys.add(retracedElement);
+ ambiguousBlocks.put(retracedElement, new ArrayList<>());
+ }
+ ambiguousBlocks.get(ListUtils.last(ambiguousKeys)).add(retracedElement);
+ });
+ if (ambiguousKeys.isEmpty()) {
+ // This happens when there is nothing to report.
+ newRetracedStackTraces.add(
+ new Pair<>(
+ retracedStackTrace.getFirst(),
+ RetraceStackTraceContext.getInitialContext()));
+ continue;
+ }
+ Collections.sort(ambiguousKeys);
+ ambiguousKeys.forEach(
+ key -> {
+ List<T> resultList = new ArrayList<>();
+ resultList.addAll(retracedStackTrace.getFirst());
+ resultList.addAll(
+ ListUtils.map(
+ ambiguousBlocks.get(key),
+ retracedElement ->
+ parsedLine.toRetracedItem(retracedElement, isVerbose)));
+ newRetracedStackTraces.add(new Pair<>(resultList, key.getContext()));
+ });
+ }
+ return newRetracedStackTraces;
+ });
+ return ListUtils.map(retracedStackTraces, Pair::getFirst);
}
/**
@@ -178,11 +228,11 @@
* @return A collection of retraced frame where each entry in the outer list is ambiguous
*/
public List<List<T>> retraceFrame(T stackTraceFrame) {
- Map<RetraceStackTraceProxy<T, ST>, List<T>> ambiguousBlocks = new HashMap<>();
- List<RetraceStackTraceProxy<T, ST>> ambiguousKeys = new ArrayList<>();
+ Map<RetraceStackTraceElementProxy<T, ST>, List<T>> ambiguousBlocks = new HashMap<>();
+ List<RetraceStackTraceElementProxy<T, ST>> ambiguousKeys = new ArrayList<>();
ST parsedLine = stackTraceLineParser.parse(stackTraceFrame);
proxyRetracer
- .retrace(parsedLine)
+ .retrace(parsedLine, RetraceStackTraceContext.getInitialContext())
.forEach(
retracedElement -> {
if (retracedElement.isTopFrame() || !retracedElement.hasRetracedClass()) {
@@ -209,7 +259,7 @@
public List<T> retraceLine(T stackTraceLine) {
ST parsedLine = stackTraceLineParser.parse(stackTraceLine);
return proxyRetracer
- .retrace(parsedLine)
+ .retrace(parsedLine, RetraceStackTraceContext.getInitialContext())
.map(
retraceFrame -> {
retraceFrame.getOriginalItem().toRetracedItem(retraceFrame, isVerbose);
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassElement.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassElement.java
index 35380e4..43bf109 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassElement.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassElement.java
@@ -4,26 +4,31 @@
package com.android.tools.r8.retrace;
import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.TypeReference;
import java.util.List;
+import java.util.Optional;
@Keep
public interface RetraceClassElement extends RetraceElement<RetraceClassResult> {
RetracedClassReference getRetracedClass();
- RetraceSourceFileResult getSourceFile();
+ RetracedSourceFile getSourceFile();
RetraceFieldResult lookupField(String fieldName);
RetraceMethodResult lookupMethod(String methodName);
- RetraceFrameResult lookupFrame(String methodName);
-
- RetraceFrameResult lookupFrame(String methodName, int position);
+ RetraceFrameResult lookupFrame(Optional<Integer> position, String methodName);
RetraceFrameResult lookupFrame(
- String methodName, int position, List<TypeReference> formalTypes, TypeReference returnType);
+ Optional<Integer> position,
+ String methodName,
+ List<TypeReference> formalTypes,
+ TypeReference returnType);
+
+ RetraceFrameResult lookupFrame(Optional<Integer> position, MethodReference methodReference);
RetraceUnknownJsonMappingInformationResult getUnknownJsonMappingInformation();
}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
index 29f4703..e2b59fa 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.Keep;
import com.android.tools.r8.references.TypeReference;
import java.util.List;
+import java.util.Optional;
@Keep
public interface RetraceClassResult extends RetraceResult<RetraceClassElement> {
@@ -22,11 +23,13 @@
RetraceMethodResult lookupMethod(
String methodName, List<TypeReference> formalTypes, TypeReference returnType);
- RetraceFrameResult lookupFrame(String methodName);
-
- RetraceFrameResult lookupFrame(String methodName, int position);
+ RetraceFrameResult lookupFrame(
+ RetraceStackTraceContext context, Optional<Integer> position, String methodName);
RetraceFrameResult lookupFrame(
- String methodName, int position, List<TypeReference> formalTypes, TypeReference returnType);
-
+ RetraceStackTraceContext context,
+ Optional<Integer> position,
+ String methodName,
+ List<TypeReference> formalTypes,
+ TypeReference returnType);
}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceElement.java b/src/main/java/com/android/tools/r8/retrace/RetraceElement.java
index 55de558..63d40db 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceElement.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceElement.java
@@ -16,4 +16,6 @@
R getRetraceResultContext();
boolean isCompilerSynthesized();
+
+ RetraceStackTraceContext getContext();
}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceFieldElement.java b/src/main/java/com/android/tools/r8/retrace/RetraceFieldElement.java
index 5982550..d51e175 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceFieldElement.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFieldElement.java
@@ -14,5 +14,5 @@
RetraceClassElement getClassElement();
- RetraceSourceFileResult getSourceFile();
+ RetracedSourceFile getSourceFile();
}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceFrameElement.java b/src/main/java/com/android/tools/r8/retrace/RetraceFrameElement.java
index 3295c44..b114240 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceFrameElement.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFrameElement.java
@@ -20,7 +20,7 @@
void visitNonCompilerSynthesizedFrames(BiConsumer<RetracedMethodReference, Integer> consumer);
- RetraceSourceFileResult getSourceFile(RetracedClassMemberReference frame);
+ RetracedSourceFile getSourceFile(RetracedClassMemberReference frame);
List<? extends RetracedMethodReference> getOuterFrames();
}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceMethodElement.java b/src/main/java/com/android/tools/r8/retrace/RetraceMethodElement.java
index 887bff5..132a200 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceMethodElement.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceMethodElement.java
@@ -14,5 +14,5 @@
RetraceClassElement getClassElement();
- RetraceSourceFileResult getSourceFile();
+ RetracedSourceFile getSourceFile();
}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceContext.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceContext.java
new file mode 100644
index 0000000..9004cc1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceContext.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2021, 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.retrace;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.retrace.internal.RetraceStackTraceContextImpl;
+
+@Keep
+public interface RetraceStackTraceContext {
+
+ static RetraceStackTraceContext getInitialContext() {
+ return new RetraceStackTraceContextImpl();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceProxy.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceElementProxy.java
similarity index 82%
rename from src/main/java/com/android/tools/r8/retrace/RetraceStackTraceProxy.java
rename to src/main/java/com/android/tools/r8/retrace/RetraceStackTraceElementProxy.java
index d326609d..c148bc0 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceProxy.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceElementProxy.java
@@ -8,8 +8,8 @@
import java.util.List;
@Keep
-public interface RetraceStackTraceProxy<T, ST extends StackTraceElementProxy<T, ST>>
- extends Comparable<RetraceStackTraceProxy<T, ST>> {
+public interface RetraceStackTraceElementProxy<T, ST extends StackTraceElementProxy<T, ST>>
+ extends Comparable<RetraceStackTraceElementProxy<T, ST>> {
boolean isAmbiguous();
@@ -44,4 +44,6 @@
String getSourceFile();
int getLineNumber();
+
+ RetraceStackTraceContext getContext();
}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java b/src/main/java/com/android/tools/r8/retrace/RetracedSourceFile.java
similarity index 66%
rename from src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java
rename to src/main/java/com/android/tools/r8/retrace/RetracedSourceFile.java
index 9941d9c..c0e0a7d 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedSourceFile.java
@@ -6,11 +6,10 @@
import com.android.tools.r8.Keep;
-// TODO: This does not seem to be a "result" per say, should this rather be a RetracedSourceFile?
@Keep
-public interface RetraceSourceFileResult {
+public interface RetracedSourceFile {
boolean hasRetraceResult();
- String getFilename();
+ String getSourceFile();
}
diff --git a/src/main/java/com/android/tools/r8/retrace/Retracer.java b/src/main/java/com/android/tools/r8/retrace/Retracer.java
index b99f20c..c609e44 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retracer.java
@@ -22,6 +22,9 @@
RetraceFrameResult retraceFrame(MethodReference methodReference, int position);
+ RetraceFrameResult retraceFrame(
+ MethodReference methodReference, int position, RetraceStackTraceContext context);
+
RetraceFieldResult retraceField(FieldReference fieldReference);
RetraceTypeResult retraceType(TypeReference typeReference);
diff --git a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java
index 7cffce0..46c7eff 100644
--- a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java
+++ b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java
@@ -14,7 +14,7 @@
public abstract boolean hasMethodName();
- public abstract boolean hasFileName();
+ public abstract boolean hasSourceFile();
public abstract boolean hasLineNumber();
@@ -28,7 +28,7 @@
public abstract String getMethodName();
- public abstract String getFileName();
+ public abstract String getSourceFile();
public abstract int getLineNumber();
@@ -38,5 +38,6 @@
public abstract String getMethodArguments();
- public abstract T toRetracedItem(RetraceStackTraceProxy<T, ST> retracedProxy, boolean verbose);
+ public abstract T toRetracedItem(
+ RetraceStackTraceElementProxy<T, ST> retracedProxy, boolean verbose);
}
diff --git a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java
index 18be7e7..851bbc2 100644
--- a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java
@@ -11,7 +11,8 @@
@Keep
public interface StackTraceElementProxyRetracer<T, ST extends StackTraceElementProxy<T, ST>> {
- Stream<RetraceStackTraceProxy<T, ST>> retrace(ST element);
+ Stream<? extends RetraceStackTraceElementProxy<T, ST>> retrace(
+ ST element, RetraceStackTraceContext context);
static <T, ST extends StackTraceElementProxy<T, ST>>
StackTraceElementProxyRetracer<T, ST> createDefault(Retracer retracer) {
diff --git a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
index 4bdb42c..0296d71 100644
--- a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
@@ -76,10 +76,42 @@
*/
public List<String> retrace(List<String> stackTrace) {
List<String> retracedStrings = new ArrayList<>();
- retraceStackTrace(stackTrace, result -> joinAmbiguousLines(result, retracedStrings::add));
+ List<List<String>> retracedStackTraces =
+ removeDuplicateStackTraces(retraceStackTrace(stackTrace));
+ if (retracedStackTraces.size() > 1) {
+ retracedStrings.add(
+ "There are "
+ + retracedStackTraces.size()
+ + " ambiguous stack traces."
+ + (isVerbose ? "" : " Use --verbose to have all listed."));
+ }
+ for (int i = 0; i < retracedStackTraces.size(); i++) {
+ if (i > 0) {
+ retracedStrings.add("< OR >");
+ }
+ retracedStrings.addAll(retracedStackTraces.get(i));
+ if (!isVerbose) {
+ break;
+ }
+ }
return retracedStrings;
}
+ private List<List<String>> removeDuplicateStackTraces(List<List<String>> stackTraces) {
+ if (stackTraces.size() == 1) {
+ return stackTraces;
+ }
+ Set<List<String>> seenStackTraces = new HashSet<>();
+ List<List<String>> nonDuplicateStackTraces = new ArrayList<>();
+ stackTraces.forEach(
+ stackTrace -> {
+ if (seenStackTraces.add(stackTrace)) {
+ nonDuplicateStackTraces.add(stackTrace);
+ }
+ });
+ return nonDuplicateStackTraces;
+ }
+
/**
* Retraces a single stack trace line and returns the potential list of original frames
*
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
index 55ed4db..54c90e6 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
@@ -11,19 +11,22 @@
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.naming.mappinginformation.MappingInformation;
import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.retrace.RetraceClassElement;
import com.android.tools.r8.retrace.RetraceClassResult;
import com.android.tools.r8.retrace.RetraceFrameResult;
-import com.android.tools.r8.retrace.RetraceSourceFileResult;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
import com.android.tools.r8.retrace.RetraceUnknownJsonMappingInformationResult;
+import com.android.tools.r8.retrace.RetracedSourceFile;
import com.android.tools.r8.retrace.Retracer;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Pair;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.function.BiFunction;
import java.util.stream.Stream;
@@ -121,25 +124,26 @@
}
@Override
- public RetraceFrameResultImpl lookupFrame(String methodName) {
- return lookupFrame(MethodDefinition.create(obfuscatedReference, methodName), -1);
- }
-
- @Override
- public RetraceFrameResultImpl lookupFrame(String methodName, int position) {
+ public RetraceFrameResultImpl lookupFrame(
+ RetraceStackTraceContext context, Optional<Integer> position, String methodName) {
return lookupFrame(MethodDefinition.create(obfuscatedReference, methodName), position);
}
@Override
public RetraceFrameResultImpl lookupFrame(
- String methodName, int position, List<TypeReference> formalTypes, TypeReference returnType) {
+ RetraceStackTraceContext context,
+ Optional<Integer> position,
+ String methodName,
+ List<TypeReference> formalTypes,
+ TypeReference returnType) {
return lookupFrame(
MethodDefinition.create(
Reference.method(obfuscatedReference, methodName, formalTypes, returnType)),
position);
}
- private RetraceFrameResultImpl lookupFrame(MethodDefinition definition, int position) {
+ private RetraceFrameResultImpl lookupFrame(
+ MethodDefinition definition, Optional<Integer> position) {
List<Pair<RetraceClassElementImpl, List<MappedRange>>> mappings = new ArrayList<>();
internalStream()
.forEach(
@@ -154,7 +158,7 @@
}
private List<List<MappedRange>> getMappedRangesForFrame(
- RetraceClassElementImpl element, MethodDefinition definition, int position) {
+ RetraceClassElementImpl element, MethodDefinition definition, Optional<Integer> position) {
List<List<MappedRange>> overloadedRanges = new ArrayList<>();
if (mapper == null) {
overloadedRanges.add(null);
@@ -167,8 +171,8 @@
return overloadedRanges;
}
List<MappedRange> mappedRangesForPosition = null;
- if (position >= 0) {
- mappedRangesForPosition = mappedRanges.allRangesForLine(position, false);
+ if (position.isPresent() && position.get() >= 0) {
+ mappedRangesForPosition = mappedRanges.allRangesForLine(position.get(), false);
}
if (mappedRangesForPosition == null || mappedRangesForPosition.isEmpty()) {
mappedRangesForPosition = mappedRanges.getMappedRanges();
@@ -240,15 +244,15 @@
}
@Override
- public RetraceSourceFileResult getSourceFile() {
+ public RetracedSourceFile getSourceFile() {
if (classResult.mapper != null) {
for (MappingInformation info : classResult.mapper.getAdditionalMappingInfo()) {
if (info.isFileNameInformation()) {
- return new RetraceSourceFileResultImpl(info.asFileNameInformation().getFileName());
+ return new RetracedSourceFileImpl(info.asFileNameInformation().getFileName());
}
}
}
- return new RetraceSourceFileResultImpl(null);
+ return new RetracedSourceFileImpl(null);
}
@Override
@@ -269,6 +273,12 @@
}
@Override
+ public RetraceStackTraceContext getContext() {
+ // TODO(b/197936862): Extend the context to enable tracking information.
+ return RetraceStackTraceContext.getInitialContext();
+ }
+
+ @Override
public RetraceFieldResultImpl lookupField(String fieldName) {
return lookupField(FieldDefinition.create(classReference.getClassReference(), fieldName));
}
@@ -322,27 +332,28 @@
}
@Override
- public RetraceFrameResultImpl lookupFrame(String methodName) {
- return lookupFrame(methodName, -1);
- }
-
- @Override
- public RetraceFrameResultImpl lookupFrame(String methodName, int position) {
+ public RetraceFrameResultImpl lookupFrame(Optional<Integer> position, String methodName) {
return lookupFrame(
- MethodDefinition.create(classReference.getClassReference(), methodName), position);
+ position, MethodDefinition.create(classReference.getClassReference(), methodName));
}
@Override
public RetraceFrameResult lookupFrame(
+ Optional<Integer> position,
String methodName,
- int position,
List<TypeReference> formalTypes,
TypeReference returnType) {
return lookupFrame(
+ position,
MethodDefinition.create(
Reference.method(
- classReference.getClassReference(), methodName, formalTypes, returnType)),
- position);
+ classReference.getClassReference(), methodName, formalTypes, returnType)));
+ }
+
+ @Override
+ public RetraceFrameResult lookupFrame(
+ Optional<Integer> position, MethodReference methodReference) {
+ return lookupFrame(position, MethodDefinition.create(methodReference));
}
@Override
@@ -351,7 +362,8 @@
mapper.getAdditionalMappingInfo());
}
- private RetraceFrameResultImpl lookupFrame(MethodDefinition definition, int position) {
+ private RetraceFrameResultImpl lookupFrame(
+ Optional<Integer> position, MethodDefinition definition) {
MethodDefinition methodDefinition =
MethodDefinition.create(classReference.getClassReference(), definition.getName());
ImmutableList.Builder<Pair<RetraceClassElementImpl, List<MappedRange>>> builder =
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java
index 6f5e557..b536bea 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java
@@ -10,7 +10,8 @@
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.retrace.RetraceFieldElement;
import com.android.tools.r8.retrace.RetraceFieldResult;
-import com.android.tools.r8.retrace.RetraceSourceFileResult;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.RetracedSourceFile;
import com.android.tools.r8.retrace.Retracer;
import com.android.tools.r8.retrace.internal.RetraceClassResultImpl.RetraceClassElementImpl;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -113,6 +114,11 @@
}
@Override
+ public RetraceStackTraceContext getContext() {
+ return RetraceStackTraceContext.getInitialContext();
+ }
+
+ @Override
public boolean isUnknown() {
return fieldReference.isUnknown();
}
@@ -133,7 +139,7 @@
}
@Override
- public RetraceSourceFileResult getSourceFile() {
+ public RetracedSourceFile getSourceFile() {
return classElement.getSourceFile();
}
}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
index edde5ab..2f79d8e 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
@@ -11,9 +11,10 @@
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.retrace.RetraceFrameElement;
import com.android.tools.r8.retrace.RetraceFrameResult;
-import com.android.tools.r8.retrace.RetraceSourceFileResult;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
import com.android.tools.r8.retrace.RetracedClassMemberReference;
import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.RetracedSourceFile;
import com.android.tools.r8.retrace.Retracer;
import com.android.tools.r8.retrace.internal.RetraceClassResultImpl.RetraceClassElementImpl;
import com.android.tools.r8.utils.ListUtils;
@@ -24,14 +25,15 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.stream.Stream;
-public class RetraceFrameResultImpl implements RetraceFrameResult {
+class RetraceFrameResultImpl implements RetraceFrameResult {
private final RetraceClassResultImpl classResult;
private final MethodDefinition methodDefinition;
- private final int obfuscatedPosition;
+ private final Optional<Integer> obfuscatedPosition;
private final List<Pair<RetraceClassElementImpl, List<MappedRange>>> mappedRanges;
private final Retracer retracer;
@@ -41,7 +43,7 @@
RetraceClassResultImpl classResult,
List<Pair<RetraceClassElementImpl, List<MappedRange>>> mappedRanges,
MethodDefinition methodDefinition,
- int obfuscatedPosition,
+ Optional<Integer> obfuscatedPosition,
Retracer retracer) {
this.classResult = classResult;
this.methodDefinition = methodDefinition;
@@ -127,17 +129,24 @@
}
private RetracedMethodReferenceImpl getRetracedMethod(
- MethodReference methodReference, MappedRange mappedRange, int obfuscatedPosition) {
- if (mappedRange.minifiedRange == null || (obfuscatedPosition == -1 && !isAmbiguous())) {
+ MethodReference methodReference,
+ MappedRange mappedRange,
+ Optional<Integer> obfuscatedPosition) {
+ if (mappedRange.minifiedRange == null
+ || (obfuscatedPosition.orElse(-1) == -1 && !isAmbiguous())) {
int originalLineNumber = mappedRange.getFirstLineNumberOfOriginalRange();
- return RetracedMethodReferenceImpl.create(
- methodReference, originalLineNumber > 0 ? originalLineNumber : obfuscatedPosition);
+ if (originalLineNumber > 0) {
+ return RetracedMethodReferenceImpl.create(methodReference, originalLineNumber);
+ } else {
+ return RetracedMethodReferenceImpl.create(methodReference);
+ }
}
- if (!mappedRange.minifiedRange.contains(obfuscatedPosition)) {
+ if (!obfuscatedPosition.isPresent()
+ || !mappedRange.minifiedRange.contains(obfuscatedPosition.get())) {
return RetracedMethodReferenceImpl.create(methodReference);
}
return RetracedMethodReferenceImpl.create(
- methodReference, mappedRange.getOriginalLineNumber(obfuscatedPosition));
+ methodReference, mappedRange.getOriginalLineNumber(obfuscatedPosition.get()));
}
public static class ElementImpl implements RetraceFrameElement {
@@ -146,14 +155,14 @@
private final RetraceFrameResultImpl retraceFrameResult;
private final RetraceClassElementImpl classElement;
private final List<MappedRange> mappedRanges;
- private final int obfuscatedPosition;
+ private final Optional<Integer> obfuscatedPosition;
- public ElementImpl(
+ private ElementImpl(
RetraceFrameResultImpl retraceFrameResult,
RetraceClassElementImpl classElement,
RetracedMethodReferenceImpl methodReference,
List<MappedRange> mappedRanges,
- int obfuscatedPosition) {
+ Optional<Integer> obfuscatedPosition) {
this.methodReference = methodReference;
this.retraceFrameResult = retraceFrameResult;
this.classElement = classElement;
@@ -179,6 +188,11 @@
}
@Override
+ public RetraceStackTraceContext getContext() {
+ return RetraceStackTraceContext.getInitialContext();
+ }
+
+ @Override
public RetraceFrameResult getRetraceResultContext() {
return retraceFrameResult;
}
@@ -224,7 +238,7 @@
}
@Override
- public RetraceSourceFileResult getSourceFile(RetracedClassMemberReference frame) {
+ public RetracedSourceFile getSourceFile(RetracedClassMemberReference frame) {
return RetraceUtils.getSourceFileOrLookup(
frame.getHolderClass(), classElement, retraceFrameResult.retracer);
}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
index 37b8ffa..8ccdbdb 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
@@ -10,13 +10,16 @@
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.retrace.RetraceMethodElement;
import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.RetracedSourceFile;
import com.android.tools.r8.retrace.Retracer;
import com.android.tools.r8.retrace.internal.RetraceClassResultImpl.RetraceClassElementImpl;
import com.android.tools.r8.utils.Pair;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Stream;
public class RetraceMethodResultImpl implements RetraceMethodResult {
@@ -91,7 +94,7 @@
classResult,
narrowedRanges.isEmpty() ? noMappingRanges : narrowedRanges,
methodDefinition,
- position,
+ Optional.of(position),
retracer);
}
@@ -146,6 +149,11 @@
}
@Override
+ public RetraceStackTraceContext getContext() {
+ return RetraceStackTraceContext.getInitialContext();
+ }
+
+ @Override
public boolean isUnknown() {
return methodReference.isUnknown();
}
@@ -166,7 +174,7 @@
}
@Override
- public com.android.tools.r8.retrace.RetraceSourceFileResult getSourceFile() {
+ public RetracedSourceFile getSourceFile() {
return RetraceUtils.getSourceFileOrLookup(
methodReference.getHolderClass(), classElement, retraceMethodResult.retracer);
}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceContextImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceContextImpl.java
new file mode 100644
index 0000000..b33e06d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceContextImpl.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2021, 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.retrace.internal;
+
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+
+public class RetraceStackTraceContextImpl implements RetraceStackTraceContext {}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
index d1a6b10..920393b 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
@@ -14,10 +14,10 @@
import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.retrace.RetraceClassElement;
import com.android.tools.r8.retrace.RetraceClassResult;
-import com.android.tools.r8.retrace.RetraceSourceFileResult;
import com.android.tools.r8.retrace.RetracedClassReference;
import com.android.tools.r8.retrace.RetracedMethodReference;
import com.android.tools.r8.retrace.RetracedMethodReference.KnownRetracedMethodReference;
+import com.android.tools.r8.retrace.RetracedSourceFile;
import com.android.tools.r8.retrace.Retracer;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -75,18 +75,18 @@
return clazz.substring(lastIndexOfPeriod + 1, endIndex);
}
- public static RetraceSourceFileResult getSourceFileOrLookup(
+ public static RetracedSourceFile getSourceFileOrLookup(
RetracedClassReference holder, RetraceClassElement context, Retracer retracer) {
if (holder.equals(context.getRetracedClass())) {
return context.getSourceFile();
}
RetraceClassResult contextClassResult = retracer.retraceClass(holder.getClassReference());
- Box<RetraceSourceFileResult> retraceSourceFile = new Box<>();
+ Box<RetracedSourceFile> retraceSourceFile = new Box<>();
contextClassResult.forEach(element -> retraceSourceFile.set(element.getSourceFile()));
return retraceSourceFile.get();
}
- public static String inferFileName(
+ public static String inferSourceFile(
String retracedClassName, String sourceFile, boolean hasRetraceResult) {
if (!hasRetraceResult || KEEP_SOURCEFILE_NAMES.contains(sourceFile)) {
return sourceFile;
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceSourceFileResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedSourceFileImpl.java
similarity index 67%
rename from src/main/java/com/android/tools/r8/retrace/internal/RetraceSourceFileResultImpl.java
rename to src/main/java/com/android/tools/r8/retrace/internal/RetracedSourceFileImpl.java
index d3753e3..ef19bb3 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceSourceFileResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedSourceFileImpl.java
@@ -4,13 +4,13 @@
package com.android.tools.r8.retrace.internal;
-import com.android.tools.r8.retrace.RetraceSourceFileResult;
+import com.android.tools.r8.retrace.RetracedSourceFile;
-public class RetraceSourceFileResultImpl implements RetraceSourceFileResult {
+public class RetracedSourceFileImpl implements RetracedSourceFile {
private final String filename;
- RetraceSourceFileResultImpl(String filename) {
+ RetracedSourceFileImpl(String filename) {
this.filename = filename;
}
@@ -20,7 +20,7 @@
}
@Override
- public String getFilename() {
+ public String getSourceFile() {
return filename;
}
}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
index d1c5874..0758be3 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -12,6 +12,8 @@
import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.retrace.InvalidMappingFileException;
import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetraceFrameResult;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
import com.android.tools.r8.retrace.Retracer;
import java.io.BufferedReader;
@@ -53,7 +55,13 @@
}
@Override
- public RetraceFrameResultImpl retraceFrame(MethodReference methodReference, int position) {
+ public RetraceFrameResult retraceFrame(MethodReference methodReference, int position) {
+ return retraceFrame(methodReference, position, RetraceStackTraceContext.getInitialContext());
+ }
+
+ @Override
+ public RetraceFrameResult retraceFrame(
+ MethodReference methodReference, int position, RetraceStackTraceContext context) {
return retraceClass(methodReference.getHolderClass())
.lookupMethod(methodReference.getMethodName())
.narrowByPosition(position);
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
index 06143aa..9fe783b 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
@@ -9,23 +9,24 @@
import com.android.tools.r8.retrace.RetraceClassResult;
import com.android.tools.r8.retrace.RetraceFieldResult;
import com.android.tools.r8.retrace.RetraceFrameResult;
-import com.android.tools.r8.retrace.RetraceSourceFileResult;
-import com.android.tools.r8.retrace.RetraceStackTraceProxy;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.RetraceStackTraceElementProxy;
import com.android.tools.r8.retrace.RetraceTypeResult;
+import com.android.tools.r8.retrace.RetraceTypeResult.Element;
import com.android.tools.r8.retrace.RetracedClassReference;
import com.android.tools.r8.retrace.RetracedFieldReference;
import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.RetracedSourceFile;
import com.android.tools.r8.retrace.RetracedTypeReference;
import com.android.tools.r8.retrace.Retracer;
import com.android.tools.r8.retrace.StackTraceElementProxy;
import com.android.tools.r8.retrace.StackTraceElementProxyRetracer;
-import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.ListUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Optional;
import java.util.function.Consumer;
-import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -39,178 +40,181 @@
}
@Override
- public Stream<RetraceStackTraceProxy<T, ST>> retrace(ST element) {
- if (!element.hasClassName()) {
- RetraceStackTraceProxyImpl.Builder<T, ST> builder =
- RetraceStackTraceProxyImpl.builder(element);
- return Stream.of(builder.build());
+ public Stream<? extends RetraceStackTraceElementProxy<T, ST>> retrace(
+ ST element, RetraceStackTraceContext context) {
+ Stream<RetraceStackTraceElementProxyImpl<T, ST>> currentResults =
+ Stream.of(RetraceStackTraceElementProxyImpl.create(element, context));
+ if (!element.hasClassName()
+ && !element.hasFieldOrReturnType()
+ && !element.hasMethodArguments()) {
+ return currentResults;
}
- RetraceClassResult classResult = retracer.retraceClass(element.getClassReference());
- if (element.hasMethodName()) {
- return retraceMethod(element, classResult);
- } else if (element.hasFieldName()) {
- return retraceField(element, classResult);
- } else {
- return retraceClassOrType(element, classResult);
+ currentResults = retraceFieldOrReturnType(currentResults, element);
+ currentResults = retracedMethodArguments(currentResults, element);
+ if (element.hasClassName()) {
+ RetraceClassResult classResult = retracer.retraceClass(element.getClassReference());
+ if (element.hasMethodName()) {
+ currentResults = retraceMethod(currentResults, element, classResult);
+ } else if (element.hasFieldName()) {
+ currentResults = retraceField(currentResults, element, classResult);
+ } else {
+ currentResults = retraceClassOrType(currentResults, element, classResult);
+ }
}
+ return currentResults;
}
- private Stream<RetraceStackTraceProxy<T, ST>> retraceClassOrType(
- ST element, RetraceClassResult classResult) {
- return classResult.stream()
- .flatMap(
- classElement ->
- retraceFieldOrReturnType(element)
- .flatMap(
- fieldOrReturnTypeConsumer ->
- retracedMethodArguments(element)
- .map(
- argumentsConsumer -> {
- RetraceStackTraceProxyImpl.Builder<T, ST> proxy =
- RetraceStackTraceProxyImpl.builder(element)
- .setRetracedClass(classElement.getRetracedClass())
- .setAmbiguous(classResult.isAmbiguous())
- .setTopFrame(true);
- argumentsConsumer.accept(proxy);
- fieldOrReturnTypeConsumer.accept(proxy);
- if (element.hasFileName()) {
- proxy.setSourceFile(
- getSourceFile(
- classElement::getSourceFile,
- classElement.getRetracedClass(),
- element.getFileName(),
- classResult.hasRetraceResult()));
- }
- return proxy.build();
- })));
- }
-
- private Stream<RetraceStackTraceProxy<T, ST>> retraceMethod(
- ST element, RetraceClassResult classResult) {
- return retraceFieldOrReturnType(element)
- .flatMap(
- fieldOrReturnTypeConsumer ->
- retracedMethodArguments(element)
- .flatMap(
- argumentsConsumer -> {
- RetraceFrameResult frameResult =
- element.hasLineNumber()
- ? classResult.lookupFrame(
- element.getMethodName(), element.getLineNumber())
- : classResult.lookupFrame(element.getMethodName());
- return frameResult.stream()
- .flatMap(
- frameElement -> {
- List<RetraceStackTraceProxy<T, ST>> retracedProxies =
- new ArrayList<>();
- frameElement.visitNonCompilerSynthesizedFrames(
- (frame, index) -> {
- boolean isTopFrame = retracedProxies.isEmpty();
- RetraceStackTraceProxyImpl.Builder<T, ST> proxy =
- RetraceStackTraceProxyImpl.builder(element)
- .setRetracedClass(frame.getHolderClass())
- .setRetracedMethod(frame)
- .setAmbiguous(
- frameResult.isAmbiguous() && isTopFrame)
- .setTopFrame(isTopFrame);
- if (element.hasLineNumber()) {
- proxy.setLineNumber(
- frame.getOriginalPositionOrDefault(
- element.getLineNumber()));
- }
- if (element.hasFileName()) {
- proxy.setSourceFile(
- getSourceFile(
- () -> frameElement.getSourceFile(frame),
- frame.getHolderClass(),
- element.getFileName(),
- classResult.hasRetraceResult()));
- }
- fieldOrReturnTypeConsumer.accept(proxy);
- argumentsConsumer.accept(proxy);
- retracedProxies.add(proxy.build());
- });
- return retracedProxies.stream();
- });
- }));
- }
-
- private Stream<RetraceStackTraceProxy<T, ST>> retraceField(
- ST element, RetraceClassResult classResult) {
- return retraceFieldOrReturnType(element)
- .flatMap(
- fieldOrReturnTypeConsumer ->
- retracedMethodArguments(element)
- .flatMap(
- argumentsConsumer -> {
- RetraceFieldResult retraceFieldResult =
- classResult.lookupField(element.getFieldName());
- return retraceFieldResult.stream()
- .map(
- fieldElement -> {
- RetraceStackTraceProxyImpl.Builder<T, ST> proxy =
- RetraceStackTraceProxyImpl.builder(element)
- .setRetracedClass(
- fieldElement.getField().getHolderClass())
- .setRetracedField(fieldElement.getField())
- .setAmbiguous(retraceFieldResult.isAmbiguous())
- .setTopFrame(true);
- if (element.hasFileName()) {
- proxy.setSourceFile(
- getSourceFile(
- // May not be fieldElement::getSourceFile since this
- // throws off the jdk11 compiler,
- () -> fieldElement.getSourceFile(),
- fieldElement.getField().getHolderClass(),
- element.getFileName(),
+ private Stream<RetraceStackTraceElementProxyImpl<T, ST>> retraceClassOrType(
+ Stream<RetraceStackTraceElementProxyImpl<T, ST>> currentResults,
+ ST element,
+ RetraceClassResult classResult) {
+ return currentResults.flatMap(
+ proxy ->
+ classResult.stream()
+ .map(
+ classElement ->
+ proxy
+ .builder()
+ .setRetracedClass(classElement.getRetracedClass())
+ .joinAmbiguous(classResult.isAmbiguous())
+ .setTopFrame(true)
+ .setContext(classElement.getContext())
+ .applyIf(
+ element.hasSourceFile(),
+ builder -> {
+ RetracedSourceFile sourceFile = classElement.getSourceFile();
+ builder.setSourceFile(
+ sourceFile.hasRetraceResult()
+ ? sourceFile.getSourceFile()
+ : RetraceUtils.inferSourceFile(
+ classElement.getRetracedClass().getTypeName(),
+ element.getSourceFile(),
classResult.hasRetraceResult()));
- }
- fieldOrReturnTypeConsumer.accept(proxy);
- argumentsConsumer.accept(proxy);
- return proxy.build();
- });
- }));
+ })
+ .build()));
}
- private String getSourceFile(
- Supplier<RetraceSourceFileResult> sourceFile,
- RetracedClassReference classReference,
- String fileName,
- boolean hasRetraceResult) {
- RetraceSourceFileResult sourceFileResult = sourceFile.get();
- return sourceFileResult.hasRetraceResult()
- ? sourceFileResult.getFilename()
- : RetraceUtils.inferFileName(classReference.getTypeName(), fileName, hasRetraceResult);
+ private Stream<RetraceStackTraceElementProxyImpl<T, ST>> retraceMethod(
+ Stream<RetraceStackTraceElementProxyImpl<T, ST>> currentResults,
+ ST element,
+ RetraceClassResult classResult) {
+ return currentResults.flatMap(
+ proxy -> {
+ RetraceFrameResult frameResult =
+ classResult.lookupFrame(
+ proxy.context,
+ element.hasLineNumber() ? Optional.of(element.getLineNumber()) : Optional.empty(),
+ element.getMethodName());
+ return frameResult.stream()
+ .flatMap(
+ frameElement -> {
+ List<RetraceStackTraceElementProxyImpl<T, ST>> retracedProxies =
+ new ArrayList<>();
+ frameElement.visitNonCompilerSynthesizedFrames(
+ (frame, index) -> {
+ boolean isTopFrame = index == 0;
+ retracedProxies.add(
+ proxy
+ .builder()
+ .setRetracedClass(frame.getHolderClass())
+ .setRetracedMethod(frame)
+ .joinAmbiguous(frameResult.isAmbiguous() && isTopFrame)
+ .setTopFrame(isTopFrame)
+ .setContext(frameElement.getContext())
+ .applyIf(
+ element.hasLineNumber(),
+ builder -> {
+ builder.setLineNumber(
+ frame.getOriginalPositionOrDefault(
+ element.getLineNumber()));
+ })
+ .applyIf(
+ element.hasSourceFile(),
+ builder -> {
+ RetracedSourceFile sourceFileResult =
+ frameElement.getSourceFile(frame);
+ builder.setSourceFile(
+ sourceFileResult.hasRetraceResult()
+ ? sourceFileResult.getSourceFile()
+ : RetraceUtils.inferSourceFile(
+ frame.getHolderClass().getTypeName(),
+ element.getSourceFile(),
+ classResult.hasRetraceResult()));
+ })
+ .build());
+ });
+ return retracedProxies.stream();
+ });
+ });
}
- private Stream<Consumer<RetraceStackTraceProxyImpl.Builder<T, ST>>> retraceFieldOrReturnType(
- ST element) {
+ private Stream<RetraceStackTraceElementProxyImpl<T, ST>> retraceField(
+ Stream<RetraceStackTraceElementProxyImpl<T, ST>> currentResults,
+ ST element,
+ RetraceClassResult classResult) {
+ return currentResults.flatMap(
+ proxy -> {
+ RetraceFieldResult retraceFieldResult = classResult.lookupField(element.getFieldName());
+ return retraceFieldResult.stream()
+ .map(
+ fieldElement ->
+ proxy
+ .builder()
+ .setRetracedClass(fieldElement.getField().getHolderClass())
+ .setRetracedField(fieldElement.getField())
+ .joinAmbiguous(retraceFieldResult.isAmbiguous())
+ .setTopFrame(true)
+ .setContext(fieldElement.getContext())
+ .applyIf(
+ element.hasSourceFile(),
+ builder -> {
+ RetracedSourceFile sourceFile = fieldElement.getSourceFile();
+ builder.setSourceFile(
+ sourceFile.hasRetraceResult()
+ ? sourceFile.getSourceFile()
+ : RetraceUtils.inferSourceFile(
+ fieldElement.getField().getHolderClass().getTypeName(),
+ element.getSourceFile(),
+ classResult.hasRetraceResult()));
+ })
+ .build());
+ });
+ }
+
+ private Stream<RetraceStackTraceElementProxyImpl<T, ST>> retraceFieldOrReturnType(
+ Stream<RetraceStackTraceElementProxyImpl<T, ST>> currentResults, ST element) {
if (!element.hasFieldOrReturnType()) {
- return Stream.of(noEffect -> {});
+ return currentResults;
}
String elementOrReturnType = element.getFieldOrReturnType();
if (elementOrReturnType.equals("void")) {
- return Stream.of(
- proxy -> proxy.setRetracedFieldOrReturnType(RetracedTypeReferenceImpl.createVoid()));
+ return currentResults.map(
+ proxy ->
+ proxy
+ .builder()
+ .setRetracedFieldOrReturnType(RetracedTypeReferenceImpl.createVoid())
+ .build());
} else {
TypeReference typeReference = Reference.typeFromTypeName(elementOrReturnType);
RetraceTypeResult retraceTypeResult = retracer.retraceType(typeReference);
- return retraceTypeResult.stream()
- .map(
- type ->
- (proxy -> {
- proxy.setRetracedFieldOrReturnType(type.getType());
- if (retraceTypeResult.isAmbiguous()) {
- proxy.setAmbiguous(true);
- }
- }));
+ List<Element> retracedElements = retraceTypeResult.stream().collect(Collectors.toList());
+ return currentResults.flatMap(
+ proxy ->
+ retracedElements.stream()
+ .map(
+ retracedResult ->
+ proxy
+ .builder()
+ .setRetracedFieldOrReturnType(retracedResult.getType())
+ .joinAmbiguous(retraceTypeResult.isAmbiguous())
+ .build()));
}
}
- private Stream<Consumer<RetraceStackTraceProxyImpl.Builder<T, ST>>> retracedMethodArguments(
- ST element) {
+ private Stream<RetraceStackTraceElementProxyImpl<T, ST>> retracedMethodArguments(
+ Stream<RetraceStackTraceElementProxyImpl<T, ST>> currentResults, ST element) {
if (!element.hasMethodArguments()) {
- return Stream.of(noEffect -> {});
+ return currentResults;
}
List<RetraceTypeResult> retracedResults =
Arrays.stream(element.getMethodArguments().split(","))
@@ -218,40 +222,37 @@
.collect(Collectors.toList());
List<List<RetracedTypeReference>> initial = new ArrayList<>();
initial.add(new ArrayList<>());
- Box<Boolean> isAmbiguous = new Box<>(false);
- List<List<RetracedTypeReference>> retracedArguments =
+ List<List<RetracedTypeReference>> allRetracedArguments =
ListUtils.fold(
retracedResults,
initial,
(acc, retracedTypeResult) -> {
- if (retracedTypeResult.isAmbiguous()) {
- isAmbiguous.set(true);
- }
List<List<RetracedTypeReference>> newResult = new ArrayList<>();
retracedTypeResult.forEach(
- retracedElement -> {
- acc.forEach(
- oldResult -> {
- List<RetracedTypeReference> newList = new ArrayList<>(oldResult);
- newList.add(retracedElement.getType());
- newResult.add(newList);
- });
- });
+ retracedElement ->
+ acc.forEach(
+ oldResult -> {
+ List<RetracedTypeReference> newList = new ArrayList<>(oldResult);
+ newList.add(retracedElement.getType());
+ newResult.add(newList);
+ }));
return newResult;
});
- return retracedArguments.stream()
- .map(
- arguments ->
- proxy -> {
- proxy.setRetracedMethodArguments(arguments);
- if (isAmbiguous.get()) {
- proxy.setAmbiguous(true);
- }
- });
+ boolean isAmbiguous = allRetracedArguments.size() > 1;
+ return currentResults.flatMap(
+ proxy ->
+ allRetracedArguments.stream()
+ .map(
+ retracedArguments ->
+ proxy
+ .builder()
+ .setRetracedMethodArguments(retracedArguments)
+ .joinAmbiguous(isAmbiguous)
+ .build()));
}
- public static class RetraceStackTraceProxyImpl<T, ST extends StackTraceElementProxy<T, ST>>
- implements RetraceStackTraceProxy<T, ST> {
+ public static class RetraceStackTraceElementProxyImpl<T, ST extends StackTraceElementProxy<T, ST>>
+ implements RetraceStackTraceElementProxy<T, ST> {
private final ST originalItem;
private final RetracedClassReference retracedClass;
@@ -263,8 +264,9 @@
private final int lineNumber;
private final boolean isAmbiguous;
private final boolean isTopFrame;
+ private final RetraceStackTraceContext context;
- private RetraceStackTraceProxyImpl(
+ private RetraceStackTraceElementProxyImpl(
ST originalItem,
RetracedClassReference retracedClass,
RetracedMethodReference retracedMethod,
@@ -274,7 +276,8 @@
String sourceFile,
int lineNumber,
boolean isAmbiguous,
- boolean isTopFrame) {
+ boolean isTopFrame,
+ RetraceStackTraceContext context) {
assert originalItem != null;
this.originalItem = originalItem;
this.retracedClass = retracedClass;
@@ -286,6 +289,7 @@
this.lineNumber = lineNumber;
this.isAmbiguous = isAmbiguous;
this.isTopFrame = isTopFrame;
+ this.context = context;
}
@Override
@@ -368,9 +372,26 @@
return sourceFile;
}
- private static <T, ST extends StackTraceElementProxy<T, ST>> Builder<T, ST> builder(
- ST originalElement) {
- return new Builder<>(originalElement);
+ private static <T, ST extends StackTraceElementProxy<T, ST>>
+ RetraceStackTraceElementProxyImpl<T, ST> create(
+ ST originalItem, RetraceStackTraceContext context) {
+ return new RetraceStackTraceElementProxyImpl<T, ST>(
+ originalItem, null, null, null, null, null, null, -1, false, false, context);
+ }
+
+ private Builder<T, ST> builder() {
+ Builder<T, ST> builder = new Builder<>(originalItem);
+ builder.classContext = retracedClass;
+ builder.methodContext = retracedMethod;
+ builder.retracedField = retracedField;
+ builder.fieldOrReturnType = fieldOrReturnType;
+ builder.methodArguments = methodArguments;
+ builder.sourceFile = sourceFile;
+ builder.lineNumber = lineNumber;
+ builder.isAmbiguous = isAmbiguous;
+ builder.isTopFrame = isTopFrame;
+ builder.context = context;
+ return builder;
}
@Override
@@ -379,7 +400,12 @@
}
@Override
- public int compareTo(RetraceStackTraceProxy<T, ST> other) {
+ public RetraceStackTraceContext getContext() {
+ return context;
+ }
+
+ @Override
+ public int compareTo(RetraceStackTraceElementProxy<T, ST> other) {
int classCompare = Boolean.compare(hasRetracedClass(), other.hasRetracedClass());
if (classCompare != 0) {
return classCompare;
@@ -433,6 +459,7 @@
private int lineNumber = -1;
private boolean isAmbiguous;
private boolean isTopFrame;
+ private RetraceStackTraceContext context;
private Builder(ST originalElement) {
this.originalElement = originalElement;
@@ -473,8 +500,8 @@
return this;
}
- private Builder<T, ST> setAmbiguous(boolean ambiguous) {
- this.isAmbiguous = ambiguous;
+ private Builder<T, ST> joinAmbiguous(boolean ambiguous) {
+ this.isAmbiguous = ambiguous || this.isAmbiguous;
return this;
}
@@ -483,12 +510,24 @@
return this;
}
- private RetraceStackTraceProxy<T, ST> build() {
+ private Builder<T, ST> setContext(RetraceStackTraceContext context) {
+ this.context = context;
+ return this;
+ }
+
+ private Builder<T, ST> applyIf(boolean condition, Consumer<Builder<T, ST>> consumer) {
+ if (condition) {
+ consumer.accept(this);
+ }
+ return this;
+ }
+
+ private RetraceStackTraceElementProxyImpl<T, ST> build() {
RetracedClassReference retracedClass = classContext;
if (methodContext != null) {
retracedClass = methodContext.getHolderClass();
}
- return new RetraceStackTraceProxyImpl<>(
+ return new RetraceStackTraceElementProxyImpl<>(
originalElement,
retracedClass,
methodContext,
@@ -498,7 +537,8 @@
sourceFile,
lineNumber,
isAmbiguous,
- isTopFrame);
+ isTopFrame,
+ context);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
index 5f512f4..7afc10b 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.retrace.RetraceStackTraceProxy;
+import com.android.tools.r8.retrace.RetraceStackTraceElementProxy;
import com.android.tools.r8.retrace.RetracedClassReference;
import com.android.tools.r8.retrace.RetracedFieldReference;
import com.android.tools.r8.retrace.RetracedTypeReference;
@@ -69,7 +69,7 @@
}
@Override
- public boolean hasFileName() {
+ public boolean hasSourceFile() {
return sourceFile.hasIndex();
}
@@ -104,8 +104,8 @@
}
@Override
- public String getFileName() {
- return hasFileName() ? getEntryInLine(sourceFile) : null;
+ public String getSourceFile() {
+ return hasSourceFile() ? getEntryInLine(sourceFile) : null;
}
@Override
@@ -141,7 +141,8 @@
@Override
public String toRetracedItem(
- RetraceStackTraceProxy<String, StackTraceElementStringProxy> retracedProxy, boolean verbose) {
+ RetraceStackTraceElementProxy<String, StackTraceElementStringProxy> retracedProxy,
+ boolean verbose) {
StringBuilder sb = new StringBuilder();
int lastSeenIndex = 0;
for (StringIndex index : orderedIndices) {
@@ -225,7 +226,7 @@
startIndex,
endIndex,
(retraced, original, verbose) ->
- retraced.hasSourceFile() ? retraced.getSourceFile() : original.getFileName());
+ retraced.hasSourceFile() ? retraced.getSourceFile() : original.getSourceFile());
orderedIndices.add(sourceFile);
return this;
}
@@ -333,14 +334,17 @@
protected final int startIndex;
protected final int endIndex;
private final TriFunction<
- RetraceStackTraceProxy<String, ?>, StackTraceElementStringProxy, Boolean, String>
+ RetraceStackTraceElementProxy<String, ?>, StackTraceElementStringProxy, Boolean, String>
retracedString;
private StringIndex(
int startIndex,
int endIndex,
TriFunction<
- RetraceStackTraceProxy<String, ?>, StackTraceElementStringProxy, Boolean, String>
+ RetraceStackTraceElementProxy<String, ?>,
+ StackTraceElementStringProxy,
+ Boolean,
+ String>
retracedString) {
this.startIndex = startIndex;
this.endIndex = endIndex;
@@ -363,7 +367,10 @@
int startIndex,
int endIndex,
TriFunction<
- RetraceStackTraceProxy<String, ?>, StackTraceElementStringProxy, Boolean, String>
+ RetraceStackTraceElementProxy<String, ?>,
+ StackTraceElementStringProxy,
+ Boolean,
+ String>
retracedString,
ClassNameType classNameType) {
super(startIndex, endIndex, retracedString);
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 242a08c..5ef1b35 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -659,6 +659,10 @@
return keepConstantArguments.contains(method);
}
+ public boolean isKeepUnusedArgumentsMethod(ProgramMethod method) {
+ return isKeepUnusedArgumentsMethod(method.getReference());
+ }
+
public boolean isKeepUnusedArgumentsMethod(DexMethod method) {
return keepUnusedArguments.contains(method);
}
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 c4e0518..52d59bb 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -10,6 +10,7 @@
import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier;
import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import static java.util.Collections.emptySet;
import com.android.tools.r8.Diagnostic;
@@ -63,6 +64,7 @@
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.InvalidCode;
import com.android.tools.r8.graph.LookupLambdaTarget;
+import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.LookupTarget;
import com.android.tools.r8.graph.MethodAccessInfoCollection;
import com.android.tools.r8.graph.MethodResolutionResult;
@@ -378,7 +380,7 @@
private final ProgramMethodSet pendingReflectiveUses = ProgramMethodSet.createLinked();
/** Mapping of types to the resolved methods for that type along with the context. */
- private final Map<DexProgramClass, Map<ResolutionSearchKey, Set<DexProgramClass>>>
+ private final Map<DexProgramClass, Map<ResolutionSearchKey, ProgramMethodSet>>
reachableVirtualTargets = new IdentityHashMap<>();
/** Collection of keep requirements for the program. */
@@ -2460,7 +2462,7 @@
}
}
- private Map<ResolutionSearchKey, Set<DexProgramClass>> getReachableVirtualTargets(
+ private Map<ResolutionSearchKey, ProgramMethodSet> getReachableVirtualTargets(
DexProgramClass clazz) {
return reachableVirtualTargets.getOrDefault(clazz, Collections.emptyMap());
}
@@ -2480,26 +2482,46 @@
assert false : "Should not be null";
return;
}
- contexts.forEach(
- context ->
- singleResolution
- .lookupVirtualDispatchTargets(
- context,
- appInfo,
- (type, subTypeConsumer, lambdaConsumer) -> {
- assert appInfo.isSubtype(currentClass.type, type);
- instantiation.apply(subTypeConsumer, lambdaConsumer);
- },
- definition ->
- keepInfo.isPinned(definition.getReference(), appInfo, options))
- .forEach(
- target ->
- markVirtualDispatchTargetAsLive(
- target,
- programMethod ->
- graphReporter.reportReachableMethodAsLive(
- singleResolution.getResolvedMethod().getReference(),
- programMethod))));
+ Map<DexProgramClass, List<ProgramMethod>> contextsByClass = new IdentityHashMap<>();
+ for (ProgramMethod context : contexts) {
+ contextsByClass
+ .computeIfAbsent(context.getHolder(), ignoreKey(ArrayList::new))
+ .add(context);
+ }
+ contextsByClass.forEach(
+ (contextHolder, contextsInHolder) -> {
+ LookupResult lookupResult =
+ singleResolution.lookupVirtualDispatchTargets(
+ contextHolder,
+ appInfo,
+ (type, subTypeConsumer, lambdaConsumer) -> {
+ assert appInfo.isSubtype(currentClass.type, type);
+ instantiation.apply(subTypeConsumer, lambdaConsumer);
+ },
+ definition ->
+ keepInfo.isPinned(definition.getReference(), appInfo, options));
+ lookupResult.forEach(
+ target ->
+ markVirtualDispatchTargetAsLive(
+ target,
+ programMethod ->
+ graphReporter.reportReachableMethodAsLive(
+ singleResolution.getResolvedMethod().getReference(),
+ programMethod)));
+ lookupResult.forEachFailureDependency(
+ method -> {
+ DexProgramClass clazz =
+ getProgramClassOrNull(method.getHolderType(), contextHolder);
+ if (clazz != null) {
+ failedMethodResolutionTargets.add(method.getReference());
+ for (ProgramMethod context : contextsInHolder) {
+ markMethodAsTargeted(
+ new ProgramMethod(clazz, method),
+ KeepReason.invokedFrom(context));
+ }
+ }
+ });
+ });
});
}
@@ -2886,7 +2908,7 @@
}
private void markVirtualMethodAsReachable(
- DexMethod method, boolean interfaceInvoke, ProgramDefinition context, KeepReason reason) {
+ DexMethod method, boolean interfaceInvoke, ProgramMethod context, KeepReason reason) {
if (method.holder.isArrayType()) {
// This is an array type, so the actual class will be generated at runtime. We treat this
// like an invoke on a direct subtype of java.lang.Object that has no further subtypes.
@@ -2924,9 +2946,9 @@
// If the method has already been marked, just report the new reason for the resolved target and
// save the context to ensure correct lookup of virtual dispatch targets.
ResolutionSearchKey resolutionSearchKey = new ResolutionSearchKey(method, interfaceInvoke);
- Set<DexProgramClass> seenContexts = getReachableVirtualTargets(holder).get(resolutionSearchKey);
+ ProgramMethodSet seenContexts = getReachableVirtualTargets(holder).get(resolutionSearchKey);
if (seenContexts != null) {
- seenContexts.add(contextHolder);
+ seenContexts.add(context);
graphReporter.registerMethod(resolution.getResolvedMethod(), reason);
return;
}
@@ -2948,8 +2970,8 @@
// The method resolved and is accessible, so currently live overrides become live.
reachableVirtualTargets
.computeIfAbsent(holder, ignoreArgument(HashMap::new))
- .computeIfAbsent(resolutionSearchKey, ignoreArgument(Sets::newIdentityHashSet))
- .add(contextHolder);
+ .computeIfAbsent(resolutionSearchKey, ignoreArgument(ProgramMethodSet::create))
+ .add(context);
resolution
.lookupVirtualDispatchTargets(
@@ -4603,7 +4625,7 @@
virtualMethod -> {
keepInfo.joinMethod(
virtualMethod, joiner -> joiner.disallowOptimization().disallowShrinking());
- markVirtualMethodAsReachable(virtualMethod.getReference(), true, clazz, reason);
+ markVirtualMethodAsReachable(virtualMethod.getReference(), true, method, reason);
});
}
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
index d81a03f..56a9d20 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
@@ -17,6 +17,8 @@
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -39,6 +41,7 @@
private ParameterAnnotationsList parameterAnnotationsList = ParameterAnnotationsList.empty();
private AndroidApiLevel apiLevelForDefinition = NOT_SET;
private AndroidApiLevel apiLevelForCode = NOT_SET;
+ private MethodOptimizationInfo optimizationInfo = DefaultMethodOptimizationInfo.getInstance();
private boolean checkAndroidApiLevels = true;
@@ -65,6 +68,11 @@
return this;
}
+ public SyntheticMethodBuilder setOptimizationInfo(MethodOptimizationInfo optimizationInfo) {
+ this.optimizationInfo = optimizationInfo;
+ return this;
+ }
+
public SyntheticMethodBuilder setProto(DexProto proto) {
this.proto = proto;
return this;
@@ -132,6 +140,7 @@
.setClassFileVersion(classFileVersion)
.setApiLevelForDefinition(apiLevelForDefinition)
.setApiLevelForCode(apiLevelForCode)
+ .setOptimizationInfo(optimizationInfo)
.applyIf(!checkAndroidApiLevels, DexEncodedMethod.Builder::disableAndroidApiLevelCheck)
.build();
assert isValidSyntheticMethod(method, syntheticKind);
diff --git a/src/main/java/com/android/tools/r8/utils/BitSetUtils.java b/src/main/java/com/android/tools/r8/utils/BitSetUtils.java
new file mode 100644
index 0000000..483eb67
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/BitSetUtils.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2021, 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.utils;
+
+import java.util.BitSet;
+
+public class BitSetUtils {
+
+ @SuppressWarnings("unchecked")
+ public static BitSet or(BitSet bitSet, BitSet other) {
+ BitSet newBitSet = (BitSet) bitSet.clone();
+ newBitSet.or(other);
+ return newBitSet;
+ }
+
+ public static boolean verifyLessThanOrEqualTo(BitSet bitSet, BitSet other) {
+ assert other.equals(or(bitSet, other));
+ return true;
+ }
+}
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 860860b..3718fcf 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -124,7 +124,7 @@
}
}
- public static final CfVersion SUPPORTED_CF_VERSION = CfVersion.V16_PREVIEW;
+ public static final CfVersion SUPPORTED_CF_VERSION = CfVersion.V17;
public static final CfVersion EXPERIMENTAL_CF_VERSION = CfVersion.V12;
public static final int SUPPORTED_DEX_VERSION =
@@ -1219,7 +1219,7 @@
// TODO(b/69963623): enable if everything is ready, including signature rewriting at call sites.
private boolean enableLegacyConstantPropagation = false;
- private boolean enableExperimentalArgumentPropagation = false;
+ private boolean enableExperimentalArgumentPropagation = true;
private boolean enableDynamicTypePropagation = true;
public void disableOptimization() {
@@ -1235,6 +1235,10 @@
return maxNumberOfDispatchTargetsBeforeAbandoning;
}
+ public int getMaxNumberOfInParameters() {
+ return 10;
+ }
+
public boolean isEnabled() {
if (!isOptimizing() || !isShrinking()) {
return false;
@@ -1255,7 +1259,7 @@
}
public CallSiteOptimizationOptions setEnableLegacyConstantPropagation() {
- assert !isConstantPropagationEnabled();
+ assert !enableLegacyConstantPropagation;
enableLegacyConstantPropagation = true;
return this;
}
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 8fd84fc..1ff7079 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -186,6 +186,14 @@
return list;
}
+ public static <T> ArrayList<T> newInitializedArrayList(int size, T element) {
+ ArrayList<T> list = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ list.add(element);
+ }
+ return list;
+ }
+
public static <T> ImmutableList<T> newImmutableList(ForEachable<T> forEachable) {
ImmutableList.Builder<T> builder = ImmutableList.builder();
forEachable.forEach(builder::add);
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
index 04b399d..a7305a2 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -89,6 +89,17 @@
return builder.build();
}
+ @SafeVarargs
+ public static <T> ImmutableSet<T> newImmutableSetExcludingNullItems(T... items) {
+ ImmutableSet.Builder<T> builder = ImmutableSet.builder();
+ for (T item : items) {
+ if (item != null) {
+ builder.add(item);
+ }
+ }
+ return builder.build();
+ }
+
public static <T, S> Set<T> mapIdentityHashSet(Set<S> set, Function<S, T> fn) {
Set<T> out = newIdentityHashSet(set.size());
for (S element : set) {
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index 67e4148..3177689 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -17,11 +17,17 @@
private final Set<T> seen;
public static <T> WorkList<T> newEqualityWorkList() {
- return new WorkList<T>(EqualityTest.HASH);
+ return new WorkList<T>(EqualityTest.EQUALS);
+ }
+
+ public static <T> WorkList<T> newEqualityWorkList(T item) {
+ WorkList<T> workList = new WorkList<>(EqualityTest.EQUALS);
+ workList.addIfNotSeen(item);
+ return workList;
}
public static <T> WorkList<T> newEqualityWorkList(Iterable<T> items) {
- WorkList<T> workList = new WorkList<>(EqualityTest.HASH);
+ WorkList<T> workList = new WorkList<>(EqualityTest.EQUALS);
workList.addIfNotSeen(items);
return workList;
}
@@ -53,7 +59,7 @@
}
private WorkList(EqualityTest equalityTest) {
- this(equalityTest == EqualityTest.HASH ? new HashSet<>() : Sets.newIdentityHashSet());
+ this(equalityTest == EqualityTest.EQUALS ? new HashSet<>() : Sets.newIdentityHashSet());
}
private WorkList(Set<T> seen) {
@@ -120,7 +126,7 @@
}
public enum EqualityTest {
- HASH,
+ EQUALS,
IDENTITY
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/LongLivedClassSetBuilder.java b/src/main/java/com/android/tools/r8/utils/collections/LongLivedClassSetBuilder.java
new file mode 100644
index 0000000..fe3361e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/LongLivedClassSetBuilder.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2021, 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.utils.collections;
+
+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.GraphLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.SetUtils;
+import java.util.Set;
+import java.util.function.IntFunction;
+
+public class LongLivedClassSetBuilder<T extends DexClass>
+ extends LongLivedCollectionBuilder<Set<DexType>, Set<T>> {
+
+ private LongLivedClassSetBuilder(
+ GraphLens currentGraphLens,
+ IntFunction<Set<T>> factory,
+ IntFunction<Set<DexType>> factoryForBuilder) {
+ super(currentGraphLens, factory, factoryForBuilder);
+ }
+
+ public static <T extends DexClass>
+ LongLivedClassSetBuilder<T> createConcurrentBuilderForIdentitySet(
+ GraphLens currentGraphLens) {
+ return new LongLivedClassSetBuilder<>(
+ currentGraphLens, SetUtils::newIdentityHashSet, SetUtils::newConcurrentHashSet);
+ }
+
+ public void add(T clazz, GraphLens currentGraphLens) {
+ // All classes in a long lived class set should be rewritten up until the same graph lens.
+ assert verifyIsRewrittenWithLens(currentGraphLens);
+ backing.add(clazz.getType());
+ }
+
+ public LongLivedClassSetBuilder<T> rewrittenWithLens(AppView<AppInfoWithLiveness> appView) {
+ return rewrittenWithLens(appView.graphLens());
+ }
+
+ public LongLivedClassSetBuilder<T> rewrittenWithLens(GraphLens newGraphLens) {
+ // Check if the graph lens has changed (otherwise lens rewriting is not needed).
+ if (newGraphLens == appliedGraphLens) {
+ return this;
+ }
+
+ // Rewrite the backing.
+ Set<DexType> rewrittenBacking = factoryForBuilder.apply(backing.size());
+ for (DexType type : backing) {
+ rewrittenBacking.add(newGraphLens.lookupType(type, appliedGraphLens));
+ }
+ backing = rewrittenBacking;
+
+ // Record that this collection is now rewritten up until the given graph lens.
+ appliedGraphLens = newGraphLens;
+ return this;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Set<T> build(AppView<AppInfoWithLiveness> appView) {
+ Set<T> result = factory.apply(backing.size());
+ for (DexType type : backing) {
+ DexType rewrittenType = appView.graphLens().lookupType(type, appliedGraphLens);
+ T clazz = (T) appView.definitionFor(rewrittenType);
+ if (clazz != null) {
+ result.add(clazz);
+ } else {
+ assert false : "Unable to find definition for: " + rewrittenType.getTypeName();
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/LongLivedCollectionBuilder.java b/src/main/java/com/android/tools/r8/utils/collections/LongLivedCollectionBuilder.java
new file mode 100644
index 0000000..6c7fdc2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/LongLivedCollectionBuilder.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2021, 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.utils.collections;
+
+import com.android.tools.r8.graph.GraphLens;
+import java.util.function.IntFunction;
+
+public abstract class LongLivedCollectionBuilder<BuilderCollection, ResultCollection> {
+
+ // Factory for creating the final result collection.
+ protected final IntFunction<ResultCollection> factory;
+
+ // Factory for creating a builder collection.
+ protected final IntFunction<BuilderCollection> factoryForBuilder;
+
+ // The graph lens that this collection has been rewritten up until.
+ protected GraphLens appliedGraphLens;
+
+ // The underlying backing.
+ protected BuilderCollection backing;
+
+ protected LongLivedCollectionBuilder(
+ GraphLens currentGraphLens,
+ IntFunction<ResultCollection> factory,
+ IntFunction<BuilderCollection> factoryForBuilder) {
+ this.appliedGraphLens = currentGraphLens;
+ this.factory = factory;
+ this.factoryForBuilder = factoryForBuilder;
+ this.backing = factoryForBuilder.apply(2);
+ }
+
+ public boolean isRewrittenWithLens(GraphLens graphLens) {
+ return appliedGraphLens == graphLens;
+ }
+
+ public boolean verifyIsRewrittenWithLens(GraphLens graphLens) {
+ assert isRewrittenWithLens(graphLens);
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodMapBuilder.java b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodMapBuilder.java
new file mode 100644
index 0000000..1591350
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodMapBuilder.java
@@ -0,0 +1,107 @@
+// Copyright (c) 2021, 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.utils.collections;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.IntFunction;
+
+public class LongLivedProgramMethodMapBuilder<V>
+ extends LongLivedCollectionBuilder<Map<DexMethod, V>, ProgramMethodMap<?>> {
+
+ private LongLivedProgramMethodMapBuilder(
+ GraphLens currentGraphLens,
+ IntFunction<ProgramMethodMap<?>> factory,
+ IntFunction<Map<DexMethod, V>> factoryForBuilder) {
+ super(currentGraphLens, factory, factoryForBuilder);
+ }
+
+ public static <V> LongLivedProgramMethodMapBuilder<V> create(GraphLens currentGraphLens) {
+ return new LongLivedProgramMethodMapBuilder<>(
+ currentGraphLens, ProgramMethodMap::create, IdentityHashMap::new);
+ }
+
+ public static <V> LongLivedProgramMethodMapBuilder<V> createConcurrentBuilderForNonConcurrentMap(
+ GraphLens currentGraphLens) {
+ return new LongLivedProgramMethodMapBuilder<>(
+ currentGraphLens, ProgramMethodMap::create, ConcurrentHashMap::new);
+ }
+
+ public V computeIfAbsent(
+ ProgramMethod key, Function<ProgramMethod, V> fn, GraphLens currentGraphLens) {
+ assert verifyIsRewrittenWithLens(currentGraphLens);
+ return backing.computeIfAbsent(key.getReference(), ignoreKey(() -> fn.apply(key)));
+ }
+
+ public boolean isEmpty() {
+ return backing.isEmpty();
+ }
+
+ public void put(ProgramMethod key, V value, GraphLens currentGraphLens) {
+ // All methods in a long lived program method set should be rewritten up until the same graph
+ // lens.
+ assert verifyIsRewrittenWithLens(currentGraphLens);
+ backing.put(key.getReference(), value);
+ }
+
+ public LongLivedProgramMethodMapBuilder<V> rewrittenWithLens(
+ AppView<AppInfoWithLiveness> appView, BiFunction<V, GraphLens, V> valueRewriter) {
+ return rewrittenWithLens(valueRewriter, appView.graphLens());
+ }
+
+ public LongLivedProgramMethodMapBuilder<V> rewrittenWithLens(
+ BiFunction<V, GraphLens, V> valueRewriter, GraphLens newGraphLens) {
+ // Check if the graph lens has changed (otherwise lens rewriting is not needed).
+ if (newGraphLens == appliedGraphLens) {
+ return this;
+ }
+
+ // Rewrite the backing.
+ Map<DexMethod, V> rewrittenBacking = factoryForBuilder.apply(backing.size());
+ backing.forEach(
+ (key, value) -> {
+ DexMethod rewrittenKey = newGraphLens.getRenamedMethodSignature(key, appliedGraphLens);
+ V rewrittenValue = valueRewriter.apply(value, appliedGraphLens);
+ assert !rewrittenBacking.containsKey(rewrittenKey);
+ rewrittenBacking.put(rewrittenKey, rewrittenValue);
+ });
+ backing = rewrittenBacking;
+
+ // Record that this collection is now rewritten up until the given graph lens.
+ appliedGraphLens = newGraphLens;
+ return this;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <U> ProgramMethodMap<U> build(
+ AppView<AppInfoWithLiveness> appView, Function<V, U> valueTransformer) {
+ assert verifyIsRewrittenWithLens(appView.graphLens());
+
+ ProgramMethodMap<U> result = (ProgramMethodMap<U>) factory.apply(backing.size());
+ backing.forEach(
+ (key, value) -> {
+ DexProgramClass holder = asProgramClassOrNull(appView.definitionFor(key.getHolderType()));
+ ProgramMethod method = key.lookupOnProgramClass(holder);
+ if (method != null) {
+ result.put(method, valueTransformer.apply(value));
+ } else {
+ assert false;
+ }
+ });
+ return result;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
index 43daf74..ef58a2d 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
@@ -17,7 +17,11 @@
private final Map<Wrapper<K>, V> backing;
ProgramMemberMap(Supplier<Map<Wrapper<K>, V>> backingFactory) {
- backing = backingFactory.get();
+ this.backing = backingFactory.get();
+ }
+
+ ProgramMemberMap(Map<Wrapper<K>, V> backing) {
+ this.backing = backing;
}
public void clear() {
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
index 8fdf054..d4dcca6 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
@@ -21,10 +21,18 @@
super(backingFactory);
}
+ private ProgramMethodMap(Map<Wrapper<ProgramMethod>, V> backing) {
+ super(backing);
+ }
+
public static <V> ProgramMethodMap<V> create() {
return new ProgramMethodMap<>(HashMap::new);
}
+ public static <V> ProgramMethodMap<V> create(int capacity) {
+ return new ProgramMethodMap<>(new HashMap<>(capacity));
+ }
+
public static <V> ProgramMethodMap<V> createConcurrent() {
return new ProgramMethodMap<>(ConcurrentHashMap::new);
}
diff --git a/src/test/examples/shaking14/KeepConstantArguments.java b/src/test/examples/shaking14/KeepConstantArguments.java
new file mode 100644
index 0000000..0ff3910
--- /dev/null
+++ b/src/test/examples/shaking14/KeepConstantArguments.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2021, 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 shaking14;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.METHOD})
+public @interface KeepConstantArguments {}
diff --git a/src/test/examples/shaking14/Subclass.java b/src/test/examples/shaking14/Subclass.java
index 31dd650..2cb520a 100644
--- a/src/test/examples/shaking14/Subclass.java
+++ b/src/test/examples/shaking14/Subclass.java
@@ -4,10 +4,13 @@
package shaking14;
public class Subclass extends Superclass {
+
+ @KeepConstantArguments
static int aMethod(int value) {
return value + 42;
}
+ @KeepConstantArguments
static double anotherMethod(double value) {
return value + 42;
}
diff --git a/src/test/examples/shaking14/keep-rules.txt b/src/test/examples/shaking14/keep-rules.txt
index 94682a3..594e244 100644
--- a/src/test/examples/shaking14/keep-rules.txt
+++ b/src/test/examples/shaking14/keep-rules.txt
@@ -10,3 +10,4 @@
# allow access modification to enable minifcation
-allowaccessmodification
+-keepconstantarguments class * { @shaking14.KeepConstantArguments *; }
diff --git a/src/test/examplesJava16/pattern_matching_for_instanceof/Main.java b/src/test/examplesJava17/pattern_matching_for_instanceof/Main.java
similarity index 100%
rename from src/test/examplesJava16/pattern_matching_for_instanceof/Main.java
rename to src/test/examplesJava17/pattern_matching_for_instanceof/Main.java
diff --git a/src/test/examplesJava16/records/EmptyRecord.java b/src/test/examplesJava17/records/EmptyRecord.java
similarity index 100%
rename from src/test/examplesJava16/records/EmptyRecord.java
rename to src/test/examplesJava17/records/EmptyRecord.java
diff --git a/src/test/examplesJava17/records/EmptyRecordAnnotation.java b/src/test/examplesJava17/records/EmptyRecordAnnotation.java
new file mode 100644
index 0000000..350e8ce
--- /dev/null
+++ b/src/test/examplesJava17/records/EmptyRecordAnnotation.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2021, 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 records;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class EmptyRecordAnnotation {
+
+ record Empty() {}
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface ClassAnnotation {
+ Class<? extends Record> theClass();
+ }
+
+ @ClassAnnotation(theClass = Record.class)
+ public static void annotatedMethod1() {}
+
+ @ClassAnnotation(theClass = Empty.class)
+ public static void annotatedMethod2() {}
+
+ public static void main(String[] args) throws Exception {
+ Class<?> annotatedMethod1Content =
+ EmptyRecordAnnotation.class
+ .getDeclaredMethod("annotatedMethod1")
+ .getAnnotation(ClassAnnotation.class)
+ .theClass();
+ System.out.println(annotatedMethod1Content);
+ Class<?> annotatedMethod2Content =
+ EmptyRecordAnnotation.class
+ .getDeclaredMethod("annotatedMethod2")
+ .getAnnotation(ClassAnnotation.class)
+ .theClass();
+ System.out.println(annotatedMethod2Content);
+ }
+}
diff --git a/src/test/examplesJava16/records/RecordInstanceOf.java b/src/test/examplesJava17/records/RecordInstanceOf.java
similarity index 100%
rename from src/test/examplesJava16/records/RecordInstanceOf.java
rename to src/test/examplesJava17/records/RecordInstanceOf.java
diff --git a/src/test/examplesJava16/records/RecordInvokeCustom.java b/src/test/examplesJava17/records/RecordInvokeCustom.java
similarity index 100%
rename from src/test/examplesJava16/records/RecordInvokeCustom.java
rename to src/test/examplesJava17/records/RecordInvokeCustom.java
diff --git a/src/test/examplesJava16/records/RecordReflection.java b/src/test/examplesJava17/records/RecordReflection.java
similarity index 100%
rename from src/test/examplesJava16/records/RecordReflection.java
rename to src/test/examplesJava17/records/RecordReflection.java
diff --git a/src/test/examplesJava16/records/RecordWithMembers.java b/src/test/examplesJava17/records/RecordWithMembers.java
similarity index 100%
rename from src/test/examplesJava16/records/RecordWithMembers.java
rename to src/test/examplesJava17/records/RecordWithMembers.java
diff --git a/src/test/examplesJava16/records/SimpleRecord.java b/src/test/examplesJava17/records/SimpleRecord.java
similarity index 100%
rename from src/test/examplesJava16/records/SimpleRecord.java
rename to src/test/examplesJava17/records/SimpleRecord.java
diff --git a/src/test/examplesJava17/records/UnusedRecordField.java b/src/test/examplesJava17/records/UnusedRecordField.java
new file mode 100644
index 0000000..1d184cd
--- /dev/null
+++ b/src/test/examplesJava17/records/UnusedRecordField.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2021, 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 records;
+
+public class UnusedRecordField {
+
+ Record unusedInstanceField;
+
+ void printHello() {
+ System.out.println("Hello!");
+ }
+
+ public static void main(String[] args) {
+ new UnusedRecordField().printHello();
+ }
+}
diff --git a/src/test/examplesJava17/records/UnusedRecordMethod.java b/src/test/examplesJava17/records/UnusedRecordMethod.java
new file mode 100644
index 0000000..342b178
--- /dev/null
+++ b/src/test/examplesJava17/records/UnusedRecordMethod.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2021, 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 records;
+
+public class UnusedRecordMethod {
+
+ Record unusedInstanceMethod(Record unused) {
+ return null;
+ }
+
+ void printHello() {
+ System.out.println("Hello!");
+ }
+
+ public static void main(String[] args) {
+ new UnusedRecordMethod().printHello();
+ }
+}
diff --git a/src/test/examplesJava17/records/UnusedRecordReflection.java b/src/test/examplesJava17/records/UnusedRecordReflection.java
new file mode 100644
index 0000000..1a4891b
--- /dev/null
+++ b/src/test/examplesJava17/records/UnusedRecordReflection.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2021, 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 records;
+
+import java.lang.reflect.Method;
+
+public class UnusedRecordReflection {
+
+ Record instanceField;
+
+ Record method(int i, Record unused, int j) {
+ return null;
+ }
+
+ Object reflectiveGetField() {
+ try {
+ return this.getClass().getDeclaredField("instanceField").get(this);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ Object reflectiveCallMethod() {
+ try {
+ for (Method declaredMethod : this.getClass().getDeclaredMethods()) {
+ if (declaredMethod.getName().equals("method")) {
+ return declaredMethod.invoke(this, 0, null, 1);
+ }
+ }
+ throw new RuntimeException("Unreachable");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println(new UnusedRecordReflection().reflectiveGetField());
+ System.out.println(new UnusedRecordReflection().reflectiveCallMethod());
+ }
+}
diff --git a/src/test/examplesJava16/sealed/Compiler.java b/src/test/examplesJava17/sealed/Compiler.java
similarity index 100%
rename from src/test/examplesJava16/sealed/Compiler.java
rename to src/test/examplesJava17/sealed/Compiler.java
diff --git a/src/test/examplesJava16/sealed/D8Compiler.java b/src/test/examplesJava17/sealed/D8Compiler.java
similarity index 100%
rename from src/test/examplesJava16/sealed/D8Compiler.java
rename to src/test/examplesJava17/sealed/D8Compiler.java
diff --git a/src/test/examplesJava16/sealed/Main.java b/src/test/examplesJava17/sealed/Main.java
similarity index 100%
rename from src/test/examplesJava16/sealed/Main.java
rename to src/test/examplesJava17/sealed/Main.java
diff --git a/src/test/examplesJava16/sealed/R8Compiler.java b/src/test/examplesJava17/sealed/R8Compiler.java
similarity index 100%
rename from src/test/examplesJava16/sealed/R8Compiler.java
rename to src/test/examplesJava17/sealed/R8Compiler.java
diff --git a/src/test/java/com/android/tools/r8/KeepConstantArguments.java b/src/test/java/com/android/tools/r8/KeepConstantArguments.java
index 87edb4f..132b92d 100644
--- a/src/test/java/com/android/tools/r8/KeepConstantArguments.java
+++ b/src/test/java/com/android/tools/r8/KeepConstantArguments.java
@@ -4,7 +4,10 @@
package com.android.tools.r8;
import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface KeepConstantArguments {}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index c81cdfc..188f23f 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -1287,8 +1287,16 @@
private static Map<String, List<String>> keepRules =
ImmutableMap.of(
- "021-string2", ImmutableList.of("-dontwarn junit.framework.**"),
- "082-inline-execute", ImmutableList.of("-dontwarn junit.framework.**"));
+ "021-string2",
+ ImmutableList.of("-dontwarn junit.framework.**"),
+ "082-inline-execute",
+ ImmutableList.of("-dontwarn junit.framework.**"),
+ // Constructor MakeBoundType.<init>(int) is called using reflection.
+ "476-checker-ctor-fence-redun-elim",
+ ImmutableList.of(
+ "-keep class TestDontOptimizeAcrossEscape$MakeBoundTypeTest$MakeBoundType {",
+ " void <init>(int);",
+ "}"));
private static Map<String, Consumer<InternalOptions>> configurations =
ImmutableMap.of(
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index fb37493..08dd682 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -120,7 +120,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 10, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 9, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -159,7 +159,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 10, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 9, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -204,7 +204,7 @@
b ->
b.addProguardConfiguration(
getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaringnplus"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaringnplus"))
.run();
}
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index 05f9e16..20d71c6 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -29,7 +29,7 @@
JDK9("jdk9", 53),
JDK10("jdk10", 54),
JDK11("jdk11", 55),
- JDK16("jdk16", 60),
+ JDK17("jdk17", 61),
;
private final String name;
@@ -70,13 +70,13 @@
private static final Path JDK9_PATH =
Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "openjdk-9.0.4");
private static final Path JDK11_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk-11");
- private static final Path JDK16_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk-16");
+ private static final Path JDK17_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk-17");
private static final Map<CfVm, Path> jdkPaths =
ImmutableMap.of(
CfVm.JDK8, JDK8_PATH,
CfVm.JDK9, JDK9_PATH,
CfVm.JDK11, JDK11_PATH,
- CfVm.JDK16, JDK16_PATH);
+ CfVm.JDK17, JDK17_PATH);
public static CfRuntime getCheckedInJdk(CfVm vm) {
if (vm == CfVm.JDK8) {
@@ -121,9 +121,9 @@
return new CfRuntime(CfVm.JDK11, getCheckedInJdkHome(CfVm.JDK11));
}
- // TODO(b/169692487): Add this to 'getCheckedInCfRuntimes' when we start having support for JDK16.
- public static CfRuntime getCheckedInJdk16() {
- return new CfRuntime(CfVm.JDK16, getCheckedInJdkHome(CfVm.JDK16));
+ // TODO(b/169692487): Add this to 'getCheckedInCfRuntimes' when we start having support for JDK17.
+ public static CfRuntime getCheckedInJdk17() {
+ return new CfRuntime(CfVm.JDK17, getCheckedInJdkHome(CfVm.JDK17));
}
public static List<CfRuntime> getCheckedInCfRuntimes() {
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
index 8cd7860..19c8015 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
@@ -32,7 +32,7 @@
private static final String STRING = "java.lang.String";
- private boolean enableArgumentRemoval;
+ private boolean enableUnusedArgumentRemoval;
@Parameterized.Parameters(name = "{0}, argument removal: {1}")
public static List<Object[]> data() {
@@ -40,9 +40,10 @@
getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
}
- public NonConstructorRelaxationTest(TestParameters parameters, boolean enableArgumentRemoval) {
+ public NonConstructorRelaxationTest(
+ TestParameters parameters, boolean enableUnusedArgumentRemoval) {
super(parameters);
- this.enableArgumentRemoval = enableArgumentRemoval;
+ this.enableUnusedArgumentRemoval = enableUnusedArgumentRemoval;
}
@Test
@@ -78,11 +79,13 @@
R8TestRunResult result =
testForR8(parameters.getBackend())
.addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage()))
+ .addUnusedArgumentAnnotations()
+ .enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
.enableMemberValuePropagationAnnotations()
+ .enableUnusedArgumentAnnotations(!enableUnusedArgumentRemoval)
.addKeepMainRule(mainClass)
- .addOptionsModification(o -> o.enableArgumentRemoval = enableArgumentRemoval)
.noMinification()
.addKeepRules(
// Note: we use '-checkdiscard' to indirectly check that the access relaxation is
@@ -104,23 +107,25 @@
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), mainClass);
- assertEquals(
- expectedOutput,
- result
- .getStdOut()
- .replace("java.lang.IncompatibleClassChangeError", "java.lang.IllegalAccessError"));
+ assertEquals(expectedOutput, result.getStdOut());
CodeInspector inspector = result.inspector();
+
+ MethodSignature barMethodSignatureAfterArgumentRemoval =
+ enableUnusedArgumentRemoval
+ ? new MethodSignature("bara", STRING, ImmutableList.of())
+ : new MethodSignature("bar", STRING, ImmutableList.of("int"));
assertPublic(inspector, A.class, new MethodSignature("baz", STRING, ImmutableList.of()));
assertPublic(inspector, A.class, new MethodSignature("bar", STRING, ImmutableList.of()));
- assertPublic(inspector, A.class, new MethodSignature("bar", STRING, ImmutableList.of("int")));
+ assertPublic(inspector, A.class, barMethodSignatureAfterArgumentRemoval);
- MethodSignature blahMethodSignature =
+ MethodSignature blahMethodSignatureAfterArgumentRemoval =
new MethodSignature(
- "blah", STRING, enableArgumentRemoval ? ImmutableList.of() : ImmutableList.of("int"));
- assertPublic(inspector, A.class, blahMethodSignature);
- assertPublic(inspector, B.class, blahMethodSignature);
- assertPublic(inspector, BB.class, blahMethodSignature);
+ "blah",
+ STRING,
+ enableUnusedArgumentRemoval ? ImmutableList.of() : ImmutableList.of("int"));
+ assertPublic(inspector, A.class, blahMethodSignatureAfterArgumentRemoval);
+ assertPublic(inspector, BB.class, blahMethodSignatureAfterArgumentRemoval);
}
@Test
@@ -162,6 +167,7 @@
.addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage()))
.addKeepMainRule(mainClass)
.addOptionsModification(o -> o.enableVerticalClassMerging = enableVerticalClassMerging)
+ .enableConstantArgumentAnnotations()
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
.enableMemberValuePropagationAnnotations()
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
index c832fe7..7974306 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.accessrelaxation.privateinstance;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
@@ -16,6 +17,7 @@
return "Sub1::foo1()";
}
+ @KeepConstantArguments
@NeverInline
private String bar1(int i) {
return "Sub1::bar1(" + i + ")";
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
index ffdeec6..6e8d5f7 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.accessrelaxation.privateinstance;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
@@ -16,6 +17,7 @@
return "Sub2::foo2()";
}
+ @KeepConstantArguments
@NeverInline
private String bar1(int i) {
return "Sub2::bar1(" + i + ")";
@@ -25,6 +27,7 @@
return bar1(1);
}
+ @KeepConstantArguments
@NeverInline
private String bar2(int i) {
return "Sub2::bar2(" + i + ")";
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privatestatic/A.java b/src/test/java/com/android/tools/r8/accessrelaxation/privatestatic/A.java
index a05f41f..e5981b3 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privatestatic/A.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privatestatic/A.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.accessrelaxation.privatestatic;
+import com.android.tools.r8.KeepConstantArguments;
+import com.android.tools.r8.KeepUnusedArguments;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NeverPropagateValue;
@@ -34,6 +36,7 @@
@NeverInline
@NeverPropagateValue
+ @KeepUnusedArguments
private static String bar(int i) {
return "A::bar(int)";
}
@@ -42,6 +45,8 @@
return bar(1);
}
+ @KeepConstantArguments
+ @KeepUnusedArguments
@NeverInline
@NeverPropagateValue
private static String blah(int i) {
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privatestatic/BB.java b/src/test/java/com/android/tools/r8/accessrelaxation/privatestatic/BB.java
index a6c40ff..4ac5250 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privatestatic/BB.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privatestatic/BB.java
@@ -4,12 +4,17 @@
package com.android.tools.r8.accessrelaxation.privatestatic;
+import com.android.tools.r8.KeepConstantArguments;
+import com.android.tools.r8.KeepUnusedArguments;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NeverPropagateValue;
import com.android.tools.r8.NoHorizontalClassMerging;
@NoHorizontalClassMerging
public class BB extends A {
+
+ @KeepUnusedArguments
+ @KeepConstantArguments
@NeverInline
@NeverPropagateValue
private static String blah(int i) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
index 602f4a1..bdaf09d 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestBase;
@@ -44,6 +45,7 @@
.addDefaultRuntimeLibrary(parameters)
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(Main.class)
+ .enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
.apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
@@ -63,6 +65,7 @@
@NoHorizontalClassMerging
public static class ApiCaller {
+ @KeepConstantArguments
public static void callInterfaceMethod(Api api) {
System.out.println("ApiCaller::callInterfaceMethod");
if (api != null) {
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
index 07ad8f0..1790724 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -8,6 +8,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -93,6 +94,7 @@
ClassBuilder cls1 = jasminBuilder.addClass("Cls1", absCls.name, itf1.name);
// Mimic Kotlin's "internal" class
cls1.setAccess("");
+ cls1.addRuntimeInvisibleAnnotation(KeepConstantArguments.class.getTypeName());
cls1.addBridgeMethod("foo", ImmutableList.of("Ljava/lang/String;"), "V",
".limit stack 2",
".limit locals 2",
@@ -143,6 +145,7 @@
.addProgramClassFileData(jasminBuilder.buildClasses())
.addKeepMainRule(mainClass.name)
.addOptionsModification(this::configure)
+ .enableConstantArgumentAnnotations()
.noHorizontalClassMerging(cls2Class.name)
.noMinification()
.setMinApi(parameters.getApiLevel())
@@ -239,6 +242,7 @@
"return");
ClassBuilder cls2 = jasminBuilder.addClass("DerivedString", baseCls.name);
+ cls2.addRuntimeInvisibleAnnotation(KeepConstantArguments.class.getTypeName());
cls2.addVirtualMethod("bar", ImmutableList.of("Ljava/lang/String;"), "V",
".limit stack 2",
".limit locals 2",
@@ -272,6 +276,7 @@
.addProgramClassFileData(jasminBuilder.buildClasses())
.addKeepMainRule(mainClass.name)
.addOptionsModification(this::configure)
+ .enableConstantArgumentAnnotations()
.noHorizontalClassMerging(derivedIntegerClass.name)
.noMinification()
.setMinApi(parameters.getApiLevel())
@@ -347,6 +352,7 @@
"return");
ClassBuilder subCls = jasminBuilder.addClass("DerivedString", baseCls.name);
+ subCls.addRuntimeInvisibleAnnotation(KeepConstantArguments.class.getTypeName());
subCls.addVirtualMethod("bar", ImmutableList.of("Ljava/lang/String;"), "V",
".limit stack 2",
".limit locals 2",
@@ -381,6 +387,7 @@
.addProgramClassFileData(jasminBuilder.buildClasses())
.addKeepMainRule(mainClass.name)
.addOptionsModification(this::configure)
+ .enableConstantArgumentAnnotations()
.noMinification()
.setMinApi(parameters.getApiLevel())
.compile()
@@ -433,6 +440,7 @@
"aload_1",
"invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V",
"return");
+ cls.addRuntimeInvisibleAnnotation(KeepConstantArguments.class.getTypeName());
cls.addVirtualMethod("bar", ImmutableList.of("Ljava/lang/String;"), "V",
".limit stack 2",
".limit locals 2",
@@ -467,6 +475,7 @@
.addProgramClassFileData(jasminBuilder.buildClasses())
.addKeepMainRule(mainClass.name)
.addOptionsModification(this::configure)
+ .enableConstantArgumentAnnotations()
.noMinification()
.setMinApi(parameters.getApiLevel())
.compile()
@@ -495,7 +504,6 @@
// Callees are invoked with a simple constant, e.g., "Bar". Propagating it into the callees
// bothers what the tests want to check, such as exact instructions in the body that include
// invocation kinds, like virtual call to a bridge.
- assert !options.callSiteOptimizationOptions().isConstantPropagationEnabled();
// Disable inlining to avoid the (short) tested method from being inlined and then removed.
options.enableInlining = false;
}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
index 81c5c04..45855d8 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoVerticalClassMerging;
@@ -71,6 +72,7 @@
int.class))
.transform())
.addKeepMainRule(TestClass.class)
+ .enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
.enableNoVerticalClassMergingAnnotations()
@@ -119,6 +121,7 @@
// This bridge cannot be hoisted to A, since it would then become inaccessible to the call site
// in TestClass.main().
+ @KeepConstantArguments
@NeverInline
/*bridge*/ String bridgeB(Object o) {
return (String) m((String) o);
@@ -131,6 +134,7 @@
// This bridge is invoked from another package. However, this does not prevent us from hoisting
// the bridge to B, although B is not public, since users from outside this package can still
// access bridgeC() via class C. From B, the bridge can be hoisted again to A.
+ @KeepConstantArguments
@NeverInline
public /*bridge*/ String bridgeC(Object o) {
return (String) m((String) o);
@@ -142,6 +146,7 @@
// This bridge cannot be hoisted to A, since it would then become inaccessible to the call site
// in TestClass.main().
+ @KeepConstantArguments
@NeverInline
/*bridge*/ String bridgeB(Object o, int a, int b, int c, int d, int e) {
return (String) m((String) o, a, b, c, d, e);
@@ -154,6 +159,7 @@
// This bridge is invoked from another package. However, this does not prevent us from hoisting
// the bridge to B, although B is not public, since users from outside this package can still
// access bridgeC() via class C. From B, the bridge can be hoisted again to A.
+ @KeepConstantArguments
@NeverInline
public /*bridge*/ String bridgeC(Object o, int a, int b, int c, int d, int e) {
return (String) m((String) o, a, b, c, d, e);
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/FinalBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/FinalBridgeHoistingTest.java
index de2e041..bf958f8 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/FinalBridgeHoistingTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/FinalBridgeHoistingTest.java
@@ -7,6 +7,7 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
@@ -42,6 +43,7 @@
.transform())
.addKeepMainRule(TestClass.class)
.addKeepClassAndMembersRules(B1.class)
+ .enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.setMinApi(parameters.getApiLevel())
@@ -85,6 +87,7 @@
@NeverClassInline
static class B1 extends A {
+ @KeepConstantArguments
public String virtualBridge(Object o) {
return (String) m((String) o);
}
@@ -93,6 +96,7 @@
@NeverClassInline
static class B2 extends A {
+ @KeepConstantArguments
@NeverInline
public final /*bridge*/ String virtualBridge(Object o) {
return (String) m((String) o);
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonSuperclassBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonSuperclassBridgeHoistingTest.java
index f2a91a8..e987a43 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonSuperclassBridgeHoistingTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonSuperclassBridgeHoistingTest.java
@@ -7,6 +7,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertFalse;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoVerticalClassMerging;
@@ -42,6 +43,7 @@
.setBridge(B.class.getDeclaredMethod("bridge", Object.class))
.transform())
.addKeepMainRule(TestClass.class)
+ .enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
.enableNeverClassInliningAnnotations()
@@ -76,6 +78,7 @@
@NeverClassInline
static class B extends A {
+ @KeepConstantArguments
@NeverInline
public Object m(String arg) {
return System.currentTimeMillis() >= 0 ? arg : null;
@@ -83,6 +86,7 @@
// This bridge cannot be hoisted to A, since it targets a method on the enclosing class.
// Hoisting the bridge to A would lead to a NoSuchMethodError.
+ @KeepConstantArguments
@NeverInline
public /*bridge*/ String bridge(Object o) {
return (String) m((String) o);
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
index dbdb906..ae77a45 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
@@ -7,6 +7,7 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
@@ -51,6 +52,7 @@
.setBridge(B5.class.getDeclaredMethod("virtualBridge", Object.class))
.transform())
.addKeepMainRule(TestClass.class)
+ .enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
@@ -106,6 +108,7 @@
static class A {
+ @KeepConstantArguments
@NeverInline
public Object m(String arg) {
return System.currentTimeMillis() >= 0 ? arg : null;
@@ -122,12 +125,14 @@
// This bridge can be hoisted to A if the invoke-super instruction is rewritten to an
// invoke-virtual instruction.
+ @KeepConstantArguments
@NeverInline
public /*bridge*/ String superBridge(Object o) {
return (String) super.m((String) o);
}
// This bridge can be hoisted to A.
+ @KeepConstantArguments
@NeverInline
public /*bridge*/ String virtualBridge(Object o) {
return (String) m((String) o);
@@ -139,12 +144,14 @@
static class B2 extends A {
// By hoisting B1.superBridge() to A this method bridge redundant.
+ @KeepConstantArguments
@NeverInline
public /*bridge*/ String superBridge(Object o) {
return (String) super.m((String) o);
}
// By hoisting B1.virtualBridge() to A this method bridge redundant.
+ @KeepConstantArguments
@NeverInline
public /*bridge*/ String virtualBridge(Object o) {
return (String) m((String) o);
@@ -171,11 +178,13 @@
@NoHorizontalClassMerging
static class B4 extends A {
+ @KeepConstantArguments
@NeverInline
public String superBridge(Object o) {
return System.currentTimeMillis() >= 0 ? ((String) o) : null;
}
+ @KeepConstantArguments
@NeverInline
public String virtualBridge(Object o) {
return System.currentTimeMillis() >= 0 ? ((String) o) : null;
@@ -188,11 +197,13 @@
@NoHorizontalClassMerging
static class B5 extends A {
+ @KeepConstantArguments
@NeverInline
public /*bridge*/ String superBridge(Object o) {
return (String) super.m2((String) o);
}
+ @KeepConstantArguments
@NeverInline
public /*bridge*/ String virtualBridge(Object o) {
return (String) m2((String) o);
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
index e7d430f..64c19e5 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.bridgeremoval.hoisting.testclasses;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.NoVerticalClassMerging;
@@ -33,6 +34,7 @@
@NoVerticalClassMerging
public static class AWithRangedInvoke {
+ @KeepConstantArguments
@NeverInline
public Object m(String arg, int a, int b, int c, int d, int e) {
return System.currentTimeMillis() > 0 ? arg + a + b + c + d + e : null;
diff --git a/src/test/java/com/android/tools/r8/classmerging/StatefulSingletonClassesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/StatefulSingletonClassesMergingTest.java
index fadf934..3f887af 100644
--- a/src/test/java/com/android/tools/r8/classmerging/StatefulSingletonClassesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/StatefulSingletonClassesMergingTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.classmerging;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NeverPropagateValue;
@@ -35,6 +36,7 @@
.addKeepMainRule(Main.class)
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class))
+ .enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.enableMemberValuePropagationAnnotations()
.enableNeverClassInliningAnnotations()
@@ -58,6 +60,9 @@
@NeverPropagateValue private final String data;
+ // TODO(b/198758663): With argument propagation the constructors end up not being equivalent,
+ // which prevents merging in the final round of horizontal class merging.
+ @KeepConstantArguments
A(String data) {
this.data = data;
}
@@ -75,6 +80,9 @@
@NeverPropagateValue private final String data;
+ // TODO(b/198758663): With argument propagation the constructors end up not being equivalent,
+ // which prevents merging in the final round of horizontal class merging.
+ @KeepConstantArguments
B(String data) {
this.data = data;
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
index 3858a1e..667c261 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
@@ -9,6 +9,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestParameters;
@@ -52,18 +53,13 @@
} else {
inspector.assertIsCompleteMergeGroup(A.class, B.class, C.class);
}
+ inspector.assertNoOtherClassesMerged();
})
+ .enableConstantArgumentAnnotations()
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), Main.class)
- .applyIf(
- enableProguardCompatibilityMode,
- result ->
- result.assertSuccessWithOutputLines(
- "a", "b", "c", "foo", "null", "annotation 2", "annotation 1", "annotation 2"),
- result ->
- result.assertSuccessWithOutputLines("a", "b", "c", "foo", "null", "annotation 2"))
+ .compile()
.inspect(
codeInspector -> {
assertThat(codeInspector.clazz(TypeAnnotation.class), isPresent());
@@ -73,7 +69,15 @@
codeInspector.clazz(B.class),
onlyIf(enableProguardCompatibilityMode, isPresent()));
assertThat(codeInspector.clazz(C.class), isAbsent());
- });
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ enableProguardCompatibilityMode,
+ result ->
+ result.assertSuccessWithOutputLines(
+ "a", "b", "c", "foo", "null", "annotation 2", "annotation 1", "annotation 2"),
+ result ->
+ result.assertSuccessWithOutputLines("a", "b", "c", "foo", "null", "annotation 2"));
}
@Retention(RetentionPolicy.RUNTIME)
@@ -114,6 +118,7 @@
}
static class Main {
+ @KeepConstantArguments
@NeverInline
public static void foo(TypeAnnotation annotation) {
System.out.println(annotation);
@@ -124,7 +129,7 @@
System.out.println(annotation.toString().replaceFirst(".*@.*", "annotation 2"));
}
- public static void main(String[] args) throws NoSuchMethodException {
+ public static void main(String[] args) {
A a = new A();
B b = new B("b");
C c = new C("c");
diff --git a/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java b/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java
index aab82f0..352186b 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java
@@ -8,6 +8,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
@@ -156,6 +157,7 @@
nonConstArraySize(0);
}
+ @KeepConstantArguments
@NeverInline
static void nonConstArraySize(int argumentTypesSize) {
try {
@@ -485,28 +487,23 @@
@Test
public void testNonConstArraySize() throws Exception {
- Class<?> mainClass = MainNonConstArraySize.class;
- R8Command.Builder builder =
- ToolHelper.prepareR8CommandBuilder(
- readClasses(A.class, mainClass, NeverInline.class), emptyConsumer(backend))
- .addLibraryFiles(runtimeJar(backend));
- builder.addProguardConfiguration(
- ImmutableList.of(keepMainProguardConfigurationWithInliningAnnotation(mainClass)),
- Origin.unknown());
- ToolHelper.allowTestProguardOptions(builder);
- AndroidApp output = ToolHelper.runR8(builder.build());
- CodeInspector inspector = new CodeInspector(output);
-
- assertThat(
- inspector.clazz(A.class).method("void", "method0", ImmutableList.of()),
- isPresentAndRenamed());
-
- // The reference run on the Java VM will succeed, whereas the run on the R8 output will fail
- // as in this test we fail to recognize the reflective call. To compare the output of the
- // successful reference run append "java.lang.NoSuchMethodException" to it.
- assertEquals(
- runOnJava(mainClass) + "java.lang.NoSuchMethodException",
- runOnVM(output, mainClass, backend));
+ testForR8(backend)
+ .addProgramClasses(MainNonConstArraySize.class, A.class)
+ .addKeepMainRule(MainNonConstArraySize.class)
+ .enableConstantArgumentAnnotations()
+ .enableInliningAnnotations()
+ .run(MainNonConstArraySize.class)
+ .inspect(
+ inspector -> {
+ assertThat(
+ inspector.clazz(A.class).method("void", "method0", ImmutableList.of()),
+ isPresentAndRenamed());
+ })
+ // The reference run on the Java VM will succeed, whereas the run on the R8 output will fail
+ // as in this test we fail to recognize the reflective call. To compare the output of the
+ // successful reference run append "java.lang.NoSuchMethodException" to it.
+ .assertSuccessWithOutput(
+ runOnJava(MainNonConstArraySize.class) + "java.lang.NoSuchMethodException");
}
@Test
diff --git a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
index b137f64..a63c85a 100644
--- a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
@@ -51,6 +51,11 @@
@Test
// Once R8 does not use expanded frames this can be enabled again.
public void test() throws Exception {
+ // TODO(b/199700280): Reenable on 12.0.0 when we have the libjdwp.so file include and the flags
+ // fixed.
+ Assume.assumeTrue(
+ "Skipping test " + testName.getMethodName() + " because debugging not enabled in 12.0.0",
+ !ToolHelper.getDexVm().isEqualTo(DexVm.ART_12_0_0_HOST));
Assume.assumeTrue(ToolHelper.getDexVm().isNewerThan(DexVm.ART_5_1_1_HOST)
&& !ToolHelper.isWindows());
DebugTestConfig cfConfig = new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
diff --git a/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java b/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java
index 22bca9f..4f72a65 100644
--- a/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java
@@ -4,10 +4,12 @@
package com.android.tools.r8.debug;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.VmTestRunner;
import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
import java.util.Collections;
+import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -21,6 +23,11 @@
@Test
@IgnoreIfVmOlderThan(Version.V6_0_1)
public void testHitOnEntryOnly() throws Throwable {
+ // TODO(b/199700280): Reenable on 12.0.0 when we have the libjdwp.so file include and the flags
+ // fixed.
+ Assume.assumeTrue(
+ "Skipping test " + testName.getMethodName() + " because debugging not enabled in 12.0.0",
+ !ToolHelper.getDexVm().isEqualTo(DexVm.ART_12_0_0_HOST));
DebugTestConfig cf = new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
DebugTestConfig d8 = new D8DebugTestConfig().compileAndAdd(
temp, Collections.singletonList(ToolHelper.getClassFileForTestClass(CLASS)));
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index d92d4ec..baa98a2 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -188,6 +188,11 @@
protected DebugTestRunner getDebugTestRunner(
DebugTestConfig config, String debuggeeClass, List<JUnit3Wrapper.Command> commands)
throws Throwable {
+ // TODO(b/199700280): Reenable on 12.0.0 when we have the libjdwp.so file include and the flags
+ // fixed.
+ Assume.assumeTrue(
+ "Skipping test " + testName.getMethodName() + " because debugging not enabled in 12.0.0",
+ !ToolHelper.getDexVm().isEqualTo(DexVm.ART_12_0_0_HOST));
// Skip test due to unsupported runtime.
Assume.assumeTrue("Skipping test " + testName.getMethodName() + " because ART is not supported",
ToolHelper.artSupported());
diff --git a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
index 12c6bc8..349ac60 100644
--- a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
import com.android.tools.r8.origin.Origin;
@@ -335,6 +336,11 @@
}
private DebugStreamComparator init(String pkg, String clazz) throws Exception {
+ // TODO(b/199700280): Reenable on 12.0.0 when we have the libjdwp.so file include and the flags
+ // fixed.
+ Assume.assumeTrue(
+ "Skipping test " + testName.getMethodName() + " because debugging not enabled in 12.0.0",
+ !ToolHelper.getDexVm().isEqualTo(DexVm.ART_12_0_0_HOST));
// See verifyStateLocation in DebugTestBase.
Assume.assumeTrue(
"Streaming on Dalvik DEX runtimes has some unknown interference issue",
diff --git a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
index 3e7367e..8a503e9 100644
--- a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.R8Command.Builder;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
@@ -69,6 +70,11 @@
}
private void stepOutput(byte[] clazz) throws Exception {
+ // TODO(b/199700280): Reenable on 12.0.0 when we have the libjdwp.so file include and the flags
+ // fixed.
+ Assume.assumeTrue(
+ "Skipping test " + testName.getMethodName() + " because debugging not enabled in 12.0.0",
+ !ToolHelper.getDexVm().isEqualTo(DexVm.ART_12_0_0_HOST));
// See verifyStateLocation in DebugTestBase.
Assume.assumeTrue(
"Streaming on Dalvik DEX runtimes has some unknown interference issue",
diff --git a/src/test/java/com/android/tools/r8/debug/NonExitingMethodTestRunner.java b/src/test/java/com/android/tools/r8/debug/NonExitingMethodTestRunner.java
index df358b2..6fb4aa7 100644
--- a/src/test/java/com/android/tools/r8/debug/NonExitingMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/NonExitingMethodTestRunner.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.VmTestRunner;
import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
@@ -56,6 +57,11 @@
@Test
@IgnoreIfVmOlderThan(Version.V6_0_1)
public void test() throws Exception {
+ // TODO(b/199700280): Reenable on 12.0.0 when we have the libjdwp.so file include and the flags
+ // fixed.
+ Assume.assumeTrue(
+ "Skipping test " + testName.getMethodName() + " because debugging not enabled in 12.0.0",
+ !ToolHelper.getDexVm().isEqualTo(DexVm.ART_12_0_0_HOST));
Assume.assumeTrue(
"Skipping test "
+ testName.getMethodName()
diff --git a/src/test/java/com/android/tools/r8/debug/PostIncrementTestRunner.java b/src/test/java/com/android/tools/r8/debug/PostIncrementTestRunner.java
index 6ddc9db..8f967e7 100644
--- a/src/test/java/com/android/tools/r8/debug/PostIncrementTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/PostIncrementTestRunner.java
@@ -24,6 +24,11 @@
@Test
@IgnoreIfVmOlderOrEqualThan(Version.V5_1_1)
public void test() throws Exception {
+ // TODO(b/199700280): Reenable on 12.0.0 when we have the libjdwp.so file include and the flags
+ // fixed.
+ Assume.assumeTrue(
+ "Skipping test " + testName.getMethodName() + " because debugging not enabled in 12.0.0",
+ !ToolHelper.getDexVm().isEqualTo(DexVm.ART_12_0_0_HOST));
Assume.assumeTrue("Older runtimes cause some kind of debug streaming issues",
ToolHelper.getDexVm().isNewerThan(DexVm.ART_5_1_1_HOST));
DebugTestConfig cfConfig = new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
diff --git a/src/test/java/com/android/tools/r8/debug/ThrowNPETestRunner.java b/src/test/java/com/android/tools/r8/debug/ThrowNPETestRunner.java
index 9271847..1f9eca9 100644
--- a/src/test/java/com/android/tools/r8/debug/ThrowNPETestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/ThrowNPETestRunner.java
@@ -23,6 +23,11 @@
@Test
@IgnoreIfVmOlderOrEqualThan(Version.V5_1_1)
public void test() throws Exception {
+ // TODO(b/199700280): Reenable on 12.0.0 when we have the libjdwp.so file include and the flags
+ // fixed.
+ Assume.assumeTrue(
+ "Skipping test " + testName.getMethodName() + " because debugging not enabled in 12.0.0",
+ !ToolHelper.getDexVm().isEqualTo(DexVm.ART_12_0_0_HOST));
Assume.assumeTrue("Older runtimes cause some kind of debug streaming issues",
ToolHelper.getDexVm().isNewerThan(DexVm.ART_5_1_1_HOST));
DebugTestConfig cfConfig = new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilFunctionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilFunctionTest.java
index 0ac3687..2e0481d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilFunctionTest.java
@@ -8,6 +8,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
@@ -89,6 +90,7 @@
.addKeepMainRule(TestClass.class)
.addInnerClasses(JavaUtilFunctionTest.class)
.setMinApi(parameters.getApiLevel())
+ .enableConstantArgumentAnnotations()
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
.inspect(this::checkRewrittenArguments)
@@ -103,6 +105,7 @@
static class TestClass {
+ @KeepConstantArguments
@NeverInline
private static String applyFunction(Function<String, String> f) {
return f.apply("Hello, world");
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaPrivateStaticMethodTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaPrivateStaticMethodTest.java
new file mode 100644
index 0000000..448234c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaPrivateStaticMethodTest.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2021, 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.desugar.lambdas;
+
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LambdaPrivateStaticMethodTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello world");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public LambdaPrivateStaticMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ interface MyFun {
+ void run();
+ }
+
+ static class TestClass {
+
+ public static void run(MyFun fn) {
+ fn.run();
+ }
+
+ public static void main(String[] args) {
+ run(() -> System.out.println("Hello world"));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java
new file mode 100644
index 0000000..4ea7cd7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2021, 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.desugar.records;
+
+import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE_R8_CF_TO_CF;
+import static com.android.tools.r8.utils.InternalOptions.TestingOptions;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+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 EmptyRecordAnnotationTest extends TestBase {
+
+ private static final String RECORD_NAME = "EmptyRecordAnnotation";
+ private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+ private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+ private static final String EXPECTED_RESULT_CF =
+ StringUtils.lines("class java.lang.Record", "class records.EmptyRecordAnnotation$Empty");
+ private static final String EXPECTED_RESULT_DEX =
+ StringUtils.lines(
+ "class com.android.tools.r8.RecordTag", "class records.EmptyRecordAnnotation$Empty");
+
+ private final TestParameters parameters;
+
+ public EmptyRecordAnnotationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
+ return buildParameters(
+ getTestParameters()
+ .withCustomRuntime(CfRuntime.getCheckedInJdk17())
+ .withDexRuntimes()
+ .withAllApiLevelsAlsoForCf()
+ .build());
+ }
+
+ @Test
+ public void testD8AndJvm() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClassFileData(PROGRAM_DATA)
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT_CF);
+ }
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT_DEX);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ R8FullTestBuilder builder =
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepRules("-keep class records.EmptyRecordAnnotation { *; }")
+ .addKeepMainRule(MAIN_TYPE)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
+ if (parameters.isCfRuntime()) {
+ builder
+ .addKeepRules(RECORD_KEEP_RULE_R8_CF_TO_CF)
+ .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+ .compile()
+ .inspect(RecordTestUtils::assertRecordsAreRecords)
+ .enableJVMPreview()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT_CF);
+ return;
+ }
+ builder
+ .addKeepRules("-keepattributes *Annotation*")
+ .addKeepRules("-keep class records.EmptyRecordAnnotation$Empty")
+ .addKeepRules("-keep class java.lang.Record")
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT_DEX);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
index 817114e..effcba3 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
@@ -4,9 +4,10 @@
package com.android.tools.r8.desugar.records;
-import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE;
+import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE_R8_CF_TO_CF;
import static com.android.tools.r8.utils.InternalOptions.TestingOptions;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfRuntime;
@@ -32,10 +33,10 @@
@Parameterized.Parameters(name = "{0}")
public static List<Object[]> data() {
- // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
return buildParameters(
getTestParameters()
- .withCustomRuntime(CfRuntime.getCheckedInJdk16())
+ .withCustomRuntime(CfRuntime.getCheckedInJdk17())
.withDexRuntimes()
.withAllApiLevelsAlsoForCf()
.build());
@@ -46,7 +47,6 @@
if (parameters.isCfRuntime()) {
testForJvm()
.addProgramClassFileData(PROGRAM_DATA)
- .enablePreview()
.run(parameters.getRuntime(), MAIN_TYPE)
.assertSuccessWithOutput(EXPECTED_RESULT);
}
@@ -61,29 +61,22 @@
@Test
public void testR8() throws Exception {
+ R8FullTestBuilder builder =
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_TYPE)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
if (parameters.isCfRuntime()) {
- testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
- .setMinApi(parameters.getApiLevel())
- .addKeepRules(RECORD_KEEP_RULE)
- .addKeepMainRule(MAIN_TYPE)
+ builder
+ .addKeepRules(RECORD_KEEP_RULE_R8_CF_TO_CF)
.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
- .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
.compile()
.inspect(RecordTestUtils::assertRecordsAreRecords)
- .enableJVMPreview()
.run(parameters.getRuntime(), MAIN_TYPE)
.assertSuccessWithOutput(EXPECTED_RESULT);
return;
}
- testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
- .setMinApi(parameters.getApiLevel())
- .addKeepRules(RECORD_KEEP_RULE)
- .addKeepMainRule(MAIN_TYPE)
- .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
- .compile()
- .run(parameters.getRuntime(), MAIN_TYPE)
- .assertSuccessWithOutput(EXPECTED_RESULT);
+ builder.run(parameters.getRuntime(), MAIN_TYPE).assertSuccessWithOutput(EXPECTED_RESULT);
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
index 6d0f1a3..d4da59e 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
@@ -4,8 +4,9 @@
package com.android.tools.r8.desugar.records;
-import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE;
+import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE_R8_CF_TO_CF;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfRuntime;
@@ -32,10 +33,10 @@
@Parameterized.Parameters(name = "{0}")
public static List<Object[]> data() {
- // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
return buildParameters(
getTestParameters()
- .withCustomRuntime(CfRuntime.getCheckedInJdk16())
+ .withCustomRuntime(CfRuntime.getCheckedInJdk17())
.withDexRuntimes()
.withAllApiLevelsAlsoForCf()
.build());
@@ -46,7 +47,6 @@
if (parameters.isCfRuntime()) {
testForJvm()
.addProgramClassFileData(PROGRAM_DATA)
- .enablePreview()
.run(parameters.getRuntime(), MAIN_TYPE)
.assertSuccessWithOutput(EXPECTED_RESULT);
}
@@ -61,29 +61,22 @@
@Test
public void testR8() throws Exception {
+ R8FullTestBuilder builder =
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_TYPE)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
if (parameters.isCfRuntime()) {
- testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
- .setMinApi(parameters.getApiLevel())
- .addKeepRules(RECORD_KEEP_RULE)
- .addKeepMainRule(MAIN_TYPE)
+ builder
+ .addKeepRules(RECORD_KEEP_RULE_R8_CF_TO_CF)
.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
- .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
.compile()
.inspect(RecordTestUtils::assertRecordsAreRecords)
- .enableJVMPreview()
.run(parameters.getRuntime(), MAIN_TYPE)
.assertSuccessWithOutput(EXPECTED_RESULT);
return;
}
- testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
- .setMinApi(parameters.getApiLevel())
- .addKeepRules(RECORD_KEEP_RULE)
- .addKeepMainRule(MAIN_TYPE)
- .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
- .compile()
- .run(parameters.getRuntime(), MAIN_TYPE)
- .assertSuccessWithOutput(EXPECTED_RESULT);
+ builder.run(parameters.getRuntime(), MAIN_TYPE).assertSuccessWithOutput(EXPECTED_RESULT);
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java
index 0383285..2d771cc 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java
@@ -4,8 +4,6 @@
package com.android.tools.r8.desugar.records;
-import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE;
-
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.InternalOptions.TestingOptions;
@@ -79,7 +77,6 @@
testForR8(parameters.getBackend())
.addProgramFiles(desugared)
.setMinApi(parameters.getApiLevel())
- .addKeepRules(RECORD_KEEP_RULE)
.addKeepMainRule(MAIN_TYPE)
.addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
.compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
index 3326483..1fc2f3e 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
@@ -4,8 +4,9 @@
package com.android.tools.r8.desugar.records;
-import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE;
+import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE_R8_CF_TO_CF;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfRuntime;
@@ -45,10 +46,10 @@
@Parameterized.Parameters(name = "{0}")
public static List<Object[]> data() {
- // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
return buildParameters(
getTestParameters()
- .withCustomRuntime(CfRuntime.getCheckedInJdk16())
+ .withCustomRuntime(CfRuntime.getCheckedInJdk17())
.withDexRuntimes()
.withAllApiLevelsAlsoForCf()
.build());
@@ -59,7 +60,6 @@
if (parameters.isCfRuntime()) {
testForJvm()
.addProgramClassFileData(PROGRAM_DATA)
- .enablePreview()
.run(parameters.getRuntime(), MAIN_TYPE)
.assertSuccessWithOutput(EXPECTED_RESULT);
}
@@ -74,29 +74,22 @@
@Test
public void testR8() throws Exception {
+ R8FullTestBuilder builder =
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_TYPE)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
if (parameters.isCfRuntime()) {
- testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
- .setMinApi(parameters.getApiLevel())
- .addKeepRules(RECORD_KEEP_RULE)
- .addKeepMainRule(MAIN_TYPE)
+ builder
+ .addKeepRules(RECORD_KEEP_RULE_R8_CF_TO_CF)
.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
- .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
.compile()
.inspect(RecordTestUtils::assertRecordsAreRecords)
- .enableJVMPreview()
.run(parameters.getRuntime(), MAIN_TYPE)
.assertSuccessWithOutput(EXPECTED_RESULT);
return;
}
- testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
- .setMinApi(parameters.getApiLevel())
- .addKeepRules(RECORD_KEEP_RULE)
- .addKeepMainRule(MAIN_TYPE)
- .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
- .compile()
- .run(parameters.getRuntime(), MAIN_TYPE)
- .assertSuccessWithOutput(EXPECTED_RESULT);
+ builder.run(parameters.getRuntime(), MAIN_TYPE).assertSuccessWithOutput(EXPECTED_RESULT);
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
index ef11ef4..7af5f41 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
@@ -40,10 +40,10 @@
@Parameterized.Parameters(name = "{0}")
public static List<Object[]> data() {
- // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
return buildParameters(
getTestParameters()
- .withCustomRuntime(CfRuntime.getCheckedInJdk16())
+ .withCustomRuntime(CfRuntime.getCheckedInJdk17())
.withDexRuntimes()
.withAllApiLevelsAlsoForCf()
.build());
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
index cad3c5b..26d259a 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
@@ -4,7 +4,7 @@
package com.android.tools.r8.desugar.records;
-import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE;
+import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE_R8_CF_TO_CF;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -42,16 +42,15 @@
@Parameterized.Parameters(name = "{0}")
public static List<Object[]> data() {
- // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
return buildParameters(
- getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk16()).build());
+ getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk17()).build());
}
@Test
public void testJvm() throws Exception {
testForJvm()
.addProgramClassFileData(PROGRAM_DATA)
- .enablePreview()
.run(parameters.getRuntime(), MAIN_TYPE)
.assertSuccessWithOutput(EXPECTED_RESULT);
}
@@ -61,8 +60,8 @@
testForR8(parameters.getBackend())
.addProgramClassFileData(PROGRAM_DATA)
.setMinApi(parameters.getApiLevel())
- .addKeepRules(RECORD_KEEP_RULE)
.addKeepMainRule(MAIN_TYPE)
+ .addKeepRules(RECORD_KEEP_RULE_R8_CF_TO_CF)
.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
.addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
.compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
index 224a873..3f8adcc 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
@@ -33,15 +33,18 @@
*/
public class RecordTestUtils {
- private static final String EXAMPLE_FOLDER = "examplesJava16";
+ private static final String EXAMPLE_FOLDER = "examplesJava17";
private static final String RECORD_FOLDER = "records";
public static Path jar() {
return Paths.get(ToolHelper.TESTS_BUILD_DIR, EXAMPLE_FOLDER, RECORD_FOLDER + ".jar");
}
- // TODO(b/169645628): Consider if that keep rule should be required or not.
- public static final String RECORD_KEEP_RULE =
+ // TODO(b/197081367): Rediscuss if we want to maintain the toString() method.
+ // In R8 Cf to Cf we need to maintain the record component attributes, the class name and fields
+ // so the mapping between the record components and fields is valid and the toString() prints
+ // the record as specified (including the class name).
+ public static final String RECORD_KEEP_RULE_R8_CF_TO_CF =
"-keepattributes *\n" + "-keep class * extends java.lang.Record { private final <fields>; }";
public static Path[] getJdk15LibraryFiles(TemporaryFolder temp) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
index 4b2c62a..f1fc5ae 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
@@ -4,8 +4,9 @@
package com.android.tools.r8.desugar.records;
-import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE;
+import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE_R8_CF_TO_CF;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfRuntime;
@@ -34,10 +35,10 @@
@Parameterized.Parameters(name = "{0}")
public static List<Object[]> data() {
- // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
return buildParameters(
getTestParameters()
- .withCustomRuntime(CfRuntime.getCheckedInJdk16())
+ .withCustomRuntime(CfRuntime.getCheckedInJdk17())
.withDexRuntimes()
.withAllApiLevelsAlsoForCf()
.build());
@@ -48,7 +49,6 @@
if (parameters.isCfRuntime()) {
testForJvm()
.addProgramClassFileData(PROGRAM_DATA)
- .enablePreview()
.run(parameters.getRuntime(), MAIN_TYPE)
.assertSuccessWithOutput(EXPECTED_RESULT);
}
@@ -63,29 +63,22 @@
@Test
public void testR8() throws Exception {
+ R8FullTestBuilder builder =
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_TYPE)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
if (parameters.isCfRuntime()) {
- testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
- .setMinApi(parameters.getApiLevel())
- .addKeepRules(RECORD_KEEP_RULE)
- .addKeepMainRule(MAIN_TYPE)
+ builder
+ .addKeepRules(RECORD_KEEP_RULE_R8_CF_TO_CF)
.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
- .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
.compile()
.inspect(RecordTestUtils::assertRecordsAreRecords)
- .enableJVMPreview()
.run(parameters.getRuntime(), MAIN_TYPE)
.assertSuccessWithOutput(EXPECTED_RESULT);
return;
}
- testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
- .setMinApi(parameters.getApiLevel())
- .addKeepRules(RECORD_KEEP_RULE)
- .addKeepMainRule(MAIN_TYPE)
- .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
- .compile()
- .run(parameters.getRuntime(), MAIN_TYPE)
- .assertSuccessWithOutput(EXPECTED_RESULT);
+ builder.run(parameters.getRuntime(), MAIN_TYPE).assertSuccessWithOutput(EXPECTED_RESULT);
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
index ad338ed..68ad95e 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
@@ -4,8 +4,9 @@
package com.android.tools.r8.desugar.records;
-import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE;
+import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE_R8_CF_TO_CF;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfRuntime;
@@ -33,10 +34,10 @@
@Parameterized.Parameters(name = "{0}")
public static List<Object[]> data() {
- // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
return buildParameters(
getTestParameters()
- .withCustomRuntime(CfRuntime.getCheckedInJdk16())
+ .withCustomRuntime(CfRuntime.getCheckedInJdk17())
.withDexRuntimes()
.withAllApiLevelsAlsoForCf()
.build());
@@ -47,7 +48,6 @@
if (parameters.isCfRuntime()) {
testForJvm()
.addProgramClassFileData(PROGRAM_DATA)
- .enablePreview()
.run(parameters.getRuntime(), MAIN_TYPE)
.assertSuccessWithOutput(EXPECTED_RESULT);
}
@@ -65,27 +65,23 @@
@Test
public void testR8() throws Exception {
+ R8FullTestBuilder builder =
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_TYPE)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
if (parameters.isCfRuntime()) {
- testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
- .setMinApi(parameters.getApiLevel())
- .addKeepRules(RECORD_KEEP_RULE)
- .addKeepMainRule(MAIN_TYPE)
+ builder
+ .addKeepRules(RECORD_KEEP_RULE_R8_CF_TO_CF)
.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
- .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
.compile()
.inspect(RecordTestUtils::assertRecordsAreRecords)
- .enableJVMPreview()
.run(parameters.getRuntime(), MAIN_TYPE)
.assertSuccessWithOutput(EXPECTED_RESULT);
return;
}
- testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
- .setMinApi(parameters.getApiLevel())
- .addKeepRules(RECORD_KEEP_RULE)
- .addKeepMainRule(MAIN_TYPE)
- .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ builder
.compile()
.inspectWithOptions(
RecordTestUtils::assertNoJavaLangRecord,
@@ -96,29 +92,24 @@
@Test
public void testR8NoMinification() throws Exception {
+ R8FullTestBuilder builder =
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_TYPE)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
if (parameters.isCfRuntime()) {
- testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
- .noMinification()
- .setMinApi(parameters.getApiLevel())
- .addKeepRules(RECORD_KEEP_RULE)
- .addKeepMainRule(MAIN_TYPE)
+ builder
+ .addKeepRules(RECORD_KEEP_RULE_R8_CF_TO_CF)
.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
- .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
.compile()
.inspect(RecordTestUtils::assertRecordsAreRecords)
- .enableJVMPreview()
.run(parameters.getRuntime(), MAIN_TYPE)
.assertSuccessWithOutput(EXPECTED_RESULT);
return;
}
- testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
- .noMinification()
- .setMinApi(parameters.getApiLevel())
- .addKeepRules(RECORD_KEEP_RULE)
- .addKeepMainRule(MAIN_TYPE)
- .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ builder
.compile()
.inspectWithOptions(
RecordTestUtils::assertNoJavaLangRecord,
diff --git a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordFieldTest.java b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordFieldTest.java
new file mode 100644
index 0000000..5c520cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordFieldTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2021, 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.desugar.records;
+
+import static com.android.tools.r8.utils.InternalOptions.TestingOptions;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+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 UnusedRecordFieldTest extends TestBase {
+
+ private static final String RECORD_NAME = "UnusedRecordField";
+ private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+ private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+ private static final String EXPECTED_RESULT = StringUtils.lines("Hello!");
+
+ private final TestParameters parameters;
+
+ public UnusedRecordFieldTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
+ return buildParameters(
+ getTestParameters()
+ .withCustomRuntime(CfRuntime.getCheckedInJdk17())
+ .withDexRuntimes()
+ .withAllApiLevelsAlsoForCf()
+ .build());
+ }
+
+ @Test
+ public void testD8AndJvm() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClassFileData(PROGRAM_DATA)
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ R8FullTestBuilder builder =
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepRules("-keep class records.UnusedRecordField { *; }")
+ .addKeepMainRule(MAIN_TYPE)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
+ if (parameters.isCfRuntime()) {
+ builder
+ .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+ .compile()
+ .inspect(RecordTestUtils::assertRecordsAreRecords)
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ return;
+ }
+ builder.run(parameters.getRuntime(), MAIN_TYPE).assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordMethodTest.java b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordMethodTest.java
new file mode 100644
index 0000000..4c2ddf5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordMethodTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2021, 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.desugar.records;
+
+import static com.android.tools.r8.utils.InternalOptions.TestingOptions;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+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 UnusedRecordMethodTest extends TestBase {
+
+ private static final String RECORD_NAME = "UnusedRecordMethod";
+ private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+ private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+ private static final String EXPECTED_RESULT = StringUtils.lines("Hello!");
+
+ private final TestParameters parameters;
+
+ public UnusedRecordMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
+ return buildParameters(
+ getTestParameters()
+ .withCustomRuntime(CfRuntime.getCheckedInJdk17())
+ .withDexRuntimes()
+ .withAllApiLevelsAlsoForCf()
+ .build());
+ }
+
+ @Test
+ public void testD8AndJvm() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClassFileData(PROGRAM_DATA)
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ R8FullTestBuilder builder =
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepRules("-keep class records.UnusedRecordMethod { *; }")
+ .addKeepMainRule(MAIN_TYPE)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
+ if (parameters.isCfRuntime()) {
+ builder
+ .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+ .compile()
+ .inspect(RecordTestUtils::assertRecordsAreRecords)
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ return;
+ }
+ builder.run(parameters.getRuntime(), MAIN_TYPE).assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordReflectionTest.java b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordReflectionTest.java
new file mode 100644
index 0000000..7317cea
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordReflectionTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2021, 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.desugar.records;
+
+import static com.android.tools.r8.utils.InternalOptions.TestingOptions;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+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 UnusedRecordReflectionTest extends TestBase {
+
+ private static final String RECORD_NAME = "UnusedRecordReflection";
+ private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+ private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+ private static final String EXPECTED_RESULT = StringUtils.lines("null", "null");
+
+ private final TestParameters parameters;
+
+ public UnusedRecordReflectionTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
+ return buildParameters(
+ getTestParameters()
+ .withCustomRuntime(CfRuntime.getCheckedInJdk17())
+ .withDexRuntimes()
+ .withAllApiLevelsAlsoForCf()
+ .build());
+ }
+
+ @Test
+ public void testD8AndJvm() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClassFileData(PROGRAM_DATA)
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ R8FullTestBuilder builder =
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepRules("-keep class records.UnusedRecordReflection { *; }")
+ .addKeepMainRule(MAIN_TYPE)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
+ if (parameters.isCfRuntime()) {
+ builder
+ .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+ .compile()
+ .inspect(RecordTestUtils::assertRecordsAreRecords)
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ return;
+ }
+ builder.run(parameters.getRuntime(), MAIN_TYPE).assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
index d311db8..710f6f5 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime;
-import com.android.tools.r8.examples.jdk16.Sealed;
+import com.android.tools.r8.examples.jdk17.Sealed;
import com.android.tools.r8.utils.AndroidApiLevel;
import java.util.List;
import org.junit.Test;
@@ -28,9 +28,9 @@
@Parameters(name = "{0}")
public static List<Object[]> data() {
- // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
return buildParameters(
- getTestParameters().withCustomRuntime(TestRuntime.getCheckedInJdk16()).build(),
+ getTestParameters().withCustomRuntime(TestRuntime.getCheckedInJdk17()).build(),
Backend.values());
}
@@ -43,8 +43,7 @@
assumeTrue(backend == Backend.CF);
testForJvm()
.addRunClasspathFiles(Sealed.jar())
- .enablePreview()
- .run(TestRuntime.getCheckedInJdk16(), Sealed.Main.typeName())
+ .run(TestRuntime.getCheckedInJdk17(), Sealed.Main.typeName())
.assertSuccessWithOutputLines("R8 compiler", "D8 compiler");
}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionInvokeVirtualTest.java
index 363e539..3feba89 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionInvokeVirtualTest.java
@@ -83,7 +83,8 @@
testForRuntime(parameters)
.addProgramClasses(getProgramClasses())
.addProgramClassFileData(getProgramClassData())
- .run(parameters.getRuntime(), TestClass.class));
+ .run(parameters.getRuntime(), TestClass.class),
+ false);
}
@Test
@@ -95,21 +96,24 @@
.addKeepAllClassesRule()
.setMinApi(parameters.getApiLevel())
.compile()
- .run(parameters.getRuntime(), TestClass.class));
+ .run(parameters.getRuntime(), TestClass.class),
+ true);
}
- private void checkResult(TestRunResult<?> result) {
+ private void checkResult(TestRunResult<?> result, boolean isR8) {
// Invalid invoke case is where the invoke-virtual targets C.m.
if (invalidInvoke) {
- // Up to 4.4 the exception for targeting a private static was ICCE.
- if (isDexOlderThanOrEqual(Version.V4_4_4)) {
- result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
- return;
- }
- // Then up to 6.0 the runtime just ignores privates leading to incorrectly hitting I.m
- if (isDexOlderThanOrEqual(Version.V6_0_1)) {
- result.assertSuccessWithOutput(EXPECTED);
- return;
+ if (!isR8) {
+ // Up to 4.4 the exception for targeting a private static was ICCE.
+ if (isDexOlderThanOrEqual(Version.V4_4_4)) {
+ result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+ return;
+ }
+ // Then up to 6.0 the runtime just ignores privates leading to incorrectly hitting I.m
+ if (isDexOlderThanOrEqual(Version.V6_0_1)) {
+ result.assertSuccessWithOutput(EXPECTED);
+ return;
+ }
}
// The expected behavior is IAE since the resolved method is private.
result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java
index c960b33..2639a1f 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java
@@ -103,7 +103,8 @@
// Invalid invoke case is where the invoke-virtual targets C.m.
if (invalidInvoke) {
if (parameters.isDexRuntimeVersion(Version.V7_0_0)
- && parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring()) {
+ && parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring()
+ && !isR8) {
// The v7 VM incorrectly fails to throw.
result.assertSuccessWithOutput(EXPECTED);
} else {
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
index 32cfb18..cbe8874 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
@@ -138,7 +138,7 @@
}
@Test(expected = CompilationFailedException.class)
- @IgnoreForRangeOfVmVersions(from = Version.V7_0_0, to = Version.V10_0_0) // No desugaring
+ @IgnoreForRangeOfVmVersions(from = Version.V7_0_0, to = Version.V12_0_0) // No desugaring
public void testInvokeDefault1() throws Exception {
ensureSameOutput(
TestMainDefault1.class.getCanonicalName(),
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
index 4c69310..093f902 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
@@ -8,6 +8,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertFalse;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoVerticalClassMerging;
@@ -45,6 +46,7 @@
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
.addKeepRules(enumKeepRules.getKeepRules())
+ .enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
@@ -89,11 +91,13 @@
@NeverClassInline
static class B extends A {
+ @KeepConstantArguments
@NeverInline
void m(int x, MyEnum y) {
System.out.println("B.m(" + x + " : int, " + y.toString() + " : MyEnum)");
}
+ @KeepConstantArguments
@NeverInline
@Override
void m(MyEnum x, int y) {
diff --git a/src/test/java/com/android/tools/r8/examples/jdk16/PatternMatchingForInstenceof.java b/src/test/java/com/android/tools/r8/examples/jdk17/PatternMatchingForInstanceof.java
similarity index 78%
rename from src/test/java/com/android/tools/r8/examples/jdk16/PatternMatchingForInstenceof.java
rename to src/test/java/com/android/tools/r8/examples/jdk17/PatternMatchingForInstanceof.java
index 0cc4846..ec5f4cd 100644
--- a/src/test/java/com/android/tools/r8/examples/jdk16/PatternMatchingForInstenceof.java
+++ b/src/test/java/com/android/tools/r8/examples/jdk17/PatternMatchingForInstanceof.java
@@ -2,14 +2,14 @@
// 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.examples.jdk16;
+package com.android.tools.r8.examples.jdk17;
import com.android.tools.r8.examples.JavaExampleClassProxy;
import java.nio.file.Path;
-public class PatternMatchingForInstenceof {
+public class PatternMatchingForInstanceof {
- private static final String EXAMPLE_FILE = "examplesJava16/pattern_matching_for_instanceof";
+ private static final String EXAMPLE_FILE = "examplesJava17/pattern_matching_for_instanceof";
public static final JavaExampleClassProxy Main =
new JavaExampleClassProxy(EXAMPLE_FILE, "pattern_matching_for_instanceof/Main");
diff --git a/src/test/java/com/android/tools/r8/examples/jdk16/Records.java b/src/test/java/com/android/tools/r8/examples/jdk17/Records.java
similarity index 85%
rename from src/test/java/com/android/tools/r8/examples/jdk16/Records.java
rename to src/test/java/com/android/tools/r8/examples/jdk17/Records.java
index a8677b8..aa4581e 100644
--- a/src/test/java/com/android/tools/r8/examples/jdk16/Records.java
+++ b/src/test/java/com/android/tools/r8/examples/jdk17/Records.java
@@ -2,14 +2,14 @@
// 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.examples.jdk16;
+package com.android.tools.r8.examples.jdk17;
import com.android.tools.r8.examples.JavaExampleClassProxy;
import java.nio.file.Path;
public class Records {
- private static final String EXAMPLE_FILE = "examplesJava16/records";
+ private static final String EXAMPLE_FILE = "examplesJava17/records";
public static final JavaExampleClassProxy Main =
new JavaExampleClassProxy(EXAMPLE_FILE, "records/Main");
diff --git a/src/test/java/com/android/tools/r8/examples/jdk16/Sealed.java b/src/test/java/com/android/tools/r8/examples/jdk17/Sealed.java
similarity index 89%
rename from src/test/java/com/android/tools/r8/examples/jdk16/Sealed.java
rename to src/test/java/com/android/tools/r8/examples/jdk17/Sealed.java
index c995d13..8858316 100644
--- a/src/test/java/com/android/tools/r8/examples/jdk16/Sealed.java
+++ b/src/test/java/com/android/tools/r8/examples/jdk17/Sealed.java
@@ -2,14 +2,14 @@
// 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.examples.jdk16;
+package com.android.tools.r8.examples.jdk17;
import com.android.tools.r8.examples.JavaExampleClassProxy;
import java.nio.file.Path;
public class Sealed {
- private static final String EXAMPLE_FILE = "examplesJava16/sealed";
+ private static final String EXAMPLE_FILE = "examplesJava17/sealed";
public static final JavaExampleClassProxy Compiler =
new JavaExampleClassProxy(EXAMPLE_FILE, "sealed/Compiler");
diff --git a/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultTest.java b/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultTest.java
index 9c9926d..698de1b 100644
--- a/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultTest.java
@@ -63,12 +63,7 @@
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), Main.class)
- .applyIf(
- parameters.isCfRuntime()
- || parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M),
- r -> r.assertFailureWithErrorThatThrows(IllegalAccessError.class),
- // TODO(b/152199517): Should be illegal access for DEX.
- r -> r.assertSuccessWithOutputLines("I::foo"));
+ .assertFailureWithErrorThatThrows(IllegalAccessError.class);
}
@NoVerticalClassMerging
diff --git a/src/test/java/com/android/tools/r8/internal/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/internal/retrace/RetraceTests.java
index 441fd68..d283ee6 100644
--- a/src/test/java/com/android/tools/r8/internal/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/internal/retrace/RetraceTests.java
@@ -88,6 +88,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return null;
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -123,6 +128,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return null;
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -162,6 +172,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return null;
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/internal/retrace/stacktraces/RetraceInternalStackTraceForTest.java b/src/test/java/com/android/tools/r8/internal/retrace/stacktraces/RetraceInternalStackTraceForTest.java
index e4005de..978e33a 100644
--- a/src/test/java/com/android/tools/r8/internal/retrace/stacktraces/RetraceInternalStackTraceForTest.java
+++ b/src/test/java/com/android/tools/r8/internal/retrace/stacktraces/RetraceInternalStackTraceForTest.java
@@ -57,6 +57,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ throw new RuntimeException("Do not test");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
index 3c7307c..f392b1d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
@@ -4,9 +4,9 @@
package com.android.tools.r8.ir.optimize.callsites;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertTrue;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 9cb52da..cb494cb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -240,7 +240,7 @@
SingleTestRunResult<?> result =
testForR8(parameters.getBackend())
.addProgramClasses(classes)
- .enableProguardTestOptions()
+ .enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
.addKeepMainRule(main)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java
index 2b29cea..d9785fe 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize.classinliner.invalidroot;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
@@ -72,6 +73,7 @@
}
}
+ @KeepConstantArguments
private void neverReturnsNormallyExtra(String prefix, NeverReturnsNormally a) {
throw new RuntimeException("neverReturnsNormallyExtra(" +
prefix + ", " + (a == null ? "null" : a.foo()) + "): " + next());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress199142666.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress199142666.java
index e92f84a..7f77799 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress199142666.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress199142666.java
@@ -4,13 +4,13 @@
package com.android.tools.r8.ir.optimize.inliner;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
-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.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import java.io.IOException;
@@ -39,24 +39,21 @@
@Test
public void testInliningWhenInvalidCaller() throws Exception {
- R8TestRunResult run =
- testForR8(parameters.getBackend())
- .addProgramClasses(HasInvalidStaticCall.class)
- .addProgramClassFileData(getVirtualAAsStaticA())
- .addKeepMainRule(HasInvalidStaticCall.class)
- .addKeepMethodRules(StaticA.class, "void foo()")
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), HasInvalidStaticCall.class);
- if (parameters.getRuntime().asDex().getVm().getVersion().isDalvik()) {
- // TODO(b/199142666): We should not inline to provoke this error.
- run.assertFailureWithErrorThatMatches(
- containsString("invoke type does not match method type of"));
- } else {
- // TODO(b/199142666): We should consider if we want to inline in this case (there are no
- // verification errors)
- run.assertSuccessWithOutputLines("foochanged")
- .inspect(inspector -> ensureThisNumberOfCalls(inspector, HasInvalidStaticCall.class, 2));
- }
+ testForR8(parameters.getBackend())
+ .addProgramClasses(HasInvalidStaticCall.class)
+ .addProgramClassFileData(getVirtualAAsStaticA())
+ .addKeepMainRule(HasInvalidStaticCall.class)
+ .addKeepMethodRules(StaticA.class, "void foo()")
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), HasInvalidStaticCall.class)
+ // TODO(b/199142666): We should consider if we want to inline in this case (there are no
+ // verification errors)
+ .assertSuccessWithOutputLines("foochanged")
+ .inspect(
+ inspector -> {
+ ensureThisNumberOfCalls(inspector, HasInvalidStaticCall.class, 1);
+ ensureThisNumberThrowICCE(inspector, HasInvalidStaticCall.class, 1);
+ });
}
@Test
@@ -70,10 +67,13 @@
.run(parameters.getRuntime(), TargetHasInvalidStaticCall.class)
.assertSuccessWithEmptyOutput()
.inspect(
- inspector -> ensureThisNumberOfCalls(inspector, TargetHasInvalidStaticCall.class, 1));
+ inspector -> {
+ ensureThisNumberOfCalls(inspector, TargetHasInvalidStaticCall.class, 0);
+ ensureThisNumberThrowICCE(inspector, TargetHasInvalidStaticCall.class, 2);
+ });
}
- private void ensureThisNumberOfCalls(CodeInspector inspector, Class clazz, int fooCalls) {
+ private void ensureThisNumberOfCalls(CodeInspector inspector, Class<?> clazz, int fooCalls) {
long count =
inspector
.clazz(clazz)
@@ -85,6 +85,27 @@
assertEquals(fooCalls, count);
}
+ private void ensureThisNumberThrowICCE(CodeInspector inspector, Class<?> clazz, int expected) {
+ IRCode code = inspector.clazz(clazz).mainMethod().buildIR();
+ long count =
+ code.streamInstructions()
+ .filter(Instruction::isThrow)
+ .filter(
+ instruction ->
+ instruction
+ .getFirstOperand()
+ .isDefinedByInstructionSatisfying(
+ definition ->
+ definition.isNewInstance()
+ && definition
+ .asNewInstance()
+ .getType()
+ .getTypeName()
+ .equals(IncompatibleClassChangeError.class.getTypeName())))
+ .count();
+ assertEquals(expected, count);
+ }
+
static class StaticA {
public static void callFoo() {
StaticA.foo();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/NopInliningConstraintTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/NopInliningConstraintTest.java
index 3295203..dcc572b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/NopInliningConstraintTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/NopInliningConstraintTest.java
@@ -40,6 +40,8 @@
.addKeepMainRule(Main.class)
.enableAlwaysInliningAnnotations()
.enableInliningAnnotations()
+ // TODO(b/173398086): uniqueMethodWithName() does not work with argument removal.
+ .noMinification()
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfNullOrNotNullInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfNullOrNotNullInliningTest.java
index c5073c5..f75c4c8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfNullOrNotNullInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfNullOrNotNullInliningTest.java
@@ -10,6 +10,7 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverSingleCallerInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -51,6 +52,7 @@
.addProgramClasses(mainClass, TestMethods.class)
.addKeepMainRule(mainClass)
.apply(this::configure)
+ .enableConstantArgumentAnnotations()
.enableNeverSingleCallerInlineAnnotations()
.compile()
.inspect(this::inspect)
@@ -116,6 +118,7 @@
static class TestMethods {
+ @KeepConstantArguments
@NeverSingleCallerInline
static void simpleIfNullTest(Object o) {
if (o == null) {
@@ -135,6 +138,7 @@
System.out.println("!");
}
+ @KeepConstantArguments
@NeverSingleCallerInline
static void simpleIfBothNullTest(Object o1, Object o2) {
if (o1 == null && o2 == null) {
@@ -154,6 +158,7 @@
System.out.println("!");
}
+ @KeepConstantArguments
@NeverSingleCallerInline
static void simpleIfNotNullTest(Object o) {
if (o != null) {
@@ -173,6 +178,7 @@
System.out.println("!");
}
+ @KeepConstantArguments
@NeverSingleCallerInline
static void simpleIfBothNotNullTest(Object o1, Object o2) {
if (o1 != null && o2 != null) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfTrueOrFalseInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfTrueOrFalseInliningTest.java
index b7a7186..180b971 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfTrueOrFalseInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfTrueOrFalseInliningTest.java
@@ -10,6 +10,7 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverSingleCallerInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -51,6 +52,7 @@
.addProgramClasses(mainClass, TestMethods.class)
.addKeepMainRule(mainClass)
.apply(this::configure)
+ .enableConstantArgumentAnnotations()
.enableNeverSingleCallerInlineAnnotations()
.compile()
.inspect(this::inspect)
@@ -114,6 +116,7 @@
static class TestMethods {
+ @KeepConstantArguments
@NeverSingleCallerInline
static void simpleIfTrueTest(boolean b) {
if (b) {
@@ -133,6 +136,7 @@
System.out.println("!");
}
+ @KeepConstantArguments
@NeverSingleCallerInline
static void simpleIfBothTrueTest(boolean b1, boolean b2) {
if (b1 && b2) {
@@ -152,6 +156,7 @@
System.out.println("!");
}
+ @KeepConstantArguments
@NeverSingleCallerInline
static void simpleIfFalseTest(boolean b) {
if (!b) {
@@ -171,6 +176,7 @@
System.out.println("!");
}
+ @KeepConstantArguments
@NeverSingleCallerInline
static void simpleIfBothFalseTest(boolean b1, boolean b2) {
if (!b1 && !b2) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
index 791bbae..09aef4c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
@@ -177,6 +177,6 @@
MethodSubject barMethodSubject = testClass.uniqueMethodWithName("bar");
Iterator<InstructionSubject> barInstructionIterator =
barMethodSubject.iterateInstructions(InstructionSubject::isInstanceOf);
- assertEquals(4, Streams.stream(barInstructionIterator).count());
+ assertEquals(2, Streams.stream(barInstructionIterator).count());
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java
index 29de662..3a0b304 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -64,6 +65,7 @@
.enableInliningAnnotations()
.addInnerClasses(B134462736.class)
.addKeepMainRule(TestClass.class)
+ .enableConstantArgumentAnnotations()
.setMinApi(parameters.getApiLevel())
.noMinification()
.addOptionsModification(
@@ -78,11 +80,14 @@
}
public static class TestClass {
+
+ @KeepConstantArguments
@NeverInline
public void consumer(String arg1, String arg2) {
System.out.println(arg1 + " " + arg2);
}
+ @KeepConstantArguments
@NeverInline
public void method1(StringBuilder builder, String arg1, String arg2) {
builder.append(arg1);
@@ -90,6 +95,7 @@
consumer(builder.toString(), null);
}
+ @KeepConstantArguments
@NeverInline
public void method2(StringBuilder builder, String arg1, String arg2) {
builder.append(arg1);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java
index e384d9b..8cc3b8c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java
@@ -5,20 +5,23 @@
package com.android.tools.r8.ir.optimize.outliner.primitivetypes;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.KeepConstantArguments;
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 com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.BooleanUtils;
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.CodeMatchers;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -26,18 +29,30 @@
@RunWith(Parameterized.class)
public class PrimitiveTypesTest extends TestBase {
+ private final boolean enableArgumentPropagation;
private final TestParameters parameters;
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
+ @Parameterized.Parameters(name = "{1}, argument propagation: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
}
- public PrimitiveTypesTest(TestParameters parameters) {
+ public PrimitiveTypesTest(boolean keepConstantArguments, TestParameters parameters) {
+ this.enableArgumentPropagation = keepConstantArguments;
this.parameters = parameters;
}
private void validateOutlining(CodeInspector inspector, Class<?> testClass, String argumentType) {
+ boolean isStringBuilderOptimized =
+ enableArgumentPropagation
+ && parameters.isDexRuntime()
+ && (argumentType.equals("char") || argumentType.equals("boolean"));
+ if (isStringBuilderOptimized) {
+ assertEquals(1, inspector.allClasses().size());
+ return;
+ }
+
ClassSubject outlineClass =
inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(testClass, 0));
MethodSubject outline0Method =
@@ -56,6 +71,8 @@
public void runTest(Class<?> testClass, String argumentType, String expectedOutput)
throws Exception {
testForR8(parameters.getBackend())
+ .addConstantArgumentAnnotations()
+ .enableConstantArgumentAnnotations(!enableArgumentPropagation)
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.addProgramClasses(testClass)
@@ -120,6 +137,7 @@
static class TestClassBoolean {
+ @KeepConstantArguments
@NeverInline
public static String method1(boolean b) {
StringBuilder sb = new StringBuilder();
@@ -128,6 +146,7 @@
return sb.toString();
}
+ @KeepConstantArguments
@NeverInline
public static String method2(boolean b) {
StringBuilder sb = new StringBuilder();
@@ -144,6 +163,7 @@
static class TestClassByte {
+ @KeepConstantArguments
@NeverInline
public static String method1(byte b) {
MyStringBuilder sb = new MyStringBuilder();
@@ -152,6 +172,7 @@
return sb.toString();
}
+ @KeepConstantArguments
@NeverInline
public static String method2(byte b) {
MyStringBuilder sb = new MyStringBuilder();
@@ -168,6 +189,7 @@
static class TestClassShort {
+ @KeepConstantArguments
@NeverInline
public static String method1(short s) {
MyStringBuilder sb = new MyStringBuilder();
@@ -176,6 +198,7 @@
return sb.toString();
}
+ @KeepConstantArguments
@NeverInline
public static String method2(short s) {
MyStringBuilder sb = new MyStringBuilder();
@@ -192,6 +215,7 @@
static class TestClassChar {
+ @KeepConstantArguments
@NeverInline
public static String method1(char c) {
StringBuilder sb = new StringBuilder();
@@ -200,6 +224,7 @@
return sb.toString();
}
+ @KeepConstantArguments
@NeverInline
public static String method2(char c) {
StringBuilder sb = new StringBuilder();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index 5a60806..a1b6f08 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -410,6 +410,7 @@
R8TestRunResult result =
testForR8(parameters.getBackend())
.addProgramClasses(classes)
+ .enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.addKeepMainRule(main)
.allowAccessModification()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/dualcallinline/Candidate.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/dualcallinline/Candidate.java
index 9ccd630..4733ab3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/dualcallinline/Candidate.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/dualcallinline/Candidate.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize.staticizer.dualcallinline;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
public class Candidate {
@@ -12,6 +13,7 @@
return bar("Candidate::foo()");
}
+ @KeepConstantArguments
@NeverInline
public String bar(String other) {
return "Candidate::bar(" + other + ")";
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
index 5c9c9a3..6c47b03 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
@@ -58,7 +58,6 @@
// This test wants to check if compile-time computation is not applied to non-null,
// non-constant value. In a simple test setting, call-site optimization knows the argument is
// always a non-null, specific constant, but that is beyond the scope of this test.
- assert !options.callSiteOptimizationOptions().isConstantPropagationEnabled();
}
private void test(SingleTestRunResult result, int expectedStringIsEmptyCount) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
index 0125189..ee595ce 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
@@ -11,7 +11,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -25,15 +26,15 @@
@RunWith(Parameterized.class)
public class ParameterRewritingTest extends TestBase {
- private final Backend backend;
+ private final TestParameters parameters;
- @Parameters(name = "Backend: {0}")
- public static Backend[] data() {
- return ToolHelper.getBackends();
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public ParameterRewritingTest(Backend backend) {
- this.backend = backend;
+ public ParameterRewritingTest(TestParameters parameters) {
+ this.parameters = parameters;
}
@Test
@@ -49,14 +50,15 @@
testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expected);
CodeInspector inspector =
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addInnerClasses(ParameterRewritingTest.class)
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
.addOptionsModification(options -> options.enableClassInlining = false)
.noMinification()
- .run(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expected)
.inspector();
@@ -64,7 +66,7 @@
MethodSubject createStaticMethodSubject =
factoryClassSubject.uniqueMethodWithName("createStatic");
assertThat(createStaticMethodSubject, isPresent());
- assertEquals(1, createStaticMethodSubject.getMethod().getReference().proto.parameters.size());
+ assertEquals(1, createStaticMethodSubject.getMethod().getParameters().size());
for (int i = 1; i <= 3; ++i) {
String createStaticWithUnusedMethodName = "createStaticWithUnused" + i;
@@ -73,8 +75,8 @@
assertThat(createStaticWithUnusedMethodSubject, isPresent());
DexMethod method = createStaticWithUnusedMethodSubject.getMethod().getReference();
- assertEquals(1, method.proto.parameters.size());
- assertEquals("java.lang.String", method.proto.parameters.toString());
+ assertEquals(1, method.getParameters().size());
+ assertEquals("java.lang.String", method.getParameters().toString());
}
MethodSubject createStaticWithUnusedMethodSubject =
@@ -82,9 +84,9 @@
assertThat(createStaticWithUnusedMethodSubject, isPresent());
DexMethod method = createStaticWithUnusedMethodSubject.getMethod().getReference();
- assertEquals(3, method.proto.parameters.size());
+ assertEquals(3, method.getParameters().size());
assertEquals(
- "java.lang.String java.lang.String java.lang.String", method.proto.parameters.toString());
+ "java.lang.String java.lang.String java.lang.String", method.getParameters().toString());
assertThat(inspector.clazz(Uninstantiated.class), not(isPresent()));
}
@@ -92,26 +94,39 @@
static class TestClass {
public static void main(String[] args) {
- Object obj1 = Factory.createStatic(null, "Factory.createStatic()");
+ Object obj1 = Factory.createStatic(null, asNonConstantString("Factory.createStatic()"));
System.out.println(" -> " + obj1);
Object obj2 =
- Factory.createStaticWithUnused1(new Object(), null, "Factory.createStaticWithUnused1()");
+ Factory.createStaticWithUnused1(
+ new Object(), null, asNonConstantString("Factory.createStaticWithUnused1()"));
System.out.println(" -> " + obj2);
Object obj3 =
- Factory.createStaticWithUnused2(null, new Object(), "Factory.createStaticWithUnused2()");
+ Factory.createStaticWithUnused2(
+ null, new Object(), asNonConstantString("Factory.createStaticWithUnused2()"));
System.out.println(" -> " + obj3);
Object obj4 =
- Factory.createStaticWithUnused3(null, "Factory.createStaticWithUnused3()", new Object());
+ Factory.createStaticWithUnused3(
+ null, asNonConstantString("Factory.createStaticWithUnused3()"), new Object());
System.out.println(" -> " + obj4);
Object obj5 =
Factory.createStaticWithUnused4(
- "Factory", new Object(), null, ".", new Object(), null, "createStaticWithUnused4()");
+ asNonConstantString("Factory"),
+ new Object(),
+ null,
+ asNonConstantString("."),
+ new Object(),
+ null,
+ asNonConstantString("createStaticWithUnused4()"));
System.out.println(" -> " + obj5);
}
+
+ public static String asNonConstantString(String string) {
+ return System.currentTimeMillis() > 0 ? string : null;
+ }
}
@NoHorizontalClassMerging
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
index 339bbbc..663bcf6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
@@ -158,12 +158,14 @@
static class TestClass {
public static void main(String[] args) {
- testRemoveStaticFromStart(null, "Hello", " world!");
- testRemoveStaticFromMiddle("Hello", null, " world!");
- testRemoveStaticFromEnd("Hello", " world!", null);
- new TestClass().testRemoveVirtualFromStart(null, "Hello", " world!");
- new TestClass().testRemoveVirtualFromMiddle("Hello", null, " world!");
- new TestClass().testRemoveVirtualFromEnd("Hello", " world!", null);
+ String hello = System.currentTimeMillis() > 0 ? "Hello" : null;
+ String world = System.currentTimeMillis() > 0 ? " world!" : null;
+ testRemoveStaticFromStart(null, hello, world);
+ testRemoveStaticFromMiddle(hello, null, world);
+ testRemoveStaticFromEnd(hello, world, null);
+ new TestClass().testRemoveVirtualFromStart(null, hello, world);
+ new TestClass().testRemoveVirtualFromMiddle(hello, null, world);
+ new TestClass().testRemoveVirtualFromEnd(hello, world, null);
}
@KeepConstantArguments
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java
index b7ddd9e..ed2d01d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java
@@ -8,6 +8,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
@@ -60,6 +61,7 @@
testForR8(parameters.getBackend())
.addInnerClasses(PrivateInstanceMethodCollisionTest.class)
.addKeepMainRule(TestClass.class)
+ .enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.minification(minification)
@@ -109,11 +111,13 @@
@NeverClassInline
static class A {
+ @KeepConstantArguments
@NeverInline
private void foo(String used) {
System.out.println("A#foo(" + used + ")");
}
+ @KeepConstantArguments
@NeverInline
void foo(String used, Object unused) {
System.out.println("A#foo(" + used + ", Object)");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
index 7cf6193..2f3e14f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
@@ -8,6 +8,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.KeepUnusedArguments;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
@@ -66,6 +67,7 @@
.addKeepAttributes("RuntimeVisibleParameterAnnotations")
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
+ .enableConstantArgumentAnnotations()
.enableUnusedArgumentAnnotations(keepUnusedArguments)
// TODO(b/123060011): Mapping not working in presence of unused argument removal.
.minification(keepUnusedArguments)
@@ -152,6 +154,7 @@
new TestClass().testRemoveVirtualFromEnd("Hello", " world!", null);
}
+ @KeepConstantArguments
@KeepUnusedArguments
@NeverInline
static void testRemoveStaticFromStart(
@@ -159,6 +162,7 @@
System.out.println(used + otherUsed);
}
+ @KeepConstantArguments
@KeepUnusedArguments
@NeverInline
static void testRemoveStaticFromMiddle(
@@ -166,6 +170,7 @@
System.out.println(used + otherUsed);
}
+ @KeepConstantArguments
@KeepUnusedArguments
@NeverInline
static void testRemoveStaticFromEnd(
@@ -173,6 +178,7 @@
System.out.println(used + otherUsed);
}
+ @KeepConstantArguments
@KeepUnusedArguments
@NeverInline
void testRemoveVirtualFromStart(
@@ -180,6 +186,7 @@
System.out.println(used + otherUsed);
}
+ @KeepConstantArguments
@KeepUnusedArguments
@NeverInline
void testRemoveVirtualFromMiddle(
@@ -187,6 +194,7 @@
System.out.println(used + otherUsed);
}
+ @KeepConstantArguments
@KeepUnusedArguments
@NeverInline
void testRemoveVirtualFromEnd(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionMappingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionMappingTest.java
index d8a7d6b..7d9a941 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionMappingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionMappingTest.java
@@ -10,6 +10,7 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestBase;
@@ -50,6 +51,7 @@
.addProgramClasses(Main.class)
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(Main.class)
+ .enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.addKeepAttributeLineNumberTable()
.run(parameters.getRuntime(), Main.class)
@@ -92,6 +94,7 @@
System.out.println("test with unused");
}
+ @KeepConstantArguments
@NeverInline
public static void test(String used, String unused) {
System.out.println("test with used: " + used);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsDoubleTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsDoubleTest.java
index 95d571d..282371b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsDoubleTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsDoubleTest.java
@@ -6,7 +6,9 @@
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import java.util.Collection;
import org.junit.Assert;
@@ -27,16 +29,19 @@
}
static class TestClass {
+ @KeepConstantArguments
@NeverInline
public static double a(double a) {
return a;
}
+ @KeepConstantArguments
@NeverInline
public static double a(double a, double b) {
return a;
}
+ @KeepConstantArguments
@NeverInline
public static double a(double a, double b, double c) {
return a;
@@ -50,6 +55,12 @@
}
@Override
+ public void configure(R8FullTestBuilder builder) {
+ super.configure(builder);
+ builder.enableConstantArgumentAnnotations();
+ }
+
+ @Override
public Class<?> getTestClass() {
return TestClass.class;
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsIntTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsIntTest.java
index 06f665d..80309b2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsIntTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsIntTest.java
@@ -6,7 +6,9 @@
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.google.common.collect.ImmutableSet;
import java.util.Collection;
@@ -31,22 +33,26 @@
static class TestClass {
+ @KeepConstantArguments
@NeverInline
public static int a(int a) {
return a;
}
+ @KeepConstantArguments
@NeverInline
public static int a(int a, int b) {
return a;
}
+ @KeepConstantArguments
@NeverInline
public static int iinc(int a, int b) {
b++;
return a;
}
+ @KeepConstantArguments
@NeverInline
public static int a(int a, int b, int c) {
return a;
@@ -61,6 +67,12 @@
}
@Override
+ public void configure(R8FullTestBuilder builder) {
+ super.configure(builder);
+ builder.enableConstantArgumentAnnotations();
+ }
+
+ @Override
public Class<?> getTestClass() {
return TestClass.class;
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsLongTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsLongTest.java
index 6f44c8d..0dba75b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsLongTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsLongTest.java
@@ -6,7 +6,9 @@
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import java.util.Collection;
import org.junit.Assert;
@@ -27,16 +29,20 @@
}
static class TestClass {
+
+ @KeepConstantArguments
@NeverInline
public static long a(long a) {
return a;
}
+ @KeepConstantArguments
@NeverInline
public static long a(long a, long b) {
return a;
}
+ @KeepConstantArguments
@NeverInline
public static long a(long a, long b, long c) {
return a;
@@ -50,6 +56,12 @@
}
@Override
+ public void configure(R8FullTestBuilder builder) {
+ super.configure(builder);
+ builder.enableConstantArgumentAnnotations();
+ }
+
+ @Override
public Class<?> getTestClass() {
return TestClass.class;
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsMixedTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsMixedTest.java
index 717adac..dd63e78 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsMixedTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsMixedTest.java
@@ -6,7 +6,9 @@
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import java.util.Collection;
import org.junit.Assert;
@@ -27,6 +29,8 @@
}
static class TestClass {
+
+ @KeepConstantArguments
@NeverInline
public static int a(int a, Object b) {
return a;
@@ -37,6 +41,7 @@
return a;
}
+ @KeepConstantArguments
@NeverInline
public static int a(int a, Object b, int c) {
return c;
@@ -56,6 +61,12 @@
}
@Override
+ public void configure(R8FullTestBuilder builder) {
+ super.configure(builder);
+ builder.enableConstantArgumentAnnotations();
+ }
+
+ @Override
public Class<?> getTestClass() {
return TestClass.class;
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsMixedWidthTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsMixedWidthTest.java
index e10793d..7ed9021 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsMixedWidthTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsMixedWidthTest.java
@@ -6,7 +6,9 @@
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import java.util.Collection;
import org.junit.Assert;
@@ -27,21 +29,26 @@
}
static class TestClass {
+
+ @KeepConstantArguments
@NeverInline
public static int a(int a, long b) {
return a;
}
+ @KeepConstantArguments
@NeverInline
public static long a(long a, int b) {
return a;
}
+ @KeepConstantArguments
@NeverInline
public static int a(int a, long b, int c) {
return c;
}
+ @KeepConstantArguments
@NeverInline
public static long a(long a, int b, long c) {
return c;
@@ -56,6 +63,12 @@
}
@Override
+ public void configure(R8FullTestBuilder builder) {
+ super.configure(builder);
+ builder.enableConstantArgumentAnnotations();
+ }
+
+ @Override
public Class<?> getTestClass() {
return TestClass.class;
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 7f41f05..beb87b8 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -95,6 +95,8 @@
private boolean isInterface = false;
private String access = "public";
+ private final List<String> pendingAnnotations = new ArrayList<>();
+
private ClassBuilder(String name) {
this(name, "java/lang/Object");
}
@@ -124,6 +126,13 @@
return addMethod("public abstract", name, argumentTypes, returnType);
}
+ public void addRuntimeInvisibleAnnotation(String typeName) {
+ pendingAnnotations.add(
+ StringUtils.lines(
+ ".annotation invisible " + DescriptorUtils.javaTypeToDescriptor(typeName),
+ ".end annotation"));
+ }
+
public MethodSignature addFinalMethod(
String name,
List<String> argumentTypes,
@@ -221,6 +230,10 @@
.append(StringUtils.join("", argumentTypes, BraceType.PARENS))
.append(returnType)
.append("\n");
+ if (!pendingAnnotations.isEmpty()) {
+ pendingAnnotations.forEach(builder::append);
+ pendingAnnotations.clear();
+ }
for (String line : lines) {
builder.append(line).append("\n");
}
diff --git a/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java b/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java
index 345f198..95cf5b2 100644
--- a/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java
@@ -8,7 +8,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfRuntime;
-import com.android.tools.r8.examples.jdk16.PatternMatchingForInstenceof;
+import com.android.tools.r8.examples.jdk17.PatternMatchingForInstanceof;
import com.android.tools.r8.utils.InternalOptions.TestingOptions;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
@@ -26,15 +26,15 @@
private static List<String> EXPECTED = ImmutableList.of("Hello, world!");
- private static final Path JAR = PatternMatchingForInstenceof.jar();
- private static final String MAIN = PatternMatchingForInstenceof.Main.typeName();
+ private static final Path JAR = PatternMatchingForInstanceof.jar();
+ private static final String MAIN = PatternMatchingForInstanceof.Main.typeName();
@Parameters(name = "{0}")
public static List<Object[]> data() {
- // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
return buildParameters(
getTestParameters()
- .withCustomRuntime(CfRuntime.getCheckedInJdk16())
+ .withCustomRuntime(CfRuntime.getCheckedInJdk17())
.withDexRuntimes()
.withAllApiLevelsAlsoForCf()
.build());
@@ -45,7 +45,6 @@
if (parameters.isCfRuntime()) {
testForJvm()
.addRunClasspathFiles(JAR)
- .enablePreview()
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines(EXPECTED);
}
@@ -71,7 +70,6 @@
} else {
testForJvm()
.addRunClasspathFiles(builder.compile().writeToZip())
- .enablePreview()
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines(EXPECTED);
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index 495eae6..2516b74 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -262,7 +262,7 @@
}
}
- private void checkMethodPresenceInInput(
+ protected void checkMethodPresenceInInput(
String className, MethodSignature methodSignature, boolean isPresent) {
boolean foundMethod = AsmUtils.doesMethodExist(classpath, className,
methodSignature.name, methodSignature.toDescriptor());
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index 3d872c4..46adea0 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
@@ -28,7 +29,6 @@
import java.util.Collection;
import java.util.Collections;
import org.junit.Assert;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -111,7 +111,7 @@
} else {
assertTrue(fieldSubject.getField().accessFlags.isPrivate());
checkMethodIsKept(outerClass, getterAccessor);
- checkMethodIsKept(outerClass, setterAccessor);
+ checkMethodIsRemoved(outerClass, setterAccessor);
}
});
}
@@ -146,7 +146,7 @@
assertTrue(fieldSubject.getField().accessFlags.isPrivate());
checkMethodIsKept(outerClass, getterAccessor);
- checkMethodIsKept(outerClass, setterAccessor);
+ checkMethodIsRemoved(outerClass, setterAccessor);
}
});
}
@@ -180,7 +180,7 @@
} else {
assertTrue(fieldSubject.getField().accessFlags.isPrivate());
checkMethodIsKept(outerClass, getterAccessor);
- checkMethodIsKept(outerClass, setterAccessor);
+ checkMethodIsRemoved(outerClass, setterAccessor);
}
});
}
@@ -214,7 +214,7 @@
} else {
assertTrue(fieldSubject.getField().accessFlags.isPrivate());
checkMethodIsKept(outerClass, getterAccessor);
- checkMethodIsKept(outerClass, setterAccessor);
+ checkMethodIsRemoved(outerClass, setterAccessor);
}
});
}
@@ -247,7 +247,7 @@
} else {
assertTrue(fieldSubject.getField().accessFlags.isPrivate());
checkMethodIsKept(outerClass, getterAccessor);
- checkMethodIsKept(outerClass, setterAccessor);
+ checkMethodIsRemoved(outerClass, setterAccessor);
}
});
}
@@ -340,7 +340,6 @@
}
@Test
- @Ignore("b/74103342")
public void testAccessorFromPrivate() throws Exception {
TestKotlinCompanionClass testedClass = ACCESSOR_COMPANION_PROPERTY_CLASS;
String mainClass = addMainToClasspath("accessors.AccessorKt",
@@ -348,28 +347,8 @@
runTest("accessors", mainClass)
.inspect(
inspector -> {
- ClassSubject outerClass =
- checkClassIsKept(inspector, testedClass.getOuterClassName());
- ClassSubject companionClass = checkClassIsKept(inspector, testedClass.getClassName());
- String propertyName = "property";
- FieldSubject fieldSubject =
- checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
- assertTrue(fieldSubject.getField().accessFlags.isStatic());
-
- // We cannot inline the getter because we don't know if NPE is preserved.
- MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
- checkMethodIsKept(companionClass, getter);
-
- // We should always inline the static accessor method.
- MemberNaming.MethodSignature getterAccessor =
- testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER);
- checkMethodIsRemoved(companionClass, getterAccessor);
-
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- } else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- }
+ checkClassIsRemoved(inspector, testedClass.getOuterClassName());
+ checkClassIsRemoved(inspector, testedClass.getClassName());
});
}
@@ -389,7 +368,6 @@
}
@Test
- @Ignore("b/74103342")
public void testPrivatePropertyAccessorForInnerClassCanBeInlined() throws Exception {
TestKotlinClass testedClass = PROPERTY_ACCESS_FOR_INNER_CLASS;
String mainClass = addMainToClasspath(testedClass.className + "Kt",
@@ -397,6 +375,13 @@
runTest("accessors", mainClass)
.inspect(
inspector -> {
+ if (allowAccessModification
+ && (testParameters.isCfRuntime()
+ || !kotlinParameters.is(KOTLINC_1_5_0, KotlinTargetVersion.JAVA_8))) {
+ checkClassIsRemoved(inspector, testedClass.getClassName());
+ return;
+ }
+
ClassSubject classSubject = checkClassIsKept(inspector, testedClass.getClassName());
String propertyName = "privateProp";
@@ -415,13 +400,12 @@
} else {
assertTrue(fieldSubject.getField().accessFlags.isPrivate());
checkMethodIsKept(classSubject, getterAccessor);
- checkMethodIsKept(classSubject, setterAccessor);
+ checkMethodIsRemoved(classSubject, setterAccessor);
}
});
}
@Test
- @Ignore("b/74103342")
public void testPrivateLateInitPropertyAccessorForInnerClassCanBeInlined() throws Exception {
TestKotlinClass testedClass = PROPERTY_ACCESS_FOR_INNER_CLASS;
String mainClass = addMainToClasspath(testedClass.className + "Kt",
@@ -429,31 +413,15 @@
runTest("accessors", mainClass)
.inspect(
inspector -> {
- ClassSubject classSubject = checkClassIsKept(inspector, testedClass.getClassName());
-
- String propertyName = "privateLateInitProp";
- FieldSubject fieldSubject =
- checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName);
- assertFalse(fieldSubject.getField().accessFlags.isStatic());
-
- MemberNaming.MethodSignature getterAccessor =
- testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER);
- MemberNaming.MethodSignature setterAccessor =
- testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_INNER);
- if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
- checkMethodIsRemoved(classSubject, getterAccessor);
- checkMethodIsRemoved(classSubject, setterAccessor);
+ if (kotlinc.getCompilerVersion() == KOTLINC_1_5_0 && testParameters.isDexRuntime()) {
+ checkClassIsKept(inspector, testedClass.getClassName());
} else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
- checkMethodIsKept(classSubject, getterAccessor);
- checkMethodIsKept(classSubject, setterAccessor);
+ checkClassIsRemoved(inspector, testedClass.getClassName());
}
});
}
@Test
- @Ignore("b/74103342")
public void testAccessorForLambdaIsRemovedWhenNotUsed() throws Exception {
TestKotlinClass testedClass = PROPERTY_ACCESS_FOR_LAMBDA_CLASS;
String mainClass = addMainToClasspath(testedClass.className + "Kt",
@@ -461,21 +429,11 @@
runTest("accessors", mainClass)
.inspect(
inspector -> {
- ClassSubject classSubject = checkClassIsKept(inspector, testedClass.getClassName());
- String propertyName = "property";
-
- MemberNaming.MethodSignature getterAccessor =
- testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_LAMBDA);
- MemberNaming.MethodSignature setterAccessor =
- testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_LAMBDA);
-
- checkMethodIsRemoved(classSubject, getterAccessor);
- checkMethodIsRemoved(classSubject, setterAccessor);
+ checkClassIsRemoved(inspector, testedClass.getClassName());
});
}
@Test
- @Ignore("b/74103342")
public void testAccessorForLambdaCanBeInlined() throws Exception {
TestKotlinClass testedClass = PROPERTY_ACCESS_FOR_LAMBDA_CLASS;
String mainClass = addMainToClasspath(testedClass.className + "Kt",
@@ -483,16 +441,25 @@
runTest("accessors", mainClass)
.inspect(
inspector -> {
+ if (allowAccessModification) {
+ checkClassIsRemoved(inspector, testedClass.getClassName());
+ return;
+ }
+
ClassSubject classSubject = checkClassIsKept(inspector, testedClass.getClassName());
String propertyName = "property";
FieldSubject fieldSubject =
checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName);
assertFalse(fieldSubject.getField().accessFlags.isStatic());
+ AccessorKind accessorKind =
+ kotlinc.getCompilerVersion() == KOTLINC_1_5_0
+ ? AccessorKind.FROM_INNER
+ : AccessorKind.FROM_LAMBDA;
MemberNaming.MethodSignature getterAccessor =
- testedClass.getGetterAccessorForProperty(propertyName, AccessorKind.FROM_LAMBDA);
+ testedClass.getGetterAccessorForProperty(propertyName, accessorKind);
MemberNaming.MethodSignature setterAccessor =
- testedClass.getSetterAccessorForProperty(propertyName, AccessorKind.FROM_LAMBDA);
+ testedClass.getSetterAccessorForProperty(propertyName, accessorKind);
if (allowAccessModification) {
assertTrue(fieldSubject.getField().accessFlags.isPublic());
checkMethodIsRemoved(classSubject, getterAccessor);
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
index 5de5dbd..4457386 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
@@ -54,9 +54,7 @@
"intrinsics",
"intrinsics.IntrinsicsKt",
testBuilder ->
- testBuilder
- .addKeepRules(extraRules)
- .noHorizontalClassMerging(Intrinsics.class))
+ testBuilder.addKeepRules(extraRules).noHorizontalClassMerging(Intrinsics.class))
.inspect(
inspector -> {
ClassSubject intrinsicsClass =
@@ -75,8 +73,7 @@
"checkParameterIsNotNull",
"void",
Lists.newArrayList("java.lang.Object", "java.lang.String")),
- // TODO(b/179866251): This is also different on CF
- !allowAccessModification || testParameters.isCfRuntime())
+ !allowAccessModification)
.build());
});
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index 65822f7..c605951 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -253,9 +253,7 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
ClassSubject classSubject =
@@ -272,7 +270,7 @@
} else {
assertTrue(fieldSubject.getField().accessFlags.isPrivate());
checkMethodIsKept(classSubject, getter);
- checkMethodIsKept(classSubject, setter);
+ checkMethodIsRemoved(classSubject, setter);
}
});
}
@@ -650,9 +648,7 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
@@ -671,7 +667,7 @@
checkMethodIsRemoved(objectClass, setter);
} else {
checkMethodIsKept(objectClass, getter);
- checkMethodIsKept(objectClass, setter);
+ checkMethodIsRemoved(objectClass, setter);
}
if (allowAccessModification) {
@@ -724,9 +720,7 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
@@ -746,7 +740,7 @@
checkMethodIsRemoved(objectClass, setter);
} else {
checkMethodIsKept(objectClass, getter);
- checkMethodIsKept(objectClass, setter);
+ checkMethodIsRemoved(objectClass, setter);
}
if (allowAccessModification) {
@@ -765,9 +759,7 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
@@ -787,7 +779,7 @@
checkMethodIsRemoved(objectClass, setter);
} else {
checkMethodIsKept(objectClass, getter);
- checkMethodIsKept(objectClass, setter);
+ checkMethodIsRemoved(objectClass, setter);
}
if (allowAccessModification) {
@@ -896,9 +888,7 @@
runTest(
PACKAGE_NAME,
mainClass,
- testBuilder ->
- testBuilder
- .addOptionsModification(disableAggressiveClassOptimizations))
+ testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
.inspect(
inspector -> {
ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
@@ -916,7 +906,7 @@
} else {
assertTrue(fieldSubject.getField().accessFlags.isPrivate());
checkMethodIsKept(objectClass, getter);
- checkMethodIsKept(objectClass, setter);
+ checkMethodIsRemoved(objectClass, setter);
}
});
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
index e83626b..091a8b4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
@@ -47,7 +48,13 @@
ImmutableList.of("java.util.Collection", STRING, STRING, "java.lang.Integer"));
final String mainClassName = ex1.getClassName();
- final String extraRules = neverInlineMethod(mainClassName, testMethodSignature);
+ final String extraRules =
+ StringUtils.lines(
+ neverInlineMethod(mainClassName, testMethodSignature),
+ // TODO(b/173398086): uniqueMethodWithName() does not work with argument removal.
+ "-keepclassmembers,allowoptimization,allowshrinking class non_null.Example1Kt {",
+ " *** forMakeAndModel(...);",
+ "}");
runTest(
FOLDER,
mainClassName,
@@ -56,7 +63,11 @@
inspector -> {
ClassSubject clazz = checkClassIsKept(inspector, ex1.getClassName());
- MethodSubject testMethod = checkMethodIsKept(clazz, testMethodSignature);
+ // Verify forMakeAndModel(...) is present in the input.
+ checkMethodPresenceInInput(clazz.getOriginalName(), testMethodSignature, true);
+
+ // Find forMakeAndModel(...) after parameter removal.
+ MethodSubject testMethod = clazz.uniqueMethodWithName(testMethodSignature.name);
long ifzCount =
testMethod.streamInstructions().filter(i -> i.isIfEqz() || i.isIfNez()).count();
long paramNullCheckCount =
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinification.java b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
index aeb142f..3cf01e0 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinification.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
@@ -102,7 +102,7 @@
class Main {
public static void main(String[] args) {
- Enum e = Enum.valueOf("VALUE1");
+ Enum e = Enum.valueOf(System.currentTimeMillis() > 0 ? "VALUE1" : null);
System.out.print(e);
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
index 3c52044..c129553 100644
--- a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
@@ -97,7 +97,12 @@
getKotlinAnnotationJar(kotlinc))
.addProgramFiles(getJavaJarFile(FOLDER))
.addKeepMainRule(mainClassName)
+ .addKeepRules(
+ "-keepconstantarguments class kotlin.jvm.internal.Intrinsics {",
+ " *** checkParameterIsNotNull(...);",
+ "}")
.allowDiagnosticWarningMessages()
+ .enableProguardTestOptions()
.minification(minification)
.compile()
.assertAllWarningMessagesMatch(
@@ -158,7 +163,10 @@
"-neverclassinline class **." + targetClassName,
"-" + NoVerticalClassMergingRule.RULE_NAME + " class **." + targetClassName,
"-" + NoHorizontalClassMergingRule.RULE_NAME + " class **." + targetClassName,
- "-neverinline class **." + targetClassName + " { <methods>; }"))
+ "-neverinline class **." + targetClassName + " { <methods>; }",
+ "-keepconstantarguments class kotlin.jvm.internal.Intrinsics {",
+ " *** checkParameterIsNotNull(...);",
+ "}"))
.allowDiagnosticWarningMessages()
.minification(minification)
.compile()
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java
index 1d188fb..a689715 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java
@@ -204,8 +204,7 @@
compileResult
.run(parameters.getRuntime(), swappingMain)
- .assertFailureWithErrorThatThrows(IllegalAccessError.class)
- .assertFailureWithErrorThatMatches(containsString(getMethodSignature("X", "y")));
+ .assertFailureWithErrorThatThrows(IllegalAccessError.class);
CodeInspector codeInspector = compileResult.inspector();
ClassSubject base = codeInspector.clazz("A");
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
index 3d9645d..6c1175c 100644
--- a/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
@@ -444,10 +444,12 @@
ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
assertEquals(0, javaOutput.exitCode);
- List<String> pgConfigs = ImmutableList.of(
- keepMainProguardConfiguration(CLASS_NAME),
- "-overloadaggressively",
- "-dontshrink");
+ List<String> pgConfigs =
+ ImmutableList.of(
+ keepMainProguardConfiguration(CLASS_NAME),
+ "-overloadaggressively",
+ "-dontoptimize",
+ "-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null, backend);
CodeInspector codeInspector = new CodeInspector(app);
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CheckNotZeroMethodWithArgumentRemovalTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CheckNotZeroMethodWithArgumentRemovalTest.java
new file mode 100644
index 0000000..be25553
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CheckNotZeroMethodWithArgumentRemovalTest.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2021, 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.optimize.argumentpropagation;
+
+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.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.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CheckNotZeroMethodWithArgumentRemovalTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+ .enableInliningAnnotations()
+ // TODO(b/173398086): uniqueMethodWithName() does not work with argument removal.
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject checkNotNullSubject =
+ mainClassSubject.uniqueMethodWithName("checkNotNull");
+ assertThat(checkNotNullSubject, isPresent());
+ // TODO(b/199864962): Allow parameter removal from check-not-null classified methods.
+ assertEquals(2, checkNotNullSubject.getProgramMethod().getReference().getArity());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ checkNotNull(System.currentTimeMillis() > 0 ? MyEnum.A : null, "x");
+ checkNotNull(System.currentTimeMillis() > 0 ? new Object() : null, "x");
+ }
+
+ @NeverInline
+ static void checkNotNull(Object o, String name) {
+ if (o == null) {
+ throw new NullPointerException("Expected not null, but " + name + " was null");
+ }
+ }
+ }
+
+ enum MyEnum {
+ A,
+ B
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/MixedArgumentRemovalAndEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/MixedArgumentRemovalAndEnumUnboxingTest.java
index ad4c08a..58f2423 100644
--- a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/MixedArgumentRemovalAndEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/MixedArgumentRemovalAndEnumUnboxingTest.java
@@ -84,7 +84,7 @@
}
@NeverInline
- static void test(Object alwaysNull, MyEnum alwaysA, Object alsoAlwaysNull, MyEnum alwaysB) {
+ static void test(Main alwaysNull, MyEnum alwaysA, Main alsoAlwaysNull, MyEnum alwaysB) {
if (alwaysNull == null && alsoAlwaysNull == null) {
System.out.println(alwaysA.name());
System.out.println(alwaysB.name());
diff --git a/src/test/java/com/android/tools/r8/regress/b165825758/Regress165825758Test.java b/src/test/java/com/android/tools/r8/regress/b165825758/Regress165825758Test.java
index bf95231..41d6a49 100644
--- a/src/test/java/com/android/tools/r8/regress/b165825758/Regress165825758Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b165825758/Regress165825758Test.java
@@ -9,6 +9,7 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverReprocessMethod;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -35,7 +36,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public Regress165825758Test(TestParameters parameters) {
@@ -57,6 +58,7 @@
.addInnerClasses(Regress165825758Test.class)
.addKeepMainRule(TestClass.class)
.addKeepClassRules(A.class)
+ .enableNeverReprocessMethodAnnotations()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED)
@@ -99,6 +101,7 @@
static class A {
@NeverInline
+ @NeverReprocessMethod
void synchronizedMethod() {
synchronized (this) {
TestClass.throwNpe();
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java
index d9833ae..8c754e5 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java
@@ -8,6 +8,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -36,6 +37,7 @@
.addKeepClassRules(NonPublicKeptAnnotation.class)
.addKeepRuntimeVisibleParameterAnnotations()
.apply(this::configureRepackaging)
+ .enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
@@ -67,6 +69,7 @@
public static class IneligibleForRepackaging {
+ @KeepConstantArguments
@NeverInline
public static void greet(@NonPublicKeptAnnotation String greeting) {
System.out.println(greeting);
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleResolutionWithFailingDispatchTest.java b/src/test/java/com/android/tools/r8/resolution/SingleResolutionWithFailingDispatchTest.java
new file mode 100644
index 0000000..a5acf15
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/SingleResolutionWithFailingDispatchTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2021, 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.resolution;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestRuntime.CfVm;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SingleResolutionWithFailingDispatchTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class, I.class, J.class)
+ .addProgramClassFileData(getProgramClassFileData())
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::inspectRunResult);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, I.class, J.class)
+ .addProgramClassFileData(getProgramClassFileData())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::inspectRunResult);
+ }
+
+ private void inspectRunResult(TestRunResult<?> runResult) {
+ if (parameters.isCfRuntime(CfVm.JDK11)) {
+ runResult.assertFailureWithErrorThatThrows(AbstractMethodError.class);
+ } else {
+ runResult.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+ }
+ }
+
+ private static byte[] getProgramClassFileData() throws IOException {
+ return transformer(A.class).setImplements(I.class, J.class).transform();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ I i = new A();
+ i.m();
+ }
+ }
+
+ interface I {
+
+ default void m() {
+ System.out.println("I.m()");
+ }
+ }
+
+ interface J {
+
+ default void m() {
+ System.out.println("J.m()");
+ }
+ }
+
+ static class A implements I /*, J*/ {}
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
index 473f349..9a14cad 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
@@ -131,10 +131,6 @@
}
private Class<? extends Throwable> expectedRuntimeError() {
- if (parameters.isDexRuntime()
- && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
- return IncompatibleClassChangeError.class;
- }
return IllegalAccessError.class;
}
}
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
index cc2005d..8171081 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
@@ -150,7 +150,8 @@
// virtual dispatch to C.f. See b/140013075.
runResult.assertSuccessWithOutputLines("Called C.f");
} else {
- runResult.assertFailureWithErrorThatMatches(containsString(expectedRuntimeError()));
+ runResult.assertFailureWithErrorThatMatches(
+ containsString(expectedRuntimeError(isCorrectedByR8)));
}
}
@@ -161,9 +162,10 @@
&& runtime.asDex().getVm().isOlderThanOrEqual(DexVm.ART_7_0_0_HOST);
}
- private String expectedRuntimeError() {
+ private String expectedRuntimeError(boolean isCorrectedByR8) {
if (parameters.isDexRuntime()
- && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
+ && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)
+ && !isCorrectedByR8) {
return "IncompatibleClassChangeError";
}
return "IllegalAccessError";
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
index 9c596c9..6289967 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
@@ -186,7 +186,7 @@
.addProgramClasses(getClasses())
.addProgramClassFileData(getTransformedClasses())
.run(parameters.getRuntime(), Main.class)
- .apply(this::checkExpectedResult);
+ .apply(runResult -> checkExpectedResult(runResult, false));
}
@Test
@@ -197,10 +197,10 @@
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(Main.class)
.run(parameters.getRuntime(), Main.class)
- .apply(this::checkExpectedResult);
+ .apply(runResult -> checkExpectedResult(runResult, true));
}
- private void checkExpectedResult(TestRunResult<?> result) {
+ private void checkExpectedResult(TestRunResult<?> result, boolean isR8) {
// If not in the same nest, the error is always illegal access.
if (!inSameNest) {
result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
@@ -209,8 +209,8 @@
// If in the same nest but the reference is not exact, the error is always no such method.
if (!symbolicReferenceIsDefiningType) {
- // TODO(b/145775365): D8/R8 does not preserve the thrown error.
- if (parameters.isDexRuntime()) {
+ // TODO(b/145775365): D8 does not preserve the thrown error.
+ if (parameters.isDexRuntime() && !isR8) {
result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
return;
}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
index 9a549f2..795f109 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
@@ -82,7 +82,7 @@
.addProgramClasses(getClasses())
.addProgramClassFileData(getTransformedClasses())
.run(parameters.getRuntime(), Main.class)
- .apply(this::checkExpectedResult);
+ .apply(runResult -> checkExpectedResult(runResult, false));
}
@Test
@@ -93,11 +93,11 @@
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(Main.class)
.run(parameters.getRuntime(), Main.class)
- .apply(this::checkExpectedResult);
+ .apply(runResult -> checkExpectedResult(runResult, true));
}
- private void checkExpectedResult(TestRunResult<?> result) {
- if (inSameNest && parameters.isCfRuntime()) {
+ private void checkExpectedResult(TestRunResult<?> result, boolean isR8) {
+ if (inSameNest && (parameters.isCfRuntime() || isR8)) {
result.assertFailureWithErrorThatMatches(containsString(NoSuchMethodError.class.getName()));
} else {
result.assertFailureWithErrorThatMatches(containsString(IllegalAccessError.class.getName()));
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
index ec1222f..9adae80 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
@@ -81,7 +81,7 @@
.addProgramClasses(getClasses())
.addProgramClassFileData(getTransformedClasses())
.run(parameters.getRuntime(), Main.class)
- .apply(this::checkExpectedResult);
+ .apply(runResult -> checkExpectedResult(runResult, false));
}
@Test
@@ -92,11 +92,11 @@
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(Main.class)
.run(parameters.getRuntime(), Main.class)
- .apply(this::checkExpectedResult);
+ .apply(runResult -> checkExpectedResult(runResult, true));
}
- private void checkExpectedResult(TestRunResult<?> result) {
- if (inSameNest && parameters.isCfRuntime()) {
+ private void checkExpectedResult(TestRunResult<?> result, boolean isR8) {
+ if (inSameNest && (parameters.isCfRuntime() || isR8)) {
result.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
} else {
result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
index c1a704e..9872e09 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -109,7 +109,7 @@
stackTrace.mapping(),
StringUtils.joinLines(stackTrace.obfuscatedStackTrace()),
false,
- StringUtils.joinLines(stackTrace.retracedStackTrace()) + StringUtils.LINE_SEPARATOR,
+ StringUtils.joinLines(stackTrace.retraceVerboseStackTrace()) + StringUtils.LINE_SEPARATOR,
"--verbose");
}
@@ -120,7 +120,7 @@
stackTrace.mapping(),
StringUtils.joinLines(stackTrace.obfuscatedStackTrace()),
false,
- StringUtils.joinLines(stackTrace.retracedStackTrace()) + StringUtils.LINE_SEPARATOR,
+ StringUtils.joinLines(stackTrace.retraceVerboseStackTrace()) + StringUtils.LINE_SEPARATOR,
"-verbose");
}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 8cc0883..3a8fd5b 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -19,14 +19,17 @@
import com.android.tools.r8.retrace.stacktraces.ActualBotStackTraceBase;
import com.android.tools.r8.retrace.stacktraces.ActualIdentityStackTrace;
import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTrace;
+import com.android.tools.r8.retrace.stacktraces.AmbiguousMethodVerboseStackTrace;
import com.android.tools.r8.retrace.stacktraces.AmbiguousMissingLineStackTrace;
import com.android.tools.r8.retrace.stacktraces.AmbiguousStackTrace;
import com.android.tools.r8.retrace.stacktraces.AmbiguousWithMultipleLineMappingsStackTrace;
import com.android.tools.r8.retrace.stacktraces.AmbiguousWithSignatureNonVerboseStackTrace;
+import com.android.tools.r8.retrace.stacktraces.AmbiguousWithSignatureVerboseStackTrace;
import com.android.tools.r8.retrace.stacktraces.AutoStackTrace;
import com.android.tools.r8.retrace.stacktraces.CircularReferenceStackTrace;
import com.android.tools.r8.retrace.stacktraces.ColonInFileNameStackTrace;
import com.android.tools.r8.retrace.stacktraces.FileNameExtensionStackTrace;
+import com.android.tools.r8.retrace.stacktraces.FoundMethodVerboseStackTrace;
import com.android.tools.r8.retrace.stacktraces.InlineFileNameStackTrace;
import com.android.tools.r8.retrace.stacktraces.InlineFileNameWithInnerClassesStackTrace;
import com.android.tools.r8.retrace.stacktraces.InlineNoLineNumberStackTrace;
@@ -53,6 +56,7 @@
import com.android.tools.r8.retrace.stacktraces.SyntheticLambdaMethodWithInliningStackTrace;
import com.android.tools.r8.retrace.stacktraces.UnicodeInFileNameStackTrace;
import com.android.tools.r8.retrace.stacktraces.UnknownSourceStackTrace;
+import com.android.tools.r8.retrace.stacktraces.VerboseUnknownStackTrace;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
@@ -71,17 +75,20 @@
@RunWith(Parameterized.class)
public class RetraceTests extends TestBase {
- @Parameters(name = "{0}, external: {1}")
+ @Parameters(name = "{0}, external: {1}, verbose: {2}")
public static Collection<Object[]> data() {
- return buildParameters(getTestParameters().withCfRuntimes().build(), BooleanUtils.values());
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), BooleanUtils.values(), BooleanUtils.values());
}
private final TestParameters testParameters;
private final boolean external;
+ private final boolean verbose;
- public RetraceTests(TestParameters parameters, boolean external) {
+ public RetraceTests(TestParameters parameters, boolean external, boolean verbose) {
this.testParameters = parameters;
this.external = external;
+ this.verbose = verbose;
}
@Test
@@ -282,6 +289,26 @@
runRetraceTest(new MultipleLinesNoLineNumberStackTrace());
}
+ @Test
+ public void testFoundMethod() throws Exception {
+ runRetraceTest(new FoundMethodVerboseStackTrace());
+ }
+
+ @Test
+ public void testUnknownMethod() throws Exception {
+ runRetraceTest(new AmbiguousMethodVerboseStackTrace());
+ }
+
+ @Test
+ public void testVerboseUnknownMethod() throws Exception {
+ runRetraceTest(new VerboseUnknownStackTrace());
+ }
+
+ @Test
+ public void testAmbiguousMissingLineVerbose() throws Exception {
+ runRetraceTest(new AmbiguousWithSignatureVerboseStackTrace());
+ }
+
private void inspectRetraceTest(
StackTraceForTest stackTraceForTest, Consumer<Retracer> inspection) {
inspection.accept(
@@ -302,6 +329,11 @@
private TestDiagnosticMessagesImpl runRetraceTest(
StackTraceForTest stackTraceForTest, boolean allowExperimentalMapping) throws Exception {
+ String expectedStackTrace =
+ StringUtils.joinLines(
+ verbose
+ ? stackTraceForTest.retraceVerboseStackTrace()
+ : stackTraceForTest.retracedStackTrace());
if (external) {
assumeTrue(testParameters.isCfRuntime());
// The external dependency is built on top of R8Lib. If test.py is run with
@@ -327,13 +359,13 @@
command.add("com.android.tools.r8.retrace.Retrace");
command.add(mappingFile.toString());
command.add(stackTraceFile.toString());
+ if (verbose) {
+ command.add("-verbose");
+ }
command.add("-quiet");
ProcessBuilder builder = new ProcessBuilder(command);
ProcessResult processResult = ToolHelper.runProcess(builder);
- assertEquals(
- StringUtils.joinLines(stackTraceForTest.retracedStackTrace())
- + StringUtils.LINE_SEPARATOR,
- processResult.stdout);
+ assertEquals(expectedStackTrace + StringUtils.LINE_SEPARATOR, processResult.stdout);
// TODO(b/177204438): Parse diagnostics from stdErr
return new TestDiagnosticMessagesImpl();
} else {
@@ -343,10 +375,8 @@
.setProguardMapProducer(ProguardMapProducer.fromString(stackTraceForTest.mapping()))
.setStackTrace(stackTraceForTest.obfuscatedStackTrace())
.setRetracedStackTraceConsumer(
- retraced ->
- assertEquals(
- StringUtils.joinLines(stackTraceForTest.retracedStackTrace()),
- StringUtils.joinLines(retraced)))
+ retraced -> assertEquals(expectedStackTrace, StringUtils.joinLines(retraced)))
+ .setVerbose(verbose)
.build();
Retrace.runForTesting(retraceCommand, allowExperimentalMapping);
return diagnosticsHandler;
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java
deleted file mode 100644
index 8f8e049..0000000
--- a/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2019, 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.retrace;
-
-import static junit.framework.TestCase.assertEquals;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestDiagnosticMessagesImpl;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.retrace.stacktraces.AmbiguousMethodVerboseStackTrace;
-import com.android.tools.r8.retrace.stacktraces.AmbiguousWithSignatureVerboseStackTrace;
-import com.android.tools.r8.retrace.stacktraces.FoundMethodVerboseStackTrace;
-import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
-import com.android.tools.r8.retrace.stacktraces.VerboseUnknownStackTrace;
-import java.util.Collection;
-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 RetraceVerboseTests extends TestBase {
-
- @Parameters(name = "{0}")
- public static Collection<Object[]> data() {
- return buildParameters(getTestParameters().withNoneRuntime().build());
- }
-
- public RetraceVerboseTests(TestParameters parameters) {}
-
- @Test
- public void testFoundMethod() {
- runRetraceTest(new FoundMethodVerboseStackTrace());
- }
-
- @Test
- public void testUnknownMethod() {
- runRetraceTest(new AmbiguousMethodVerboseStackTrace());
- }
-
- @Test
- public void testVerboseUnknownMethod() {
- runRetraceTest(new VerboseUnknownStackTrace());
- }
-
- @Test
- public void testAmbiguousMissingLineVerbose() {
- assumeTrue("b/169346455", false);
- runRetraceTest(new AmbiguousWithSignatureVerboseStackTrace());
- }
-
- private void runRetraceTest(StackTraceForTest stackTraceForTest) {
- TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
- RetraceCommand retraceCommand =
- RetraceCommand.builder(diagnosticsHandler)
- .setProguardMapProducer(ProguardMapProducer.fromString(stackTraceForTest.mapping()))
- .setStackTrace(stackTraceForTest.obfuscatedStackTrace())
- .setVerbose(true)
- .setRetracedStackTraceConsumer(
- retraced -> assertEquals(stackTraceForTest.retracedStackTrace(), retraced))
- .build();
- Retrace.run(retraceCommand);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java b/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java
index 30dc206..5bc7a26 100644
--- a/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java
@@ -63,7 +63,7 @@
.clazz(ClassWithCustomFileName.class)
.retraceUnique()
.getSourceFile()
- .getFilename());
+ .getSourceFile());
}));
}
@@ -78,7 +78,7 @@
inspector.clazz(ClassWithoutCustomFileName.class).retraceUnique();
assertEquals(
"SourceFileTest.java",
- RetraceUtils.inferFileName(
+ RetraceUtils.inferSourceFile(
retraceClassElement.getRetracedClass().getTypeName(), "nofile.java", true));
}));
}
diff --git a/src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java b/src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java
index f74fcda..9af1378 100644
--- a/src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java
@@ -10,10 +10,10 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestDiagnosticMessagesImpl;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.retrace.stacktraces.InlineFileNameStackTrace;
import com.android.tools.r8.retrace.stacktraces.RetraceAssertionErrorStackTrace;
import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import java.util.Collections;
@@ -27,13 +27,16 @@
@RunWith(Parameterized.class)
public class StackTraceRegularExpressionParserTests extends TestBase {
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withNoneRuntime().build();
+ @Parameters(name = "{0}, verbose: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(getTestParameters().withNoneRuntime().build(), BooleanUtils.values());
}
- public StackTraceRegularExpressionParserTests(TestParameters parameters) {
+ private final boolean verbose;
+
+ public StackTraceRegularExpressionParserTests(TestParameters parameters, boolean verbose) {
parameters.assertNoneRuntime();
+ this.verbose = verbose;
}
@Test
@@ -57,6 +60,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("foocom.android.tools.r8.a");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -86,6 +94,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("AA.AA.AA b.b.b c.c.c");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -114,6 +127,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("AA/AA b/b/b c/c/c");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -141,6 +159,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("a.b.c.a");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -168,6 +191,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.void foo()");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -195,6 +223,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.a");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -227,6 +260,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("a.b.c.a");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -254,6 +292,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.int foo");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -281,6 +324,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.a");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -303,6 +351,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Collections.singletonList("foo.Bar$Baz.int baz(Bar.java)");
+ }
+
+ @Override
public String mapping() {
return StringUtils.lines(
"com.android.tools.r8.naming.retrace.Main -> a.b.c:",
@@ -337,6 +390,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8(R8.java)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -364,6 +422,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("a.b.d(SourceFile)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -392,6 +455,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.boolean foo()(7)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -420,6 +488,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.boolean foo()(7)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -448,6 +521,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.boolean foo()(42)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -475,6 +553,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.boolean foo()(4)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -505,6 +588,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.boolean foo()(7)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -532,6 +620,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("void", "a.a.a[]", "a.a.a[][][]");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -565,6 +658,15 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ // TODO(b/199919195): Consider not writing full method description.
+ return ImmutableList.of(
+ "void com.android.tools.r8.R8.void foo()",
+ "com.android.tools.r8.D8[] com.android.tools.r8.R8.com.android.tools.r8.D8[]"
+ + " bar()");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -601,6 +703,13 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of(
+ "void com.android.tools.r8.R8.foo",
+ "com.android.tools.r8.D8[] com.android.tools.r8.R8.bar");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -628,6 +737,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("void");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -659,6 +773,13 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of(
+ "com.android.tools.r8.R8.void foo(int,com.android.tools.r8.D8[],boolean)"
+ + "(int,com.android.tools.r8.D8[],boolean)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -689,6 +810,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.void foo()()");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -721,6 +847,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("com.android.tools.r8.R8.bar(com.android.tools.r8.D8)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -759,6 +890,14 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of(
+ "com.android.tools.r8.R8: foo bar baz",
+ " at com.android.tools.r8.Bar.void foo()(Bar.java)",
+ " at com.android.tools.r8.Baz.void bar()(Baz.java)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -794,6 +933,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of("%c\\com.android.tools.r8.Bar\\%c.void m()(\\Bar.java:13)\\%S");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
@@ -809,9 +953,14 @@
.setStackTrace(stackTraceForTest.obfuscatedStackTrace())
.setRetracedStackTraceConsumer(
retraced -> {
- assertEquals(stackTraceForTest.retracedStackTrace(), retraced);
+ assertEquals(
+ verbose
+ ? stackTraceForTest.retraceVerboseStackTrace()
+ : stackTraceForTest.retracedStackTrace(),
+ retraced);
})
.setRegularExpression(regularExpression)
+ .setVerbose(verbose)
.build();
Retrace.run(retraceCommand);
return diagnosticsHandler;
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInferSourceFileTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInferSourceFileTest.java
index 829969d..5563fea 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInferSourceFileTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInferSourceFileTest.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.retrace.ProguardMapProducer;
-import com.android.tools.r8.retrace.RetraceSourceFileResult;
+import com.android.tools.r8.retrace.RetracedSourceFile;
import com.android.tools.r8.retrace.Retracer;
import java.util.ArrayList;
import java.util.List;
@@ -40,7 +40,7 @@
@Test
public void testRetracingSourceFile() {
- List<RetraceSourceFileResult> sourceFileResults = new ArrayList<>();
+ List<RetracedSourceFile> sourceFileResults = new ArrayList<>();
Retracer.createDefault(ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {})
.retraceClass(Reference.classFromTypeName("a"))
.forEach(clazz -> sourceFileResults.add(clazz.getSourceFile()));
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSourceFileNotFoundTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSourceFileNotFoundTest.java
index a567df1..29a97e9 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSourceFileNotFoundTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSourceFileNotFoundTest.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.retrace.ProguardMapProducer;
-import com.android.tools.r8.retrace.RetraceSourceFileResult;
+import com.android.tools.r8.retrace.RetracedSourceFile;
import com.android.tools.r8.retrace.Retracer;
import java.util.ArrayList;
import java.util.List;
@@ -40,7 +40,7 @@
@Test
public void testRetracingSourceFile() {
- List<RetraceSourceFileResult> sourceFileResults = new ArrayList<>();
+ List<RetracedSourceFile> sourceFileResults = new ArrayList<>();
Retracer.createDefault(ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {})
.retraceClass(Reference.classFromTypeName("a"))
.forEach(clazz -> sourceFileResults.add(clazz.getSourceFile()));
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSourceFileTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSourceFileTest.java
index f94e0e4..e3152e8 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSourceFileTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSourceFileTest.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.retrace.ProguardMapProducer;
-import com.android.tools.r8.retrace.RetraceSourceFileResult;
+import com.android.tools.r8.retrace.RetracedSourceFile;
import com.android.tools.r8.retrace.Retracer;
import java.util.ArrayList;
import java.util.List;
@@ -41,13 +41,13 @@
@Test
public void testRetracingSourceFile() {
- List<RetraceSourceFileResult> sourceFileResults = new ArrayList<>();
+ List<RetracedSourceFile> sourceFileResults = new ArrayList<>();
Retracer.createDefault(ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {})
.retraceClass(Reference.classFromTypeName("a"))
.forEach(clazz -> sourceFileResults.add(clazz.getSourceFile()));
assertEquals(1, sourceFileResults.size());
assertTrue(sourceFileResults.get(0).hasRetraceResult());
- assertEquals("SomeFileName.kt", sourceFileResults.get(0).getFilename());
+ assertEquals("SomeFileName.kt", sourceFileResults.get(0).getSourceFile());
}
}
}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSynthesizedFrameTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSynthesizedFrameTest.java
index e7da9a7..e1ad580 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSynthesizedFrameTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSynthesizedFrameTest.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.retrace.Retracer;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -48,7 +49,7 @@
ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {})
.retraceClass(Reference.classFromTypeName("a"))
.stream()
- .flatMap(element -> element.lookupFrame("a", 3).stream())
+ .flatMap(element -> element.lookupFrame(Optional.of(3), "a").stream())
.collect(Collectors.toList());
assertEquals(1, frameResults.size());
RetraceFrameElement retraceFrameElement = frameResults.get(0);
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSynthesizedInnerFrameTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSynthesizedInnerFrameTest.java
index 96aa22d..73367a6 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSynthesizedInnerFrameTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSynthesizedInnerFrameTest.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.retrace.Retracer;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -48,7 +49,7 @@
ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {})
.retraceClass(Reference.classFromTypeName("a"))
.stream()
- .flatMap(element -> element.lookupFrame("a", 3).stream())
+ .flatMap(element -> element.lookupFrame(Optional.of(3), "a").stream())
.collect(Collectors.toList());
assertEquals(1, frameResults.size());
RetraceFrameElement retraceFrameElement = frameResults.get(0);
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualIdentityStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualIdentityStackTrace.java
index 9ac7068..1b78928 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualIdentityStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualIdentityStackTrace.java
@@ -55,6 +55,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return obfuscatedStackTrace();
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTrace.java
index 0e38636..8ffd732 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTrace.java
@@ -67,6 +67,38 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "com.android.tools.r8.CompilationFailedException: Compilation failed to complete",
+ "\tat com.android.tools.r8.BaseCommand$Builder."
+ + "com.android.tools.r8.BaseCommand build()(BaseCommand.java:143)",
+ "\tat com.android.tools.r8.R8TestBuilder.internalCompile(R8TestBuilder.java:104)",
+ "\tat com.android.tools.r8.R8TestBuilder.internalCompile(R8TestBuilder.java:29)",
+ "\tat com.android.tools.r8.TestCompilerBuilder.compile(TestCompilerBuilder.java:89)",
+ "\tat com.android.tools.r8.TestCompilerBuilder.run(TestCompilerBuilder.java:113)",
+ "\tat com.android.tools.r8.TestBuilder.run(TestBuilder.java:49)",
+ "\tat com.android.tools.r8.ir.optimize.classinliner.ClassInlinerTest.testCodeSample(ClassInlinerTest.java:289)",
+ "",
+ "Caused by:",
+ "com.android.tools.r8.utils.AbortException: Error: offset: 158, line: 2, column: 33,"
+ + " Unexpected attribute at <no file>:2:33",
+ "-keepattributes -keepattributes LineNumberTable",
+ " ^",
+ "\tat com.android.tools.r8.utils.Reporter.void failIfPendingErrors()(Reporter.java:101)",
+ "\tat com.android.tools.r8.shaking.ProguardConfigurationParser."
+ + "void parse(java.util.List)(ProguardConfigurationParser.java:187)",
+ "\tat com.android.tools.r8.R8Command$Builder."
+ + "com.android.tools.r8.R8Command makeR8Command()(R8Command.java:432)",
+ "\tat com.android.tools.r8.R8Command$Builder."
+ + "com.android.tools.r8.R8Command makeCommand()(R8Command.java:413)",
+ "\tat com.android.tools.r8.R8Command$Builder."
+ + "com.android.tools.r8.BaseCommand makeCommand()(R8Command.java:61)",
+ "\tat com.android.tools.r8.BaseCommand$Builder."
+ + "com.android.tools.r8.BaseCommand build()(BaseCommand.java:139)",
+ "\t... 6 more");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTraceWithInfo.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTraceWithInfo.java
index d57b024..ce0441d 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTraceWithInfo.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTraceWithInfo.java
@@ -69,6 +69,32 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "com.android.tools.r8.CompilationFailedException: Compilation failed to complete",
+ "\tat com.android.tools.r8.BaseCommand$Builder.build(BaseCommand.java:143)",
+ "\tat com.android.tools.r8.R8TestBuilder.internalCompile(R8TestBuilder.java:104)",
+ "\tat com.android.tools.r8.R8TestBuilder.internalCompile(R8TestBuilder.java:29)",
+ "\tat com.android.tools.r8.TestCompilerBuilder.compile(TestCompilerBuilder.java:89)",
+ "\tat com.android.tools.r8.TestCompilerBuilder.run(TestCompilerBuilder.java:113)",
+ "\tat com.android.tools.r8.TestBuilder.run(TestBuilder.java:49)",
+ "\tat com.android.tools.r8.ir.optimize.classinliner.ClassInlinerTest.testCodeSample(ClassInlinerTest.java:289)",
+ "",
+ "Caused by:",
+ "com.android.tools.r8.utils.AbortException: Error: offset: 158, line: 2, column: 33,"
+ + " Unexpected attribute at <no file>:2:33",
+ "-keepattributes -keepattributes LineNumberTable",
+ " ^",
+ "\tat com.android.tools.r8.utils.Reporter.failIfPendingErrors(Reporter.java:101)",
+ "\tat com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:187)",
+ "\tat com.android.tools.r8.R8Command$Builder.makeR8Command(R8Command.java:432)",
+ "\tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:413)",
+ "\tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:61)",
+ "\tat com.android.tools.r8.BaseCommand$Builder.build(BaseCommand.java:139)",
+ "\t... 6 more");
+ }
+
+ @Override
public int expectedWarnings() {
return 1;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMethodVerboseStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMethodVerboseStackTrace.java
index 3d3a1ee..5ce9e89 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMethodVerboseStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMethodVerboseStackTrace.java
@@ -33,12 +33,26 @@
return Arrays.asList(
"Exception in thread \"main\" java.lang.NullPointerException",
"\tat com.android.tools.r8.naming.retrace.Main.c(Main.java)",
- "\tat com.android.tools.r8.naming.retrace.Main.com.android.Foo main("
- + "java.lang.String[])(Main.java)",
- "\t<OR #1> at com.android.tools.r8.naming.retrace.Main.void main("
- + "com.android.Bar)(Main.java)",
- "\tat com.android.tools.r8.naming.retrace.Main.com.android.Foo main("
- + "java.lang.String[],com.android.Bar)(Main.java)");
+ "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java)",
+ "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java)");
+ }
+
+ @Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "There are 2 ambiguous stack traces.",
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.c(Main.java)",
+ "\tat com.android.tools.r8.naming.retrace.Main.com.android.Foo"
+ + " main(java.lang.String[])(Main.java)",
+ "\tat com.android.tools.r8.naming.retrace.Main.com.android.Foo"
+ + " main(java.lang.String[],com.android.Bar)(Main.java)",
+ "< OR >",
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.c(Main.java)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void main(com.android.Bar)(Main.java)",
+ "\tat com.android.tools.r8.naming.retrace.Main.com.android.Foo"
+ + " main(java.lang.String[],com.android.Bar)(Main.java)");
}
@Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java
index 310390c..3442711 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java
@@ -25,15 +25,82 @@
@Override
public List<String> retracedStackTrace() {
return Arrays.asList(
+ "There are 8 ambiguous stack traces. Use --verbose to have all listed.",
"com.android.tools.r8.CompilationException: foo[parens](Source:3)",
" at com.android.tools.r8.R8.bar(R8.java:7)",
- " <OR #1> at com.android.tools.r8.R8.foo(R8.java:7)",
" at com.android.tools.r8.R8.bar(R8.java:8)",
- " <OR #1> at com.android.tools.r8.R8.foo(R8.java:8)",
" at com.android.tools.r8.R8.main(Unknown Source)",
"Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
" at com.android.tools.r8.R8.bar(R8.java:9)",
- " <OR #1> at com.android.tools.r8.R8.foo(R8.java:9)",
+ " ... 42 more");
+ }
+
+ @Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "There are 8 ambiguous stack traces.",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java:7)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java:8)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java:9)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java:7)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java:8)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java:9)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java:7)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java:8)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java:9)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java:7)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java:8)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java:9)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java:7)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java:8)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java:9)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java:7)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java:8)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java:9)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java:7)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java:8)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java:9)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java:7)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java:8)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java:9)",
" ... 42 more");
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java
index 38fbb08..989868f 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java
@@ -24,43 +24,83 @@
@Override
public List<String> retracedStackTrace() {
- // ProGuard version shows ambiguous traces differently:
- //
- // Proguard Retrace:
- // com.android.tools.r8.CompilationException: foo[parens](Source:3)
- // at com.android.tools.r8.R8.foo(R8.java)
- // bar(R8.java)
- // at com.android.tools.r8.R8.foo(R8.java)
- // bar(R8.java)
- // at com.android.tools.r8.R8.main(r8.java)
- // Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)
- // at com.android.tools.r8.R8.foo(R8.java)
- // bar(R8.java)
- // ... 42 more
- //
- // Other Retrace:
- // com.android.tools.r8.CompilationException: foo[parens](Source:3)
- // at com.android.tools.r8.R8.foo(R8.java)
- // at <OR> com.android.tools.r8.R8.bar(R8.java)
- // at com.android.tools.r8.R8.foo(R8.java)
- // at <OR> com.android.tools.r8.R8.bar(R8.java)
- // at com.android.tools.r8.R8.main(r8.java)
- // Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)
- // at <OR> com.android.tools.r8.R8.bar(R8.java)
- // at com.android.tools.r8.R8(r8.java)
- // ... 42 more
- //
- // We have decided on the format below.
return Arrays.asList(
+ "There are 8 ambiguous stack traces. Use --verbose to have all listed.",
"com.android.tools.r8.CompilationException: foo[parens](Source:3)",
" at com.android.tools.r8.R8.bar(R8.java)",
- " <OR #1> at com.android.tools.r8.R8.foo(R8.java)",
" at com.android.tools.r8.R8.bar(R8.java)",
- " <OR #1> at com.android.tools.r8.R8.foo(R8.java)",
" at com.android.tools.r8.R8.main(Unknown Source)",
"Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
" at com.android.tools.r8.R8.bar(R8.java)",
- " <OR #1> at com.android.tools.r8.R8.foo(R8.java)",
+ " ... 42 more");
+ }
+
+ @Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "There are 8 ambiguous stack traces.",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
" ... 42 more");
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java
index 4543fab..1041ace 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java
@@ -36,6 +36,14 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "java.lang.IndexOutOfBoundsException",
+ "\tat java.util.ArrayList.get(ArrayList.java:411)",
+ "\tat com.android.tools.r8.Internal.void foo(int)(Internal.java)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureNonVerboseStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureNonVerboseStackTrace.java
index e4dc987..97f2162 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureNonVerboseStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureNonVerboseStackTrace.java
@@ -37,6 +37,27 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "There are 4 ambiguous stack traces.",
+ "java.lang.IndexOutOfBoundsException",
+ "\tat java.util.ArrayList.get(ArrayList.java:411)",
+ "\tat com.android.tools.r8.Internal.boolean foo(int,int)(Internal.java)",
+ "< OR >",
+ "java.lang.IndexOutOfBoundsException",
+ "\tat java.util.ArrayList.get(ArrayList.java:411)",
+ "\tat com.android.tools.r8.Internal.void foo(int)(Internal.java)",
+ "< OR >",
+ "java.lang.IndexOutOfBoundsException",
+ "\tat java.util.ArrayList.get(ArrayList.java:411)",
+ "\tat com.android.tools.r8.Internal.void foo(int,boolean)(Internal.java)",
+ "< OR >",
+ "java.lang.IndexOutOfBoundsException",
+ "\tat java.util.ArrayList.get(ArrayList.java:411)",
+ "\tat com.android.tools.r8.Internal.void foo(int,int)(Internal.java)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureVerboseStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureVerboseStackTrace.java
index 45c103f..23991fb 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureVerboseStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureVerboseStackTrace.java
@@ -33,10 +33,28 @@
return Arrays.asList(
"java.lang.IndexOutOfBoundsException",
"\tat java.util.ArrayList.get(ArrayList.java:411)",
+ "\tat com.android.tools.r8.Internal.foo(Internal.java)");
+ }
+
+ @Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "There are 4 ambiguous stack traces.",
+ "java.lang.IndexOutOfBoundsException",
+ "\tat java.util.ArrayList.get(ArrayList.java:411)",
"\tat com.android.tools.r8.Internal.boolean foo(int,int)(Internal.java)",
- "\t<OR> at com.android.tools.r8.Internal.void foo(int)(Internal.java)",
- "\t<OR> at com.android.tools.r8.Internal.void foo(int,boolean)(Internal.java)",
- "\t<OR> at com.android.tools.r8.Internal.void foo(int,int)(Internal.java)");
+ "< OR >",
+ "java.lang.IndexOutOfBoundsException",
+ "\tat java.util.ArrayList.get(ArrayList.java:411)",
+ "\tat com.android.tools.r8.Internal.void foo(int)(Internal.java)",
+ "< OR >",
+ "java.lang.IndexOutOfBoundsException",
+ "\tat java.util.ArrayList.get(ArrayList.java:411)",
+ "\tat com.android.tools.r8.Internal.void foo(int,boolean)(Internal.java)",
+ "< OR >",
+ "java.lang.IndexOutOfBoundsException",
+ "\tat java.util.ArrayList.get(ArrayList.java:411)",
+ "\tat com.android.tools.r8.Internal.void foo(int,int)(Internal.java)");
}
@Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AutoStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AutoStackTrace.java
index 0504b58..ab7f261 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AutoStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AutoStackTrace.java
@@ -35,6 +35,14 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of(
+ "java.io.IOException: INVALID_SENDER",
+ "\tat com.android.tools.r8.AutoTest.void foo(int)(AutoTest.java:200)",
+ "\tat com.android.tools.r8.AutoTest.void foo(int,int)(AutoTest.java:24)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/CircularReferenceStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/CircularReferenceStackTrace.java
index 5df37f8..c0b1977 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/CircularReferenceStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/CircularReferenceStackTrace.java
@@ -43,6 +43,19 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ " [CIRCULAR REFERENCE:foo.bar.Baz]",
+ " [CIRCULAR REFERENCE:foo.bar.Qux]",
+ " [CIRCULAR REFERENCE:None.existing.class]",
+ " [CIRCULAR REFERENCE:foo.bar.Baz] ",
+ " [CIRCU:AA]",
+ " [CIRCULAR REFERENCE:A.A",
+ " [CIRCULAR REFERENCE:]",
+ " [CIRCULAR REFERENCE:None existing class]");
+ }
+
+ @Override
public int expectedWarnings() {
return 5;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/ColonInFileNameStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/ColonInFileNameStackTrace.java
index 0bdb870..618b83f 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/ColonInFileNameStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/ColonInFileNameStackTrace.java
@@ -29,6 +29,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of(" at some.Class.int strawberry(int)(Class.kt:99)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/FileNameExtensionStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/FileNameExtensionStackTrace.java
index dc77ca6..06cc043 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/FileNameExtensionStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/FileNameExtensionStackTrace.java
@@ -52,6 +52,24 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "foo.bar.baz: Problem when compiling program",
+ " at R8.main(R8.java:800)",
+ " at R8.main(Native Method)",
+ " at R8.main(R8.java:)",
+ " at R8.main(R8.kt:1)",
+ " at R8.main(R8.java)",
+ " at R8.main(R8.java)",
+ " at R8.main(R8.java)",
+ " at R8.main(R8.java)",
+ " at R8.main(R8.java:1)",
+ "Suppressed: foo.bar.baz: You have to write the program first",
+ " at R8.retrace(R8.java:184)",
+ " ... 7 more");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/FoundMethodVerboseStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/FoundMethodVerboseStackTrace.java
index ce7a8f5..b4812a5 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/FoundMethodVerboseStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/FoundMethodVerboseStackTrace.java
@@ -27,6 +27,13 @@
public List<String> retracedStackTrace() {
return Arrays.asList(
"Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:102)");
+ }
+
+ @Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
"\tat com.android.tools.r8.naming.retrace.Main.com.android.Foo main(java.lang.String[],"
+ "com.android.Bar)(Main.java:102)");
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameStackTrace.java
index ef36965..048b941 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameStackTrace.java
@@ -28,6 +28,17 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat foo.Bar$Baz.void baz(long)(Bar.java:0)",
+ "\tat Foo$Bar.void bar(int)(Foo.java:2)",
+ "\tat com.android.tools.r8.naming.retrace.Main$Foo"
+ + ".void method1(java.lang.String)(Main.java:8)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String[])(Main.java:7)");
+ }
+
+ @Override
public String mapping() {
return StringUtils.lines(
"com.android.tools.r8.naming.retrace.Main -> com.android.tools.r8.naming.retrace.Main:",
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameWithInnerClassesStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameWithInnerClassesStackTrace.java
index 8d7551a..177eb8a 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameWithInnerClassesStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameWithInnerClassesStackTrace.java
@@ -26,6 +26,14 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat foo.Bar$Baz$Qux.void baz(long)(Bar.java:0)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String[])(Main.java:7)");
+ }
+
+ @Override
public String mapping() {
return StringUtils.lines(
"com.android.tools.r8.naming.retrace.Main -> com.android.tools.r8.naming.retrace.Main:",
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineNumberStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineNumberStackTrace.java
index dda09dd..763ec80 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineNumberStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineNumberStackTrace.java
@@ -28,6 +28,16 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.void method3(long)(Main.java:0)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void method2(int)(Main.java:0)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void method1(java.lang.String)(Main.java:0)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String[])(Main.java:0)");
+ }
+
+ @Override
public String mapping() {
return StringUtils.lines(
"com.android.tools.r8.naming.retrace.Main -> com.android.tools.r8.naming.retrace.Main:",
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java
index 3c22097..f5943b5 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java
@@ -48,6 +48,17 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ " at com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
+ + ".void throwsException()(KotlinJavaSourceFileTestLibrary.kt:22)",
+ " at com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
+ + ".void callsThrowsException()(KotlinJavaSourceFileTestLibrary.kt:19)",
+ " at com.google.appreduce.remapper.KotlinJavaSourceFileTestObject"
+ + ".void main(java.lang.String[])(KotlinJavaSourceFileTestObject.java:32)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineWithLineNumbersStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineWithLineNumbersStackTrace.java
index 59c9693..eb1144e 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineWithLineNumbersStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineWithLineNumbersStackTrace.java
@@ -44,6 +44,18 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.void method3(long)(Main.java:81)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void method2(int)(Main.java:88)",
+ "\tat com.android.tools.r8.naming.retrace.Main."
+ + "void method1(java.lang.String)(Main.java:96)",
+ "\tat com.android.tools.r8.naming.retrace.Main."
+ + "void main(java.lang.String[])(Main.java:102)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InvalidStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InvalidStackTrace.java
index 87d30d5..737a32a 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/InvalidStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InvalidStackTrace.java
@@ -35,6 +35,16 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ " hulubulu",
+ " XXX, where are you",
+ "foo.bar.baz: Problem when compiling program",
+ " . . . 7 more",
+ " ... 7 more");
+ }
+
+ @Override
public int expectedWarnings() {
return 1;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/MemberFieldOverlapStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/MemberFieldOverlapStackTrace.java
index 9b5ba56..ca6b1c9 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/MemberFieldOverlapStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/MemberFieldOverlapStackTrace.java
@@ -38,6 +38,13 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat foo.Bar.int method()(Bar.java:5)");
+ }
+
+ @Override
public int expectedWarnings() {
return 1;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleDotsInFileNameStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleDotsInFileNameStackTrace.java
index a67335f..43ac3ab 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleDotsInFileNameStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleDotsInFileNameStackTrace.java
@@ -29,6 +29,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of(" at some.Class.int strawberry(int)(Class.kt:99)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleLinesNoLineNumberStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleLinesNoLineNumberStackTrace.java
index 54ef0bf..004b7d5 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleLinesNoLineNumberStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleLinesNoLineNumberStackTrace.java
@@ -29,10 +29,21 @@
@Override
public List<String> retracedStackTrace() {
return Arrays.asList(
+ "There are 2 ambiguous stack traces. Use --verbose to have all listed.",
"Exception in thread \"main\" java.lang.NullPointerException",
- "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java)",
- "\t<OR #1> at com.android.tools.r8.naming.retrace.Main.method1(Main.java)",
- "\t<OR #1> at com.android.tools.r8.naming.retrace.Main.main(Main.java)");
+ "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java)");
+ }
+
+ @Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "There are 2 ambiguous stack traces.",
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String[])(Main.java)",
+ "< OR >",
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.void method1(java.lang.String)(Main.java)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String[])(Main.java)");
}
@Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/NamedModuleStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/NamedModuleStackTrace.java
index 8bd402f..1866585 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/NamedModuleStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/NamedModuleStackTrace.java
@@ -50,6 +50,17 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "SomeFakeException: this is a fake exception",
+ "\tat com.android.tools.r8.Classloader/named_module@9.0/com.android.tools.r8.Main.main(Main.java:1)",
+ "\tat com.android.tools.r8.Classloader//com.android.tools.r8.Main.foo(Main.java:2)",
+ "\tat named_module@2.1/com.android.tools.r8.Main.bar(Main.java:3)",
+ "\tat named_module/com.android.tools.r8.Main.baz(Main.java:4)",
+ "\tat com.android.tools.r8.Main.qux(Main.java:5)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscatedLineNumberWithOverrideTest.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscatedLineNumberWithOverrideTest.java
index 23c5652..858c06b 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscatedLineNumberWithOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscatedLineNumberWithOverrideTest.java
@@ -35,16 +35,52 @@
@Override
public List<String> retracedStackTrace() {
return Arrays.asList(
+ "There are 4 ambiguous stack traces. Use --verbose to have all listed.",
"Exception in thread \"main\" java.lang.NullPointerException",
"\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:3)",
"\tat com.android.tools.r8.naming.retrace.Main.overload1(Main.java:7)",
- "\t<OR #1> at com.android.tools.r8.naming.retrace.Main.overload2(Main.java:11)",
"\tat com.android.tools.r8.naming.retrace.Main.definedOverload(Main.java:7)",
- "\t<OR #1> at com.android.tools.r8.naming.retrace.Main.definedOverload(Main.java:11)",
"\tat com.android.tools.r8.naming.retrace.Main.mainPC(Main.java:42)");
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "There are 4 ambiguous stack traces.",
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String)(Main.java:3)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void overload1()(Main.java:7)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void definedOverload()(Main.java:7)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " mainPC(java.lang.String[])(Main.java:42)",
+ "< OR >",
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String)(Main.java:3)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void overload1()(Main.java:7)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " definedOverload(java.lang.String)(Main.java:11)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " mainPC(java.lang.String[])(Main.java:42)",
+ "< OR >",
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String)(Main.java:3)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " overload2(java.lang.String)(Main.java:11)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void definedOverload()(Main.java:7)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " mainPC(java.lang.String[])(Main.java:42)",
+ "< OR >",
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String)(Main.java:3)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " overload2(java.lang.String)(Main.java:11)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " definedOverload(java.lang.String)(Main.java:11)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " mainPC(java.lang.String[])(Main.java:42)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java
index 3f944e2..579541f 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java
@@ -31,6 +31,16 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.void foo(long)(Main.java:1)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void bar(int)(Main.java:3)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void baz()(Main.java:8)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String[])(Main.java:7)");
+ }
+
+ @Override
public String mapping() {
return StringUtils.lines(
"com.android.tools.r8.naming.retrace.Main -> foo:",
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/NullStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/NullStackTrace.java
index 99aa584..9cd0c45 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/NullStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/NullStackTrace.java
@@ -34,6 +34,12 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ fail();
+ return null;
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/ObfucatedExceptionClassStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/ObfucatedExceptionClassStackTrace.java
index 707874d..de7673b 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/ObfucatedExceptionClassStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/ObfucatedExceptionClassStackTrace.java
@@ -35,6 +35,16 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "foo.bar.baz: Problem when compiling program",
+ " at r8.main(App:800)",
+ "Caused by: foo.bar.baz: You have to write the program first",
+ " at r8.retrace(App:184)",
+ " ... 7 more");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/ObfuscatedRangeToSingleLineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/ObfuscatedRangeToSingleLineStackTrace.java
index 1579756..e87126f 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/ObfuscatedRangeToSingleLineStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/ObfuscatedRangeToSingleLineStackTrace.java
@@ -40,6 +40,16 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "UnknownException: This is just a fake exception",
+ " at foo.bar.Baz.void qux()(Baz.java:27)",
+ " at foo.bar.Baz.void qux()(Baz.java:42)",
+ " at foo.bar.Baz.void quux()(Baz.java:113)",
+ " at foo.bar.Baz.void quuz()(Baz.java:72)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/OverloadSameLineTest.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/OverloadSameLineTest.java
index f1fbedb..c5fe164 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/OverloadSameLineTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/OverloadSameLineTest.java
@@ -29,11 +29,24 @@
@Override
public List<String> retracedStackTrace() {
return Arrays.asList(
+ "There are 3 ambiguous stack traces. Use --verbose to have all listed.",
"Exception in thread \"main\" java.lang.NullPointerException",
- // TODO(b/199058242): Should be ambiguous and not inline frames
- "\tat com.android.tools.r8.naming.retrace.Main.overload(Main.java:7)",
- "\t<OR #1> at com.android.tools.r8.naming.retrace.Main.overload(Main.java:15)",
- "\t<OR #2> at com.android.tools.r8.naming.retrace.Main.overload(Main.java:13)");
+ "\tat com.android.tools.r8.naming.retrace.Main.overload(Main.java:7)");
+ }
+
+ @Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "There are 3 ambiguous stack traces.",
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.void overload()(Main.java:7)",
+ "< OR >",
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.void overload(int)(Main.java:15)",
+ "< OR >",
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " overload(java.lang.String)(Main.java:13)");
}
@Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/PGStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/PGStackTrace.java
index 8698ac9..2a3a63a 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/PGStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/PGStackTrace.java
@@ -45,6 +45,19 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "09-16 15:43:01.249 23316 23316 E AndroidRuntime: java.lang.NullPointerException: Attempt"
+ + " to invoke virtual method 'boolean"
+ + " com.google.android.foo(com.google.android.foo.Data$Key)' on a null object"
+ + " reference",
+ "09-16 15:43:01.249 23316 23316 E AndroidRuntime: at"
+ + " com.google.apps.sectionheader.SectionHeaderListController.onToolbarStateChanged(SectionHeaderListController.java:586)",
+ "09-16 15:43:01.249 23316 23316 E AndroidRuntime: at"
+ + " com.google.apps.Controller.onToolbarStateChanged(Controller.java:1087)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/RetraceAssertionErrorStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/RetraceAssertionErrorStackTrace.java
index d6bcacc..11d2669 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/RetraceAssertionErrorStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/RetraceAssertionErrorStackTrace.java
@@ -36,6 +36,23 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "java.lang.AssertionError",
+ " at com.android.tools.r8.retrace.RetraceCore$StackTraceNode."
+ + "void <init>(java.util.List)(RetraceCore.java:31)",
+ " at com.android.tools.r8.retrace.RetraceCore."
+ + "void retraceLine(java.util.List,int,java.util.List)(RetraceCore.java:117)",
+ " at com.android.tools.r8.retrace.RetraceCore."
+ + "com.android.tools.r8.retrace."
+ + "RetraceCore$RetraceResult retrace()(RetraceCore.java:107)",
+ " at com.android.tools.r8.retrace.Retrace."
+ + "void run(com.android.tools.r8.retrace.RetraceCommand)(Retrace.java:116)",
+ " at com.android.tools.r8.retrace.RetraceTests."
+ + "testNullLineTrace(RetraceTests.java:73)");
+ }
+
+ @Override
public String mapping() {
return StringUtils.lines(
"com.android.tools.r8.retrace.Retrace -> com.android.tools.r8.retrace.Retrace:",
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/SingleLineNoLineNumberStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/SingleLineNoLineNumberStackTrace.java
index 0b0c7ef..120cd8e 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/SingleLineNoLineNumberStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/SingleLineNoLineNumberStackTrace.java
@@ -36,17 +36,48 @@
@Override
public List<String> retracedStackTrace() {
return Arrays.asList(
+ "There are 2 ambiguous stack traces. Use --verbose to have all listed.",
"Exception in thread \"main\" java.lang.NullPointerException",
"\tat com.android.tools.r8.naming.retrace.Main.method1(Main.java:42)",
"\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:28)",
"\tat com.android.tools.r8.naming.retrace.Main.method2(Main.java:42)",
"\tat com.android.tools.r8.naming.retrace.Main.main2(Main.java:29)",
"\tat com.android.tools.r8.naming.retrace.Main.main3(Main.java:30)",
- "\t<OR #1> at com.android.tools.r8.naming.retrace.Main.method3(Main.java:72)",
"\tat com.android.tools.r8.naming.retrace.Main.main4(Main.java:153)");
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "There are 2 ambiguous stack traces.",
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " method1(java.lang.String)(Main.java:42)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String[])(Main.java:28)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " method2(java.lang.String)(Main.java:42)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " main2(java.lang.String[])(Main.java:29)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " main3(java.lang.String[])(Main.java:30)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " main4(java.lang.String[])(Main.java:153)",
+ "< OR >",
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " method1(java.lang.String)(Main.java:42)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String[])(Main.java:28)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " method2(java.lang.String)(Main.java:42)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " main2(java.lang.String[])(Main.java:29)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " method3(java.lang.String)(Main.java:72)",
+ "\tat com.android.tools.r8.naming.retrace.Main.void"
+ + " main4(java.lang.String[])(Main.java:153)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileNameSynthesizeStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileNameSynthesizeStackTrace.java
index 0315fc0..9e15a2d 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileNameSynthesizeStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileNameSynthesizeStackTrace.java
@@ -36,6 +36,14 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "\tat android.support.v7.widget.ActionMenuView.void invokeItem()(ActionMenuView.java:624)",
+ "\tat noMappingKt.noMapping(AW779999992:21)",
+ "\tat android.support.v7.widget.ActionMenuViewKt.void invokeItem()(ActionMenuView.kt:624)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileWithNumberAndEmptyStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileWithNumberAndEmptyStackTrace.java
index 1522918..fd20517 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileWithNumberAndEmptyStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileWithNumberAndEmptyStackTrace.java
@@ -39,6 +39,23 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ " at com.android.tools.r8.utils.ExceptionUtils.void withR8CompilationHandler("
+ + "com.android.tools.r8.utils.Reporter,"
+ + "com.android.tools.r8.utils.ExceptionUtils$CompileAction)(ExceptionUtils.java:59)",
+ " at com.android.tools.r8.R8.void runForTesting("
+ + "com.android.tools.r8.utils.AndroidApp,"
+ + "com.android.tools.r8.utils.InternalOptions)(R8.java:261)",
+ " at com.android.tools.r8.utils.ExceptionUtils.void withR8CompilationHandler("
+ + "com.android.tools.r8.utils.Reporter,"
+ + "com.android.tools.r8.utils.ExceptionUtils$CompileAction)(ExceptionUtils.java:59)",
+ " at com.android.tools.r8.R8.void runForTesting("
+ + "com.android.tools.r8.utils.AndroidApp,"
+ + "com.android.tools.r8.utils.InternalOptions)(R8.java:261)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/StackTraceForTest.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/StackTraceForTest.java
index e9783e6..29baca3 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/StackTraceForTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/StackTraceForTest.java
@@ -14,5 +14,7 @@
List<String> retracedStackTrace();
+ List<String> retraceVerboseStackTrace();
+
int expectedWarnings();
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/SuppressedStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/SuppressedStackTrace.java
index eb44d96..e0cd74c 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/SuppressedStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/SuppressedStackTrace.java
@@ -35,6 +35,16 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "foo.bar.baz: Problem when compiling program",
+ " at r8.main(App:800)",
+ "Suppressed: foo.bar.baz: You have to write the program first",
+ " at r8.retrace(App:184)",
+ " ... 7 more");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodStackTrace.java
index 793fc0c..6beaba4 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodStackTrace.java
@@ -32,6 +32,16 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ " at example.Foo.void lambda$main$0()(Foo.java:225)",
+ " at example.Foo.void runIt()(Foo.java:218)",
+ " at example.Foo.void main()(Foo.java:223)",
+ " at example.Main.void main(java.lang.String[])(Main.java:123)");
+ }
+
+ @Override
public String mapping() {
return StringUtils.lines(
"# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodWithInliningStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodWithInliningStackTrace.java
index c132733..201700d 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodWithInliningStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodWithInliningStackTrace.java
@@ -31,6 +31,16 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ " at example.Foo.void lambda$main$0()(Foo.java:225)",
+ " at example.Foo.void runIt()(Foo.java:218)",
+ " at example.Foo.void main()(Foo.java:223)",
+ " at example.Main.void main(java.lang.String[])(Main.java:123)");
+ }
+
+ @Override
public String mapping() {
return StringUtils.lines(
"# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/UnicodeInFileNameStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnicodeInFileNameStackTrace.java
index 5090c39..1b3c0eb 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/UnicodeInFileNameStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnicodeInFileNameStackTrace.java
@@ -29,6 +29,11 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return ImmutableList.of(" at some.Class.int strawberry(int)(Class.kt:99)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownSourceStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownSourceStackTrace.java
index 13b8bdd..2f946f8 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownSourceStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownSourceStackTrace.java
@@ -25,15 +25,82 @@
@Override
public List<String> retracedStackTrace() {
return Arrays.asList(
+ "There are 8 ambiguous stack traces. Use --verbose to have all listed.",
"com.android.tools.r8.CompilationException: foo[parens](Source:3)",
" at com.android.tools.r8.R8.bar(R8.java)",
- " <OR #1> at com.android.tools.r8.R8.foo(R8.java)",
" at com.android.tools.r8.R8.bar(R8.java)",
- " <OR #1> at com.android.tools.r8.R8.foo(R8.java)",
" at com.android.tools.r8.R8.main(Unknown Source)",
"Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
" at com.android.tools.r8.R8.bar(R8.java)",
- " <OR #1> at com.android.tools.r8.R8.foo(R8.java)",
+ " ... 42 more");
+ }
+
+ @Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "There are 8 ambiguous stack traces.",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void bar(int,int)(R8.java)",
+ " ... 42 more",
+ "< OR >",
+ "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
+ " at com.android.tools.r8.R8.main(Unknown Source)",
+ "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+ " at com.android.tools.r8.R8.void foo(int)(R8.java)",
" ... 42 more");
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/VerboseUnknownStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/VerboseUnknownStackTrace.java
index 3cb6808..24e773c 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/VerboseUnknownStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/VerboseUnknownStackTrace.java
@@ -27,6 +27,12 @@
}
@Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "java.lang.IndexOutOfBoundsException", "\tat java.util.ArrayList.get(ArrayList.java:411)");
+ }
+
+ @Override
public int expectedWarnings() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index 7733b23..a9574e8 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -57,6 +57,7 @@
testForR8(parameters.getBackend())
.addProgramClassesAndInnerClasses(Ordinals.class)
.addKeepMainRule(Ordinals.class)
+ .enableConstantArgumentAnnotations()
.enableForceInliningAnnotations()
.enableInliningAnnotations()
.enableSideEffectAnnotations()
@@ -106,6 +107,7 @@
testForR8(parameters.getBackend())
.addProgramClassesAndInnerClasses(Names.class)
.addKeepMainRule(Names.class)
+ .enableConstantArgumentAnnotations()
.enableForceInliningAnnotations()
.enableInliningAnnotations()
.enableSideEffectAnnotations()
@@ -152,6 +154,7 @@
testForR8(parameters.getBackend())
.addProgramClassesAndInnerClasses(ToStrings.class)
.addKeepMainRule(ToStrings.class)
+ .enableConstantArgumentAnnotations()
.enableForceInliningAnnotations()
.enableInliningAnnotations()
.enableSideEffectAnnotations()
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/Names.java b/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
index 5dd5ec0..1c2964c 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.AssumeMayHaveSideEffects;
import com.android.tools.r8.ForceInline;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
import java.util.concurrent.TimeUnit;
@@ -69,6 +70,7 @@
return Number.DEFAULT.name();
}
+ @KeepConstantArguments
@NeverInline
private static String phi(boolean value) {
Number number = Number.ONE;
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java b/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java
index e1eee2c..c3fbffc 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.AssumeMayHaveSideEffects;
import com.android.tools.r8.ForceInline;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
import java.util.concurrent.TimeUnit;
@@ -79,6 +80,7 @@
return Number.DEFAULT.ordinal();
}
+ @KeepConstantArguments
@NeverInline
private static long phi(boolean value) {
Number number = Number.ONE;
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java b/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java
index 8055344..020f5fd 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.AssumeMayHaveSideEffects;
import com.android.tools.r8.ForceInline;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
@@ -106,6 +107,7 @@
return NoToString.DEFAULT.toString();
}
+ @KeepConstantArguments
@NeverInline
private static String phi(boolean value) {
NoToString number = NoToString.ONE;
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/MaxIntSwitchTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/MaxIntSwitchTest.java
index b32e476..26dcb7a 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switches/MaxIntSwitchTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/MaxIntSwitchTest.java
@@ -7,6 +7,7 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NeverPropagateValue;
import com.android.tools.r8.TestBase;
@@ -104,6 +105,7 @@
testForR8(parameters.getBackend())
.addInnerClasses(this.getClass())
.addKeepMainRule(TestClass.class)
+ .enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.enableMemberValuePropagationAnnotations()
.setMinApi(parameters.getApiLevel())
@@ -125,6 +127,7 @@
"?");
static class TestClass {
+ @KeepConstantArguments
@NeverInline
@NeverPropagateValue
public static void f(int i) {
@@ -143,6 +146,7 @@
}
}
+ @KeepConstantArguments
@NeverInline
@NeverPropagateValue
public static void g(int i) {
@@ -162,6 +166,7 @@
}
}
+ @KeepConstantArguments
@NeverInline
@NeverPropagateValue
public static void h(int i) {
@@ -174,6 +179,7 @@
}
}
+ @KeepConstantArguments
@NeverInline
@NeverPropagateValue
public static void s(String s) {
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index d98725d..6a95824 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticException;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.proguardConfigurationRuleDoesNotMatch;
import static com.android.tools.r8.utils.codeinspector.Matchers.typeVariableNotInScope;
@@ -13,7 +12,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.R8;
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
@@ -76,8 +74,7 @@
assertThat(method, isPresent());
}
- // TODO(b/159966986): A general keep rule should not cause compiler assertion errors.
- @Test(expected = CompilationFailedException.class)
+ @Test
public void testPresentAnnotation() throws Exception {
testForR8(Backend.CF)
.addProgramFiles(R8_JAR)
@@ -85,10 +82,7 @@
.addDontWarnGoogle()
.addDontWarnJavax()
.addDontWarn("org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement")
- .allowDiagnosticInfoMessages()
- .compileWithExpectedDiagnostics(
- diagnostics -> diagnostics.assertErrorsMatch(diagnosticException(AssertionError.class)))
- .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation);
+ .compile();
}
@Test
diff --git a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
index f529432..7c9c834 100644
--- a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
@@ -105,9 +105,9 @@
"In C.m3()",
"In A.m4()",
"In A.m1()", // With Java: Caught IllegalAccessError when calling B.m1()
- "In A.m3()", // With Java: Caught IncompatibleClassChangeError when calling B.m3()
+ "Caught IncompatibleClassChangeError when calling B.m3()",
"In C.m1()", // With Java: Caught IllegalAccessError when calling B.m1()
- "In C.m3()", // With Java: Caught IncompatibleClassChangeError when calling B.m3()
+ "Caught IncompatibleClassChangeError when calling B.m3()",
"In C.m1()",
"In C.m3()",
"");
@@ -179,7 +179,7 @@
assertThat(classSubject, isPresentAndRenamed());
assertThat(classSubject.method("void", "m1", ImmutableList.of()), isPresent());
assertThat(classSubject.method("void", "m2", ImmutableList.of()), isAbsent());
- assertThat(classSubject.method("void", "m3", ImmutableList.of()), isPresent());
+ assertThat(classSubject.method("void", "m3", ImmutableList.of()), isAbsent());
assertThat(classSubject.method("void", "m4", ImmutableList.of()), isAbsent());
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/array/DeadArrayLengthTest.java b/src/test/java/com/android/tools/r8/shaking/array/DeadArrayLengthTest.java
index 14259ca..03d5aa6 100644
--- a/src/test/java/com/android/tools/r8/shaking/array/DeadArrayLengthTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/array/DeadArrayLengthTest.java
@@ -31,7 +31,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public DeadArrayLengthTest(TestParameters parameters) {
@@ -65,7 +65,7 @@
testForD8()
.release()
.addProgramClasses(MAIN)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(EXPECTED_OUTPUT)
.inspect(codeInspector -> inspect(codeInspector, false));
@@ -79,7 +79,7 @@
.enableInliningAnnotations()
.enableMemberValuePropagationAnnotations()
.noMinification()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(EXPECTED_OUTPUT)
.inspect(codeInspector -> inspect(codeInspector, true));
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
index 9dfff6e..fdea6ab 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
@@ -9,6 +9,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestBuilder;
@@ -80,6 +81,7 @@
ProguardKeepAttributes.INNER_CLASSES,
ProguardKeepAttributes.ENCLOSING_METHOD)
.setMinApi(parameters.getApiLevel())
+ .enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.run(parameters.getRuntime(), KeptClass.class)
@@ -137,6 +139,7 @@
public List<P> notKeptField;
+ @KeepConstantArguments
@NeverInline
public List<P> notKeptMethod(P p1, P p2) {
if (notKeptField != null) {
@@ -163,6 +166,7 @@
return (R) keptField;
}
+ @KeepConstantArguments
@NeverInline
@SuppressWarnings("unchecked")
public <R> R notKeptMethod(T t) {
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
index d951404..74232e1 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
@@ -3,6 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -10,7 +14,6 @@
import com.google.common.collect.ImmutableList;
import java.util.Collections;
import java.util.List;
-import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -41,7 +44,7 @@
@Test
public void testKeeprules() throws Exception {
runTest(
- TreeShaking2Test::shaking2SuperClassIsAbstract,
+ TreeShaking2Test::shaking2SuperClassIsRemoved,
null,
null,
ImmutableList.of("src/test/examples/shaking2/keep-rules.txt"));
@@ -62,16 +65,15 @@
null, null, null, ImmutableList.of("src/test/examples/shaking2/keep-rules-printusage.txt"));
}
- private static void shaking2SuperClassIsAbstract(CodeInspector inspector) {
+ private static void shaking2SuperClassIsRemoved(CodeInspector inspector) {
ClassSubject clazz = inspector.clazz("shaking2.SuperClass");
- Assert.assertTrue(clazz.isAbstract());
- Assert.assertTrue(clazz.method("void", "virtualMethod", Collections.emptyList()).isAbstract());
- Assert.assertTrue(
- clazz
- .method(
- "void",
- "virtualMethod2",
- ImmutableList.of("int", "int", "int", "int", "int", "int", "int", "int"))
- .isAbstract());
+ assertTrue(clazz.isAbstract());
+ assertTrue(clazz.method("void", "virtualMethod", Collections.emptyList()).isAbstract());
+ assertThat(
+ clazz.method(
+ "void",
+ "virtualMethod2",
+ ImmutableList.of("int", "int", "int", "int", "int", "int", "int", "int")),
+ isAbsent());
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java
index 4740853..4f8131d 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java
@@ -10,6 +10,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.KeepUnusedArguments;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
@@ -173,6 +174,7 @@
.addInnerClasses(KeepParameterNamesTest.class)
.addKeepMainRule(TestClass.class)
.addKeepRules("-keep class " + Api.class.getTypeName() + "{ api*(...); }")
+ .enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableUnusedArgumentAnnotations()
@@ -209,6 +211,7 @@
"In Api.api2",
"In Api.api3");
testForR8(parameters.getBackend())
+ .enableConstantArgumentAnnotations()
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
.enableUnusedArgumentAnnotations()
@@ -253,6 +256,7 @@
}
@NeverInline
+ @KeepConstantArguments
@KeepUnusedArguments
void api1(int parameter1, String parameter2) {
try {
@@ -264,12 +268,14 @@
}
@NeverInline
+ @KeepConstantArguments
@KeepUnusedArguments
void api2(long parameter1, double parameter2) {
System.out.println("In Api.api2");
}
@NeverInline
+ @KeepConstantArguments
@KeepUnusedArguments
void api3(List<String> parameter1, Map<String, Object> parameter2) {
System.out.println("In Api.api3");
diff --git a/src/test/kotlinR8TestResources/unused_singleton/main.kt b/src/test/kotlinR8TestResources/unused_singleton/main.kt
index 213acb1..6c06b8c 100644
--- a/src/test/kotlinR8TestResources/unused_singleton/main.kt
+++ b/src/test/kotlinR8TestResources/unused_singleton/main.kt
@@ -10,6 +10,6 @@
fun provideGreeting() = "Hello"
}
-fun main(args: Array<String>) {
+fun main(args: Array<String>?) {
println(provideGreeting())
}
diff --git a/third_party/openjdk/jdk-17/linux.tar.gz.sha1 b/third_party/openjdk/jdk-17/linux.tar.gz.sha1
new file mode 100644
index 0000000..27451d3
--- /dev/null
+++ b/third_party/openjdk/jdk-17/linux.tar.gz.sha1
@@ -0,0 +1 @@
+fa768e38f9c28e401174cecdd4e326b5fae120e1
\ No newline at end of file
diff --git a/third_party/openjdk/jdk-17/osx.tar.gz.sha1 b/third_party/openjdk/jdk-17/osx.tar.gz.sha1
new file mode 100644
index 0000000..ff2288f
--- /dev/null
+++ b/third_party/openjdk/jdk-17/osx.tar.gz.sha1
@@ -0,0 +1 @@
+7c62e0a6a3168e138f4bad06439650a9e0869333
\ No newline at end of file
diff --git a/third_party/openjdk/jdk-17/windows.tar.gz.sha1 b/third_party/openjdk/jdk-17/windows.tar.gz.sha1
new file mode 100644
index 0000000..0a02a42
--- /dev/null
+++ b/third_party/openjdk/jdk-17/windows.tar.gz.sha1
@@ -0,0 +1 @@
+07d750b353ebd4f5ef9ec99c916e50b33f47a8a8
\ No newline at end of file
diff --git a/third_party/retrace/binary_compatibility.tar.gz.sha1 b/third_party/retrace/binary_compatibility.tar.gz.sha1
index 0aafcf6..c3fc5b5 100644
--- a/third_party/retrace/binary_compatibility.tar.gz.sha1
+++ b/third_party/retrace/binary_compatibility.tar.gz.sha1
@@ -1 +1 @@
-23a607be2e3644321df25d73e1db663ad06e79cb
\ No newline at end of file
+8a3bfb12c41dc6fc56545c186729f234f2680b55
\ No newline at end of file