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