Merge commit '8f6c03384ef834acec9a9497024748b9815bb61f' into dev-release
diff --git a/build.gradle b/build.gradle index 338df88..6050eda 100644 --- a/build.gradle +++ b/build.gradle
@@ -35,7 +35,7 @@ ext { androidSupportVersion = '25.4.0' - asmVersion = '7.2' + asmVersion = '8.0' espressoVersion = '3.0.0' fastutilVersion = '7.2.0' guavaVersion = '23.0' @@ -279,6 +279,7 @@ examplesKotlinCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" kotlinR8TestResourcesCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" errorprone("com.google.errorprone:error_prone_core:$errorproneVersion") + testImplementation "org.jetbrains.kotlin:kotlin-reflect:1.3.31" } def r8LibPath = "$buildDir/libs/r8lib.jar" @@ -404,6 +405,7 @@ "benchmarks/kotlin-benches", "chrome/chrome_180917_ffbaa8", "chrome/chrome_200430", + "chrome/monochrome_public_minimal_apks/chrome_200520", "classlib", "cf_segments", "desugar/desugar_20180308", @@ -2398,4 +2400,4 @@ println commandLine.join(' ') } } -} \ No newline at end of file +}
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java index 5781482..cf475fa 100644 --- a/src/main/java/com/android/tools/r8/D8Command.java +++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -393,7 +393,6 @@ assert internal.neverMergePrefixes.contains("j$."); // Assert some of R8 optimizations are disabled. - assert !internal.enableDynamicTypeOptimization; assert !internal.enableInlining; assert !internal.enableClassInlining; assert !internal.enableHorizontalClassMerging;
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java index 3c7fdd2..ac56a10 100644 --- a/src/main/java/com/android/tools/r8/D8CommandParser.java +++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -130,7 +130,10 @@ + ".", " --intermediate # Compile an intermediate result intended for later", " # merging.", - " --file-per-class # Produce a separate dex file per input class", + " --file-per-class # Produce a separate dex file per class.", + " # Synthetic classes are in their own file.", + " --file-per-class-file # Produce a separate dex file per input .class file.", + " # Synthetic classes are with their originating class.", " --no-desugaring # Force disable desugaring.", " --desugared-lib <file> # Specify desugared library configuration.", " # <file> is a desugared library configuration (json).", @@ -210,6 +213,8 @@ compilationMode = CompilationMode.RELEASE; } else if (arg.equals("--file-per-class")) { outputMode = OutputMode.DexFilePerClass; + } else if (arg.equals("--file-per-class-file")) { + outputMode = OutputMode.DexFilePerClassFile; } else if (arg.equals("--classfile")) { outputMode = OutputMode.ClassFile; } else if (arg.equals("--output")) {
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java index b1072df..9b0f9de 100644 --- a/src/main/java/com/android/tools/r8/L8Command.java +++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -162,7 +162,6 @@ assert !internal.passthroughDexCode; // Assert some of R8 optimizations are disabled. - assert !internal.enableDynamicTypeOptimization; assert !internal.enableInlining; assert !internal.enableClassInlining; assert !internal.enableHorizontalClassMerging;
diff --git a/src/main/java/com/android/tools/r8/L8CommandParser.java b/src/main/java/com/android/tools/r8/L8CommandParser.java index 0802127..e083cec 100644 --- a/src/main/java/com/android/tools/r8/L8CommandParser.java +++ b/src/main/java/com/android/tools/r8/L8CommandParser.java
@@ -18,8 +18,8 @@ public class L8CommandParser extends BaseCompilerCommandParser<L8Command, L8Command.Builder> { - private static final Set<String> OPTIONS_WITH_PARAMETER = - ImmutableSet.of("--output", "--lib", MIN_API_FLAG, "--desugared-lib", THREAD_COUNT_FLAG); + private static final Set<String> OPTIONS_WITH_PARAMETER = ImmutableSet.of( + "--output", "--lib", MIN_API_FLAG, "--desugared-lib", THREAD_COUNT_FLAG, "--pg-conf"); public static void main(String[] args) throws CompilationFailedException { L8Command command = parse(args, Origin.root()).build();
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 8406706..34677e0 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -515,10 +515,8 @@ assert changed; appView.setVerticallyMergedClasses(verticalClassMerger.getMergedClasses()); application = application.asDirect().rewrittenWithLens(lens); - lens.initializeCacheForLookupMethodInAllContexts(); appViewWithLiveness.setAppInfo( appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens)); - lens.unsetCacheForLookupMethodInAllContexts(); } timing.end(); } @@ -544,6 +542,7 @@ timing.begin("UninstantiatedTypeOptimization"); UninstantiatedTypeOptimizationGraphLense lens = new UninstantiatedTypeOptimization(appViewWithLiveness) + .strenghtenOptimizationInfo() .run( new MethodPoolCollection(appViewWithLiveness, subtypingInfo), executorService, @@ -697,7 +696,7 @@ appView.withGeneratedMessageLiteBuilderShrinker( shrinker -> - shrinker.removeDeadBuilderReferencesFromDynamicMethods( + shrinker.rewriteDeadBuilderReferencesFromDynamicMethods( appViewWithLiveness, executorService, timing)); if (options.isShrinking()) { @@ -914,6 +913,10 @@ shrinker -> shrinker.setDeadProtoTypes(appViewWithLiveness.appInfo().getDeadProtoTypes())); } + appView.withGeneratedMessageLiteBuilderShrinker( + shrinker -> + shrinker.rewriteDeadBuilderReferencesFromDynamicMethods( + appViewWithLiveness, executorService, timing)); return appViewWithLiveness; }
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java index 656f3aa..5abbc25 100644 --- a/src/main/java/com/android/tools/r8/R8Command.java +++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -842,7 +842,6 @@ ? LineNumberOptimization.ON : LineNumberOptimization.OFF; - assert internal.enableDynamicTypeOptimization || !proguardConfiguration.isOptimizing(); assert internal.enableHorizontalClassMerging || !proguardConfiguration.isOptimizing(); assert !internal.enableTreeShakingOfLibraryMethodOverrides; assert internal.enableVerticalClassMerging || !proguardConfiguration.isOptimizing();
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java index 6220eb4..8c93579 100644 --- a/src/main/java/com/android/tools/r8/R8CommandParser.java +++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -33,6 +33,7 @@ "--pg-conf", "--pg-map-output", "--desugared-lib", + "--desugared-lib-pg-conf-output", THREAD_COUNT_FLAG); private static final Set<String> OPTIONS_WITH_TWO_PARAMETERS = ImmutableSet.of("--feature"); @@ -83,6 +84,8 @@ + " <file>.", " --desugared-lib <file> # Specify desugared library configuration.", " # <file> is a desugared library configuration (json).", + " --desugared-lib-pg-conf-output <file> # Output the Proguard configuration ", + " # needed by L8 to <file>.", " --no-tree-shaking # Force disable tree shaking of unreachable classes.", " --no-minification # Force disable minification of names.", " --no-data-resources # Ignore all data resources.", @@ -252,6 +255,9 @@ builder.setProguardMapOutputPath(Paths.get(nextArg)); } else if (arg.equals("--desugared-lib")) { builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg))); + } else if (arg.equals("--desugared-lib-pg-conf-output")) { + StringConsumer consumer = new StringConsumer.FileConsumer(Paths.get(nextArg)); + builder.setDesugaredLibraryKeepRuleConsumer(consumer); } else if (arg.equals("--no-data-resources")) { state.includeDataResources = false; } else if (arg.startsWith("--")) {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java index 8ee86f3..5d102bdf 100644 --- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java +++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -455,7 +455,7 @@ private void insertAttributeAnnotations() { // Convert inner-class attributes to DEX annotations for (DexProgramClass clazz : application.classes()) { - EnclosingMethodAttribute enclosingMethod = clazz.getEnclosingMethod(); + EnclosingMethodAttribute enclosingMethod = clazz.getEnclosingMethodAttribute(); List<InnerClassAttribute> innerClasses = clazz.getInnerClasses(); if (enclosingMethod == null && innerClasses.isEmpty()) { continue; @@ -521,7 +521,7 @@ } // Clear the attribute structures now that they are represented in annotations. - clazz.clearEnclosingMethod(); + clazz.clearEnclosingMethodAttribute(); clazz.clearInnerClasses(); } }
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java index 26dca03..a416e51 100644 --- a/src/main/java/com/android/tools/r8/dex/DexParser.java +++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -642,8 +642,7 @@ int size, DexMethodAnnotation[] annotations, DexParameterAnnotation[] parameters, - boolean skipCodes, - boolean ensureNonAbstract) { + boolean skipCodes) { DexEncodedMethod[] methods = new DexEncodedMethod[size]; int methodIndex = 0; MemberAnnotationIterator<DexMethod, DexAnnotationSet> annotationIterator = @@ -661,19 +660,13 @@ code = codes.get(codeOff); } DexMethod method = indexedItems.getMethod(methodIndex); - DexEncodedMethod encodedMethod = + methods[i] = new DexEncodedMethod( method, accessFlags, annotationIterator.getNextFor(method), parameterAnnotationsIterator.getNextFor(method), code); - if (accessFlags.isAbstract() && ensureNonAbstract) { - accessFlags.unsetAbstract(); - assert !options.isGeneratingClassFiles(); - encodedMethod = encodedMethod.toEmptyThrowingMethodDex(false); - } - methods[i] = encodedMethod; } return methods; } @@ -754,17 +747,13 @@ directMethodsSize, annotationsDirectory.methods, annotationsDirectory.parameters, - classKind != ClassKind.PROGRAM, - options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug() - && !flags.isAbstract()); + classKind != ClassKind.PROGRAM); virtualMethods = readMethods( virtualMethodsSize, annotationsDirectory.methods, annotationsDirectory.parameters, - classKind != ClassKind.PROGRAM, - options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug() - && !flags.isAbstract()); + classKind != ClassKind.PROGRAM); } AttributesAndAnnotations attrs =
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java index 0b5d100..3706dec 100644 --- a/src/main/java/com/android/tools/r8/graph/AppInfo.java +++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -21,9 +21,6 @@ private final DexApplication app; private final DexItemFactory dexItemFactory; - // TODO(b/151804585): Remove this cache. - private final ConcurrentHashMap<DexType, Map<DexField, DexEncodedField>> fieldDefinitionsCache; - // For some optimizations, e.g. optimizing synthetic classes, we may need to resolve the current // class being optimized. private final ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses; @@ -33,31 +30,28 @@ private final BooleanBox obsolete; public AppInfo(DexApplication application) { - this(application, new ConcurrentHashMap<>(), new ConcurrentHashMap<>(), new BooleanBox()); + this(application, new ConcurrentHashMap<>(), new BooleanBox()); } // For desugaring. protected AppInfo(AppInfo appInfo) { - this(appInfo.app, appInfo.fieldDefinitionsCache, appInfo.synthesizedClasses, appInfo.obsolete); + this(appInfo.app, appInfo.synthesizedClasses, appInfo.obsolete); } // For AppInfoWithLiveness. protected AppInfo(AppInfoWithClassHierarchy previous) { this( ((AppInfo) previous).app, - new ConcurrentHashMap<>(((AppInfo) previous).fieldDefinitionsCache), new ConcurrentHashMap<>(((AppInfo) previous).synthesizedClasses), new BooleanBox()); } private AppInfo( DexApplication application, - ConcurrentHashMap<DexType, Map<DexField, DexEncodedField>> fieldDefinitionsCache, ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses, BooleanBox obsolete) { this.app = application; this.dexItemFactory = application.dexItemFactory; - this.fieldDefinitionsCache = fieldDefinitionsCache; this.synthesizedClasses = synthesizedClasses; this.obsolete = obsolete; } @@ -102,7 +96,6 @@ assert checkIfObsolete(); assert clazz.type.isD8R8SynthesizedClassType(); DexProgramClass previous = synthesizedClasses.put(clazz.type, clazz); - invalidateFieldCacheFor(clazz.type); assert previous == null || previous == clazz; } @@ -131,19 +124,6 @@ } @Override - public DexDefinition definitionFor(DexReference reference) { - assert checkIfObsolete(); - if (reference.isDexType()) { - return definitionFor(reference.asDexType()); - } - if (reference.isDexMethod()) { - return definitionFor(reference.asDexMethod()); - } - assert reference.isDexField(); - return definitionFor(reference.asDexField()); - } - - @Override public DexClass definitionFor(DexType type) { return definitionForWithoutExistenceAssert(type); } @@ -181,6 +161,7 @@ return definition == null ? Origin.unknown() : definition.origin; } + @Deprecated @Override public DexEncodedMethod definitionFor(DexMethod method) { assert checkIfObsolete(); @@ -195,20 +176,6 @@ return clazz.getMethodCollection().getMethod(method); } - @Override - public DexEncodedField definitionFor(DexField field) { - assert checkIfObsolete(); - return getFieldDefinitions(field.holder).get(field); - } - - private Map<DexField, DexEncodedField> getFieldDefinitions(DexType type) { - return fieldDefinitionsCache.computeIfAbsent(type, this::computeFieldDefinitions); - } - - public void invalidateFieldCacheFor(DexType type) { - fieldDefinitionsCache.remove(type); - } - /** * Lookup static method on the method holder, or answers null. *
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java index 8077269..b4534f5 100644 --- a/src/main/java/com/android/tools/r8/graph/AppView.java +++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -226,16 +226,7 @@ return this.sourceDebugExtensions.get(clazz); } - @Override - public final DexDefinition definitionFor(DexReference reference) { - return appInfo().definitionFor(reference); - } - - @Override - public final DexEncodedField definitionFor(DexField field) { - return appInfo().definitionFor(field); - } - + @Deprecated @Override public final DexEncodedMethod definitionFor(DexMethod method) { return appInfo().definitionFor(method);
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java index 030497d..e33792c 100644 --- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java +++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -167,6 +167,10 @@ return isSet(Constants.ACC_ABSTRACT); } + public void demoteFromAbstract() { + demote(Constants.ACC_ABSTRACT); + } + public void setAbstract() { set(Constants.ACC_ABSTRACT); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java index c2cdc32..09aeb11 100644 --- a/src/main/java/com/android/tools/r8/graph/DexClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -176,12 +176,10 @@ private boolean verifyNoAbstractMethodsOnNonAbstractClasses( Iterable<DexEncodedMethod> methods, InternalOptions options) { - if (options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()) { - if (!isAbstract()) { - for (DexEncodedMethod method : methods) { - assert !method.isAbstract() - : "Non-abstract method on abstract class: `" + method.method.toSourceString() + "`"; - } + if (options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug() && !isAbstract()) { + for (DexEncodedMethod method : methods) { + assert !method.isAbstract() + : "Non-abstract method on abstract class: `" + method.method.toSourceString() + "`"; } } return true; @@ -406,6 +404,15 @@ return methodCollection.getVirtualMethod(predicate); } + /** Find member in this class matching {@param member}. */ + @SuppressWarnings("unchecked") + public <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> D lookupMember( + DexMember<D, R> member) { + DexEncodedMember<?, ?> definition = + member.isDexField() ? lookupField(member.asDexField()) : lookupMethod(member.asDexMethod()); + return (D) definition; + } + /** Find method in this class matching {@param method}. */ public DexEncodedMethod lookupMethod(DexMethod method) { return methodCollection.getMethod(method); @@ -700,15 +707,15 @@ return innerClasses; } - public EnclosingMethodAttribute getEnclosingMethod() { + public EnclosingMethodAttribute getEnclosingMethodAttribute() { return enclosingMethod; } - public void clearEnclosingMethod() { + public void clearEnclosingMethodAttribute() { enclosingMethod = null; } - public void removeEnclosingMethod(Predicate<EnclosingMethodAttribute> predicate) { + public void removeEnclosingMethodAttribute(Predicate<EnclosingMethodAttribute> predicate) { if (enclosingMethod != null && predicate.test(enclosingMethod)) { enclosingMethod = null; } @@ -752,7 +759,7 @@ public boolean isMemberClass() { InnerClassAttribute innerClass = getInnerClassAttributeForThisClass(); boolean isMember = innerClass != null && innerClass.getOuter() != null && innerClass.isNamed(); - assert !isMember || getEnclosingMethod() == null; + assert !isMember || getEnclosingMethodAttribute() == null; return isMember; }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java index 3d4e92b..214e41c 100644 --- a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java +++ b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
@@ -6,18 +6,9 @@ public interface DexDefinitionSupplier { - DexDefinition definitionFor(DexReference reference); - - DexEncodedField definitionFor(DexField field); - + @Deprecated DexEncodedMethod definitionFor(DexMethod method); - @SuppressWarnings("unchecked") - default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> - DexEncodedMember<D, R> definitionFor(DexMember<D, R> member) { - return (DexEncodedMember<D, R>) definitionFor((DexReference) member); - } - DexClass definitionFor(DexType type); DexProgramClass definitionForProgramType(DexType type);
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 7071fda..c12f3f4 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -705,6 +705,10 @@ return builder.toString(); } + public void clearParameterAnnotations() { + parameterAnnotationsList = ParameterAnnotationsList.empty(); + } + public String toSmaliString(ClassNameMapper naming) { checkIfObsolete(); StringBuilder builder = new StringBuilder(); @@ -767,17 +771,7 @@ null); } - public DexCode buildEmptyThrowingDexCode() { - Instruction insn[] = {new Const(0, 0), new Throw(0)}; - return generateCodeFromTemplate(1, 0, insn); - } - public DexEncodedMethod toEmptyThrowingMethod(InternalOptions options) { - // Note that we are not marking this instance obsolete, since this util is only used by - // TreePruner while keeping non-live yet targeted, empty method. Such method can be retrieved - // again only during the 2nd round of tree sharking, and seeing an obsolete empty body v.s. - // seeing this empty throwing code do not matter. - // If things are changed, the cure point is obsolete instances inside RootSet. return options.isGeneratingClassFiles() ? toEmptyThrowingMethodCf() : toEmptyThrowingMethodDex(true); @@ -791,7 +785,28 @@ if (setIsLibraryOverride && isNonPrivateVirtualMethod()) { builder.setIsLibraryMethodOverride(isLibraryMethodOverride()); } - return builder.build(); + DexEncodedMethod result = builder.build(); + setObsolete(); + return result; + } + + private DexEncodedMethod toEmptyThrowingMethodCf() { + checkIfObsolete(); + assert !shouldNotHaveCode(); + Builder builder = builder(this); + builder.setCode(buildEmptyThrowingCfCode()); + if (isNonPrivateVirtualMethod()) { + builder.setIsLibraryMethodOverride(isLibraryMethodOverride()); + } + DexEncodedMethod result = builder.build(); + setObsolete(); + return result; + } + + public Code buildEmptyThrowingCode(InternalOptions options) { + return options.isGeneratingClassFiles() + ? buildEmptyThrowingCfCode() + : buildEmptyThrowingDexCode(); } public CfCode buildEmptyThrowingCfCode() { @@ -805,17 +820,9 @@ Collections.emptyList()); } - private DexEncodedMethod toEmptyThrowingMethodCf() { - checkIfObsolete(); - assert !shouldNotHaveCode(); - Builder builder = builder(this); - builder.setCode(buildEmptyThrowingCfCode()); - if (isNonPrivateVirtualMethod()) { - builder.setIsLibraryMethodOverride(isLibraryMethodOverride()); - } - // Note that we are not marking this instance obsolete: - // refer to Dex-backend version of this method above. - return builder.build(); + public DexCode buildEmptyThrowingDexCode() { + Instruction insn[] = {new Const(0, 0), new Throw(0)}; + return generateCodeFromTemplate(1, 0, insn); } public DexEncodedMethod toMethodThatLogsError(AppView<?> appView) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java index d10a2c8..84d581e 100644 --- a/src/main/java/com/android/tools/r8/graph/DexField.java +++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -6,6 +6,9 @@ import com.android.tools.r8.dex.IndexedItemCollection; import com.android.tools.r8.errors.CompilationError; import com.android.tools.r8.naming.NamingLens; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; public class DexField extends DexMember<DexEncodedField, DexField> { @@ -23,6 +26,36 @@ } @Override + public DexEncodedField lookupOnClass(DexClass clazz) { + return clazz != null ? clazz.lookupField(this) : null; + } + + @Override + public <T> T apply( + Function<DexType, T> classConsumer, + Function<DexField, T> fieldConsumer, + Function<DexMethod, T> methodConsumer) { + return fieldConsumer.apply(this); + } + + @Override + public void accept( + Consumer<DexType> classConsumer, + Consumer<DexField> fieldConsumer, + Consumer<DexMethod> methodConsumer) { + fieldConsumer.accept(this); + } + + @Override + public <T> void accept( + BiConsumer<DexType, T> classConsumer, + BiConsumer<DexField, T> fieldConsumer, + BiConsumer<DexMethod, T> methodConsumer, + T arg) { + fieldConsumer.accept(this, arg); + } + + @Override public int computeHashCode() { return holder.hashCode() + type.hashCode() * 7
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java index 1a318c0..d61746d 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -147,6 +147,10 @@ public final DexString boxedNumberDescriptor = createString("Ljava/lang/Number;"); public final DexString boxedVoidDescriptor = createString("Ljava/lang/Void;"); + public final DexString waitMethodName = createString("wait"); + public final DexString notifyMethodName = createString("notify"); + public final DexString notifyAllMethodName = createString("notifyAll"); + public final DexString unboxBooleanMethodName = createString("booleanValue"); public final DexString unboxByteMethodName = createString("byteValue"); public final DexString unboxCharMethodName = createString("charValue"); @@ -164,6 +168,7 @@ public final DexString startsWithMethodName = createString("startsWith"); public final DexString endsWithMethodName = createString("endsWith"); public final DexString equalsMethodName = createString("equals"); + public final DexString hashCodeMethodName = createString("hashCode"); public final DexString equalsIgnoreCaseMethodName = createString("equalsIgnoreCase"); public final DexString contentEqualsMethodName = createString("contentEquals"); public final DexString indexOfMethodName = createString("indexOf"); @@ -1269,6 +1274,7 @@ public final DexMethod toString; public final DexMethod compareTo; public final DexMethod equals; + public final DexMethod hashCode; public final DexMethod constructor = createMethod(enumType, createProto(voidType, stringType, intType), constructorMethodName); @@ -1309,6 +1315,8 @@ equalsMethodName, booleanDescriptor, new DexString[] {objectDescriptor}); + hashCode = + createMethod(enumDescriptor, hashCodeMethodName, intDescriptor, DexString.EMPTY_ARRAY); } public boolean isValuesMethod(DexMethod method, DexClass enumClass) { @@ -1495,7 +1503,7 @@ createMethod(stringDescriptor, compareToIgnoreCaseMethodName, intDescriptor, needsOneString); - hashCode = createMethod(stringType, createProto(intType), "hashCode"); + hashCode = createMethod(stringType, createProto(intType), hashCodeMethodName); valueOf = createMethod( stringDescriptor, valueOfMethodName, stringDescriptor, needsOneObject); toString = createMethod(
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java index d04808b..02fd431 100644 --- a/src/main/java/com/android/tools/r8/graph/DexMember.java +++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -13,6 +13,10 @@ this.holder = holder; } + public DexEncodedMember<?, ?> lookupOnClass(DexClass clazz) { + return clazz != null ? clazz.lookupMember(this) : null; + } + public abstract boolean match(R entry); public abstract boolean match(D entry);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java index d3dd5fc..8a50931 100644 --- a/src/main/java/com/android/tools/r8/graph/DexMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -11,6 +11,9 @@ import com.android.tools.r8.references.TypeReference; import java.util.ArrayList; import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; public class DexMethod extends DexMember<DexEncodedMethod, DexMethod> { @@ -29,6 +32,36 @@ } @Override + public <T> T apply( + Function<DexType, T> classConsumer, + Function<DexField, T> fieldConsumer, + Function<DexMethod, T> methodConsumer) { + return methodConsumer.apply(this); + } + + @Override + public void accept( + Consumer<DexType> classConsumer, + Consumer<DexField> fieldConsumer, + Consumer<DexMethod> methodConsumer) { + methodConsumer.accept(this); + } + + @Override + public <T> void accept( + BiConsumer<DexType, T> classConsumer, + BiConsumer<DexField, T> fieldConsumer, + BiConsumer<DexMethod, T> methodConsumer, + T arg) { + methodConsumer.accept(this, arg); + } + + @Override + public DexEncodedMethod lookupOnClass(DexClass clazz) { + return clazz != null ? clazz.lookupMember(this) : null; + } + + @Override public String toString() { return "Method " + holder + "." + name + " " + proto.toString(); } @@ -191,4 +224,8 @@ return name == dexItemFactory.deserializeLambdaMethodName && proto == dexItemFactory.deserializeLambdaMethodProto; } + + public boolean isInstanceInitializer(DexDefinitionSupplier definitions) { + return definitions.dexItemFactory().isConstructor(this); + } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java index 900712b..423acef 100644 --- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -133,6 +133,10 @@ synthesizedDirectlyFrom.forEach(this::addSynthesizedFrom); } + public void forEachProgramField(Consumer<ProgramField> consumer) { + forEachField(field -> consumer.accept(new ProgramField(this, field))); + } + public void forEachProgramMethod(Consumer<ProgramMethod> consumer) { forEachProgramMethodMatching(alwaysTrue(), consumer); } @@ -242,8 +246,8 @@ if (interfaces != null) { interfaces.collectIndexedItems(indexedItems, method, instructionOffset); } - if (getEnclosingMethod() != null) { - getEnclosingMethod().collectIndexedItems(indexedItems); + if (getEnclosingMethodAttribute() != null) { + getEnclosingMethodAttribute().collectIndexedItems(indexedItems); } for (InnerClassAttribute attribute : getInnerClasses()) { attribute.collectIndexedItems(indexedItems); @@ -269,7 +273,7 @@ @Override void collectMixedSectionItems(MixedSectionCollection mixedItems) { - assert getEnclosingMethod() == null; + assert getEnclosingMethodAttribute() == null; assert getInnerClasses().isEmpty(); if (hasAnnotations()) { mixedItems.setAnnotationsDirectoryForClass(this, new DexAnnotationDirectory(this)); @@ -278,7 +282,7 @@ @Override public void addDependencies(MixedSectionCollection collector) { - assert getEnclosingMethod() == null; + assert getEnclosingMethodAttribute() == null; assert getInnerClasses().isEmpty(); // We only have a class data item if there are methods or fields. if (hasMethodsOrFields()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexReference.java b/src/main/java/com/android/tools/r8/graph/DexReference.java index 42286db..c37c9de 100644 --- a/src/main/java/com/android/tools/r8/graph/DexReference.java +++ b/src/main/java/com/android/tools/r8/graph/DexReference.java
@@ -3,7 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.graph; -import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; @@ -13,6 +14,22 @@ */ public abstract class DexReference extends IndexedDexItem { + public abstract <T> T apply( + Function<DexType, T> classConsumer, + Function<DexField, T> fieldConsumer, + Function<DexMethod, T> methodConsumer); + + public abstract void accept( + Consumer<DexType> classConsumer, + Consumer<DexField> fieldConsumer, + Consumer<DexMethod> methodConsumer); + + public abstract <T> void accept( + BiConsumer<DexType, T> classConsumer, + BiConsumer<DexField, T> fieldConsumer, + BiConsumer<DexMethod, T> methodConsumer, + T arg); + public boolean isDexType() { return false; } @@ -49,22 +66,6 @@ return DexItem.filter(stream, DexReference.class); } - public DexDefinition toDefinition(AppInfo appInfo) { - if (isDexType()) { - return appInfo.definitionFor(asDexType()); - } else if (isDexField()) { - return appInfo.definitionFor(asDexField()); - } else { - assert isDexMethod(); - return appInfo.definitionFor(asDexMethod()); - } - } - - public static Stream<DexDefinition> mapToDefinition( - Stream<DexReference> references, AppInfo appInfo) { - return references.map(r -> r.toDefinition(appInfo)).filter(Objects::nonNull); - } - private static <T extends DexReference> Stream<T> filter( Stream<DexReference> stream, Predicate<DexReference> pred,
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java index 01c5298..fc7ae3e 100644 --- a/src/main/java/com/android/tools/r8/graph/DexType.java +++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -24,14 +24,15 @@ import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.InternalOptions.OutlineOptions; -import com.android.tools.r8.utils.Pair; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; public class DexType extends DexReference implements PresortedComparable<DexType> { @@ -117,6 +118,31 @@ } @Override + public <T> T apply( + Function<DexType, T> classConsumer, + Function<DexField, T> fieldConsumer, + Function<DexMethod, T> methodConsumer) { + return classConsumer.apply(this); + } + + @Override + public void accept( + Consumer<DexType> classConsumer, + Consumer<DexField> fieldConsumer, + Consumer<DexMethod> methodConsumer) { + classConsumer.accept(this); + } + + @Override + public <T> void accept( + BiConsumer<DexType, T> classConsumer, + BiConsumer<DexField, T> fieldConsumer, + BiConsumer<DexMethod, T> methodConsumer, + T arg) { + classConsumer.accept(this, arg); + } + + @Override public String toSourceString() { if (toStringCache == null) { // TODO(ager): Pass in a ProguardMapReader to map names back to original names. @@ -409,15 +435,4 @@ public String getPackageName() { return DescriptorUtils.getPackageNameFromBinaryName(toBinaryName()); } - - public Pair<String, String> rewritingPrefixIn(Map<String, String> map) { - // TODO(b/134732760): Rewrite this to use descriptors and not Strings. - String javaClassName = this.toString(); - for (String rewritePrefix : map.keySet()) { - if (javaClassName.startsWith(rewritePrefix)) { - return new Pair<>(rewritePrefix, map.get(rewritePrefix)); - } - } - return null; - } }
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java index 5f0a349..3699d2e 100644 --- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java +++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -73,24 +73,7 @@ return classpathClasses; } - @Override - public DexDefinition definitionFor(DexReference reference) { - if (reference.isDexType()) { - return definitionFor(reference.asDexType()); - } - if (reference.isDexMethod()) { - return definitionFor(reference.asDexMethod()); - } - assert reference.isDexField(); - return definitionFor(reference.asDexField()); - } - - @Override - public DexEncodedField definitionFor(DexField field) { - DexClass clazz = definitionFor(field.holder); - return clazz != null ? clazz.lookupField(field) : null; - } - + @Deprecated @Override public DexEncodedMethod definitionFor(DexMethod method) { DexClass clazz = definitionFor(method.holder);
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java index ea55ec4..0bcbc76 100644 --- a/src/main/java/com/android/tools/r8/graph/GraphLense.java +++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -4,6 +4,8 @@ package com.android.tools.r8.graph; import com.android.tools.r8.ir.code.Invoke.Type; +import com.android.tools.r8.shaking.KeepInfoCollection; +import com.android.tools.r8.utils.SetUtils; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.ImmutableMap; @@ -12,13 +14,11 @@ import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap; import it.unimi.dsi.fastutil.objects.Object2BooleanMap; -import java.util.ArrayDeque; -import java.util.Collections; -import java.util.Deque; +import java.util.ArrayList; import java.util.IdentityHashMap; +import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Function; import java.util.function.Supplier; /** @@ -189,13 +189,6 @@ public abstract RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method); - // Context sensitive graph lenses should override this method. - public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) { - DexMethod result = lookupMethod(method); - assert result != null; - return ImmutableSet.of(result); - } - public abstract DexField lookupField(DexField field); public DexMethod lookupGetFieldForMethod(DexField field, DexMethod context) { @@ -263,6 +256,14 @@ return true; } + public <T extends DexReference> boolean assertPinnedNotModified(KeepInfoCollection keepInfo) { + List<DexReference> pinnedItems = new ArrayList<>(); + keepInfo.forEachPinnedType(pinnedItems::add); + keepInfo.forEachPinnedMethod(pinnedItems::add); + keepInfo.forEachPinnedField(pinnedItems::add); + return assertReferencesNotModified(pinnedItems); + } + public <T extends DexReference> boolean assertReferencesNotModified(Iterable<T> references) { for (DexReference reference : references) { if (reference.isDexField()) { @@ -280,37 +281,40 @@ return true; } - public ImmutableSet<DexReference> rewriteReferencesConservatively(Set<DexReference> original) { - ImmutableSet.Builder<DexReference> builder = ImmutableSet.builder(); - for (DexReference item : original) { - if (item.isDexMethod()) { - DexMethod method = item.asDexMethod(); - builder.addAll(lookupMethodInAllContexts(method)); - } else { - builder.add(lookupReference(item)); - } + public DexReference rewriteReference(DexReference reference) { + if (reference.isDexField()) { + return getRenamedFieldSignature(reference.asDexField()); } - return builder.build(); + if (reference.isDexMethod()) { + return getRenamedMethodSignature(reference.asDexMethod()); + } + assert reference.isDexType(); + return lookupType(reference.asDexType()); } - public Object2BooleanMap<DexReference> rewriteReferencesConservatively( - Object2BooleanMap<DexReference> original) { - Object2BooleanMap<DexReference> result = new Object2BooleanArrayMap<>(); - for (Object2BooleanMap.Entry<DexReference> entry : original.object2BooleanEntrySet()) { - DexReference item = entry.getKey(); - if (item.isDexMethod()) { - DexMethod method = item.asDexMethod(); - for (DexMethod candidate : lookupMethodInAllContexts(method)) { - result.put(candidate, entry.getBooleanValue()); - } - } else { - result.put(lookupReference(item), entry.getBooleanValue()); - } + public Set<DexReference> rewriteReferences(Set<DexReference> references) { + Set<DexReference> result = SetUtils.newIdentityHashSet(references.size()); + for (DexReference reference : references) { + result.add(rewriteReference(reference)); } return result; } - public ImmutableSortedSet<DexMethod> rewriteMethodsWithRenamedSignature(Set<DexMethod> methods) { + public <T> ImmutableMap<DexReference, T> rewriteReferenceKeys(Map<DexReference, T> map) { + ImmutableMap.Builder<DexReference, T> builder = ImmutableMap.builder(); + map.forEach((reference, value) -> builder.put(rewriteReference(reference), value)); + return builder.build(); + } + + public Object2BooleanMap<DexReference> rewriteReferenceKeys(Object2BooleanMap<DexReference> map) { + Object2BooleanMap<DexReference> result = new Object2BooleanArrayMap<>(); + for (Object2BooleanMap.Entry<DexReference> entry : map.object2BooleanEntrySet()) { + result.put(rewriteReference(entry.getKey()), entry.getBooleanValue()); + } + return result; + } + + public ImmutableSortedSet<DexMethod> rewriteMethods(Set<DexMethod> methods) { ImmutableSortedSet.Builder<DexMethod> builder = new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare); for (DexMethod method : methods) { @@ -319,25 +323,24 @@ return builder.build(); } - public ImmutableSortedSet<DexMethod> rewriteMethodsConservatively(Set<DexMethod> original) { - ImmutableSortedSet.Builder<DexMethod> builder = + public <T> ImmutableMap<DexField, T> rewriteFieldKeys(Map<DexField, T> map) { + ImmutableMap.Builder<DexField, T> builder = ImmutableMap.builder(); + map.forEach((field, value) -> builder.put(getRenamedFieldSignature(field), value)); + return builder.build(); + } + + public ImmutableSet<DexType> rewriteTypes(Set<DexType> types) { + ImmutableSortedSet.Builder<DexType> builder = new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare); - if (isContextFreeForMethods()) { - for (DexMethod item : original) { - builder.add(lookupMethod(item)); - } - } else { - for (DexMethod item : original) { - builder.addAll(lookupMethodInAllContexts(item)); - } + for (DexType type : types) { + builder.add(lookupType(type)); } return builder.build(); } - public static <T extends DexReference, S> ImmutableMap<T, S> rewriteReferenceKeys( - Map<T, S> original, Function<T, T> rewrite) { - ImmutableMap.Builder<T, S> builder = ImmutableMap.builder(); - original.forEach((item, value) -> builder.put(rewrite.apply(item), value)); + public <T> ImmutableMap<DexType, T> rewriteTypeKeys(Map<DexType, T> map) { + ImmutableMap.Builder<DexType, T> builder = ImmutableMap.builder(); + map.forEach((type, value) -> builder.put(lookupType(type), value)); return builder.build(); } @@ -380,51 +383,13 @@ continue; } DexMethod originalMethod = getOriginalMethodSignature(method.method); - assert originalMethods.contains(originalMethod) - || verifyIsBridgeMethod( - originalMethod, originalApplication, originalMethods, dexItemFactory) - : "Unable to map method `" - + originalMethod.toSourceString() - + "` back to original program"; + assert originalMethods.contains(originalMethod); } } return true; } - // Check if `method` is a bridge method for a method that is in the original application. - // This is needed because member rebinding synthesizes bridge methods for visibility. - private static boolean verifyIsBridgeMethod( - DexMethod method, - DexApplication originalApplication, - Set<DexMethod> originalMethods, - DexItemFactory dexItemFactory) { - Deque<DexType> worklist = new ArrayDeque<>(); - Set<DexType> visited = Sets.newIdentityHashSet(); - worklist.add(method.holder); - while (!worklist.isEmpty()) { - DexType holder = worklist.removeFirst(); - if (!visited.add(holder)) { - // Already visited previously. - continue; - } - DexMethod targetMethod = dexItemFactory.createMethod(holder, method.proto, method.name); - if (originalMethods.contains(targetMethod)) { - return true; - } - // Stop traversing upwards if we reach the Object. - if (holder == dexItemFactory.objectType) { - continue; - } - DexClass clazz = originalApplication.definitionFor(holder); - if (clazz != null) { - worklist.add(clazz.superType); - Collections.addAll(worklist, clazz.interfaces.values); - } - } - return false; - } - private static class IdentityGraphLense extends GraphLense { private static IdentityGraphLense INSTANCE = new IdentityGraphLense(); @@ -728,15 +693,6 @@ } @Override - public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) { - Set<DexMethod> result = Sets.newIdentityHashSet(); - for (DexMethod previous : previousLense.lookupMethodInAllContexts(method)) { - result.add(methodMap.getOrDefault(previous, previous)); - } - return result; - } - - @Override public DexField lookupField(DexField field) { DexField previous = previousLense.lookupField(field); return fieldMap.getOrDefault(previous, previous);
diff --git a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java index b40f328..74f9880 100644 --- a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java +++ b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
@@ -85,8 +85,8 @@ DexType context = getOuter(); if (context == null) { DexClass inner = appInfo.definitionFor(getInner()); - if (inner != null && inner.getEnclosingMethod() != null) { - EnclosingMethodAttribute enclosingMethodAttribute = inner.getEnclosingMethod(); + if (inner != null && inner.getEnclosingMethodAttribute() != null) { + EnclosingMethodAttribute enclosingMethodAttribute = inner.getEnclosingMethodAttribute(); if (enclosingMethodAttribute.getEnclosingClass() != null) { context = enclosingMethodAttribute.getEnclosingClass(); } else {
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 1f04cca..7936e33 100644 --- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java +++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -110,7 +110,7 @@ if (application.options.getProguardConfiguration() != null) { ProguardKeepAttributes keep = application.options.getProguardConfiguration().getKeepAttributes(); - if (!keep.sourceFile && !keep.sourceDebugExtension) { + if (!keep.sourceFile && !keep.sourceDebugExtension && !keep.methodParameters) { parsingOptions |= SKIP_DEBUG; } }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java index 06f8c42..6c93947 100644 --- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java +++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -1008,8 +1008,9 @@ || keep.localVariableTypeTable || reachabilitySensitive; boolean lineInfo = keep.lineNumberTable; + boolean methodParaeters = keep.methodParameters; - if (!localsInfo && !lineInfo) { + if (!localsInfo && !lineInfo && !methodParaeters) { parsingOptions |= ClassReader.SKIP_DEBUG; }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java index fbb42a9..01d86a1 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -7,8 +7,10 @@ import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.value.AbstractValue; @@ -106,8 +108,9 @@ } AbstractValue abstractValue = root.getAbstractValue(appView, context); if (abstractValue.isSingleFieldValue()) { - DexEncodedField field = - appView.definitionFor(abstractValue.asSingleFieldValue().getField()); + DexField fieldReference = abstractValue.asSingleFieldValue().getField(); + DexClass holder = appView.definitionForHolder(fieldReference); + DexEncodedField field = fieldReference.lookupOnClass(holder); if (field != null && field.isEnum()) { return false; }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java index be72b24..54d5160 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
@@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.analysis.constant; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.IRCode; @@ -14,6 +16,7 @@ import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.StringSwitch; import com.android.tools.r8.ir.code.Value; +import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.BitSet; import java.util.Deque; @@ -21,6 +24,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; /** * Implementation of Sparse Conditional Constant Propagation from the paper of Wegman and Zadeck @@ -29,6 +33,7 @@ */ public class SparseConditionalConstantPropagation { + private final AppView<?> appView; private final IRCode code; private final Map<Value, LatticeElement> mapping = new HashMap<>(); private final Deque<Value> ssaEdges = new LinkedList<>(); @@ -37,7 +42,8 @@ private final BitSet[] executableFlowEdges; private final BitSet visitedBlocks; - public SparseConditionalConstantPropagation(IRCode code) { + public SparseConditionalConstantPropagation(AppView<?> appView, IRCode code) { + this.appView = appView; this.code = code; nextBlockNumber = code.getHighestBlockNumber() + 1; executableFlowEdges = new BitSet[nextBlockNumber]; @@ -45,7 +51,6 @@ } public void run() { - BasicBlock firstBlock = code.entryBlock(); visitInstructions(firstBlock); @@ -77,8 +82,8 @@ } private void rewriteCode() { + Set<Value> affectedValues = Sets.newIdentityHashSet(); List<BasicBlock> blockToAnalyze = new ArrayList<>(); - mapping.entrySet().stream() .filter(entry -> entry.getValue().isConst()) .forEach( @@ -86,6 +91,7 @@ Value value = entry.getKey(); ConstNumber evaluatedConst = entry.getValue().asConst().getConstNumber(); if (value.definition != evaluatedConst) { + value.addAffectedValuesTo(affectedValues); if (value.isPhi()) { // D8 relies on dead code removal to get rid of the dead phi itself. if (value.hasAnyUsers()) { @@ -112,11 +118,12 @@ } } }); - for (BasicBlock block : blockToAnalyze) { block.deduplicatePhis(); } - + if (!affectedValues.isEmpty()) { + new TypeAnalysis(appView).narrowing(affectedValues); + } code.removeAllDeadAndTrivialPhis(); }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java index fbb6443..1481061 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -64,8 +64,13 @@ FieldAssignmentTracker(AppView<AppInfoWithLiveness> appView) { this.appView = appView; - this.fieldAccessGraph = new FieldAccessGraph(appView); - this.objectAllocationGraph = new ObjectAllocationGraph(appView); + this.fieldAccessGraph = new FieldAccessGraph(); + this.objectAllocationGraph = new ObjectAllocationGraph(); + } + + public void initialize() { + fieldAccessGraph.initialize(appView); + objectAllocationGraph.initialize(appView); initializeAbstractInstanceFieldValues(); } @@ -308,10 +313,11 @@ private final Reference2IntMap<DexEncodedField> pendingFieldWrites = new Reference2IntOpenHashMap<>(); - FieldAccessGraph(AppView<AppInfoWithLiveness> appView) { + FieldAccessGraph() {} + + public void initialize(AppView<AppInfoWithLiveness> appView) { FieldAccessInfoCollection<?> fieldAccessInfoCollection = appView.appInfo().getFieldAccessInfoCollection(); - fieldAccessInfoCollection.flattenAccessContexts(); fieldAccessInfoCollection.forEach( info -> { DexEncodedField field = @@ -356,7 +362,9 @@ private final Reference2IntMap<DexProgramClass> pendingObjectAllocations = new Reference2IntOpenHashMap<>(); - ObjectAllocationGraph(AppView<AppInfoWithLiveness> appView) { + ObjectAllocationGraph() {} + + public void initialize(AppView<AppInfoWithLiveness> appView) { ObjectAllocationInfoCollection objectAllocationInfos = appView.appInfo().getObjectAllocationInfoCollection(); objectAllocationInfos.forEachClassWithKnownAllocationSites(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java index a2a932c..9fe885d 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
@@ -5,7 +5,9 @@ package com.android.tools.r8.ir.analysis.fieldvalueanalysis; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedField; +import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.GraphLense; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.SetUtils; @@ -71,12 +73,14 @@ assert !isEmpty(); ConcreteMutableFieldSet rewrittenSet = new ConcreteMutableFieldSet(); for (DexEncodedField field : fields) { - DexEncodedField rewrittenField = appView.definitionFor(lens.lookupField(field.field)); + DexField rewrittenFieldReference = lens.lookupField(field.field); + DexClass holder = appView.definitionForHolder(rewrittenFieldReference); + DexEncodedField rewrittenField = rewrittenFieldReference.lookupOnClass(holder); if (rewrittenField == null) { assert false; continue; } - rewrittenSet.add(field); + rewrittenSet.add(rewrittenField); } return rewrittenSet; }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java index c64dff4..667c1ff 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -9,7 +9,6 @@ import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexField; -import com.android.tools.r8.graph.DexReference; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.FieldAccessInfo; import com.android.tools.r8.graph.FieldAccessInfoCollection; @@ -26,6 +25,7 @@ import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.DefaultTreePrunerConfiguration; import com.android.tools.r8.shaking.Enqueuer; +import com.android.tools.r8.shaking.KeepInfoCollection; import com.android.tools.r8.shaking.TreePrunerConfiguration; import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.collections.SortedProgramMethodSet; @@ -190,7 +190,7 @@ resolutionResult.asSuccessfulResolution().getResolutionPair().asProgramField(); return field != null && isDeadProtoExtensionField( - field, appInfo.getFieldAccessInfoCollection(), appInfo.getPinnedItems()); + field, appInfo.getFieldAccessInfoCollection(), appInfo.getKeepInfo()); } return false; } @@ -198,8 +198,8 @@ public boolean isDeadProtoExtensionField( ProgramField field, FieldAccessInfoCollection<?> fieldAccessInfoCollection, - Set<DexReference> pinnedItems) { - if (pinnedItems.contains(field.getReference())) { + KeepInfoCollection keepInfo) { + if (keepInfo.getFieldInfo(field).isPinned()) { return false; }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java index deadfa0..c589048 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -4,41 +4,48 @@ package com.android.tools.r8.ir.analysis.proto; +import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; +import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull; +import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull; + import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo; -import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.SubtypingInfo; +import com.android.tools.r8.graph.analysis.EnqueuerAnalysis; import com.android.tools.r8.ir.analysis.type.ClassTypeElement; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; -import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.CheckCast; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionListIterator; +import com.android.tools.r8.ir.code.InvokeDirect; import com.android.tools.r8.ir.code.InvokeVirtual; -import com.android.tools.r8.ir.code.Switch; +import com.android.tools.r8.ir.code.NewInstance; +import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.CallGraph.Node; import com.android.tools.r8.ir.conversion.IRConverter; import com.android.tools.r8.ir.conversion.MethodProcessor; -import com.android.tools.r8.ir.optimize.CodeRewriter; import com.android.tools.r8.ir.optimize.Inliner; import com.android.tools.r8.ir.optimize.Inliner.Reason; -import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer; import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer; import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple; import com.android.tools.r8.ir.optimize.inliner.FixedInliningReasonStrategy; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.Enqueuer; +import com.android.tools.r8.shaking.EnqueuerWorklist; +import com.android.tools.r8.utils.ObjectUtils; import com.android.tools.r8.utils.PredicateSet; import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.Timing; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.IdentityHashMap; @@ -53,6 +60,7 @@ private final AppView<? extends AppInfoWithClassHierarchy> appView; private final ProtoReferences references; + private final boolean enableAggressiveBuilderOptimization; private final Map<DexProgramClass, ProgramMethod> builders = new IdentityHashMap<>(); @@ -60,11 +68,98 @@ AppView<? extends AppInfoWithClassHierarchy> appView, ProtoReferences references) { this.appView = appView; this.references = references; + this.enableAggressiveBuilderOptimization = computeEnableAggressiveBuilderOptimization(); + // If this fails it is likely an unsupported version of the protobuf library. + assert enableAggressiveBuilderOptimization; + } + + private boolean computeEnableAggressiveBuilderOptimization() { + boolean unexpectedGeneratedMessageLiteBuilder = + ObjectUtils.getBooleanOrElse( + appView + .appInfo() + .definitionForWithoutExistenceAssert(references.generatedMessageLiteBuilderType), + clazz -> clazz.getMethodCollection().hasMethods(DexEncodedMethod::isAbstract), + true); + boolean unexpectedGeneratedMessageLiteExtendableBuilder = + ObjectUtils.getBooleanOrElse( + appView + .appInfo() + .definitionForWithoutExistenceAssert( + references.generatedMessageLiteExtendableBuilderType), + clazz -> clazz.getMethodCollection().hasMethods(DexEncodedMethod::isAbstract), + true); + if (unexpectedGeneratedMessageLiteBuilder) { + appView + .options() + .reporter + .warning( + "Unexpected implementation of `" + + references.generatedMessageLiteBuilderType.toSourceString() + + "`: disabling aggressive protobuf builder optimization."); + return false; + } + if (unexpectedGeneratedMessageLiteExtendableBuilder) { + appView + .options() + .reporter + .warning( + "Unexpected implementation of `" + + references.generatedMessageLiteExtendableBuilderType.toSourceString() + + "`: disabling aggressive protobuf builder optimization."); + return false; + } + return true; + } + + public EnqueuerAnalysis createEnqueuerAnalysis() { + Set<DexProgramClass> seen = Sets.newIdentityHashSet(); + return new EnqueuerAnalysis() { + @Override + public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist, Timing timing) { + builders.forEach( + (builder, dynamicMethod) -> { + if (seen.add(builder)) { + // This builder class is never used in the program except from dynamicMethod(), + // which creates an instance of the builder. Instead of creating an instance of the + // builder class, we just instantiate the parent builder class. For this to work, + // we make the parent builder non-abstract. + DexProgramClass superClass = + asProgramClassOrNull(appView.definitionFor(builder.superType)); + assert superClass != null; + superClass.accessFlags.demoteFromAbstract(); + if (superClass.type == references.generatedMessageLiteBuilderType) { + // Manually trace `new GeneratedMessageLite.Builder(DEFAULT_INSTANCE)` since we + // haven't rewritten the code yet. + worklist.enqueueTraceNewInstanceAction( + references.generatedMessageLiteBuilderType, dynamicMethod); + worklist.enqueueTraceInvokeDirectAction( + references.generatedMessageLiteBuilderMethods.constructorMethod, + dynamicMethod); + } else { + assert superClass.type == references.generatedMessageLiteExtendableBuilderType; + // Manually trace `new GeneratedMessageLite.ExtendableBuilder(DEFAULT_INSTANCE)` + // since we haven't rewritten the code yet. + worklist.enqueueTraceNewInstanceAction( + references.generatedMessageLiteExtendableBuilderType, dynamicMethod); + worklist.enqueueTraceInvokeDirectAction( + references.generatedMessageLiteExtendableBuilderMethods.constructorMethod, + dynamicMethod); + } + worklist.enqueueTraceStaticFieldRead( + references.getDefaultInstanceField(dynamicMethod.getHolder()), dynamicMethod); + } + }); + } + }; } /** Returns true if an action was deferred. */ public boolean deferDeadProtoBuilders( DexProgramClass clazz, ProgramMethod method, BooleanSupplier register) { + if (!enableAggressiveBuilderOptimization) { + return false; + } DexEncodedMethod definition = method.getDefinition(); if (references.isDynamicMethod(definition) && references.isGeneratedMessageLiteBuilder(clazz)) { if (register.getAsBoolean()) { @@ -77,40 +172,79 @@ } /** - * Reprocesses each dynamicMethod() that references a dead builder to remove the dead builder + * Reprocesses each dynamicMethod() that references a dead builder to rewrite the dead builder * references. */ - public void removeDeadBuilderReferencesFromDynamicMethods( + public void rewriteDeadBuilderReferencesFromDynamicMethods( AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing) throws ExecutionException { + if (builders.isEmpty()) { + return; + } timing.begin("Remove dead builder references"); AppInfoWithLiveness appInfo = appView.appInfo(); IRConverter converter = new IRConverter(appView, Timing.empty()); - CodeRewriter codeRewriter = new CodeRewriter(appView, converter); - MethodToInvokeSwitchCaseAnalyzer switchCaseAnalyzer = new MethodToInvokeSwitchCaseAnalyzer(); - if (switchCaseAnalyzer.isInitialized()) { - ThreadUtils.processItems( - builders.entrySet(), - entry -> { - if (!appInfo.isLiveProgramClass(entry.getKey())) { - removeDeadBuilderReferencesFromDynamicMethod( - appView, entry.getValue(), converter, codeRewriter, switchCaseAnalyzer); - } - }, - executorService); - } + ThreadUtils.processMap( + builders, + (builder, dynamicMethod) -> { + if (!appInfo.isLiveProgramClass(builder)) { + rewriteDeadBuilderReferencesFromDynamicMethod( + appView, builder, dynamicMethod, converter); + } + }, + executorService); builders.clear(); timing.end(); // Remove dead builder references } - private void removeDeadBuilderReferencesFromDynamicMethod( + private void rewriteDeadBuilderReferencesFromDynamicMethod( AppView<AppInfoWithLiveness> appView, + DexProgramClass builder, ProgramMethod dynamicMethod, - IRConverter converter, - CodeRewriter codeRewriter, - SwitchCaseAnalyzer switchCaseAnalyzer) { + IRConverter converter) { IRCode code = dynamicMethod.buildIR(appView); - codeRewriter.rewriteSwitch(code, switchCaseAnalyzer); + InstructionListIterator instructionIterator = code.instructionListIterator(); + + assert builder.superType == references.generatedMessageLiteBuilderType + || builder.superType == references.generatedMessageLiteExtendableBuilderType; + + DexField defaultInstanceField = references.getDefaultInstanceField(dynamicMethod.getHolder()); + Value builderValue = + code.createValue(ClassTypeElement.create(builder.superType, definitelyNotNull(), appView)); + Value defaultInstanceValue = + code.createValue(ClassTypeElement.create(defaultInstanceField.type, maybeNull(), appView)); + + // Replace `new Message.Builder()` by `new GeneratedMessageLite.Builder()` + // (or `new GeneratedMessageLite.ExtendableBuilder()`). + NewInstance newInstance = + instructionIterator.nextUntil( + instruction -> + instruction.isNewInstance() && instruction.asNewInstance().clazz == builder.type); + assert newInstance != null; + instructionIterator.replaceCurrentInstruction(new NewInstance(builder.superType, builderValue)); + + // Replace `builder.<init>()` by `builder.<init>(Message.DEFAULT_INSTANCE)`. + // + // We may also see an accessibility bridge constructor, because the Builder constructor is + // private. The accessibility bridge takes null as an argument. + InvokeDirect constructorInvoke = + instructionIterator.nextUntil( + instruction -> { + assert instruction.isInvokeDirect() || instruction.isConstNumber(); + return instruction.isInvokeDirect(); + }); + assert constructorInvoke != null; + instructionIterator.replaceCurrentInstruction( + new StaticGet(defaultInstanceValue, defaultInstanceField)); + instructionIterator.setInsertionPosition(constructorInvoke.getPosition()); + instructionIterator.add( + new InvokeDirect( + builder.superType == references.generatedMessageLiteBuilderType + ? references.generatedMessageLiteBuilderMethods.constructorMethod + : references.generatedMessageLiteExtendableBuilderMethods.constructorMethod, + null, + ImmutableList.of(builderValue, defaultInstanceValue))); + converter.removeDeadCodeAndFinalizeIR( dynamicMethod, code, OptimizationFeedbackSimple.getInstance(), Timing.empty()); } @@ -232,51 +366,6 @@ } } - private class MethodToInvokeSwitchCaseAnalyzer extends SwitchCaseAnalyzer { - - private final int newBuilderOrdinal; - - private MethodToInvokeSwitchCaseAnalyzer() { - EnumValueInfoMap enumValueInfoMap = - appView.appInfo().withLiveness().getEnumValueInfoMap(references.methodToInvokeType); - if (enumValueInfoMap != null) { - EnumValueInfo newBuilderValueInfo = - enumValueInfoMap.getEnumValueInfo(references.methodToInvokeMembers.newBuilderField); - if (newBuilderValueInfo != null) { - newBuilderOrdinal = newBuilderValueInfo.ordinal; - return; - } - } - newBuilderOrdinal = -1; - } - - public boolean isInitialized() { - return newBuilderOrdinal >= 0; - } - - @Override - public boolean switchCaseIsUnreachable(Switch theSwitch, int index) { - if (theSwitch.isStringSwitch()) { - assert false : "Unexpected string-switch instruction in dynamicMethod()"; - return false; - } - if (index != newBuilderOrdinal) { - return false; - } - Value switchValue = theSwitch.value(); - if (!switchValue.isDefinedByInstructionSatisfying(Instruction::isInvokeVirtual)) { - return false; - } - InvokeVirtual definition = switchValue.definition.asInvokeVirtual(); - if (definition.getInvokedMethod() != appView.dexItemFactory().enumMethods.ordinal) { - return false; - } - TypeElement enumType = definition.getReceiver().getType(); - return enumType.isClassType() - && enumType.asClassType().getClassType() == references.methodToInvokeType; - } - } - private static class RootSetExtension { private final AppView<? extends AppInfoWithClassHierarchy> appView; @@ -305,8 +394,9 @@ void extend(SubtypingInfo subtypingInfo) { alwaysClassInlineGeneratedMessageLiteBuilders(); - // GeneratedMessageLite heuristics. + // MessageLite and GeneratedMessageLite heuristics. alwaysInlineCreateBuilderFromGeneratedMessageLite(); + neverMergeMessageLite(); // * extends GeneratedMessageLite heuristics. bypassClinitforInliningNewBuilderMethods(subtypingInfo); @@ -353,5 +443,11 @@ neverMerge.add(references.generatedMessageLiteBuilderType); neverMerge.add(references.generatedMessageLiteExtendableBuilderType); } + + private void neverMergeMessageLite() { + // MessageLite is used in several signatures that we use for recognizing methods, so don't + // allow it to me merged. + neverMerge.add(references.messageLiteType); + } } }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java index ca4ca1e..e8770c7 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
@@ -43,11 +43,19 @@ return Reason.ALWAYS; } return references.isDynamicMethod(target) || references.isDynamicMethodBridge(target) - ? computeInliningReasonForDynamicMethod(invoke) + ? computeInliningReasonForDynamicMethod(invoke, target, context) : parent.computeInliningReason(invoke, target, context); } - private Reason computeInliningReasonForDynamicMethod(InvokeMethod invoke) { + private Reason computeInliningReasonForDynamicMethod( + InvokeMethod invoke, ProgramMethod target, ProgramMethod context) { + // Do not allow inlining of dynamicMethod() into a proto library class. This should only happen + // if there is exactly one proto message in the program, since we would otherwise not be able + // to conclude a single target. + if (references.isDynamicMethod(target) && references.isProtoLibraryClass(context.getHolder())) { + return Reason.NEVER; + } + Value methodToInvokeValue = invoke .inValues()
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java index 861c0cd..8fd43ee 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
@@ -17,6 +17,8 @@ public class ProtoReferences { + private final DexItemFactory dexItemFactory; + public final DexType enumLiteMapType; public final DexType extendableMessageType; public final DexType extensionDescriptorType; @@ -25,6 +27,7 @@ public final DexType generatedMessageLiteType; public final DexType generatedMessageLiteBuilderType; public final DexType generatedMessageLiteExtendableBuilderType; + public final DexType generatedMessageLiteExtendableMessageType; public final DexType rawMessageInfoType; public final DexType messageLiteType; public final DexType methodToInvokeType; @@ -37,10 +40,13 @@ generatedMessageLiteExtendableBuilderMethods; public final MethodToInvokeMembers methodToInvokeMembers; + public final DexString defaultInstanceFieldName; public final DexString dynamicMethodName; public final DexString findLiteExtensionByNumberName; public final DexString newBuilderMethodName; + public final DexString protobufPackageDescriptorPrefix; + public final DexProto dynamicMethodProto; public final DexProto findLiteExtensionByNumberProto; @@ -49,6 +55,8 @@ public final DexMethod rawMessageInfoConstructor; public ProtoReferences(DexItemFactory factory) { + dexItemFactory = factory; + // Types. enumLiteMapType = factory.createType("Lcom/google/protobuf/Internal$EnumLiteMap;"); extendableMessageType = @@ -63,6 +71,8 @@ factory.createType("Lcom/google/protobuf/GeneratedMessageLite$Builder;"); generatedMessageLiteExtendableBuilderType = factory.createType("Lcom/google/protobuf/GeneratedMessageLite$ExtendableBuilder;"); + generatedMessageLiteExtendableMessageType = + factory.createType("Lcom/google/protobuf/GeneratedMessageLite$ExtendableMessage;"); rawMessageInfoType = factory.createType("Lcom/google/protobuf/RawMessageInfo;"); messageLiteType = factory.createType("Lcom/google/protobuf/MessageLite;"); methodToInvokeType = @@ -70,10 +80,14 @@ wireFormatFieldType = factory.createType("Lcom/google/protobuf/WireFormat$FieldType;"); // Names. + defaultInstanceFieldName = factory.createString("DEFAULT_INSTANCE"); dynamicMethodName = factory.createString("dynamicMethod"); findLiteExtensionByNumberName = factory.createString("findLiteExtensionByNumber"); newBuilderMethodName = factory.createString("newBuilder"); + // Other names. + protobufPackageDescriptorPrefix = factory.createString("Lcom/google/protobuf/"); + // Protos. dynamicMethodProto = factory.createProto( @@ -105,6 +119,10 @@ methodToInvokeMembers = new MethodToInvokeMembers(factory); } + public DexField getDefaultInstanceField(DexProgramClass holder) { + return dexItemFactory.createField(holder.type, holder.type, defaultInstanceFieldName); + } + public boolean isAbstractGeneratedMessageLiteBuilder(DexProgramClass clazz) { return clazz.type == generatedMessageLiteBuilderType || clazz.type == generatedMessageLiteExtendableBuilderType; @@ -123,7 +141,8 @@ } public boolean isDynamicMethodBridge(DexMethod method) { - return method == generatedMessageLiteMethods.dynamicMethodBridgeMethod; + return method == generatedMessageLiteMethods.dynamicMethodBridgeMethod + || method == generatedMessageLiteMethods.dynamicMethodBridgeMethodWithObject; } public boolean isDynamicMethodBridge(DexEncodedMethod method) { @@ -154,6 +173,10 @@ return method.match(newMessageInfoMethod) || method == rawMessageInfoConstructor; } + public boolean isProtoLibraryClass(DexProgramClass clazz) { + return clazz.type.descriptor.startsWith(protobufPackageDescriptorPrefix); + } + public class GeneratedExtensionMethods { public final DexMethod constructor; @@ -192,6 +215,7 @@ public final DexMethod createBuilderMethod; public final DexMethod dynamicMethodBridgeMethod; + public final DexMethod dynamicMethodBridgeMethodWithObject; public final DexMethod isInitializedMethod; public final DexMethod newRepeatedGeneratedExtension; public final DexMethod newSingularGeneratedExtension; @@ -207,6 +231,12 @@ generatedMessageLiteType, dexItemFactory.createProto(dexItemFactory.objectType, methodToInvokeType), "dynamicMethod"); + dynamicMethodBridgeMethodWithObject = + dexItemFactory.createMethod( + generatedMessageLiteType, + dexItemFactory.createProto( + dexItemFactory.objectType, methodToInvokeType, dexItemFactory.objectType), + "dynamicMethod"); isInitializedMethod = dexItemFactory.createMethod( generatedMessageLiteType, @@ -241,7 +271,7 @@ } } - class GeneratedMessageLiteBuilderMethods { + public class GeneratedMessageLiteBuilderMethods { public final DexMethod buildPartialMethod; public final DexMethod constructorMethod; @@ -260,9 +290,10 @@ } } - class GeneratedMessageLiteExtendableBuilderMethods { + public class GeneratedMessageLiteExtendableBuilderMethods { public final DexMethod buildPartialMethod; + public final DexMethod constructorMethod; private GeneratedMessageLiteExtendableBuilderMethods(DexItemFactory dexItemFactory) { buildPartialMethod = @@ -270,6 +301,12 @@ generatedMessageLiteExtendableBuilderType, dexItemFactory.createProto(extendableMessageType), "buildPartial"); + constructorMethod = + dexItemFactory.createMethod( + generatedMessageLiteExtendableBuilderType, + dexItemFactory.createProto( + dexItemFactory.voidType, generatedMessageLiteExtendableMessageType), + dexItemFactory.constructorMethodName); } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java index 3f0d75a..ed2cdd8 100644 --- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java +++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; +import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult; public class AlwaysMaterializingDefinition extends ConstInstruction { @@ -29,9 +30,9 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { + public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) { // This instruction may never be considered dead as it must remain. - return false; + return DeadInstructionResult.notDead(); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java index 3cd25ea..45fa439 100644 --- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java +++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; +import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; @@ -30,8 +31,8 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - return false; + public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) { + return DeadInstructionResult.notDead(); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java index 4b99975..21b02ba 100644 --- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java +++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; +import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; @@ -29,9 +30,9 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { + public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) { // This instruction may never be considered dead as it must remain. - return false; + return DeadInstructionResult.notDead(); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java index 5b68bf5..fbca901 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Argument.java +++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; +import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; import java.util.Set; @@ -62,11 +63,11 @@ } @Override - public boolean canBeDeadCode(AppView<?> appview, IRCode code) { + public DeadInstructionResult canBeDeadCode(AppView<?> appview, IRCode code) { // Never remove argument instructions. That would change the signature of the method. // TODO(b/65810338): If we can tell that a method never uses an argument we might be able to // rewrite the signature and call-sites. - return false; + return DeadInstructionResult.notDead(); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java index e9f074b..4f0995b 100644 --- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java +++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -84,11 +84,6 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - return !instructionMayHaveSideEffects(appView, code.context()); - } - - @Override public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) { if (super.identicalAfterRegisterAllocation(other, allocator)) { // The array length instruction doesn't carry the element type. The art verifier doesn't
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java index 860473c..9dae2a8 100644 --- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java +++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -191,11 +191,6 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - return !instructionMayHaveSideEffects(appView, code.context()); - } - - @Override public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) { // We cannot share ArrayPut instructions without knowledge of the type of the array input. // If multiple primitive array types flow to the same ArrayPut instruction the art verifier
diff --git a/src/main/java/com/android/tools/r8/ir/code/Assume.java b/src/main/java/com/android/tools/r8/ir/code/Assume.java index 117af7b..7aed1d9 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Assume.java +++ b/src/main/java/com/android/tools/r8/ir/code/Assume.java
@@ -5,7 +5,6 @@ import com.android.tools.r8.cf.LoadStoreHelper; import com.android.tools.r8.cf.TypeVerificationHelper; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexType; @@ -16,7 +15,6 @@ 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.utils.BooleanUtils; import java.util.Objects; import java.util.Set; @@ -25,11 +23,11 @@ private static final String ERROR_MESSAGE = "Expected Assume instructions to be removed after IR processing."; - private final DynamicTypeAssumption dynamicTypeAssumption; + private DynamicTypeAssumption dynamicTypeAssumption; private final NonNullAssumption nonNullAssumption; private final Instruction origin; - private Assume( + public Assume( DynamicTypeAssumption dynamicTypeAssumption, NonNullAssumption nonNullAssumption, Value dest, @@ -38,9 +36,6 @@ AppView<?> appView) { super(dest, src); assert dynamicTypeAssumption != null || nonNullAssumption != null; - assert BooleanUtils.intValue(dynamicTypeAssumption != null) - + BooleanUtils.intValue(nonNullAssumption != null) - == 1; assert dynamicTypeAssumption == null || dynamicTypeAssumption.verifyCorrectnessOfValues(dest, src, appView); assert nonNullAssumption == null @@ -78,7 +73,7 @@ } public boolean verifyInstructionIsNeeded(AppView<?> appView) { - if (isAssumeDynamicType()) { + if (hasDynamicTypeAssumption()) { assert dynamicTypeAssumption.verifyCorrectnessOfValues(outValue(), src(), appView); } return true; @@ -112,13 +107,7 @@ @Override public String getInstructionName() { - if (isAssumeDynamicType()) { - return "AssumeDynamicType"; - } - if (isAssumeNonNull()) { - return "AssumeNonNull"; - } - throw new Unimplemented(); + return "Assume"; } @Override @@ -131,13 +120,15 @@ return this; } - @Override - public boolean isAssumeDynamicType() { + public boolean hasDynamicTypeAssumption() { return dynamicTypeAssumption != null; } - @Override - public boolean isAssumeNonNull() { + public void unsetDynamicTypeAssumption() { + dynamicTypeAssumption = null; + } + + public boolean hasNonNullAssumption() { return nonNullAssumption != null; } @@ -149,7 +140,7 @@ if (outType.isPrimitiveType()) { return false; } - if (isAssumeDynamicType()) { + if (hasDynamicTypeAssumption()) { outType = dynamicTypeAssumption.getDynamicUpperBoundType(); } if (appView.appInfo().hasLiveness()) { @@ -220,14 +211,11 @@ @Override public TypeElement evaluate(AppView<?> appView) { - if (isAssumeDynamicType()) { - return src().getType(); - } - if (isAssumeNonNull()) { + if (hasNonNullAssumption()) { assert src().getType().isReferenceType(); return src().getType().asReferenceType().asMeetWithNotNull(); } - throw new Unimplemented(); + return src().getType(); } @Override @@ -255,15 +243,14 @@ assert super.verifyTypes(appView); TypeElement inType = src().getType(); + assert inType.isReferenceType() : inType; + TypeElement outType = getOutType(); - if (isAssumeDynamicType()) { - assert inType.isReferenceType() : inType; - assert outType.equals(inType) + if (hasNonNullAssumption()) { + assert inType.isNullType() || outType.equals(inType.asReferenceType().asMeetWithNotNull()) : "At " + this + System.lineSeparator() + outType + " != " + inType; } else { - assert isAssumeNonNull() : this; - assert inType.isReferenceType() : inType; - assert inType.isNullType() || outType.equals(inType.asReferenceType().asMeetWithNotNull()) + assert outType.equals(inType) : "At " + this + System.lineSeparator() + outType + " != " + inType; } return true; @@ -276,21 +263,21 @@ // assumption became "truth." // 2) invoke-interface could be devirtualized, while its dynamic type and/or non-null receiver // are still valid. - String originString = - origin.hasBlock() ? " (origin: `" + origin.toString() + "`)" : " (obsolete origin)"; - if (isAssumeNonNull()) { - return super.toString() + originString; + StringBuilder builder = new StringBuilder(super.toString()); + if (hasNonNullAssumption()) { + builder.append("; not null"); } - if (isAssumeDynamicType()) { - return super.toString() - + "; upper bound: " - + dynamicTypeAssumption.dynamicUpperBoundType - + (dynamicTypeAssumption.dynamicLowerBoundType != null - ? "; lower bound: " + dynamicTypeAssumption.dynamicLowerBoundType - : "") - + originString; + if (hasDynamicTypeAssumption()) { + if (hasOutValue()) { + if (!dynamicTypeAssumption.dynamicUpperBoundType.equalUpToNullability(outValue.getType())) { + builder.append("; upper bound: ").append(dynamicTypeAssumption.dynamicUpperBoundType); + } + } + if (dynamicTypeAssumption.dynamicLowerBoundType != null) { + builder.append("; lower bound: ").append(dynamicTypeAssumption.dynamicLowerBoundType); + } } - return super.toString(); + return builder.toString(); } public static class DynamicTypeAssumption { @@ -298,7 +285,7 @@ private final TypeElement dynamicUpperBoundType; private final ClassTypeElement dynamicLowerBoundType; - private DynamicTypeAssumption( + public DynamicTypeAssumption( TypeElement dynamicUpperBoundType, ClassTypeElement dynamicLowerBoundType) { this.dynamicUpperBoundType = dynamicUpperBoundType; this.dynamicLowerBoundType = dynamicLowerBoundType;
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java index 26b0acd..b0ef472 100644 --- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java +++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -1292,36 +1292,17 @@ builder.append(": "); builder.append(instruction.toString()); if (DebugLocalInfo.PRINT_LEVEL != PrintLevel.NONE) { - List<Value> localEnds = new ArrayList<>(instruction.getDebugValues().size()); - List<Value> localStarts = new ArrayList<>(instruction.getDebugValues().size()); - List<Value> localLive = new ArrayList<>(instruction.getDebugValues().size()); - for (Value value : instruction.getDebugValues()) { - if (value.getDebugLocalEnds().contains(instruction)) { - localEnds.add(value); - } else if (value.getDebugLocalStarts().contains(instruction)) { - localStarts.add(value); - } else { - assert value.debugUsers().contains(instruction); - localLive.add(value); - } + if (!instruction.getDebugValues().isEmpty()) { + builder.append(" [end: "); + StringUtils.append(builder, instruction.getDebugValues(), ", ", BraceType.NONE); + builder.append("]"); } - printDebugValueSet("live", localLive, builder); - printDebugValueSet("end", localEnds, builder); - printDebugValueSet("start", localStarts, builder); } builder.append("\n"); } return builder.toString(); } - private void printDebugValueSet(String header, List<Value> locals, StringBuilder builder) { - if (!locals.isEmpty()) { - builder.append(" [").append(header).append(": "); - StringUtils.append(builder, locals, ", ", BraceType.NONE); - builder.append("]"); - } - } - public void print(CfgPrinter printer) { printer.begin("block"); printer.print("name \"B").append(number).append("\"\n"); @@ -1990,15 +1971,34 @@ Phi phi = phiIt.next(); Wrapper<Phi> key = equivalence.wrap(phi); Phi replacement = wrapper2phi.get(key); - if (replacement != null) { - phi.replaceUsers(replacement); - for (Value operand : phi.getOperands()) { - operand.removePhiUser(phi); - } - phiIt.remove(); - } else { + if (replacement == null) { wrapper2phi.put(key, phi); + continue; } + // Two phis may be duplicates but still differ in debug info. + // For example, it may be that the one phi denotes the result of a local pre-increment, while + // the other phi represents the modified local, e.g., cond ? ++x : x will give rise to: + // v2 <- phi(v0(x), v1(x)) + // v3(x) <- phi(v0(x), v1(x)) + // where v2 is used as the result of the expression and v3 is the local slot value of x. + // This should be replaced by a single phi. + if (phi.getLocalInfo() != replacement.getLocalInfo()) { + if (replacement.getLocalInfo() == null) { + // The replacement must take over the debug info. + replacement.setLocalInfo(phi.getLocalInfo()); + } else if (phi.getLocalInfo() == null) { + // The replacement already owns debug info. + } else { + // The phis define two distinct locals and cannot be de-duped. + assert phi.hasLocalInfo() && replacement.hasLocalInfo(); + continue; + } + } + phi.replaceUsers(replacement); + for (Value operand : phi.getOperands()) { + operand.removePhiUser(phi); + } + phiIt.remove(); } } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java index b4ceb0d..50cec6e 100644 --- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -87,10 +87,20 @@ } @Override + public boolean hasInsertionPosition() { + return position != null; + } + + @Override public void setInsertionPosition(Position position) { this.position = position; } + @Override + public void unsetInsertionPosition() { + this.position = null; + } + /** * Adds an instruction to the block. The instruction will be added just before the current cursor * position. @@ -198,7 +208,9 @@ } current.moveDebugValues(newInstruction); newInstruction.setBlock(block); - newInstruction.setPosition(current.getPosition()); + if (!newInstruction.hasPosition()) { + newInstruction.setPosition(current.getPosition()); + } listIterator.remove(); listIterator.add(newInstruction); current.clearBlock(); @@ -210,7 +222,15 @@ IRCode code, InternalOptions options, long value, TypeElement type) { ConstNumber constNumberInstruction = code.createNumberConstant(value, type); // Note that we only keep position info for throwing instructions in release mode. - constNumberInstruction.setPosition(options.debug ? current.getPosition() : Position.none()); + if (!hasInsertionPosition()) { + Position position; + if (options.debug) { + position = current != null ? current.getPosition() : block.getPosition(); + } else { + position = Position.none(); + } + constNumberInstruction.setPosition(position); + } add(constNumberInstruction); return constNumberInstruction.outValue(); } @@ -235,12 +255,6 @@ // Replace the instruction by const-number. ConstNumber constNumber = code.createIntConstant(value, current.getLocalInfo()); - for (Value inValue : current.inValues()) { - if (inValue.hasLocalInfo()) { - // Add this value as a debug value to avoid changing its live range. - constNumber.addDebugValue(inValue); - } - } replaceCurrentInstruction(constNumber); } @@ -256,12 +270,6 @@ TypeElement oldType = current.getOutType(); Value value = code.createValue(newType, current.getLocalInfo()); StaticGet staticGet = new StaticGet(value, field); - for (Value inValue : current.inValues()) { - if (inValue.hasLocalInfo()) { - // Add this value as a debug value to avoid changing its live range. - staticGet.addDebugValue(inValue); - } - } replaceCurrentInstruction(staticGet); // Update affected values. @@ -280,58 +288,97 @@ if (current == null) { throw new IllegalStateException(); } - BasicBlock block = current.getBlock(); + + Instruction toBeReplaced = current; + + BasicBlock block = toBeReplaced.getBlock(); assert !blocksToRemove.contains(block); assert affectedValues != null; - BasicBlock normalSuccessorBlock = split(code, blockIterator); + // Split the block before the instruction that should be replaced by `throw null`. previous(); - // Unlink all blocks that are dominated by successor. - { - DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS); - blocksToRemove.addAll(block.unlink(normalSuccessorBlock, dominatorTree, affectedValues)); + BasicBlock throwBlock; + if (block.hasCatchHandlers() && !toBeReplaced.instructionTypeCanThrow()) { + // We need to insert the throw instruction in a block of its own, so split the current block + // into three blocks, where the intermediate block only contains a goto instruction. + throwBlock = split(code, blockIterator, true); + throwBlock.listIterator(code).split(code, blockIterator); + } else { + split(code, blockIterator, true); + throwBlock = block; } - // Insert constant null before the instruction. + // Position the instruction iterator before the goto instruction. + assert !hasNext(); previous(); - Value nullValue = insertConstNullInstruction(code, appView.options()); - next(); + + // Unlink all blocks that are dominated by the unique normal successor of the throw block. + blocksToRemove.addAll( + throwBlock.unlink( + throwBlock.getUniqueNormalSuccessor(), + new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS), + affectedValues)); + + InstructionListIterator throwBlockInstructionIterator; + if (throwBlock == block) { + throwBlockInstructionIterator = this; + } else { + throwBlockInstructionIterator = throwBlock.listIterator(code); + throwBlockInstructionIterator.setInsertionPosition(position); + } + + // Insert constant null before the goto instruction. + Value nullValue = + throwBlockInstructionIterator.insertConstNullInstruction(code, appView.options()); + + // Move past the inserted goto instruction. + throwBlockInstructionIterator.next(); + assert !throwBlockInstructionIterator.hasNext(); // Replace the instruction by throw. Throw throwInstruction = new Throw(nullValue); - for (Value inValue : current.inValues()) { - if (inValue.hasLocalInfo()) { - // Add this value as a debug value to avoid changing its live range. - throwInstruction.addDebugValue(inValue); - } + if (hasInsertionPosition()) { + throwInstruction.setPosition(position); + } else if (toBeReplaced.getPosition().isSome()) { + throwInstruction.setPosition(toBeReplaced.getPosition()); + } else { + // The instruction that is being removed cannot throw, and thus it must be unreachable as we + // are replacing it by `throw null`, so we can safely use a none-position. + assert !toBeReplaced.instructionTypeCanThrow(); + throwInstruction.setPosition(Position.syntheticNone()); } - replaceCurrentInstruction(throwInstruction); - next(); - remove(); + throwBlockInstructionIterator.replaceCurrentInstruction(throwInstruction); - // Remove all catch handlers where the guard does not include NullPointerException. if (block.hasCatchHandlers()) { - CatchHandlers<BasicBlock> catchHandlers = block.getCatchHandlers(); - catchHandlers.forEach( - (guard, target) -> { - if (blocksToRemove.contains(target)) { - // Already removed previously. This may happen if two catch handlers have the same - // target. - return; - } - if (!appView.appInfo().isSubtype(appView.dexItemFactory().npeType, guard)) { - // TODO(christofferqa): Consider updating previous dominator tree instead of - // rebuilding it from scratch. - DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS); - blocksToRemove.addAll(block.unlink(target, dominatorTree, affectedValues)); - } - }); + if (block == throwBlock) { + // Remove all catch handlers where the guard does not include NullPointerException if the + // replaced instruction could throw. + CatchHandlers<BasicBlock> catchHandlers = block.getCatchHandlers(); + catchHandlers.forEach( + (guard, target) -> { + if (blocksToRemove.contains(target)) { + // Already removed previously. This may happen if two catch handlers have the same + // target. + return; + } + if (!appView.appInfo().isSubtype(appView.dexItemFactory().npeType, guard)) { + // TODO(christofferqa): Consider updating previous dominator tree instead of + // rebuilding it from scratch. + DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS); + blocksToRemove.addAll(block.unlink(target, dominatorTree, affectedValues)); + } + }); + } else { + // We replaced a dead, non-throwing instruction by a throwing instruction. Since this is + // dead code, we don't need to worry about the catch handlers of the `throwBlock`. + } } } @Override - public BasicBlock split(IRCode code, ListIterator<BasicBlock> blocksIterator) { + public BasicBlock split( + IRCode code, ListIterator<BasicBlock> blocksIterator, boolean keepCatchHandlers) { List<BasicBlock> blocks = code.blocks; assert blocksIterator == null || IteratorUtils.peekPrevious(blocksIterator) == block; @@ -346,7 +393,6 @@ // Prepare the new block, placing the exception handlers on the block with the throwing // instruction. - boolean keepCatchHandlers = hasPrevious() && peekPrevious().instructionTypeCanThrow(); newBlock = block.createSplitBlock(blockNumber, keepCatchHandlers); // Add a goto instruction.
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java index 4594db9..d28135d 100644 --- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java +++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -133,11 +133,6 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - return !instructionMayHaveSideEffects(appView, code.context()); - } - - @Override public boolean isOutConstant() { return true; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java index 3bd1d56..0a76de9 100644 --- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java +++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.code; +import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull; + import com.android.tools.r8.cf.LoadStoreHelper; import com.android.tools.r8.cf.TypeVerificationHelper; import com.android.tools.r8.cf.code.CfConstString; @@ -11,13 +13,13 @@ import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.ir.analysis.type.Nullability; 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.UnknownValue; import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo; import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; +import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult; import com.android.tools.r8.shaking.AppInfoWithLiveness; import java.io.UTFDataFormatException; @@ -132,9 +134,12 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { + public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) { // No side-effect, such as throwing an exception, in CF. - return appView.options().isGeneratingClassFiles() || !instructionInstanceCanThrow(); + if (appView.options().isGeneratingClassFiles() || !instructionInstanceCanThrow()) { + return DeadInstructionResult.deadIfOutValueIsDead(); + } + return DeadInstructionResult.notDead(); } @Override @@ -154,7 +159,7 @@ @Override public TypeElement evaluate(AppView<?> appView) { - return TypeElement.stringClassType(appView, Nullability.definitelyNotNull()); + return TypeElement.stringClassType(appView, definitelyNotNull()); } @Override @@ -170,4 +175,12 @@ } return UnknownValue.getInstance(); } + + @Override + public boolean verifyTypes(AppView<?> appView) { + assert super.verifyTypes(appView); + TypeElement expectedType = TypeElement.stringClassType(appView, definitelyNotNull()); + assert getOutType().equals(expectedType); + return true; + } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java index 292d6b7..5a3e094 100644 --- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java +++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; +import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; @@ -71,10 +72,10 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { + public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) { // Reads are never dead code. // They should also have a non-empty set of debug values (see RegAlloc::computeDebugInfo) - return false; + return DeadInstructionResult.notDead(); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java index 842eaa4..1ff7509 100644 --- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java +++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; +import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; import com.android.tools.r8.utils.StringUtils; @@ -83,8 +84,8 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - return false; + public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) { + return DeadInstructionResult.notDead(); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java index fd6f9a9..c347f87 100644 --- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java +++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; +import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; @@ -67,8 +68,8 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - return false; + public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) { + return DeadInstructionResult.notDead(); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java index 959dbfc..0753447 100644 --- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java +++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -16,6 +16,7 @@ import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo; import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; +import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo; @@ -131,9 +132,9 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { + public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) { // No side-effect, such as throwing an exception, in CF. - return true; + return DeadInstructionResult.deadIfOutValueIsDead(); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java index a0af5e2..3ef2eee 100644 --- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java +++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -245,10 +245,14 @@ liveStack.addLast(use); } } - assert instruction.getDebugValues().stream().allMatch(Value::needsRegister); - assert instruction.getDebugValues().stream().allMatch(Value::hasLocalInfo); - live.addAll(instruction.getDebugValues()); - liveLocals.addAll(instruction.getDebugValues()); + if (!instruction.getDebugValues().isEmpty()) { + ArrayList<Value> sortedValues = new ArrayList<>(instruction.getDebugValues()); + sortedValues.sort(Value::compareTo); + assert sortedValues.stream().allMatch(Value::needsRegister); + assert sortedValues.stream().allMatch(Value::hasLocalInfo); + live.addAll(sortedValues); + liveLocals.addAll(sortedValues); + } } for (Phi phi : block.getPhis()) { if (phi.isValueOnStack()) { @@ -609,17 +613,17 @@ // We can only type check the program if we have subtyping information. Therefore, we do not // require that the program type checks in D8. if (appView.enableWholeProgramOptimizations()) { - assert validAssumeDynamicTypeInstructions(appView); + assert validAssumeInstructions(appView); assert new TypeChecker(appView.withLiveness()).check(this); } assert blocks.stream().allMatch(block -> block.verifyTypes(appView)); return true; } - private boolean validAssumeDynamicTypeInstructions(AppView<?> appView) { + private boolean validAssumeInstructions(AppView<?> appView) { for (BasicBlock block : blocks) { for (Instruction instruction : block.getInstructions()) { - if (instruction.isAssumeDynamicType()) { + if (instruction.isAssume()) { assert instruction.asAssume().verifyInstructionIsNeeded(appView); } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java index b9cc832..bec911e 100644 --- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -68,7 +68,8 @@ } @Override - public BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator) { + public BasicBlock split( + IRCode code, ListIterator<BasicBlock> blockIterator, boolean keepCatchHandlers) { throw new Unimplemented(); } @@ -159,4 +160,19 @@ public void removeOrReplaceByDebugLocalRead() { instructionIterator.removeOrReplaceByDebugLocalRead(); } + + @Override + public boolean hasInsertionPosition() { + return instructionIterator.hasInsertionPosition(); + } + + @Override + public void setInsertionPosition(Position position) { + instructionIterator.setInsertionPosition(position); + } + + @Override + public void unsetInsertionPosition() { + instructionIterator.unsetInsertionPosition(); + } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java index 9077c97..faebe44 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java +++ b/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
@@ -16,6 +16,9 @@ Value object(); + boolean instructionInstanceCanThrow( + AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption); + boolean instructionMayHaveSideEffects( AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java index 924d256..7dad747 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java +++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -123,16 +123,6 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - // instance-get can be dead code as long as it cannot have any of the following: - // * NoSuchFieldError (resolution failure) - // * IncompatibleClassChangeError (instance-* instruction for static fields) - // * IllegalAccessError (not visible from the access context) - // * NullPointerException (null receiver) - return !instructionMayHaveSideEffects(appView, code.context()); - } - - @Override public int maxInValueRegister() { return Constants.U4BIT_MAX; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java index b8a5787..296b2f9 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java +++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -142,20 +142,6 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - // instance-put can be dead as long as it cannot have any of the following: - // * NoSuchFieldError (resolution failure) - // * IncompatibleClassChangeError (static-* instruction for instance fields) - // * IllegalAccessError (not visible from the access context) - // * NullPointerException (null receiver) - // * not read at all - boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.context()); - assert appView.enableWholeProgramOptimizations() || haveSideEffects - : "Expected instance-put instruction to have side effects in D8"; - return !haveSideEffects; - } - - @Override public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) { if (!super.identicalAfterRegisterAllocation(other, allocator)) { return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java index 1400455..ee7d57e 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java +++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -24,6 +24,7 @@ import com.android.tools.r8.ir.analysis.value.UnknownValue; import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; +import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; import com.android.tools.r8.ir.regalloc.RegisterAllocator; @@ -32,9 +33,9 @@ import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.StringUtils.BraceType; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.Iterator; -import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.function.Function; @@ -45,7 +46,7 @@ protected final List<Value> inValues = new ArrayList<>(); private BasicBlock block = null; private int number = -1; - private LinkedHashSet<Value> debugValues = null; + private Set<Value> debugValues = null; private Position position = null; protected Instruction(Value outValue) { @@ -154,11 +155,9 @@ public void addDebugValue(Value value) { assert value.hasLocalInfo(); if (debugValues == null) { - debugValues = new LinkedHashSet<>(); + debugValues = Sets.newIdentityHashSet(); } - if (debugValues.add(value)) { - value.addDebugUser(this); - } + debugValues.add(value); } public static void clearUserInfo(Instruction instruction) { @@ -197,17 +196,19 @@ } public void replaceDebugValue(Value oldValue, Value newValue) { - if (debugValues.remove(oldValue)) { - // TODO(mathiasr): Enable this assertion when BasicBlock has current position so trivial phi - // removal can take local info into account. - // assert newValue.getLocalInfo() == oldValue.getLocalInfo() - // : "Replacing debug values with inconsistent locals " + - // oldValue.getLocalInfo() + " and " + newValue.getLocalInfo() + - // ". This is likely a code transformation bug " + - // "that has not taken local information into account"; - if (newValue.hasLocalInfo()) { - addDebugValue(newValue); - } + assert oldValue.hasLocalInfo(); + assert newValue.hasLocalInfo(); + assert newValue.getLocalInfo() == oldValue.getLocalInfo() + : "Replacing debug values with inconsistent locals " + + oldValue.getLocalInfo() + + " and " + + newValue.getLocalInfo() + + ". This is likely a code transformation bug " + + "that has not taken local information into account"; + boolean removed = debugValues.remove(oldValue); + assert removed; + if (removed && newValue.hasLocalInfo()) { + newValue.addDebugLocalEnd(this); } } @@ -221,12 +222,6 @@ debugValues.clear(); } - public void moveDebugValue(Value value, Instruction target) { - assert debugValues.contains(value); - value.replaceDebugUser(this, target); - debugValues.remove(value); - } - public void removeDebugValue(Value value) { assert value.hasLocalInfo(); if (debugValues != null) { @@ -591,10 +586,10 @@ } /** Returns true is this instruction can be treated as dead code if its outputs are not used. */ - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - // TODO(b/129530569): instructions with fine-grained side effect analysis may use: - // return !instructionMayHaveSideEffects(appView, code.method.holder()); - return !instructionInstanceCanThrow(); + public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) { + return instructionMayHaveSideEffects(appView, code.context()) + ? DeadInstructionResult.notDead() + : DeadInstructionResult.deadIfOutValueIsDead(); } /** @@ -717,12 +712,12 @@ return null; } - public boolean isAssumeDynamicType() { - return false; + public final boolean isAssumeWithDynamicTypeAssumption() { + return isAssume() && asAssume().hasDynamicTypeAssumption(); } - public boolean isAssumeNonNull() { - return false; + public final boolean isAssumeWithNonNullAssumption() { + return isAssume() && asAssume().hasNonNullAssumption(); } public boolean isBinop() { @@ -1466,17 +1461,70 @@ return false; } - public enum SideEffectAssumption { - NONE, - CLASS_ALREADY_INITIALIZED, - RECEIVER_NOT_NULL; + public static class SideEffectAssumption { - boolean canAssumeClassIsAlreadyInitialized() { - return this == CLASS_ALREADY_INITIALIZED; + public static final SideEffectAssumption NONE = new SideEffectAssumption(); + + public static final SideEffectAssumption CLASS_ALREADY_INITIALIZED = + new SideEffectAssumption() { + + @Override + public boolean canAssumeClassIsAlreadyInitialized() { + return true; + } + }; + + public static final SideEffectAssumption INVOKED_METHOD_DOES_NOT_HAVE_SIDE_EFFECTS = + new SideEffectAssumption() { + + @Override + public boolean canAssumeInvokedMethodDoesNotHaveSideEffects() { + return true; + } + }; + + public static final SideEffectAssumption RECEIVER_NOT_NULL = + new SideEffectAssumption() { + + @Override + public boolean canAssumeReceiverIsNotNull() { + return true; + } + }; + + public boolean canAssumeClassIsAlreadyInitialized() { + return false; } - boolean canAssumeReceiverIsNotNull() { - return this == RECEIVER_NOT_NULL; + public boolean canAssumeInvokedMethodDoesNotHaveSideEffects() { + return false; + } + + public boolean canAssumeReceiverIsNotNull() { + return false; + } + + public SideEffectAssumption join(SideEffectAssumption other) { + return new SideEffectAssumption() { + + @Override + public boolean canAssumeClassIsAlreadyInitialized() { + return SideEffectAssumption.this.canAssumeClassIsAlreadyInitialized() + || other.canAssumeClassIsAlreadyInitialized(); + } + + @Override + public boolean canAssumeInvokedMethodDoesNotHaveSideEffects() { + return SideEffectAssumption.this.canAssumeInvokedMethodDoesNotHaveSideEffects() + || other.canAssumeInvokedMethodDoesNotHaveSideEffects(); + } + + @Override + public boolean canAssumeReceiverIsNotNull() { + return SideEffectAssumption.this.canAssumeInvokedMethodDoesNotHaveSideEffects() + || other.canAssumeInvokedMethodDoesNotHaveSideEffects(); + } + }; } } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java index a546ee3..2790e66 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -58,10 +58,18 @@ */ void removeOrReplaceByDebugLocalRead(); + default boolean hasInsertionPosition() { + return false; + } + default void setInsertionPosition(Position position) { // Intentionally empty. } + default void unsetInsertionPosition() { + // Intentionally empty. + } + default Value insertConstNullInstruction(IRCode code, InternalOptions options) { return insertConstNumberInstruction(code, options, 0, TypeElement.getNull()); } @@ -104,20 +112,25 @@ /** * Split the block into two blocks at the point of the {@link ListIterator} cursor. The existing - * block will have all the instructions before the cursor, and the new block all the - * instructions after the cursor. + * block will have all the instructions before the cursor, and the new block all the instructions + * after the cursor. * - * If the current block has catch handlers these catch handlers will be attached to the block + * <p>If the current block has catch handlers these catch handlers will be attached to the block * containing the throwing instruction after the split. * * @param code the IR code for the block this iterator originates from. * @param blockIterator basic block iterator used to iterate the blocks. This must be positioned - * just after the block for which this is the instruction iterator. After this method returns it - * will be positioned just after the basic block returned. Calling {@link #remove} without - * further navigation will remove that block. + * just after the block for which this is the instruction iterator. After this method returns + * it will be positioned just after the basic block returned. Calling {@link #remove} without + * further navigation will remove that block. + * @param keepCatchHandlers whether to keep catch handlers on the original block. * @return Returns the new block with the instructions after the cursor. */ - BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator); + BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator, boolean keepCatchHandlers); + + default BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator) { + return split(code, blockIterator, hasPrevious() && peekPrevious().instructionTypeCanThrow()); + } default BasicBlock split(IRCode code) { return split(code, null);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java index 0f736a0..020254f 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -20,12 +20,11 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; +import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import java.util.ArrayList; import java.util.List; -import java.util.function.Predicate; public class InvokeDirect extends InvokeMethodWithReceiver { @@ -162,54 +161,20 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { + public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) { ProgramMethod context = code.context(); if (instructionMayHaveSideEffects(appView, context)) { - return false; + return DeadInstructionResult.notDead(); } - - if (appView.dexItemFactory().isConstructor(getInvokedMethod())) { - // If it is a constructor call that initializes an uninitialized object, then the - // uninitialized object must be dead. This is the case if all the constructor calls cannot - // have side effects and the instance is dead except for the constructor calls. - List<Instruction> otherInitCalls = null; - for (Instruction user : getReceiver().uniqueUsers()) { - if (user == this) { - continue; - } - if (user.isInvokeDirect()) { - InvokeDirect invoke = user.asInvokeDirect(); - if (appView.dexItemFactory().isConstructor(invoke.getInvokedMethod()) - && invoke.getReceiver() == getReceiver()) { - // If another constructor call than `this` is found, then it must not have side effects. - if (invoke.instructionMayHaveSideEffects(appView, context)) { - return false; - } - if (otherInitCalls == null) { - otherInitCalls = new ArrayList<>(); - } - otherInitCalls.add(invoke); - } - } - } - - // Now check that the instance is dead except for the constructor calls. - final List<Instruction> finalOtherInitCalls = otherInitCalls; - Predicate<Instruction> ignoreConstructorCalls = - instruction -> - instruction == this - || (finalOtherInitCalls != null && finalOtherInitCalls.contains(instruction)); - if (!getReceiver().isDead(appView, code, ignoreConstructorCalls)) { - return false; - } - - // Verify that it is not a super-constructor call (these cannot be removed). - if (getReceiver().getAliasedValue() == code.getThis()) { - return false; - } + if (!getInvokedMethod().isInstanceInitializer(appView)) { + return DeadInstructionResult.deadIfOutValueIsDead(); } - - return true; + // Super-constructor calls cannot be removed. + if (getReceiver().getAliasedValue() == code.getThis()) { + return DeadInstructionResult.notDead(); + } + // Constructor calls can only be removed if the receiver is dead. + return DeadInstructionResult.deadIfInValueIsDead(getReceiver()); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java index a2aa502..79f7152 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -12,7 +12,7 @@ import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.graph.ResolutionResult; +import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult; import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis; import com.android.tools.r8.ir.analysis.type.ClassTypeElement; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; @@ -150,7 +150,8 @@ return false; } // Check that the receiver information comes from a dynamic type. - if (!getReceiver().definition.isAssumeDynamicType()) { + if (!getReceiver() + .isDefinedByInstructionSatisfying(Instruction::isAssumeWithDynamicTypeAssumption)) { return false; } // Now, it can be that the upper bound is more precise than the lower: @@ -208,9 +209,12 @@ assert appView.appInfo().hasLiveness(); AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness(); - ResolutionResult resolutionResult = - appViewWithLiveness.appInfo().resolveMethod(getInvokedMethod(), getInterfaceBit()); - if (resolutionResult.isFailedResolution()) { + SingleResolutionResult resolutionResult = + appViewWithLiveness + .appInfo() + .resolveMethod(getInvokedMethod(), getInterfaceBit()) + .asSingleResolution(); + if (resolutionResult == null) { return true; } @@ -221,6 +225,16 @@ return true; } + if (assumption.canAssumeInvokedMethodDoesNotHaveSideEffects()) { + return false; + } + + DexEncodedMethod resolvedMethod = resolutionResult.getResolvedMethod(); + if (appViewWithLiveness.appInfo().noSideEffects.containsKey(getInvokedMethod()) + || appViewWithLiveness.appInfo().noSideEffects.containsKey(resolvedMethod.getReference())) { + return false; + } + // Find the target and check if the invoke may have side effects. DexEncodedMethod target = lookupSingleTarget(appViewWithLiveness, context); if (target == null) { @@ -242,9 +256,4 @@ return optimizationInfo.mayHaveSideEffects(); } - - @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - return !instructionMayHaveSideEffects(appView, code.context()); - } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java index cc83815..314317e 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -182,11 +182,6 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - return !instructionMayHaveSideEffects(appView, code.context()); - } - - @Override public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) { return false; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java index fc6028f..2783471 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -195,11 +195,6 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - return !instructionMayHaveSideEffects(appView, code.context()); - } - - @Override public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) { return false; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java index 7f5836a..00c88b8 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -229,9 +229,4 @@ type -> appInfoWithLiveness.isSubtype(context.getHolderType(), type), Sets.newIdentityHashSet()); } - - @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - return !instructionMayHaveSideEffects(appView, code.context()); - } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java index 9536919..9f4a26f 100644 --- a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java +++ b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; import java.util.List; @@ -32,8 +33,8 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - return false; + public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) { + return DeadInstructionResult.notDead(); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java index d73f8df..d851eb9 100644 --- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -77,8 +77,9 @@ } @Override - public BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator) { - return currentBlockIterator.split(code, blockIterator); + public BasicBlock split( + IRCode code, ListIterator<BasicBlock> blockIterator, boolean keepCatchHandlers) { + return currentBlockIterator.split(code, blockIterator, keepCatchHandlers); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java index 4c87b70..dd74687 100644 --- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java +++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; +import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; import com.android.tools.r8.utils.InternalOptions; @@ -80,10 +81,14 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - return !(appView.options().debug - || code.method().getOptimizationInfo().isReachabilitySensitive()) - && appView.options().isGeneratingDex(); + public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) { + InternalOptions options = appView.options(); + if (options.debug + || code.context().getDefinition().getOptimizationInfo().isReachabilitySensitive() + || options.isGeneratingClassFiles()) { + return DeadInstructionResult.notDead(); + } + return DeadInstructionResult.deadIfOutValueIsDead(); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java index c730efc..eb12b5a 100644 --- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java +++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -15,6 +15,7 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; +import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; @@ -82,13 +83,16 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { + public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) { if (instructionInstanceCanThrow()) { - return false; + return DeadInstructionResult.notDead(); } // This would belong better in instructionInstanceCanThrow, but that is not passed an appInfo. DexType baseType = type.toBaseType(appView.dexItemFactory()); - return baseType.isPrimitiveType() || appView.definitionFor(baseType) != null; + if (baseType.isPrimitiveType() || appView.definitionFor(baseType) != null) { + return DeadInstructionResult.deadIfOutValueIsDead(); + } + return DeadInstructionResult.notDead(); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java index 54d1ad4..bc75936 100644 --- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java +++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -138,9 +138,4 @@ public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) { return false; } - - @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - return !instructionMayHaveSideEffects(appView, code.context()); - } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java index c1b2bd7..81af63c 100644 --- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java +++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -198,11 +198,6 @@ return false; } - @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - return !instructionMayHaveSideEffects(appView, code.context()); - } - public void markNoSpilling() { allowSpilling = false; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java index a9365d8..cd5cb77 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Phi.java +++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -260,6 +260,18 @@ // leave the phi in there and check at the end that there are no trivial phis. return false; } + if (getLocalInfo() != same.getLocalInfo()) { + if (getLocalInfo() == null) { + // The to-be replaced phi has no local info, so all is OK. + } else if (same.getLocalInfo() == null) { + // Move the local info to the replacement phi. + same.setLocalInfo(getLocalInfo()); + } else { + // The phi's define distinct locals and are not trivially the same. + assert hasLocalInfo() && same.hasLocalInfo(); + return false; + } + } // Ensure that the value that replaces this phi is constrained to the type of this phi. if (builder != null && type.isPreciseType() && !type.isBottom()) { builder.constrainType(same, ValueTypeConstraint.fromTypeLattice(type)); @@ -430,6 +442,9 @@ } } } + if (getType().isReferenceType() && getType().isDefinitelyNotNull()) { + return result.asReferenceType().asMeetWithNotNull(); + } return result; } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java index 0124c45..9ec7256 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Pop.java +++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; +import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; @@ -91,9 +92,9 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { + public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) { // Pop cannot be dead code as it modifies the stack height. - return false; + return DeadInstructionResult.notDead(); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java index 4dc7c12..2a5ffbd 100644 --- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java +++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -149,16 +149,6 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - // static-get can be dead as long as it cannot have any of the following: - // * NoSuchFieldError (resolution failure) - // * IncompatibleClassChangeError (static-* instruction for instance fields) - // * IllegalAccessError (not visible from the access context) - // * side-effects in <clinit> - return !instructionMayHaveSideEffects(appView, code.context()); - } - - @Override public int maxInValueRegister() { return Constants.U8BIT_MAX; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java index 36b6520..e268cbe 100644 --- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java +++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -138,20 +138,6 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - // static-put can be dead as long as it cannot have any of the following: - // * NoSuchFieldError (resolution failure) - // * IncompatibleClassChangeError (static-* instruction for instance fields) - // * IllegalAccessError (not visible from the access context) - // * side-effects in <clinit> - // * not read _globally_ - boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.context()); - assert appView.enableWholeProgramOptimizations() || haveSideEffects - : "Expected static-put instruction to have side effects in D8"; - return !haveSideEffects; - } - - @Override public int maxInValueRegister() { return Constants.U8BIT_MAX; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java index ccd57ea..8100111 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Store.java +++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -15,6 +15,7 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; +import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; @@ -100,8 +101,11 @@ } @Override - public boolean canBeDeadCode(AppView<?> appView, IRCode code) { - return !(outValue instanceof FixedLocalValue); + public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) { + if (outValue instanceof FixedLocalValue) { + return DeadInstructionResult.notDead(); + } + return DeadInstructionResult.deadIfOutValueIsDead(); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java index 53af0fc..c2a63d4 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Value.java +++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -25,6 +25,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.analysis.value.UnknownValue; +import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult; import com.android.tools.r8.ir.regalloc.LiveIntervals; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.position.MethodPosition; @@ -37,14 +38,10 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.ints.IntList; -import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; -import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; @@ -153,63 +150,13 @@ private static class DebugData { final DebugLocalInfo local; - Map<Instruction, DebugUse> users = new HashMap<>(); + Set<Instruction> users = Sets.newIdentityHashSet(); DebugData(DebugLocalInfo local) { this.local = local; } } - // A debug-value user represents a point where the value is live, ends or starts. - // If a point is marked as both ending and starting then it is simply live, but we maintain - // the marker so as not to unintentionally end it if marked again. - private enum DebugUse { - LIVE, START, END, LIVE_FINAL; - - DebugUse start() { - switch (this) { - case LIVE: - case START: - return START; - case END: - case LIVE_FINAL: - return LIVE_FINAL; - default: - throw new Unreachable(); - } - } - - DebugUse end() { - switch (this) { - case LIVE: - case END: - return END; - case START: - case LIVE_FINAL: - return LIVE_FINAL; - default: - throw new Unreachable(); - } - } - - static DebugUse join(DebugUse a, DebugUse b) { - if (a == LIVE_FINAL || b == LIVE_FINAL) { - return LIVE_FINAL; - } - if (a == b) { - return a; - } - if (a == LIVE) { - return b; - } - if (b == LIVE) { - return a; - } - assert (a == START && b == END) || (a == END && b == START); - return LIVE_FINAL; - } - } - public static final int UNDEFINED_NUMBER = -1; public static final Value UNDEFINED = new Value(UNDEFINED_NUMBER, TypeElement.getBottom(), null); @@ -326,62 +273,16 @@ debugData = new DebugData(local); } - public void clearLocalInfo() { - assert debugData.users.isEmpty(); - debugData = null; - } - - public boolean hasSameOrNoLocal(Value other) { - assert other != null; - return hasLocalInfo() - ? other.getLocalInfo() == this.getLocalInfo() - : !other.hasLocalInfo(); - } - - public List<Instruction> getDebugLocalStarts() { - if (debugData == null) { - return Collections.emptyList(); - } - List<Instruction> starts = new ArrayList<>(debugData.users.size()); - for (Entry<Instruction, DebugUse> entry : debugData.users.entrySet()) { - if (entry.getValue() == DebugUse.START) { - starts.add(entry.getKey()); - } - } - return starts; - } - - public List<Instruction> getDebugLocalEnds() { - if (debugData == null) { - return Collections.emptyList(); - } - List<Instruction> ends = new ArrayList<>(debugData.users.size()); - for (Entry<Instruction, DebugUse> entry : debugData.users.entrySet()) { - if (entry.getValue() == DebugUse.END) { - ends.add(entry.getKey()); - } - } - return ends; - } - - public void addDebugLocalStart(Instruction start) { - assert start != null; - debugData.users.put(start, markStart(debugData.users.get(start))); - } - - private DebugUse markStart(DebugUse use) { - assert use != null; - return use == null ? DebugUse.START : use.start(); + public Set<Instruction> getDebugLocalEnds() { + return debugData == null + ? Collections.emptySet() + : Collections.unmodifiableSet(debugData.users); } public void addDebugLocalEnd(Instruction end) { assert end != null; - debugData.users.put(end, markEnd(debugData.users.get(end))); - } - - private DebugUse markEnd(DebugUse use) { - assert use != null; - return use == null ? DebugUse.END : use.end(); + debugData.users.add(end); + end.addDebugValue(this); } public void linkTo(Value other) { @@ -491,7 +392,7 @@ } public Set<Instruction> debugUsers() { - return debugData == null ? null : Collections.unmodifiableSet(debugData.users.keySet()); + return debugData == null ? null : Collections.unmodifiableSet(debugData.users); } public boolean hasAnyUsers() { @@ -502,6 +403,10 @@ return debugData != null && !debugData.users.isEmpty(); } + public boolean hasNonDebugUsers() { + return hasUsers() || hasPhiUsers(); + } + public boolean hasPhiUsers() { return !phiUsers.isEmpty(); } @@ -618,11 +523,6 @@ uniquePhiUsers = null; } - public void addDebugUser(Instruction user) { - assert hasLocalInfo(); - debugData.users.putIfAbsent(user, DebugUse.LIVE); - } - public boolean isUninitializedLocal() { return definition != null && definition.isDebugLocalUninitialized(); } @@ -684,7 +584,7 @@ user.replaceOperand(this, newValue); } if (debugData != null) { - for (Entry<Instruction, DebugUse> user : debugData.users.entrySet()) { + for (Instruction user : debugData.users) { replaceUserInDebugData(user, newValue); } debugData.users.clear(); @@ -746,10 +646,10 @@ } } if (debugData != null) { - Iterator<Entry<Instruction, DebugUse>> users = debugData.users.entrySet().iterator(); + Iterator<Instruction> users = debugData.users.iterator(); while (users.hasNext()) { - Entry<Instruction, DebugUse> user = users.next(); - if (selectedInstructions.contains(user.getKey())) { + Instruction user = users.next(); + if (selectedInstructions.contains(user)) { replaceUserInDebugData(user, newValue); users.remove(); } @@ -757,30 +657,18 @@ } } - private void replaceUserInDebugData(Entry<Instruction, DebugUse> user, Value newValue) { - Instruction instruction = user.getKey(); - DebugUse debugUse = user.getValue(); - instruction.replaceDebugValue(this, newValue); + private void replaceUserInDebugData(Instruction user, Value newValue) { + user.replaceDebugValue(this, newValue); // If user is a DebugLocalRead and now has no debug values, we would like to remove it. // However, replaceUserInDebugData() is called in contexts where the instruction list is being // iterated, so we cannot remove user from the instruction list at this point. - if (newValue.hasLocalInfo()) { - DebugUse existing = newValue.debugData.users.get(instruction); - assert existing != null; - newValue.debugData.users.put(instruction, DebugUse.join(debugUse, existing)); - } } public void replaceDebugUser(Instruction oldUser, Instruction newUser) { - DebugUse use = debugData.users.remove(oldUser); - if (use == DebugUse.START && newUser.outValue == this) { - // Register allocation requires that debug values are live at the entry to the instruction. - // Remove this debug use since it is starting at the instruction that defines it. - return; - } - if (use != null) { - newUser.addDebugValue(this); - debugData.users.put(newUser, use); + boolean removed = debugData.users.remove(oldUser); + assert removed; + if (removed) { + addDebugLocalEnd(newUser); } } @@ -948,8 +836,8 @@ */ public boolean isNeverNull() { assert type.isReferenceType(); - return (definition != null && definition.isAssumeNonNull()) - || type.nullability().isDefinitelyNotNull(); + return isDefinedByInstructionSatisfying(Instruction::isAssumeWithNonNullAssumption) + || type.isDefinitelyNotNull(); } public boolean isArgument() { @@ -1093,9 +981,18 @@ if (ignoreUser.test(instruction)) { continue; } - if (!instruction.canBeDeadCode(appView, code)) { + DeadInstructionResult result = instruction.canBeDeadCode(appView, code); + if (result.isNotDead()) { return false; } + if (result.isMaybeDead()) { + for (Value valueRequiredToBeDead : result.getValuesRequiredToBeDead()) { + if (!active.contains(valueRequiredToBeDead) + && !valueRequiredToBeDead.isDead(appView, code, ignoreUser, active)) { + return false; + } + } + } Value outValue = instruction.outValue(); if (outValue != null && !active.contains(outValue) @@ -1168,14 +1065,23 @@ Value root = getAliasedValue(); if (root.isPhi()) { assert getSpecificAliasedValue( - value -> !value.isPhi() && value.definition.isAssumeDynamicType()) + value -> + value.isDefinedByInstructionSatisfying( + Instruction::isAssumeWithDynamicTypeAssumption)) == null; - return root.getDynamicUpperBoundType(appView); + TypeElement result = root.getDynamicUpperBoundType(appView); + if (getType().isReferenceType() && getType().isDefinitelyNotNull()) { + return result.asReferenceType().asMeetWithNotNull(); + } + return result; } // Try to find an alias of the receiver, which is defined by an instruction of the type Assume. Value aliasedValue = - getSpecificAliasedValue(value -> !value.isPhi() && value.definition.isAssumeDynamicType()); + getSpecificAliasedValue( + value -> + value.isDefinedByInstructionSatisfying( + Instruction::isAssumeWithDynamicTypeAssumption)); TypeElement lattice; if (aliasedValue != null) { // If there is an alias of the receiver, which is defined by an Assume instruction that @@ -1228,7 +1134,8 @@ } // Try to find an alias of the receiver, which is defined by an instruction of the type Assume. - Value aliasedValue = getSpecificAliasedValue(value -> value.definition.isAssumeDynamicType()); + Value aliasedValue = + getSpecificAliasedValue(value -> value.definition.isAssumeWithDynamicTypeAssumption()); if (aliasedValue != null) { ClassTypeElement lattice = aliasedValue.definition.asAssume().getDynamicTypeAssumption().getDynamicLowerBoundType();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java index 0dd98e2..60fcec2 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -2377,12 +2377,10 @@ // Add a use if this instruction is overwriting a previous value of the same local. if (previousLocalValue != null && previousLocalValue.getLocalInfo() == ir.getLocalInfo()) { assert ir.outValue() != null; - ir.addDebugValue(previousLocalValue); previousLocalValue.addDebugLocalEnd(ir); } // Add reads of locals if any are pending. for (Value value : debugLocalEnds) { - ir.addDebugValue(value); value.addDebugLocalEnd(ir); } previousLocalValue = null;
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 844ccce..35760b4 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
@@ -53,7 +53,6 @@ import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter; import com.android.tools.r8.ir.optimize.AssertionsRewriter; import com.android.tools.r8.ir.optimize.AssumeInserter; -import com.android.tools.r8.ir.optimize.Assumer; import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization; import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult; import com.android.tools.r8.ir.optimize.CodeRewriter; @@ -70,7 +69,6 @@ import com.android.tools.r8.ir.optimize.RedundantFieldLoadElimination; import com.android.tools.r8.ir.optimize.ReflectionOptimizer; import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter; -import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization; import com.android.tools.r8.ir.optimize.classinliner.ClassInliner; import com.android.tools.r8.ir.optimize.enums.EnumUnboxer; import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer; @@ -160,15 +158,13 @@ private final Devirtualizer devirtualizer; private final CovariantReturnTypeAnnotationTransformer covariantReturnTypeAnnotationTransformer; private final StringSwitchRemover stringSwitchRemover; - private final UninstantiatedTypeOptimization uninstantiatedTypeOptimization; private final TypeChecker typeChecker; private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter; private final ServiceLoaderRewriter serviceLoaderRewriter; private final EnumValueOptimizer enumValueOptimizer; private final EnumUnboxer enumUnboxer; - // Assumers that will insert Assume instructions. - public final Collection<Assumer> assumers = new ArrayList<>(); + public final AssumeInserter assumeInserter; private final DynamicTypeOptimization dynamicTypeOptimization; final AssertionsRewriter assertionsRewriter; @@ -256,7 +252,6 @@ this.lensCodeRewriter = null; this.identifierNameStringMarker = null; this.devirtualizer = null; - this.uninstantiatedTypeOptimization = null; this.typeChecker = null; this.d8NestBasedAccessDesugaring = null; this.stringSwitchRemover = null; @@ -264,6 +259,7 @@ this.methodOptimizationInfoCollector = null; this.enumValueOptimizer = null; this.enumUnboxer = null; + this.assumeInserter = null; return; } this.lambdaRewriter = @@ -291,20 +287,12 @@ assert appView.appInfo().hasLiveness(); assert appView.rootSet() != null; AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness(); - if (options.enableNonNullTracking) { - assumers.add(new AssumeInserter(appViewWithLiveness)); - } + assumeInserter = new AssumeInserter(appViewWithLiveness); this.classInliner = options.enableClassInlining && options.enableInlining ? new ClassInliner() : null; this.classStaticizer = options.enableClassStaticizer ? new ClassStaticizer(appViewWithLiveness, this) : null; - this.dynamicTypeOptimization = - options.enableDynamicTypeOptimization - ? new DynamicTypeOptimization(appViewWithLiveness) - : null; - if (dynamicTypeOptimization != null) { - assumers.add(dynamicTypeOptimization); - } + this.dynamicTypeOptimization = new DynamicTypeOptimization(appViewWithLiveness); this.fieldAccessAnalysis = FieldAccessAnalysis.enable(options) ? new FieldAccessAnalysis(appViewWithLiveness) : null; this.libraryMethodOverrideAnalysis = @@ -329,10 +317,6 @@ } this.devirtualizer = options.enableDevirtualization ? new Devirtualizer(appViewWithLiveness) : null; - this.uninstantiatedTypeOptimization = - options.enableUninstantiatedTypeOptimization - ? new UninstantiatedTypeOptimization(appViewWithLiveness) - : null; this.typeChecker = new TypeChecker(appViewWithLiveness); this.d8NestBasedAccessDesugaring = null; this.serviceLoaderRewriter = @@ -347,6 +331,7 @@ this.enumValueOptimizer = options.enableEnumValueOptimization ? new EnumValueOptimizer(appViewWithLiveness) : null; } else { + this.assumeInserter = null; this.classInliner = null; this.classStaticizer = null; this.dynamicTypeOptimization = null; @@ -359,7 +344,6 @@ this.lensCodeRewriter = null; this.identifierNameStringMarker = null; this.devirtualizer = null; - this.uninstantiatedTypeOptimization = null; this.typeChecker = null; this.d8NestBasedAccessDesugaring = options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null; @@ -489,6 +473,8 @@ public DexApplication convert(DexApplication application, ExecutorService executor) throws ExecutionException { removeLambdaDeserializationMethods(); + workaroundAbstractMethodOnNonAbstractClassVerificationBug( + executor, OptimizationFeedbackIgnore.getInstance()); timing.begin("IR conversion"); ThreadUtils.processItems(application.classes(), this::convertMethods, executor); @@ -652,6 +638,28 @@ } } + private void workaroundAbstractMethodOnNonAbstractClassVerificationBug( + ExecutorService executorService, OptimizationFeedback feedback) throws ExecutionException { + if (!options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()) { + return; + } + assert delayedOptimizationFeedback.noUpdatesLeft(); + ThreadUtils.processItems( + appView.appInfo().classes(), + clazz -> { + if (!clazz.isAbstract()) { + clazz.forEachMethod( + method -> { + if (method.isAbstract()) { + method.accessFlags.unsetAbstract(); + finalizeEmptyThrowingCode(method, feedback); + } + }); + } + }, + executorService); + } + public DexApplication optimize() throws ExecutionException { ExecutorService executor = Executors.newSingleThreadExecutor(); try { @@ -667,6 +675,8 @@ computeReachabilitySensitivity(application); collectLambdaMergingCandidates(application); collectStaticizerCandidates(application); + workaroundAbstractMethodOnNonAbstractClassVerificationBug( + executorService, simpleOptimizationFeedback); // The process is in two phases in general. // 1) Subject all DexEncodedMethods to optimization, except some optimizations that require @@ -675,6 +685,9 @@ // 2) Revisit DexEncodedMethods for the collected candidates. printPhase("Primary optimization pass"); + if (fieldAccessAnalysis != null) { + fieldAccessAnalysis.fieldAssignmentTracker().initialize(); + } // Process the application identifying outlining candidates. GraphLense graphLenseForIR = appView.graphLense(); @@ -1259,9 +1272,9 @@ previous = printMethod(code, "IR after disable assertions (SSA)", previous); - timing.begin("Insert assume instructions"); - CodeRewriter.insertAssumeInstructions(code, assumers, timing); - timing.end(); + if (assumeInserter != null) { + assumeInserter.insertAssumeInstructions(code, timing); + } previous = printMethod(code, "IR after inserting assume instructions (SSA)", previous); @@ -1323,14 +1336,6 @@ assert code.verifyTypes(appView); - if (uninstantiatedTypeOptimization != null) { - timing.begin("Rewrite uninstantiated types"); - uninstantiatedTypeOptimization.rewrite(code); - timing.end(); - } - - assert code.verifyTypes(appView); - timing.begin("Remove trivial type checks/casts"); codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code); timing.end(); @@ -1370,10 +1375,10 @@ codeRewriter.splitRangeInvokeConstants(code); timing.end(); timing.begin("Propogate sparse conditionals"); - new SparseConditionalConstantPropagation(code).run(); + new SparseConditionalConstantPropagation(appView, code).run(); timing.end(); - timing.begin("Rewrite always throwing invokes"); - codeRewriter.processMethodsNeverReturningNormally(code); + timing.begin("Rewrite always throwing instructions"); + codeRewriter.optimizeAlwaysThrowingInstructions(code); timing.end(); timing.begin("Simplify control flow"); if (codeRewriter.simplifyControlFlow(code)) { @@ -1597,7 +1602,7 @@ timing.end(); } - if (!assumers.isEmpty()) { + if (assumeInserter != null) { timing.begin("Remove assume instructions"); CodeRewriter.removeAssumeInstructions(appView, code); timing.end(); @@ -1702,10 +1707,7 @@ private void finalizeEmptyThrowingCode(DexEncodedMethod method, OptimizationFeedback feedback) { assert options.isGeneratingClassFiles() || options.isGeneratingDex(); - Code emptyThrowingCode = - options.isGeneratingClassFiles() - ? method.buildEmptyThrowingCfCode() - : method.buildEmptyThrowingDexCode(); + Code emptyThrowingCode = method.buildEmptyThrowingCode(options); method.setCode(emptyThrowingCode, appView); feedback.markProcessed(method, ConstraintWithTarget.ALWAYS); }
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 7211439..e54ec6c 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
@@ -44,8 +44,6 @@ void methodReturnValueOnlyDependsOnArguments(DexEncodedMethod method); - void methodNeverReturnsNull(DexEncodedMethod method); - void methodNeverReturnsNormally(DexEncodedMethod method); void markProcessed(DexEncodedMethod method, ConstraintWithTarget state);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java index e66d342..ba49fc0 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
@@ -14,6 +14,7 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.FieldInstruction; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionListIterator; @@ -84,16 +85,20 @@ } } } else if (instruction.isFieldInstruction()) { - DexEncodedField encodedField = - appView.definitionFor(instruction.asFieldInstruction().getField()); - if (encodedField != null && fieldAccessRequiresRewriting(encodedField, method)) { + // Since we only need to desugar accesses to private fields, and all accesses to private + // fields must be accessing the private field directly on its holder, we can lookup the + // field on the holder instead of resolving the field. + FieldInstruction fieldInstruction = instruction.asFieldInstruction(); + DexClass holder = appView.definitionForHolder(fieldInstruction.getField()); + DexEncodedField field = fieldInstruction.getField().lookupOnClass(holder); + if (field != null && fieldAccessRequiresRewriting(field, method)) { if (instruction.isInstanceGet() || instruction.isStaticGet()) { - DexMethod bridge = ensureFieldAccessBridge(encodedField, true); + DexMethod bridge = ensureFieldAccessBridge(field, true); instructions.replaceCurrentInstruction( new InvokeStatic(bridge, instruction.outValue(), instruction.inValues())); } else { assert instruction.isInstancePut() || instruction.isStaticPut(); - DexMethod bridge = ensureFieldAccessBridge(encodedField, false); + DexMethod bridge = ensureFieldAccessBridge(field, false); instructions.replaceCurrentInstruction( new InvokeStatic(bridge, instruction.outValue(), instruction.inValues())); }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java index 88108f9..a91c1aa 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; +import com.android.tools.r8.graph.DebugLocalInfo; import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexClasspathClass; @@ -529,15 +530,16 @@ DexMethod conversionMethod = createConversionMethod(argType, argType, argVivifiedType); // The value is null only if the input is null. Value convertedValue = - createConversionValue(code, inValue.getType().nullability(), argVivifiedType); + createConversionValue(code, inValue.getType().nullability(), argVivifiedType, null); return new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(inValue)); } private Instruction createReturnConversionAndReplaceUses( IRCode code, InvokeMethod invokeMethod, DexType returnType, DexType returnVivifiedType) { DexMethod conversionMethod = createConversionMethod(returnType, returnVivifiedType, returnType); - Value convertedValue = createConversionValue(code, Nullability.maybeNull(), returnType); Value outValue = invokeMethod.outValue(); + Value convertedValue = + createConversionValue(code, Nullability.maybeNull(), returnType, outValue.getLocalInfo()); outValue.replaceUsers(convertedValue); // The only user of out value is now the new invoke static, so no type propagation is required. outValue.setType( @@ -571,8 +573,9 @@ conversionHolder, factory.createProto(destType, srcType), factory.convertMethodName); } - private Value createConversionValue(IRCode code, Nullability nullability, DexType valueType) { - return code.createValue(TypeElement.fromDexType(valueType, nullability, appView)); + private Value createConversionValue( + IRCode code, Nullability nullability, DexType valueType, DebugLocalInfo localInfo) { + return code.createValue(TypeElement.fromDexType(valueType, nullability, appView), localInfo); } public boolean canConvert(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java index 7da225c..2586141 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -84,14 +84,15 @@ return appView.definitionFor(appView.graphLense().lookupType(type)); } - private DexEncodedMethod definitionFor( + private DexEncodedMethod lookupOnHolder( DexMethod method, DexClassAndMethod context, Invoke.Type invokeType) { return appView.definitionFor( appView.graphLense().lookupMethod(method, context.getReference(), invokeType).getMethod()); } - private DexEncodedField definitionFor(DexField field) { - return appView.definitionFor(appView.graphLense().lookupField(field)); + private DexEncodedField lookupOnHolder(DexField field) { + DexField rewritten = appView.graphLense().lookupField(field); + return rewritten.lookupOnClass(appView.definitionForHolder(rewritten)); } // Extract the list of types in the programClass' nest, of host hostClass @@ -365,7 +366,7 @@ if (!method.holder.isClassType()) { return false; } - DexEncodedMethod encodedMethod = definitionFor(method, context, invokeType); + DexEncodedMethod encodedMethod = lookupOnHolder(method, context, invokeType); if (encodedMethod != null && invokeRequiresRewriting(encodedMethod, context)) { ensureInvokeBridge(encodedMethod); return true; @@ -374,7 +375,10 @@ } private boolean registerFieldAccess(DexField field, boolean isGet) { - DexEncodedField encodedField = definitionFor(field); + // Since we only need to desugar accesses to private fields, and all accesses to private + // fields must be accessing the private field directly on its holder, we can lookup the field + // on the holder instead of resolving the field. + DexEncodedField encodedField = lookupOnHolder(field); if (encodedField != null && fieldAccessRequiresRewriting(encodedField, context)) { ensureFieldAccessBridge(encodedField, isGet); return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java index 81b43ef..e4b35af 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -340,7 +340,7 @@ if (method.holder() == dexItemFactory.kotlin.assertions.type) { rewriteKotlinAssertionEnable(code, transformation, iterator, invoke); } else { - iterator.replaceCurrentInstruction(code.createIntConstant(0)); + iterator.replaceCurrentInstruction(code.createIntConstant(0, current.getLocalInfo())); } } } else if (current.isStaticPut()) { @@ -355,13 +355,16 @@ if (isInitializerEnablingJavaVmAssertions && staticGet.getField().name == dexItemFactory.assertionsDisabled) { iterator.replaceCurrentInstruction( - code.createIntConstant(transformation == AssertionTransformation.DISABLE ? 1 : 0)); + code.createIntConstant( + transformation == AssertionTransformation.DISABLE ? 1 : 0, + current.getLocalInfo())); } // Rewrite kotlin._Assertions.ENABLED getter. if (staticGet.getField() == dexItemFactory.kotlin.assertions.enabledField) { iterator.replaceCurrentInstruction( code.createIntConstant( - kotlinTransformation == AssertionTransformation.DISABLE ? 0 : 1)); + kotlinTransformation == AssertionTransformation.DISABLE ? 0 : 1, + current.getLocalInfo())); } } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java index 611a0a1..0008896 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
@@ -3,19 +3,23 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.optimize; +import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull; import static com.google.common.base.Predicates.alwaysTrue; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.ir.analysis.type.ClassTypeElement; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.Assume; +import com.android.tools.r8.ir.code.Assume.DynamicTypeAssumption; import com.android.tools.r8.ir.code.Assume.NonNullAssumption; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.BasicBlockIterator; +import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.DominatorTree; import com.android.tools.r8.ir.code.FieldInstruction; import com.android.tools.r8.ir.code.IRCode; @@ -29,8 +33,11 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo; import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; +import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.TriConsumer; +import com.android.tools.r8.utils.TriFunction; +import com.android.tools.r8.utils.TriPredicate; import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; @@ -44,24 +51,23 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import java.util.function.BiFunction; -import java.util.function.BiPredicate; +import java.util.function.Consumer; import java.util.function.Predicate; -public class AssumeInserter implements Assumer { +public class AssumeInserter { private final AppView<? extends AppInfoWithClassHierarchy> appView; + private final InternalOptions options; public AssumeInserter(AppView<? extends AppInfoWithClassHierarchy> appView) { this.appView = appView; + this.options = appView.options(); } - @Override public void insertAssumeInstructions(IRCode code, Timing timing) { insertAssumeInstructionsInBlocks(code, code.listIterator(), alwaysTrue(), timing); } - @Override public void insertAssumeInstructionsInBlocks( IRCode code, BasicBlockIterator blockIterator, @@ -89,7 +95,7 @@ timing.end(); timing.begin("Part 3: Compute dominated users"); - Map<Instruction, Set<Value>> redundantKeys = + Map<Instruction, Map<Value, AssumedValueInfo>> redundantAssumedValues = computeDominanceForAssumedValues(code, assumedValues); timing.end(); if (assumedValues.isEmpty()) { @@ -97,7 +103,7 @@ } timing.begin("Part 4: Remove redundant dominated assume instructions"); - removeRedundantDominatedAssumeInstructions(assumedValues, redundantKeys); + removeRedundantDominatedAssumeInstructions(assumedValues, redundantAssumedValues); timing.end(); if (assumedValues.isEmpty()) { return; @@ -145,29 +151,15 @@ } } - Value outValue = current.outValue(); if (current.isInvokeMethod()) { - InvokeMethod invoke = current.asInvokeMethod(); - if (invoke.hasOutValue() || !invoke.getInvokedMethod().proto.parameters.isEmpty()) { - // Case (2) and (3). - needsAssumeInstruction |= - computeAssumedValuesFromSingleTarget(code, invoke, assumedValuesBuilder); - } + // Case (2) and (3). + needsAssumeInstruction |= + computeAssumedValuesForInvokeMethod( + code, current.asInvokeMethod(), assumedValuesBuilder); } else if (current.isFieldGet()) { // Case (4), field-get instructions that are guaranteed to read a non-null value. - FieldInstruction fieldInstruction = current.asFieldInstruction(); - DexField field = fieldInstruction.getField(); - if (isNullableReferenceTypeWithNonDebugUsers(outValue)) { - DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField(); - if (encodedField != null) { - FieldOptimizationInfo optimizationInfo = encodedField.getOptimizationInfo(); - if (optimizationInfo.getDynamicUpperBoundType() != null - && optimizationInfo.getDynamicUpperBoundType().isDefinitelyNotNull()) { - assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(current, outValue); - needsAssumeInstruction = true; - } - } - } + needsAssumeInstruction |= + computeAssumedValuesForFieldGet(current.asFieldInstruction(), assumedValuesBuilder); } // If we need to insert an assume instruction into a block with catch handlers, we split the @@ -202,6 +194,35 @@ } } + private boolean computeAssumedValuesForInvokeMethod( + IRCode code, InvokeMethod invoke, AssumedValues.Builder assumedValuesBuilder) { + if (!invoke.hasOutValue() && invoke.getInvokedMethod().proto.parameters.isEmpty()) { + return false; + } + + DexMethod invokedMethod = invoke.getInvokedMethod(); + if (invokedMethod.holder.isArrayType() + && invokedMethod.match(appView.dexItemFactory().objectMembers.clone)) { + return computeAssumedValuesFromArrayClone(invoke, assumedValuesBuilder); + } + + return computeAssumedValuesFromSingleTarget(code, invoke, assumedValuesBuilder); + } + + private boolean computeAssumedValuesFromArrayClone( + InvokeMethod invoke, AssumedValues.Builder assumedValuesBuilder) { + Value outValue = invoke.outValue(); + if (outValue == null || !outValue.hasNonDebugUsers()) { + return false; + } + + TypeElement dynamicUpperBoundType = + TypeElement.fromDexType(invoke.getInvokedMethod().holder, definitelyNotNull(), appView); + assumedValuesBuilder.addAssumedValueKnownToDominateAllUsers( + invoke, outValue, dynamicUpperBoundType, null); + return true; + } + private boolean computeAssumedValuesFromSingleTarget( IRCode code, InvokeMethod invoke, AssumedValues.Builder assumedValuesBuilder) { DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.context()); @@ -214,11 +235,13 @@ // Case (2), invocations that are guaranteed to return a non-null value. Value outValue = invoke.outValue(); - if (outValue != null - && optimizationInfo.neverReturnsNull() - && isNullableReferenceTypeWithNonDebugUsers(outValue)) { - assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(invoke, outValue); - needsAssumeInstruction = true; + if (outValue != null && outValue.hasNonDebugUsers()) { + needsAssumeInstruction = + computeAssumedValuesForOutValue( + invoke, + optimizationInfo.getDynamicUpperBoundTypeOrElse(outValue.getType()), + optimizationInfo.getDynamicLowerBoundType(), + assumedValuesBuilder); } // Case (3), parameters that are not null after the invocation. @@ -239,31 +262,116 @@ return needsAssumeInstruction; } + private boolean computeAssumedValuesForFieldGet( + FieldInstruction fieldGet, AssumedValues.Builder assumedValuesBuilder) { + Value outValue = fieldGet.outValue(); + if (!outValue.hasNonDebugUsers()) { + return false; + } + + DexEncodedField field = appView.appInfo().resolveField(fieldGet.getField()).getResolvedField(); + if (field == null) { + return false; + } + + FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo(); + return computeAssumedValuesForOutValue( + fieldGet, + optimizationInfo.getDynamicUpperBoundTypeOrElse(outValue.getType()), + optimizationInfo.getDynamicLowerBoundType(), + assumedValuesBuilder); + } + + private boolean computeAssumedValuesForOutValue( + Instruction instruction, + TypeElement dynamicUpperBoundType, + ClassTypeElement dynamicLowerBoundType, + AssumedValues.Builder assumedValuesBuilder) { + Value outValue = instruction.outValue(); + + // Do not insert dynamic type information if it does not refine the static type. + boolean isRedundant = + !dynamicUpperBoundType.strictlyLessThan(outValue.getType(), appView) + && dynamicLowerBoundType == null; + if (isRedundant) { + return false; + } + + // Do not insert dynamic type information if the dynamic type only refines the nullability. + if (dynamicUpperBoundType.equalUpToNullability(outValue.getType()) + && dynamicLowerBoundType == null) { + assert dynamicUpperBoundType.isDefinitelyNotNull(); + assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(instruction, outValue); + } else { + assumedValuesBuilder.addAssumedValueKnownToDominateAllUsers( + instruction, outValue, dynamicUpperBoundType, dynamicLowerBoundType); + } + return true; + } + private void removeRedundantAssumeInstructions(AssumedValues assumedValues) { assumedValues.removeIf( - (instruction, assumedValue) -> { + (instruction, assumedValue, assumedValueInfo) -> { + // Assumed values with dynamic type information are never redundant. + if (assumedValueInfo.hasDynamicTypeInfo()) { + return false; + } + + assert assumedValueInfo.isNonNull(); + + // Otherwise, it is redundant if it is defined by another instruction that guarantees its + // non-nullness. if (assumedValue.isPhi()) { return false; } + Instruction definition = assumedValue.definition; - return definition != instruction && assumedValues.contains(definition, assumedValue); + if (definition == instruction) { + return false; + } + + AssumedValueInfo otherAssumedValueInfo = + assumedValues.getAssumedValueInfo(definition, assumedValue); + if (otherAssumedValueInfo == null) { + return false; + } + + if (!otherAssumedValueInfo.isNonNull()) { + // This is not redundant, but we can strenghten it with the dynamic type information + // from the other assume instruction. + assumedValueInfo.setDynamicTypeAssumption( + otherAssumedValueInfo.getDynamicTypeAssumption()); + return false; + } + + return true; }); } - private Map<Instruction, Set<Value>> computeDominanceForAssumedValues( + private Map<Instruction, Map<Value, AssumedValueInfo>> computeDominanceForAssumedValues( IRCode code, AssumedValues assumedValues) { - Map<Instruction, Set<Value>> redundantKeys = new IdentityHashMap<>(); + Map<Instruction, Map<Value, AssumedValueInfo>> redundantAssumedValues = new IdentityHashMap<>(); LazyDominatorTree lazyDominatorTree = new LazyDominatorTree(code); Map<BasicBlock, Set<BasicBlock>> dominatedBlocksCache = new IdentityHashMap<>(); assumedValues.computeDominance( - (instruction, assumedValue) -> { - Set<Value> alreadyAssumedValues = redundantKeys.get(instruction); - if (alreadyAssumedValues != null && alreadyAssumedValues.contains(assumedValue)) { - // Returning redundant() will cause the entry (instruction, assumedValue) to be removed. - return AssumedDominance.redundant(); + (instruction, assumedValue, assumedValueInfo) -> { + Map<Value, AssumedValueInfo> alreadyAssumedValues = + redundantAssumedValues.get(instruction); + if (alreadyAssumedValues != null) { + AssumedValueInfo alreadyAssumedValueInfo = alreadyAssumedValues.get(assumedValue); + if (alreadyAssumedValueInfo != null) { + if (assumedValueInfo.isSubsumedBy(alreadyAssumedValueInfo)) { + // Returning redundant() will cause the entry (instruction, assumedValue) to be + // removed. + return AssumedDominance.redundant(); + } + + // This assume value is dominated by the other assume value, so strengthen this one. + assumedValueInfo.strengthenWith(alreadyAssumedValueInfo); + } } - // If this value is non-null since its definition, then it is known to dominate all users. + // If this value is the out-value of some instruction it is known to dominate all users. if (assumedValue == instruction.outValue()) { return AssumedDominance.everything(); } @@ -327,9 +435,9 @@ // Record that there is no need to insert an assume instruction for the non-null-value // after the given user in case the user is also a null check for the non-null-value. - redundantKeys - .computeIfAbsent(user, ignore -> Sets.newIdentityHashSet()) - .add(assumedValue); + redundantAssumedValues + .computeIfAbsent(user, ignore -> new IdentityHashMap<>()) + .put(assumedValue, assumedValueInfo); } } for (Phi user : assumedValue.uniquePhiUsers()) { @@ -341,68 +449,54 @@ } return dominance.build(); }); - return redundantKeys; + return redundantAssumedValues; } private void removeRedundantDominatedAssumeInstructions( - AssumedValues assumedValues, Map<Instruction, Set<Value>> redundantKeys) { - assumedValues.removeAll(redundantKeys); + AssumedValues assumedValues, + Map<Instruction, Map<Value, AssumedValueInfo>> redundantAssumedValues) { + assumedValues.removeAll(redundantAssumedValues); } private void materializeAssumeInstructions(IRCode code, AssumedValues assumedValues) { Set<Value> affectedValues = Sets.newIdentityHashSet(); Map<BasicBlock, Map<Instruction, List<Instruction>>> pendingInsertions = new IdentityHashMap<>(); - assumedValues.forEach( - (instruction, assumedValue, assumedValueInfo) -> { - BasicBlock block = instruction.getBlock(); - BasicBlock insertionBlock = getInsertionBlock(instruction); - AssumedDominance dominance = assumedValueInfo.getDominance(); - Value newValue = - code.createValue( - assumedValue.getType().asReferenceType().asMeetWithNotNull(), - assumedValue.getLocalInfo()); - if (dominance.isEverything()) { - assumedValue.replaceUsers(newValue); - } else if (dominance.isEverythingElse()) { - assumedValue.replaceSelectiveInstructionUsers(newValue, user -> user != instruction); - assumedValue.replacePhiUsers(newValue); - } else if (dominance.isSomething()) { - SomethingAssumedDominance somethingDominance = dominance.asSomething(); - somethingDominance - .getDominatedPhiUsers() - .forEach( - (user, indices) -> { - IntListIterator iterator = indices.iterator(); - while (iterator.hasNext()) { - Value operand = user.getOperand(iterator.nextInt()); - if (operand != assumedValue) { - assert operand.isDefinedByInstructionSatisfying( - Instruction::isAssumeNonNull); - iterator.remove(); - } - } - }); - assumedValue.replaceSelectiveUsers( - newValue, - somethingDominance.getDominatedUsers(), - somethingDominance.getDominatedPhiUsers()); - } - affectedValues.addAll(newValue.affectedValues()); - - Assume assumeInstruction = - Assume.createAssumeNonNullInstruction(newValue, assumedValue, instruction, appView); - assumeInstruction.setPosition(instruction.getPosition()); - if (insertionBlock != block) { - insertionBlock.listIterator(code).add(assumeInstruction); - } else { - pendingInsertions - .computeIfAbsent(block, ignore -> new IdentityHashMap<>()) - .computeIfAbsent(instruction, ignore -> new ArrayList<>()) - .add(assumeInstruction); - } - }); + // We materialize the assume instructions in two steps. First, we materialize all the assume + // instructions that do not dominate everything. These assume instructions can refine previous + // assume instructions, so we materialize those first as they are "stronger". + // + // Example: + // 1. Object value = getNullableValueWithDynamicType(); + // 2. Object nullableValueWithDynamicType = assume(value, ...) + // 3. checkNotNull(value); + // 4. Object nonNullValueWithDynamicType = assume(value, ...) + // 5. return value; + // + // In this example, we first materialize the assume instruction in line 4, and replace the + // dominated use of `value` in line 5 by the new assumed value `nonNullValueWithDynamicType`. + // Afterwards, we materialize the assume instruction in line 2, and replace all remaining users + // of `value` by `nullableValueWithDynamicType`. + // + // Result: + // 1. Object value = getNullableValueWithDynamicType(); + // 2. Object nullableValueWithDynamicType = assume(value, ...) + // 3. checkNotNull(nullableValueWithDynamicType); + // 4. Object nonNullValueWithDynamicType = assume(value, ...) + // 5. return nonNullValueWithDynamicType; + materializeSelectedAssumeInstructions( + code, + assumedValues, + affectedValues, + pendingInsertions, + assumedValueInfo -> !assumedValueInfo.dominance.isEverything()); + materializeSelectedAssumeInstructions( + code, + assumedValues, + affectedValues, + pendingInsertions, + assumedValueInfo -> assumedValueInfo.dominance.isEverything()); pendingInsertions.forEach( (block, pendingInsertionsPerInstruction) -> { InstructionListIterator instructionIterator = block.listIterator(code); @@ -420,6 +514,83 @@ } } + private void materializeSelectedAssumeInstructions( + IRCode code, + AssumedValues assumedValues, + Set<Value> affectedValues, + Map<BasicBlock, Map<Instruction, List<Instruction>>> pendingInsertions, + Predicate<AssumedValueInfo> predicate) { + assumedValues.removeIf( + (instruction, assumedValue, assumedValueInfo) -> { + if (!predicate.test(assumedValueInfo)) { + return false; + } + + BasicBlock block = instruction.getBlock(); + BasicBlock insertionBlock = getInsertionBlock(instruction); + + AssumedDominance dominance = assumedValueInfo.getDominance(); + Value newValue = + assumedValueInfo.isNull() + ? code.createValue(TypeElement.getNull()) + : code.createValue( + assumedValueInfo.isNonNull() + ? assumedValue.getType().asReferenceType().asMeetWithNotNull() + : assumedValue.getType(), + assumedValue.getLocalInfo()); + if (dominance.isEverything()) { + assumedValue.replaceUsers(newValue); + } else if (dominance.isEverythingElse()) { + assumedValue.replaceSelectiveInstructionUsers(newValue, user -> user != instruction); + assumedValue.replacePhiUsers(newValue); + } else if (dominance.isSomething()) { + SomethingAssumedDominance somethingDominance = dominance.asSomething(); + somethingDominance + .getDominatedPhiUsers() + .forEach( + (user, indices) -> { + IntListIterator iterator = indices.iterator(); + while (iterator.hasNext()) { + Value operand = user.getOperand(iterator.nextInt()); + if (operand != assumedValue) { + assert operand.isDefinedByInstructionSatisfying(Instruction::isAssume); + iterator.remove(); + } + } + }); + assumedValue.replaceSelectiveUsers( + newValue, + somethingDominance.getDominatedUsers(), + somethingDominance.getDominatedPhiUsers()); + } + affectedValues.addAll(newValue.affectedValues()); + + Instruction assumeInstruction; + if (assumedValueInfo.isNull()) { + assumeInstruction = new ConstNumber(newValue, 0); + } else { + assumeInstruction = + new Assume( + assumedValueInfo.dynamicTypeAssumption, + assumedValueInfo.nonNullAssumption, + newValue, + assumedValue, + instruction, + appView); + } + assumeInstruction.setPosition(instruction.getPosition()); + if (insertionBlock != block) { + insertionBlock.listIterator(code).add(assumeInstruction); + } else { + pendingInsertions + .computeIfAbsent(block, ignore -> new IdentityHashMap<>()) + .computeIfAbsent(instruction, ignore -> new ArrayList<>()) + .add(assumeInstruction); + } + return true; + }); + } + private BasicBlock getInsertionBlock(Instruction instruction) { if (instruction.isIf()) { return instruction.asIf().targetFromNonNullObject(); @@ -461,10 +632,6 @@ return type.isReferenceType() && type.asReferenceType().isNullable(); } - private static boolean isNullableReferenceTypeWithNonDebugUsers(Value value) { - return isNullableReferenceType(value) && value.numberOfAllNonDebugUsers() > 0; - } - private static boolean isNullableReferenceTypeWithOtherNonDebugUsers( Value value, Instruction ignore) { if (isNullableReferenceType(value)) { @@ -483,6 +650,7 @@ static class AssumedValueInfo { AssumedDominance dominance; + DynamicTypeAssumption dynamicTypeAssumption; NonNullAssumption nonNullAssumption; AssumedValueInfo(AssumedDominance dominance) { @@ -497,9 +665,55 @@ this.dominance = dominance; } + boolean hasDynamicTypeInfo() { + return dynamicTypeAssumption != null; + } + + DynamicTypeAssumption getDynamicTypeAssumption() { + return dynamicTypeAssumption; + } + + void setDynamicTypeAssumption(DynamicTypeAssumption dynamicTypeAssumption) { + this.dynamicTypeAssumption = dynamicTypeAssumption; + } + + void setDynamicTypeAssumption( + TypeElement dynamicUpperBoundType, ClassTypeElement dynamicLowerBoundType) { + dynamicTypeAssumption = + new DynamicTypeAssumption(dynamicUpperBoundType, dynamicLowerBoundType); + if (dynamicUpperBoundType.isDefinitelyNotNull()) { + setNotNull(); + } + if (dynamicLowerBoundType != null && dynamicLowerBoundType.isDefinitelyNotNull()) { + setNotNull(); + } + } + + boolean isNull() { + return dynamicTypeAssumption != null + && dynamicTypeAssumption.getDynamicUpperBoundType().isDefinitelyNull(); + } + + boolean isNonNull() { + return nonNullAssumption != null; + } + void setNotNull() { nonNullAssumption = NonNullAssumption.get(); } + + boolean isSubsumedBy(AssumedValueInfo other) { + return !hasDynamicTypeInfo() && other.isNonNull(); + } + + void strengthenWith(AssumedValueInfo info) { + if (info.isNonNull()) { + setNotNull(); + } + if (!hasDynamicTypeInfo() && info.hasDynamicTypeInfo()) { + setDynamicTypeAssumption(info.getDynamicTypeAssumption()); + } + } } static class AssumedValues { @@ -519,7 +733,8 @@ return new Builder(); } - void computeDominance(BiFunction<Instruction, Value, AssumedDominance> function) { + void computeDominance( + TriFunction<Instruction, Value, AssumedValueInfo, AssumedDominance> function) { Iterator<Entry<Instruction, Map<Value, AssumedValueInfo>>> outerIterator = assumedValues.entrySet().iterator(); while (outerIterator.hasNext()) { @@ -539,7 +754,7 @@ continue; } assert dominance.isUnknown(); - dominance = function.apply(instruction, assumedValue); + dominance = function.apply(instruction, assumedValue, assumedValueInfo); if ((dominance.isNothing() && !assumedValue.isArgument()) || dominance.isUnknown()) { innerIterator.remove(); } else { @@ -552,9 +767,9 @@ } } - boolean contains(Instruction instruction, Value assumedValue) { + AssumedValueInfo getAssumedValueInfo(Instruction instruction, Value assumedValue) { Map<Value, AssumedValueInfo> dominancePerValue = assumedValues.get(instruction); - return dominancePerValue != null && dominancePerValue.containsKey(assumedValue); + return dominancePerValue != null ? dominancePerValue.get(assumedValue) : null; } boolean isEmpty() { @@ -569,12 +784,12 @@ consumer.accept(instruction, assumedValue, assumedValueInfo))); } - void removeAll(Map<Instruction, Set<Value>> keys) { + void removeAll(Map<Instruction, Map<Value, AssumedValueInfo>> keys) { keys.forEach( - (instruction, values) -> { + (instruction, redundantAssumedValues) -> { Map<Value, AssumedValueInfo> dominancePerValue = assumedValues.get(instruction); if (dominancePerValue != null) { - values.forEach(dominancePerValue::remove); + redundantAssumedValues.keySet().forEach(dominancePerValue::remove); if (dominancePerValue.isEmpty()) { assumedValues.remove(instruction); } @@ -582,7 +797,7 @@ }); } - void removeIf(BiPredicate<Instruction, Value> predicate) { + void removeIf(TriPredicate<Instruction, Value, AssumedValueInfo> predicate) { Iterator<Entry<Instruction, Map<Value, AssumedValueInfo>>> outerIterator = assumedValues.entrySet().iterator(); while (outerIterator.hasNext()) { @@ -592,8 +807,10 @@ Iterator<Entry<Value, AssumedValueInfo>> innerIterator = dominancePerValue.entrySet().iterator(); while (innerIterator.hasNext()) { - Value assumedValue = innerIterator.next().getKey(); - if (predicate.test(instruction, assumedValue)) { + Entry<Value, AssumedValueInfo> innerEntry = innerIterator.next(); + Value assumedValue = innerEntry.getKey(); + AssumedValueInfo assumedValueInfo = innerEntry.getValue(); + if (predicate.test(instruction, assumedValue, assumedValueInfo)) { innerIterator.remove(); } } @@ -609,29 +826,49 @@ new LinkedHashMap<>(); // Used to avoid unnecessary block splitting during phase 1. - private final Set<Value> assumedValuesKnownToDominateAllUsers = Sets.newIdentityHashSet(); + private final Set<Value> nonNullValuesKnownToDominateAllUsers = Sets.newIdentityHashSet(); - private void addNonNullValue( - Instruction instruction, Value nonNullValue, AssumedDominance dominance) { - assumedValues - .computeIfAbsent(instruction, ignore -> new LinkedHashMap<>()) - .computeIfAbsent(nonNullValue, ignore -> new AssumedValueInfo(dominance)) - .setNotNull(); - if (dominance.isEverything()) { - assumedValuesKnownToDominateAllUsers.add(nonNullValue); + private void updateAssumedValueInfo( + Instruction instruction, + Value assumedValue, + AssumedDominance dominance, + Consumer<AssumedValueInfo> consumer) { + AssumedValueInfo assumedValueInfo = + assumedValues + .computeIfAbsent(instruction, ignore -> new LinkedHashMap<>()) + .computeIfAbsent(assumedValue, ignore -> new AssumedValueInfo(dominance)); + consumer.accept(assumedValueInfo); + if (dominance.isEverything() && assumedValueInfo.isNonNull()) { + nonNullValuesKnownToDominateAllUsers.add(assumedValue); } } + void addAssumedValueKnownToDominateAllUsers( + Instruction instruction, + Value assumedValue, + TypeElement dynamicUpperBoundType, + ClassTypeElement dynamicLowerBoundType) { + updateAssumedValueInfo( + instruction, + assumedValue, + AssumedDominance.everything(), + assumedValueInfo -> + assumedValueInfo.setDynamicTypeAssumption( + dynamicUpperBoundType, dynamicLowerBoundType)); + } + void addNonNullValueKnownToDominateAllUsers(Instruction instruction, Value nonNullValue) { - addNonNullValue(instruction, nonNullValue, AssumedDominance.everything()); + updateAssumedValueInfo( + instruction, nonNullValue, AssumedDominance.everything(), AssumedValueInfo::setNotNull); } void addNonNullValueWithUnknownDominance(Instruction instruction, Value nonNullValue) { - addNonNullValue(instruction, nonNullValue, AssumedDominance.unknown()); + updateAssumedValueInfo( + instruction, nonNullValue, AssumedDominance.unknown(), AssumedValueInfo::setNotNull); } public boolean isMaybeNull(Value value) { - return !assumedValuesKnownToDominateAllUsers.contains(value); + return !nonNullValuesKnownToDominateAllUsers.contains(value); } public AssumedValues build() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java similarity index 60% rename from src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java rename to src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java index 7f45db3..41f72cb 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
@@ -16,60 +16,68 @@ import java.util.Set; /** - * When we have Assume instructions with a DynamicTypeAssumption we generally verify that the - * dynamic type in the Assume node is always at least as precise as the static type of the - * corresponding value. + * When we have Assume instructions we generally verify that the Assume instructions contribute with + * non-trivial information to the IR (e.g., the dynamic type should be more precise than the static + * type). * * <p>Therefore, when this property may no longer hold for an Assume instruction, we need to remove * it. * * <p>This class is a helper class to remove these instructions. Unlike {@link * CodeRewriter#removeAssumeInstructions} this class does not unconditionally remove all Assume - * instructions, not does it remove all Assume instructions with a DynamicTypeAssumption. + * instructions. */ -public class AssumeDynamicTypeRemover { +public class AssumeRemover { private final AppView<?> appView; private final IRCode code; private final Set<Value> affectedValues = Sets.newIdentityHashSet(); - private final Set<Assume> assumeDynamicTypeInstructionsToRemove = Sets.newIdentityHashSet(); + private final Set<Assume> assumeInstructionsToRemove = Sets.newIdentityHashSet(); private boolean mayHaveIntroducedTrivialPhi = false; - public AssumeDynamicTypeRemover(AppView<?> appView, IRCode code) { + public AssumeRemover(AppView<?> appView, IRCode code) { this.appView = appView; this.code = code; } + public Set<Value> getAffectedValues() { + return affectedValues; + } + public boolean mayHaveIntroducedTrivialPhi() { return mayHaveIntroducedTrivialPhi; } - public void markForRemoval(Assume assumeDynamicTypeInstruction) { - assumeDynamicTypeInstructionsToRemove.add(assumeDynamicTypeInstruction); - } - - public void markUsersForRemoval(Value value) { + public void markAssumeDynamicTypeUsersForRemoval(Value value) { for (Instruction user : value.aliasedUsers()) { - if (user.isAssumeDynamicType()) { - markForRemoval(user.asAssume()); + if (user.isAssume()) { + Assume assumeInstruction = user.asAssume(); + assumeInstruction.unsetDynamicTypeAssumption(); + if (!assumeInstruction.hasNonNullAssumption()) { + assumeInstruction.unsetDynamicTypeAssumption(); + } } } } + private void markForRemoval(Assume assumeInstruction) { + assumeInstructionsToRemove.add(assumeInstruction); + } + public void removeIfMarked( - Assume assumeDynamicTypeInstruction, InstructionListIterator instructionIterator) { - if (assumeDynamicTypeInstructionsToRemove.remove(assumeDynamicTypeInstruction)) { - Value inValue = assumeDynamicTypeInstruction.src(); - Value outValue = assumeDynamicTypeInstruction.outValue(); + Assume assumeInstruction, InstructionListIterator instructionIterator) { + if (assumeInstructionsToRemove.remove(assumeInstruction)) { + Value inValue = assumeInstruction.src(); + Value outValue = assumeInstruction.outValue(); // Check if we need to run the type analysis for the affected values of the out-value. if (!outValue.getType().equals(inValue.getType())) { affectedValues.addAll(outValue.affectedValues()); } - if (outValue.numberOfPhiUsers() > 0) { + if (outValue.hasPhiUsers()) { mayHaveIntroducedTrivialPhi = true; } @@ -78,16 +86,20 @@ } } - public AssumeDynamicTypeRemover removeMarkedInstructions(Set<BasicBlock> blocksToBeRemoved) { - if (!assumeDynamicTypeInstructionsToRemove.isEmpty()) { + public AssumeRemover removeMarkedInstructions() { + return removeMarkedInstructions(null); + } + + public AssumeRemover removeMarkedInstructions(Set<BasicBlock> blocksToBeRemoved) { + if (!assumeInstructionsToRemove.isEmpty()) { for (BasicBlock block : code.blocks) { - if (blocksToBeRemoved.contains(block)) { + if (blocksToBeRemoved != null && blocksToBeRemoved.contains(block)) { continue; } InstructionListIterator instructionIterator = block.listIterator(code); while (instructionIterator.hasNext()) { Instruction instruction = instructionIterator.next(); - if (instruction.isAssumeDynamicType()) { + if (instruction.isAssume()) { removeIfMarked(instruction.asAssume(), instructionIterator); } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java index 9708120..5eb23bb 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -35,6 +35,7 @@ import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.ForEachable; +import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions; import com.android.tools.r8.utils.LazyBox; import com.android.tools.r8.utils.Timing; @@ -276,7 +277,13 @@ } private void abandonCallSitePropagation(ForEachable<ProgramMethod> methods) { - methods.forEach(method -> method.getDefinition().abandonCallSiteOptimizationInfo()); + if (InternalOptions.assertionsEnabled()) { + synchronized (this) { + methods.forEach(method -> method.getDefinition().abandonCallSiteOptimizationInfo()); + } + } else { + methods.forEach(method -> method.getDefinition().abandonCallSiteOptimizationInfo()); + } } private CallSiteOptimizationInfo computeCallSiteOptimizationInfoFromArguments( @@ -435,12 +442,19 @@ return null; } - private boolean verifyAllProgramDispatchTargetsHaveBeenAbandoned( + private synchronized boolean verifyAllProgramDispatchTargetsHaveBeenAbandoned( InvokeMethod invoke, ProgramMethod context) { ProgramMethodSet targets = invoke.lookupProgramDispatchTargets(appView, context); if (targets != null) { for (ProgramMethod target : targets) { - assert target.getDefinition().getCallSiteOptimizationInfo().isAbandoned(); + assert target.getDefinition().getCallSiteOptimizationInfo().isAbandoned() + : "Expected method `" + + target.toSourceString() + + "` to be marked as abandoned (called from `" + + invoke.toString() + + "` in `" + + context.toSourceString() + + "`)"; } } return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java index e769dc9..7272948 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -30,7 +30,6 @@ import com.android.tools.r8.graph.DexValue.DexValueString; import com.android.tools.r8.graph.FieldResolutionResult; import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.ir.analysis.ValueMayDependOnEnvironmentAnalysis; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.ArrayPut; import com.android.tools.r8.ir.code.BasicBlock; @@ -361,8 +360,6 @@ private Map<DexEncodedField, StaticPut> findFinalFieldPutsWhileCollectingUnnecessaryStaticPuts( IRCode code, ProgramMethod context, Set<StaticPut> unnecessaryStaticPuts) { - ValueMayDependOnEnvironmentAnalysis environmentAnalysis = - new ValueMayDependOnEnvironmentAnalysis(appView, code); Map<DexEncodedField, StaticPut> finalFieldPuts = Maps.newIdentityHashMap(); Map<DexField, Set<StaticPut>> isWrittenBefore = Maps.newIdentityHashMap(); Set<DexEncodedField> isReadBefore = Sets.newIdentityHashSet(); @@ -471,12 +468,7 @@ // the value of one of the fields in the enclosing class. if (instruction.isInvoke() && instruction.hasOutValue()) { Value outValue = instruction.outValue(); - if (outValue.numberOfAllUsers() > 0) { - if (instruction.isInvokeNewArray() - && environmentAnalysis.isConstantArrayThroughoutMethod(outValue)) { - // OK, this value is technically a constant. - continue; - } + if (outValue.hasNonDebugUsers()) { return validateFinalFieldPuts(finalFieldPuts, isWrittenBefore); } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java index 172a6b3..c6447c1 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -7,7 +7,6 @@ import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull; import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull; -import static com.google.common.base.Predicates.alwaysTrue; import com.android.tools.r8.dex.Constants; import com.android.tools.r8.errors.CompilationError; @@ -52,8 +51,10 @@ import com.android.tools.r8.ir.code.IRMetadata; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.If.Type; +import com.android.tools.r8.ir.code.InstanceFieldInstruction; import com.android.tools.r8.ir.code.InstanceOf; import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption; import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.InstructionOrPhi; @@ -61,6 +62,7 @@ import com.android.tools.r8.ir.code.Invoke; import com.android.tools.r8.ir.code.InvokeDirect; import com.android.tools.r8.ir.code.InvokeMethod; +import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; import com.android.tools.r8.ir.code.InvokeNewArray; import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.InvokeVirtual; @@ -79,13 +81,14 @@ import com.android.tools.r8.ir.code.Xor; import com.android.tools.r8.ir.conversion.IRConverter; import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer; +import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; +import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.InternalOutputMode; import com.android.tools.r8.utils.LongInterval; import com.android.tools.r8.utils.SetUtils; -import com.android.tools.r8.utils.Timing; import com.google.common.base.Equivalence; import com.google.common.base.Equivalence.Wrapper; import com.google.common.base.Suppliers; @@ -117,6 +120,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.BitSet; import java.util.Collection; import java.util.Deque; import java.util.HashMap; @@ -159,21 +163,6 @@ this.dexItemFactory = appView.dexItemFactory(); } - public static void insertAssumeInstructions( - IRCode code, Collection<Assumer> assumers, Timing timing) { - insertAssumeInstructionsInBlocks(code, assumers, alwaysTrue(), timing); - } - - public static void insertAssumeInstructionsInBlocks( - IRCode code, Collection<Assumer> assumers, Predicate<BasicBlock> blockTester, Timing timing) { - timing.begin("Insert assume instructions"); - for (Assumer assumer : assumers) { - assumer.insertAssumeInstructionsInBlocks(code, code.listIterator(), blockTester, timing); - assert code.isConsistentSSA(); - } - timing.end(); - } - public static void removeAssumeInstructions(AppView<?> appView, IRCode code) { // We need to update the types of all values whose definitions depend on a non-null value. // This is needed to preserve soundness of the types after the Assume instructions have been @@ -1249,7 +1238,7 @@ return false; } - AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code); + AssumeRemover assumeRemover = new AssumeRemover(appView, code); boolean changed = false; boolean mayHaveRemovedTrivialPhi = false; Set<Value> affectedValues = Sets.newIdentityHashSet(); @@ -1282,7 +1271,7 @@ // return false unless it is object. if (argument.getType().lessThanOrEqual(outValue.getType(), appView)) { affectedValues.addAll(outValue.affectedValues()); - assumeDynamicTypeRemover.markUsersForRemoval(outValue); + assumeRemover.markAssumeDynamicTypeUsersForRemoval(outValue); mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0; outValue.replaceUsers(argument); invoke.setOutValue(null); @@ -1292,12 +1281,12 @@ } } } - assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved).finish(); + assumeRemover.removeMarkedInstructions(blocksToBeRemoved).finish(); if (!blocksToBeRemoved.isEmpty()) { code.removeBlocks(blocksToBeRemoved); code.removeAllDeadAndTrivialPhis(affectedValues); assert code.getUnreachableBlocks().isEmpty(); - } else if (mayHaveRemovedTrivialPhi || assumeDynamicTypeRemover.mayHaveIntroducedTrivialPhi()) { + } else if (mayHaveRemovedTrivialPhi || assumeRemover.mayHaveIntroducedTrivialPhi()) { code.removeAllDeadAndTrivialPhis(affectedValues); } if (!affectedValues.isEmpty()) { @@ -1502,7 +1491,9 @@ if (result == InstanceOfResult.UNKNOWN) { Value aliasedValue = inValue.getSpecificAliasedValue( - value -> !value.isPhi() && value.definition.isAssumeDynamicType()); + value -> + value.isDefinedByInstructionSatisfying( + Instruction::isAssumeWithDynamicTypeAssumption)); if (aliasedValue != null) { TypeElement dynamicType = aliasedValue @@ -2232,7 +2223,6 @@ instruction.outValue().replaceUsers(inValue); Value overwrittenLocal = instruction.removeDebugValue(localInfo); if (overwrittenLocal != null) { - inValue.definition.addDebugValue(overwrittenLocal); overwrittenLocal.addDebugLocalEnd(inValue.definition); } if (prevInstruction != null && @@ -2553,10 +2543,13 @@ if (singleFieldValue.getField() == otherSingleFieldValue.getField()) { simplifyIfWithKnownCondition(code, block, theIf, 0); } else { - DexEncodedField field = appView.definitionFor(singleFieldValue.getField()); + DexClass holder = appView.definitionForHolder(singleFieldValue.getField()); + DexEncodedField field = singleFieldValue.getField().lookupOnClass(holder); if (field != null && field.isEnum()) { + DexClass otherHolder = + appView.definitionForHolder(otherSingleFieldValue.getField()); DexEncodedField otherField = - appView.definitionFor(otherSingleFieldValue.getField()); + otherSingleFieldValue.getField().lookupOnClass(otherHolder); if (otherField != null && otherField.isEnum()) { simplifyIfWithKnownCondition(code, block, theIf, 1); } @@ -2822,69 +2815,94 @@ return changed; } - // Find all method invocations that never returns normally, split the block - // after each such invoke instruction and follow it with a block throwing a - // null value (which should result in NPE). Note that this throw is not + // Find all instructions that always throw, split the block after each such instruction and follow + // it with a block throwing a null value (which should result in NPE). Note that this throw is not // expected to be ever reached, but is intended to satisfy verifier. - public void processMethodsNeverReturningNormally(IRCode code) { + public void optimizeAlwaysThrowingInstructions(IRCode code) { if (!appView.appInfo().hasLiveness()) { return; } + AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness(); + Set<Value> affectedValues = Sets.newIdentityHashSet(); + Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet(); ListIterator<BasicBlock> blockIterator = code.listIterator(); + ProgramMethod context = code.context(); while (blockIterator.hasNext()) { BasicBlock block = blockIterator.next(); if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) { continue; } - InstructionListIterator insnIterator = block.listIterator(code); - while (insnIterator.hasNext()) { - Instruction insn = insnIterator.next(); - if (!insn.isInvokeMethod()) { + if (blocksToRemove.contains(block)) { + continue; + } + InstructionListIterator instructionIterator = block.listIterator(code); + while (instructionIterator.hasNext()) { + Instruction instruction = instructionIterator.next(); + if (instruction.throwsOnNullInput()) { + Value inValue = instruction.getNonNullInput(); + if (inValue.isAlwaysNull(appView)) { + // Insert `throw null` after the instruction if it is not guaranteed to throw an NPE. + if (instruction.isInstanceFieldInstruction()) { + InstanceFieldInstruction instanceFieldInstruction = + instruction.asInstanceFieldInstruction(); + if (instanceFieldInstruction.instructionInstanceCanThrow( + appView, context, SideEffectAssumption.RECEIVER_NOT_NULL)) { + instructionIterator.next(); + } + } else if (instruction.isInvokeMethodWithReceiver()) { + InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver(); + SideEffectAssumption assumption = + SideEffectAssumption.RECEIVER_NOT_NULL.join( + SideEffectAssumption.INVOKED_METHOD_DOES_NOT_HAVE_SIDE_EFFECTS); + if (invoke.instructionMayHaveSideEffects(appView, context, assumption)) { + instructionIterator.next(); + } + } + instructionIterator.replaceCurrentInstructionWithThrowNull( + appViewWithLiveness, code, blockIterator, blocksToRemove, affectedValues); + continue; + } + } + + if (!instruction.isInvokeMethod()) { continue; } - InvokeMethod invoke = insn.asInvokeMethod(); + InvokeMethod invoke = instruction.asInvokeMethod(); DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView.withLiveness(), code.context()); - if (singleTarget == null || !singleTarget.getOptimizationInfo().neverReturnsNormally()) { + if (singleTarget == null) { continue; } - // Split the block. - { - BasicBlock newBlock = insnIterator.split(code, blockIterator); - assert !insnIterator.hasNext(); // must be pointing *after* inserted GoTo. - // Move block iterator back so current block is 'newBlock'. - blockIterator.previous(); + MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo(); - newBlock.unlinkSinglePredecessorSiblingsAllowed(); + // If the invoke instruction is a null check, we can remove it. + boolean isNullCheck = false; + if (optimizationInfo.hasNonNullParamOrThrow()) { + BitSet nonNullParamOrThrow = optimizationInfo.getNonNullParamOrThrow(); + for (int i = 0; i < invoke.arguments().size(); i++) { + Value argument = invoke.arguments().get(i); + if (argument.isAlwaysNull(appView) && nonNullParamOrThrow.get(i)) { + isNullCheck = true; + break; + } + } } - - // We want to follow the invoke instruction with 'throw null', which should - // be unreachable but is needed to satisfy the verifier. Note that we have - // to put 'throw null' into a separate block to make sure we don't get two - // throwing instructions in the block having catch handler. This new block - // does not need catch handlers. - Instruction gotoInsn = insnIterator.previous(); - assert gotoInsn.isGoto(); - assert insnIterator.hasNext(); - BasicBlock throwNullBlock = insnIterator.split(code, blockIterator); - InstructionListIterator throwNullInsnIterator = throwNullBlock.listIterator(code); - - // Insert 'null' constant. - ConstNumber nullConstant = code.createConstNull(gotoInsn.getLocalInfo()); - nullConstant.setPosition(invoke.getPosition()); - throwNullInsnIterator.add(nullConstant); - - // Replace Goto with Throw. - Throw notReachableThrow = new Throw(nullConstant.outValue()); - Instruction insnGoto = throwNullInsnIterator.next(); - assert insnGoto.isGoto(); - throwNullInsnIterator.replaceCurrentInstruction(notReachableThrow); + // If the invoke instruction never returns normally, we can insert a throw null instruction + // after the invoke. + if (isNullCheck || optimizationInfo.neverReturnsNormally()) { + instructionIterator.setInsertionPosition(invoke.getPosition()); + instructionIterator.next(); + instructionIterator.replaceCurrentInstructionWithThrowNull( + appViewWithLiveness, code, blockIterator, blocksToRemove, affectedValues); + instructionIterator.unsetInsertionPosition(); + } } } - Set<Value> affectedValues = code.removeUnreachableBlocks(); + code.removeBlocks(blocksToRemove); + assert code.getUnreachableBlocks().isEmpty(); if (!affectedValues.isEmpty()) { new TypeAnalysis(appView).narrowing(affectedValues); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java index ef9c66c..71684b1 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -5,6 +5,7 @@ import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; +import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; @@ -19,6 +20,7 @@ import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Timing; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterators; import java.util.ArrayDeque; import java.util.Collection; import java.util.Deque; @@ -68,7 +70,7 @@ || !instruction.hasOutValue() || instruction.outValue().hasAnyUsers(); // No dead instructions. - assert !instruction.canBeDeadCode(appView, code) + assert !instruction.canBeDeadCode(appView, code).isDeadIfOutValueIsDead() || (instruction.hasOutValue() && !instruction.outValue().isDead(appView, code)); } } @@ -117,14 +119,25 @@ while (iterator.hasPrevious()) { Instruction current = iterator.previous(); // Remove unused invoke results. - if (current.isInvoke() - && current.outValue() != null - && !current.outValue().isUsed()) { + if (current.isInvoke() && current.hasOutValue() && !current.outValue().isUsed()) { current.setOutValue(null); } - if (!current.canBeDeadCode(appView, code)) { + DeadInstructionResult deadInstructionResult = current.canBeDeadCode(appView, code); + if (deadInstructionResult.isNotDead()) { continue; } + if (deadInstructionResult.isMaybeDead()) { + boolean satisfied = true; + for (Value valueRequiredToBeDead : deadInstructionResult.getValuesRequiredToBeDead()) { + if (!valueRequiredToBeDead.isDead(appView, code)) { + satisfied = false; + break; + } + } + if (!satisfied) { + continue; + } + } Value outValue = current.outValue(); if (outValue != null && !outValue.isDead(appView, code)) { continue; @@ -207,4 +220,61 @@ } return builder.build(); } + + public abstract static class DeadInstructionResult { + + private static final DeadInstructionResult DEFINITELY_DEAD_INSTANCE = + new DeadInstructionResult() { + @Override + public boolean isDeadIfOutValueIsDead() { + return true; + } + }; + + private static final DeadInstructionResult DEFINITELY_NOT_DEAD_INSTANCE = + new DeadInstructionResult() { + @Override + public boolean isNotDead() { + return true; + } + }; + + public static DeadInstructionResult deadIfOutValueIsDead() { + return DEFINITELY_DEAD_INSTANCE; + } + + public static DeadInstructionResult notDead() { + return DEFINITELY_NOT_DEAD_INSTANCE; + } + + public static DeadInstructionResult deadIfInValueIsDead(Value inValueRequiredToBeDead) { + return new DeadInstructionResult() { + @Override + public boolean isMaybeDead() { + return true; + } + + @Override + public Iterable<Value> getValuesRequiredToBeDead() { + return () -> Iterators.singletonIterator(inValueRequiredToBeDead); + } + }; + } + + public boolean isDeadIfOutValueIsDead() { + return false; + } + + public boolean isNotDead() { + return false; + } + + public boolean isMaybeDead() { + return false; + } + + public Iterable<Value> getValuesRequiredToBeDead() { + throw new Unreachable(); + } + } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java index c4bf62f..f1ed11b 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -265,7 +265,8 @@ return null; } - if (inliner.isBlacklisted(singleTarget, whyAreYouNotInliningReporter)) { + if (inliner.isBlacklisted( + invoke, resolutionResult, singleTarget, whyAreYouNotInliningReporter)) { return null; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java index 7752d6c..cb8f858 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -53,6 +53,7 @@ public void devirtualizeInvokeInterface(IRCode code) { Set<Value> affectedValues = Sets.newIdentityHashSet(); + AssumeRemover assumeRemover = new AssumeRemover(appView, code); ProgramMethod context = code.context(); Map<InvokeInterface, InvokeVirtual> devirtualizedCall = new IdentityHashMap<>(); DominatorTree dominatorTree = new DominatorTree(code); @@ -76,7 +77,7 @@ // (out <-) invoke-virtual rcv_c, ... C#foo // ... // non_null_rcv <- non-null rcv_c // <- Update the input rcv to the non-null, too. - if (current.isAssumeNonNull()) { + if (current.isAssumeWithNonNullAssumption()) { Assume nonNull = current.asAssume(); Instruction origin = nonNull.origin(); if (origin.isInvokeInterface() @@ -253,8 +254,8 @@ it.next(); } } - affectedValues.addAll(receiver.affectedValues()); + assumeRemover.markAssumeDynamicTypeUsersForRemoval(receiver); if (!receiver.hasLocalInfo()) { receiver.replaceSelectiveUsers( newReceiver, ImmutableSet.of(devirtualizedInvoke), ImmutableMap.of()); @@ -266,6 +267,8 @@ } } } + assumeRemover.removeMarkedInstructions(); + affectedValues.addAll(assumeRemover.getAffectedValues()); if (!affectedValues.isEmpty()) { new TypeAnalysis(appView).narrowing(affectedValues); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java index 6e2d9c7..20e0515 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
@@ -4,36 +4,19 @@ package com.android.tools.r8.ir.optimize; -import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull; -import static com.google.common.base.Predicates.alwaysTrue; - import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.analysis.type.ClassTypeElement; import com.android.tools.r8.ir.analysis.type.TypeElement; -import com.android.tools.r8.ir.code.Assume; import com.android.tools.r8.ir.code.BasicBlock; -import com.android.tools.r8.ir.code.BasicBlockIterator; import com.android.tools.r8.ir.code.IRCode; -import com.android.tools.r8.ir.code.Instruction; -import com.android.tools.r8.ir.code.InstructionListIterator; -import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.JumpInstruction; -import com.android.tools.r8.ir.code.Position; -import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; -import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.Timing; import java.util.ArrayList; import java.util.List; -import java.util.ListIterator; -import java.util.function.Predicate; -public class DynamicTypeOptimization implements Assumer { +public class DynamicTypeOptimization { private final AppView<AppInfoWithLiveness> appView; @@ -41,125 +24,6 @@ this.appView = appView; } - @Override - public void insertAssumeInstructions(IRCode code, Timing timing) { - insertAssumeInstructionsInBlocks(code, code.listIterator(), alwaysTrue(), timing); - } - - @Override - public void insertAssumeInstructionsInBlocks( - IRCode code, - BasicBlockIterator blockIterator, - Predicate<BasicBlock> blockTester, - Timing timing) { - timing.begin("Insert assume dynamic type instructions"); - while (blockIterator.hasNext()) { - BasicBlock block = blockIterator.next(); - if (blockTester.test(block)) { - insertAssumeDynamicTypeInstructionsInBlock(code, blockIterator, block); - } - } - timing.end(); - } - - // TODO(b/127461806): Should also insert AssumeDynamicType instructions after instanceof - // instructions. - private void insertAssumeDynamicTypeInstructionsInBlock( - IRCode code, ListIterator<BasicBlock> blockIterator, BasicBlock block) { - InstructionListIterator instructionIterator = block.listIterator(code); - while (instructionIterator.hasNext()) { - Instruction current = instructionIterator.next(); - if (!current.hasOutValue() || !current.outValue().isUsed()) { - continue; - } - - TypeElement dynamicUpperBoundType; - ClassTypeElement dynamicLowerBoundType; - if (current.isInvokeMethod()) { - InvokeMethod invoke = current.asInvokeMethod(); - DexMethod invokedMethod = invoke.getInvokedMethod(); - - DexType staticReturnTypeRaw = invokedMethod.proto.returnType; - if (!staticReturnTypeRaw.isReferenceType()) { - continue; - } - - if (invokedMethod.holder.isArrayType() - && invokedMethod.match(appView.dexItemFactory().objectMembers.clone)) { - dynamicUpperBoundType = - TypeElement.fromDexType(invokedMethod.holder, definitelyNotNull(), appView); - dynamicLowerBoundType = null; - } else { - DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.context()); - if (singleTarget == null) { - continue; - } - - MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo(); - if (optimizationInfo.returnsArgument()) { - // Don't insert an assume-instruction since we will replace all usages of the out-value - // by the corresponding argument. - continue; - } - - dynamicUpperBoundType = optimizationInfo.getDynamicUpperBoundType(); - dynamicLowerBoundType = optimizationInfo.getDynamicLowerBoundType(); - } - } else if (current.isStaticGet()) { - StaticGet staticGet = current.asStaticGet(); - DexEncodedField encodedField = - appView.appInfo().resolveField(staticGet.getField()).getResolvedField(); - if (encodedField == null) { - continue; - } - - dynamicUpperBoundType = encodedField.getOptimizationInfo().getDynamicUpperBoundType(); - dynamicLowerBoundType = encodedField.getOptimizationInfo().getDynamicLowerBoundType(); - } else { - continue; - } - - Value outValue = current.outValue(); - boolean isTrivial = - (dynamicUpperBoundType == null - || !dynamicUpperBoundType.strictlyLessThan(outValue.getType(), appView)) - && dynamicLowerBoundType == null; - if (isTrivial) { - continue; - } - - if (dynamicUpperBoundType == null) { - dynamicUpperBoundType = outValue.getType(); - } - - // Split block if needed (only debug instructions are allowed after the throwing - // instruction, if any). - BasicBlock insertionBlock = - block.hasCatchHandlers() ? instructionIterator.split(code, blockIterator) : block; - - // Replace usages of out-value by the out-value of the AssumeDynamicType instruction. - Value specializedOutValue = code.createValue(outValue.getType(), outValue.getLocalInfo()); - outValue.replaceUsers(specializedOutValue); - - // Insert AssumeDynamicType instruction. - Assume assumeInstruction = - Assume.createAssumeDynamicTypeInstruction( - dynamicUpperBoundType, - dynamicLowerBoundType, - specializedOutValue, - outValue, - current, - appView); - assumeInstruction.setPosition( - appView.options().debug ? current.getPosition() : Position.none()); - if (insertionBlock == block) { - instructionIterator.add(assumeInstruction); - } else { - insertionBlock.listIterator(code).add(assumeInstruction); - } - } - } - /** * Computes the dynamic return type of the given method. *
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java index 17cea8b..73da99f 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -118,29 +118,40 @@ } boolean isBlacklisted( - ProgramMethod method, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { - DexMethod reference = method.getReference(); - if (method.getDefinition().getOptimizationInfo().forceInline() - && appView.appInfo().neverInline.contains(reference)) { + InvokeMethod invoke, + SingleResolutionResult resolutionResult, + ProgramMethod singleTarget, + WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { + AppInfoWithLiveness appInfo = appView.appInfo(); + DexMethod singleTargetReference = singleTarget.getReference(); + if (singleTarget.getDefinition().getOptimizationInfo().forceInline() + && appInfo.neverInline.contains(singleTargetReference)) { throw new Unreachable(); } - if (appView.appInfo().isPinned(reference)) { + if (appInfo.isPinned(singleTargetReference)) { whyAreYouNotInliningReporter.reportPinned(); return true; } - if (blacklist.contains(appView.graphLense().getOriginalMethodSignature(reference)) - || TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(reference, appView)) { + if (blacklist.contains(appView.graphLense().getOriginalMethodSignature(singleTargetReference)) + || TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod( + singleTargetReference, appView)) { whyAreYouNotInliningReporter.reportBlacklisted(); return true; } - if (appView.appInfo().neverInline.contains(reference)) { + if (appInfo.neverInline.contains(singleTargetReference)) { whyAreYouNotInliningReporter.reportMarkedAsNeverInline(); return true; } + if (appInfo.noSideEffects.containsKey(invoke.getInvokedMethod()) + || appInfo.noSideEffects.containsKey(resolutionResult.getResolvedMethod().getReference()) + || appInfo.noSideEffects.containsKey(singleTargetReference)) { + return true; + } + return false; } @@ -932,7 +943,7 @@ OptimizationFeedback feedback, InliningIRProvider inliningIRProvider, Timing timing) { - AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code); + AssumeRemover assumeRemover = new AssumeRemover(appView, code); Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet(); BasicBlockIterator blockIterator = code.listIterator(); ClassInitializationAnalysis classInitializationAnalysis = @@ -1025,7 +1036,7 @@ // Mark AssumeDynamicType instruction for the out-value for removal, if any. Value outValue = invoke.outValue(); if (outValue != null) { - assumeDynamicTypeRemover.markUsersForRemoval(outValue); + assumeRemover.markAssumeDynamicTypeUsersForRemoval(outValue); } boolean inlineeMayHaveInvokeMethod = inlinee.code.metadata().mayHaveInvokeMethod(); @@ -1073,14 +1084,14 @@ IteratorUtils.previousUntil(blockIterator, previous -> previous == block); blockIterator.next(); } - } else if (current.isAssumeDynamicType()) { - assumeDynamicTypeRemover.removeIfMarked(current.asAssume(), iterator); + } else if (current.isAssume()) { + assumeRemover.removeIfMarked(current.asAssume(), iterator); } } } assert inlineeStack.isEmpty(); - assumeDynamicTypeRemover.removeMarkedInstructions(blocksToRemove); - assumeDynamicTypeRemover.finish(); + assumeRemover.removeMarkedInstructions(blocksToRemove); + assumeRemover.finish(); classInitializationAnalysis.finish(); code.removeBlocks(blocksToRemove); code.removeAllDeadAndTrivialPhis(); @@ -1129,50 +1140,33 @@ BasicBlockIterator blockIterator, BasicBlock block, Timing timing) { - InternalOptions options = appView.options(); - boolean skip = - !(options.enableDynamicTypeOptimization - || options.enableNonNullTracking - || options.enableValuePropagation); - if (skip) { - return; - } - BasicBlock state = IteratorUtils.peekNext(blockIterator); Set<BasicBlock> inlineeBlocks = SetUtils.newIdentityHashSet(inlinee.blocks); // Run member value propagation on the inlinee blocks. - if (options.enableValuePropagation) { + if (appView.options().enableValuePropagation) { rewindBlockIteratorToFirstInlineeBlock(blockIterator, block); applyMemberValuePropagationToInlinee(code, blockIterator, block, inlineeBlocks); } // Add non-null IRs only to the inlinee blocks. - if (options.enableNonNullTracking) { - Assumer nonNullTracker = new AssumeInserter(appView); - applyAssumerToInlinee(nonNullTracker, code, blockIterator, block, inlineeBlocks, timing); - } + insertAssumeInstructions(code, blockIterator, block, inlineeBlocks, timing); - // Add dynamic type assumptions only to the inlinee blocks. - if (options.enableDynamicTypeOptimization) { - applyAssumerToInlinee( - new DynamicTypeOptimization(appView), code, blockIterator, block, inlineeBlocks, timing); - } // Restore the old state of the iterator. rewindBlockIteratorToFirstInlineeBlock(blockIterator, state); // TODO(b/72693244): need a test where refined env in inlinee affects the caller. } - private void applyAssumerToInlinee( - Assumer assumer, + private void insertAssumeInstructions( IRCode code, BasicBlockIterator blockIterator, BasicBlock block, Set<BasicBlock> inlineeBlocks, Timing timing) { rewindBlockIteratorToFirstInlineeBlock(blockIterator, block); - assumer.insertAssumeInstructionsInBlocks(code, blockIterator, inlineeBlocks::contains, timing); + new AssumeInserter(appView) + .insertAssumeInstructionsInBlocks(code, blockIterator, inlineeBlocks::contains, timing); assert !blockIterator.hasNext(); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java index 51c1c01..96fd58c 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -230,21 +230,23 @@ Set<Value> affectedValues, ListIterator<BasicBlock> blocks, InstructionListIterator iterator, - InvokeMethod current) { - DexMethod invokedMethod = current.getInvokedMethod(); + InvokeMethod invoke) { + DexMethod invokedMethod = invoke.getInvokedMethod(); + if (invokedMethod.proto.returnType.isVoidType()) { + return; + } + + if (!invoke.hasOutValue() || !invoke.outValue().hasNonDebugUsers()) { + return; + } + DexType invokedHolder = invokedMethod.holder; if (!invokedHolder.isClassType()) { return; } - DexEncodedMethod target = current.lookupSingleTarget(appView, context); - if (target != null && target.isInstanceInitializer()) { - // Member value propagation does not apply to constructors. Removing a call to a constructor - // that is marked as having no side effects could lead to verification errors, due to - // uninitialized instances being used. - return; - } - ProguardMemberRuleLookup lookup = lookupMemberRule(target); + DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context); + ProguardMemberRuleLookup lookup = lookupMemberRule(singleTarget); if (lookup == null) { // -assumenosideeffects rules are applied to upward visible and overriding methods, but only // references that have actual definitions are marked by the root set builder. So, here, we @@ -253,60 +255,51 @@ appView.appInfo().unsafeResolveMethodDueToDexFormat(invokedMethod).getSingleTarget(); lookup = lookupMemberRule(resolutionTarget); } - boolean invokeReplaced = false; - if (lookup != null) { - boolean hasUsedOutValue = current.hasOutValue() && current.outValue().isUsed(); - if (!hasUsedOutValue) { - if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS) { - // Remove invoke if marked as having no side effects and the return value is not used. - iterator.removeOrReplaceByDebugLocalRead(); - } - return; - } + if (lookup != null) { // Check to see if a constant value can be assumed. // But, if the current matched rule is -assumenosideeffects without the return value, it won't // be transformed into a replacement instruction. Check if there is -assumevalues rule bound // to the target. - if (target != null + if (singleTarget != null && lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS && !lookup.rule.hasReturnValue()) { - ProguardMemberRule rule = appView.appInfo().assumedValues.get(target.toReference()); + ProguardMemberRule rule = appView.appInfo().assumedValues.get(singleTarget.toReference()); if (rule != null) { lookup = new ProguardMemberRuleLookup(RuleType.ASSUME_VALUES, rule); } } - invokeReplaced = - tryConstantReplacementFromProguard( - code, affectedValues, blocks, iterator, current, lookup); + if (tryConstantReplacementFromProguard( + code, affectedValues, blocks, iterator, invoke, lookup)) { + return; + } } - if (invokeReplaced || !current.hasOutValue()) { - return; - } + // No Proguard rule could replace the instruction check for knowledge about the return value. - if (target == null || !mayPropagateValueFor(target)) { + if (singleTarget == null || !mayPropagateValueFor(singleTarget)) { return; } - AbstractValue abstractReturnValue = target.getOptimizationInfo().getAbstractReturnValue(); + AbstractValue abstractReturnValue = singleTarget.getOptimizationInfo().getAbstractReturnValue(); + if (abstractReturnValue.isSingleValue()) { SingleValue singleReturnValue = abstractReturnValue.asSingleValue(); if (singleReturnValue.isMaterializableInContext(appView, context)) { - BasicBlock block = current.getBlock(); - Position position = current.getPosition(); + BasicBlock block = invoke.getBlock(); + Position position = invoke.getPosition(); Instruction replacement = - singleReturnValue.createMaterializingInstruction(appView, code, current); - affectedValues.addAll(current.outValue().affectedValues()); - current.moveDebugValues(replacement); - current.outValue().replaceUsers(replacement.outValue()); - current.setOutValue(null); + singleReturnValue.createMaterializingInstruction(appView, code, invoke); + affectedValues.addAll(invoke.outValue().affectedValues()); + invoke.moveDebugValues(replacement); + invoke.outValue().replaceUsers(replacement.outValue()); + invoke.setOutValue(null); - if (current.isInvokeMethodWithReceiver()) { - replaceInstructionByNullCheckIfPossible(current, iterator, context); - } else if (current.isInvokeStatic()) { + if (invoke.isInvokeMethodWithReceiver()) { + replaceInstructionByNullCheckIfPossible(invoke, iterator, context); + } else if (invoke.isInvokeStatic()) { replaceInstructionByInitClassIfPossible( - current, target.holder(), code, iterator, context); + invoke, singleTarget.holder(), code, iterator, context); } // Insert the definition of the replacement. @@ -316,7 +309,7 @@ } else { iterator.add(replacement); } - target.getMutableOptimizationInfo().markAsPropagated(); + singleTarget.getMutableOptimizationInfo().markAsPropagated(); } } } @@ -358,9 +351,7 @@ } AbstractValue abstractValue; - if (field.type.isAlwaysNull(appView)) { - abstractValue = appView.abstractValueFactory().createSingleNumberValue(0); - } else if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) { + if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) { abstractValue = target.getOptimizationInfo().getAbstractValue(); if (abstractValue.isUnknown() && !target.isStatic()) { AbstractValue abstractReceiverValue =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java index ef81097..5178020 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -155,7 +155,7 @@ return appView.appInfo().withLiveness().resolveField(field).getResolvedField(); } if (field.holder == method.getHolderType()) { - return appView.definitionFor(field); + return method.getHolder().lookupField(field); } return null; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java index d0ad73a..263cadd 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -35,8 +35,9 @@ import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Collections; +import java.util.IdentityHashMap; import java.util.List; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Map; import java.util.concurrent.atomic.AtomicReference; /** @@ -72,8 +73,6 @@ private static final String SERVICE_LOADER_METHOD_PREFIX_NAME = "$load"; private AtomicReference<DexProgramClass> synthesizedClass = new AtomicReference<>(); - private ConcurrentHashMap<DexType, DexEncodedMethod> synthesizedServiceLoaders = - new ConcurrentHashMap<>(); private final AppView<? extends AppInfoWithLiveness> appView; @@ -88,6 +87,9 @@ public void rewrite(IRCode code, MethodProcessingId methodProcessingId) { DexItemFactory factory = appView.dexItemFactory(); InstructionListIterator instructionIterator = code.instructionListIterator(); + // Create a map from service type to loader methods local to this context since two + // service loader calls to the same type in different methods and in the same wave can race. + Map<DexType, DexEncodedMethod> synthesizedServiceLoaders = new IdentityHashMap<>(); while (instructionIterator.hasNext()) { Instruction instruction = instructionIterator.next();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java index 85f0274..6a8b6a5 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -7,7 +7,6 @@ import static com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.Strategy.ALLOW_ARGUMENT_REMOVAL; import static com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.Strategy.DISALLOW_ARGUMENT_REMOVAL; -import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; @@ -15,22 +14,17 @@ 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.FieldAccessInfo; +import com.android.tools.r8.graph.FieldAccessInfoCollection; import com.android.tools.r8.graph.GraphLense.NestedGraphLense; -import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.RewrittenPrototypeDescription; import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection; import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo; import com.android.tools.r8.graph.TopDownClassHierarchyTraversal; -import com.android.tools.r8.ir.analysis.type.TypeAnalysis; -import com.android.tools.r8.ir.code.BasicBlock; -import com.android.tools.r8.ir.code.FieldInstruction; -import com.android.tools.r8.ir.code.IRCode; -import com.android.tools.r8.ir.code.Instruction; -import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption; -import com.android.tools.r8.ir.code.InstructionListIterator; -import com.android.tools.r8.ir.code.InvokeMethod; -import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool; +import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; +import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.MethodSignatureEquivalence; import com.android.tools.r8.utils.Timing; @@ -40,11 +34,9 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; -import java.util.BitSet; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; -import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; @@ -104,6 +96,33 @@ this.appView = appView; } + public UninstantiatedTypeOptimization strenghtenOptimizationInfo() { + OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance(); + FieldAccessInfoCollection<?> fieldAccessInfoCollection = + appView.appInfo().getFieldAccessInfoCollection(); + AbstractValue nullValue = appView.abstractValueFactory().createSingleNumberValue(0); + for (DexProgramClass clazz : appView.appInfo().classes()) { + clazz.forEachField( + field -> { + if (field.type().isAlwaysNull(appView)) { + FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field); + if (fieldAccessInfo != null) { + // Clear all writes since each write must write `null` to the field. + fieldAccessInfo.asMutable().clearWrites(); + } + feedback.recordFieldHasAbstractValue(field, appView, nullValue); + } + }); + clazz.forEachMethod( + method -> { + if (method.returnType().isAlwaysNull(appView)) { + feedback.methodReturnsAbstractValue(method, appView, nullValue); + } + }); + } + return this; + } + public UninstantiatedTypeOptimizationGraphLense run( MethodPoolCollection methodPoolCollection, ExecutorService executorService, Timing timing) { try { @@ -324,124 +343,4 @@ return dexItemFactory.createMethod(method.holder, newProto, method.name); } - - public void rewrite(IRCode code) { - AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code); - Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet(); - ListIterator<BasicBlock> blockIterator = code.listIterator(); - Set<Value> valuesToNarrow = Sets.newIdentityHashSet(); - while (blockIterator.hasNext()) { - BasicBlock block = blockIterator.next(); - if (blocksToBeRemoved.contains(block)) { - continue; - } - InstructionListIterator instructionIterator = block.listIterator(code); - while (instructionIterator.hasNext()) { - Instruction instruction = instructionIterator.next(); - if (instruction.throwsOnNullInput()) { - Value couldBeNullValue = instruction.getNonNullInput(); - if (isThrowNullCandidate(couldBeNullValue, instruction, appView, code.context())) { - instructionIterator.replaceCurrentInstructionWithThrowNull( - appView, code, blockIterator, blocksToBeRemoved, valuesToNarrow); - continue; - } - } - if (instruction.isInvokeMethod()) { - rewriteInvoke( - instruction.asInvokeMethod(), - blockIterator, - instructionIterator, - code, - assumeDynamicTypeRemover, - blocksToBeRemoved, - valuesToNarrow); - } - } - } - assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved).finish(); - code.removeBlocks(blocksToBeRemoved); - code.removeAllDeadAndTrivialPhis(valuesToNarrow); - code.removeUnreachableBlocks(); - if (!valuesToNarrow.isEmpty()) { - new TypeAnalysis(appView).narrowing(valuesToNarrow); - } - assert code.isConsistentSSA(); - } - - private static boolean isThrowNullCandidate( - Value couldBeNullValue, - Instruction current, - AppView<? extends AppInfoWithClassHierarchy> appView, - ProgramMethod context) { - if (!couldBeNullValue.isAlwaysNull(appView)) { - return false; - } - if (current.isFieldInstruction()) { - // Other resolution-related errors come first. - FieldInstruction fieldInstruction = current.asFieldInstruction(); - // We can't replace the current instruction with `throw null` if it may throw another - // exception than NullPointerException. - if (fieldInstruction.instructionInstanceCanThrow( - appView, context, SideEffectAssumption.RECEIVER_NOT_NULL)) { - return false; - } - } - return true; - } - - // invoke instructions with a null receiver has already been rewritten to `throw null`. - // At this point, we attempt to explore non-null-param-or-throw optimization info and replace - // the invocation with `throw null` if an argument is known to be null and the method is going to - // throw for that null argument. - private void rewriteInvoke( - InvokeMethod invoke, - ListIterator<BasicBlock> blockIterator, - InstructionListIterator instructionIterator, - IRCode code, - AssumeDynamicTypeRemover assumeDynamicTypeRemover, - Set<BasicBlock> blocksToBeRemoved, - Set<Value> affectedValues) { - DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.context()); - if (target == null) { - return; - } - - BitSet facts = target.getOptimizationInfo().getNonNullParamOrThrow(); - if (facts != null) { - for (int i = 0; i < invoke.arguments().size(); i++) { - Value argument = invoke.arguments().get(i); - if (argument.isAlwaysNull(appView) && facts.get(i)) { - instructionIterator.replaceCurrentInstructionWithThrowNull( - appView, code, blockIterator, blocksToBeRemoved, affectedValues); - return; - } - } - } - - DexType returnType = target.method.proto.returnType; - if (returnType.isAlwaysNull(appView)) { - replaceOutValueByNull( - invoke, instructionIterator, code, assumeDynamicTypeRemover, affectedValues); - } - } - - private void replaceOutValueByNull( - Instruction instruction, - InstructionListIterator instructionIterator, - IRCode code, - AssumeDynamicTypeRemover assumeDynamicTypeRemover, - Set<Value> affectedValues) { - assert instructionIterator.peekPrevious() == instruction; - if (instruction.hasOutValue()) { - Value outValue = instruction.outValue(); - if (outValue.numberOfAllUsers() > 0) { - assumeDynamicTypeRemover.markUsersForRemoval(outValue); - instructionIterator.previous(); - affectedValues.addAll(outValue.affectedValues()); - outValue.replaceUsers( - instructionIterator.insertConstNullInstruction(code, appView.options())); - instructionIterator.next(); - } - } - } }
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 379fc28..c5cd89d 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
@@ -4,6 +4,7 @@ package com.android.tools.r8.ir.optimize.enums; +import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull; import com.android.tools.r8.graph.AppView; @@ -38,6 +39,7 @@ import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.Invoke; import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.MemberType; @@ -325,6 +327,8 @@ return; } ImmutableSet<DexType> enumsToUnbox = ImmutableSet.copyOf(this.enumsUnboxingCandidates.keySet()); + // Update keep info on any of the enum methods of the removed classes. + updatePinnedItems(enumsToUnbox); enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumsToUnbox); NestedGraphLense enumUnboxingLens = new TreeFixer(enumsToUnbox).fixupTypeReferences(); appView.setUnboxedEnums(enumUnboxerRewriter.getEnumsToUnbox()); @@ -368,6 +372,21 @@ postBuilder.rewrittenWithLens(appView, previousLens); } + private void updatePinnedItems(Set<DexType> enumsToUnbox) { + appView + .appInfo() + .getKeepInfo() + .mutate( + keepInfo -> { + for (DexType type : enumsToUnbox) { + DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type)); + assert !keepInfo.getClassInfo(clazz).isPinned(); + clazz.forEachProgramMethod(keepInfo::unsafeUnpinMethod); + clazz.forEachField(field -> keepInfo.unsafeUnpinField(clazz, field)); + } + }); + } + public void finishAnalysis() { for (DexType toUnbox : enumsUnboxingCandidates.keySet()) { DexProgramClass enumClass = appView.definitionForProgramType(toUnbox); @@ -418,6 +437,7 @@ DexMethod singleTarget = encodedSingleTarget.method; DexClass dexClass = appView.definitionFor(singleTarget.holder); if (dexClass == null) { + assert false; return Reason.INVALID_INVOKE; } if (dexClass.isProgramClass()) { @@ -429,14 +449,22 @@ return Reason.INVALID_INIT; } } + // Check that the enum-value only flows into parameters whose type exactly matches the + // enum's type. int offset = BooleanUtils.intValue(!encodedSingleTarget.isStatic()); for (int i = 0; i < singleTarget.proto.parameters.size(); i++) { - if (invokeMethod.inValues().get(offset + i) == enumValue) { + if (invokeMethod.getArgument(offset + i) == enumValue) { if (singleTarget.proto.parameters.values[i].toBaseType(factory) != enumClass.type) { return Reason.GENERIC_INVOKE; } } } + if (invokeMethod.isInvokeMethodWithReceiver()) { + Value receiver = invokeMethod.asInvokeMethodWithReceiver().getReceiver(); + if (receiver == enumValue && dexClass.isInterface()) { + return Reason.DEFAULT_METHOD_INVOKE; + } + } return Reason.ELIGIBLE; } if (dexClass.isClasspathClass()) { @@ -457,6 +485,8 @@ return Reason.ELIGIBLE; } else if (singleTarget == factory.enumMethods.ordinal) { return Reason.ELIGIBLE; + } else if (singleTarget == factory.enumMethods.hashCode) { + return Reason.ELIGIBLE; } else if (singleTarget == factory.enumMethods.constructor) { // Enum constructor call is allowed only if first call of an enum initializer. if (code.method().isInstanceInitializer() @@ -645,10 +675,9 @@ INTERFACE, INSTANCE_FIELD, GENERIC_INVOKE, + DEFAULT_METHOD_INVOKE, UNEXPECTED_STATIC_FIELD, UNRESOLVABLE_FIELD, - VIRTUAL_METHOD, - UNEXPECTED_DIRECT_METHOD, CONST_CLASS, INVALID_PHI, NO_INIT, @@ -697,7 +726,6 @@ if (m.isInitializer()) { clearEnumToUnboxMethod(m); } else { - assert m.isStatic(); unboxedEnumsMethods.add( fixupEncodedMethodToUtility(m, factory.enumUnboxingUtilityType)); methodsToRemove.add(m); @@ -717,7 +745,7 @@ appView.definitionForProgramType(factory.enumUnboxingUtilityType); assert utilityClass != null : "Should have been synthesized upfront"; utilityClass.addDirectMethods(unboxedEnumsMethods); - return lensBuilder.build(factory, appView.graphLense()); + return lensBuilder.build(factory, appView.graphLense(), enumsToUnbox); } private void clearEnumToUnboxMethod(DexEncodedMethod enumMethod) { @@ -726,11 +754,7 @@ // enumUnboxerRewriter will generate invalid code. // To work around this problem we clear such methods, i.e., we replace the code object by // an empty throwing code object, so reprocessing won't take time and will be valid. - enumMethod.setCode( - appView.options().isGeneratingClassFiles() - ? enumMethod.buildEmptyThrowingCfCode() - : enumMethod.buildEmptyThrowingDexCode(), - appView); + enumMethod.setCode(enumMethod.buildEmptyThrowingCode(appView.options()), appView); } private DexEncodedMethod fixupEncodedMethodToUtility( @@ -738,17 +762,29 @@ DexMethod method = encodedMethod.method; DexString newMethodName = factory.createString( - enumUnboxerRewriter.compatibleName(method.holder) + "$$" + method.name.toString()); - DexMethod newMethod = fixupMethod(method, newHolder, newMethodName); - lensBuilder.move(method, newMethod, encodedMethod.isStatic()); + enumUnboxerRewriter.compatibleName(method.holder) + + "$" + + (encodedMethod.isDirectMethod() ? "d" : "v") + + "$" + + method.name.toString()); + DexProto proto = + encodedMethod.isStatic() + ? method.proto + : factory.prependTypeToProto(method.holder, method.proto); + DexMethod newMethod = factory.createMethod(newHolder, fixupProto(proto), newMethodName); + lensBuilder.move(method, encodedMethod.isStatic(), newMethod, true); encodedMethod.accessFlags.promoteToPublic(); + encodedMethod.accessFlags.promoteToStatic(); + encodedMethod.clearAnnotations(); + encodedMethod.clearParameterAnnotations(); return encodedMethod.toTypeSubstitutedMethod(newMethod); } private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod encodedMethod) { DexMethod newMethod = fixupMethod(encodedMethod.method); if (newMethod != encodedMethod.method) { - lensBuilder.move(encodedMethod.method, newMethod, encodedMethod.isStatic()); + boolean isStatic = encodedMethod.isStatic(); + lensBuilder.move(encodedMethod.method, isStatic, newMethod, isStatic); return encodedMethod.toTypeSubstitutedMethod(newMethod); } return encodedMethod; @@ -819,6 +855,7 @@ private static class EnumUnboxingLens extends NestedGraphLense { private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges; + private final Set<DexType> unboxedEnums; EnumUnboxingLens( Map<DexType, DexType> typeMap, @@ -828,7 +865,8 @@ BiMap<DexMethod, DexMethod> originalMethodSignatures, GraphLense previousLense, DexItemFactory dexItemFactory, - Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges) { + Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges, + Set<DexType> unboxedEnums) { super( typeMap, methodMap, @@ -838,6 +876,7 @@ previousLense, dexItemFactory); this.prototypeChanges = prototypeChanges; + this.unboxedEnums = unboxedEnums; } @Override @@ -849,6 +888,17 @@ return prototypeChanges.getOrDefault(method, RewrittenPrototypeDescription.none()); } + @Override + protected Invoke.Type mapInvocationType( + DexMethod newMethod, DexMethod originalMethod, Invoke.Type type) { + if (unboxedEnums.contains(originalMethod.holder)) { + // Methods moved from unboxed enums to the utility class are either static or statified. + assert newMethod != originalMethod; + return Invoke.Type.STATIC; + } + return type; + } + public static Builder builder() { return new Builder(); } @@ -858,15 +908,23 @@ private Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges = new IdentityHashMap<>(); - public void move(DexMethod from, DexMethod to, boolean isStatic) { + public void move(DexMethod from, boolean fromStatic, DexMethod to, boolean toStatic) { super.move(from, to); - int offset = BooleanUtils.intValue(!isStatic); + int offsetDiff = 0; + int toOffset = BooleanUtils.intValue(!toStatic); ArgumentInfoCollection.Builder builder = ArgumentInfoCollection.builder(); + if (fromStatic != toStatic) { + assert toStatic; + offsetDiff = 1; + builder.addArgumentInfo( + 0, new RewrittenTypeInfo(from.holder, to.proto.parameters.values[0])); + } for (int i = 0; i < from.proto.parameters.size(); i++) { DexType fromType = from.proto.parameters.values[i]; - DexType toType = to.proto.parameters.values[i]; + DexType toType = to.proto.parameters.values[i + offsetDiff]; if (fromType != toType) { - builder.addArgumentInfo(i + offset, new RewrittenTypeInfo(fromType, toType)); + builder.addArgumentInfo( + i + offsetDiff + toOffset, new RewrittenTypeInfo(fromType, toType)); } } RewrittenTypeInfo returnInfo = @@ -877,8 +935,8 @@ to, RewrittenPrototypeDescription.createForRewrittenTypes(returnInfo, builder.build())); } - @Override - public EnumUnboxingLens build(DexItemFactory dexItemFactory, GraphLense previousLense) { + public EnumUnboxingLens build( + DexItemFactory dexItemFactory, GraphLense previousLense, Set<DexType> unboxedEnums) { if (typeMap.isEmpty() && methodMap.isEmpty() && fieldMap.isEmpty()) { return null; } @@ -890,7 +948,8 @@ originalMethodSignatures, previousLense, dexItemFactory, - ImmutableMap.copyOf(prototypeChanges)); + ImmutableMap.copyOf(prototypeChanges), + ImmutableSet.copyOf(unboxedEnums)); } } }
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 b5530c2..08ba8b4 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
@@ -7,17 +7,15 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedField; 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.DexMember; -import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexProto; -import com.android.tools.r8.graph.DexReference; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap; import com.android.tools.r8.ir.optimize.enums.EnumUnboxer.Reason; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.KeepInfoCollection; import com.android.tools.r8.utils.collections.ProgramMethodSet; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -57,12 +55,6 @@ enumUnboxer.reportFailure(clazz.type, Reason.SUBTYPES); return false; } - // TODO(b/147860220): interfaces without default methods should be acceptable if the build setup - // is correct (all abstract methods are implemented). - if (!clazz.interfaces.isEmpty()) { - enumUnboxer.reportFailure(clazz.type, Reason.INTERFACE); - return false; - } if (!clazz.instanceFields().isEmpty()) { enumUnboxer.reportFailure(clazz.type, Reason.INSTANCE_FIELD); return false; @@ -71,10 +63,6 @@ enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_STATIC_FIELD); return false; } - if (clazz.getMethodCollection().hasVirtualMethods()) { - enumUnboxer.reportFailure(clazz.type, Reason.VIRTUAL_METHOD); - return false; - } EnumValueInfoMap enumValueInfoMap = appView.appInfo().withLiveness().getEnumValueInfoMap(clazz.type); if (enumValueInfoMap == null) { @@ -85,8 +73,8 @@ // TODO(b/155036467): Fail lazily when an unsupported method is not only present but also used. // Only Enums with default initializers and static methods can be unboxed at the moment. for (DexEncodedMethod directMethod : clazz.directMethods()) { - if (!(directMethod.isStatic() || isStandardEnumInitializer(directMethod))) { - enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_DIRECT_METHOD); + if (directMethod.isInstanceInitializer() && !isStandardEnumInitializer(directMethod)) { + enumUnboxer.reportFailure(clazz.type, Reason.INVALID_INIT); return false; } } @@ -146,22 +134,17 @@ // A holder type, for field or method, should block enum unboxing only if the enum type is // also kept. This is to allow the keep rule -keepclassmembers to be used on enums while // enum unboxing can still be performed. - for (DexReference item : appView.appInfo().getPinnedItems()) { - if (item.isDexType()) { - removePinnedCandidate(item.asDexType()); - } else if (item.isDexField()) { - DexField field = item.asDexField(); - removePinnedIfNotHolder(field, field.type); - } else { - assert item.isDexMethod(); - DexMethod method = item.asDexMethod(); - DexProto proto = method.proto; - removePinnedIfNotHolder(method, proto.returnType); - for (DexType parameterType : proto.parameters.values) { - removePinnedIfNotHolder(method, parameterType); - } - } - } + KeepInfoCollection keepInfo = appView.appInfo().getKeepInfo(); + keepInfo.forEachPinnedType(this::removePinnedCandidate); + keepInfo.forEachPinnedField(field -> removePinnedIfNotHolder(field, field.type)); + keepInfo.forEachPinnedMethod( + method -> { + DexProto proto = method.proto; + removePinnedIfNotHolder(method, proto.returnType); + for (DexType parameterType : proto.parameters.values) { + removePinnedIfNotHolder(method, parameterType); + } + }); } private void removePinnedIfNotHolder(DexMember<?, ?> member, DexType type) {
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 541cd92..1832a2a 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
@@ -129,7 +129,8 @@ DexMethod invokedMethod = invokeMethod.getInvokedMethod(); DexType enumType = getEnumTypeOrNull(invokeMethod.getReceiver(), convertedEnums); if (enumType != null) { - if (invokedMethod == factory.enumMethods.ordinal) { + if (invokedMethod == factory.enumMethods.ordinal + || invokedMethod == factory.enumMethods.hashCode) { replaceEnumInvoke( iterator, invokeMethod, ordinalUtilityMethod, m -> synthesizeOrdinalMethod()); continue;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java index 1d6668c..2cd8633 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -4,6 +4,8 @@ package com.android.tools.r8.ir.optimize.enums; +import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull; + import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedMethod; @@ -15,6 +17,7 @@ import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo; import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; +import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.ArrayGet; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo; @@ -61,6 +64,7 @@ return; } + Set<Value> affectedValues = Sets.newIdentityHashSet(); InstructionListIterator iterator = code.instructionListIterator(); while (iterator.hasNext()) { Instruction current = iterator.next(); @@ -103,8 +107,12 @@ if (isOrdinalInvoke) { iterator.replaceCurrentInstruction(new ConstNumber(outValue, valueInfo.ordinal)); } else if (isNameInvoke) { + Value newValue = + code.createValue(TypeElement.stringClassType(appView, definitelyNotNull())); iterator.replaceCurrentInstruction( - new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW)); + new ConstString( + newValue, enumField.name, ThrowingInfo.defaultForConstString(appView.options()))); + newValue.addAffectedValuesTo(affectedValues); } else { assert isToStringInvoke; DexClass enumClazz = appView.appInfo().definitionFor(enumField.type); @@ -119,12 +127,16 @@ if (singleTarget != null && singleTarget.method != factory.enumMethods.toString) { continue; } + Value newValue = + code.createValue(TypeElement.stringClassType(appView, definitelyNotNull())); iterator.replaceCurrentInstruction( - new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW)); + new ConstString( + newValue, enumField.name, ThrowingInfo.defaultForConstString(appView.options()))); + newValue.addAffectedValuesTo(affectedValues); } } else if (current.isArrayLength()) { // Rewrites MyEnum.values().length to a constant int. - Instruction arrayDefinition = current.asArrayLength().array().definition; + Instruction arrayDefinition = current.asArrayLength().array().getAliasedValue().definition; if (arrayDefinition != null && arrayDefinition.isInvokeStatic()) { DexMethod invokedMethod = arrayDefinition.asInvokeStatic().getInvokedMethod(); DexProgramClass enumClass = appView.definitionForProgramType(invokedMethod.holder); @@ -140,6 +152,9 @@ } } } + if (!affectedValues.isEmpty()) { + new TypeAnalysis(appView).narrowing(affectedValues); + } assert code.isConsistentSSA(); }
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 59e24a6..0e3e339 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
@@ -18,7 +18,7 @@ import java.util.BitSet; import java.util.Set; -public class DefaultMethodOptimizationInfo implements MethodOptimizationInfo { +public class DefaultMethodOptimizationInfo extends MethodOptimizationInfo { public static final MethodOptimizationInfo DEFAULT_INSTANCE = new DefaultMethodOptimizationInfo(); @@ -124,11 +124,6 @@ } @Override - public boolean neverReturnsNull() { - return UNKNOWN_NEVER_RETURNS_NULL; - } - - @Override public boolean neverReturnsNormally() { return UNKNOWN_NEVER_RETURNS_NORMALLY; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java index 09ad5e5..0365122 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
@@ -26,6 +26,11 @@ public abstract TypeElement getDynamicUpperBoundType(); + public final TypeElement getDynamicUpperBoundTypeOrElse(TypeElement orElse) { + TypeElement dynamicUpperBoundType = getDynamicUpperBoundType(); + return dynamicUpperBoundType != null ? dynamicUpperBoundType : orElse; + } + public abstract boolean isDead(); public abstract boolean valueHasBeenPropagated();
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 16ce797..f919a1e 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
@@ -15,7 +15,7 @@ import java.util.BitSet; import java.util.Set; -public interface MethodOptimizationInfo { +public abstract class MethodOptimizationInfo { enum InlinePreference { NeverInline, @@ -23,63 +23,70 @@ Default } - boolean isDefaultMethodOptimizationInfo(); + public abstract boolean isDefaultMethodOptimizationInfo(); - boolean isUpdatableMethodOptimizationInfo(); + public abstract boolean isUpdatableMethodOptimizationInfo(); - UpdatableMethodOptimizationInfo asUpdatableMethodOptimizationInfo(); + public abstract UpdatableMethodOptimizationInfo asUpdatableMethodOptimizationInfo(); - boolean cannotBeKept(); + public abstract boolean cannotBeKept(); - boolean classInitializerMayBePostponed(); + public abstract boolean classInitializerMayBePostponed(); - TypeElement getDynamicUpperBoundType(); + public abstract TypeElement getDynamicUpperBoundType(); - ClassTypeElement getDynamicLowerBoundType(); + public final TypeElement getDynamicUpperBoundTypeOrElse(TypeElement orElse) { + TypeElement dynamicUpperBoundType = getDynamicUpperBoundType(); + return dynamicUpperBoundType != null ? dynamicUpperBoundType : orElse; + } - ParameterUsage getParameterUsages(int parameter); + public abstract ClassTypeElement getDynamicLowerBoundType(); - BitSet getNonNullParamOrThrow(); + public abstract ParameterUsage getParameterUsages(int parameter); - BitSet getNonNullParamOnNormalExits(); + public final boolean hasNonNullParamOrThrow() { + return getNonNullParamOrThrow() != null; + } - boolean hasBeenInlinedIntoSingleCallSite(); + public abstract BitSet getNonNullParamOrThrow(); - boolean isReachabilitySensitive(); + public abstract BitSet getNonNullParamOnNormalExits(); - boolean returnsArgument(); + public abstract boolean hasBeenInlinedIntoSingleCallSite(); - int getReturnedArgument(); + public abstract boolean isReachabilitySensitive(); - boolean neverReturnsNull(); + public abstract boolean returnsArgument(); - boolean neverReturnsNormally(); + public abstract int getReturnedArgument(); - BridgeInfo getBridgeInfo(); + public abstract boolean neverReturnsNormally(); - ClassInlinerEligibilityInfo getClassInlinerEligibility(); + public abstract BridgeInfo getBridgeInfo(); - Set<DexType> getInitializedClassesOnNormalExit(); + public abstract ClassInlinerEligibilityInfo getClassInlinerEligibility(); - InstanceInitializerInfo getInstanceInitializerInfo(); + public abstract Set<DexType> getInitializedClassesOnNormalExit(); - boolean isInitializerEnablingJavaVmAssertions(); + public abstract InstanceInitializerInfo getInstanceInitializerInfo(); - AbstractValue getAbstractReturnValue(); + public abstract boolean isInitializerEnablingJavaVmAssertions(); - boolean forceInline(); + public abstract AbstractValue getAbstractReturnValue(); - boolean neverInline(); + public abstract boolean forceInline(); - boolean checksNullReceiverBeforeAnySideEffect(); + public abstract boolean neverInline(); - boolean triggersClassInitBeforeAnySideEffect(); + public abstract boolean checksNullReceiverBeforeAnySideEffect(); - boolean mayHaveSideEffects(); + public abstract boolean triggersClassInitBeforeAnySideEffect(); - boolean returnValueOnlyDependsOnArguments(); + public abstract boolean mayHaveSideEffects(); - boolean returnValueHasBeenPropagated(); + public abstract boolean returnValueOnlyDependsOnArguments(); - UpdatableMethodOptimizationInfo mutableCopy(); + public abstract boolean returnValueHasBeenPropagated(); + + public abstract UpdatableMethodOptimizationInfo mutableCopy(); }
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 cac5801..9e270af 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
@@ -360,14 +360,12 @@ return; } Value returnValue = firstExit.returnValue(); - boolean isNeverNull = returnValue.getType().isReferenceType() && returnValue.isNeverNull(); for (int i = 1; i < normalExits.size(); i++) { Return exit = normalExits.get(i).exit().asReturn(); Value value = exit.returnValue(); if (value != returnValue) { returnValue = null; } - isNeverNull &= value.getType().isReferenceType() && value.isNeverNull(); } if (returnValue != null) { Value aliasedValue = returnValue.getAliasedValue(); @@ -382,9 +380,6 @@ } } } - if (isNeverNull) { - feedback.methodNeverReturnsNull(method); - } } private void computeInstanceInitializerInfo( @@ -958,21 +953,38 @@ if (!staticReturnTypeRaw.isReferenceType()) { return; } - TypeElement dynamicReturnType = + TypeElement dynamicUpperBoundReturnType = dynamicTypeOptimization.computeDynamicReturnType(method, code); - if (dynamicReturnType != null) { - TypeElement staticReturnType = - TypeElement.fromDexType(staticReturnTypeRaw, Nullability.maybeNull(), appView); - // If the dynamic return type is not more precise than the static return type there is no - // need to record it. - if (dynamicReturnType.strictlyLessThan(staticReturnType, appView)) { - feedback.methodReturnsObjectWithUpperBoundType(method, appView, dynamicReturnType); + if (dynamicUpperBoundReturnType != null) { + if (dynamicUpperBoundReturnType.isReferenceType() + && dynamicUpperBoundReturnType.isDefinitelyNull()) { + feedback.methodReturnsAbstractValue( + method, appView, appView.abstractValueFactory().createSingleNumberValue(0)); + feedback.methodReturnsObjectWithUpperBoundType(method, appView, TypeElement.getNull()); + } else { + TypeElement staticReturnType = + TypeElement.fromDexType(staticReturnTypeRaw, Nullability.maybeNull(), appView); + // If the dynamic return type is not more precise than the static return type there is no + // need to record it. + if (dynamicUpperBoundReturnType.strictlyLessThan(staticReturnType, appView)) { + feedback.methodReturnsObjectWithUpperBoundType( + method, appView, dynamicUpperBoundReturnType); + } } } - ClassTypeElement exactReturnType = + + if (dynamicUpperBoundReturnType != null && dynamicUpperBoundReturnType.isNullType()) { + return; + } + + ClassTypeElement dynamicLowerBoundReturnType = dynamicTypeOptimization.computeDynamicLowerBoundType(method, code); - if (exactReturnType != null) { - feedback.methodReturnsObjectWithLowerBoundType(method, exactReturnType); + if (dynamicLowerBoundReturnType != null) { + assert dynamicUpperBoundReturnType == null + || dynamicUpperBoundReturnType + .nullability() + .lessThanOrEqual(dynamicLowerBoundReturnType.nullability()); + feedback.methodReturnsObjectWithLowerBoundType(method, dynamicLowerBoundReturnType); } } } @@ -1156,7 +1168,7 @@ // Collect basic blocks that check nullability of the parameter. nullCheckedBlocks.clear(); for (Instruction user : argument.uniqueUsers()) { - if (user.isAssumeNonNull()) { + if (user.isAssumeWithNonNullAssumption()) { nullCheckedBlocks.add(user.asAssume().getBlock()); } if (user.isIf()
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 d4eef20..d9db007 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
@@ -201,11 +201,6 @@ } @Override - public synchronized void methodNeverReturnsNull(DexEncodedMethod method) { - getMethodOptimizationInfoForUpdating(method).markNeverReturnsNull(); - } - - @Override public synchronized void methodNeverReturnsNormally(DexEncodedMethod method) { getMethodOptimizationInfoForUpdating(method).markNeverReturnsNormally(); }
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 435f05f..49ffaf9 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
@@ -90,9 +90,6 @@ public void methodReturnValueOnlyDependsOnArguments(DexEncodedMethod method) {} @Override - public void methodNeverReturnsNull(DexEncodedMethod method) {} - - @Override public void methodNeverReturnsNormally(DexEncodedMethod method) {} @Override
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 9696162..822e0c5 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
@@ -104,7 +104,7 @@ @Override public void methodReturnsObjectWithUpperBoundType( DexEncodedMethod method, AppView<?> appView, TypeElement type) { - // Ignored. + method.getMutableOptimizationInfo().markReturnsObjectWithUpperBoundType(appView, type); } @Override @@ -124,11 +124,6 @@ } @Override - public void methodNeverReturnsNull(DexEncodedMethod method) { - method.getMutableOptimizationInfo().markNeverReturnsNull(); - } - - @Override public void methodNeverReturnsNormally(DexEncodedMethod method) { // Ignored. } @@ -140,8 +135,7 @@ @Override public void markProcessed(DexEncodedMethod method, ConstraintWithTarget state) { - // Just as processed, don't provide any inlining constraints. - method.markProcessed(ConstraintWithTarget.NEVER); + method.markProcessed(state); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java index c1012ca..3971c57 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -22,7 +22,7 @@ import java.util.Set; import java.util.function.Function; -public class UpdatableMethodOptimizationInfo implements MethodOptimizationInfo { +public class UpdatableMethodOptimizationInfo extends MethodOptimizationInfo { private Set<DexType> initializedClassesOnNormalExit = DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT; @@ -71,9 +71,9 @@ private static final int HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG = 0x4; private static final int MAY_HAVE_SIDE_EFFECT_FLAG = 0x8; private static final int RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG = 0x10; - private static final int NEVER_RETURNS_NULL_FLAG = 0x20; + private static final int UNUSED_FLAG_1 = 0x20; private static final int NEVER_RETURNS_NORMALLY_FLAG = 0x40; - private static final int UNUSED_FLAG = 0x80; + private static final int UNUSED_FLAG_2 = 0x80; private static final int CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG = 0x100; private static final int TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG = 0x200; private static final int INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG = 0x400; @@ -97,11 +97,10 @@ defaultFlags |= BooleanUtils.intValue(defaultOptInfo.returnValueOnlyDependsOnArguments()) * RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG; - defaultFlags |= - BooleanUtils.intValue(defaultOptInfo.neverReturnsNull()) * NEVER_RETURNS_NULL_FLAG; + defaultFlags |= 0 * UNUSED_FLAG_1; defaultFlags |= BooleanUtils.intValue(defaultOptInfo.neverReturnsNormally()) * NEVER_RETURNS_NORMALLY_FLAG; - defaultFlags |= 0 * UNUSED_FLAG; + defaultFlags |= 0 * UNUSED_FLAG_2; defaultFlags |= BooleanUtils.intValue(defaultOptInfo.checksNullReceiverBeforeAnySideEffect()) * CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG; @@ -293,11 +292,6 @@ } @Override - public boolean neverReturnsNull() { - return isFlagSet(NEVER_RETURNS_NULL_FLAG); - } - - @Override public boolean neverReturnsNormally() { return isFlagSet(NEVER_RETURNS_NORMALLY_FLAG); } @@ -402,10 +396,6 @@ setFlag(RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG); } - void markNeverReturnsNull() { - setFlag(NEVER_RETURNS_NULL_FLAG); - } - void markNeverReturnsNormally() { setFlag(NEVER_RETURNS_NORMALLY_FLAG); } @@ -429,6 +419,9 @@ || type.lessThanOrEqualUpToNullability(returnsObjectWithUpperBoundType, appView) : "upper bound type changed from " + returnsObjectWithUpperBoundType + " to " + type; returnsObjectWithUpperBoundType = type; + if (type.isNullType()) { + returnsObjectWithLowerBoundType = null; + } } void markReturnsObjectWithLowerBoundType(ClassTypeElement type) { @@ -436,9 +429,7 @@ // Currently, we only have a lower bound type when we have _exact_ runtime type information. // Thus, the type should never become more precise (although the nullability could). assert returnsObjectWithLowerBoundType == DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE - || (type.equalUpToNullability(returnsObjectWithLowerBoundType) - && type.nullability() - .lessThanOrEqual(returnsObjectWithLowerBoundType.nullability())) + || type.equalUpToNullability(returnsObjectWithLowerBoundType) : "lower bound type changed from " + returnsObjectWithLowerBoundType + " to " + type; returnsObjectWithLowerBoundType = type; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java index 2fe8a19..fe70918 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
@@ -4,7 +4,9 @@ package com.android.tools.r8.ir.optimize.info.field; + import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexField; @@ -32,9 +34,10 @@ BiConsumer<DexEncodedField, InstanceFieldInitializationInfo> consumer) { infos.forEach( (field, info) -> { - DexEncodedField encodedField = definitions.definitionFor(field); - if (encodedField != null) { - consumer.accept(encodedField, info); + DexClass holder = definitions.definitionForHolder(field); + DexEncodedField definition = field.lookupOnClass(holder); + if (definition != null) { + consumer.accept(definition, info); } else { assert false; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java index e392112..e9ab954 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
@@ -49,7 +49,7 @@ genericSignature, mainMethod, innerClass, - lambda.getEnclosingMethod()); + lambda.getEnclosingMethodAttribute()); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java index ea39dce..495ab0b 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
@@ -49,7 +49,7 @@ genericSignature, mainMethod, innerClass, - lambda.getEnclosingMethod()); + lambda.getEnclosingMethodAttribute()); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java index 3a7e099..bcea380 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -44,8 +44,7 @@ register(new ObjectMethodOptimizer(appView)); register(new ObjectsMethodOptimizer(appView)); register(new StringMethodOptimizer(appView)); - if (appView.enableWholeProgramOptimizations() - && appView.options().enableDynamicTypeOptimization) { + if (appView.enableWholeProgramOptimizations()) { // Subtyping is required to prove the enum class is a subtype of java.lang.Enum. register(new EnumMethodOptimizer(appView)); } @@ -66,7 +65,7 @@ for (LibraryMembers libraryMembers : appView.dexItemFactory().libraryMembersCollection) { libraryMembers.forEachFinalField( field -> { - DexEncodedField definition = appView.definitionFor(field); + DexEncodedField definition = field.lookupOnClass(appView.definitionForHolder(field)); if (definition != null) { if (definition.isFinal()) { finalLibraryFields.add(definition);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java index a677b43..92c78af 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -4,12 +4,15 @@ package com.android.tools.r8.ir.optimize.library; +import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull; + import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.analysis.value.AbstractValueFactory; import com.android.tools.r8.ir.analysis.value.ObjectState; import com.android.tools.r8.ir.optimize.info.LibraryOptimizationInfoInitializerFeedback; @@ -57,7 +60,16 @@ for (DexMethod method : dexItemFactory.libraryMethodsReturningNonNull) { DexEncodedMethod definition = lookupMethod(method); if (definition != null) { - feedback.methodNeverReturnsNull(definition); + TypeElement staticType = + TypeElement.fromDexType(method.proto.returnType, maybeNull(), appView); + feedback.methodReturnsObjectWithUpperBoundType( + definition, + appView, + definition + .getOptimizationInfo() + .getDynamicUpperBoundTypeOrElse(staticType) + .asReferenceType() + .asDefinitelyNotNull()); } } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java index 0e0e1c9..29250dc 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -35,6 +35,7 @@ import com.android.tools.r8.ir.conversion.MethodProcessingId; import com.android.tools.r8.ir.conversion.MethodProcessor; import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor; +import com.android.tools.r8.ir.optimize.AssumeInserter; import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult; import com.android.tools.r8.ir.optimize.CodeRewriter; import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; @@ -390,7 +391,10 @@ } private void insertAssumeInstructions(IRCode code, MethodProcessor methodProcessor) { - CodeRewriter.insertAssumeInstructions(code, converter.assumers, Timing.empty()); + AssumeInserter assumeInserter = converter.assumeInserter; + if (assumeInserter != null) { + assumeInserter.insertAssumeInstructions(code, Timing.empty()); + } } private BiConsumer<IRCode, MethodProcessor> collectOptimizationInfo(
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java index 7eddfcc..7af08f9 100644 --- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java +++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -347,6 +347,7 @@ while (instructionIterator.hasNext()) { Instruction instruction = instructionIterator.next(); if (!instructionIterator.hasNext()) { + instruction.clearDebugValues(); break; }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java index 61bdbbc..5e9d63f 100644 --- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java +++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.cf.code.CfConstString; import com.android.tools.r8.cf.code.CfFieldInstruction; import com.android.tools.r8.cf.code.CfIf; +import com.android.tools.r8.cf.code.CfIfCmp; import com.android.tools.r8.cf.code.CfInstruction; import com.android.tools.r8.cf.code.CfInvoke; import com.android.tools.r8.cf.code.CfLabel; @@ -66,7 +67,7 @@ CfLabel dest = new CfLabel(); instructions.add(new CfLoad(ValueType.fromDexType(factory.intType), 0)); instructions.add(new CfConstNumber(enumValueInfo.convertToInt(), ValueType.INT)); - instructions.add(new CfIf(If.Type.EQ, ValueType.INT, dest)); + instructions.add(new CfIfCmp(If.Type.NE, ValueType.INT, dest)); instructions.add(new CfConstString(field.name)); instructions.add(new CfReturn(ValueType.OBJECT)); instructions.add(dest);
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java index 18de3c5..01c3daa 100644 --- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java +++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -172,8 +172,8 @@ writeAnnotations(writer::visitAnnotation, clazz.annotations().annotations); ImmutableMap<DexString, DexValue> defaults = getAnnotationDefaults(clazz.annotations()); - if (clazz.getEnclosingMethod() != null) { - clazz.getEnclosingMethod().write(writer, namingLens); + if (clazz.getEnclosingMethodAttribute() != null) { + clazz.getEnclosingMethodAttribute().write(writer, namingLens); } if (clazz.getNestHostClassAttribute() != null) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java b/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java index 84d6e82..7431fb9 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java +++ b/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
@@ -13,6 +13,7 @@ import kotlinx.metadata.KmTypeVisitor; import kotlinx.metadata.KmValueParameterVisitor; import kotlinx.metadata.KmVariance; +import kotlinx.metadata.KmVersionRequirementVisitor; /** * The reason for having these visitor providers is to make the separation of concern a bit easier @@ -101,4 +102,10 @@ KmTypeVisitor get(int flags, String typeFlexibilityId); } + + @FunctionalInterface + public interface KmVersionRequirementVisitorProvider { + + KmVersionRequirementVisitor get(); + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java index 602165c..07f6b0b 100644 --- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java +++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -42,7 +42,7 @@ public static final class ClassClassifiers { public static final String arrayBinaryName = NAME + "/Array"; - public static final String anyName = NAME + "/Any"; + public static final String anyDescriptor = "L" + NAME + "/Any;"; } // Mappings from JVM types to Kotlin types (of type DexType)
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java index a4bc5b0..90ed44a 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
@@ -4,14 +4,12 @@ package com.android.tools.r8.kotlin; -import static com.android.tools.r8.kotlin.KotlinMetadataUtils.referenceTypeFromBinaryName; - import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexDefinitionSupplier; -import com.android.tools.r8.graph.DexString; -import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.EnqueuerMetadataTraceable; import com.android.tools.r8.utils.DescriptorUtils; import com.google.common.collect.ImmutableList; import java.util.List; @@ -20,35 +18,33 @@ import kotlinx.metadata.KmAnnotationArgument; // Holds information about a KmAnnotation -public class KotlinAnnotationInfo { +public class KotlinAnnotationInfo implements EnqueuerMetadataTraceable { private static final List<KotlinAnnotationInfo> EMPTY_ANNOTATIONS = ImmutableList.of(); - private final DexType annotationType; + private final KotlinTypeReference annotationType; // TODO(b/155053894): Model KmAnnotationArgument. private final Map<String, KmAnnotationArgument<?>> arguments; private KotlinAnnotationInfo( - DexType annotationType, Map<String, KmAnnotationArgument<?>> arguments) { + KotlinTypeReference annotationType, Map<String, KmAnnotationArgument<?>> arguments) { this.annotationType = annotationType; this.arguments = arguments; } - private static KotlinAnnotationInfo create( - KmAnnotation annotation, DexDefinitionSupplier definitionSupplier) { + private static KotlinAnnotationInfo create(KmAnnotation annotation, DexItemFactory factory) { return new KotlinAnnotationInfo( - referenceTypeFromBinaryName(annotation.getClassName(), definitionSupplier), + KotlinTypeReference.fromBinaryName(annotation.getClassName(), factory), annotation.getArguments()); } - static List<KotlinAnnotationInfo> create( - List<KmAnnotation> annotations, DexDefinitionSupplier definitionSupplier) { + static List<KotlinAnnotationInfo> create(List<KmAnnotation> annotations, DexItemFactory factory) { if (annotations.isEmpty()) { return EMPTY_ANNOTATIONS; } ImmutableList.Builder<KotlinAnnotationInfo> builder = ImmutableList.builder(); for (KmAnnotation annotation : annotations) { - builder.add(create(annotation, definitionSupplier)); + builder.add(create(annotation, factory)); } return builder.build(); } @@ -57,12 +53,20 @@ KmVisitorProviders.KmAnnotationVisitorProvider visitorProvider, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) { - if (appView.appInfo().wasPruned(annotationType)) { + String renamedDescriptor = + annotationType.toRenamedDescriptorOrDefault(appView, namingLens, null); + if (renamedDescriptor == null) { + // The type has been pruned return; } - DexString descriptor = namingLens.lookupDescriptor(annotationType); - String classifier = DescriptorUtils.descriptorToKotlinClassifier(descriptor.toString()); + String classifier = DescriptorUtils.descriptorToKotlinClassifier(renamedDescriptor); KmAnnotation annotation = new KmAnnotation(classifier, arguments); visitorProvider.get(annotation); } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + annotationType.trace(definitionSupplier); + // TODO(b/155053894): Trace annotation arguments. + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java index b507093..9987457 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -4,17 +4,17 @@ package com.android.tools.r8.kotlin; -import static com.android.tools.r8.kotlin.KotlinMetadataUtils.referenceTypeFromBinaryName; import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toJvmFieldSignature; import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toJvmMethodSignature; +import static com.android.tools.r8.utils.FunctionUtils.forEachApply; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexString; -import com.android.tools.r8.graph.DexType; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.DescriptorUtils; @@ -41,14 +41,15 @@ private final KotlinDeclarationContainerInfo declarationContainerInfo; private final List<KotlinTypeParameterInfo> typeParameters; private final List<KotlinTypeInfo> superTypes; - private final List<DexType> sealedSubClasses; - private final List<DexType> nestedClasses; + private final List<KotlinTypeReference> sealedSubClasses; + private final List<KotlinTypeReference> nestedClasses; // TODO(b/154347404): Understand enum entries. private final List<String> enumEntries; - private final DexType anonymousObjectOrigin; + private final KotlinVersionRequirementInfo versionRequirements; + private final KotlinTypeReference anonymousObjectOrigin; private final String packageName; - public KotlinClassInfo( + private KotlinClassInfo( int flags, String name, String moduleName, @@ -56,10 +57,11 @@ List<KotlinTypeParameterInfo> typeParameters, List<KotlinConstructorInfo> constructorsWithNoBacking, List<KotlinTypeInfo> superTypes, - List<DexType> sealedSubClasses, - List<DexType> nestedClasses, + List<KotlinTypeReference> sealedSubClasses, + List<KotlinTypeReference> nestedClasses, List<String> enumEntries, - DexType anonymousObjectOrigin, + KotlinVersionRequirementInfo versionRequirements, + KotlinTypeReference anonymousObjectOrigin, String packageName) { this.flags = flags; this.name = name; @@ -71,6 +73,7 @@ this.sealedSubClasses = sealedSubClasses; this.nestedClasses = nestedClasses; this.enumEntries = enumEntries; + this.versionRequirements = versionRequirements; this.anonymousObjectOrigin = anonymousObjectOrigin; this.packageName = packageName; } @@ -79,7 +82,7 @@ KmClass kmClass, String packageName, DexClass hostClass, - DexDefinitionSupplier definitionSupplier, + DexItemFactory factory, Reporter reporter, Consumer<DexEncodedMethod> keepByteCode) { Map<String, DexEncodedField> fieldMap = new HashMap<>(); @@ -93,7 +96,7 @@ ImmutableList.Builder<KotlinConstructorInfo> notBackedConstructors = ImmutableList.builder(); for (KmConstructor kmConstructor : kmClass.getConstructors()) { KotlinConstructorInfo constructorInfo = - KotlinConstructorInfo.create(kmConstructor, definitionSupplier, reporter); + KotlinConstructorInfo.create(kmConstructor, factory, reporter); JvmMethodSignature signature = JvmExtensionsKt.getSignature(kmConstructor); if (signature != null) { DexEncodedMethod method = methodMap.get(signature.asString()); @@ -107,60 +110,61 @@ } KotlinDeclarationContainerInfo container = KotlinDeclarationContainerInfo.create( - kmClass, methodMap, fieldMap, definitionSupplier, reporter, keepByteCode); + kmClass, methodMap, fieldMap, factory, reporter, keepByteCode); setCompanionObject(kmClass, hostClass, reporter); return new KotlinClassInfo( kmClass.getFlags(), kmClass.name, JvmExtensionsKt.getModuleName(kmClass), container, - KotlinTypeParameterInfo.create(kmClass.getTypeParameters(), definitionSupplier, reporter), + KotlinTypeParameterInfo.create(kmClass.getTypeParameters(), factory, reporter), notBackedConstructors.build(), - getSuperTypes(kmClass.getSupertypes(), definitionSupplier, reporter), - getSealedSubClasses(hostClass, kmClass.getSealedSubclasses(), definitionSupplier), - getNestedClasses(hostClass, kmClass.getNestedClasses(), definitionSupplier), + getSuperTypes(kmClass.getSupertypes(), factory, reporter), + getSealedSubClasses(kmClass.getSealedSubclasses(), factory), + getNestedClasses(hostClass, kmClass.getNestedClasses(), factory), kmClass.getEnumEntries(), - getAnonymousObjectOrigin(kmClass, definitionSupplier), + KotlinVersionRequirementInfo.create(kmClass.getVersionRequirements()), + getAnonymousObjectOrigin(kmClass, factory), packageName); } - private static DexType getAnonymousObjectOrigin( - KmClass kmClass, DexDefinitionSupplier definitionSupplier) { + private static KotlinTypeReference getAnonymousObjectOrigin( + KmClass kmClass, DexItemFactory factory) { String anonymousObjectOriginName = JvmExtensionsKt.getAnonymousObjectOriginName(kmClass); if (anonymousObjectOriginName != null) { - return referenceTypeFromBinaryName(anonymousObjectOriginName, definitionSupplier); + return KotlinTypeReference.fromBinaryName(anonymousObjectOriginName, factory); } return null; } - private static List<DexType> getNestedClasses( - DexClass clazz, List<String> nestedClasses, DexDefinitionSupplier definitionSupplier) { - ImmutableList.Builder<DexType> nestedTypes = ImmutableList.builder(); + private static List<KotlinTypeReference> getNestedClasses( + DexClass clazz, List<String> nestedClasses, DexItemFactory factory) { + ImmutableList.Builder<KotlinTypeReference> nestedTypes = ImmutableList.builder(); for (String nestedClass : nestedClasses) { String binaryName = clazz.type.toBinaryName() + DescriptorUtils.INNER_CLASS_SEPARATOR + nestedClass; - nestedTypes.add(referenceTypeFromBinaryName(binaryName, definitionSupplier)); + nestedTypes.add(KotlinTypeReference.fromBinaryName(binaryName, factory)); } return nestedTypes.build(); } - private static List<DexType> getSealedSubClasses( - DexClass clazz, List<String> sealedSubclasses, DexDefinitionSupplier definitionSupplier) { - ImmutableList.Builder<DexType> sealedTypes = ImmutableList.builder(); + private static List<KotlinTypeReference> getSealedSubClasses( + List<String> sealedSubclasses, DexItemFactory factory) { + ImmutableList.Builder<KotlinTypeReference> sealedTypes = ImmutableList.builder(); for (String sealedSubClass : sealedSubclasses) { String binaryName = sealedSubClass.replace( DescriptorUtils.JAVA_PACKAGE_SEPARATOR, DescriptorUtils.INNER_CLASS_SEPARATOR); - sealedTypes.add(referenceTypeFromBinaryName(binaryName, definitionSupplier)); + sealedTypes.add(KotlinTypeReference.fromBinaryName(binaryName, factory)); } return sealedTypes.build(); } private static List<KotlinTypeInfo> getSuperTypes( - List<KmType> superTypes, DexDefinitionSupplier definitionSupplier, Reporter reporter) { + List<KmType> superTypes, DexItemFactory factory, Reporter reporter) { ImmutableList.Builder<KotlinTypeInfo> superTypeInfos = ImmutableList.builder(); for (KmType superType : superTypes) { - superTypeInfos.add(KotlinTypeInfo.create(superType, definitionSupplier, reporter)); + superTypeInfos.add(KotlinTypeInfo.create(superType, factory, reporter)); } return superTypeInfos.build(); } @@ -205,7 +209,7 @@ kmClass.setName( originalDescriptor.equals(rewrittenDescriptor) ? this.name - : KotlinMetadataUtils.kotlinNameFromDescriptor(rewrittenDescriptor)); + : DescriptorUtils.getBinaryNameFromDescriptor(rewrittenDescriptor.toString())); // Find a companion object. for (DexEncodedField field : clazz.fields()) { if (field.getKotlinMemberInfo().isCompanion()) { @@ -240,34 +244,35 @@ superType.rewrite(kmClass::visitSupertype, appView, namingLens); } // Rewrite nested classes. - for (DexType nestedClass : nestedClasses) { - if (appView.appInfo().isNonProgramTypeOrLiveProgramType(nestedClass)) { - String descriptor = - KotlinMetadataUtils.kotlinNameFromDescriptor(namingLens.lookupDescriptor(nestedClass)); + for (KotlinTypeReference nestedClass : nestedClasses) { + String nestedDescriptor = nestedClass.toRenamedBinaryNameOrDefault(appView, namingLens, null); + if (nestedDescriptor != null) { // If the class is a nested class, it should be on the form Foo.Bar$Baz, where Baz is the // name we should record. - int innerClassIndex = descriptor.lastIndexOf(DescriptorUtils.INNER_CLASS_SEPARATOR); - kmClass.visitNestedClass(descriptor.substring(innerClassIndex + 1)); + int innerClassIndex = nestedDescriptor.lastIndexOf(DescriptorUtils.INNER_CLASS_SEPARATOR); + kmClass.visitNestedClass(nestedDescriptor.substring(innerClassIndex + 1)); } } // Rewrite sealed sub classes. - for (DexType sealedSubClass : sealedSubClasses) { - if (appView.appInfo().isNonProgramTypeOrLiveProgramType(sealedSubClass)) { - String descriptor = - KotlinMetadataUtils.kotlinNameFromDescriptor( - namingLens.lookupDescriptor(sealedSubClass)); + for (KotlinTypeReference sealedSubClass : sealedSubClasses) { + String sealedDescriptor = + sealedSubClass.toRenamedBinaryNameOrDefault(appView, namingLens, null); + if (sealedDescriptor != null) { kmClass.visitSealedSubclass( - descriptor.replace( + sealedDescriptor.replace( DescriptorUtils.INNER_CLASS_SEPARATOR, DescriptorUtils.JAVA_PACKAGE_SEPARATOR)); } } // TODO(b/154347404): Understand enum entries. kmClass.getEnumEntries().addAll(enumEntries); - + versionRequirements.rewrite(kmClass::visitVersionRequirement); JvmExtensionsKt.setModuleName(kmClass, moduleName); if (anonymousObjectOrigin != null) { - JvmExtensionsKt.setAnonymousObjectOriginName( - kmClass, KotlinMetadataUtils.kotlinNameFromDescriptor(anonymousObjectOrigin.descriptor)); + String renamedAnon = + anonymousObjectOrigin.toRenamedBinaryNameOrDefault(appView, namingLens, null); + if (renamedAnon != null) { + JvmExtensionsKt.setAnonymousObjectOriginName(kmClass, renamedAnon); + } } KotlinClassMetadata.Class.Writer writer = new KotlinClassMetadata.Class.Writer(); @@ -279,4 +284,18 @@ public String getPackageName() { return packageName; } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + forEachApply(constructorsWithNoBacking, constructor -> constructor::trace, definitionSupplier); + declarationContainerInfo.trace(definitionSupplier); + forEachApply(typeParameters, param -> param::trace, definitionSupplier); + forEachApply(superTypes, type -> type::trace, definitionSupplier); + forEachApply(sealedSubClasses, sealed -> sealed::trace, definitionSupplier); + forEachApply(nestedClasses, nested -> nested::trace, definitionSupplier); + // TODO(b/154347404): trace enum entries. + if (anonymousObjectOrigin != null) { + anonymousObjectOrigin.trace(definitionSupplier); + } + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java index f9662ef..1a24001 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
@@ -8,9 +8,14 @@ import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.EnqueuerMetadataTraceable; import kotlinx.metadata.jvm.KotlinClassHeader; -public interface KotlinClassLevelInfo { +public interface KotlinClassLevelInfo extends EnqueuerMetadataTraceable { + + default boolean isNoKotlinInformation() { + return false; + } default boolean isClass() { return false;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java index 5fe02b2..2171a00 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexEncodedAnnotation; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexValue; import com.android.tools.r8.graph.DexValue.DexValueArray; @@ -31,22 +32,18 @@ public static KotlinClassLevelInfo getKotlinInfo( Kotlin kotlin, DexClass clazz, - DexDefinitionSupplier definitionSupplier, + DexItemFactory factory, Reporter reporter, boolean onlyProcessLambda, Consumer<DexEncodedMethod> keepByteCode) { - DexAnnotation meta = - clazz - .annotations() - .getFirstMatching(definitionSupplier.dexItemFactory().kotlinMetadataType); + DexAnnotation meta = clazz.annotations().getFirstMatching(factory.kotlinMetadataType); if (meta != null) { try { KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, meta.annotation); if (onlyProcessLambda && kMetadata.getHeader().getKind() != KOTLIN_METADATA_KIND_LAMBDA) { return NO_KOTLIN_INFO; } - return createKotlinInfo( - kotlin, clazz, kMetadata, definitionSupplier, reporter, keepByteCode); + return createKotlinInfo(kotlin, clazz, kMetadata, factory, reporter, keepByteCode); } catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) { reporter.info( new StringDiagnostic( @@ -68,6 +65,14 @@ return NO_KOTLIN_INFO; } + public static boolean hasKotlinClassMetadataAnnotation( + DexClass clazz, DexDefinitionSupplier definitionSupplier) { + return clazz + .annotations() + .getFirstMatching(definitionSupplier.dexItemFactory().kotlinMetadataType) + != null; + } + public static KotlinClassMetadata toKotlinClassMetadata( Kotlin kotlin, DexEncodedAnnotation metadataAnnotation) { Map<DexString, DexAnnotationElement> elementMap = new IdentityHashMap<>(); @@ -103,7 +108,7 @@ Kotlin kotlin, DexClass clazz, KotlinClassMetadata kMetadata, - DexDefinitionSupplier definitionSupplier, + DexItemFactory factory, Reporter reporter, Consumer<DexEncodedMethod> keepByteCode) { String packageName = kMetadata.getHeader().getPackageName(); @@ -112,7 +117,7 @@ ((KotlinClassMetadata.Class) kMetadata).toKmClass(), packageName, clazz, - definitionSupplier, + factory, reporter, keepByteCode); } else if (kMetadata instanceof KotlinClassMetadata.FileFacade) { @@ -121,20 +126,20 @@ (KotlinClassMetadata.FileFacade) kMetadata, packageName, clazz, - definitionSupplier, + factory, reporter, keepByteCode); } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) { // multi-file class with the same @JvmName. return KotlinMultiFileClassFacadeInfo.create( - (KotlinClassMetadata.MultiFileClassFacade) kMetadata, packageName, definitionSupplier); + (KotlinClassMetadata.MultiFileClassFacade) kMetadata, packageName, factory); } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) { // A single file, which is part of multi-file class. return KotlinMultiFileClassPartInfo.create( (KotlinClassMetadata.MultiFileClassPart) kMetadata, packageName, clazz, - definitionSupplier, + factory, reporter, keepByteCode); } else if (kMetadata instanceof KotlinClassMetadata.SyntheticClass) { @@ -143,7 +148,7 @@ packageName, clazz, kotlin, - definitionSupplier, + factory, reporter); } else { throw new MetadataError("unsupported 'k' value: " + kMetadata.getHeader().getKind());
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java index d6ff5b4..ac19e28 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
@@ -4,15 +4,13 @@ package com.android.tools.r8.kotlin; -import static com.android.tools.r8.kotlin.KotlinMetadataUtils.referenceTypeFromDescriptor; - import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexDefinitionSupplier; -import com.android.tools.r8.graph.DexString; -import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.kotlin.Kotlin.ClassClassifiers; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.EnqueuerMetadataTraceable; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.Reporter; import kotlinx.metadata.KmClassifier; @@ -20,10 +18,10 @@ import kotlinx.metadata.KmClassifier.TypeParameter; import kotlinx.metadata.KmTypeVisitor; -public abstract class KotlinClassifierInfo { +public abstract class KotlinClassifierInfo implements EnqueuerMetadataTraceable { public static KotlinClassifierInfo create( - KmClassifier classifier, DexDefinitionSupplier definitionSupplier, Reporter reporter) { + KmClassifier classifier, DexItemFactory factory, Reporter reporter) { if (classifier instanceof KmClassifier.Class) { String originalTypeName = ((KmClassifier.Class) classifier).getName(); // If this name starts with '.', it represents a local class or an anonymous object. This is @@ -35,7 +33,7 @@ isLocalOrAnonymous ? originalTypeName.substring(1) : originalTypeName); if (DescriptorUtils.isClassDescriptor(descriptor)) { return new KotlinClassClassifierInfo( - referenceTypeFromDescriptor(descriptor, definitionSupplier), isLocalOrAnonymous); + KotlinTypeReference.fromDescriptor(descriptor, factory), isLocalOrAnonymous); } else { return new KotlinUnknownClassClassifierInfo(originalTypeName); } @@ -54,10 +52,10 @@ public static class KotlinClassClassifierInfo extends KotlinClassifierInfo { - private final DexType type; + private final KotlinTypeReference type; private final boolean isLocalOrAnonymous; - private KotlinClassClassifierInfo(DexType type, boolean isLocalOrAnonymous) { + private KotlinClassClassifierInfo(KotlinTypeReference type, boolean isLocalOrAnonymous) { this.type = type; this.isLocalOrAnonymous = isLocalOrAnonymous; } @@ -65,20 +63,21 @@ @Override void rewrite( KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) { - if (appView.appInfo().wasPruned(type)) { - visitor.visitClass(ClassClassifiers.anyName); - return; - } - DexString descriptor = namingLens.lookupDescriptor(type); + String descriptor = + type.toRenamedDescriptorOrDefault(appView, namingLens, ClassClassifiers.anyDescriptor); // For local or anonymous classes, the classifier is prefixed with '.' and inner classes are // separated with '$'. if (isLocalOrAnonymous) { - visitor.visitClass( - "." + DescriptorUtils.getBinaryNameFromDescriptor(descriptor.toString())); + visitor.visitClass("." + DescriptorUtils.getBinaryNameFromDescriptor(descriptor)); } else { - visitor.visitClass(DescriptorUtils.descriptorToKotlinClassifier(descriptor.toString())); + visitor.visitClass(DescriptorUtils.descriptorToKotlinClassifier(descriptor)); } } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + type.trace(definitionSupplier); + } } public static class KotlinTypeParameterClassifierInfo extends KotlinClassifierInfo { @@ -94,6 +93,11 @@ KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) { visitor.visitTypeParameter(typeId); } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + // Intentionally empty. + } } public static class KotlinTypeAliasClassifierInfo extends KotlinClassifierInfo { @@ -109,6 +113,11 @@ KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) { visitor.visitTypeAlias(typeAlias); } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + // Intentionally empty. + } } public static class KotlinUnknownClassClassifierInfo extends KotlinClassifierInfo { @@ -123,6 +132,11 @@ KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) { visitor.visitClass(classifier); } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + // Intentionally empty. + } } public static class KotlinUnknownClassifierInfo extends KotlinClassifierInfo { @@ -137,5 +151,10 @@ KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) { visitor.visitTypeAlias(classifier); } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + // Intentionally empty. + } } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java index 2f97b4f..1e19f1a 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.kotlin; +import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.naming.NamingLens; @@ -27,4 +28,9 @@ String finalName = dexString.toString(); visitor.visitCompanionObject(finalName); } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + // Do nothing. + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java index 6f9f366..c28b4a7 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java
@@ -4,9 +4,12 @@ package com.android.tools.r8.kotlin; +import static com.android.tools.r8.utils.FunctionUtils.forEachApply; + import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Reporter; @@ -21,27 +24,30 @@ // Information from original KmValueParameter(s) if available. private final int flags; // Information about the value parameters. - private final List<KotlinValueParameterInfo> valueParameterInfos; + private final List<KotlinValueParameterInfo> valueParameters; + // Information about version requirements. + private final KotlinVersionRequirementInfo versionRequirements; // Information about the signature. private final KotlinJvmMethodSignatureInfo signature; private KotlinConstructorInfo( int flags, - List<KotlinValueParameterInfo> valueParameterInfos, + List<KotlinValueParameterInfo> valueParameters, + KotlinVersionRequirementInfo versionRequirements, KotlinJvmMethodSignatureInfo signature) { this.flags = flags; - this.valueParameterInfos = valueParameterInfos; + this.valueParameters = valueParameters; + this.versionRequirements = versionRequirements; this.signature = signature; } public static KotlinConstructorInfo create( - KmConstructor kmConstructor, DexDefinitionSupplier definitionSupplier, Reporter reporter) { + KmConstructor kmConstructor, DexItemFactory factory, Reporter reporter) { return new KotlinConstructorInfo( kmConstructor.getFlags(), - KotlinValueParameterInfo.create( - kmConstructor.getValueParameters(), definitionSupplier, reporter), - KotlinJvmMethodSignatureInfo.create( - JvmExtensionsKt.getSignature(kmConstructor), definitionSupplier)); + KotlinValueParameterInfo.create(kmConstructor.getValueParameters(), factory, reporter), + KotlinVersionRequirementInfo.create(kmConstructor.getVersionRequirements()), + KotlinJvmMethodSignatureInfo.create(JvmExtensionsKt.getSignature(kmConstructor), factory)); } public void rewrite( @@ -56,9 +62,10 @@ if (signature != null) { JvmExtensionsKt.setSignature(kmConstructor, signature.rewrite(method, appView, namingLens)); } - for (KotlinValueParameterInfo valueParameterInfo : valueParameterInfos) { + for (KotlinValueParameterInfo valueParameterInfo : valueParameters) { valueParameterInfo.rewrite(kmConstructor::visitValueParameter, appView, namingLens); } + versionRequirements.rewrite(kmConstructor::visitVersionRequirement); kmClass.getConstructors().add(kmConstructor); } @@ -71,4 +78,12 @@ public KotlinConstructorInfo asConstructor() { return this; } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + forEachApply(valueParameters, param -> param::trace, definitionSupplier); + if (signature != null) { + signature.trace(definitionSupplier); + } + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java index cd349ab..a83f77a 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
@@ -5,15 +5,18 @@ package com.android.tools.r8.kotlin; import static com.android.tools.r8.kotlin.KotlinMetadataUtils.isValidMethodDescriptor; +import static com.android.tools.r8.utils.FunctionUtils.forEachApply; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.kotlin.KotlinMetadataUtils.KmPropertyProcessor; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.EnqueuerMetadataTraceable; import com.android.tools.r8.utils.Reporter; import com.google.common.collect.ImmutableList; import java.util.IdentityHashMap; @@ -29,7 +32,7 @@ import kotlinx.metadata.jvm.JvmMethodSignature; // Holds information about KmDeclarationContainer -public class KotlinDeclarationContainerInfo { +public class KotlinDeclarationContainerInfo implements EnqueuerMetadataTraceable { private final List<KotlinTypeAliasInfo> typeAliases; // The functions in notBackedFunctions are KmFunctions where we could not find a representative. @@ -51,7 +54,7 @@ KmDeclarationContainer container, Map<String, DexEncodedMethod> methodSignatureMap, Map<String, DexEncodedField> fieldSignatureMap, - DexDefinitionSupplier definitionSupplier, + DexItemFactory factory, Reporter reporter, Consumer<DexEncodedMethod> keepByteCode) { ImmutableList.Builder<KotlinFunctionInfo> notBackedFunctions = ImmutableList.builder(); @@ -62,7 +65,7 @@ continue; } KotlinFunctionInfo kotlinFunctionInfo = - KotlinFunctionInfo.create(kmFunction, definitionSupplier, reporter); + KotlinFunctionInfo.create(kmFunction, factory, reporter); DexEncodedMethod method = methodSignatureMap.get(signature.asString()); if (method == null) { notBackedFunctions.add(kotlinFunctionInfo); @@ -85,7 +88,7 @@ ImmutableList.Builder<KotlinPropertyInfo> notBackedProperties = ImmutableList.builder(); for (KmProperty kmProperty : container.getProperties()) { KotlinPropertyInfo kotlinPropertyInfo = - KotlinPropertyInfo.create(kmProperty, definitionSupplier, reporter); + KotlinPropertyInfo.create(kmProperty, factory, reporter); KmPropertyProcessor propertyProcessor = new KmPropertyProcessor(kmProperty); boolean hasBacking = false; if (propertyProcessor.fieldSignature() != null) { @@ -119,7 +122,7 @@ } } return new KotlinDeclarationContainerInfo( - getTypeAliases(container.getTypeAliases(), definitionSupplier, reporter), + getTypeAliases(container.getTypeAliases(), factory, reporter), notBackedFunctions.build(), notBackedProperties.build()); } @@ -139,10 +142,10 @@ } private static List<KotlinTypeAliasInfo> getTypeAliases( - List<KmTypeAlias> aliases, DexDefinitionSupplier definitionSupplier, Reporter reporter) { + List<KmTypeAlias> aliases, DexItemFactory factory, Reporter reporter) { ImmutableList.Builder<KotlinTypeAliasInfo> builder = ImmutableList.builder(); for (KmTypeAlias alias : aliases) { - builder.add(KotlinTypeAliasInfo.create(alias, definitionSupplier, reporter)); + builder.add(KotlinTypeAliasInfo.create(alias, factory, reporter)); } return builder.build(); } @@ -208,6 +211,13 @@ } } + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + forEachApply(typeAliases, alias -> alias::trace, definitionSupplier); + forEachApply(functionsWithNoBacking, function -> function::trace, definitionSupplier); + forEachApply(propertiesWithNoBacking, property -> property::trace, definitionSupplier); + } + public static class KotlinPropertyGroup { private DexEncodedField backingField = null;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java index 7df7afb..8d9d468 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java
@@ -4,7 +4,9 @@ package com.android.tools.r8.kotlin; -public interface KotlinFieldLevelInfo { +import com.android.tools.r8.shaking.EnqueuerMetadataTraceable; + +public interface KotlinFieldLevelInfo extends EnqueuerMetadataTraceable { default boolean isCompanion() { return false;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java index c31df8c..94617e1 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Reporter; @@ -32,12 +33,12 @@ FileFacade kmFileFacade, String packageName, DexClass clazz, - DexDefinitionSupplier definitionSupplier, + DexItemFactory factory, Reporter reporter, Consumer<DexEncodedMethod> keepByteCode) { return new KotlinFileFacadeInfo( KotlinPackageInfo.create( - kmFileFacade.toKmPackage(), clazz, definitionSupplier, reporter, keepByteCode), + kmFileFacade.toKmPackage(), clazz, factory, reporter, keepByteCode), packageName); } @@ -65,4 +66,9 @@ public String getPackageName() { return packageName; } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + packageInfo.trace(definitionSupplier); + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java index c8c8836..da2e960 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexDefinitionSupplier; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Reporter; @@ -45,22 +46,20 @@ } static KotlinFlexibleTypeUpperBoundInfo create( - KmFlexibleTypeUpperBound flexibleTypeUpperBound, - DexDefinitionSupplier definitionSupplier, - Reporter reporter) { + KmFlexibleTypeUpperBound flexibleTypeUpperBound, DexItemFactory factory, Reporter reporter) { if (flexibleTypeUpperBound == null) { return NO_FLEXIBLE_UPPER_BOUND; } KmType kmType = flexibleTypeUpperBound.getType(); return new KotlinFlexibleTypeUpperBoundInfo( kmType.getFlags(), - KotlinClassifierInfo.create(kmType.classifier, definitionSupplier, reporter), - KotlinTypeInfo.create(kmType.getAbbreviatedType(), definitionSupplier, reporter), - KotlinTypeInfo.create(kmType.getOuterType(), definitionSupplier, reporter), - getArguments(kmType.getArguments(), definitionSupplier, reporter), - KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), definitionSupplier), + KotlinClassifierInfo.create(kmType.classifier, factory, reporter), + KotlinTypeInfo.create(kmType.getAbbreviatedType(), factory, reporter), + KotlinTypeInfo.create(kmType.getOuterType(), factory, reporter), + getArguments(kmType.getArguments(), factory, reporter), + KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), factory), KotlinFlexibleTypeUpperBoundInfo.create( - kmType.getFlexibleTypeUpperBound(), definitionSupplier, reporter), + kmType.getFlexibleTypeUpperBound(), factory, reporter), flexibleTypeUpperBound.getTypeFlexibilityId()); } @@ -74,4 +73,12 @@ } super.rewrite(flags -> visitorProvider.get(flags, typeFlexibilityId), appView, namingLens); } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + if (this == NO_FLEXIBLE_UPPER_BOUND) { + return; + } + super.trace(definitionSupplier); + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java index 58b07cb..d71d574 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
@@ -4,12 +4,12 @@ package com.android.tools.r8.kotlin; -import static com.android.tools.r8.kotlin.KotlinMetadataUtils.referenceTypeFromBinaryName; +import static com.android.tools.r8.utils.FunctionUtils.forEachApply; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Reporter; @@ -36,7 +36,11 @@ // Information about the signature private final KotlinJvmMethodSignatureInfo signature; // Information about the lambdaClassOrigin. - private final DexType lambdaClassOrigin; + private final KotlinTypeReference lambdaClassOrigin; + // Information about version requirements. + private final KotlinVersionRequirementInfo versionRequirements; + // A value describing if any of the parameters are crossinline. + private final boolean crossInlineParameter; private KotlinFunctionInfo( int flags, @@ -46,7 +50,9 @@ List<KotlinValueParameterInfo> valueParameters, List<KotlinTypeParameterInfo> typeParameters, KotlinJvmMethodSignatureInfo signature, - DexType lambdaClassOrigin) { + KotlinTypeReference lambdaClassOrigin, + KotlinVersionRequirementInfo versionRequirements, + boolean crossInlineParameter) { this.flags = flags; this.name = name; this.returnType = returnType; @@ -55,29 +61,43 @@ this.typeParameters = typeParameters; this.signature = signature; this.lambdaClassOrigin = lambdaClassOrigin; + this.versionRequirements = versionRequirements; + this.crossInlineParameter = crossInlineParameter; + } + + public boolean hasCrossInlineParameter() { + return crossInlineParameter; } static KotlinFunctionInfo create( - KmFunction kmFunction, DexDefinitionSupplier definitionSupplier, Reporter reporter) { + KmFunction kmFunction, DexItemFactory factory, Reporter reporter) { + boolean isCrossInline = false; + List<KotlinValueParameterInfo> valueParameters = + KotlinValueParameterInfo.create(kmFunction.getValueParameters(), factory, reporter); + for (KotlinValueParameterInfo valueParameter : valueParameters) { + if (valueParameter.isCrossInline()) { + isCrossInline = true; + break; + } + } return new KotlinFunctionInfo( kmFunction.getFlags(), kmFunction.getName(), - KotlinTypeInfo.create(kmFunction.getReturnType(), definitionSupplier, reporter), - KotlinTypeInfo.create(kmFunction.getReceiverParameterType(), definitionSupplier, reporter), - KotlinValueParameterInfo.create( - kmFunction.getValueParameters(), definitionSupplier, reporter), - KotlinTypeParameterInfo.create( - kmFunction.getTypeParameters(), definitionSupplier, reporter), - KotlinJvmMethodSignatureInfo.create( - JvmExtensionsKt.getSignature(kmFunction), definitionSupplier), - getlambdaClassOrigin(kmFunction, definitionSupplier)); + KotlinTypeInfo.create(kmFunction.getReturnType(), factory, reporter), + KotlinTypeInfo.create(kmFunction.getReceiverParameterType(), factory, reporter), + valueParameters, + KotlinTypeParameterInfo.create(kmFunction.getTypeParameters(), factory, reporter), + KotlinJvmMethodSignatureInfo.create(JvmExtensionsKt.getSignature(kmFunction), factory), + getlambdaClassOrigin(kmFunction, factory), + KotlinVersionRequirementInfo.create(kmFunction.getVersionRequirements()), + isCrossInline); } - private static DexType getlambdaClassOrigin( - KmFunction kmFunction, DexDefinitionSupplier definitionSupplier) { + private static KotlinTypeReference getlambdaClassOrigin( + KmFunction kmFunction, DexItemFactory factory) { String lambdaClassOriginName = JvmExtensionsKt.getLambdaClassOriginName(kmFunction); if (lambdaClassOriginName != null) { - return referenceTypeFromBinaryName(lambdaClassOriginName, definitionSupplier); + return KotlinTypeReference.fromBinaryName(lambdaClassOriginName, factory); } return null; } @@ -108,14 +128,18 @@ if (receiverParameterType != null) { receiverParameterType.rewrite(kmFunction::visitReceiverParameterType, appView, namingLens); } + versionRequirements.rewrite(kmFunction::visitVersionRequirement); JvmFunctionExtensionVisitor extensionVisitor = (JvmFunctionExtensionVisitor) kmFunction.visitExtensions(JvmFunctionExtensionVisitor.TYPE); if (signature != null && extensionVisitor != null) { extensionVisitor.visit(signature.rewrite(method, appView, namingLens)); } if (lambdaClassOrigin != null && extensionVisitor != null) { - extensionVisitor.visitLambdaClassOriginName( - KotlinMetadataUtils.kotlinNameFromDescriptor(lambdaClassOrigin.descriptor)); + String lambdaClassOriginName = + lambdaClassOrigin.toRenamedBinaryNameOrDefault(appView, namingLens, null); + if (lambdaClassOriginName != null) { + extensionVisitor.visitLambdaClassOriginName(lambdaClassOriginName); + } } } @@ -132,4 +156,24 @@ public boolean isExtensionFunction() { return receiverParameterType != null; } + + public KotlinJvmMethodSignatureInfo getSignature() { + return signature; + } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + forEachApply(valueParameters, param -> param::trace, definitionSupplier); + returnType.trace(definitionSupplier); + if (receiverParameterType != null) { + receiverParameterType.trace(definitionSupplier); + } + forEachApply(typeParameters, param -> param::trace, definitionSupplier); + if (signature != null) { + signature.trace(definitionSupplier); + } + if (lambdaClassOrigin != null) { + lambdaClassOrigin.trace(definitionSupplier); + } + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java index 69c0442..6daf3d7 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
@@ -4,39 +4,37 @@ package com.android.tools.r8.kotlin; -import static com.android.tools.r8.kotlin.KotlinMetadataUtils.referenceTypeFromDescriptor; -import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toRenamedDescriptorOrDefault; - import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexEncodedField; -import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.EnqueuerMetadataTraceable; import kotlinx.metadata.jvm.JvmFieldSignature; /** * The JvmSignature for a method or property does not always correspond to the actual signature, see * b/154201250. We therefore need to model the signature as well. */ -public class KotlinJvmFieldSignatureInfo { +public class KotlinJvmFieldSignatureInfo implements EnqueuerMetadataTraceable { - private final DexType type; + private final KotlinTypeReference type; private final String name; - private KotlinJvmFieldSignatureInfo(String name, DexType type) { + private KotlinJvmFieldSignatureInfo(String name, KotlinTypeReference type) { this.name = name; this.type = type; } public static KotlinJvmFieldSignatureInfo create( - JvmFieldSignature fieldSignature, DexDefinitionSupplier definitionSupplier) { + JvmFieldSignature fieldSignature, DexItemFactory factory) { if (fieldSignature == null) { return null; } return new KotlinJvmFieldSignatureInfo( fieldSignature.getName(), - referenceTypeFromDescriptor(fieldSignature.getDesc(), definitionSupplier)); + KotlinTypeReference.fromDescriptor(fieldSignature.getDesc(), factory)); } public JvmFieldSignature rewrite( @@ -51,6 +49,11 @@ } String defValue = appView.dexItemFactory().objectType.toDescriptorString(); return new JvmFieldSignature( - finalName, toRenamedDescriptorOrDefault(type, appView, namingLens, defValue)); + finalName, type.toRenamedDescriptorOrDefault(appView, namingLens, defValue)); + } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + type.trace(definitionSupplier); } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java index d9d5c9d..f4674a1 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
@@ -4,15 +4,15 @@ package com.android.tools.r8.kotlin; -import static com.android.tools.r8.kotlin.KotlinMetadataUtils.referenceTypeFromDescriptor; -import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toRenamedDescriptorOrDefault; +import static com.android.tools.r8.utils.FunctionUtils.forEachApply; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.EnqueuerMetadataTraceable; import com.android.tools.r8.utils.DescriptorUtils; import com.google.common.collect.ImmutableList; import java.util.List; @@ -22,36 +22,51 @@ * The JvmSignature for a method or property does not always correspond to the actual signature, see * b/154201250. We therefore need to model the signature as well. */ -public class KotlinJvmMethodSignatureInfo { +public class KotlinJvmMethodSignatureInfo implements EnqueuerMetadataTraceable { - private static final List<DexType> EMPTY_PARAMETERS_LIST = ImmutableList.of(); + private static final List<KotlinTypeReference> EMPTY_PARAMETERS_LIST = ImmutableList.of(); private final String name; - private final DexType returnType; - private final List<DexType> parameters; + private final KotlinTypeReference returnType; + private final List<KotlinTypeReference> parameters; + private final String invalidDescriptor; - private KotlinJvmMethodSignatureInfo(String name, DexType returnType, List<DexType> parameters) { + private KotlinJvmMethodSignatureInfo( + String name, KotlinTypeReference returnType, List<KotlinTypeReference> parameters) { this.name = name; this.returnType = returnType; this.parameters = parameters; + this.invalidDescriptor = null; + } + + private KotlinJvmMethodSignatureInfo(String name, String invalidDescriptor) { + this.name = name; + this.invalidDescriptor = invalidDescriptor; + this.parameters = EMPTY_PARAMETERS_LIST; + this.returnType = null; } public static KotlinJvmMethodSignatureInfo create( - JvmMethodSignature methodSignature, DexDefinitionSupplier definitionSupplier) { + JvmMethodSignature methodSignature, DexItemFactory factory) { if (methodSignature == null) { return null; } String kotlinDescriptor = methodSignature.getDesc(); + if (!KotlinMetadataUtils.isValidMethodDescriptor(kotlinDescriptor)) { + // If the method descriptor is invalid, keep it as invalid. + return new KotlinJvmMethodSignatureInfo(methodSignature.getName(), kotlinDescriptor); + } String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(kotlinDescriptor); - DexType returnType = referenceTypeFromDescriptor(returnTypeDescriptor, definitionSupplier); + KotlinTypeReference returnType = + KotlinTypeReference.fromDescriptor(returnTypeDescriptor, factory); String[] descriptors = DescriptorUtils.getArgumentTypeDescriptors(kotlinDescriptor); if (descriptors.length == 0) { return new KotlinJvmMethodSignatureInfo( methodSignature.getName(), returnType, EMPTY_PARAMETERS_LIST); } - ImmutableList.Builder<DexType> parameters = ImmutableList.builder(); + ImmutableList.Builder<KotlinTypeReference> parameters = ImmutableList.builder(); for (String descriptor : descriptors) { - parameters.add(referenceTypeFromDescriptor(descriptor, definitionSupplier)); + parameters.add(KotlinTypeReference.fromDescriptor(descriptor, factory)); } return new KotlinJvmMethodSignatureInfo( methodSignature.getName(), returnType, parameters.build()); @@ -59,6 +74,10 @@ public JvmMethodSignature rewrite( DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) { + if (invalidDescriptor != null) { + return new JvmMethodSignature(name, invalidDescriptor); + } + assert returnType != null; String finalName = name; if (method != null) { String methodName = method.method.name.toString(); @@ -70,11 +89,38 @@ StringBuilder descBuilder = new StringBuilder(); descBuilder.append("("); String defValue = appView.dexItemFactory().objectType.toDescriptorString(); - for (DexType parameter : parameters) { - descBuilder.append(toRenamedDescriptorOrDefault(parameter, appView, namingLens, defValue)); + for (KotlinTypeReference parameter : parameters) { + descBuilder.append(parameter.toRenamedDescriptorOrDefault(appView, namingLens, defValue)); } descBuilder.append(")"); - descBuilder.append(toRenamedDescriptorOrDefault(returnType, appView, namingLens, defValue)); + descBuilder.append(returnType.toRenamedDescriptorOrDefault(appView, namingLens, defValue)); return new JvmMethodSignature(finalName, descBuilder.toString()); } + + @Override + public String toString() { + if (invalidDescriptor != null) { + return name + "(" + invalidDescriptor + ")"; + } + assert returnType != null; + StringBuilder descBuilder = new StringBuilder(); + descBuilder.append(name); + descBuilder.append("("); + for (KotlinTypeReference parameter : parameters) { + descBuilder.append(parameter.toString()); + } + descBuilder.append(")"); + descBuilder.append(returnType.toString()); + return descBuilder.toString(); + } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + if (invalidDescriptor != null) { + return; + } + assert returnType != null; + returnType.trace(definitionSupplier); + forEachApply(parameters, param -> param::trace, definitionSupplier); + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java index d25f105..459599b 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
@@ -10,48 +10,44 @@ import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.EnqueuerMetadataTraceable; import com.android.tools.r8.utils.Reporter; import kotlinx.metadata.KmLambda; -import kotlinx.metadata.KmLambdaVisitor; import kotlinx.metadata.jvm.JvmExtensionsKt; import kotlinx.metadata.jvm.JvmMethodSignature; // Holds information about a KmLambda -public class KotlinLambdaInfo { +public class KotlinLambdaInfo implements EnqueuerMetadataTraceable { private final KotlinFunctionInfo function; + private final boolean hasBacking; - private KotlinLambdaInfo(KotlinFunctionInfo function) { + private KotlinLambdaInfo(KotlinFunctionInfo function, boolean hasBacking) { this.function = function; + this.hasBacking = hasBacking; } static KotlinLambdaInfo create( - DexClass clazz, - KmLambda lambda, - DexDefinitionSupplier definitionSupplier, - Reporter reporter) { + DexClass clazz, KmLambda lambda, DexItemFactory factory, Reporter reporter) { if (lambda == null) { assert false; return null; } + KotlinFunctionInfo kotlinFunctionInfo = + KotlinFunctionInfo.create(lambda.function, factory, reporter); JvmMethodSignature signature = JvmExtensionsKt.getSignature(lambda.function); - if (signature == null) { - assert false; - return null; - } - for (DexEncodedMethod method : clazz.methods()) { - if (toJvmMethodSignature(method.method).asString().equals(signature.asString())) { - KotlinFunctionInfo kotlinFunctionInfo = - KotlinFunctionInfo.create(lambda.function, definitionSupplier, reporter); - method.setKotlinMemberInfo(kotlinFunctionInfo); - return new KotlinLambdaInfo(kotlinFunctionInfo); + if (signature != null) { + for (DexEncodedMethod method : clazz.methods()) { + if (toJvmMethodSignature(method.method).asString().equals(signature.asString())) { + method.setKotlinMemberInfo(kotlinFunctionInfo); + return new KotlinLambdaInfo(kotlinFunctionInfo, true); + } } } - // TODO(b/155536535): Resolve this assert for NestTreeShakeJarVerificationTest. - // assert false; - return null; + return new KotlinLambdaInfo(kotlinFunctionInfo, false); } boolean rewrite( @@ -59,13 +55,31 @@ DexClass clazz, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) { + if (!hasBacking) { + function.rewrite(visitorProvider.get()::visitFunction, null, appView, namingLens); + return true; + } + DexEncodedMethod backing = null; for (DexEncodedMethod method : clazz.methods()) { if (method.getKotlinMemberInfo() == function) { - KmLambdaVisitor kmLambdaVisitor = visitorProvider.get(); - function.rewrite(kmLambdaVisitor::visitFunction, method, appView, namingLens); - return true; + backing = method; + break; } } - return false; + if (backing == null) { + appView + .options() + .reporter + .info( + KotlinMetadataDiagnostic.lambdaBackingNotFound(clazz.type, function.getSignature())); + return false; + } + function.rewrite(visitorProvider.get()::visitFunction, backing, appView, namingLens); + return true; + } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + function.trace(definitionSupplier); } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java index 83947d7..3415e13 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
@@ -73,4 +73,18 @@ + StringUtils.LINE_SEPARATOR + StringUtils.stacktraceAsString(t)); } + + static KotlinMetadataDiagnostic lambdaBackingNotFound( + DexType type, KotlinJvmMethodSignatureInfo signatureInfo) { + return new KotlinMetadataDiagnostic( + Origin.unknown(), + Position.UNKNOWN, + "The lambda function " + + signatureInfo.toString() + + " could no longer be found in " + + type.toSourceString() + + " . The method is most likely pruned and would require a specific keep rule to keep" + + " alive. As a result, the metadata information regarding the lambda structure has" + + " been discarded."); + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java index 28928ef..ba814e9 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -4,11 +4,20 @@ package com.android.tools.r8.kotlin; +import static com.android.tools.r8.kotlin.KotlinClassMetadataReader.hasKotlinClassMetadataAnnotation; +import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO; +import static com.android.tools.r8.utils.FunctionUtils.forEachApply; + +import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexDefinitionSupplier; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.EnclosingMethodAttribute; import com.android.tools.r8.graph.analysis.EnqueuerAnalysis; import com.android.tools.r8.shaking.Enqueuer; import com.google.common.collect.Sets; @@ -18,12 +27,11 @@ private final AppView<?> appView; private final DexDefinitionSupplier definitionSupplier; - private final Set<DexMethod> keepByteCodeFunctions = Sets.newIdentityHashSet(); public KotlinMetadataEnqueuerExtension( - AppView<?> appView, DexDefinitionSupplier definitionSupplier) { + AppView<?> appView, DexDefinitionSupplier definitionSupplier, Set<DexType> prunedTypes) { this.appView = appView; - this.definitionSupplier = definitionSupplier; + this.definitionSupplier = new KotlinMetadataDefinitionSupplier(definitionSupplier, prunedTypes); } @Override @@ -31,23 +39,111 @@ DexType kotlinMetadataType = appView.dexItemFactory().kotlinMetadataType; DexClass kotlinMetadataClass = appView.appInfo().definitionForWithoutExistenceAssert(kotlinMetadataType); - // We will process kotlin.Metadata even if the type is not present in the program, as long as - // the annotation will be in the output + // In the first round of tree shaking build up all metadata such that it can be traced later. boolean keepMetadata = - enqueuer.isPinned(kotlinMetadataType) - || enqueuer.isMissing(kotlinMetadataType) - || (kotlinMetadataClass != null && kotlinMetadataClass.isNotProgramClass()); + kotlinMetadataClass == null + || kotlinMetadataClass.isNotProgramClass() + || enqueuer.isPinned(kotlinMetadataType); + if (enqueuer.getMode().isInitialTreeShaking()) { + Set<DexMethod> keepByteCodeFunctions = Sets.newIdentityHashSet(); + Set<DexProgramClass> localOrAnonymousClasses = Sets.newIdentityHashSet(); + enqueuer.forAllLiveClasses( + clazz -> { + boolean onlyProcessLambdas = !keepMetadata || !enqueuer.isPinned(clazz.type); + assert clazz.getKotlinInfo().isNoKotlinInformation(); + clazz.setKotlinInfo( + KotlinClassMetadataReader.getKotlinInfo( + appView.dexItemFactory().kotlin, + clazz, + definitionSupplier.dexItemFactory(), + appView.options().reporter, + onlyProcessLambdas, + method -> keepByteCodeFunctions.add(method.method))); + if (clazz.getEnclosingMethodAttribute() != null + && clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) { + localOrAnonymousClasses.add(clazz); + } + }); + appView.setCfByteCodePassThrough(keepByteCodeFunctions); + for (DexProgramClass localOrAnonymousClass : localOrAnonymousClasses) { + EnclosingMethodAttribute enclosingAttribute = + localOrAnonymousClass.getEnclosingMethodAttribute(); + DexClass holder = + definitionSupplier.definitionForHolder(enclosingAttribute.getEnclosingMethod()); + if (holder == null) { + continue; + } + DexEncodedMethod method = holder.lookupMethod(enclosingAttribute.getEnclosingMethod()); + // If we cannot lookup the method, the conservative choice is keep the byte code. + if (method == null + || (method.getKotlinMemberInfo().isFunction() + && method.getKotlinMemberInfo().asFunction().hasCrossInlineParameter())) { + localOrAnonymousClass.forEachProgramMethod( + m -> keepByteCodeFunctions.add(m.getReference())); + } + } + } else { + assert verifyKotlinMetadataModeledForAllClasses(enqueuer, keepMetadata); + } + // Trace through the modeled kotlin metadata. enqueuer.forAllLiveClasses( clazz -> { - clazz.setKotlinInfo( - KotlinClassMetadataReader.getKotlinInfo( - appView.dexItemFactory().kotlin, - clazz, - definitionSupplier, - appView.options().reporter, - !keepMetadata || !enqueuer.isPinned(clazz.type), - method -> keepByteCodeFunctions.add(method.method))); + clazz.getKotlinInfo().trace(definitionSupplier); + forEachApply( + clazz.methods(), method -> method.getKotlinMemberInfo()::trace, definitionSupplier); + forEachApply( + clazz.fields(), field -> field.getKotlinMemberInfo()::trace, definitionSupplier); }); - appView.setCfByteCodePassThrough(keepByteCodeFunctions); + } + + private boolean verifyKotlinMetadataModeledForAllClasses( + Enqueuer enqueuer, boolean keepMetadata) { + enqueuer.forAllLiveClasses( + clazz -> { + // Trace through class and member definitions + assert !hasKotlinClassMetadataAnnotation(clazz, definitionSupplier) + || !keepMetadata + || !enqueuer.isPinned(clazz.type) + || clazz.getKotlinInfo() != NO_KOTLIN_INFO; + }); + return true; + } + + public static class KotlinMetadataDefinitionSupplier implements DexDefinitionSupplier { + + private final DexDefinitionSupplier baseSupplier; + private final Set<DexType> prunedTypes; + + private KotlinMetadataDefinitionSupplier( + DexDefinitionSupplier baseSupplier, Set<DexType> prunedTypes) { + this.baseSupplier = baseSupplier; + this.prunedTypes = prunedTypes; + } + + @Override + public DexEncodedMethod definitionFor(DexMethod method) { + throw new Unreachable("Should not be called"); + } + + @Override + public DexClass definitionFor(DexType type) { + // TODO(b/157700128) Metadata cannot at this point keep anything alive. Therefore, if a type + // has been pruned it may still be referenced, so we do an early check here to ensure it will + // not end up as. Ideally, those types should be removed by a pass on the modeled data. + if (prunedTypes != null && prunedTypes.contains(type)) { + return null; + } + return baseSupplier.definitionFor(type); + } + + @Override + public DexProgramClass definitionForProgramType(DexType type) { + throw new Unreachable("Should not be called"); + } + + @Override + public DexItemFactory dexItemFactory() { + return baseSupplier.dexItemFactory(); + } } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java index 1f21451..1ee78a5 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
@@ -12,7 +12,6 @@ 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.DexString; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -59,6 +58,16 @@ public String getPackageName() { throw new Unreachable("Should never be called"); } + + @Override + public boolean isNoKotlinInformation() { + return true; + } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + // No information needed to trace. + } } static JvmFieldSignature toJvmFieldSignature(DexField field) { @@ -137,41 +146,6 @@ } } - static String toRenamedDescriptorOrDefault( - DexType type, - AppView<AppInfoWithLiveness> appView, - NamingLens namingLens, - String defaultValue) { - if (appView.appInfo().wasPruned(type)) { - return defaultValue; - } - DexString descriptor = namingLens.lookupDescriptor(type); - if (descriptor != null) { - return descriptor.toString(); - } - return defaultValue; - } - - static String kotlinNameFromDescriptor(DexString descriptor) { - return DescriptorUtils.getBinaryNameFromDescriptor(descriptor.toString()); - } - - static DexType referenceTypeFromBinaryName( - String binaryName, DexDefinitionSupplier definitionSupplier) { - return referenceTypeFromDescriptor( - DescriptorUtils.getDescriptorFromClassBinaryName(binaryName), definitionSupplier); - } - - static DexType referenceTypeFromDescriptor( - String descriptor, DexDefinitionSupplier definitionSupplier) { - DexType type = definitionSupplier.dexItemFactory().createType(descriptor); - // Lookup the definition, ignoring the result. This populates the sets in the Enqueuer. - if (type.isClassType()) { - definitionSupplier.definitionFor(type); - } - return type; - } - public static boolean mayProcessKotlinMetadata(AppView<?> appView) { // This can run before we have determined the pinned items, because we may need to load the // stack-map table on input. This is therefore a conservative guess on kotlin.Metadata is kept.
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java index fa21f81..bc124c6 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
@@ -33,6 +33,7 @@ import kotlinx.metadata.KmTypeParameter; import kotlinx.metadata.KmTypeProjection; import kotlinx.metadata.KmValueParameter; +import kotlinx.metadata.KmVersionRequirement; import kotlinx.metadata.jvm.JvmExtensionsKt; import kotlinx.metadata.jvm.JvmFieldSignature; import kotlinx.metadata.jvm.JvmMethodSignature; @@ -351,6 +352,7 @@ appendKmProperty(nextNextIndent, sb, kmProperty); }); }); + appendKmVersionRequirement(indent, sb, kmClass.getVersionRequirements()); appendKeyValue( indent, "constructors", @@ -389,6 +391,7 @@ JvmMethodSignature signature = JvmExtensionsKt.getSignature(constructor); appendKeyValue( newIndent, "signature", sb, signature != null ? signature.asString() : "null"); + appendKmVersionRequirement(newIndent, sb, constructor.getVersionRequirements()); }); } @@ -420,6 +423,7 @@ "valueParameters", sb, nextIndent -> appendValueParameters(nextIndent, sb, function.getValueParameters())); + appendKmVersionRequirement(newIndent, sb, function.getVersionRequirements()); JvmMethodSignature signature = JvmExtensionsKt.getSignature(function); appendKeyValue( newIndent, "signature", sb, signature != null ? signature.asString() : "null"); @@ -461,6 +465,7 @@ "setterParameter", sb, nextIndent -> appendValueParameter(nextIndent, sb, kmProperty.getSetterParameter())); + appendKmVersionRequirement(newIndent, sb, kmProperty.getVersionRequirements()); appendKeyValue(newIndent, "jvmFlags", sb, JvmExtensionsKt.getJvmFlags(kmProperty) + ""); JvmFieldSignature fieldSignature = JvmExtensionsKt.getFieldSignature(kmProperty); appendKeyValue( @@ -730,6 +735,7 @@ nextIndent -> { appendKmType(nextIndent, sb, kmTypeAlias.underlyingType); }); + appendKmVersionRequirement(newIndent, sb, kmTypeAlias.getVersionRequirements()); }); } @@ -753,4 +759,44 @@ }); }); } + + private static void appendKmVersionRequirement( + String indent, StringBuilder sb, List<KmVersionRequirement> kmVersionRequirements) { + appendKeyValue( + indent, + "versionRequirements", + sb, + newIndent -> { + appendKmList( + newIndent, + "KmVersionRequirement", + sb, + kmVersionRequirements, + (nextIndent, kmVersionRequirement) -> { + appendKmSection( + nextIndent, + "KmVersionRequirement", + sb, + nextNextIndent -> { + appendKeyValue(nextNextIndent, "kind", sb, kmVersionRequirement.kind.name()); + appendKeyValue( + nextNextIndent, "level", sb, kmVersionRequirement.level.name()); + appendKeyValue( + nextNextIndent, + "errorCode", + sb, + kmVersionRequirement.getErrorCode() == null + ? "null" + : kmVersionRequirement.getErrorCode().toString()); + appendKeyValue( + nextNextIndent, "message", sb, kmVersionRequirement.getMessage()); + appendKeyValue( + nextNextIndent, + "version", + sb, + kmVersionRequirement.getVersion().toString()); + }); + }); + }); + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java index 604374e..a02cb0a 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java
@@ -4,7 +4,9 @@ package com.android.tools.r8.kotlin; -public interface KotlinMethodLevelInfo { +import com.android.tools.r8.shaking.EnqueuerMetadataTraceable; + +public interface KotlinMethodLevelInfo extends EnqueuerMetadataTraceable { default boolean isConstructor() { return false;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java index 3baea9c..1ccd35f 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
@@ -4,14 +4,14 @@ package com.android.tools.r8.kotlin; +import static com.android.tools.r8.utils.FunctionUtils.forEachApply; + import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexDefinitionSupplier; -import com.android.tools.r8.graph.DexString; -import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.DescriptorUtils; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; @@ -22,23 +22,20 @@ // Holds information about Metadata.MultiFileClassFace public class KotlinMultiFileClassFacadeInfo implements KotlinClassLevelInfo { - private final List<DexType> partClassNames; + private final List<KotlinTypeReference> partClassNames; private final String packageName; - private KotlinMultiFileClassFacadeInfo(List<DexType> partClassNames, String packageName) { + private KotlinMultiFileClassFacadeInfo( + List<KotlinTypeReference> partClassNames, String packageName) { this.partClassNames = partClassNames; this.packageName = packageName; } static KotlinMultiFileClassFacadeInfo create( - MultiFileClassFacade kmMultiFileClassFacade, - String packageName, - DexDefinitionSupplier definitionSupplier) { - ImmutableList.Builder<DexType> builder = ImmutableList.builder(); + MultiFileClassFacade kmMultiFileClassFacade, String packageName, DexItemFactory factory) { + ImmutableList.Builder<KotlinTypeReference> builder = ImmutableList.builder(); for (String partClassName : kmMultiFileClassFacade.getPartClassNames()) { - String descriptor = DescriptorUtils.getDescriptorFromClassBinaryName(partClassName); - DexType type = definitionSupplier.dexItemFactory().createType(descriptor); - builder.add(type); + builder.add(KotlinTypeReference.fromBinaryName(partClassName, factory)); } return new KotlinMultiFileClassFacadeInfo(builder.build(), packageName); } @@ -59,11 +56,10 @@ KotlinClassMetadata.MultiFileClassFacade.Writer writer = new KotlinClassMetadata.MultiFileClassFacade.Writer(); List<String> partClassNameStrings = new ArrayList<>(partClassNames.size()); - for (DexType partClassName : partClassNames) { - if (appView.appInfo().isNonProgramTypeOrLiveProgramType(partClassName)) { - DexString descriptor = namingLens.lookupDescriptor(partClassName); - String classifier = DescriptorUtils.descriptorToKotlinClassifier(descriptor.toString()); - partClassNameStrings.add(classifier); + for (KotlinTypeReference partClassName : partClassNames) { + String binaryName = partClassName.toRenamedBinaryNameOrDefault(appView, namingLens, null); + if (binaryName != null) { + partClassNameStrings.add(binaryName); } } return writer.write(partClassNameStrings).getHeader(); @@ -73,4 +69,9 @@ public String getPackageName() { return packageName; } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + forEachApply(partClassNames, type -> type::trace, definitionSupplier); + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java index d4ec800..9470082 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Reporter; @@ -20,6 +21,7 @@ // Holds information about Metadata.MultiFileClassPartInfo public class KotlinMultiFileClassPartInfo implements KotlinClassLevelInfo { + // TODO(b/157630779): Maybe model facadeClassName. private final String facadeClassName; private final KotlinPackageInfo packageInfo; private final String packageName; @@ -35,13 +37,12 @@ MultiFileClassPart classPart, String packageName, DexClass clazz, - DexDefinitionSupplier definitionSupplier, + DexItemFactory factory, Reporter reporter, Consumer<DexEncodedMethod> keepByteCode) { return new KotlinMultiFileClassPartInfo( classPart.getFacadeClassName(), - KotlinPackageInfo.create( - classPart.toKmPackage(), clazz, definitionSupplier, reporter, keepByteCode), + KotlinPackageInfo.create(classPart.toKmPackage(), clazz, factory, reporter, keepByteCode), packageName); } @@ -70,4 +71,9 @@ public String getPackageName() { return packageName; } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + packageInfo.trace(definitionSupplier); + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java index d98e109..8f0268a 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
@@ -12,8 +12,10 @@ import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.EnqueuerMetadataTraceable; import com.android.tools.r8.utils.Reporter; import java.util.HashMap; import java.util.Map; @@ -22,7 +24,7 @@ import kotlinx.metadata.jvm.JvmExtensionsKt; // Holds information about a KmPackage object. -public class KotlinPackageInfo { +public class KotlinPackageInfo implements EnqueuerMetadataTraceable { private final String moduleName; private final KotlinDeclarationContainerInfo containerInfo; @@ -35,7 +37,7 @@ public static KotlinPackageInfo create( KmPackage kmPackage, DexClass clazz, - DexDefinitionSupplier definitionSupplier, + DexItemFactory factory, Reporter reporter, Consumer<DexEncodedMethod> keepByteCode) { Map<String, DexEncodedField> fieldMap = new HashMap<>(); @@ -49,7 +51,7 @@ return new KotlinPackageInfo( JvmExtensionsKt.getModuleName(kmPackage), KotlinDeclarationContainerInfo.create( - kmPackage, methodMap, fieldMap, definitionSupplier, reporter, keepByteCode)); + kmPackage, methodMap, fieldMap, factory, reporter, keepByteCode)); } public void rewrite( @@ -66,4 +68,9 @@ namingLens); JvmExtensionsKt.setModuleName(kmPackage, moduleName); } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + containerInfo.trace(definitionSupplier); + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java index 802428a..4d14c67 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
@@ -4,10 +4,13 @@ package com.android.tools.r8.kotlin; +import static com.android.tools.r8.utils.FunctionUtils.forEachApply; + import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Reporter; @@ -41,6 +44,8 @@ private final List<KotlinTypeParameterInfo> typeParameters; + private final KotlinVersionRequirementInfo versionRequirements; + private final int jvmFlags; private final KotlinJvmFieldSignatureInfo fieldSignature; @@ -60,6 +65,7 @@ KotlinTypeInfo receiverParameterType, KotlinValueParameterInfo setterParameter, List<KotlinTypeParameterInfo> typeParameters, + KotlinVersionRequirementInfo versionRequirements, int jvmFlags, KotlinJvmFieldSignatureInfo fieldSignature, KotlinJvmMethodSignatureInfo getterSignature, @@ -73,6 +79,7 @@ this.receiverParameterType = receiverParameterType; this.setterParameter = setterParameter; this.typeParameters = typeParameters; + this.versionRequirements = versionRequirements; this.jvmFlags = jvmFlags; this.fieldSignature = fieldSignature; this.getterSignature = getterSignature; @@ -81,27 +88,25 @@ } public static KotlinPropertyInfo create( - KmProperty kmProperty, DexDefinitionSupplier definitionSupplier, Reporter reporter) { + KmProperty kmProperty, DexItemFactory factory, Reporter reporter) { return new KotlinPropertyInfo( kmProperty.getFlags(), kmProperty.getGetterFlags(), kmProperty.getSetterFlags(), kmProperty.getName(), - KotlinTypeInfo.create(kmProperty.getReturnType(), definitionSupplier, reporter), - KotlinTypeInfo.create(kmProperty.getReceiverParameterType(), definitionSupplier, reporter), - KotlinValueParameterInfo.create( - kmProperty.getSetterParameter(), definitionSupplier, reporter), - KotlinTypeParameterInfo.create( - kmProperty.getTypeParameters(), definitionSupplier, reporter), + KotlinTypeInfo.create(kmProperty.getReturnType(), factory, reporter), + KotlinTypeInfo.create(kmProperty.getReceiverParameterType(), factory, reporter), + KotlinValueParameterInfo.create(kmProperty.getSetterParameter(), factory, reporter), + KotlinTypeParameterInfo.create(kmProperty.getTypeParameters(), factory, reporter), + KotlinVersionRequirementInfo.create(kmProperty.getVersionRequirements()), JvmExtensionsKt.getJvmFlags(kmProperty), - KotlinJvmFieldSignatureInfo.create( - JvmExtensionsKt.getFieldSignature(kmProperty), definitionSupplier), + KotlinJvmFieldSignatureInfo.create(JvmExtensionsKt.getFieldSignature(kmProperty), factory), KotlinJvmMethodSignatureInfo.create( - JvmExtensionsKt.getGetterSignature(kmProperty), definitionSupplier), + JvmExtensionsKt.getGetterSignature(kmProperty), factory), KotlinJvmMethodSignatureInfo.create( - JvmExtensionsKt.getSetterSignature(kmProperty), definitionSupplier), + JvmExtensionsKt.getSetterSignature(kmProperty), factory), KotlinJvmMethodSignatureInfo.create( - JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty), definitionSupplier)); + JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty), factory)); } @Override @@ -146,6 +151,7 @@ for (KotlinTypeParameterInfo typeParameter : typeParameters) { typeParameter.rewrite(kmProperty::visitTypeParameter, appView, namingLens); } + versionRequirements.rewrite(kmProperty::visitVersionRequirement); JvmPropertyExtensionVisitor extensionVisitor = (JvmPropertyExtensionVisitor) kmProperty.visitExtensions(JvmPropertyExtensionVisitor.TYPE); if (extensionVisitor != null) { @@ -160,4 +166,30 @@ } } } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + if (returnType != null) { + returnType.trace(definitionSupplier); + } + if (receiverParameterType != null) { + receiverParameterType.trace(definitionSupplier); + } + if (setterParameter != null) { + setterParameter.trace(definitionSupplier); + } + forEachApply(typeParameters, param -> param::trace, definitionSupplier); + if (fieldSignature != null) { + fieldSignature.trace(definitionSupplier); + } + if (getterSignature != null) { + getterSignature.trace(definitionSupplier); + } + if (setterSignature != null) { + setterSignature.trace(definitionSupplier); + } + if (syntheticMethodForAnnotations != null) { + syntheticMethodForAnnotations.trace(definitionSupplier); + } + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java index 38933b5..5af2a4c 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexDefinitionSupplier; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Reporter; @@ -41,7 +42,7 @@ String packageName, DexClass clazz, Kotlin kotlin, - DexDefinitionSupplier definitionSupplier, + DexItemFactory factory, Reporter reporter) { KmLambda lambda = null; if (syntheticClass.isLambda()) { @@ -49,9 +50,7 @@ assert lambda != null; } return new KotlinSyntheticClassInfo( - lambda != null - ? KotlinLambdaInfo.create(clazz, lambda, definitionSupplier, reporter) - : null, + lambda != null ? KotlinLambdaInfo.create(clazz, lambda, factory, reporter) : null, getFlavour(syntheticClass, clazz, kotlin), packageName); } @@ -92,6 +91,13 @@ } @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + if (lambda != null) { + lambda.trace(definitionSupplier); + } + } + + @Override public String getPackageName() { return packageName; }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java index b4b5354..bb75b7f 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java
@@ -4,17 +4,21 @@ package com.android.tools.r8.kotlin; +import static com.android.tools.r8.utils.FunctionUtils.forEachApply; + import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexDefinitionSupplier; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.EnqueuerMetadataTraceable; import com.android.tools.r8.utils.Reporter; import java.util.List; import kotlinx.metadata.KmTypeAlias; import kotlinx.metadata.KmTypeAliasVisitor; // Holds information about KmTypeAlias -public class KotlinTypeAliasInfo { +public class KotlinTypeAliasInfo implements EnqueuerMetadataTraceable { private final int flags; private final String name; @@ -22,6 +26,7 @@ private final KotlinTypeInfo expandedType; private final List<KotlinTypeParameterInfo> typeParameters; private final List<KotlinAnnotationInfo> annotations; + private final KotlinVersionRequirementInfo versionRequirements; private KotlinTypeAliasInfo( int flags, @@ -29,7 +34,8 @@ KotlinTypeInfo underlyingType, KotlinTypeInfo expandedType, List<KotlinTypeParameterInfo> typeParameters, - List<KotlinAnnotationInfo> annotations) { + List<KotlinAnnotationInfo> annotations, + KotlinVersionRequirementInfo versionRequirements) { this.flags = flags; this.name = name; assert underlyingType != null; @@ -38,17 +44,19 @@ this.expandedType = expandedType; this.typeParameters = typeParameters; this.annotations = annotations; + this.versionRequirements = versionRequirements; } public static KotlinTypeAliasInfo create( - KmTypeAlias alias, DexDefinitionSupplier definitionSupplier, Reporter reporter) { + KmTypeAlias alias, DexItemFactory factory, Reporter reporter) { return new KotlinTypeAliasInfo( alias.getFlags(), alias.getName(), - KotlinTypeInfo.create(alias.underlyingType, definitionSupplier, reporter), - KotlinTypeInfo.create(alias.expandedType, definitionSupplier, reporter), - KotlinTypeParameterInfo.create(alias.getTypeParameters(), definitionSupplier, reporter), - KotlinAnnotationInfo.create(alias.getAnnotations(), definitionSupplier)); + KotlinTypeInfo.create(alias.underlyingType, factory, reporter), + KotlinTypeInfo.create(alias.expandedType, factory, reporter), + KotlinTypeParameterInfo.create(alias.getTypeParameters(), factory, reporter), + KotlinAnnotationInfo.create(alias.getAnnotations(), factory), + KotlinVersionRequirementInfo.create(alias.getVersionRequirements())); } void rewrite( @@ -64,5 +72,14 @@ for (KotlinAnnotationInfo annotation : annotations) { annotation.rewrite(kmTypeAliasVisitor::visitAnnotation, appView, namingLens); } + versionRequirements.rewrite(kmTypeAliasVisitor::visitVersionRequirement); + } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + underlyingType.trace(definitionSupplier); + expandedType.trace(definitionSupplier); + forEachApply(typeParameters, typeParam -> typeParam::trace, definitionSupplier); + forEachApply(annotations, annotation -> annotation::trace, definitionSupplier); } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java index 8601667..f21db98 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
@@ -4,10 +4,14 @@ package com.android.tools.r8.kotlin; +import static com.android.tools.r8.utils.FunctionUtils.forEachApply; + import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexDefinitionSupplier; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.EnqueuerMetadataTraceable; import com.android.tools.r8.utils.Reporter; import com.google.common.collect.ImmutableList; import java.util.List; @@ -18,7 +22,7 @@ import kotlinx.metadata.jvm.JvmTypeExtensionVisitor; // Provides access to Kotlin information about a kotlin type. -public class KotlinTypeInfo { +public class KotlinTypeInfo implements EnqueuerMetadataTraceable { private static final List<KotlinTypeProjectionInfo> EMPTY_ARGUMENTS = ImmutableList.of(); @@ -47,32 +51,29 @@ this.flexibleTypeUpperBoundInfo = flexibleTypeUpperBoundInfo; } - static KotlinTypeInfo create( - KmType kmType, DexDefinitionSupplier definitionSupplier, Reporter reporter) { + static KotlinTypeInfo create(KmType kmType, DexItemFactory factory, Reporter reporter) { if (kmType == null) { return null; } return new KotlinTypeInfo( kmType.getFlags(), - KotlinClassifierInfo.create(kmType.classifier, definitionSupplier, reporter), - KotlinTypeInfo.create(kmType.getAbbreviatedType(), definitionSupplier, reporter), - KotlinTypeInfo.create(kmType.getOuterType(), definitionSupplier, reporter), - getArguments(kmType.getArguments(), definitionSupplier, reporter), - KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), definitionSupplier), + KotlinClassifierInfo.create(kmType.classifier, factory, reporter), + KotlinTypeInfo.create(kmType.getAbbreviatedType(), factory, reporter), + KotlinTypeInfo.create(kmType.getOuterType(), factory, reporter), + getArguments(kmType.getArguments(), factory, reporter), + KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), factory), KotlinFlexibleTypeUpperBoundInfo.create( - kmType.getFlexibleTypeUpperBound(), definitionSupplier, reporter)); + kmType.getFlexibleTypeUpperBound(), factory, reporter)); } static List<KotlinTypeProjectionInfo> getArguments( - List<KmTypeProjection> projections, - DexDefinitionSupplier definitionSupplier, - Reporter reporter) { + List<KmTypeProjection> projections, DexItemFactory factory, Reporter reporter) { if (projections.isEmpty()) { return EMPTY_ARGUMENTS; } ImmutableList.Builder<KotlinTypeProjectionInfo> arguments = ImmutableList.builder(); for (KmTypeProjection projection : projections) { - arguments.add(KotlinTypeProjectionInfo.create(projection, definitionSupplier, reporter)); + arguments.add(KotlinTypeProjectionInfo.create(projection, factory, reporter)); } return arguments.build(); } @@ -107,4 +108,18 @@ } } } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + classifier.trace(definitionSupplier); + if (abbreviatedType != null) { + abbreviatedType.trace(definitionSupplier); + } + if (outerType != null) { + outerType.trace(definitionSupplier); + } + forEachApply(arguments, argument -> argument::trace, definitionSupplier); + flexibleTypeUpperBoundInfo.trace(definitionSupplier); + forEachApply(annotations, annotation -> annotation::trace, definitionSupplier); + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java index fd11c11..53c2eb2 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
@@ -4,10 +4,14 @@ package com.android.tools.r8.kotlin; +import static com.android.tools.r8.utils.FunctionUtils.forEachApply; + import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexDefinitionSupplier; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.EnqueuerMetadataTraceable; import com.android.tools.r8.utils.Reporter; import com.google.common.collect.ImmutableList; import java.util.List; @@ -19,7 +23,7 @@ import kotlinx.metadata.jvm.JvmTypeParameterExtensionVisitor; // Provides access to Kotlin information about a type-parameter. -public class KotlinTypeParameterInfo { +public class KotlinTypeParameterInfo implements EnqueuerMetadataTraceable { private static final List<KotlinTypeParameterInfo> EMPTY_TYPE_PARAMETERS = ImmutableList.of(); private static final List<KotlinTypeInfo> EMPTY_UPPER_BOUNDS = ImmutableList.of(); @@ -47,41 +51,36 @@ } private static KotlinTypeParameterInfo create( - KmTypeParameter kmTypeParameter, - DexDefinitionSupplier definitionSupplier, - Reporter reporter) { + KmTypeParameter kmTypeParameter, DexItemFactory factory, Reporter reporter) { return new KotlinTypeParameterInfo( kmTypeParameter.getFlags(), kmTypeParameter.getId(), kmTypeParameter.getName(), kmTypeParameter.getVariance(), - getUpperBounds(kmTypeParameter.getUpperBounds(), definitionSupplier, reporter), - KotlinAnnotationInfo.create( - JvmExtensionsKt.getAnnotations(kmTypeParameter), definitionSupplier)); + getUpperBounds(kmTypeParameter.getUpperBounds(), factory, reporter), + KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmTypeParameter), factory)); } static List<KotlinTypeParameterInfo> create( - List<KmTypeParameter> kmTypeParameters, - DexDefinitionSupplier definitionSupplier, - Reporter reporter) { + List<KmTypeParameter> kmTypeParameters, DexItemFactory factory, Reporter reporter) { if (kmTypeParameters.isEmpty()) { return EMPTY_TYPE_PARAMETERS; } ImmutableList.Builder<KotlinTypeParameterInfo> builder = ImmutableList.builder(); for (KmTypeParameter kmTypeParameter : kmTypeParameters) { - builder.add(create(kmTypeParameter, definitionSupplier, reporter)); + builder.add(create(kmTypeParameter, factory, reporter)); } return builder.build(); } private static List<KotlinTypeInfo> getUpperBounds( - List<KmType> upperBounds, DexDefinitionSupplier definitionSupplier, Reporter reporter) { + List<KmType> upperBounds, DexItemFactory factory, Reporter reporter) { if (upperBounds.isEmpty()) { return EMPTY_UPPER_BOUNDS; } ImmutableList.Builder<KotlinTypeInfo> builder = ImmutableList.builder(); for (KmType upperBound : upperBounds) { - builder.add(KotlinTypeInfo.create(upperBound, definitionSupplier, reporter)); + builder.add(KotlinTypeInfo.create(upperBound, factory, reporter)); } return builder.build(); } @@ -104,4 +103,10 @@ annotation.rewrite(extensionVisitor::visitAnnotation, appView, namingLens); } } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + forEachApply(originalUpperBounds, upperBound -> upperBound::trace, definitionSupplier); + forEachApply(annotations, annotation -> annotation::trace, definitionSupplier); + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java index 68da5db..9ab3de0 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
@@ -6,14 +6,16 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexDefinitionSupplier; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.EnqueuerMetadataTraceable; import com.android.tools.r8.utils.Reporter; import kotlinx.metadata.KmTypeProjection; import kotlinx.metadata.KmVariance; // Provides access to Kotlin information about the type projection of a type (arguments). -public class KotlinTypeProjectionInfo { +public class KotlinTypeProjectionInfo implements EnqueuerMetadataTraceable { final KmVariance variance; final KotlinTypeInfo typeInfo; @@ -24,12 +26,10 @@ } static KotlinTypeProjectionInfo create( - KmTypeProjection kmTypeProjection, - DexDefinitionSupplier definitionSupplier, - Reporter reporter) { + KmTypeProjection kmTypeProjection, DexItemFactory factory, Reporter reporter) { return new KotlinTypeProjectionInfo( kmTypeProjection.getVariance(), - KotlinTypeInfo.create(kmTypeProjection.getType(), definitionSupplier, reporter)); + KotlinTypeInfo.create(kmTypeProjection.getType(), factory, reporter)); } private boolean isStarProjection() { @@ -47,4 +47,11 @@ typeInfo.rewrite(flags -> visitorProvider.get(flags, variance), appView, namingLens); } } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + if (typeInfo != null) { + typeInfo.trace(definitionSupplier); + } + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java new file mode 100644 index 0000000..dd2be72 --- /dev/null +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
@@ -0,0 +1,108 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.kotlin; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexDefinitionSupplier; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexString; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.naming.NamingLens; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.EnqueuerMetadataTraceable; +import com.android.tools.r8.utils.DescriptorUtils; + +/** + * To account for invalid type references in kotlin metadata, the class KotlinTypeReference will + * either hold a DexType reference, or a String, with the original name reference, which is not a + * valid jvm descriptor/name. The values will be disjoint. + */ +class KotlinTypeReference implements EnqueuerMetadataTraceable { + + private final DexType known; + private final String unknown; + + private KotlinTypeReference(DexType known) { + this.known = known; + this.unknown = null; + assert known != null; + } + + private KotlinTypeReference(String unknown) { + this.known = null; + this.unknown = unknown; + assert unknown != null; + } + + static KotlinTypeReference fromBinaryName(String binaryName, DexItemFactory factory) { + if (DescriptorUtils.isValidBinaryName(binaryName)) { + return fromDescriptor( + DescriptorUtils.getDescriptorFromClassBinaryName(binaryName), factory, binaryName); + } + return new KotlinTypeReference(binaryName); + } + + static KotlinTypeReference fromDescriptor(String descriptor, DexItemFactory factory) { + return fromDescriptor(descriptor, factory, descriptor); + } + + static KotlinTypeReference fromDescriptor( + String descriptor, DexItemFactory factory, String unknownValue) { + if (DescriptorUtils.isDescriptor(descriptor)) { + DexType type = factory.createType(descriptor); + return new KotlinTypeReference(type); + } + return new KotlinTypeReference(unknownValue); + } + + String toRenamedDescriptorOrDefault( + AppView<AppInfoWithLiveness> appView, NamingLens namingLens, String defaultValue) { + if (unknown != null) { + return unknown; + } + assert known != null; + if (!known.isClassType()) { + return known.descriptor.toString(); + } + if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(known)) { + return defaultValue; + } + DexString descriptor = namingLens.lookupDescriptor(known); + if (descriptor != null) { + return descriptor.toString(); + } + return defaultValue; + } + + String toRenamedBinaryNameOrDefault( + AppView<AppInfoWithLiveness> appView, NamingLens namingLens, String defaultValue) { + if (unknown != null) { + // Unknown values are always on the input form, so we can just return it. + return unknown; + } + String descriptor = toRenamedDescriptorOrDefault(appView, namingLens, defaultValue); + if (descriptor == null) { + return null; + } + if (descriptor.equals(defaultValue)) { + // We assume that the default value passed in is already a binary name. + return descriptor; + } + return DescriptorUtils.getBinaryNameFromDescriptor(descriptor); + } + + @Override + public String toString() { + return known != null ? known.descriptor.toString() : unknown; + } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + if (known != null && known.isClassType()) { + // Lookup the definition, ignoring the result. This populates the sets in the Enqueuer. + definitionSupplier.definitionFor(known); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java index ee2f99b..83dd5d2 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
@@ -6,17 +6,20 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexDefinitionSupplier; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.EnqueuerMetadataTraceable; import com.android.tools.r8.utils.Reporter; import com.google.common.collect.ImmutableList; import java.util.List; import kotlinx.metadata.KmType; import kotlinx.metadata.KmValueParameter; import kotlinx.metadata.KmValueParameterVisitor; +import kotlinx.metadata.internal.metadata.deserialization.Flags; // Provides access to Kotlin information about value parameter. -class KotlinValueParameterInfo { +class KotlinValueParameterInfo implements EnqueuerMetadataTraceable { private static final List<KotlinValueParameterInfo> EMPTY_VALUE_PARAMETERS = ImmutableList.of(); // Original parameter name. final String name; @@ -35,10 +38,12 @@ this.varargElementType = varargElementType; } + boolean isCrossInline() { + return Flags.IS_CROSSINLINE.get(flags); + } + static KotlinValueParameterInfo create( - KmValueParameter kmValueParameter, - DexDefinitionSupplier definitionSupplier, - Reporter reporter) { + KmValueParameter kmValueParameter, DexItemFactory factory, Reporter reporter) { if (kmValueParameter == null) { return null; } @@ -46,21 +51,18 @@ return new KotlinValueParameterInfo( kmValueParameter.getFlags(), kmValueParameter.getName(), - KotlinTypeInfo.create(kmType, definitionSupplier, reporter), - KotlinTypeInfo.create( - kmValueParameter.getVarargElementType(), definitionSupplier, reporter)); + KotlinTypeInfo.create(kmType, factory, reporter), + KotlinTypeInfo.create(kmValueParameter.getVarargElementType(), factory, reporter)); } static List<KotlinValueParameterInfo> create( - List<KmValueParameter> parameters, - DexDefinitionSupplier definitionSupplier, - Reporter reporter) { + List<KmValueParameter> parameters, DexItemFactory factory, Reporter reporter) { if (parameters.isEmpty()) { return EMPTY_VALUE_PARAMETERS; } ImmutableList.Builder<KotlinValueParameterInfo> builder = ImmutableList.builder(); for (KmValueParameter parameter : parameters) { - builder.add(create(parameter, definitionSupplier, reporter)); + builder.add(create(parameter, factory, reporter)); } return builder.build(); } @@ -76,4 +78,12 @@ kmValueParameterVisitor::visitVarargElementType, appView, namingLens); } } + + @Override + public void trace(DexDefinitionSupplier definitionSupplier) { + type.trace(definitionSupplier); + if (varargElementType != null) { + varargElementType.trace(definitionSupplier); + } + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinVersionRequirementInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinVersionRequirementInfo.java new file mode 100644 index 0000000..9530f4c --- /dev/null +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinVersionRequirementInfo.java
@@ -0,0 +1,82 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.kotlin; + +import com.google.common.collect.ImmutableList; +import java.util.List; +import kotlinx.metadata.KmVersion; +import kotlinx.metadata.KmVersionRequirement; +import kotlinx.metadata.KmVersionRequirementLevel; +import kotlinx.metadata.KmVersionRequirementVersionKind; +import kotlinx.metadata.KmVersionRequirementVisitor; + +class KotlinVersionRequirementInfo { + + private static final KotlinVersionRequirementInfo NO_VERSION_REQUIREMENTS = + new KotlinVersionRequirementInfo(ImmutableList.of()); + + private final List<KotlinVersionRequirementPoint> versionRequirements; + + private KotlinVersionRequirementInfo(List<KotlinVersionRequirementPoint> versionRequirements) { + this.versionRequirements = versionRequirements; + } + + static KotlinVersionRequirementInfo create(List<KmVersionRequirement> kmVersionRequirements) { + if (kmVersionRequirements.isEmpty()) { + return NO_VERSION_REQUIREMENTS; + } + ImmutableList.Builder<KotlinVersionRequirementPoint> builder = ImmutableList.builder(); + for (KmVersionRequirement kmVersionRequirement : kmVersionRequirements) { + builder.add(KotlinVersionRequirementPoint.create(kmVersionRequirement)); + } + return new KotlinVersionRequirementInfo(builder.build()); + } + + public void rewrite(KmVisitorProviders.KmVersionRequirementVisitorProvider visitorProvider) { + if (this == NO_VERSION_REQUIREMENTS) { + return; + } + for (KotlinVersionRequirementPoint versionRequirement : versionRequirements) { + versionRequirement.rewrite(visitorProvider.get()); + } + } + + private static class KotlinVersionRequirementPoint { + + private final Integer errorCode; + private final KmVersionRequirementVersionKind kind; + private final KmVersionRequirementLevel level; + private final String message; + private final KmVersion version; + + private KotlinVersionRequirementPoint( + KmVersionRequirementVersionKind kind, + KmVersionRequirementLevel level, + Integer errorCode, + String message, + KmVersion version) { + this.errorCode = errorCode; + this.kind = kind; + this.level = level; + this.message = message; + this.version = version; + } + + private static KotlinVersionRequirementPoint create(KmVersionRequirement kmVersionRequirement) { + return new KotlinVersionRequirementPoint( + kmVersionRequirement.kind, + kmVersionRequirement.level, + kmVersionRequirement.getErrorCode(), + kmVersionRequirement.getMessage(), + kmVersionRequirement.version); + } + + private void rewrite(KmVersionRequirementVisitor visitor) { + visitor.visit(kind, level, errorCode, message); + visitor.visitVersion(version.getMajor(), version.getMinor(), version.getPatch()); + visitor.visitEnd(); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java index 8cffa05..b014c4f 100644 --- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java +++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -3,15 +3,19 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.naming; +import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; + import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.FieldAccessInfo; import com.android.tools.r8.graph.FieldAccessInfoCollection; +import com.android.tools.r8.graph.ProgramField; import com.android.tools.r8.graph.SubtypingInfo; import com.android.tools.r8.graph.TopDownClassHierarchyTraversal; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -178,9 +182,7 @@ getOrCreateReservedFieldNamingState(clazz.type); FieldNamingState state = parentState.createChildState(reservedNames); if (clazz.isProgramClass()) { - for (DexEncodedField field : clazz.fields()) { - renameField(field, state); - } + clazz.asProgramClass().forEachProgramField(field -> renameField(field, state)); } assert !states.containsKey(clazz.type); @@ -212,11 +214,14 @@ for (DexClass clazz : partition) { if (clazz.isProgramClass()) { assert clazz.isInterface(); - for (DexEncodedField field : clazz.fields()) { - DexString newName = renameField(field, state); - namesToBeReservedInImplementsSubclasses.markReservedDirectly( - newName, field.field.name, field.field.type); - } + clazz + .asProgramClass() + .forEachProgramField( + field -> { + DexString newName = renameField(field, state); + namesToBeReservedInImplementsSubclasses.markReservedDirectly( + newName, field.getReference().name, field.getReference().type); + }); } } @@ -236,11 +241,10 @@ } } - private DexString renameField(DexEncodedField encodedField, FieldNamingState state) { - DexField field = encodedField.field; + private DexString renameField(ProgramField field, FieldNamingState state) { DexString newName = state.getOrCreateNameFor(field); - if (newName != field.name) { - renaming.put(field, newName); + if (newName != field.getReference().name) { + renaming.put(field.getReference(), newName); } return newName; } @@ -256,32 +260,17 @@ } private void renameNonReboundAccessToField(DexField field) { - // Already renamed + // If the given field reference is a non-rebound reference to a program field, then assign the + // same name as the resolved field. if (renaming.containsKey(field)) { return; } - DexEncodedField definition = appView.definitionFor(field); - if (definition != null) { - assert definition.field == field; + DexProgramClass holder = asProgramClassOrNull(appView.definitionForHolder(field)); + if (holder == null) { return; } - // Now, `field` is reference. Find its definition and check if it's renamed. - DexClass holder = appView.definitionFor(field.holder); - // We don't care pruned types or library classes. - if (holder == null || holder.isNotProgramClass()) { - return; - } - definition = appView.appInfo().resolveField(field).getResolvedField(); - if (definition == null) { - // The program is already broken in the sense that it has an unresolvable field reference. - // Leave it as-is. - return; - } - assert definition.field != field; - assert definition.holder() != field.holder; - // If the definition is renamed, - if (renaming.containsKey(definition.field)) { - // Assign the same, renamed name as the definition to the reference. + DexEncodedField definition = appView.appInfo().resolveFieldOn(holder, field).getResolvedField(); + if (definition != null && definition.field != field && renaming.containsKey(definition.field)) { renaming.put(field, renaming.get(definition.field)); } }
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java index f8d80cb..35e3d3f 100644 --- a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java +++ b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
@@ -6,11 +6,9 @@ import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexEncodedField; -import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.ProgramField; import com.android.tools.r8.naming.FieldNamingState.InternalState; import java.util.IdentityHashMap; import java.util.Map; @@ -20,7 +18,7 @@ private final ReservedFieldNamingState reservedNames; private final MemberNamingStrategy strategy; - private final BiPredicate<DexString, DexField> isAvailable; + private final BiPredicate<DexString, ProgramField> isAvailable; public FieldNamingState( AppView<? extends AppInfoWithClassHierarchy> appView, MemberNamingStrategy strategy) { @@ -42,7 +40,8 @@ super(appView, internalStates); this.reservedNames = reservedNames; this.strategy = strategy; - this.isAvailable = (newName, field) -> !reservedNames.isReserved(newName, field.type); + this.isAvailable = + (newName, field) -> !reservedNames.isReserved(newName, field.getReference().type); } public FieldNamingState createChildState(ReservedFieldNamingState reservedNames) { @@ -52,20 +51,13 @@ return childState; } - public DexString getOrCreateNameFor(DexField field) { - DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField(); - if (encodedField != null) { - DexClass clazz = appView.definitionFor(encodedField.holder()); - if (clazz == null) { - return field.name; - } - DexString reservedName = strategy.getReservedName(encodedField, clazz); - if (reservedName != null) { - return reservedName; - } + public DexString getOrCreateNameFor(ProgramField field) { + DexString reservedName = strategy.getReservedName(field.getDefinition(), field.getHolder()); + if (reservedName != null) { + return reservedName; } // TODO(b/133208730) If we cannot resolve the field, are we then allowed to rename it? - return getOrCreateInternalState(field).createNewName(field); + return getOrCreateInternalState(field.getReference()).createNewName(field); } public void includeReservations(ReservedFieldNamingState reservedNames) { @@ -100,9 +92,9 @@ this.nextNameIndex = nextNameIndex; } - public DexString createNewName(DexField field) { + public DexString createNewName(ProgramField field) { DexString name = strategy.next(field, this, isAvailable); - assert !reservedNames.isReserved(name, field.type); + assert !reservedNames.isReserved(name, field.getReference().type); return name; }
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java index bacc7e0..cddbea8 100644 --- a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java +++ b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
@@ -7,9 +7,9 @@ import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexString; +import com.android.tools.r8.graph.ProgramField; import java.util.function.BiPredicate; public interface MemberNamingStrategy { @@ -20,9 +20,9 @@ BiPredicate<DexString, DexMethod> isAvailable); DexString next( - DexField field, + ProgramField field, InternalNamingState internalState, - BiPredicate<DexString, DexField> isAvailable); + BiPredicate<DexString, ProgramField> isAvailable); DexString getReservedName(DexEncodedMethod method, DexClass holder);
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java index 8f1a41a..d0f0381 100644 --- a/src/main/java/com/android/tools/r8/naming/Minifier.java +++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -11,11 +11,11 @@ import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.ProgramField; import com.android.tools.r8.graph.SubtypingInfo; import com.android.tools.r8.kotlin.KotlinMetadataRewriter; import com.android.tools.r8.naming.ClassNameMinifier.ClassNamingStrategy; @@ -240,10 +240,10 @@ @Override public DexString next( - DexField field, + ProgramField field, InternalNamingState internalState, - BiPredicate<DexString, DexField> isAvailable) { - assert checkAllowMemberRenaming(field.holder); + BiPredicate<DexString, ProgramField> isAvailable) { + assert checkAllowMemberRenaming(field.getHolderType()); DexString candidate; do { candidate = getNextName(internalState, false);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java index 3ce8e7f..e720b30 100644 --- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java +++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -16,6 +16,7 @@ import com.android.tools.r8.graph.DexReference; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.ProgramField; import com.android.tools.r8.graph.SubtypingInfo; import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter; import com.android.tools.r8.kotlin.KotlinMetadataRewriter; @@ -32,7 +33,6 @@ import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.Timing; -import com.android.tools.r8.utils.TriFunction; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.Maps; @@ -301,8 +301,9 @@ DexField originalField = ((FieldSignature) signature).toDexField(factory, type); addMemberNaming( originalField, memberNaming, addToAdditionalMaps ? additionalFieldNamings : null); - DexEncodedField encodedField = appView.definitionFor(originalField); - if (encodedField == null || !encodedField.accessFlags.isPrivate()) { + DexClass holder = appView.definitionForHolder(originalField); + DexEncodedField field = originalField.lookupOnClass(holder); + if (field == null || !field.isPrivate()) { nonPrivateMembers.put(originalField, memberNaming); } } @@ -465,62 +466,46 @@ @Override public DexString next( - DexMethod method, + DexMethod reference, InternalNamingState internalState, BiPredicate<DexString, DexMethod> isAvailable) { - DexEncodedMethod definition = appView.definitionFor(method); - DexString nextName = - nextName( - method, - definition, - method.name, - method.holder, - internalState, - isAvailable, - super::next); - assert nextName == method.name || !definition.isClassInitializer(); - assert nextName == method.name - || !appView.definitionFor(method.holder).accessFlags.isAnnotation(); + DexClass holder = appView.definitionForHolder(reference); + assert holder != null; + DexEncodedMethod method = holder.lookupMethod(reference); + DexString reservedName = getReservedName(method, reference.name, holder); + DexString nextName; + if (reservedName != null) { + if (!isAvailable.test(reservedName, reference)) { + reportReservationError(reference, reservedName); + } + nextName = reservedName; + } else { + assert !mappedNames.containsKey(reference); + assert appView.rootSet().mayBeMinified(reference, appView); + nextName = super.next(reference, internalState, isAvailable); + } + assert nextName == reference.name || !method.isInitializer(); + assert nextName == reference.name || !holder.isAnnotation(); return nextName; } @Override public DexString next( - DexField field, + ProgramField field, InternalNamingState internalState, - BiPredicate<DexString, DexField> isAvailable) { - return nextName( - field, - appView.definitionFor(field), - field.name, - field.holder, - internalState, - isAvailable, - super::next); - } - - private <T extends DexReference> DexString nextName( - T reference, - DexDefinition definition, - DexString name, - DexType holderType, - InternalNamingState internalState, - BiPredicate<DexString, T> isAvailable, - TriFunction<T, InternalNamingState, BiPredicate<DexString, T>, DexString> generateName) { - assert definition.isDexEncodedMethod() || definition.isDexEncodedField(); - assert definition.toReference() == reference; - DexClass holder = appView.definitionFor(holderType); - assert holder != null; - DexString reservedName = getReservedName(definition, name, holder); + BiPredicate<DexString, ProgramField> isAvailable) { + DexField reference = field.getReference(); + DexString reservedName = + getReservedName(field.getDefinition(), reference.name, field.getHolder()); if (reservedName != null) { - if (!isAvailable.test(reservedName, reference)) { - reportReservationError(definition.toReference(), reservedName); + if (!isAvailable.test(reservedName, field)) { + reportReservationError(reference, reservedName); } return reservedName; } assert !mappedNames.containsKey(reference); assert appView.rootSet().mayBeMinified(reference, appView); - return generateName.apply(reference, internalState, isAvailable); + return super.next(field, internalState, isAvailable); } @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java index 31e239b..54decce 100644 --- a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java +++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
@@ -254,8 +254,9 @@ // Remove all of the bridges in the eligible subclasses. for (DexProgramClass subclass : eligibleSubclasses) { + assert !appView.appInfo().isPinned(method); DexEncodedMethod removed = subclass.removeMethod(method); - assert removed != null && !appView.appInfo().isPinned(removed.method); + assert removed != null; } }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java index b992f8d..1c1de57 100644 --- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java +++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -347,9 +347,9 @@ computeMethodRebinding(appInfo.directInvokes, this::anyLookup, Type.DIRECT); // Likewise static invokes. computeMethodRebinding(appInfo.staticInvokes, this::anyLookup, Type.STATIC); - computeFieldRebinding(); - - return builder.build(lense); + GraphLense lens = builder.build(lense); + appInfo.getFieldAccessInfoCollection().flattenAccessContexts(); + return lens; } }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java index 95e2772..01c923d 100644 --- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java +++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
@@ -15,7 +15,6 @@ import java.util.Collections; import java.util.IdentityHashMap; import java.util.Map; -import java.util.Set; public class MemberRebindingLense extends NestedGraphLense { @@ -99,11 +98,6 @@ } @Override - public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) { - return previousLense.lookupMethodInAllContexts(method); - } - - @Override protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) { return super.mapVirtualInterfaceInvocationTypes(appView, newMethod, originalMethod, type); }
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java index 584b84b..3c0d9dd 100644 --- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java +++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -258,9 +258,9 @@ } private boolean enclosingMethodPinned(DexClass clazz) { - return clazz.getEnclosingMethod() != null - && clazz.getEnclosingMethod().getEnclosingClass() != null - && appView.appInfo().isPinned(clazz.getEnclosingMethod().getEnclosingClass()); + return clazz.getEnclosingMethodAttribute() != null + && clazz.getEnclosingMethodAttribute().getEnclosingClass() != null + && appView.appInfo().isPinned(clazz.getEnclosingMethodAttribute().getEnclosingClass()); } private static boolean hasInnerClassesFromSet(DexProgramClass clazz, Set<DexType> innerClasses) { @@ -292,7 +292,7 @@ } if (keptAnyway || keepForThisInnerClass || keepForThisEnclosingClass) { if (!keep.enclosingMethod) { - clazz.clearEnclosingMethod(); + clazz.clearEnclosingMethodAttribute(); } if (!keep.innerClasses) { clazz.clearInnerClasses(); @@ -305,14 +305,15 @@ if (appView.appInfo().isPinned(ica.getInner())) { return false; } - if (appView.appInfo().isPinned(ica.getOuter())) { + DexType outer = ica.getOuter(); + if (outer != null && appView.appInfo().isPinned(outer)) { return false; } if (finalKeepForThisInnerClass && ica.getInner() == clazz.type) { return false; } if (finalKeepForThisEnclosingClass - && ica.getOuter() == clazz.type + && outer == clazz.type && classesToRetainInnerClassAttributeFor.contains(ica.getInner())) { return false; } @@ -322,7 +323,7 @@ } else { // These attributes are only relevant for reflection, and this class is not used for // reflection. (Note that clearing these attributes can enable more vertical class merging.) - clazz.clearEnclosingMethod(); + clazz.clearEnclosingMethodAttribute(); clazz.clearInnerClasses(); } }
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 275b882..87bfd9a 100644 --- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java +++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -5,7 +5,6 @@ import static com.android.tools.r8.graph.DexEncodedMethod.asProgramMethodOrNull; import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; -import static com.android.tools.r8.graph.GraphLense.rewriteReferenceKeys; import static com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult.isOverriding; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; @@ -27,6 +26,7 @@ import com.android.tools.r8.graph.FieldAccessInfoCollection; import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl; import com.android.tools.r8.graph.FieldAccessInfoImpl; +import com.android.tools.r8.graph.FieldResolutionResult; import com.android.tools.r8.graph.GraphLense; import com.android.tools.r8.graph.GraphLense.NestedGraphLense; import com.android.tools.r8.graph.InstantiatedSubTypeInfo; @@ -44,6 +44,7 @@ import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter; import com.android.tools.r8.ir.desugar.LambdaDescriptor; import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter; +import com.android.tools.r8.utils.AssertionUtils; import com.android.tools.r8.utils.CollectionUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ListUtils; @@ -51,9 +52,6 @@ import com.android.tools.r8.utils.TraversalContinuation; import com.android.tools.r8.utils.Visibility; import com.android.tools.r8.utils.collections.ProgramMethodSet; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ImmutableSortedSet; -import com.google.common.collect.ImmutableSortedSet.Builder; import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; import it.unimi.dsi.fastutil.objects.Object2BooleanMap; @@ -63,13 +61,11 @@ import java.util.Deque; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.function.Consumer; -import java.util.function.Function; import java.util.stream.Collectors; /** Encapsulates liveness and reachability information for an application. */ @@ -136,10 +132,8 @@ * will have been removed from the code. */ public final Set<DexCallSite> callSites; - /** Set of all items that have to be kept independent of whether they are used. */ - final Set<DexReference> pinnedItems; - /** Set of kept items that are allowed to be publicized. */ - final Set<DexReference> allowAccessModification; + /** Collection of keep requirements for the program. */ + private final KeepInfoCollection keepInfo; /** All items with assumemayhavesideeffects rule. */ public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects; /** All items with assumenosideeffects rule. */ @@ -217,8 +211,7 @@ SortedMap<DexMethod, ProgramMethodSet> directInvokes, SortedMap<DexMethod, ProgramMethodSet> staticInvokes, Set<DexCallSite> callSites, - Set<DexReference> pinnedItems, - Set<DexReference> allowAccessModification, + KeepInfoCollection keepInfo, Map<DexReference, ProguardMemberRule> mayHaveSideEffects, Map<DexReference, ProguardMemberRule> noSideEffects, Map<DexReference, ProguardMemberRule> assumedValues, @@ -253,8 +246,7 @@ this.liveMethods = liveMethods; this.fieldAccessInfoCollection = fieldAccessInfoCollection; this.objectAllocationInfoCollection = objectAllocationInfoCollection; - this.pinnedItems = pinnedItems; - this.allowAccessModification = allowAccessModification; + this.keepInfo = keepInfo; this.mayHaveSideEffects = mayHaveSideEffects; this.noSideEffects = noSideEffects; this.assumedValues = assumedValues; @@ -304,8 +296,7 @@ SortedMap<DexMethod, ProgramMethodSet> directInvokes, SortedMap<DexMethod, ProgramMethodSet> staticInvokes, Set<DexCallSite> callSites, - Set<DexReference> pinnedItems, - Set<DexReference> allowAccessModification, + KeepInfoCollection keepInfo, Map<DexReference, ProguardMemberRule> mayHaveSideEffects, Map<DexReference, ProguardMemberRule> noSideEffects, Map<DexReference, ProguardMemberRule> assumedValues, @@ -340,8 +331,7 @@ this.liveMethods = liveMethods; this.fieldAccessInfoCollection = fieldAccessInfoCollection; this.objectAllocationInfoCollection = objectAllocationInfoCollection; - this.pinnedItems = pinnedItems; - this.allowAccessModification = allowAccessModification; + this.keepInfo = keepInfo; this.mayHaveSideEffects = mayHaveSideEffects; this.noSideEffects = noSideEffects; this.assumedValues = assumedValues; @@ -392,8 +382,7 @@ previous.directInvokes, previous.staticInvokes, previous.callSites, - previous.pinnedItems, - previous.allowAccessModification, + previous.keepInfo, previous.mayHaveSideEffects, previous.noSideEffects, previous.assumedValues, @@ -443,10 +432,7 @@ previous.directInvokes, previous.staticInvokes, previous.callSites, - additionalPinnedItems == null - ? previous.pinnedItems - : CollectionUtils.mergeSets(previous.pinnedItems, additionalPinnedItems), - previous.allowAccessModification, + extendPinnedItems(previous, additionalPinnedItems), previous.mayHaveSideEffects, previous.noSideEffects, previous.assumedValues, @@ -471,7 +457,44 @@ previous.constClassReferences, previous.initClassReferences); copyMetadataFromPrevious(previous); - assert removedClasses == null || assertNoItemRemoved(previous.pinnedItems, removedClasses); + assert keepInfo.verifyNoneArePinned(removedClasses, previous); + } + + private static KeepInfoCollection extendPinnedItems( + AppInfoWithLiveness previous, Collection<DexReference> additionalPinnedItems) { + if (additionalPinnedItems == null || additionalPinnedItems.isEmpty()) { + return previous.keepInfo; + } + return previous.keepInfo.mutate( + collection -> { + for (DexReference reference : additionalPinnedItems) { + if (reference.isDexType()) { + DexProgramClass clazz = + asProgramClassOrNull(previous.definitionFor(reference.asDexType())); + if (clazz != null) { + collection.pinClass(clazz); + } + } else if (reference.isDexMethod()) { + DexMethod method = reference.asDexMethod(); + DexProgramClass clazz = asProgramClassOrNull(previous.definitionFor(method.holder)); + if (clazz != null) { + DexEncodedMethod definition = clazz.lookupMethod(method); + if (definition != null) { + collection.pinMethod(clazz, definition); + } + } + } else { + DexField field = reference.asDexField(); + DexProgramClass clazz = asProgramClassOrNull(previous.definitionFor(field.holder)); + if (clazz != null) { + DexEncodedField definition = clazz.lookupField(field); + if (definition != null) { + collection.pinField(clazz, definition); + } + } + } + } + }); } public AppInfoWithLiveness( @@ -491,8 +514,7 @@ this.liveMethods = previous.liveMethods; this.fieldAccessInfoCollection = previous.fieldAccessInfoCollection; this.objectAllocationInfoCollection = previous.objectAllocationInfoCollection; - this.pinnedItems = previous.pinnedItems; - this.allowAccessModification = previous.allowAccessModification; + this.keepInfo = previous.keepInfo; this.mayHaveSideEffects = previous.mayHaveSideEffects; this.noSideEffects = previous.noSideEffects; this.assumedValues = previous.assumedValues; @@ -543,8 +565,6 @@ || TwrCloseResourceRewriter.isUtilityClassDescriptor(type) // TODO(b/150736225): Not sure how to remove these. || DesugaredLibraryAPIConverter.isVivifiedType(type) - // TODO(b/149363884): Handle references to dead proto builders. - || type.toDescriptorString().endsWith("$Builder;") : "Failed lookup of non-missing type: " + type; return definition; } @@ -754,23 +774,6 @@ singleTargetLookupCache.removeInstantiatedType(clazz.type, this); } - private boolean assertNoItemRemoved(Collection<DexReference> items, Collection<DexType> types) { - Set<DexType> typeSet = ImmutableSet.copyOf(types); - for (DexReference item : items) { - DexType typeToCheck; - if (item.isDexType()) { - typeToCheck = item.asDexType(); - } else if (item.isDexMethod()) { - typeToCheck = item.asDexMethod().holder; - } else { - assert item.isDexField(); - typeToCheck = item.asDexField().holder; - } - assert !typeSet.contains(typeToCheck); - } - return true; - } - private boolean isInstantiatedDirectly(DexProgramClass clazz) { assert checkIfObsolete(); DexType type = clazz.type; @@ -797,7 +800,7 @@ if (info != null && info.isRead()) { return true; } - return isPinned(field) + return keepInfo.isPinned(field, this) // Fields in the class that is synthesized by D8/R8 would be used soon. || field.holder.isD8R8SynthesizedClassType() // For library classes we don't know whether a field is read. @@ -869,7 +872,7 @@ return method.getDefinition().hasCode() && !method.getDefinition().isLibraryMethodOverride().isPossiblyTrue() && !neverReprocess.contains(reference) - && !pinnedItems.contains(reference); + && !keepInfo.getMethodInfo(method).isPinned(); } public boolean mayPropagateValueFor(DexReference reference) { @@ -884,27 +887,15 @@ return holder == null || holder.isLibraryClass() || holder.isClasspathClass(); } - private static <T extends PresortedComparable<T>> ImmutableSortedSet<T> rewriteItems( - Set<T> original, Function<T, T> rewrite) { - Builder<T> builder = new Builder<>(PresortedComparable::slowCompare); - for (T item : original) { - builder.add(rewrite.apply(item)); - } - return builder.build(); - } - - private static <T extends PresortedComparable<T>> - SortedMap<T, ProgramMethodSet> rewriteKeysConservativelyWhileMergingValues( - Map<T, ProgramMethodSet> original, Function<T, Set<T>> rewrite) { - SortedMap<T, ProgramMethodSet> result = new TreeMap<>(PresortedComparable::slowCompare); - for (T item : original.keySet()) { - Set<T> rewrittenKeys = rewrite.apply(item); - for (T rewrittenKey : rewrittenKeys) { - result - .computeIfAbsent(rewrittenKey, k -> ProgramMethodSet.create()) - .addAll(original.get(item)); - } - } + private static SortedMap<DexMethod, ProgramMethodSet> rewriteInvokesWithContexts( + Map<DexMethod, ProgramMethodSet> invokes, GraphLense lens) { + SortedMap<DexMethod, ProgramMethodSet> result = new TreeMap<>(PresortedComparable::slowCompare); + invokes.forEach( + (method, contexts) -> + result + .computeIfAbsent( + lens.getRenamedMethodSignature(method), ignore -> ProgramMethodSet.create()) + .addAll(contexts)); return Collections.unmodifiableSortedMap(result); } @@ -927,12 +918,14 @@ public boolean isAccessModificationAllowed(DexReference reference) { assert options().getProguardConfiguration().isAccessModificationAllowed(); - return allowAccessModification.contains(reference) || !isPinned(reference); + return keepInfo + .getInfo(reference, this) + .isAccessModificationAllowed(options().getProguardConfiguration()); } public boolean isPinned(DexReference reference) { assert checkIfObsolete(); - return pinnedItems.contains(reference); + return keepInfo.isPinned(reference, this); } public boolean hasPinnedInstanceInitializer(DexType type) { @@ -948,9 +941,8 @@ return false; } - public Set<DexReference> getPinnedItems() { - assert checkIfObsolete(); - return pinnedItems; + public KeepInfoCollection getKeepInfo() { + return keepInfo; } /** @@ -984,70 +976,62 @@ // Switchmap classes should never be affected by renaming. assert lens.assertDefinitionsNotModified( switchMaps.keySet().stream() - .map(this::definitionFor) - .filter(Objects::nonNull) + .map(this::resolveField) + .filter(FieldResolutionResult::isSuccessfulResolution) + .map(FieldResolutionResult::getResolvedField) .collect(Collectors.toList())); assert lens.assertDefinitionsNotModified( neverMerge.stream() .map(this::definitionFor) - .filter(Objects::nonNull) - .collect(Collectors.toList())); - - assert lens.assertDefinitionsNotModified( - alwaysInline.stream() - .map(this::definitionFor) - .filter(Objects::nonNull) + .filter(AssertionUtils::assertNotNull) .collect(Collectors.toList())); return new AppInfoWithLiveness( application, deadProtoTypes, missingTypes, - rewriteItems(liveTypes, lens::lookupType), - rewriteItems(instantiatedAppServices, lens::lookupType), - lens.rewriteMethodsConservatively(targetedMethods), - lens.rewriteMethodsConservatively(failedResolutionTargets), - lens.rewriteMethodsConservatively(bootstrapMethods), - lens.rewriteMethodsConservatively(methodsTargetedByInvokeDynamic), - lens.rewriteMethodsConservatively(virtualMethodsTargetedByInvokeDirect), - lens.rewriteMethodsConservatively(liveMethods), + lens.rewriteTypes(liveTypes), + lens.rewriteTypes(instantiatedAppServices), + lens.rewriteMethods(targetedMethods), + lens.rewriteMethods(failedResolutionTargets), + lens.rewriteMethods(bootstrapMethods), + lens.rewriteMethods(methodsTargetedByInvokeDynamic), + lens.rewriteMethods(virtualMethodsTargetedByInvokeDirect), + lens.rewriteMethods(liveMethods), fieldAccessInfoCollection.rewrittenWithLens(application, lens), objectAllocationInfoCollection.rewrittenWithLens(application, lens), - rewriteKeysConservativelyWhileMergingValues( - virtualInvokes, lens::lookupMethodInAllContexts), - rewriteKeysConservativelyWhileMergingValues( - interfaceInvokes, lens::lookupMethodInAllContexts), - rewriteKeysConservativelyWhileMergingValues(superInvokes, lens::lookupMethodInAllContexts), - rewriteKeysConservativelyWhileMergingValues(directInvokes, lens::lookupMethodInAllContexts), - rewriteKeysConservativelyWhileMergingValues(staticInvokes, lens::lookupMethodInAllContexts), + rewriteInvokesWithContexts(virtualInvokes, lens), + rewriteInvokesWithContexts(interfaceInvokes, lens), + rewriteInvokesWithContexts(superInvokes, lens), + rewriteInvokesWithContexts(directInvokes, lens), + rewriteInvokesWithContexts(staticInvokes, lens), // TODO(sgjesse): Rewrite call sites as well? Right now they are only used by minification // after second tree shaking. callSites, - lens.rewriteReferencesConservatively(pinnedItems), - lens.rewriteReferencesConservatively(allowAccessModification), - rewriteReferenceKeys(mayHaveSideEffects, lens::lookupReference), - rewriteReferenceKeys(noSideEffects, lens::lookupReference), - rewriteReferenceKeys(assumedValues, lens::lookupReference), - lens.rewriteMethodsWithRenamedSignature(alwaysInline), - lens.rewriteMethodsWithRenamedSignature(forceInline), - lens.rewriteMethodsWithRenamedSignature(neverInline), - lens.rewriteMethodsWithRenamedSignature(whyAreYouNotInlining), - lens.rewriteMethodsWithRenamedSignature(keepConstantArguments), - lens.rewriteMethodsWithRenamedSignature(keepUnusedArguments), - lens.rewriteMethodsWithRenamedSignature(reprocess), - lens.rewriteMethodsWithRenamedSignature(neverReprocess), + keepInfo.rewrite(lens), + lens.rewriteReferenceKeys(mayHaveSideEffects), + lens.rewriteReferenceKeys(noSideEffects), + lens.rewriteReferenceKeys(assumedValues), + lens.rewriteMethods(alwaysInline), + lens.rewriteMethods(forceInline), + lens.rewriteMethods(neverInline), + lens.rewriteMethods(whyAreYouNotInlining), + lens.rewriteMethods(keepConstantArguments), + lens.rewriteMethods(keepUnusedArguments), + lens.rewriteMethods(reprocess), + lens.rewriteMethods(neverReprocess), alwaysClassInline.rewriteItems(lens::lookupType), - rewriteItems(neverClassInline, lens::lookupType), - rewriteItems(neverMerge, lens::lookupType), - lens.rewriteReferencesConservatively(neverPropagateValue), - lens.rewriteReferencesConservatively(identifierNameStrings), + lens.rewriteTypes(neverClassInline), + lens.rewriteTypes(neverMerge), + lens.rewriteReferences(neverPropagateValue), + lens.rewriteReferenceKeys(identifierNameStrings), // Don't rewrite pruned types - the removed types are identified by their original name. prunedTypes, - rewriteReferenceKeys(switchMaps, lens::lookupField), + lens.rewriteFieldKeys(switchMaps), enumValueInfoMaps.rewrittenWithLens(lens), - rewriteItems(constClassReferences, lens::lookupType), - rewriteReferenceKeys(initClassReferences, lens::lookupType)); + lens.rewriteTypes(constClassReferences), + lens.rewriteTypeKeys(initClassReferences)); } /**
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 8a54cca..a0a1924 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -92,7 +92,9 @@ import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction; import com.android.tools.r8.shaking.EnqueuerWorklist.EnqueuerAction; import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness; +import com.android.tools.r8.shaking.KeepInfoCollection.MutableKeepInfoCollection; import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet; +import com.android.tools.r8.shaking.RootSetBuilder.ItemsWithRules; import com.android.tools.r8.shaking.RootSetBuilder.RootSet; import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult; import com.android.tools.r8.utils.Action; @@ -237,7 +239,7 @@ private final Set<DexType> missingTypes = Sets.newIdentityHashSet(); /** Set of proto types that were found to be dead during the first round of tree shaking. */ - private Set<DexType> initialDeadProtoTypes; + private Set<DexType> initialDeadProtoTypes = Sets.newIdentityHashSet(); /** Set of types that were found to be missing during the first round of tree shaking. */ private Set<DexType> initialMissingTypes; @@ -305,18 +307,8 @@ */ private final Set<DexReference> reportedMissing = Sets.newIdentityHashSet(); - /** - * A set of references that we are keeping due to keep rules. This may differ from the root set - * due to dependent keep rules. - */ - private final Set<DexReference> pinnedItems = Sets.newIdentityHashSet(); - - /** - * A set of references that we are keeping due to keep rules, which we are allowed to publicize. - */ - // TODO(b/156715504): This should be maintained in a structure that describes what we are allowed - // and not allowed to do with program items that are referenced from keep rules. - private final Map<DexReference, OptionalBool> allowAccessModification = new IdentityHashMap<>(); + /** Collection of keep requirements for the program. */ + private final MutableKeepInfoCollection keepInfo = new MutableKeepInfoCollection(); /** * A set of seen const-class references that both serve as an initial lock-candidate set and will @@ -374,10 +366,15 @@ this.useRegistryFactory = createUseRegistryFactory(); this.workList = EnqueuerWorklist.createWorklist(appView); - if (options.protoShrinking().enableGeneratedMessageLiteShrinking - && mode.isInitialOrFinalTreeShaking()) { - registerAnalysis(new ProtoEnqueuerExtension(appView)); + if (mode.isInitialOrFinalTreeShaking()) { + if (options.protoShrinking().enableGeneratedMessageLiteShrinking) { + registerAnalysis(new ProtoEnqueuerExtension(appView)); + } + appView.withGeneratedMessageLiteBuilderShrinker( + shrinker -> registerAnalysis(shrinker.createEnqueuerAnalysis())); } + + liveTypes = new SetWithReportedReason<>(); initializedTypes = new SetWithReportedReason<>(); targetedMethods = new SetWithReason<>(graphReporter::registerMethod); @@ -507,25 +504,6 @@ recordTypeReference(field.type); } - public DexDefinition definitionFor(DexReference reference) { - if (reference.isDexType()) { - return definitionFor(reference.asDexType()); - } else if (reference.isDexMethod()) { - return definitionFor(reference.asDexMethod()); - } else { - assert reference.isDexField(); - return definitionFor(reference.asDexField()); - } - } - - public DexEncodedField definitionFor(DexField field) { - DexClass clazz = definitionFor(field.holder); - if (clazz == null) { - return null; - } - return clazz.lookupField(field); - } - public DexEncodedMethod definitionFor(DexMethod method) { DexClass clazz = definitionFor(method.holder); if (clazz == null) { @@ -546,6 +524,10 @@ return clazz; } + public boolean isPinned(DexType type) { + return keepInfo.isPinned(type, appInfo); + } + private void addLiveNonProgramType(DexClass clazz) { assert clazz.isNotProgramClass(); // Fast path to avoid the worklist when the class is already seen. @@ -614,24 +596,98 @@ } } - private static <T> SetWithReason<T> newSetWithoutReasonReporter() { - return new SetWithReason<>((f, r) -> {}); - } - private void enqueueRootItems(Map<DexReference, Set<ProguardKeepRuleBase>> items) { items.entrySet().forEach(this::enqueueRootItem); } + private void enqueueRootItems(ItemsWithRules items) { + items.forEachField(this::enqueueRootField); + items.forEachMethod(this::enqueueRootMethod); + items.forEachClass(this::enqueueRootClass); + } + private void enqueueRootItem(Entry<DexReference, Set<ProguardKeepRuleBase>> root) { - DexDefinition item = appView.definitionFor(root.getKey()); - if (item != null) { - enqueueRootItem(item, root.getValue()); + DexReference reference = root.getKey(); + Set<ProguardKeepRuleBase> rules = root.getValue(); + if (reference.isDexField()) { + enqueueRootField(reference.asDexField(), rules); + } else if (reference.isDexMethod()) { + enqueueRootMethod(reference.asDexMethod(), rules); + } else if (reference.isDexType()) { + enqueueRootClass(reference.asDexType(), rules); } else { - // TODO(b/123923324): Verify that root items are present. - // assert false : "Expected root item `" + root.getKey().toSourceString() + "` to be present"; + throw new Unreachable(); } } + // TODO(b/123923324): Verify that root items are present. + private void enqueueRootClass(DexType type, Set<ProguardKeepRuleBase> rules) { + DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type)); + if (clazz != null) { + enqueueRootClass(clazz, rules, null); + } + } + + private void enqueueRootClass( + DexProgramClass clazz, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) { + KeepReasonWitness witness = graphReporter.reportKeepClass(precondition, rules, clazz); + keepClassWithRules(clazz, rules); + if (clazz.isAnnotation()) { + workList.enqueueMarkAnnotationInstantiatedAction(clazz, witness); + } else if (clazz.isInterface()) { + workList.enqueueMarkInterfaceInstantiatedAction(clazz, witness); + } else { + workList.enqueueMarkInstantiatedAction(clazz, null, InstantiationReason.KEEP_RULE, witness); + if (clazz.hasDefaultInitializer()) { + ProgramMethod defaultInitializer = clazz.getProgramDefaultInitializer(); + if (forceProguardCompatibility) { + workList.enqueueMarkMethodKeptAction( + defaultInitializer, + graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer)); + } + if (clazz.isExternalizable(appView)) { + enqueueMarkMethodLiveAction(defaultInitializer, witness); + } + } + } + } + + // TODO(b/123923324): Verify that root items are present. + private void enqueueRootField(DexField reference, Set<ProguardKeepRuleBase> rules) { + DexProgramClass holder = getProgramClassOrNull(reference.holder); + if (holder != null) { + ProgramField field = holder.lookupProgramField(reference); + if (field != null) { + enqueueRootField(field, rules, null); + } + } + } + + private void enqueueRootField( + ProgramField field, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) { + keepFieldWithRules(field.getHolder(), field.getDefinition(), rules); + workList.enqueueMarkFieldKeptAction( + field, graphReporter.reportKeepField(precondition, rules, field.getDefinition())); + } + + // TODO(b/123923324): Verify that root items are present. + private void enqueueRootMethod(DexMethod reference, Set<ProguardKeepRuleBase> rules) { + DexProgramClass holder = getProgramClassOrNull(reference.holder); + if (holder != null) { + ProgramMethod method = holder.lookupProgramMethod(reference); + if (method != null) { + enqueueRootMethod(method, rules, null); + } + } + } + + private void enqueueRootMethod( + ProgramMethod method, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) { + keepMethodWithRules(method.getHolder(), method.getDefinition(), rules); + workList.enqueueMarkMethodKeptAction( + method, graphReporter.reportKeepMethod(precondition, rules, method.getDefinition())); + } + private void enqueueRootItem(DexDefinition item, Set<ProguardKeepRuleBase> rules) { internalEnqueueRootItem(item, rules, null); } @@ -640,45 +696,24 @@ DexDefinition item, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) { if (item.isDexClass()) { DexProgramClass clazz = item.asDexClass().asProgramClass(); - KeepReasonWitness witness = graphReporter.reportKeepClass(precondition, rules, clazz); - if (clazz.isAnnotation()) { - workList.enqueueMarkAnnotationInstantiatedAction(clazz, witness); - } else if (clazz.isInterface()) { - workList.enqueueMarkInterfaceInstantiatedAction(clazz, witness); - } else { - workList.enqueueMarkInstantiatedAction(clazz, null, InstantiationReason.KEEP_RULE, witness); - if (clazz.hasDefaultInitializer()) { - ProgramMethod defaultInitializer = clazz.getProgramDefaultInitializer(); - if (forceProguardCompatibility) { - workList.enqueueMarkMethodKeptAction( - defaultInitializer, - graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer)); - } - if (clazz.isExternalizable(appView)) { - enqueueMarkMethodLiveAction(defaultInitializer, witness); - } - } + if (clazz != null) { + enqueueRootClass(clazz, rules, precondition); } } else if (item.isDexEncodedField()) { DexEncodedField field = item.asDexEncodedField(); DexProgramClass holder = getProgramClassOrNull(field.holder()); if (holder != null) { - workList.enqueueMarkFieldKeptAction( - new ProgramField(holder, field), - graphReporter.reportKeepField(precondition, rules, field)); + enqueueRootField(new ProgramField(holder, field), rules, precondition); } } else if (item.isDexEncodedMethod()) { - DexEncodedMethod encodedMethod = item.asDexEncodedMethod(); - DexProgramClass holder = getProgramClassOrNull(encodedMethod.holder()); + DexEncodedMethod method = item.asDexEncodedMethod(); + DexProgramClass holder = getProgramClassOrNull(method.holder()); if (holder != null) { - workList.enqueueMarkMethodKeptAction( - new ProgramMethod(holder, encodedMethod), - graphReporter.reportKeepMethod(precondition, rules, encodedMethod)); + enqueueRootMethod(new ProgramMethod(holder, method), rules, precondition); } } else { throw new IllegalArgumentException(item.toString()); } - addPinnedItem(item.toReference(), rules); } private void enqueueFirstNonSerializableClassInitializer( @@ -1314,7 +1349,7 @@ boolean skipTracing = appView.withGeneratedExtensionRegistryShrinker( shrinker -> - shrinker.isDeadProtoExtensionField(field, fieldAccessInfoCollection, pinnedItems), + shrinker.isDeadProtoExtensionField(field, fieldAccessInfoCollection, keepInfo), false); if (skipTracing) { addDeadProtoTypeCandidate(field.getHolder()); @@ -1373,7 +1408,7 @@ boolean skipTracing = appView.withGeneratedExtensionRegistryShrinker( shrinker -> - shrinker.isDeadProtoExtensionField(field, fieldAccessInfoCollection, pinnedItems), + shrinker.isDeadProtoExtensionField(field, fieldAccessInfoCollection, keepInfo), false); if (skipTracing) { addDeadProtoTypeCandidate(field.getHolder()); @@ -1482,7 +1517,7 @@ recordTypeReference(innerClassAttribute.getInner()); recordTypeReference(innerClassAttribute.getOuter()); } - EnclosingMethodAttribute enclosingMethodAttribute = holder.getEnclosingMethod(); + EnclosingMethodAttribute enclosingMethodAttribute = holder.getEnclosingMethodAttribute(); if (enclosingMethodAttribute != null) { DexMethod enclosingMethod = enclosingMethodAttribute.getEnclosingMethod(); if (enclosingMethod != null) { @@ -1619,8 +1654,8 @@ } private void enqueueHolderWithDependentInstanceConstructor( - DexProgramClass clazz, ProgramMethod instanceInitializer, Set<ProguardKeepRuleBase> reasons) { - enqueueRootItem(clazz, reasons); + ProgramMethod instanceInitializer, Set<ProguardKeepRuleBase> reasons) { + enqueueRootItem(instanceInitializer.getHolder(), reasons); } private void processAnnotations(DexProgramClass holder, DexDefinition annotatedItem) { @@ -1808,12 +1843,12 @@ holder, (dexType, ignored) -> { if (holder.isProgramClass()) { - DexReference holderReference = holder.toReference(); - addPinnedItem(holderReference); - rootSet.shouldNotBeMinified(holderReference); + DexProgramClass holderClass = holder.asProgramClass(); + keepInfo.keepClass(holderClass); + rootSet.shouldNotBeMinified(holder.toReference()); for (DexEncodedMember<?, ?> member : holder.members()) { + keepInfo.keepMember(holderClass, member); DexMember<?, ?> memberReference = member.toReference(); - addPinnedItem(memberReference); rootSet.shouldNotBeMinified(memberReference); } } @@ -1841,7 +1876,7 @@ private void reportMissingClass(DexType clazz) { assert !mode.isFinalTreeShaking() || appView.dexItemFactory().isPossiblyCompilerSynthesizedType(clazz) - || (initialDeadProtoTypes != null && initialDeadProtoTypes.contains(clazz)) + || initialDeadProtoTypes.contains(clazz) || initialMissingTypes.contains(clazz) : "Unexpected missing class `" + clazz.toSourceString() + "`"; boolean newReport = missingTypes.add(clazz); @@ -2461,7 +2496,7 @@ (type, subTypeConsumer, lambdaConsumer) -> objectAllocationInfoCollection.forEachInstantiatedSubType( type, subTypeConsumer, lambdaConsumer, appInfo), - pinnedItems::contains) + reference -> keepInfo.isPinned(reference, appInfo)) .forEach( target -> markVirtualDispatchTargetAsLive( @@ -2528,7 +2563,7 @@ // TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the // marking for not renaming it is in the root set. workList.enqueueMarkMethodKeptAction(new ProgramMethod(clazz, valuesMethod), reason); - addPinnedItem(valuesMethod.toReference()); + keepInfo.keepMethod(clazz, valuesMethod); rootSet.shouldNotBeMinified(valuesMethod.toReference()); } } @@ -2599,9 +2634,10 @@ this.dontWarnPatterns = dontWarnPatterns; // Translate the result of root-set computation into enqueuer actions. if (appView.options().getProguardConfiguration() != null - && !options.kotlinOptimizationOptions().disableKotlinSpecificOptimizations - && mode.isInitialTreeShaking()) { - registerAnalysis(new KotlinMetadataEnqueuerExtension(appView, enqueuerDefinitionSupplier)); + && !options.kotlinOptimizationOptions().disableKotlinSpecificOptimizations) { + registerAnalysis( + new KotlinMetadataEnqueuerExtension( + appView, enqueuerDefinitionSupplier, initialPrunedTypes)); } if (appView.options().isShrinking() || appView.options().getProguardConfiguration() == null) { enqueueRootItems(rootSet.noShrinking); @@ -2632,37 +2668,44 @@ return appInfoWithLiveness; } - public boolean isPinned(DexReference reference) { - return pinnedItems.contains(reference); + private void keepClassWithRules(DexProgramClass clazz, Set<ProguardKeepRuleBase> rules) { + keepInfo.joinClass( + clazz, + info -> + info.pin() + .lazyDisallowAccessModification(() -> computeDisallowAccessModification(rules))); } - private boolean addPinnedItem(DexReference reference) { - allowAccessModification.put(reference, OptionalBool.unknown()); - return pinnedItems.add(reference); + private void keepMethodWithRules( + DexProgramClass holder, DexEncodedMethod method, Set<ProguardKeepRuleBase> rules) { + keepInfo.joinMethod( + holder, + method, + info -> + info.pin() + .lazyDisallowAccessModification(() -> computeDisallowAccessModification(rules))); } - private boolean addPinnedItem(DexReference reference, Set<ProguardKeepRuleBase> rules) { - assert rules != null; - assert !rules.isEmpty(); - OptionalBool allowAccessModificationOfReference = - allowAccessModification.getOrDefault(reference, OptionalBool.TRUE); - if (allowAccessModificationOfReference.isTrue()) { - for (ProguardKeepRuleBase rule : rules) { - ProguardKeepRuleModifiers modifiers = - (rule.isProguardIfRule() ? rule.asProguardIfRule().getSubsequentRule() : rule) - .getModifiers(); - if (!modifiers.allowsAccessModification) { - allowAccessModificationOfReference = OptionalBool.FALSE; - break; - } + private void keepFieldWithRules( + DexProgramClass holder, DexEncodedField field, Set<ProguardKeepRuleBase> rules) { + keepInfo.joinField( + holder, + field, + info -> + info.pin() + .lazyDisallowAccessModification(() -> computeDisallowAccessModification(rules))); + } + + private boolean computeDisallowAccessModification(Set<ProguardKeepRuleBase> rules) { + for (ProguardKeepRuleBase rule : rules) { + ProguardKeepRuleModifiers modifiers = + (rule.isProguardIfRule() ? rule.asProguardIfRule().getSubsequentRule() : rule) + .getModifiers(); + if (!modifiers.allowsAccessModification) { + return true; } - allowAccessModification.put(reference, allowAccessModificationOfReference); } - return pinnedItems.add(reference); - } - - public boolean isMissing(DexType type) { - return missingTypes.contains(type); + return false; } private static class SyntheticAdditions { @@ -2674,15 +2717,16 @@ Map<DexType, DexClasspathClass> syntheticClasspathClasses = new IdentityHashMap<>(); - // Subset of live methods that need to be pinned. - Set<DexMethod> pinnedMethods = Sets.newIdentityHashSet(); + // Subset of live methods that need have keep requirements. + List<Pair<ProgramMethod, Consumer<KeepMethodInfo.Joiner>>> liveMethodsWithKeepActions = + new ArrayList<>(); // Subset of synthesized classes that need to be added to the main-dex file. Set<DexType> mainDexTypes = Sets.newIdentityHashSet(); boolean isEmpty() { boolean empty = syntheticInstantiations.isEmpty() && liveMethods.isEmpty(); - assert !empty || (pinnedMethods.isEmpty() && mainDexTypes.isEmpty()); + assert !empty || (liveMethodsWithKeepActions.isEmpty() && mainDexTypes.isEmpty()); return empty; } @@ -2706,9 +2750,10 @@ liveMethods.put(signature, method); } - void addLiveAndPinnedMethod(ProgramMethod method) { + void addLiveMethodWithKeepAction( + ProgramMethod method, Consumer<KeepMethodInfo.Joiner> keepAction) { addLiveMethod(method); - pinnedMethods.add(method.getDefinition().method); + liveMethodsWithKeepActions.add(new Pair<>(method, keepAction)); } void amendApplication(Builder appBuilder) { @@ -2727,7 +2772,8 @@ // All synthetic additions are initial tree shaking only. No need to track keep reasons. KeepReasonWitness fakeReason = enqueuer.graphReporter.fakeReportShouldNotBeUsed(); - pinnedMethods.forEach(enqueuer::addPinnedItem); + liveMethodsWithKeepActions.forEach( + item -> enqueuer.keepInfo.joinMethod(item.getFirst(), item.getSecond())); for (Pair<DexProgramClass, ProgramMethod> clazzAndContext : syntheticInstantiations.values()) { enqueuer.workList.enqueueMarkInstantiatedAction( @@ -2777,7 +2823,7 @@ DexProgramClass holder = bridge.getHolder(); DexEncodedMethod method = bridge.getDefinition(); holder.addVirtualMethod(method); - additions.addLiveAndPinnedMethod(bridge); + additions.addLiveMethodWithKeepAction(bridge, KeepMethodInfo.Joiner::pin); } syntheticInterfaceMethodBridges.clear(); } @@ -2842,11 +2888,12 @@ // to a static method will invalidate the reachable method sets for tracing methods. ensureLambdaAccessibility(); - // Remove the items from `allowAccessModification` that we are not allowed to publicize. - allowAccessModification.entrySet().removeIf(entry -> !entry.getValue().isTrue()); - // Compute the set of dead proto types. deadProtoTypeCandidates.removeIf(this::isTypeLive); + Set<DexType> deadProtoTypes = + SetUtils.newIdentityHashSet(deadProtoTypeCandidates.size() + initialDeadProtoTypes.size()); + deadProtoTypeCandidates.forEach(deadProtoType -> deadProtoTypes.add(deadProtoType.type)); + deadProtoTypes.addAll(initialDeadProtoTypes); // Remove the temporary mappings that have been inserted into the field access info collection // and verify that the mapping is then one-to-one. @@ -2891,7 +2938,7 @@ AppInfoWithLiveness appInfoWithLiveness = new AppInfoWithLiveness( app, - SetUtils.mapIdentityHashSet(deadProtoTypeCandidates, DexProgramClass::getType), + deadProtoTypes, mode.isFinalTreeShaking() ? Sets.union(initialMissingTypes, missingTypes) : missingTypes, @@ -2914,8 +2961,7 @@ toImmutableSortedMap(directInvokes, PresortedComparable::slowCompare), toImmutableSortedMap(staticInvokes, PresortedComparable::slowCompare), callSites, - pinnedItems, - allowAccessModification.keySet(), + keepInfo, rootSet.mayHaveSideEffects, rootSet.noSideEffects, rootSet.assumedValues, @@ -3258,9 +3304,9 @@ }); consequentRootSet.forEachMemberWithDependentItems( appView, - member -> { + (member, dependentItems) -> { if (isMemberLive(member)) { - enqueueRootItems(consequentRootSet.getDependentItems(member)); + enqueueRootItems(dependentItems); } }); // TODO(b/132600955): This modifies the root set. Should the consequent be persistent? @@ -3295,7 +3341,7 @@ ProgramMethod methodToKeep = action.getMethodToKeep(); ProgramMethod singleTarget = action.getSingleTarget(); DexEncodedMethod singleTargetMethod = singleTarget.getDefinition(); - if (rootSet.noShrinking.containsKey(singleTargetMethod.method)) { + if (rootSet.noShrinking.containsMethod(singleTarget.getReference())) { return; } if (methodToKeep != singleTarget) { @@ -3319,11 +3365,12 @@ action.getAction().accept(builder); } + // TODO(b/157700141): Determine if this is the right way to avoid modification of pinned lambdas. private void unpinLambdaMethods() { assert desugaredLambdaImplementationMethods.isEmpty() || options.desugarState == DesugarState.ON; for (DexMethod method : desugaredLambdaImplementationMethods) { - pinnedItems.remove(method); + keepInfo.unsafeUnpinMethod(method); rootSet.prune(method); } desugaredLambdaImplementationMethods.clear(); @@ -3610,7 +3657,8 @@ workList.enqueueMarkInstantiatedAction( clazz, null, InstantiationReason.REFLECTION, KeepReason.reflectiveUseIn(method)); } - if (addPinnedItem(encodedField.field)) { + if (!keepInfo.getFieldInfo(encodedField, clazz).isPinned()) { + keepInfo.pinField(clazz, encodedField); markFieldAsKept(new ProgramField(clazz, encodedField), KeepReason.reflectiveUseIn(method)); } } else { @@ -3797,14 +3845,14 @@ // Add this interface to the set of pinned items to ensure that we do not merge the // interface into its unique subtype, if any. // TODO(b/145344105): This should be superseded by the unknown interface hierarchy. - addPinnedItem(clazz.type); + keepInfo.pinClass(clazz); KeepReason reason = KeepReason.reflectiveUseIn(method); markInterfaceAsInstantiated(clazz, graphReporter.registerClass(clazz, reason)); // Also pin all of its virtual methods to ensure that the devirtualizer does not perform // illegal rewritings of invoke-interface instructions into invoke-virtual instructions. for (DexEncodedMethod virtualMethod : clazz.virtualMethods()) { - addPinnedItem(virtualMethod.method); + keepInfo.pinMethod(clazz, virtualMethod); markVirtualMethodAsReachable(virtualMethod.method, true, null, reason); } } @@ -4113,16 +4161,7 @@ this.enqueuer = enqueuer; } - @Override - public DexDefinition definitionFor(DexReference reference) { - return enqueuer.definitionFor(reference); - } - - @Override - public DexEncodedField definitionFor(DexField field) { - return enqueuer.definitionFor(field); - } - + @Deprecated @Override public DexEncodedMethod definitionFor(DexMethod method) { return enqueuer.definitionFor(method);
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerMetadataTraceable.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerMetadataTraceable.java new file mode 100644 index 0000000..e17e571 --- /dev/null +++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerMetadataTraceable.java
@@ -0,0 +1,12 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.shaking; + +import com.android.tools.r8.graph.DexDefinitionSupplier; + +public interface EnqueuerMetadataTraceable { + + void trace(DexDefinitionSupplier definitionSupplier); +}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java new file mode 100644 index 0000000..58367a9 --- /dev/null +++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -0,0 +1,93 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.shaking; + +/** Immutable keep requirements for a class. */ +public final class KeepClassInfo extends KeepInfo<KeepClassInfo.Builder, KeepClassInfo> { + + // Requires all aspects of a class to be kept. + private static final KeepClassInfo TOP = new Builder().makeTop().build(); + + // Requires no aspects of a class to be kept. + private static final KeepClassInfo BOTTOM = new Builder().makeBottom().build(); + + public static KeepClassInfo top() { + return TOP; + } + + public static KeepClassInfo bottom() { + return BOTTOM; + } + + private KeepClassInfo(Builder builder) { + super(builder); + } + + private Builder builder() { + return new Builder(this); + } + + public Joiner joiner() { + assert !isTop(); + return new Joiner(this); + } + + @Override + public boolean isTop() { + return this.equals(top()); + } + + @Override + public boolean isBottom() { + return this.equals(bottom()); + } + + public static class Builder extends KeepInfo.Builder<Builder, KeepClassInfo> { + + private Builder() { + super(); + } + + private Builder(KeepClassInfo original) { + super(original); + } + + @Override + public KeepClassInfo getTopInfo() { + return TOP; + } + + @Override + public KeepClassInfo getBottomInfo() { + return BOTTOM; + } + + @Override + public Builder self() { + return this; + } + + @Override + public boolean isEqualTo(KeepClassInfo other) { + return true; + } + + @Override + public KeepClassInfo doBuild() { + return new KeepClassInfo(this); + } + } + + public static class Joiner extends KeepInfo.Joiner<Joiner, Builder, KeepClassInfo> { + + public Joiner(KeepClassInfo info) { + super(info.builder()); + } + + @Override + Joiner self() { + return this; + } + } +}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java new file mode 100644 index 0000000..6f22db4 --- /dev/null +++ b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
@@ -0,0 +1,95 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.shaking; + +/** Immutable keep requirements for a field. */ +public final class KeepFieldInfo extends KeepInfo<KeepFieldInfo.Builder, KeepFieldInfo> { + + // Requires all aspects of a field to be kept. + private static final KeepFieldInfo TOP = new Builder().makeTop().build(); + + // Requires no aspects of a field to be kept. + private static final KeepFieldInfo BOTTOM = new Builder().makeBottom().build(); + + public static KeepFieldInfo top() { + return TOP; + } + + public static KeepFieldInfo bottom() { + return BOTTOM; + } + + private KeepFieldInfo(Builder builder) { + super(builder); + } + + // This builder is not private as there are known instances where it is safe to modify keep info + // in a non-upwards direction. + Builder builder() { + return new Builder(this); + } + + public Joiner joiner() { + assert !isTop(); + return new Joiner(this); + } + + @Override + public boolean isTop() { + return this.equals(top()); + } + + @Override + public boolean isBottom() { + return this.equals(bottom()); + } + + public static class Builder extends KeepInfo.Builder<Builder, KeepFieldInfo> { + + private Builder() { + super(); + } + + private Builder(KeepFieldInfo original) { + super(original); + } + + @Override + public KeepFieldInfo getTopInfo() { + return TOP; + } + + @Override + public KeepFieldInfo getBottomInfo() { + return BOTTOM; + } + + @Override + public Builder self() { + return this; + } + + @Override + public boolean isEqualTo(KeepFieldInfo other) { + return true; + } + + @Override + public KeepFieldInfo doBuild() { + return new KeepFieldInfo(this); + } + } + + public static class Joiner extends KeepInfo.Joiner<Joiner, Builder, KeepFieldInfo> { + + public Joiner(KeepFieldInfo info) { + super(info.builder()); + } + + @Override + Joiner self() { + return this; + } + } +}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java new file mode 100644 index 0000000..761547a --- /dev/null +++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -0,0 +1,194 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.shaking; + +import com.android.tools.r8.shaking.KeepInfo.Builder; +import java.util.function.Supplier; + +/** Keep information that can be associated with any item, i.e., class, method or field. */ +public abstract class KeepInfo<B extends Builder, K extends KeepInfo> { + + private final boolean pinned; + private final boolean allowAccessModification; + + private KeepInfo(boolean pinned, boolean allowAccessModification) { + this.pinned = pinned; + this.allowAccessModification = allowAccessModification; + } + + KeepInfo(B builder) { + this(builder.isPinned(), builder.isAccessModificationAllowed()); + } + + /** True if an item must be present in the output. */ + public boolean isPinned() { + return pinned; + } + + /** + * True if an item may have its access flags modified. + * + * <p>This method requires knowledge of the global access modification as that will override the + * concrete value on a given item. + * + * @param configuration Global configuration object to determine access modification. + */ + public boolean isAccessModificationAllowed(ProguardConfiguration configuration) { + return configuration.isAccessModificationAllowed() && internalIsAccessModificationAllowed(); + } + + // Internal accessor for the items access-modification bit. + boolean internalIsAccessModificationAllowed() { + return allowAccessModification; + } + + public abstract boolean isTop(); + + public abstract boolean isBottom(); + + public boolean isLessThanOrEquals(K other) { + // An item is less, aka, lower in the lattice, if each of its attributes is at least as + // permissive of that on other. + return (!pinned || other.isPinned()) + && (allowAccessModification || !other.internalIsAccessModificationAllowed()); + } + + /** Builder to construct an arbitrary keep info object. */ + public abstract static class Builder<B extends Builder, K extends KeepInfo> { + + abstract B self(); + + abstract K doBuild(); + + abstract K getTopInfo(); + + abstract K getBottomInfo(); + + abstract boolean isEqualTo(K other); + + private K original; + private boolean pinned; + private boolean allowAccessModification; + + Builder() { + // Default initialized. Use should be followed by makeTop/makeBottom. + } + + Builder(K original) { + this.original = original; + pinned = original.isPinned(); + allowAccessModification = original.internalIsAccessModificationAllowed(); + } + + B makeTop() { + pin(); + disallowAccessModification(); + return self(); + } + + B makeBottom() { + unpin(); + allowAccessModification(); + return self(); + } + + public K build() { + if (original != null) { + if (internalIsEqualTo(original)) { + return original; + } + if (internalIsEqualTo(getTopInfo())) { + return getTopInfo(); + } + if (internalIsEqualTo(getBottomInfo())) { + return getBottomInfo(); + } + } + return doBuild(); + } + + private boolean internalIsEqualTo(K other) { + return isPinned() == other.isPinned() + && isAccessModificationAllowed() == other.internalIsAccessModificationAllowed() + && isEqualTo(other); + } + + public boolean isPinned() { + return pinned; + } + + public boolean isAccessModificationAllowed() { + return allowAccessModification; + } + + public B setPinned(boolean pinned) { + this.pinned = pinned; + return self(); + } + + public B pin() { + return setPinned(true); + } + + public B unpin() { + return setPinned(false); + } + + public B setAllowAccessModification(boolean allowAccessModification) { + this.allowAccessModification = allowAccessModification; + return self(); + } + + public B allowAccessModification() { + return setAllowAccessModification(true); + } + + public B disallowAccessModification() { + return setAllowAccessModification(false); + } + } + + /** Joiner to construct monotonically increasing keep info object. */ + public abstract static class Joiner< + J extends Joiner, B extends Builder, K extends KeepInfo<B, K>> { + + abstract J self(); + + private final Builder<B, K> builder; + + Joiner(Builder<B, K> builder) { + this.builder = builder; + } + + public boolean isTop() { + return builder.isEqualTo(builder.getTopInfo()); + } + + public J top() { + builder.makeTop(); + return self(); + } + + public J pin() { + builder.pin(); + return self(); + } + + // Lazy modification of access modification. + // Only forced if access modification is still allowed. + public J lazyDisallowAccessModification(Supplier<Boolean> lazyShouldDisallow) { + if (builder.isAccessModificationAllowed() && lazyShouldDisallow.get()) { + builder.disallowAccessModification(); + } + return self(); + } + + public K join() { + K joined = builder.build(); + K original = builder.original; + assert original.isLessThanOrEquals(joined); + return joined; + } + } +}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java new file mode 100644 index 0000000..37c8771 --- /dev/null +++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -0,0 +1,368 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.shaking; + +import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; + +import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.graph.AppInfo; +import com.android.tools.r8.graph.DexDefinitionSupplier; +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.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexReference; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.GraphLense.NestedGraphLense; +import com.android.tools.r8.graph.ProgramField; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.shaking.KeepFieldInfo.Joiner; +import java.util.Collection; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.function.Consumer; + +// Non-mutable collection of keep information pertaining to a program. +public abstract class KeepInfoCollection { + + // TODO(b/157538235): This should not be bottom. + private static KeepClassInfo keepInfoForNonProgramClass() { + return KeepClassInfo.bottom(); + } + + // TODO(b/157538235): This should not be bottom. + private static KeepMethodInfo keepInfoForNonProgramMethod() { + return KeepMethodInfo.bottom(); + } + + // TODO(b/157538235): This should not be bottom. + private static KeepFieldInfo keepInfoForNonProgramField() { + return KeepFieldInfo.bottom(); + } + + /** + * Base accessor for keep info on a class. + * + * <p>Access may never be granted directly on DexType as the "keep info" for any non-program type + * is not the same as the default keep info for a program type. By typing the interface at program + * item we can eliminate errors where a reference to a non-program item results in optimizations + * assuming aspects of it can be changed when in fact they can not. + */ + public abstract KeepClassInfo getClassInfo(DexProgramClass clazz); + + /** + * Base accessor for keep info on a method. + * + * <p>See comment on class access for why this is typed at program method. + */ + public abstract KeepMethodInfo getMethodInfo(DexEncodedMethod method, DexProgramClass holder); + + /** + * Base accessor for keep info on a field. + * + * <p>See comment on class access for why this is typed at program field. + */ + public abstract KeepFieldInfo getFieldInfo(DexEncodedField field, DexProgramClass holder); + + public final KeepClassInfo getClassInfo(DexType type, DexDefinitionSupplier definitions) { + DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(type)); + return clazz == null ? keepInfoForNonProgramClass() : getClassInfo(clazz); + } + + public final KeepMethodInfo getMethodInfo(ProgramMethod method) { + return getMethodInfo(method.getDefinition(), method.getHolder()); + } + + public final KeepMethodInfo getMethodInfo(DexMethod method, DexDefinitionSupplier definitions) { + DexProgramClass holder = asProgramClassOrNull(definitions.definitionFor(method.holder)); + if (holder == null) { + return keepInfoForNonProgramMethod(); + } + DexEncodedMethod definition = holder.lookupMethod(method); + return definition == null ? KeepMethodInfo.bottom() : getMethodInfo(definition, holder); + } + + public final KeepFieldInfo getFieldInfo(ProgramField field) { + return getFieldInfo(field.getDefinition(), field.getHolder()); + } + + public final KeepFieldInfo getFieldInfo(DexField field, DexDefinitionSupplier definitions) { + DexProgramClass holder = asProgramClassOrNull(definitions.definitionFor(field.holder)); + if (holder == null) { + return keepInfoForNonProgramField(); + } + DexEncodedField definition = holder.lookupField(field); + return definition == null ? KeepFieldInfo.bottom() : getFieldInfo(definition, holder); + } + + public final KeepInfo getInfo(DexReference reference, DexDefinitionSupplier definitions) { + if (reference.isDexType()) { + return getClassInfo(reference.asDexType(), definitions); + } + if (reference.isDexMethod()) { + return getMethodInfo(reference.asDexMethod(), definitions); + } + if (reference.isDexField()) { + return getFieldInfo(reference.asDexField(), definitions); + } + throw new Unreachable(); + } + + public final boolean isPinned(DexReference reference, DexDefinitionSupplier definitions) { + return getInfo(reference, definitions).isPinned(); + } + + public final boolean isPinned(DexType type, DexDefinitionSupplier definitions) { + return getClassInfo(type, definitions).isPinned(); + } + + public final boolean isPinned(DexMethod method, DexDefinitionSupplier definitions) { + return getMethodInfo(method, definitions).isPinned(); + } + + public final boolean isPinned(DexField field, DexDefinitionSupplier definitions) { + return getFieldInfo(field, definitions).isPinned(); + } + + public final boolean verifyNoneArePinned(Collection<DexType> types, AppInfo appInfo) { + for (DexType type : types) { + DexProgramClass clazz = + asProgramClassOrNull(appInfo.definitionForWithoutExistenceAssert(type)); + assert clazz == null || !getClassInfo(clazz).isPinned(); + } + return true; + } + + // TODO(b/156715504): We should try to avoid the need for iterating pinned items. + @Deprecated + public abstract void forEachPinnedType(Consumer<DexType> consumer); + + // TODO(b/156715504): We should try to avoid the need for iterating pinned items. + @Deprecated + public abstract void forEachPinnedMethod(Consumer<DexMethod> consumer); + + // TODO(b/156715504): We should try to avoid the need for iterating pinned items. + @Deprecated + public abstract void forEachPinnedField(Consumer<DexField> consumer); + + public abstract KeepInfoCollection rewrite(NestedGraphLense lens); + + public abstract KeepInfoCollection mutate(Consumer<MutableKeepInfoCollection> mutator); + + // Mutation interface for building up the keep info. + public static class MutableKeepInfoCollection extends KeepInfoCollection { + + // These are typed at signatures but the interface should make sure never to allow access + // directly with a signature. See the comment in KeepInfoCollection. + private final Map<DexType, KeepClassInfo> keepClassInfo; + private final Map<DexMethod, KeepMethodInfo> keepMethodInfo; + private final Map<DexField, KeepFieldInfo> keepFieldInfo; + + MutableKeepInfoCollection() { + this(new IdentityHashMap<>(), new IdentityHashMap<>(), new IdentityHashMap<>()); + } + + private MutableKeepInfoCollection( + Map<DexType, KeepClassInfo> keepClassInfo, + Map<DexMethod, KeepMethodInfo> keepMethodInfo, + Map<DexField, KeepFieldInfo> keepFieldInfo) { + this.keepClassInfo = keepClassInfo; + this.keepMethodInfo = keepMethodInfo; + this.keepFieldInfo = keepFieldInfo; + } + + @Override + public KeepInfoCollection rewrite(NestedGraphLense lens) { + Map<DexType, KeepClassInfo> newClassInfo = new IdentityHashMap<>(keepClassInfo.size()); + keepClassInfo.forEach( + (type, info) -> { + DexType newType = lens.lookupType(type); + assert !info.isPinned() || type == newType; + newClassInfo.put(newType, info); + }); + Map<DexMethod, KeepMethodInfo> newMethodInfo = new IdentityHashMap<>(keepMethodInfo.size()); + keepMethodInfo.forEach( + (method, info) -> { + DexMethod newMethod = lens.getRenamedMethodSignature(method); + assert !info.isPinned() || method == newMethod; + newMethodInfo.put(newMethod, info); + }); + Map<DexField, KeepFieldInfo> newFieldInfo = new IdentityHashMap<>(keepFieldInfo.size()); + keepFieldInfo.forEach( + (field, info) -> { + DexField newField = lens.getRenamedFieldSignature(field); + assert !info.isPinned() || field == newField; + newFieldInfo.put(newField, info); + }); + return new MutableKeepInfoCollection(newClassInfo, newMethodInfo, newFieldInfo); + } + + @Override + public KeepClassInfo getClassInfo(DexProgramClass clazz) { + return keepClassInfo.getOrDefault(clazz.type, KeepClassInfo.bottom()); + } + + @Override + public KeepMethodInfo getMethodInfo(DexEncodedMethod method, DexProgramClass holder) { + assert method.holder() == holder.type; + return keepMethodInfo.getOrDefault(method.method, KeepMethodInfo.bottom()); + } + + @Override + public KeepFieldInfo getFieldInfo(DexEncodedField field, DexProgramClass holder) { + assert field.holder() == holder.type; + return keepFieldInfo.getOrDefault(field.field, KeepFieldInfo.bottom()); + } + + public void joinClass(DexProgramClass clazz, Consumer<KeepClassInfo.Joiner> fn) { + KeepClassInfo info = getClassInfo(clazz); + if (info.isTop()) { + return; + } + KeepClassInfo.Joiner joiner = info.joiner(); + fn.accept(joiner); + KeepClassInfo joined = joiner.join(); + if (!info.equals(joined)) { + keepClassInfo.put(clazz.type, joined); + } + } + + public void keepClass(DexProgramClass clazz) { + joinClass(clazz, KeepInfo.Joiner::top); + } + + public void pinClass(DexProgramClass clazz) { + joinClass(clazz, KeepInfo.Joiner::pin); + } + + public void joinMethod( + DexProgramClass holder, DexEncodedMethod method, Consumer<KeepMethodInfo.Joiner> fn) { + KeepMethodInfo info = getMethodInfo(method, holder); + if (info == KeepMethodInfo.top()) { + return; + } + KeepMethodInfo.Joiner joiner = info.joiner(); + fn.accept(joiner); + KeepMethodInfo joined = joiner.join(); + if (!info.equals(joined)) { + keepMethodInfo.put(method.method, joined); + } + } + + public void joinMethod(ProgramMethod programMethod, Consumer<KeepMethodInfo.Joiner> fn) { + joinMethod(programMethod.getHolder(), programMethod.getDefinition(), fn); + } + + public void keepMethod(ProgramMethod programMethod) { + keepMethod(programMethod.getHolder(), programMethod.getDefinition()); + } + + public void keepMethod(DexProgramClass holder, DexEncodedMethod method) { + joinMethod(holder, method, KeepInfo.Joiner::top); + } + + public void pinMethod(DexProgramClass holder, DexEncodedMethod method) { + joinMethod(holder, method, KeepInfo.Joiner::pin); + } + + // Unpinning a method represents a non-monotonic change to the keep info of that item. + // This is generally unsound as it requires additional analysis to determine that a method that + // was pinned no longer is. A known sound example is the enum analysis that will identify + // non-escaping enums on enum types that are not pinned, thus their methods do not need to be + // retained even if a rule has marked them as conditionally pinned. + public void unsafeUnpinMethod(ProgramMethod method) { + // This asserts that the holder is not pinned as some analysis must have established that the + // type is not "present" and thus the method need not be pinned. + assert !getClassInfo(method.getHolder()).isPinned(); + unsafeUnpinMethod(method.getReference()); + } + + // TODO(b/157700141): Avoid pinning/unpinning references. + @Deprecated + public void unsafeUnpinMethod(DexMethod method) { + KeepMethodInfo info = keepMethodInfo.get(method); + if (info != null && info.isPinned()) { + keepMethodInfo.put(method, info.builder().unpin().build()); + } + } + + public void joinField( + DexProgramClass holder, DexEncodedField field, Consumer<KeepFieldInfo.Joiner> fn) { + KeepFieldInfo info = getFieldInfo(field, holder); + if (info.isTop()) { + return; + } + Joiner joiner = info.joiner(); + fn.accept(joiner); + KeepFieldInfo joined = joiner.join(); + if (!info.equals(joined)) { + keepFieldInfo.put(field.field, joined); + } + } + + public void keepField(DexProgramClass holder, DexEncodedField field) { + joinField(holder, field, KeepInfo.Joiner::top); + } + + public void pinField(DexProgramClass holder, DexEncodedField field) { + joinField(holder, field, KeepInfo.Joiner::pin); + } + + public void keepMember(DexProgramClass holder, DexEncodedMember<?, ?> member) { + if (member.isDexEncodedMethod()) { + keepMethod(holder, member.asDexEncodedMethod()); + } else { + assert member.isDexEncodedField(); + keepField(holder, member.asDexEncodedField()); + } + } + + public void unsafeUnpinField(DexProgramClass holder, DexEncodedField field) { + assert holder.type == field.holder(); + assert !getClassInfo(holder).isPinned(); + KeepFieldInfo info = this.keepFieldInfo.get(field.toReference()); + if (info != null && info.isPinned()) { + keepFieldInfo.put(field.toReference(), info.builder().unpin().build()); + } + } + + @Override + public KeepInfoCollection mutate(Consumer<MutableKeepInfoCollection> mutator) { + mutator.accept(this); + return this; + } + + @Override + public void forEachPinnedType(Consumer<DexType> consumer) { + keepClassInfo.forEach( + (type, info) -> { + if (info.isPinned()) { + consumer.accept(type); + } + }); + } + + @Override + public void forEachPinnedMethod(Consumer<DexMethod> consumer) { + keepMethodInfo.forEach( + (method, info) -> { + if (info.isPinned()) { + consumer.accept(method); + } + }); + } + + @Override + public void forEachPinnedField(Consumer<DexField> consumer) { + keepFieldInfo.forEach( + (field, info) -> { + if (info.isPinned()) { + consumer.accept(field); + } + }); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java new file mode 100644 index 0000000..98eae1b --- /dev/null +++ b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
@@ -0,0 +1,95 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.shaking; + +/** Immutable keep requirements for a method. */ +public final class KeepMethodInfo extends KeepInfo<KeepMethodInfo.Builder, KeepMethodInfo> { + + // Requires all aspects of a method to be kept. + private static final KeepMethodInfo TOP = new Builder().makeTop().build(); + + // Requires no aspects of a method to be kept. + private static final KeepMethodInfo BOTTOM = new Builder().makeBottom().build(); + + public static KeepMethodInfo top() { + return TOP; + } + + public static KeepMethodInfo bottom() { + return BOTTOM; + } + + private KeepMethodInfo(Builder builder) { + super(builder); + } + + // This builder is not private as there are known instances where it is safe to modify keep info + // in a non-upwards direction. + Builder builder() { + return new Builder(this); + } + + public Joiner joiner() { + assert !isTop(); + return new Joiner(this); + } + + @Override + public boolean isTop() { + return this.equals(top()); + } + + @Override + public boolean isBottom() { + return this.equals(bottom()); + } + + public static class Builder extends KeepInfo.Builder<Builder, KeepMethodInfo> { + + private Builder() { + super(); + } + + private Builder(KeepMethodInfo original) { + super(original); + } + + @Override + public Builder self() { + return this; + } + + @Override + public KeepMethodInfo getTopInfo() { + return TOP; + } + + @Override + public KeepMethodInfo getBottomInfo() { + return BOTTOM; + } + + @Override + public boolean isEqualTo(KeepMethodInfo other) { + return true; + } + + @Override + public KeepMethodInfo doBuild() { + return new KeepMethodInfo(this); + } + } + + public static class Joiner extends KeepInfo.Joiner<Joiner, Builder, KeepMethodInfo> { + + public Joiner(KeepMethodInfo info) { + super(info.builder()); + } + + @Override + Joiner self() { + return this; + } + } +}
diff --git a/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java b/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java index 0979ef9..c6a1725 100644 --- a/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java +++ b/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java
@@ -8,7 +8,6 @@ import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexProgramClass; -import com.android.tools.r8.graph.DexReference; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.TopDownClassHierarchyTraversal; @@ -50,8 +49,10 @@ getClassesWithLibraryMethodOverrides(appView); // Remove all types that are pinned from the initial set of non-escaping classes. - DexReference.filterDexType(appView.appInfo().pinnedItems.stream()) - .forEach(initialNonEscapingClassesWithLibraryMethodOverrides::remove); + appView + .appInfo() + .getKeepInfo() + .forEachPinnedType(initialNonEscapingClassesWithLibraryMethodOverrides::remove); return initialNonEscapingClassesWithLibraryMethodOverrides; }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java index ffb940a..9abf328 100644 --- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java +++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -40,6 +40,7 @@ import com.android.tools.r8.utils.Consumer3; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.MethodSignatureEquivalence; +import com.android.tools.r8.utils.OriginWithPosition; import com.android.tools.r8.utils.PredicateSet; import com.android.tools.r8.utils.StringDiagnostic; import com.android.tools.r8.utils.ThreadUtils; @@ -55,7 +56,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; @@ -71,6 +71,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -81,7 +82,7 @@ private final SubtypingInfo subtypingInfo; private final DirectMappedDexApplication application; private final Iterable<? extends ProguardConfigurationRule> rules; - private final Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking = new IdentityHashMap<>(); + private final MutableItemsWithRules noShrinking = new MutableItemsWithRules(); private final Set<DexReference> noObfuscation = Sets.newIdentityHashSet(); private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>(); private final LinkedHashMap<DexReference, DexReference> checkDiscarded = new LinkedHashMap<>(); @@ -98,8 +99,8 @@ private final Set<DexType> neverClassInline = Sets.newIdentityHashSet(); private final Set<DexType> neverMerge = Sets.newIdentityHashSet(); private final Set<DexReference> neverPropagateValue = Sets.newIdentityHashSet(); - private final Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> - dependentNoShrinking = new IdentityHashMap<>(); + private final Map<DexReference, MutableItemsWithRules> dependentNoShrinking = + new IdentityHashMap<>(); private final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule = new IdentityHashMap<>(); private final Map<DexReference, ProguardMemberRule> mayHaveSideEffects = new IdentityHashMap<>(); @@ -113,6 +114,9 @@ private final DexStringCache dexStringCache = new DexStringCache(); private final Set<ProguardIfRule> ifRules = Sets.newIdentityHashSet(); + private final Map<OriginWithPosition, List<DexMethod>> assumeNoSideEffectsWarnings = + new HashMap<>(); + public RootSetBuilder( AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo, @@ -304,6 +308,7 @@ } finally { application.timing.end(); } + generateAssumeNoSideEffectsWarnings(); if (!noSideEffects.isEmpty() || !assumedValues.isEmpty()) { BottomUpClassHierarchyTraversal.forAllClasses(appView, subtypingInfo) .visit(appView.appInfo().classes(), this::propagateAssumeRules); @@ -689,53 +694,62 @@ // TODO(b/67934426): Test this code. public static void writeSeeds( AppInfoWithLiveness appInfo, PrintStream out, Predicate<DexType> include) { - for (DexReference seed : appInfo.getPinnedItems()) { - if (seed.isDexType()) { - if (include.test(seed.asDexType())) { - out.println(seed.toSourceString()); - } - } else if (seed.isDexField()) { - DexField field = seed.asDexField(); - if (include.test(field.holder)) { - out.println( - field.holder.toSourceString() - + ": " - + field.type.toSourceString() - + " " - + field.name.toSourceString()); - } - } else { - assert seed.isDexMethod(); - DexMethod method = seed.asDexMethod(); - if (!include.test(method.holder)) { - continue; - } - out.print(method.holder.toSourceString() + ": "); - DexEncodedMethod encodedMethod = appInfo.definitionFor(method); - if (encodedMethod.accessFlags.isConstructor()) { - if (encodedMethod.accessFlags.isStatic()) { - out.print(Constants.CLASS_INITIALIZER_NAME); - } else { - String holderName = method.holder.toSourceString(); - String constrName = holderName.substring(holderName.lastIndexOf('.') + 1); - out.print(constrName); - } - } else { - out.print( - method.proto.returnType.toSourceString() + " " + method.name.toSourceString()); - } - boolean first = true; - out.print("("); - for (DexType param : method.proto.parameters.values) { - if (!first) { - out.print(","); - } - first = false; - out.print(param.toSourceString()); - } - out.println(")"); - } - } + appInfo + .getKeepInfo() + .forEachPinnedType( + type -> { + if (include.test(type)) { + out.println(type.toSourceString()); + } + }); + appInfo + .getKeepInfo() + .forEachPinnedField( + field -> { + if (include.test(field.holder)) { + out.println( + field.holder.toSourceString() + + ": " + + field.type.toSourceString() + + " " + + field.name.toSourceString()); + } + }); + appInfo + .getKeepInfo() + .forEachPinnedMethod( + method -> { + if (!include.test(method.holder)) { + return; + } + DexProgramClass holder = asProgramClassOrNull(appInfo.definitionForHolder(method)); + DexEncodedMethod definition = method.lookupOnClass(holder); + if (definition == null) { + assert false; + return; + } + out.print(method.holder.toSourceString() + ": "); + if (definition.isClassInitializer()) { + out.print(Constants.CLASS_INITIALIZER_NAME); + } else if (definition.isInstanceInitializer()) { + String holderName = method.holder.toSourceString(); + String constrName = holderName.substring(holderName.lastIndexOf('.') + 1); + out.print(constrName); + } else { + out.print( + method.proto.returnType.toSourceString() + " " + method.name.toSourceString()); + } + boolean first = true; + out.print("("); + for (DexType param : method.proto.parameters.values) { + if (!first) { + out.print(","); + } + first = false; + out.print(param.toSourceString()); + } + out.println(")"); + }); out.close(); } @@ -1002,9 +1016,8 @@ } // Keep the type if the item is also kept. dependentNoShrinking - .computeIfAbsent(item.toReference(), x -> new IdentityHashMap<>()) - .computeIfAbsent(type, k -> new HashSet<>()) - .add(context); + .computeIfAbsent(item.toReference(), x -> new MutableItemsWithRules()) + .addClassWithRule(type, context); // Unconditionally add to no-obfuscation, as that is only checked for surviving items. noObfuscation.add(type); } @@ -1091,11 +1104,10 @@ if (!modifiers.allowsShrinking) { if (precondition != null) { dependentNoShrinking - .computeIfAbsent(precondition.toReference(), x -> new IdentityHashMap<>()) - .computeIfAbsent(item.toReference(), i -> new HashSet<>()) - .add(keepRule); + .computeIfAbsent(precondition.toReference(), x -> new MutableItemsWithRules()) + .addReferenceWithRule(item.toReference(), keepRule); } else { - noShrinking.computeIfAbsent(item.toReference(), i -> new HashSet<>()).add(keepRule); + noShrinking.addReferenceWithRule(item.toReference(), keepRule); } context.markAsUsed(); } @@ -1117,6 +1129,7 @@ mayHaveSideEffects.put(item.toReference(), rule); context.markAsUsed(); } else if (context instanceof ProguardAssumeNoSideEffectRule) { + checkAssumeNoSideEffectsWarnings(item, (ProguardAssumeNoSideEffectRule) context, rule); noSideEffects.put(item.toReference(), rule); context.markAsUsed(); } else if (context instanceof ProguardWhyAreYouKeepingRule) { @@ -1263,18 +1276,18 @@ final Set<DexMethod> neverInline; final Set<DexType> neverClassInline; - final Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking; + final MutableItemsWithRules noShrinking; final Set<DexReference> noObfuscation; - final Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking; + final Map<DexReference, MutableItemsWithRules> dependentNoShrinking; final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule; final List<DelayedRootSetActionItem> delayedRootSetActionItems; RootSetBase( Set<DexMethod> neverInline, Set<DexType> neverClassInline, - Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking, + MutableItemsWithRules noShrinking, Set<DexReference> noObfuscation, - Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking, + Map<DexReference, MutableItemsWithRules> dependentNoShrinking, Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule, List<DelayedRootSetActionItem> delayedRootSetActionItems) { this.neverInline = neverInline; @@ -1286,10 +1299,6 @@ this.delayedRootSetActionItems = delayedRootSetActionItems; } - public boolean noShrinking(DexReference reference) { - return noShrinking.containsKey(reference); - } - public void forEachClassWithDependentItems( DexDefinitionSupplier definitions, Consumer<DexProgramClass> consumer) { for (DexReference reference : dependentNoShrinking.keySet()) { @@ -1304,33 +1313,55 @@ } public void forEachMemberWithDependentItems( - DexDefinitionSupplier definitions, Consumer<DexEncodedMember<?, ?>> consumer) { - for (DexReference reference : dependentNoShrinking.keySet()) { - if (reference.isDexMember()) { - DexEncodedMember<?, ?> definition = definitions.definitionFor(reference.asDexMember()); - if (definition != null) { - consumer.accept(definition); - } - } - } + DexDefinitionSupplier definitions, + BiConsumer<DexEncodedMember<?, ?>, ItemsWithRules> consumer) { + dependentNoShrinking.forEach( + (reference, dependentItems) -> { + if (reference.isDexMember()) { + DexMember<?, ?> member = reference.asDexMember(); + DexProgramClass holder = + asProgramClassOrNull(definitions.definitionForHolder(member)); + if (holder != null) { + DexEncodedMember<?, ?> definition = holder.lookupMember(member); + if (definition != null) { + consumer.accept(definition, dependentItems); + } + } + } + }); } public void forEachDependentInstanceConstructor( DexProgramClass clazz, AppView<?> appView, - Consumer3<DexProgramClass, ProgramMethod, Set<ProguardKeepRuleBase>> fn) { + BiConsumer<ProgramMethod, Set<ProguardKeepRuleBase>> fn) { getDependentItems(clazz) - .forEach( + .forEachMethod( (reference, reasons) -> { - if (reference.isDexMethod()) { - DexMethod methodReference = reference.asDexMethod(); - DexProgramClass holder = - asProgramClassOrNull(appView.definitionForHolder(methodReference)); - if (holder != null) { - ProgramMethod method = holder.lookupProgramMethod(methodReference); - if (method != null && method.getDefinition().isInstanceInitializer()) { - fn.accept(clazz, method, reasons); - } + DexProgramClass holder = + asProgramClassOrNull(appView.definitionForHolder(reference)); + if (holder != null) { + ProgramMethod method = holder.lookupProgramMethod(reference); + if (method != null && method.getDefinition().isInstanceInitializer()) { + fn.accept(method, reasons); + } + } + }); + } + + public void forEachDependentMember( + DexDefinition item, + AppView<?> appView, + Consumer3<DexDefinition, DexDefinition, Set<ProguardKeepRuleBase>> fn) { + getDependentItems(item) + .forEachMember( + (reference, reasons) -> { + DexProgramClass holder = + asProgramClassOrNull(appView.definitionForHolder(reference)); + if (holder != null) { + DexEncodedMember<?, ?> member = holder.lookupMember(reference); + if (member != null) { + fn.accept(item, member, reasons); } } }); @@ -1340,35 +1371,33 @@ DexDefinition item, AppView<?> appView, Consumer3<DexDefinition, DexDefinition, Set<ProguardKeepRuleBase>> fn) { - getDependentItems(item) - .forEach( - (reference, reasons) -> { - DexDefinition definition = appView.definitionFor(reference); - if (definition != null - && !definition.isDexClass() - && !definition.isStaticMember()) { - fn.accept(item, definition, reasons); - } - }); + forEachDependentMember( + item, + appView, + (precondition, member, reasons) -> { + if (!member.isStatic()) { + fn.accept(precondition, member, reasons); + } + }); } public void forEachDependentStaticMember( DexDefinition item, AppView<?> appView, Consumer3<DexDefinition, DexDefinition, Set<ProguardKeepRuleBase>> fn) { - getDependentItems(item) - .forEach( - (reference, reasons) -> { - DexDefinition definition = appView.definitionFor(reference); - if (definition != null && !definition.isDexClass() && definition.isStaticMember()) { - fn.accept(item, definition, reasons); - } - }); + forEachDependentMember( + item, + appView, + (precondition, member, reasons) -> { + if (member.isStatic()) { + fn.accept(precondition, member, reasons); + } + }); } - Map<DexReference, Set<ProguardKeepRuleBase>> getDependentItems(DexDefinition item) { - return Collections.unmodifiableMap( - dependentNoShrinking.getOrDefault(item.toReference(), Collections.emptyMap())); + ItemsWithRules getDependentItems(DexDefinition item) { + ItemsWithRules found = dependentNoShrinking.get(item.toReference()); + return found != null ? found : ItemsWithRules.empty(); } Set<ProguardKeepRuleBase> getDependentKeepClassCompatRule(DexType type) { @@ -1376,6 +1405,292 @@ } } + abstract static class ItemsWithRules { + + public static ItemsWithRules empty() { + return MutableItemsWithRules.EMPTY; + } + + public abstract boolean containsClass(DexType type); + + public abstract boolean containsField(DexField field); + + public abstract boolean containsMethod(DexMethod method); + + public final boolean containsReference(DexReference reference) { + return reference.apply(this::containsClass, this::containsField, this::containsMethod); + } + + public abstract void forEachClass(Consumer<DexType> consumer); + + public abstract void forEachClass(BiConsumer<DexType, Set<ProguardKeepRuleBase>> consumer); + + public abstract void forEachField(Consumer<? super DexField> consumer); + + public abstract void forEachField( + BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer); + + public abstract void forEachMember(Consumer<DexMember<?, ?>> consumer); + + public abstract void forEachMember( + BiConsumer<DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer); + + public abstract void forEachMethod(Consumer<? super DexMethod> consumer); + + public abstract void forEachMethod( + BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer); + + public abstract Set<ProguardKeepRuleBase> getRulesForClass(DexType type); + + public abstract Set<ProguardKeepRuleBase> getRulesForField(DexField field); + + public abstract Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method); + + public final Set<ProguardKeepRuleBase> getRulesForReference(DexReference reference) { + return reference.apply( + this::getRulesForClass, this::getRulesForField, this::getRulesForMethod); + } + } + + static class MutableItemsWithRules extends ItemsWithRules { + + private static final ItemsWithRules EMPTY = + new MutableItemsWithRules( + Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap()); + + final Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules; + final Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules; + final Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules; + + MutableItemsWithRules() { + this(new IdentityHashMap<>(), new IdentityHashMap<>(), new IdentityHashMap<>()); + } + + private MutableItemsWithRules( + Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules, + Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules, + Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules) { + this.classesWithRules = classesWithRules; + this.fieldsWithRules = fieldsWithRules; + this.methodsWithRules = methodsWithRules; + } + + public void addAll(ItemsWithRules items) { + items.forEachClass(this::addClassWithRules); + items.forEachField(this::addFieldWithRules); + items.forEachMethod(this::addMethodWithRules); + } + + public void addClassWithRule(DexType type, ProguardKeepRuleBase rule) { + classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).add(rule); + } + + public void addClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) { + classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).addAll(rules); + } + + public void addFieldWithRule(DexField field, ProguardKeepRuleBase rule) { + fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).add(rule); + } + + public void addFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) { + fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).addAll(rules); + } + + public void addMethodWithRule(DexMethod method, ProguardKeepRuleBase rule) { + methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).add(rule); + } + + public void addMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) { + methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).addAll(rules); + } + + public void addReferenceWithRule(DexReference reference, ProguardKeepRuleBase rule) { + reference.accept( + this::addClassWithRule, this::addFieldWithRule, this::addMethodWithRule, rule); + } + + public void addReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) { + reference.accept( + this::addClassWithRules, this::addFieldWithRules, this::addMethodWithRules, rules); + } + + @Override + public boolean containsClass(DexType type) { + return classesWithRules.containsKey(type); + } + + @Override + public boolean containsField(DexField field) { + return fieldsWithRules.containsKey(field); + } + + @Override + public boolean containsMethod(DexMethod method) { + return methodsWithRules.containsKey(method); + } + + @Override + public void forEachClass(Consumer<DexType> consumer) { + classesWithRules.keySet().forEach(consumer); + } + + @Override + public void forEachClass(BiConsumer<DexType, Set<ProguardKeepRuleBase>> consumer) { + classesWithRules.forEach(consumer); + } + + @Override + public void forEachField(Consumer<? super DexField> consumer) { + fieldsWithRules.keySet().forEach(consumer); + } + + @Override + public void forEachField(BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer) { + fieldsWithRules.forEach(consumer); + } + + @Override + public void forEachMember(Consumer<DexMember<?, ?>> consumer) { + forEachField(consumer); + forEachMethod(consumer); + } + + @Override + public void forEachMember(BiConsumer<DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer) { + forEachField(consumer); + forEachMethod(consumer); + } + + @Override + public void forEachMethod(Consumer<? super DexMethod> consumer) { + methodsWithRules.keySet().forEach(consumer); + } + + @Override + public void forEachMethod(BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer) { + methodsWithRules.forEach(consumer); + } + + @Override + public Set<ProguardKeepRuleBase> getRulesForClass(DexType type) { + return classesWithRules.get(type); + } + + @Override + public Set<ProguardKeepRuleBase> getRulesForField(DexField field) { + return fieldsWithRules.get(field); + } + + @Override + public Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method) { + return methodsWithRules.get(method); + } + + public void removeClass(DexType type) { + classesWithRules.remove(type); + } + + public void removeField(DexField field) { + fieldsWithRules.remove(field); + } + + public void removeMethod(DexMethod method) { + methodsWithRules.remove(method); + } + + public void removeReference(DexReference reference) { + reference.accept(this::removeClass, this::removeField, this::removeMethod); + } + + public void putAll(ItemsWithRules items) { + items.forEachClass(this::putClassWithRules); + items.forEachField(this::putFieldWithRules); + items.forEachMethod(this::putMethodWithRules); + } + + public void putClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) { + classesWithRules.put(type, rules); + } + + public void putFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) { + fieldsWithRules.put(field, rules); + } + + public void putMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) { + methodsWithRules.put(method, rules); + } + + public void putReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) { + reference.accept( + this::putClassWithRules, this::putFieldWithRules, this::putMethodWithRules, rules); + } + + public int size() { + return classesWithRules.size() + fieldsWithRules.size() + methodsWithRules.size(); + } + } + + private void checkAssumeNoSideEffectsWarnings( + DexDefinition item, ProguardAssumeNoSideEffectRule context, ProguardMemberRule rule) { + if (rule.getRuleType() == ProguardMemberType.METHOD && rule.isSpecific()) { + return; + } + if (item.isDexEncodedMethod()) { + DexEncodedMethod method = item.asDexEncodedMethod(); + if (method.holder() == options.itemFactory.objectType) { + OriginWithPosition key = new OriginWithPosition(context.getOrigin(), context.getPosition()); + assumeNoSideEffectsWarnings.computeIfAbsent(key, k -> new ArrayList<>()).add(method.method); + } + } + } + + private boolean isWaitOrNotifyMethod(DexMethod method) { + return method.name == options.itemFactory.waitMethodName + || method.name == options.itemFactory.notifyMethodName + || method.name == options.itemFactory.notifyAllMethodName; + } + + private void generateAssumeNoSideEffectsWarnings() { + ProguardClassFilter dontWarnPatterns = + options.getProguardConfiguration() != null + ? options.getProguardConfiguration().getDontWarnPatterns() + : ProguardClassFilter.empty(); + if (dontWarnPatterns.matches(options.itemFactory.objectType)) { + return; + } + + assumeNoSideEffectsWarnings.forEach( + (originWithPosition, methods) -> { + boolean waitOrNotifyMethods = methods.stream().anyMatch(this::isWaitOrNotifyMethod); + StringBuilder message = new StringBuilder(); + message.append( + "The -assumenosideeffects rule matches methods on `java.lang.Object` with wildcards"); + if (waitOrNotifyMethods) { + message.append(" including the method(s) "); + for (int i = 0; i < methods.size(); i++) { + if (i > 0) { + message.append(i < methods.size() - 1 ? ", " : " and "); + } + message.append("`"); + message.append(methods.get(i).toSourceStringWithoutHolder()); + message.append("`"); + } + message.append(". "); + message.append("This will most likely cause problems. "); + } else { + message.append(". "); + message.append("This is most likely not intended. "); + } + message.append("Consider specifying the methods more precisely."); + options.reporter.warning( + new StringDiagnostic( + message.toString(), + originWithPosition.getOrigin(), + originWithPosition.getPosition())); + }); + } + public static class RootSet extends RootSetBase { public final ImmutableList<DexReference> reasonAsked; @@ -1398,7 +1713,7 @@ public final Set<ProguardIfRule> ifRules; private RootSet( - Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking, + MutableItemsWithRules noShrinking, Set<DexReference> noObfuscation, ImmutableList<DexReference> reasonAsked, ImmutableList<DexReference> checkDiscarded, @@ -1418,7 +1733,7 @@ Map<DexReference, ProguardMemberRule> mayHaveSideEffects, Map<DexReference, ProguardMemberRule> noSideEffects, Map<DexReference, ProguardMemberRule> assumedValues, - Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking, + Map<DexReference, MutableItemsWithRules> dependentNoShrinking, Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule, Set<DexReference> identifierNameStrings, Set<ProguardIfRule> ifRules, @@ -1472,8 +1787,7 @@ neverClassInline.addAll(consequentRootSet.neverClassInline); noObfuscation.addAll(consequentRootSet.noObfuscation); if (addNoShrinking) { - consequentRootSet.noShrinking.forEach( - (type, rules) -> noShrinking.computeIfAbsent(type, k -> new HashSet<>()).addAll(rules)); + noShrinking.addAll(consequentRootSet.noShrinking); } addDependentItems(consequentRootSet.dependentNoShrinking); consequentRootSet.dependentKeepClassCompatRule.forEach( @@ -1484,18 +1798,17 @@ } // Add dependent items that depend on -if rules. - private void addDependentItems( - Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentItems) { + private void addDependentItems(Map<DexReference, ? extends ItemsWithRules> dependentItems) { dependentItems.forEach( (reference, dependence) -> dependentNoShrinking - .computeIfAbsent(reference, x -> new IdentityHashMap<>()) + .computeIfAbsent(reference, x -> new MutableItemsWithRules()) .putAll(dependence)); } public void copy(DexReference original, DexReference rewritten) { - if (noShrinking.containsKey(original)) { - noShrinking.put(rewritten, noShrinking.get(original)); + if (noShrinking.containsReference(original)) { + noShrinking.putReferenceWithRules(rewritten, noShrinking.getRulesForReference(original)); } if (noObfuscation.contains(original)) { noObfuscation.add(rewritten); @@ -1509,7 +1822,7 @@ } public void prune(DexReference reference) { - noShrinking.remove(reference); + noShrinking.removeReference(reference); noObfuscation.remove(reference); noSideEffects.remove(reference); assumedValues.remove(reference); @@ -1527,30 +1840,29 @@ Enqueuer enqueuer) { references.removeIf( reference -> { - if (reference.isDexField()) { - DexEncodedField definition = definitions.definitionFor(reference.asDexField()); - if (definition == null) { - return true; - } - DexClass holder = definitions.definitionFor(definition.holder()); - if (holder.isProgramClass()) { - return !enqueuer.isFieldReferenced(definition); - } - return !enqueuer.isNonProgramTypeLive(holder); - } else if (reference.isDexMethod()) { - DexEncodedMethod definition = definitions.definitionFor(reference.asDexMethod()); - if (definition == null) { - return true; - } - DexClass holder = definitions.definitionFor(definition.holder()); - if (holder.isProgramClass()) { - return !enqueuer.isMethodLive(definition) && !enqueuer.isMethodTargeted(definition); - } - return !enqueuer.isNonProgramTypeLive(holder); - } else { + if (reference.isDexType()) { DexClass definition = definitions.definitionFor(reference.asDexType()); return definition == null || !enqueuer.isTypeLive(definition); } + + assert reference.isDexMember(); + + DexMember<?, ?> member = reference.asDexMember(); + DexClass holder = definitions.definitionForHolder(member); + DexEncodedMember<?, ?> definition = member.lookupOnClass(holder); + if (definition == null) { + return true; + } + if (holder.isProgramClass()) { + if (definition.isDexEncodedField()) { + DexEncodedField field = definition.asDexEncodedField(); + return !enqueuer.isFieldReferenced(field); + } + assert definition.isDexEncodedMethod(); + DexEncodedMethod method = definition.asDexEncodedMethod(); + return !enqueuer.isMethodLive(method) && !enqueuer.isMethodTargeted(method); + } + return !enqueuer.isNonProgramTypeLive(holder); }); } @@ -1582,54 +1894,49 @@ } public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) { - for (DexReference reference : noShrinking.keySet()) { - if (reference.isDexField()) { - DexField field = reference.asDexField(); - DexEncodedField encodedField = appInfo.definitionFor(field); - if (encodedField != null - && (encodedField.isStatic() || isKeptDirectlyOrIndirectly(field.holder, appInfo))) { - assert appInfo.isFieldRead(encodedField) - : "Expected kept field `" + field.toSourceString() + "` to be read"; - assert appInfo.isFieldWritten(encodedField) - : "Expected kept field `" + field.toSourceString() + "` to be written"; - } - } - } + noShrinking.forEachField( + reference -> { + DexClass holder = appInfo.definitionForHolder(reference); + DexEncodedField field = reference.lookupOnClass(holder); + if (field != null + && (field.isStatic() || isKeptDirectlyOrIndirectly(field.holder(), appInfo))) { + assert appInfo.isFieldRead(field) + : "Expected kept field `" + field.toSourceString() + "` to be read"; + assert appInfo.isFieldWritten(field) + : "Expected kept field `" + field.toSourceString() + "` to be written"; + } + }); return true; } public boolean verifyKeptMethodsAreTargetedAndLive(AppInfoWithLiveness appInfo) { - for (DexReference reference : noShrinking.keySet()) { - if (reference.isDexMethod()) { - DexMethod method = reference.asDexMethod(); - assert appInfo.targetedMethods.contains(method) - : "Expected kept method `" + method.toSourceString() + "` to be targeted"; - DexEncodedMethod encodedMethod = appInfo.definitionFor(method); - if (!encodedMethod.accessFlags.isAbstract() - && isKeptDirectlyOrIndirectly(method.holder, appInfo)) { - assert appInfo.liveMethods.contains(method) - : "Expected non-abstract kept method `" - + method.toSourceString() - + "` to be live"; - } - } - } + noShrinking.forEachMethod( + reference -> { + assert appInfo.targetedMethods.contains(reference) + : "Expected kept method `" + reference.toSourceString() + "` to be targeted"; + DexEncodedMethod method = + appInfo.definitionForHolder(reference).lookupMethod(reference); + if (!method.isAbstract() && isKeptDirectlyOrIndirectly(method.holder(), appInfo)) { + assert appInfo.liveMethods.contains(reference) + : "Expected non-abstract kept method `" + + reference.toSourceString() + + "` to be live"; + } + }); return true; } public boolean verifyKeptTypesAreLive(AppInfoWithLiveness appInfo) { - for (DexReference reference : noShrinking.keySet()) { - if (reference.isDexType()) { - DexType type = reference.asDexType(); - assert appInfo.isLiveProgramType(type) - : "Expected kept type `" + type.toSourceString() + "` to be live"; - } - } + noShrinking.forEachClass( + type -> { + assert appInfo.isLiveProgramType(type) + : "Expected kept type `" + type.toSourceString() + "` to be live"; + }); return true; } private boolean isKeptDirectlyOrIndirectly(DexType type, AppInfoWithLiveness appInfo) { - if (noShrinking.containsKey(type)) { + if (noShrinking.containsClass(type)) { return true; } DexClass clazz = appInfo.definitionFor(type); @@ -1643,41 +1950,34 @@ } public boolean verifyKeptItemsAreKept(DexApplication application, AppInfo appInfo) { - Set<DexReference> pinnedItems = - appInfo.hasLiveness() ? appInfo.withLiveness().pinnedItems : null; - // Create a mapping from each required type to the set of required members on that type. - Map<DexType, Set<DexReference>> requiredReferencesPerType = new IdentityHashMap<>(); - for (DexReference reference : noShrinking.keySet()) { - // Check that `pinnedItems` is a super set of the root set. - assert pinnedItems == null || pinnedItems.contains(reference) - : "Expected reference `" + reference.toSourceString() + "` to be pinned"; - if (reference.isDexType()) { - DexType type = reference.asDexType(); - requiredReferencesPerType.putIfAbsent(type, Sets.newIdentityHashSet()); - } else { - assert reference.isDexField() || reference.isDexMethod(); - DexType holder = - reference.isDexField() - ? reference.asDexField().holder - : reference.asDexMethod().holder; - requiredReferencesPerType - .computeIfAbsent(holder, key -> Sets.newIdentityHashSet()) - .add(reference); - } - } + Map<DexType, Set<DexMember<?, ?>>> requiredMembersPerType = new IdentityHashMap<>(); + noShrinking.forEachClass( + type -> { + assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(type) + : "Expected reference `" + type.toSourceString() + "` to be pinned"; + requiredMembersPerType.computeIfAbsent(type, key -> Sets.newIdentityHashSet()); + }); + noShrinking.forEachMember( + member -> { + assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(member) + : "Expected reference `" + member.toSourceString() + "` to be pinned"; + requiredMembersPerType + .computeIfAbsent(member.holder, key -> Sets.newIdentityHashSet()) + .add(member); + }); // Run through each class in the program and check that it has members it must have. for (DexProgramClass clazz : application.classes()) { - Set<DexReference> requiredReferences = - requiredReferencesPerType.getOrDefault(clazz.type, ImmutableSet.of()); + Set<DexMember<?, ?>> requiredMembers = + requiredMembersPerType.getOrDefault(clazz.type, ImmutableSet.of()); Set<DexField> fields = null; Set<DexMethod> methods = null; - for (DexReference requiredReference : requiredReferences) { - if (requiredReference.isDexField()) { - DexField requiredField = requiredReference.asDexField(); + for (DexMember<?, ?> requiredMember : requiredMembers) { + if (requiredMember.isDexField()) { + DexField requiredField = requiredMember.asDexField(); if (fields == null) { // Create a Set of the fields to avoid quadratic behavior. fields = @@ -1689,8 +1989,8 @@ : "Expected field `" + requiredField.toSourceString() + "` from the root set to be present"; - } else if (requiredReference.isDexMethod()) { - DexMethod requiredMethod = requiredReference.asDexMethod(); + } else { + DexMethod requiredMethod = requiredMember.asDexMethod(); if (methods == null) { // Create a Set of the methods to avoid quadratic behavior. methods = @@ -1702,20 +2002,18 @@ : "Expected method `" + requiredMethod.toSourceString() + "` from the root set to be present"; - } else { - assert false; } } - requiredReferencesPerType.remove(clazz.type); + requiredMembersPerType.remove(clazz.type); } // If the map is non-empty, then a type in the root set was not in the application. - if (!requiredReferencesPerType.isEmpty()) { - DexType type = requiredReferencesPerType.keySet().iterator().next(); + if (!requiredMembersPerType.isEmpty()) { + DexType type = requiredMembersPerType.keySet().iterator().next(); DexClass clazz = application.definitionFor(type); assert clazz == null || clazz.isProgramClass() : "Unexpected library type in root set: `" + type + "`"; - assert requiredReferencesPerType.isEmpty() + assert requiredMembersPerType.isEmpty() : "Expected type `" + type.toSourceString() + "` to be present"; } @@ -1726,7 +2024,6 @@ public String toString() { StringBuilder builder = new StringBuilder(); builder.append("RootSet"); - builder.append("\nnoShrinking: " + noShrinking.size()); builder.append("\nnoObfuscation: " + noObfuscation.size()); builder.append("\nreasonAsked: " + reasonAsked.size()); @@ -1736,13 +2033,6 @@ builder.append("\ndependentNoShrinking: " + dependentNoShrinking.size()); builder.append("\nidentifierNameStrings: " + identifierNameStrings.size()); builder.append("\nifRules: " + ifRules.size()); - - builder.append("\n\nNo Shrinking:"); - noShrinking.keySet().stream() - .sorted(Comparator.comparing(DexReference::toSourceString)) - .forEach(a -> builder - .append("\n").append(a.toSourceString()).append(" ").append(noShrinking.get(a))); - builder.append("\n"); return builder.toString(); } } @@ -1754,9 +2044,9 @@ ConsequentRootSet( Set<DexMethod> neverInline, Set<DexType> neverClassInline, - Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking, + MutableItemsWithRules noShrinking, Set<DexReference> noObfuscation, - Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking, + Map<DexReference, MutableItemsWithRules> dependentNoShrinking, Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule, List<DelayedRootSetActionItem> delayedRootSetActionItems) { super(
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java index 346caa4..ec2ced1 100644 --- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java +++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -178,7 +178,7 @@ clazz.setStaticFields(reachableStaticFields); } clazz.removeInnerClasses(this::isAttributeReferencingPrunedType); - clazz.removeEnclosingMethod(this::isAttributeReferencingPrunedItem); + clazz.removeEnclosingMethodAttribute(this::isAttributeReferencingPrunedItem); rewriteNestAttributes(clazz); usagePrinter.visited(); assert verifyNoDeadFields(clazz); @@ -300,8 +300,7 @@ // Final classes cannot be abstract, so we have to keep the method in that case. // Also some other kinds of methods cannot be abstract, so keep them around. boolean allowAbstract = - (!options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug() - || clazz.isAbstract()) + (options.canUseAbstractMethodOnNonAbstractClass() || clazz.isAbstract()) && !method.isFinal() && !method.accessFlags.isNative() && !method.accessFlags.isStrict()
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java index da9038d..bd3b4f8 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -75,14 +75,12 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; 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; -import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -273,12 +271,12 @@ // For all pinned fields, also pin the type of the field (because changing the type of the field // implicitly changes the signature of the field). Similarly, for all pinned methods, also pin // the return type and the parameter types of the method. - extractPinnedItems(appInfo.pinnedItems, AbortReason.PINNED_SOURCE); - - // TODO(christofferqa): Remove the invariant that the graph lense should not modify any - // methods from the sets alwaysInline and noSideEffects (see use of assertNotModified). - extractPinnedItems(appInfo.alwaysInline, AbortReason.ALWAYS_INLINE); - extractPinnedItems(appInfo.noSideEffects.keySet(), AbortReason.NO_SIDE_EFFECTS); + // TODO(b/156715504): Compute referenced-by-pinned in the keep info objects. + List<DexReference> pinnedItems = new ArrayList<>(); + appInfo.getKeepInfo().forEachPinnedType(pinnedItems::add); + appInfo.getKeepInfo().forEachPinnedMethod(pinnedItems::add); + appInfo.getKeepInfo().forEachPinnedField(pinnedItems::add); + extractPinnedItems(pinnedItems, AbortReason.PINNED_SOURCE); for (DexProgramClass clazz : classes) { for (DexEncodedMethod method : clazz.methods()) { @@ -396,7 +394,8 @@ if (result.shouldBreak()) { return false; } - if (sourceClass.getEnclosingMethod() != null || !sourceClass.getInnerClasses().isEmpty()) { + if (sourceClass.getEnclosingMethodAttribute() != null + || !sourceClass.getInnerClasses().isEmpty()) { // TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes. if (Log.ENABLED) { AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(sourceClass); @@ -454,7 +453,8 @@ } return false; } - if (targetClass.getEnclosingMethod() != null || !targetClass.getInnerClasses().isEmpty()) { + if (targetClass.getEnclosingMethodAttribute() != null + || !targetClass.getInnerClasses().isEmpty()) { // TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes. if (Log.ENABLED) { AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(sourceClass); @@ -646,14 +646,6 @@ } private boolean verifyGraphLens(VerticalClassMergerGraphLense graphLense) { - assert graphLense.assertDefinitionsNotModified( - appInfo.alwaysInline.stream() - .map(appInfo::definitionFor) - .filter(Objects::nonNull) - .collect(Collectors.toList())); - - assert graphLense.assertReferencesNotModified(appInfo.noSideEffects.keySet()); - // Note that the method assertReferencesNotModified() relies on getRenamedFieldSignature() and // getRenamedMethodSignature() instead of lookupField() and lookupMethod(). This is important // for this check to succeed, since it is not guaranteed that calling lookupMethod() with a @@ -680,7 +672,7 @@ // that `invoke-super A.method` instructions, which are in one of the methods from C, needs to // be rewritten to `invoke-direct C.method$B`. This is valid even though A.method() is actually // pinned, because this rewriting does not affect A.method() in any way. - assert graphLense.assertReferencesNotModified(appInfo.pinnedItems); + assert graphLense.assertPinnedNotModified(appInfo.getKeepInfo()); for (DexProgramClass clazz : appInfo.classes()) { for (DexEncodedMethod encodedMethod : clazz.methods()) { @@ -1783,10 +1775,10 @@ private boolean foundIllegalAccess; private ProgramMethod context; - private final AppView<?> appView; + private final AppView<AppInfoWithLiveness> appView; private final DexClass source; - public IllegalAccessDetector(AppView<?> appView, DexClass source) { + public IllegalAccessDetector(AppView<AppInfoWithLiveness> appView, DexClass source) { super(appView.dexItemFactory()); this.appView = appView; this.source = source; @@ -1808,7 +1800,7 @@ checkTypeReference(field.holder); checkTypeReference(field.type); - DexEncodedField definition = appView.definitionFor(field); + DexEncodedField definition = appView.appInfo().resolveField(field).getResolvedField(); if (definition == null || !definition.accessFlags.isPublic()) { foundIllegalAccess = true; } @@ -1817,7 +1809,7 @@ return true; } - private boolean checkMethodReference(DexMethod method) { + private boolean checkMethodReference(DexMethod method, OptionalBool isInterface) { if (!foundIllegalAccess) { DexType baseType = appView.graphLense().lookupType(method.holder.toBaseType(appView.dexItemFactory())); @@ -1827,8 +1819,12 @@ for (DexType type : method.proto.parameters.values) { checkTypeReference(type); } - DexEncodedMethod definition = appView.definitionFor(method); - if (definition == null || !definition.accessFlags.isPublic()) { + ResolutionResult resolutionResult = + isInterface.isUnknown() + ? appView.appInfo().unsafeResolveMethodDueToDexFormat(method) + : appView.appInfo().resolveMethod(method, isInterface.isTrue()); + if (!resolutionResult.isSingleResolution() + || !resolutionResult.asSingleResolution().getResolvedMethod().isPublic()) { foundIllegalAccess = true; } } @@ -1860,7 +1856,7 @@ assert context != null; GraphLenseLookupResult lookup = appView.graphLense().lookupMethod(method, context.getReference(), Type.VIRTUAL); - return checkMethodReference(lookup.getMethod()); + return checkMethodReference(lookup.getMethod(), OptionalBool.FALSE); } @Override @@ -1868,7 +1864,7 @@ assert context != null; GraphLenseLookupResult lookup = appView.graphLense().lookupMethod(method, context.getReference(), Type.DIRECT); - return checkMethodReference(lookup.getMethod()); + return checkMethodReference(lookup.getMethod(), OptionalBool.UNKNOWN); } @Override @@ -1876,7 +1872,7 @@ assert context != null; GraphLenseLookupResult lookup = appView.graphLense().lookupMethod(method, context.getReference(), Type.STATIC); - return checkMethodReference(lookup.getMethod()); + return checkMethodReference(lookup.getMethod(), OptionalBool.UNKNOWN); } @Override @@ -1884,7 +1880,7 @@ assert context != null; GraphLenseLookupResult lookup = appView.graphLense().lookupMethod(method, context.getReference(), Type.INTERFACE); - return checkMethodReference(lookup.getMethod()); + return checkMethodReference(lookup.getMethod(), OptionalBool.TRUE); } @Override @@ -1892,7 +1888,7 @@ assert context != null; GraphLenseLookupResult lookup = appView.graphLense().lookupMethod(method, context.getReference(), Type.SUPER); - return checkMethodReference(lookup.getMethod()); + return checkMethodReference(lookup.getMethod(), OptionalBool.UNKNOWN); } @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java index 1202384..2a0a843 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -17,7 +17,6 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; @@ -55,8 +54,6 @@ private Set<DexMethod> mergedMethods; private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges; - private Map<DexMethod, Set<DexType>> contextsForContextSensitiveMethods; - private VerticalClassMergerGraphLense( AppView<?> appView, Map<DexType, DexType> typeMap, @@ -82,24 +79,6 @@ this.originalMethodSignaturesForBridges = originalMethodSignaturesForBridges; } - public void initializeCacheForLookupMethodInAllContexts() { - assert contextsForContextSensitiveMethods == null; - contextsForContextSensitiveMethods = new IdentityHashMap<>(); - contextualVirtualToDirectMethodMaps.forEach( - (type, virtualToDirectMethodMap) -> { - for (DexMethod method : virtualToDirectMethodMap.keySet()) { - contextsForContextSensitiveMethods - .computeIfAbsent(method, ignore -> Sets.newIdentityHashSet()) - .add(type); - } - }); - } - - public void unsetCacheForLookupMethodInAllContexts() { - assert contextsForContextSensitiveMethods != null; - contextsForContextSensitiveMethods = null; - } - @Override public DexType getOriginalType(DexType type) { return previousLense.getOriginalType(type); @@ -145,22 +124,6 @@ } @Override - public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) { - assert contextsForContextSensitiveMethods != null; - ImmutableSet.Builder<DexMethod> builder = ImmutableSet.builder(); - for (DexMethod previous : previousLense.lookupMethodInAllContexts(method)) { - builder.add(methodMap.getOrDefault(previous, previous)); - Set<DexType> contexts = contextsForContextSensitiveMethods.get(previous); - if (contexts != null) { - for (DexType context : contexts) { - builder.add(contextualVirtualToDirectMethodMaps.get(context).get(previous).getMethod()); - } - } - } - return builder.build(); - } - - @Override public boolean isContextFreeForMethods() { return contextualVirtualToDirectMethodMaps.isEmpty() && previousLense.isContextFreeForMethods(); }
diff --git a/src/main/java/com/android/tools/r8/utils/AssertionUtils.java b/src/main/java/com/android/tools/r8/utils/AssertionUtils.java new file mode 100644 index 0000000..c185096 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/AssertionUtils.java
@@ -0,0 +1,13 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.utils; + +public class AssertionUtils { + + public static boolean assertNotNull(Object o) { + assert o != null; + return true; + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java index 196091e..2d78c0d 100644 --- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java +++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -369,13 +369,6 @@ return 'L' + className.replace(JAVA_PACKAGE_SEPARATOR, INNER_CLASS_SEPARATOR) + ';'; } - // TODO(b/151195430): Remove once a new version of kotlinx-metadata is released. - // Kotlin @Metadata deserialization has plain "kotlin", which will be relocated in r8lib. - // See b/70169921#comment25 for more details. - private static String backwardRelocatedName(String name) { - return name.replace("com/android/tools/r8/jetbrains/", ""); - } - /** * Get unqualified class name from its binary name. * @@ -512,6 +505,11 @@ return 'L' + descriptor + ';'; } + public static boolean isValidBinaryName(String binaryName) { + return isValidJavaType( + binaryName.replace(DESCRIPTOR_PACKAGE_SEPARATOR, JAVA_PACKAGE_SEPARATOR)); + } + public static class ModuleAndDescriptor { private final String module; private final String descriptor;
diff --git a/src/main/java/com/android/tools/r8/utils/FunctionUtils.java b/src/main/java/com/android/tools/r8/utils/FunctionUtils.java new file mode 100644 index 0000000..64e8537 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/FunctionUtils.java
@@ -0,0 +1,18 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.utils; + +import java.util.function.Consumer; +import java.util.function.Function; + +public class FunctionUtils { + + public static <T, R> void forEachApply( + Iterable<T> list, Function<T, Consumer<R>> func, R argument) { + for (T t : list) { + func.apply(t).accept(argument); + } + } +}
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 8c92acf..00a2136 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -172,14 +172,12 @@ void disableAllOptimizations() { disableGlobalOptimizations(); - enableNonNullTracking = false; enableNameReflectionOptimization = false; enableStringConcatenationOptimization = false; } public void disableGlobalOptimizations() { enableArgumentRemoval = false; - enableDynamicTypeOptimization = false; enableInlining = false; enableClassInlining = false; enableClassStaticizer = false; @@ -215,7 +213,6 @@ public boolean libraryInterfacesMayHaveStaticInitialization = false; // Optimization-related flags. These should conform to -dontoptimize and disableAllOptimizations. - public boolean enableDynamicTypeOptimization = true; public boolean enableFieldAssignmentTracker = true; public boolean enableFieldBitAccessAnalysis = System.getProperty("com.android.tools.r8.fieldBitAccessAnalysis") != null; @@ -224,7 +221,6 @@ public boolean enableArgumentRemoval = true; public boolean enableUnusedInterfaceRemoval = true; public boolean enableDevirtualization = true; - public boolean enableNonNullTracking = true; public boolean enableInlining = !Version.isDevelopmentVersion() || System.getProperty("com.android.tools.r8.disableinlining") == null; @@ -1250,6 +1246,22 @@ return minApiLevel >= level.getLevel(); } + /** + * Dex2Oat issues a warning for abstract methods on non-abstract classes, so we never allow this. + * + * <p>Note that having an invoke instruction that targets an abstract method on a non-abstract + * class will fail with a verification error on Dalvik. Therefore, this must not be more + * permissive than {@code return minApiLevel >= AndroidApiLevel.L.getLevel()}. + * + * <p>See b/132953944. + */ + @SuppressWarnings("ConstantConditions") + public boolean canUseAbstractMethodOnNonAbstractClass() { + boolean result = false; + assert !(result && canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()); + return result; + } + public boolean canUseConstClassInstructions(int cfVersion) { assert isGeneratingClassFiles(); return cfVersion >= requiredCfVersionForConstClassInstructions(); @@ -1673,7 +1685,7 @@ // // See b/132953944. public boolean canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug() { - return minApiLevel < AndroidApiLevel.L.getLevel(); + return isGeneratingDex() && minApiLevel < AndroidApiLevel.L.getLevel(); } // On dalvik we see issues when using an int value in places where a boolean, byte, char, or short
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java index a785262b..e1572f5 100644 --- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java +++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -408,7 +408,14 @@ || !Objects.equals(mp.caller, lastPosition.caller)) { break; } - lastPosition = mp; + // The mapped positions are not guaranteed to be in order, so maintain first and last + // position. + if (firstPosition.obfuscatedLine > mp.obfuscatedLine) { + firstPosition = mp; + } + if (lastPosition.obfuscatedLine < mp.obfuscatedLine) { + lastPosition = mp; + } } Range obfuscatedRange = new Range(firstPosition.obfuscatedLine, lastPosition.obfuscatedLine);
diff --git a/src/main/java/com/android/tools/r8/utils/OriginWithPosition.java b/src/main/java/com/android/tools/r8/utils/OriginWithPosition.java new file mode 100644 index 0000000..8459d19 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/OriginWithPosition.java
@@ -0,0 +1,40 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils; + +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.position.Position; +import java.util.Objects; + +public class OriginWithPosition { + private final Origin origin; + private final Position position; + + public OriginWithPosition(Origin origin, Position position) { + this.origin = origin; + this.position = position; + } + + public Origin getOrigin() { + return origin; + } + + public Position getPosition() { + return position; + } + + @Override + public int hashCode() { + return origin.hashCode() * 13 + position.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other instanceof OriginWithPosition) { + return Objects.equals(((OriginWithPosition) other).origin, origin) + && Objects.equals(((OriginWithPosition) other).position, position); + } + return false; + } +}
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 c961003..89880cc 100644 --- a/src/main/java/com/android/tools/r8/utils/SetUtils.java +++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -24,6 +24,13 @@ return result; } + public static <T> Set<T> newIdentityHashSet(Iterable<T> c1, Iterable<T> c2) { + Set<T> result = Sets.newIdentityHashSet(); + c1.forEach(result::add); + c2.forEach(result::add); + return result; + } + public static <T> Set<T> newIdentityHashSet(int capacity) { return Collections.newSetFromMap(new IdentityHashMap<>(capacity)); }
diff --git a/src/main/java/com/android/tools/r8/utils/TriPredicate.java b/src/main/java/com/android/tools/r8/utils/TriPredicate.java new file mode 100644 index 0000000..42b76a7 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/TriPredicate.java
@@ -0,0 +1,9 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils; + +public interface TriPredicate<S, T, U> { + + boolean test(S s, T t, U u); +}
diff --git a/src/test/examplesProto/proto2/BuilderOnlyReferencedFromDynamicMethodTestClass.java b/src/test/examplesProto/proto2/BuilderOnlyReferencedFromDynamicMethodTestClass.java new file mode 100644 index 0000000..e3707dc --- /dev/null +++ b/src/test/examplesProto/proto2/BuilderOnlyReferencedFromDynamicMethodTestClass.java
@@ -0,0 +1,24 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package proto2; + +import com.android.tools.r8.proto2.TestProto.Primitives; +import com.google.protobuf.GeneratedMessageLite; +import com.google.protobuf.InvalidProtocolBufferException; + +public class BuilderOnlyReferencedFromDynamicMethodTestClass { + + public static void main(String[] args) { + GeneratedMessageLite<?, ?> primitivesInDisguise; + try { + primitivesInDisguise = Primitives.parseFrom(new byte[0]); + } catch (InvalidProtocolBufferException e) { + System.out.println("Unexpected exception: " + e); + throw new RuntimeException(e); + } + Primitives primitives = (Primitives) primitivesInDisguise.toBuilder().build(); + Printer.print(primitives); + } +}
diff --git a/src/test/java/com/android/tools/r8/AssumeNoSideEffects.java b/src/test/java/com/android/tools/r8/AssumeNoSideEffects.java new file mode 100644 index 0000000..0513e17 --- /dev/null +++ b/src/test/java/com/android/tools/r8/AssumeNoSideEffects.java
@@ -0,0 +1,10 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) +public @interface AssumeNoSideEffects {}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java index ce051a5..f395118 100644 --- a/src/test/java/com/android/tools/r8/D8CommandTest.java +++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -37,14 +37,20 @@ import java.util.List; import java.util.Set; import java.util.zip.ZipFile; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; -public class D8CommandTest { +@RunWith(Parameterized.class) +public class D8CommandTest extends TestBase { - @Rule - public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest(); + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public D8CommandTest(TestParameters parameters) {} @Test(expected = CompilationFailedException.class) public void emptyBuilder() throws Throwable { @@ -267,6 +273,12 @@ parse("--main-dex-list", mainDexList.toString()); } + @Test + public void testFlagFilePerClass() throws Throwable { + D8Command command = parse("--file-per-class"); + assertTrue(command.getProgramConsumer() instanceof DexFilePerClassFileConsumer); + } + @Test(expected = CompilationFailedException.class) public void mainDexListWithFilePerClass() throws Throwable { Path mainDexList = temp.newFile("main-dex-list.txt").toPath(); @@ -274,6 +286,19 @@ assertTrue(ToolHelper.getApp(command).hasMainDexListResources()); } + @Test + public void testFlagFilePerClassFile() throws Throwable { + D8Command command = parse("--file-per-class-file"); + assertTrue(command.getProgramConsumer() instanceof DexFilePerClassFileConsumer); + } + + @Test(expected = CompilationFailedException.class) + public void mainDexListWithFilePerClassFile() throws Throwable { + Path mainDexList = temp.newFile("main-dex-list.txt").toPath(); + D8Command command = parse("--main-dex-list", mainDexList.toString(), "--file-per-class-file"); + assertTrue(ToolHelper.getApp(command).hasMainDexListResources()); + } + @Test(expected = CompilationFailedException.class) public void mainDexListWithIntermediate() throws Throwable { Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java index 5f6b792..70518d7 100644 --- a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java +++ b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
@@ -4,6 +4,7 @@ package com.android.tools.r8; import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.position.Position; import com.android.tools.r8.utils.ExceptionDiagnostic; import org.hamcrest.Description; import org.hamcrest.Matcher; @@ -64,7 +65,21 @@ @Override protected void explain(Description description) { - description.appendText("orgin ").appendText(origin.toString()); + description.appendText("origin ").appendText(origin.toString()); + } + }; + } + + public static Matcher<Diagnostic> diagnosticPosition(Position position) { + return new DiagnosticsMatcher() { + @Override + protected boolean eval(Diagnostic diagnostic) { + return diagnostic.getPosition().equals(position); + } + + @Override + protected void explain(Description description) { + description.appendText("position ").appendText(position.getDescription()); } }; }
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java index 8bc4820..18571d0 100644 --- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java +++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.debug.CfDebugTestConfig; import com.android.tools.r8.debug.DebugTestConfig; import com.android.tools.r8.errors.Unimplemented; +import com.android.tools.r8.testing.AndroidBuildVersion; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.FileUtils; import com.google.common.collect.ObjectArrays; @@ -150,4 +151,9 @@ public JvmTestBuilder addVmArguments(String... arguments) { return addVmArguments(Arrays.asList(arguments)); } + + public JvmTestBuilder addAndroidBuildVersion() { + addVmArguments("-D" + AndroidBuildVersion.PROPERTY + "=10000"); + return addProgramClasses(AndroidBuildVersion.class); + } }
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java index 705ecff..1631830 100644 --- a/src/test/java/com/android/tools/r8/L8CommandTest.java +++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8; +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -15,6 +17,7 @@ import com.android.tools.r8.origin.EmbeddedOrigin; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.FileUtils; import com.android.tools.r8.utils.ThreadUtils; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -24,14 +27,20 @@ import java.util.Collections; import java.util.List; import org.junit.Ignore; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; -public class L8CommandTest { +@RunWith(Parameterized.class) +public class L8CommandTest extends TestBase { - @Rule - public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest(); + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public L8CommandTest(TestParameters parameters) {} @Test(expected = CompilationFailedException.class) public void emptyBuilder() throws Throwable { @@ -95,6 +104,34 @@ Marker marker = markers.iterator().next(); } + @Test + public void testFlagPgConf() throws Exception { + TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl(); + Path pgconf = temp.newFolder().toPath().resolve("pg.conf"); + FileUtils.writeTextFile(pgconf, ""); + parse( + diagnostics, + "--desugared-lib", + ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString(), + "--pg-conf", + pgconf.toString()); + } + + @Test + public void testFlagPgConfMissingParameter() { + TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl(); + try { + parse( + diagnostics, + "--desugared-lib", + ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString(), + "--pg-conf"); + fail("Expected parse error"); + } catch (CompilationFailedException e) { + diagnostics.assertErrorsMatch(diagnosticMessage(containsString("Missing parameter"))); + } + } + private L8Command.Builder prepareBuilder(DiagnosticsHandler handler) { return L8Command.builder(handler) .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java index b5c518c..149c3c3 100644 --- a/src/test/java/com/android/tools/r8/R8CommandTest.java +++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -3,7 +3,9 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8; +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; import static com.android.tools.r8.ToolHelper.EXAMPLES_BUILD_DIR; +import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -37,14 +39,20 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; -public class R8CommandTest { +@RunWith(Parameterized.class) +public class R8CommandTest extends TestBase { - @Rule - public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest(); + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public R8CommandTest(TestParameters parameters) {} @Test(expected = CompilationFailedException.class) public void emptyBuilder() throws Throwable { @@ -759,6 +767,34 @@ } @Test + public void desugaredLibraryWithOutputConf() throws CompilationFailedException { + Path pgout = temp.getRoot().toPath().resolve("pgout.conf"); + R8Command r8Command = + parse( + "--desugared-lib", + "src/library_desugar/desugar_jdk_libs.json", + "--desugared-lib-pg-conf-output", + pgout.toString()); + assertFalse( + r8Command.getInternalOptions().desugaredLibraryConfiguration.getRewritePrefix().isEmpty()); + } + + @Test + public void desugaredLibraryWithOutputConfMissingArg() { + TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl(); + try { + parse( + diagnostics, + "--desugared-lib", + "src/library_desugar/desugar_jdk_libs.json", + "--desugared-lib-pg-conf-output"); + fail("Expected parse error"); + } catch (CompilationFailedException e) { + diagnostics.assertErrorsMatch(diagnosticMessage(containsString("Missing parameter"))); + } + } + + @Test public void numThreadsOption() throws Exception { assertEquals(ThreadUtils.NOT_SPECIFIED, parse().getThreadCount()); assertEquals(1, parse("--thread-count", "1").getThreadCount());
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java index 6326718..eddf1ee 100644 --- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java +++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -513,9 +513,6 @@ // Addition of checks for super-class-initialization cause this to abort on non-ToT art. "008-exceptions", - // Fails due to non-matching Exception messages. - "201-built-in-except-detail-messages", - // Generally fails on non-R8/D8 running. "156-register-dex-file-multi-loader", "412-new-array", @@ -525,9 +522,6 @@ // Addition of checks for super-class-initialization cause this to abort on non-ToT art. "008-exceptions", - // Fails due to non-matching Exception messages. - "201-built-in-except-detail-messages", - // Generally fails on non-R8/D8 running. "004-checker-UnsafeTest18", "005-annotations", @@ -544,9 +538,6 @@ // Addition of checks for super-class-initialization cause this to abort on non-ToT art. "008-exceptions", - // Fails due to non-matching Exception messages. - "201-built-in-except-detail-messages", - // Generally fails on non R8/D8 running. "004-checker-UnsafeTest18", "004-NativeAllocations", @@ -567,9 +558,6 @@ // Addition of checks for super-class-initialization cause this to abort on non-ToT art. "008-exceptions", - // Fails due to non-matching Exception messages. - "201-built-in-except-detail-messages", - // Generally fails on non R8/D8 running. "004-checker-UnsafeTest18", "004-NativeAllocations", @@ -590,9 +578,6 @@ // Addition of checks for super-class-initialization cause this to abort on non-ToT art. "008-exceptions", - // Fails due to non-matching Exception messages. - "201-built-in-except-detail-messages", - // Generally fails on non R8/D8 running. "004-checker-UnsafeTest18", "004-NativeAllocations", @@ -821,6 +806,24 @@ .put("138-duplicate-classes-check", TestCondition.any()) // Array index out of bounds exception. .put("150-loadlibrary", TestCondition.any()) + // Fails due to non-matching Exception messages. + .put( + "201-built-in-except-detail-messages", + TestCondition.or( + TestCondition.match( + TestCondition.compilers( + CompilerUnderTest.D8, CompilerUnderTest.D8_AFTER_R8CF), + TestCondition.runtimes( + DexVm.Version.V4_0_4, + DexVm.Version.V4_4_4, + DexVm.Version.V5_1_1, + DexVm.Version.V6_0_1, + DexVm.Version.V7_0_0)), + TestCondition.match( + TestCondition.compilers( + CompilerUnderTest.R8, + CompilerUnderTest.R8CF, + CompilerUnderTest.R8_AFTER_D8)))) // Uses dex file version 37 and therefore only runs on Android N and above. .put( "370-dex-v37", @@ -1170,13 +1173,6 @@ "435-new-instance" ); - private static List<String> requireUninstantiatedTypeOptimizationToBeDisabled = ImmutableList.of( - // This test inspects the message of the exception that is thrown when calling a virtual - // method with a null-receiver. This message changes when the invocation is rewritten to - // "throw null". - "201-built-in-except-detail-messages" - ); - private static List<String> hasMissingClasses = ImmutableList.of( "091-override-package-private-method", "003-omnibus-opcodes", @@ -1279,8 +1275,6 @@ private final boolean disableInlining; // Whether to disable class inlining private final boolean disableClassInlining; - // Whether to disable the uninitialized type optimization. - private final boolean disableUninstantiatedTypeOptimization; // Has missing classes. private final boolean hasMissingClasses; // Explicitly disable desugaring. @@ -1304,7 +1298,6 @@ boolean outputMayDiffer, boolean disableInlining, boolean disableClassInlining, - boolean disableUninstantiatedTypeOptimization, boolean hasMissingClasses, boolean disableDesugaring, List<String> keepRules, @@ -1323,7 +1316,6 @@ this.outputMayDiffer = outputMayDiffer; this.disableInlining = disableInlining; this.disableClassInlining = disableClassInlining; - this.disableUninstantiatedTypeOptimization = disableUninstantiatedTypeOptimization; this.hasMissingClasses = hasMissingClasses; this.disableDesugaring = disableDesugaring; this.keepRules = keepRules; @@ -1354,7 +1346,6 @@ disableInlining, true, // Disable class inlining for JCTF tests. false, - false, true, // Disable desugaring for JCTF tests. ImmutableList.of(), null); @@ -1383,7 +1374,6 @@ disableInlining, true, // Disable class inlining for JCTF tests. false, - false, true, // Disable desugaring for JCTF tests. ImmutableList.of(), null); @@ -1548,7 +1538,6 @@ outputMayDiffer.contains(name), requireInliningToBeDisabled.contains(name), requireClassInliningToBeDisabled.contains(name), - requireUninstantiatedTypeOptimizationToBeDisabled.contains(name), hasMissingClasses.contains(name), false, keepRules.getOrDefault(name, ImmutableList.of()), @@ -1619,7 +1608,6 @@ private final boolean disableInlining; private final boolean disableClassInlining; - private final boolean disableUninstantiatedTypeOptimization; private final boolean hasMissingClasses; private final boolean disableDesugaring; private final List<String> keepRules; @@ -1628,7 +1616,6 @@ private CompilationOptions(TestSpecification spec) { this.disableInlining = spec.disableInlining; this.disableClassInlining = spec.disableClassInlining; - this.disableUninstantiatedTypeOptimization = spec.disableUninstantiatedTypeOptimization; this.hasMissingClasses = spec.hasMissingClasses; this.disableDesugaring = spec.disableDesugaring; this.keepRules = spec.keepRules; @@ -1643,9 +1630,6 @@ if (disableClassInlining) { options.enableClassInlining = false; } - if (disableUninstantiatedTypeOptimization) { - options.enableUninstantiatedTypeOptimization = false; - } // Some tests actually rely on missing classes for what they test. options.ignoreMissingClasses = hasMissingClasses; if (configuration != null) { @@ -1664,7 +1648,6 @@ CompilationOptions options = (CompilationOptions) o; return disableInlining == options.disableInlining && disableClassInlining == options.disableClassInlining - && disableUninstantiatedTypeOptimization == options.disableUninstantiatedTypeOptimization && hasMissingClasses == options.hasMissingClasses; } @@ -1673,7 +1656,6 @@ return Objects.hash( disableInlining, disableClassInlining, - disableUninstantiatedTypeOptimization, hasMissingClasses); } }
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java index a109e20..9d0684d 100644 --- a/src/test/java/com/android/tools/r8/R8TestBuilder.java +++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -48,6 +48,7 @@ private AllowedDiagnosticMessages allowedDiagnosticMessages = AllowedDiagnosticMessages.NONE; private boolean allowUnusedProguardConfigurationRules = false; + private boolean enableAssumeNoSideEffectsAnnotations = false; private boolean enableConstantArgumentAnnotations = false; private boolean enableInliningAnnotations = false; private boolean enableMemberValuePropagationAnnotations = false; @@ -135,7 +136,8 @@ box.proguardConfiguration, box.syntheticProguardRules, proguardMapBuilder.toString(), - graphConsumer); + graphConsumer, + builder.getMinApiLevel()); switch (allowedDiagnosticMessages) { case ALL: compileResult.assertDiagnosticThatMatches(new IsAnything<>()); @@ -349,6 +351,21 @@ return self(); } + public T enableAssumeNoSideEffectsAnnotations() { + return enableAssumeNoSideEffectsAnnotations(AssumeNoSideEffects.class.getPackage().getName()); + } + + public T enableAssumeNoSideEffectsAnnotations(String annotationPackageName) { + if (!enableAssumeNoSideEffectsAnnotations) { + enableAssumeNoSideEffectsAnnotations = true; + addInternalKeepRules( + "-assumenosideeffects class * { @" + + annotationPackageName + + ".AssumeNoSideEffects <methods>; }"); + } + return self(); + } + public T enableInliningAnnotations() { return enableInliningAnnotations(NeverInline.class.getPackage().getName()); }
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java index 3a16807..5dac954 100644 --- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java +++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -14,7 +14,6 @@ import java.io.IOException; import java.nio.file.Path; import java.util.List; -import java.util.concurrent.ExecutionException; import java.util.function.Consumer; public class R8TestCompileResult extends TestCompileResult<R8TestCompileResult, R8TestRunResult> { @@ -23,6 +22,7 @@ private final List<ProguardConfigurationRule> syntheticProguardRules; private final String proguardMap; private final CollectingGraphConsumer graphConsumer; + private final int minApiLevel; R8TestCompileResult( TestState state, @@ -31,12 +31,14 @@ ProguardConfiguration proguardConfiguration, List<ProguardConfigurationRule> syntheticProguardRules, String proguardMap, - CollectingGraphConsumer graphConsumer) { + CollectingGraphConsumer graphConsumer, + int minApiLevel) { super(state, app, outputMode); this.proguardConfiguration = proguardConfiguration; this.syntheticProguardRules = syntheticProguardRules; this.proguardMap = proguardMap; this.graphConsumer = graphConsumer; + this.minApiLevel = minApiLevel; } @Override @@ -65,11 +67,11 @@ } @Override - public CodeInspector inspector() throws IOException, ExecutionException { + public CodeInspector inspector() throws IOException { return new CodeInspector(app, proguardMap); } - public GraphInspector graphInspector() throws IOException, ExecutionException { + public GraphInspector graphInspector() throws IOException { assert graphConsumer != null; return new GraphInspector(graphConsumer, inspector()); }
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java index 39356e1..0816496 100644 --- a/src/test/java/com/android/tools/r8/TestCompileResult.java +++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -243,6 +243,11 @@ return self(); } + public CR setSystemProperty(String name, String value) { + vmArguments.add("-D" + name + "=" + value); + return self(); + } + public Path writeToZip() throws IOException { Path file = state.getNewTempFolder().resolve("out.zip"); writeToZip(file); @@ -429,6 +434,13 @@ withArt6Plus64BitsLib && vm.getVersion().isAtLeast(DexVm.Version.V6_0_1) ? builder -> builder.appendArtOption("--64") : builder -> {}; + commandConsumer = + commandConsumer.andThen( + builder -> { + for (String vmArgument : vmArguments) { + builder.appendArtOption(vmArgument); + } + }); ProcessResult result = ToolHelper.runArtRaw( classPath, mainClass, commandConsumer, vm, withArtFrameworks, arguments);
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java index b7869e4..490cf54 100644 --- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java +++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.debug.DebugTestConfig; import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer; import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.testing.AndroidBuildVersion; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.AndroidAppConsumers; @@ -62,6 +63,14 @@ private PrintStream oldStderr = null; protected OutputMode outputMode = OutputMode.DexIndexed; + private boolean isAndroidBuildVersionAdded = false; + + public T addAndroidBuildVersion() { + addProgramClasses(AndroidBuildVersion.class); + isAndroidBuildVersionAdded = true; + return self(); + } + TestCompilerBuilder(TestState state, B builder, Backend backend) { super(state, builder); this.backend = backend; @@ -133,6 +142,9 @@ cr = internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build)) .addRunClasspathFiles(additionalRunClassPath); + if (isAndroidBuildVersionAdded) { + cr.setSystemProperty(AndroidBuildVersion.PROPERTY, "" + builder.getMinApiLevel()); + } return cr; } finally { if (stdout != null) {
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java index ac3ef82..ed0e8f1 100644 --- a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java +++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
@@ -36,7 +36,7 @@ // Match exact. - default TestDiagnosticMessages assertDiagnosticsMatch(Matcher matcher) { + default TestDiagnosticMessages assertDiagnosticsMatch(Matcher<Diagnostic> matcher) { return assertDiagnosticsMatch(Collections.singletonList(matcher)); }
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java index 19a700d..54ad442 100644 --- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java +++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -201,7 +201,8 @@ if (!matchedDiagnostics.contains(diagnostic)) { builder .append("\n - ") - .append(diagnostics.getClass().getName()) + .append(diagnostic.getClass().getName()) + .append(": ") .append(diagnostic.getDiagnosticMessage()); } } @@ -220,7 +221,8 @@ for (Diagnostic diagnostic : diagnostics) { builder .append("\n - ") - .append(diagnostics.getClass().getName()) + .append(diagnostic.getClass().getName()) + .append(": ") .append(diagnostic.getDiagnosticMessage()); } builder.append("\nAll matchers:");
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java index bf3bb9d..16fd725 100644 --- a/src/test/java/com/android/tools/r8/TestRunResult.java +++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -113,6 +113,10 @@ return self(); } + public RR assertSuccessWithEmptyOutput() { + return assertSuccessWithOutput(""); + } + public RR assertSuccessWithOutputLines(String... expected) { return assertSuccessWithOutputLines(Arrays.asList(expected)); }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java index 4c1caf7..9a29671 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
@@ -5,66 +5,155 @@ package com.android.tools.r8.desugar.desugaredlibrary.conversiontests; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; -import com.android.tools.r8.TestRuntime.DexRuntime; -import com.android.tools.r8.ToolHelper.DexVm; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase; import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer; +import com.android.tools.r8.testing.AndroidBuildVersion; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.FoundClassSubject; +import com.google.common.collect.Sets; +import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; import java.nio.file.Path; import java.util.Arrays; +import java.util.Set; 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 WrapperMergeTest extends DesugaredLibraryTestBase { + private static final String EXPECTED = StringUtils.lines("[1, 2, 3]", "[2, 3, 4]"); + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + private final TestParameters parameters; + + public WrapperMergeTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testReference() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForJvm() + .addAndroidBuildVersion() + .addProgramClassesAndInnerClasses(MyArrays1.class) + .addProgramClassesAndInnerClasses(MyArrays2.class) + .addProgramClasses(TestClass.class) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } + @Test public void testWrapperMerge() throws Exception { + assumeTrue(parameters.isDexRuntime()); // Multiple wrapper classes have to be merged here. - Path path1 = testForD8() - .addProgramClasses(Executor1.class) - .setMinApi(AndroidApiLevel.B) - .enableCoreLibraryDesugaring(AndroidApiLevel.B) - .compile() - .inspect(this::assertWrappers) - .writeToZip(); - Path path2 = testForD8() - .addProgramClasses(Executor2.class) - .setMinApi(AndroidApiLevel.B) - .enableCoreLibraryDesugaring(AndroidApiLevel.B) - .compile() - .inspect(this::assertWrappers) - .writeToZip(); + Path path1 = compileWithCoreLibraryDesugaring(MyArrays1.class); + Path path2 = compileWithCoreLibraryDesugaring(MyArrays2.class); testForD8() .addProgramFiles(path1, path2) + .addProgramClasses(TestClass.class) + .addAndroidBuildVersion() + .enableCoreLibraryDesugaring(parameters.getApiLevel()) + .setMinApi(parameters.getApiLevel()) .compile() - .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B) - .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor1.class) - .assertSuccessWithOutput(StringUtils.lines("[1, 2, 3]")); + .inspect(this::assertWrappers) + .inspect(this::assertNoDuplicates) + .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } + + private Path compileWithCoreLibraryDesugaring(Class<?> clazz) throws Exception { + return testForD8() + .addProgramClassesAndInnerClasses(clazz) + .setMinApi(parameters.getApiLevel()) + .enableCoreLibraryDesugaring(parameters.getApiLevel()) + .compile() + .inspect(this::assertWrappers) + .writeToZip(); + } + + private void assertNoDuplicates(CodeInspector inspector) { + Object2ReferenceMap<String, Set<FoundClassSubject>> map = new Object2ReferenceOpenHashMap<>(); + for (FoundClassSubject clazz : inspector.allClasses()) { + map.computeIfAbsent(clazz.getFinalName(), k -> Sets.newIdentityHashSet()).add(clazz); + } + for (Set<FoundClassSubject> duplicates : map.values()) { + if (duplicates.size() > 1) { + fail("Unexpected duplicates: " + duplicates); + } + } + } + + private boolean hasNativeIntUnaryOperator() { + return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N); } private void assertWrappers(CodeInspector inspector) { - assertEquals(2,inspector.allClasses().stream().filter(c -> c.getOriginalName().contains( - DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)).count()); + assertEquals( + hasNativeIntUnaryOperator() ? 0 : 2, + inspector.allClasses().stream() + .filter( + c -> + c.getOriginalName().contains(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)) + .count()); } - static class Executor1 { + static class MyArrays1 { - public static void main(String[] args) { - int[] ints = new int[3]; - Arrays.setAll(ints,x->x+1); - System.out.println(Arrays.toString(ints)); + interface IntGenerator { + int generate(int index); + } + + public static void setAll(int[] ints, IntGenerator generator) { + if (AndroidBuildVersion.VERSION >= 24) { + java.util.Arrays.setAll(ints, generator::generate); + } else { + for (int i = 0; i < ints.length; i++) { + ints[i] = generator.generate(i); + } + } } } - static class Executor2 { + static class MyArrays2 { - public static void main(String[] args) { - int[] ints = new int[3]; - Arrays.setAll(ints,x->x+2); - System.out.println(Arrays.toString(ints)); + interface IntGenerator { + int generate(int index); + } + + public static void setAll(int[] ints, IntGenerator generator) { + if (AndroidBuildVersion.VERSION >= 24) { + java.util.Arrays.setAll(ints, generator::generate); + } else { + for (int i = 0; i < ints.length; i++) { + ints[i] = generator.generate(i); + } + } } } + public static class TestClass { + + public static void main(String[] args) { + int[] ints = new int[3]; + MyArrays1.setAll(ints, x -> x + 1); + System.out.println(Arrays.toString(ints)); + MyArrays2.setAll(ints, x -> x + 2); + System.out.println(Arrays.toString(ints)); + } + } }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java index 03e7f70..3b9b321 100644 --- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java +++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java
@@ -10,7 +10,6 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.ToolHelper; -import com.android.tools.r8.ToolHelper.DexVm.Version; import com.android.tools.r8.utils.codeinspector.CodeInspector; import org.junit.Test; import org.junit.runner.RunWith; @@ -20,15 +19,11 @@ @RunWith(Parameterized.class) public class Java11D8CompilationTest extends TestBase { - public Java11D8CompilationTest(TestParameters parameters) { - this.parameters = parameters; - } - - private final TestParameters parameters; + public Java11D8CompilationTest(TestParameters parameters) {} @Parameters(name = "{0}") public static TestParametersCollection data() { - return getTestParameters().withDexRuntimesStartingFromIncluding(Version.V5_1_1).build(); + return getTestParameters().withNoneRuntime().build(); } private static void assertNoNests(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java index 2cfe861..f828413 100644 --- a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java +++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
@@ -4,21 +4,13 @@ package com.android.tools.r8.enumunboxing; -import static junit.framework.TestCase.assertTrue; -import static org.junit.Assert.assertEquals; - import com.android.tools.r8.NeverClassInline; -import com.android.tools.r8.NeverInline; import com.android.tools.r8.R8FullTestBuilder; import com.android.tools.r8.R8TestCompileResult; import com.android.tools.r8.R8TestRunResult; import com.android.tools.r8.TestParameters; import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumInstanceFieldMain.EnumInstanceField; -import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumInterfaceMain.EnumInterface; import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumStaticFieldMain.EnumStaticField; -import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumStaticMethodMain.EnumStaticMethod; -import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumVirtualMethodMain.EnumVirtualMethod; -import com.android.tools.r8.utils.codeinspector.CodeInspector; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,11 +21,8 @@ public class FailingEnumUnboxingTest extends EnumUnboxingTestBase { private static final Class<?>[] FAILURES = { - EnumInterface.class, EnumStaticField.class, EnumInstanceField.class, - EnumStaticMethod.class, - EnumVirtualMethod.class }; private final TestParameters parameters; @@ -61,15 +50,12 @@ } R8TestCompileResult compile = r8FullTestBuilder - .noTreeShaking() // Disabled to avoid merging Itf into EnumInterface. - .enableInliningAnnotations() .enableNeverClassInliningAnnotations() .addKeepRules(enumKeepRules.getKeepRule()) .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization)) .allowDiagnosticInfoMessages() .setMinApi(parameters.getApiLevel()) - .compile() - .inspect(this::assertEnumsAsExpected); + .compile(); for (Class<?> failure : FAILURES) { R8TestRunResult run = compile @@ -81,44 +67,6 @@ } } - private void assertEnumsAsExpected(CodeInspector inspector) { - assertEquals(1, inspector.clazz(EnumInterface.class).getDexProgramClass().interfaces.size()); - - assertTrue(inspector.clazz(EnumStaticField.class).uniqueFieldWithName("X").isPresent()); - assertTrue(inspector.clazz(EnumInstanceField.class).uniqueFieldWithName("a").isPresent()); - - assertEquals( - 5, - inspector - .clazz(EnumStaticMethod.class) - .getDexProgramClass() - .getMethodCollection() - .numberOfDirectMethods()); - assertEquals(1, inspector.clazz(EnumVirtualMethod.class).virtualMethods().size()); - } - - static class EnumInterfaceMain { - - public static void main(String[] args) { - System.out.println(EnumInterface.A.ordinal()); - System.out.println(0); - } - - @NeverClassInline - enum EnumInterface implements Itf { - A, - B, - C - } - - interface Itf { - - default int ordinal() { - return -1; - } - } - } - static class EnumStaticFieldMain { public static void main(String[] args) { @@ -158,53 +106,4 @@ System.out.println(10); } } - - static class EnumStaticMethodMain { - - @NeverClassInline - enum EnumStaticMethod { - A, - B, - C; - - // Enum cannot be unboxed if it has a static method, we do not inline so the method is - // present. - @NeverInline - static int foo() { - return Math.addExact(-1, 0); - } - } - - public static void main(String[] args) { - System.out.println(EnumStaticMethod.A.ordinal()); - System.out.println(0); - System.out.println(EnumStaticMethod.foo()); - System.out.println(-1); - } - } - - static class EnumVirtualMethodMain { - - public static void main(String[] args) { - EnumVirtualMethod e1 = EnumVirtualMethod.A; - System.out.println(e1.ordinal()); - System.out.println(0); - System.out.println(e1.valueOf()); - System.out.println(-1); - } - - @NeverClassInline - enum EnumVirtualMethod { - A, - B, - C; - - // Enum cannot be unboxed if it has a virtual method, we do not inline so the method is - // present. - @NeverInline - int valueOf() { - return Math.addExact(-1, 0); - } - } - } }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java new file mode 100644 index 0000000..5dcf75d --- /dev/null +++ b/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
@@ -0,0 +1,270 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.enumunboxing; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NeverMerge; +import com.android.tools.r8.R8TestCompileResult; +import com.android.tools.r8.R8TestRunResult; +import com.android.tools.r8.TestParameters; +import java.util.List; +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 InterfaceEnumUnboxingTest extends EnumUnboxingTestBase { + + private static final Class<?>[] FAILURES = { + FailureDefaultMethodUsed.class, FailureUsedAsInterface.class, + }; + + private static final Class<?>[] SUCCESSES = { + SuccessAbstractMethod.class, + SuccessEmptyInterface.class, + SuccessUnusedDefaultMethod.class, + SuccessUnusedDefaultMethodOverride.class, + SuccessUnusedDefaultMethodOverrideEnum.class + }; + + private final TestParameters parameters; + private final boolean enumValueOptimization; + private final KeepRule enumKeepRules; + + @Parameters(name = "{0} valueOpt: {1} keep: {2}") + public static List<Object[]> data() { + return enumUnboxingTestParameters(); + } + + public InterfaceEnumUnboxingTest( + TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) { + this.parameters = parameters; + this.enumValueOptimization = enumValueOptimization; + this.enumKeepRules = enumKeepRules; + } + + @Test + public void testEnumUnboxingFailure() throws Exception { + R8TestCompileResult compile = + testForR8(parameters.getBackend()) + .addInnerClasses(InterfaceEnumUnboxingTest.class) + .addKeepMainRules(SUCCESSES) + .addKeepMainRules(FAILURES) + .noMinification() + .enableMergeAnnotations() + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .addKeepRules(enumKeepRules.getKeepRule()) + .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization)) + .allowDiagnosticInfoMessages() + .setMinApi(parameters.getApiLevel()) + .compile(); + for (Class<?> failure : FAILURES) { + testClass(compile, failure, true); + } + for (Class<?> success : SUCCESSES) { + testClass(compile, success, false); + } + } + + private void testClass(R8TestCompileResult compile, Class<?> testClass, boolean failure) + throws Exception { + R8TestRunResult run = + compile + .inspectDiagnosticMessages( + m -> { + for (Class<?> declaredClass : testClass.getDeclaredClasses()) { + if (declaredClass.isEnum()) { + if (failure) { + assertEnumIsBoxed(declaredClass, testClass.getSimpleName(), m); + } else { + assertEnumIsUnboxed(declaredClass, testClass.getSimpleName(), m); + } + } + } + }) + .run(parameters.getRuntime(), testClass) + .assertSuccess(); + assertLines2By2Correct(run.getStdOut()); + } + + static class SuccessEmptyInterface { + + public static void main(String[] args) { + System.out.println(EnumInterface.A.ordinal()); + System.out.println(0); + } + + @NeverClassInline + enum EnumInterface implements Itf { + A, + B, + C + } + + @NeverMerge + interface Itf {} + } + + static class SuccessUnusedDefaultMethodOverrideEnum { + + public static void main(String[] args) { + System.out.println(EnumInterface.A.ordinal()); + System.out.println(0); + } + + @NeverClassInline + enum EnumInterface implements Itf { + A, + B, + C + } + + @NeverMerge + interface Itf { + @NeverInline + default int ordinal() { + return System.currentTimeMillis() > 0 ? 3 : -3; + } + } + } + + static class SuccessUnusedDefaultMethodOverride { + + public static void main(String[] args) { + System.out.println(EnumInterface.A.method()); + System.out.println(5); + } + + @NeverClassInline + enum EnumInterface implements Itf { + A, + B, + C; + + @Override + @NeverInline + public int method() { + return System.currentTimeMillis() > 0 ? 5 : -5; + } + } + + @NeverMerge + interface Itf { + @NeverInline + default int method() { + return System.currentTimeMillis() > 0 ? 3 : -3; + } + } + } + + static class SuccessUnusedDefaultMethod { + + public static void main(String[] args) { + System.out.println(EnumInterface.A.ordinal()); + System.out.println(0); + } + + @NeverClassInline + enum EnumInterface implements Itf { + A, + B, + C + } + + @NeverMerge + interface Itf { + @NeverInline + default int method() { + return System.currentTimeMillis() > 0 ? 3 : -3; + } + } + } + + static class SuccessAbstractMethod { + + public static void main(String[] args) { + System.out.println(EnumInterface.A.method()); + System.out.println(5); + } + + @NeverClassInline + enum EnumInterface implements Itf { + A, + B, + C; + + @Override + @NeverInline + public int method() { + return System.currentTimeMillis() > 0 ? 5 : -5; + } + } + + @NeverMerge + interface Itf { + int method(); + } + } + + static class FailureDefaultMethodUsed { + + public static void main(String[] args) { + System.out.println(EnumInterface.A.method()); + System.out.println(3); + } + + @NeverClassInline + enum EnumInterface implements Itf { + A, + B, + C + } + + @NeverMerge + interface Itf { + @NeverInline + default int method() { + return System.currentTimeMillis() > 0 ? 3 : -3; + } + } + } + + static class FailureUsedAsInterface { + + public static void main(String[] args) { + print(EnumInterface.A); + System.out.println(5); + } + + @NeverInline + public static void print(Itf itf) { + System.out.println(itf.method()); + } + + @NeverClassInline + enum EnumInterface implements Itf { + A, + B, + C; + + @Override + @NeverInline + public int method() { + return System.currentTimeMillis() > 0 ? 5 : -5; + } + } + + @NeverMerge + interface Itf { + @NeverInline + default int method() { + return System.currentTimeMillis() > 0 ? 3 : -3; + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java similarity index 88% rename from src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java rename to src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java index b388ed7..8bd41c3 100644 --- a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java +++ b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
@@ -14,7 +14,7 @@ import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) -public class OrdinalEnumUnboxingTest extends EnumUnboxingTestBase { +public class OrdinalHashCodeEnumUnboxingTest extends EnumUnboxingTestBase { private static final Class<?> ENUM_CLASS = MyEnum.class; @@ -27,7 +27,7 @@ return enumUnboxingTestParameters(); } - public OrdinalEnumUnboxingTest( + public OrdinalHashCodeEnumUnboxingTest( TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) { this.parameters = parameters; this.enumValueOptimization = enumValueOptimization; @@ -36,7 +36,7 @@ @Test public void testEnumUnboxing() throws Exception { - Class<Ordinal> classToTest = Ordinal.class; + Class<?> classToTest = OrdinalHashCode.class; R8TestRunResult run = testForR8(parameters.getBackend()) .addProgramClasses(classToTest, ENUM_CLASS) @@ -61,11 +61,13 @@ C } - static class Ordinal { + static class OrdinalHashCode { public static void main(String[] args) { System.out.println(MyEnum.A.ordinal()); System.out.println(0); + System.out.println(MyEnum.A.hashCode()); + System.out.println(0); } } }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java index d8d781e..b4f97ab 100644 --- a/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java +++ b/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java
@@ -65,9 +65,13 @@ @SuppressWarnings("ConstantConditions") public static void main(String[] args) { System.out.println(MyEnum.A.toString()); + System.out.println("A"); System.out.println(MyEnum.A.name()); + System.out.println("A"); System.out.println(MyEnum.B.toString()); + System.out.println("B"); System.out.println(MyEnum.B.name()); + System.out.println("B"); try { System.out.println(((MyEnum) null).toString()); } catch (NullPointerException e) {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java new file mode 100644 index 0000000..6496c7a --- /dev/null +++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
@@ -0,0 +1,166 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.enumunboxing; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.R8TestRunResult; +import com.android.tools.r8.TestParameters; +import java.util.List; +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 VirtualMethodsEnumUnboxingTest extends EnumUnboxingTestBase { + + private final TestParameters parameters; + private final boolean enumValueOptimization; + private final KeepRule enumKeepRules; + + @Parameters(name = "{0} valueOpt: {1} keep: {2}") + public static List<Object[]> data() { + return enumUnboxingTestParameters(); + } + + public VirtualMethodsEnumUnboxingTest( + TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) { + this.parameters = parameters; + this.enumValueOptimization = enumValueOptimization; + this.enumKeepRules = enumKeepRules; + } + + @Test + public void testEnumUnboxing() throws Exception { + Class<?> classToTest = VirtualMethods.class; + R8TestRunResult run = + testForR8(parameters.getBackend()) + .addInnerClasses(VirtualMethodsEnumUnboxingTest.class) + .addKeepMainRule(classToTest) + .addKeepRules(enumKeepRules.getKeepRule()) + .enableNeverClassInliningAnnotations() + .enableInliningAnnotations() + .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization)) + .allowDiagnosticInfoMessages() + .setMinApi(parameters.getApiLevel()) + .compile() + .inspectDiagnosticMessages( + m -> { + assertEnumIsUnboxed(MyEnum.class, classToTest.getSimpleName(), m); + assertEnumIsUnboxed(MyEnum2.class, classToTest.getSimpleName(), m); + }) + .run(parameters.getRuntime(), classToTest) + .assertSuccess(); + assertLines2By2Correct(run.getStdOut()); + } + + @SuppressWarnings("SameParameterValue") + @NeverClassInline + enum MyEnum { + A, + B, + C; + + @NeverInline + public void print(Object o) { + System.out.println(o); + } + + @NeverInline + public void printEnum(MyEnum e) { + System.out.println(e.ordinal()); + } + + @NeverInline + public MyEnum returnEnum(boolean bool) { + return bool ? MyEnum.A : MyEnum.B; + } + + @NeverInline + protected void printProtected() { + System.out.println("protected"); + } + + @NeverInline + void printPackagePrivate() { + System.out.println("package-private"); + } + + @NeverInline + private void printPrivate() { + System.out.println("private"); + } + + @NeverInline + public void callPrivate() { + System.out.print("call: "); + printPrivate(); + } + } + + // Use two enums to test collision. + enum MyEnum2 { + A, + B, + C; + + @NeverInline + public void print(Object o) { + System.out.println("2" + o); + } + + @NeverInline + public void printEnum(MyEnum e) { + System.out.println("2" + e.ordinal()); + } + + @NeverInline + public MyEnum returnEnum(boolean bool) { + return bool ? MyEnum.B : MyEnum.C; + } + } + + static class VirtualMethods { + + public static void main(String[] args) { + testCustomMethods(); + testCustomMethods2(); + testNonPublicMethods(); + } + + @NeverInline + private static void testNonPublicMethods() { + MyEnum.A.printPrivate(); + System.out.println("private"); + MyEnum.A.printPackagePrivate(); + System.out.println("package-private"); + MyEnum.A.printProtected(); + System.out.println("protected"); + MyEnum.A.callPrivate(); + System.out.println("call: private"); + } + + @NeverInline + private static void testCustomMethods() { + MyEnum.A.print("print"); + System.out.println("print"); + MyEnum.A.printEnum(MyEnum.A); + System.out.println(0); + System.out.println((MyEnum.A.returnEnum(true).ordinal())); + System.out.println(0); + } + + @NeverInline + private static void testCustomMethods2() { + MyEnum2.A.print("print"); + System.out.println("2print"); + MyEnum2.A.printEnum(MyEnum.A); + System.out.println(20); + System.out.println((MyEnum2.A.returnEnum(true).ordinal())); + System.out.println(1); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java index 183ee3e..118bcce 100644 --- a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java +++ b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
@@ -51,7 +51,9 @@ .assertAllInfoMessagesMatch( anyOf( equalTo("Ignoring option: -optimizations"), - containsString("Proguard configuration rule does not match anything"))) + containsString("Proguard configuration rule does not match anything"), + containsString("Invalid parameter counts in MethodParameter attributes"), + containsString("Methods with invalid MethodParameter attributes"))) .assertAllWarningMessagesMatch(containsString("Ignoring option:")); int appSize = compileResult.app.applicationSize();
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java new file mode 100644 index 0000000..6405047 --- /dev/null +++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java
@@ -0,0 +1,78 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.internal.proto; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertFalse; + +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.google.common.collect.ImmutableList; +import java.nio.file.Path; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class Proto2BuilderOnlyReferencedFromDynamicMethodTest extends ProtoShrinkingTestBase { + + private static final String MAIN = "proto2.BuilderOnlyReferencedFromDynamicMethodTestClass"; + + private static List<Path> PROGRAM_FILES = + ImmutableList.of(PROTO2_EXAMPLES_JAR, PROTO2_PROTO_JAR, PROTOBUF_LITE_JAR); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public Proto2BuilderOnlyReferencedFromDynamicMethodTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addProgramFiles(PROGRAM_FILES) + .addKeepMainRule(MAIN) + .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES) + .allowAccessModification() + .allowDiagnosticMessages() + .allowUnusedProguardConfigurationRules() + .enableProtoShrinking() + .setMinApi(parameters.getApiLevel()) + .compile() + .assertAllInfoMessagesMatch( + containsString("Proguard configuration rule does not match anything")) + .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists.")) + .inspect(this::inspect) + .run(parameters.getRuntime(), MAIN) + .assertSuccessWithOutputLines( + "false", "0", "false", "", "false", "0", "false", "0", "false", ""); + } + + private void inspect(CodeInspector outputInspector) { + verifyBuilderIsAbsent(outputInspector); + } + + private void verifyBuilderIsAbsent(CodeInspector outputInspector) { + ClassSubject generatedMessageLiteBuilder = + outputInspector.clazz("com.google.protobuf.GeneratedMessageLite$Builder"); + assertThat(generatedMessageLiteBuilder, isPresent()); + assertFalse(generatedMessageLiteBuilder.isAbstract()); + assertThat( + outputInspector.clazz("com.android.tools.r8.proto2.TestProto$Primitives$Builder"), + not(isPresent())); + } +}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java index 8c98b46..dc2b296 100644 --- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java +++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -171,9 +171,7 @@ assertThat( outputInspector.clazz( "com.android.tools.r8.proto2.Shrinking$HasFlaggedOffExtension$Builder"), - mains.equals(ImmutableList.of("proto2.HasFlaggedOffExtensionBuilderTestClass")) - ? isPresent() - : not(isPresent())); + not(isPresent())); assertThat( outputInspector.clazz("com.android.tools.r8.proto2.TestProto$Primitives$Builder"), not(isPresent()));
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/DeadConstructorWithCycleTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/DeadConstructorWithCycleTest.java new file mode 100644 index 0000000..e64fedd --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/DeadConstructorWithCycleTest.java
@@ -0,0 +1,69 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.analysis.sideeffect; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class DeadConstructorWithCycleTest extends TestBase { + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public DeadConstructorWithCycleTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(DeadConstructorWithCycleTest.class) + .addKeepMainRule(TestClass.class) + .enableNeverClassInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect(inspector -> assertThat(inspector.clazz(A.class), not(isPresent()))) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithEmptyOutput(); + } + + static class TestClass { + + public static void main(String[] args) { + Object o1 = null; + Object o2 = null; + for (int i = 1; i <= 2; i++) { + o1 = new A(o1, o2); + o2 = new A(o1, o2); + } + } + } + + @NeverClassInline + static class A { + + Object f1; + Object f2; + + A(Object o1, Object o2) { + this.f1 = o1; + this.f2 = o2; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java index a46524a..3ce1ee1 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java
@@ -166,7 +166,7 @@ @Parameterized.Parameters(name = "{0}") public static TestParametersCollection data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters().withAllRuntimesAndApiLevels().build(); } private final TestParameters parameters; @@ -228,7 +228,7 @@ testForD8() .release() .addProgramClassesAndInnerClasses(MAIN) - .setMinApi(parameters.getRuntime()) + .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), MAIN) .assertSuccessWithOutput(JAVA_OUTPUT); test(result, false); @@ -244,10 +244,9 @@ .enableMemberValuePropagationAnnotations() .addKeepMainRule(MAIN) .noMinification() - .setMinApi(parameters.getRuntime()) + .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), MAIN) .assertSuccessWithOutput(JAVA_OUTPUT); test(result, true); } - }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java index dac8b92..a9f77ce 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -62,7 +62,7 @@ while (it.hasNext()) { prev = curr != null && !curr.isGoto() ? curr : prev; curr = it.next(); - if (curr.isAssumeNonNull()) { + if (curr.isAssumeWithNonNullAssumption()) { // Make sure non-null is added to the right place. assertTrue(prev == null || prev.throwsOnNullInput() @@ -160,11 +160,11 @@ if (count == 0) { // First one in the very first line: its value should not be replaced by NonNullMarker // because this instruction will happen _before_ non-null. - assertFalse(iput.value().definition.isAssumeNonNull()); + assertFalse(iput.value().definition.isAssumeWithNonNullAssumption()); } else if (count == 1) { // Second one after a safe invocation, which should use the value added by // NonNullMarker. - assertTrue(iput.object().definition.isAssumeNonNull()); + assertTrue(iput.object().definition.isAssumeWithNonNullAssumption()); } count++; }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java index a1c69fa..83c5243 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
@@ -141,7 +141,9 @@ .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), MAIN) .assertSuccessWithOutput(JAVA_OUTPUT); - test(result, 0, 0); + // TODO(b/157427150): would be able to remove the call to requireNonNull() if we knew that it + // throws an NullPointerException that does not have a message. + test(result, 0, 1); } static class ObjectsRequireNonNullTestMain {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ThrowNPEWithMessageIfParameterIsNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ThrowNPEWithMessageIfParameterIsNullTest.java new file mode 100644 index 0000000..924278e --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/ThrowNPEWithMessageIfParameterIsNullTest.java
@@ -0,0 +1,58 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize; + +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 org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class ThrowNPEWithMessageIfParameterIsNullTest extends TestBase { + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public ThrowNPEWithMessageIfParameterIsNullTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(ThrowNPEWithMessageIfParameterIsNullTest.class) + .addKeepMainRule(TestClass.class) + .enableInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Hello world!"); + } + + static class TestClass { + + public static void main(String[] args) { + try { + checkNotNull(null); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } + + @NeverInline + static void checkNotNull(Object o) { + if (o == null) { + throw new NullPointerException("Hello world!"); + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java index 4cab645..52736da 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java
@@ -7,13 +7,11 @@ import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import com.android.tools.r8.NeverInline; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; -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; @@ -26,17 +24,14 @@ @RunWith(Parameterized.class) public class DynamicTypeOptimizationTest extends TestBase { - private final boolean enableDynamicTypeOptimization; private final TestParameters parameters; - @Parameterized.Parameters(name = "{1}, enable dynamic type optimization: {0}") + @Parameterized.Parameters(name = "{0}") public static List<Object[]> data() { - return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build()); + return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build()); } - public DynamicTypeOptimizationTest( - boolean enableDynamicTypeOptimization, TestParameters parameters) { - this.enableDynamicTypeOptimization = enableDynamicTypeOptimization; + public DynamicTypeOptimizationTest(TestParameters parameters) { this.parameters = parameters; } @@ -47,10 +42,8 @@ .addKeepMainRule(TestClass.class) // Keep B to ensure that we will treat it as being instantiated. .addKeepClassRulesWithAllowObfuscation(B.class) - .addOptionsModification( - options -> options.enableDynamicTypeOptimization = enableDynamicTypeOptimization) .enableInliningAnnotations() - .setMinApi(parameters.getRuntime()) + .setMinApi(parameters.getApiLevel()) .compile() .inspect(this::inspect) .run(parameters.getRuntime(), TestClass.class) @@ -81,8 +74,7 @@ MethodSubject testInstanceOfRemovalMethod = mainClassSubject.uniqueMethodWithName("testInstanceOfRemoval"); assertThat(testInstanceOfRemovalMethod, isPresent()); - assertEquals( - enableDynamicTypeOptimization, + assertTrue( testInstanceOfRemovalMethod .streamInstructions() .noneMatch(instruction -> instruction.isInstanceOf(aClassSubject.getFinalName()))); @@ -92,27 +84,16 @@ MethodSubject testMethodInliningMethod = mainClassSubject.uniqueMethodWithName("testMethodInlining"); assertThat(testMethodInliningMethod, isPresent()); - assertEquals( - enableDynamicTypeOptimization, interfaceSubject.uniqueMethodWithName("world").isAbsent()); - if (!enableDynamicTypeOptimization) { - assertThat( - testMethodInliningMethod, invokesMethod(interfaceSubject.uniqueMethodWithName("world"))); - } + assertTrue(interfaceSubject.uniqueMethodWithName("world").isAbsent()); // Verify that exclamationMark() has been rebound in testMethodRebinding() unless the dynamic // type optimization is disabled. MethodSubject testMethodRebindingMethod = mainClassSubject.uniqueMethodWithName("testMethodRebinding"); assertThat(testMethodRebindingMethod, isPresent()); - if (enableDynamicTypeOptimization) { - assertThat( - testMethodRebindingMethod, - invokesMethod(aClassSubject.uniqueMethodWithName("exclamationMark"))); - } else { - assertThat( - testMethodRebindingMethod, - invokesMethod(interfaceSubject.uniqueMethodWithName("exclamationMark"))); - } + assertThat( + testMethodRebindingMethod, + invokesMethod(aClassSubject.uniqueMethodWithName("exclamationMark"))); } static class TestClass { @@ -170,6 +151,7 @@ static class A implements I { + @NeverInline @Override public void hello() { System.out.print("Hello");
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 1cbeaae..791bbae 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
@@ -9,7 +9,6 @@ import com.android.tools.r8.NeverInline; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; -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; @@ -111,16 +110,14 @@ } } - @Parameters(name = "{1}, enable dynamic type optimization: {0}") + @Parameters(name = "{0}") public static List<Object[]> data() { - return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build()); + return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build()); } - private final boolean enableDynamicTypeOptimization; private final TestParameters parameters; - public InstanceOfRemovalTest(boolean enableDynamicTypeOptimization, TestParameters parameters) { - this.enableDynamicTypeOptimization = enableDynamicTypeOptimization; + public InstanceOfRemovalTest(TestParameters parameters) { this.parameters = parameters; } @@ -161,10 +158,8 @@ testForR8(parameters.getBackend()) .addProgramClasses(A.class, B.class, TestClass.class) .addKeepMainRule(TestClass.class) - .addOptionsModification( - options -> options.enableDynamicTypeOptimization = enableDynamicTypeOptimization) .enableInliningAnnotations() - .setMinApi(parameters.getRuntime()) + .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutput(expected) .inspector(); @@ -182,7 +177,6 @@ MethodSubject barMethodSubject = testClass.uniqueMethodWithName("bar"); Iterator<InstructionSubject> barInstructionIterator = barMethodSubject.iterateInstructions(InstructionSubject::isInstanceOf); - assertEquals( - enableDynamicTypeOptimization ? 4 : 6, Streams.stream(barInstructionIterator).count()); + assertEquals(4, Streams.stream(barInstructionIterator).count()); } }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java index 289e095..3940548 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.ir.optimize.uninstantiatedtypes; +import com.android.tools.r8.NeverInline; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -46,7 +47,7 @@ .addProgramClasses(I.class, J.class, Main.class) .addProgramClassFileData(getAimplementsI()) .addKeepMainRule(Main.class) - .addKeepRules("-keep class **A { createA(); }") + .enableInliningAnnotations() .setMinApi(parameters.getApiLevel()) .addOptionsModification( options -> options.enableUninstantiatedTypeOptimizationForInterfaces = true) @@ -62,7 +63,7 @@ .addProgramClasses(I.class, J.class, Main.class) .addProgramClassFileData(getAimplementsI()) .addKeepMainRule(Main.class) - .addKeepRules("-keep class **A { createA(); }") + .enableInliningAnnotations() .setMinApi(parameters.getApiLevel()) .addOptionsModification( options -> options.enableUninstantiatedTypeOptimizationForInterfaces = false) @@ -76,10 +77,13 @@ public interface J extends I {} public static class A implements J { + + @NeverInline public static J createA() { return new A(); } + @NeverInline public void f() { System.out.println("In A.f()"); }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java index 854ac14..828f4c0 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java
@@ -7,7 +7,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsNot.not; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import com.android.tools.r8.NeverInline; import com.android.tools.r8.TestBase; @@ -107,15 +106,16 @@ .anyMatch(InstructionSubject::isThrow)); if (shouldHaveThrow) { - // Check that there are no invoke instructions targeting the methods on `Static` and - // `Virtual`. + // TODO(b/157427150): Check that there are no invoke instructions targeting the methods on + // `Static` and `Virtual`. This requires that we know that their methods throw + // NullPointerExceptions without messages. Streams.stream(methodSubject.iterateInstructions()) .filter(InstructionSubject::isInvoke) .forEach( ins -> { ClassSubject clazz = inspector.clazz(ins.getMethod().holder.toSourceString()); - assertNotEquals(clazz.getOriginalName(), Static.class.getTypeName()); - assertNotEquals(clazz.getOriginalName(), Virtual.class.getTypeName()); + // assertNotEquals(clazz.getOriginalName(), Static.class.getTypeName()); + // assertNotEquals(clazz.getOriginalName(), Virtual.class.getTypeName()); }); }
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java index 8ea5334..b4d2c3a 100644 --- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java +++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -132,7 +132,8 @@ } @Override - public BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator) { + public BasicBlock split( + IRCode code, ListIterator<BasicBlock> blockIterator, boolean keepCatchHandlers) { throw new Unimplemented(); }
diff --git a/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java b/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java index 904f69f..6df1462 100644 --- a/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java +++ b/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
@@ -7,12 +7,13 @@ import static com.android.tools.r8.KotlinCompilerTool.KOTLINC; import static org.junit.Assert.assertEquals; -import com.android.tools.r8.KotlinTestBase; 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.kotlin.metadata.KotlinMetadataTestBase; import com.android.tools.r8.utils.ZipUtils; +import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import java.io.IOException; @@ -27,7 +28,7 @@ import org.junit.runners.Parameterized; @RunWith(Parameterized.class) -public class KotlinxCoroutinesTestRunner extends KotlinTestBase { +public class KotlinxCoroutinesTestRunner extends KotlinMetadataTestBase { private static final String PKG = "kotlinx-coroutines-1.3.6"; private static final Path BASE_LIBRARY = @@ -63,22 +64,44 @@ @Test public void runKotlinxCoroutinesTests_smoke() throws Exception { - Path baseJar = - kotlinc(KOTLINC, targetVersion) - .addArguments( - "-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi", - "-Xuse-experimental=kotlinx.coroutines.ObsoleteCoroutinesApi", - "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi") - .addClasspathFiles(DEPENDENCIES) - .addClasspathFiles(BASE_LIBRARY) - .addSourceFiles(TEST_SOURCES) - .compile(); - runTestsInJar(baseJar, BASE_LIBRARY); + runTestsInJar(compileTestSources(BASE_LIBRARY), BASE_LIBRARY); } - private void runTestsInJar(Path testJar, Path deps) throws Exception { + @Test + public void runKotlinxCoroutinesTests_r8() throws Exception { + Path baseJar = + testForR8(parameters.getBackend()) + .addProgramFiles(BASE_LIBRARY) + .addKeepAllClassesRule() + .addKeepAllAttributes() + // The BASE_LIBRARY contains proguard rules that do not match. + .allowUnusedProguardConfigurationRules() + .addKeepRules( + "-dontwarn reactor.blockhound.integration.BlockHoundIntegration", + "-dontwarn org.junit.runners.model.Statement", + "-dontwarn org.junit.rules.TestRule") + .compile() + .inspect(inspector -> assertEqualMetadata(new CodeInspector(BASE_LIBRARY), inspector)) + .writeToZip(); + Path testJar = compileTestSources(baseJar); + runTestsInJar(testJar, baseJar); + } + + private Path compileTestSources(Path baseJar) throws Exception { + return kotlinc(KOTLINC, targetVersion) + .addArguments( + "-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi", + "-Xuse-experimental=kotlinx.coroutines.ObsoleteCoroutinesApi", + "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi") + .addClasspathFiles(DEPENDENCIES) + .addClasspathFiles(baseJar) + .addSourceFiles(TEST_SOURCES) + .compile(); + } + + private void runTestsInJar(Path testJar, Path baseJar) throws Exception { List<Path> dependencies = new ArrayList<>(DEPENDENCIES); - dependencies.add(deps); + dependencies.add(baseJar); dependencies.add(testJar); ZipUtils.iter( testJar.toString(),
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java index 87b5d10..99aaf1b 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
@@ -3,13 +3,25 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.kotlin.metadata; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static junit.framework.TestCase.assertNotNull; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertNull; + import com.android.tools.r8.ToolHelper.KotlinTargetVersion; import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase; +import com.android.tools.r8.kotlin.KotlinMetadataWriter; import com.android.tools.r8.utils.DescriptorUtils; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.FoundClassSubject; +import junit.framework.TestCase; +import kotlinx.metadata.jvm.KotlinClassHeader; +import kotlinx.metadata.jvm.KotlinClassMetadata; -abstract class KotlinMetadataTestBase extends AbstractR8KotlinTestBase { +public abstract class KotlinMetadataTestBase extends AbstractR8KotlinTestBase { - KotlinMetadataTestBase(KotlinTargetVersion targetVersion) { + public KotlinMetadataTestBase(KotlinTargetVersion targetVersion) { super(targetVersion); } @@ -27,4 +39,29 @@ static final String KT_FUNCTION1 = "Lkotlin/Function1;"; static final String KT_COMPARABLE = "Lkotlin/Comparable;"; + + public void assertEqualMetadata(CodeInspector originalInspector, CodeInspector rewrittenInspector) + throws Exception { + for (FoundClassSubject clazzSubject : originalInspector.allClasses()) { + ClassSubject r8Clazz = rewrittenInspector.clazz(clazzSubject.getOriginalName()); + assertThat(r8Clazz, isPresent()); + KotlinClassMetadata originalMetadata = clazzSubject.getKotlinClassMetadata(); + KotlinClassMetadata rewrittenMetadata = r8Clazz.getKotlinClassMetadata(); + if (originalMetadata == null) { + assertNull(rewrittenMetadata); + continue; + } + assertNotNull(rewrittenMetadata); + KotlinClassHeader originalHeader = originalMetadata.getHeader(); + KotlinClassHeader rewrittenHeader = rewrittenMetadata.getHeader(); + TestCase.assertEquals(originalHeader.getKind(), rewrittenHeader.getKind()); + // TODO(b/154199572): Should we check for meta-data version? + TestCase.assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName()); + // We cannot assert equality of the data since it may be ordered differently. Instead we use + // the KotlinMetadataWriter. + String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata); + String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata); + TestCase.assertEquals(expected, actual); + } + } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java new file mode 100644 index 0000000..c6dd4fd --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
@@ -0,0 +1,96 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.kotlin.metadata; + +import static com.android.tools.r8.KotlinCompilerTool.KOTLINC; + +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.ToolHelper.KotlinTargetVersion; +import com.android.tools.r8.utils.DescriptorUtils; +import com.android.tools.r8.utils.StringUtils; +import java.nio.file.Path; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class MetadataRewriteCrossinlineAnonFunctionTest extends KotlinMetadataTestBase { + + private final String EXPECTED = StringUtils.lines("foo"); + private static final String PKG_LIB = PKG + ".crossinline_anon_lib"; + private static final String PKG_APP = PKG + ".crossinline_anon_app"; + + @Parameterized.Parameters(name = "{0} target: {1}") + public static Collection<Object[]> data() { + return buildParameters( + getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values()); + } + + public MetadataRewriteCrossinlineAnonFunctionTest( + TestParameters parameters, KotlinTargetVersion targetVersion) { + super(targetVersion); + this.parameters = parameters; + } + + private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>(); + private final TestParameters parameters; + + @BeforeClass + public static void createLibJar() throws Exception { + for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) { + Path baseLibJar = + kotlinc(KOTLINC, targetVersion) + .addSourceFiles( + getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib")) + .compile(); + libJars.put(targetVersion, baseLibJar); + } + } + + @Test + public void smokeTest() throws Exception { + Path libJar = libJars.get(targetVersion); + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles( + getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG_APP + ".MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test + public void testMetadataForLib() throws Exception { + Path libJar = + testForR8(parameters.getBackend()) + .addProgramFiles(libJars.get(targetVersion)) + // Allow renaming A to ensure that we rename in the flexible upper bound type. + .addKeepAllClassesRule() + .addKeepAllAttributes() + .compile() + .writeToZip(); + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles( + getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main")) + .compile(); + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addProgramFiles(output) + .run(parameters.getRuntime(), PKG_APP + ".MainKt") + .assertSuccessWithOutput(EXPECTED); + } +}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java new file mode 100644 index 0000000..ce26b77 --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java
@@ -0,0 +1,96 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.kotlin.metadata; + +import static com.android.tools.r8.KotlinCompilerTool.KOTLINC; + +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.ToolHelper.KotlinTargetVersion; +import com.android.tools.r8.utils.DescriptorUtils; +import com.android.tools.r8.utils.StringUtils; +import java.nio.file.Path; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class MetadataRewriteCrossinlineConcreteFunctionTest extends KotlinMetadataTestBase { + + private final String EXPECTED = StringUtils.lines("foo"); + private static final String PKG_LIB = PKG + ".crossinline_concrete_lib"; + private static final String PKG_APP = PKG + ".crossinline_concrete_app"; + + @Parameterized.Parameters(name = "{0} target: {1}") + public static Collection<Object[]> data() { + return buildParameters( + getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values()); + } + + public MetadataRewriteCrossinlineConcreteFunctionTest( + TestParameters parameters, KotlinTargetVersion targetVersion) { + super(targetVersion); + this.parameters = parameters; + } + + private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>(); + private final TestParameters parameters; + + @BeforeClass + public static void createLibJar() throws Exception { + for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) { + Path baseLibJar = + kotlinc(KOTLINC, targetVersion) + .addSourceFiles( + getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib")) + .compile(); + libJars.put(targetVersion, baseLibJar); + } + } + + @Test + public void smokeTest() throws Exception { + Path libJar = libJars.get(targetVersion); + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles( + getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG_APP + ".MainKt") + .assertSuccessWithOutput(EXPECTED); + } + + @Test + public void testMetadataForLib() throws Exception { + Path libJar = + testForR8(parameters.getBackend()) + .addProgramFiles(libJars.get(targetVersion)) + .addKeepAllClassesRule() + .addKeepAllAttributes() + .compile() + .writeToZip(); + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles( + getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG_APP + ".MainKt") + .assertSuccessWithOutput(EXPECTED); + } +}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java new file mode 100644 index 0000000..50007fb --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
@@ -0,0 +1,174 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.kotlin.metadata; + +import static com.android.tools.r8.KotlinCompilerTool.KOTLINC; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +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.utils.DescriptorUtils; +import com.android.tools.r8.utils.StringUtils; +import java.nio.file.Path; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class MetadataRewriteDelegatedPropertyTest extends KotlinMetadataTestBase { + + private static final String PKG_LIB = PKG + ".delegated_property_lib"; + private static final String PKG_APP = PKG + ".delegated_property_app"; + private static final String EXPECTED_MAIN = + StringUtils.lines( + "foo has been assigned to 'customDelegate' in" + + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates", + "foo has been read in CustomDelegate from 'customDelegate' in" + + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates", + "foo", + "read-only has been read in CustomReadOnlyDelegate from 'customReadOnlyDelegate' in" + + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates", + "read-only", + "Generating lazy string", + "42", + "Hello World!", + "Hello World!", + "Jane Doe", + "42", + "Checking property for image", + "Checking property for text", + "image_id", + "text_id"); + private static final String EXPECTED_REFLECT = + StringUtils.lines( + "foo has been assigned to 'customDelegate' in" + + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates", + "foo"); + + @Parameterized.Parameters(name = "{0} target: {1}") + public static Collection<Object[]> data() { + return buildParameters( + getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values()); + } + + public MetadataRewriteDelegatedPropertyTest( + TestParameters parameters, KotlinTargetVersion targetVersion) { + super(targetVersion); + this.parameters = parameters; + } + + private final TestParameters parameters; + private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>(); + + @BeforeClass + public static void createLibJar() throws Exception { + for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) { + Path baseLibJar = + kotlinc(KOTLINC, targetVersion) + .addSourceFiles( + getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib")) + .compile(); + libJars.put(targetVersion, baseLibJar); + } + } + + @Test + public void smokeTest() throws Exception { + Path libJar = libJars.get(targetVersion); + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles( + getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG_APP + ".MainKt") + .assertSuccessWithOutput(EXPECTED_MAIN); + } + + @Test + public void smokeTestReflect() throws Exception { + Path libJar = libJars.get(targetVersion); + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles( + getKotlinFileInTest( + DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main_reflect")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + testForJvm() + .addRunClasspathFiles( + ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG_APP + ".Main_reflectKt") + .assertSuccessWithOutput(EXPECTED_REFLECT); + } + + @Test + public void testMetadataForLib() throws Exception { + Path libJar = + testForR8(parameters.getBackend()) + .addClasspathFiles(ToolHelper.getKotlinStdlibJar()) + .addProgramFiles(libJars.get(targetVersion)) + .addKeepRules("-keep class " + PKG_LIB + ".Delegates { *; }") + .addKeepRules("-keep class " + PKG_LIB + ".Resource { *; }") + .addKeepRules("-keep class " + PKG_LIB + ".User { *; }") + .addKeepRules("-keep class " + PKG_LIB + ".ProvidedDelegates { *; }") + .compile() + // TODO(b/157988734): When we start modeling localDelegatedProperties, inspect the code. + .writeToZip(); + Path output = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles( + getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main")) + .setOutputPath(temp.newFolder().toPath()) + .compile(); + testForJvm() + .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar) + .addClasspath(output) + .run(parameters.getRuntime(), PKG_APP + ".MainKt") + .assertSuccessWithOutput(EXPECTED_MAIN); + } + + @Test + public void testMetadataForReflect() throws Exception { + Path libJar = + testForR8(parameters.getBackend()) + .addClasspathFiles(ToolHelper.getKotlinStdlibJar()) + .addProgramFiles(libJars.get(targetVersion)) + .addKeepRules("-keep class " + PKG_LIB + ".Delegates { *; }") + .addKeepRules("-keep class " + PKG_LIB + ".Resource { *; }") + .addKeepRules("-keep class " + PKG_LIB + ".CustomDelegate { *; }") + .compile() + .writeToZip(); + ProcessResult result = + kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles( + getKotlinFileInTest( + DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main_reflect")) + .setOutputPath(temp.newFolder().toPath()) + .compileRaw(); + assertEquals(1, result.exitCode); + assertThat( + result.stderr, + containsString( + "unsupported [reference to the synthetic extension property for a Java get/set" + + " method]")); + } +}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java index a17f7dc..71d888c 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
@@ -4,25 +4,12 @@ package com.android.tools.r8.kotlin.metadata; -import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertNotNull; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertNull; - import com.android.tools.r8.TestParameters; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.KotlinTargetVersion; -import com.android.tools.r8.kotlin.KotlinMetadataWriter; import com.android.tools.r8.shaking.ProguardKeepAttributes; -import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; -import com.android.tools.r8.utils.codeinspector.FoundClassSubject; -import java.io.IOException; import java.util.Collection; -import java.util.concurrent.ExecutionException; -import kotlinx.metadata.jvm.KotlinClassHeader; -import kotlinx.metadata.jvm.KotlinClassMetadata; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -53,31 +40,8 @@ .addKeepKotlinMetadata() .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS) .compile() - .inspect(this::inspect); - } - - public void inspect(CodeInspector inspector) throws IOException, ExecutionException { - CodeInspector stdLibInspector = new CodeInspector(ToolHelper.getKotlinStdlibJar()); - for (FoundClassSubject clazzSubject : stdLibInspector.allClasses()) { - ClassSubject r8Clazz = inspector.clazz(clazzSubject.getOriginalName()); - assertThat(r8Clazz, isPresent()); - KotlinClassMetadata originalMetadata = clazzSubject.getKotlinClassMetadata(); - KotlinClassMetadata rewrittenMetadata = r8Clazz.getKotlinClassMetadata(); - if (originalMetadata == null) { - assertNull(rewrittenMetadata); - continue; - } - assertNotNull(rewrittenMetadata); - KotlinClassHeader originalHeader = originalMetadata.getHeader(); - KotlinClassHeader rewrittenHeader = rewrittenMetadata.getHeader(); - assertEquals(originalHeader.getKind(), rewrittenHeader.getKind()); - // TODO(b/154199572): Should we check for meta-data version? - assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName()); - // We cannot assert equality of the data since it may be ordered differently. Instead we use - // the KotlinMetadataWriter. - String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata); - String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata); - assertEquals(expected, actual); - } + .inspect( + inspector -> + assertEqualMetadata(new CodeInspector(ToolHelper.getKotlinStdlibJar()), inspector)); } }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_anon_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_anon_app/main.kt new file mode 100644 index 0000000..99a3882 --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_anon_app/main.kt
@@ -0,0 +1,18 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.kotlin.metadata.crossinline_anon_app + +import com.android.tools.r8.kotlin.metadata.crossinline_anon_lib.Context +import com.android.tools.r8.kotlin.metadata.crossinline_anon_lib.Handler + +fun main() { + Handler({ context, throwable -> + println(context) + }).handle(object : Context { + override fun toString(): String { + return "foo" + } + }, NullPointerException()) +} \ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_anon_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_anon_lib/lib.kt new file mode 100644 index 0000000..b820f4a --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_anon_lib/lib.kt
@@ -0,0 +1,19 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.kotlin.metadata.crossinline_anon_lib + +public interface Context { + +} + +public inline fun Handler(crossinline handler: (Context, Throwable) -> Unit): Handler = + object : Handler { + override fun handle(context: Context, exception: Throwable) = + handler.invoke(context, exception) + } + +public interface Handler { + fun handle(context: Context, exception: Throwable) +} \ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_concrete_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_concrete_app/main.kt new file mode 100644 index 0000000..4c5c3a7 --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_concrete_app/main.kt
@@ -0,0 +1,18 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.kotlin.metadata.crossinline_concrete_app + +import com.android.tools.r8.kotlin.metadata.crossinline_concrete_lib.Context +import com.android.tools.r8.kotlin.metadata.crossinline_concrete_lib.Handler + +fun main() { + Handler({ context, throwable -> + println(context) + }).handle(object : Context { + override fun toString(): String { + return "foo" + } + }, NullPointerException()) +} \ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_concrete_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_concrete_lib/lib.kt new file mode 100644 index 0000000..cf5da42 --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_concrete_lib/lib.kt
@@ -0,0 +1,25 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.kotlin.metadata.crossinline_concrete_lib + +public interface Context { + +} + +public inline fun Handler(crossinline handler: (Context, Throwable) -> Unit): Handler = + ConcreteClass().getHandler(handler) + +class ConcreteClass { + + inline fun getHandler(crossinline handler: (Context, Throwable) -> Unit): Handler = + object : Handler { + override fun handle(context: Context, exception: Throwable) = + handler.invoke(context, exception) + } +} + +public interface Handler { + fun handle(context: Context, exception: Throwable) +} \ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main.kt new file mode 100644 index 0000000..7bfd637 --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main.kt
@@ -0,0 +1,31 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.kotlin.metadata.delegated_property_app + +import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates +import com.android.tools.r8.kotlin.metadata.delegated_property_lib.ProvidedDelegates +import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Resource +import com.android.tools.r8.kotlin.metadata.delegated_property_lib.User + +fun main() { + + val delegates = Delegates() + delegates.customDelegate = Resource("foo"); + println(delegates.customDelegate) + println(delegates.customReadOnlyDelegate) + println(delegates.lazyString) + println(delegates.localDelegatedProperties { Resource("Hello World!") }) + + val user = User(mapOf( + "name" to "Jane Doe", + "age" to 42 + )) + + println(user.name) + println(user.age) + + val providedDelegates = ProvidedDelegates() + println(providedDelegates.image) + println(providedDelegates.text) +}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main_reflect.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main_reflect.kt new file mode 100644 index 0000000..0369a86 --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main_reflect.kt
@@ -0,0 +1,21 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.kotlin.metadata.delegated_property_app + +import com.android.tools.r8.kotlin.metadata.delegated_property_lib.CustomDelegate +import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates +import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Resource +import kotlin.reflect.KMutableProperty0 +import kotlin.reflect.jvm.isAccessible + +fun main() { + val delegates = Delegates() + delegates.customDelegate = Resource("foo"); + println(delegates::customDelegate.getResource()) +} + +inline fun KMutableProperty0<*>.getResource(): Resource { + isAccessible = true + return (getDelegate() as CustomDelegate).resource +} \ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_lib/lib.kt new file mode 100644 index 0000000..54c58df --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_lib/lib.kt
@@ -0,0 +1,88 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.kotlin.metadata.delegated_property_lib + +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +class Resource(private var s : String = "") { + + override fun toString(): String { + return s; + } +} + +class CustomDelegate(var resource: Resource = Resource()) { + + operator fun getValue(thisRef: Any?, property: KProperty<*>): Resource { + println("$resource has been read in CustomDelegate from '" + + "${property.name}' in ${thisRef?.javaClass?.typeName}") + return resource; + } + + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Resource) { + println("$value has been assigned to '${property.name}'" + + " in ${thisRef?.javaClass?.typeName}") + this.resource = value + } +} + +class CustomReadOnlyDelegate(private var resource : Resource = Resource("read-only")) + : ReadOnlyProperty<Any?, Resource> { + override fun getValue(thisRef: Any?, property: KProperty<*>): Resource { + println("$resource has been read in CustomReadOnlyDelegate" + + " from '${property.name}' in ${thisRef?.javaClass?.typeName}") + return resource; + } +} + +class Delegates { + + var customDelegate : Resource by CustomDelegate() + val customReadOnlyDelegate : Resource by CustomReadOnlyDelegate() + val lazyString : String by lazy { + println("Generating lazy string") + "42" + } + + fun localDelegatedProperties(compute: () -> Resource) : Resource { + val foo by lazy(compute) + println(foo) + return foo + } +} +class User(val map : Map<String, Any?>) { + val name : String by map + val age: Int by map +} + +class ResourceDelegate(val r : Resource): ReadOnlyProperty<ProvidedDelegates, Resource> { + override fun getValue(thisRef: ProvidedDelegates, property: KProperty<*>): Resource { + return r + } +} + +class ResourceLoader(val id: Resource) { + operator fun provideDelegate( + thisRef: ProvidedDelegates, + prop: KProperty<*> + ): ReadOnlyProperty<ProvidedDelegates, Resource> { + checkProperty(prop.name) + return ResourceDelegate(id) + } + + private fun checkProperty(name: String) { + println("Checking property for " + name) + } +} + +class ProvidedDelegates { + fun bindResource(id: String): ResourceLoader { + return ResourceLoader(Resource(id)) + } + + val image by bindResource("image_id") + val text by bindResource("text_id") +}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java index cacf66b..26186aa 100644 --- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java +++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -635,9 +635,10 @@ .forEach( p -> { try { - CodeInspector i = new CodeInspector(AndroidApp.builder().addProgramFiles(p).build()); + CodeInspector i = + new CodeInspector(AndroidApp.builder().addProgramFiles(p).build()); assertFalse("Found " + clazz + " in file " + p, i.clazz(clazz).isPresent()); - } catch (IOException | ExecutionException e) { + } catch (IOException e) { e.printStackTrace(); } });
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java index 48fc320..5562395 100644 --- a/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java +++ b/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
@@ -13,8 +13,10 @@ import com.android.tools.r8.CompilationMode; import com.android.tools.r8.ForceInline; +import com.android.tools.r8.R8TestBuilder; import com.android.tools.r8.TestParameters; import com.android.tools.r8.utils.BooleanUtils; +import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.google.common.collect.ImmutableList; import java.util.Collection; import org.junit.Test; @@ -77,6 +79,18 @@ assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size()); }); } + + @Override + public void configure(R8TestBuilder<?> builder) { + builder.applyIf(mode == CompilationMode.RELEASE, R8TestBuilder::enableForceInliningAnnotations); + } + + @Override + public void inspect(CodeInspector inspector) { + if (mode == CompilationMode.RELEASE) { + assertEquals(compat ? 2 : 1, inspector.clazz(Main.class).allMethods().size()); + } + } } class Main {
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/LineNumberRangeTest.java b/src/test/java/com/android/tools/r8/naming/retrace/LineNumberRangeTest.java new file mode 100644 index 0000000..02ed76d --- /dev/null +++ b/src/test/java/com/android/tools/r8/naming/retrace/LineNumberRangeTest.java
@@ -0,0 +1,284 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.naming.retrace; + +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.CompilationMode; +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.references.Reference; +import com.android.tools.r8.shaking.ProguardKeepAttributes; +import com.android.tools.r8.utils.codeinspector.Matchers; +import com.android.tools.r8.utils.codeinspector.Matchers.LinePosition; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +@RunWith(Parameterized.class) +public class LineNumberRangeTest extends TestBase { + + private final TestParameters parameters; + + private final String classDescriptor = "Lcom/android/tools/r8/naming/retrace/Main;"; + + private final LinePosition EXPECTED_STACK_TRACE = + LinePosition.stack( + LinePosition.create( + Reference.methodFromDescriptor(classDescriptor, "method3", "()V"), + 88, + 88, + "LineNumberRangeTest.java"), + LinePosition.create( + Reference.methodFromDescriptor(classDescriptor, "method2", "()V"), + 94, + 94, + "LineNumberRangeTest.java"), + LinePosition.create( + Reference.methodFromDescriptor(classDescriptor, "method1", "()V"), + 102, + 102, + "LineNumberRangeTest.java"), + LinePosition.create( + Reference.methodFromDescriptor(classDescriptor, "main", "()V"), + 108, + 108, + "LineNumberRangeTest.java")); + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public LineNumberRangeTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testRuntime() throws Exception { + StackTrace expectedStackTrace = + testForRuntime(parameters) + .addProgramClassFileData(MainDump.dump()) + .run(parameters.getRuntime(), Main.class) + .assertFailure() + .map(StackTrace::extractFromJvm); + assertThat(expectedStackTrace, Matchers.containsLinePositions(EXPECTED_STACK_TRACE)); + } + + @Test + public void testSourceFileAndLineNumberTable() throws Exception { + R8TestRunResult result = + testForR8(parameters.getBackend()) + .addProgramClassFileData(MainDump.dump()) + .setMode(CompilationMode.DEBUG) + .addKeepMainRule(Main.class) + .addKeepAttributes( + ProguardKeepAttributes.SOURCE_FILE, ProguardKeepAttributes.LINE_NUMBER_TABLE) + .setMinApi(parameters.getApiLevel()) + .addOptionsModification( + options -> { + options.enableInlining = false; + }) + .run(parameters.getRuntime(), Main.class) + .assertFailure(); + // Extract actual stack trace and retraced stack trace from failed run result. + StackTrace actualStackTrace; + if (parameters.isCfRuntime()) { + actualStackTrace = StackTrace.extractFromJvm(result.getStdErr()); + } else { + actualStackTrace = + StackTrace.extractFromArt(result.getStdErr(), parameters.getRuntime().asDex().getVm()); + } + StackTrace retracedStackTrace = actualStackTrace.retrace(result.proguardMap()); + assertThat(retracedStackTrace, Matchers.containsLinePositions(EXPECTED_STACK_TRACE)); + } + + // This class is generated by taking the output of InliningRetraceTest without running the + // line number optimizer. + public static class MainDump implements Opcodes { + + public static byte[] dump() { + + ClassWriter classWriter = new ClassWriter(0); + MethodVisitor methodVisitor; + + classWriter.visit( + V1_8, + ACC_SUPER, + "com/android/tools/r8/naming/retrace/Main", + null, + "java/lang/Object", + null); + + classWriter.visitSource("LineNumberRangeTest.java", null); + + { + methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null); + methodVisitor.visitCode(); + Label label0 = new Label(); + methodVisitor.visitLabel(label0); + methodVisitor.visitLineNumber(82, label0); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(1, 1); + methodVisitor.visitEnd(); + } + { + methodVisitor = + classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "method3", "()V", null, null); + methodVisitor.visitCode(); + Label label0 = new Label(); + methodVisitor.visitLabel(label0); + methodVisitor.visitLineNumber(86, label0); + methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + methodVisitor.visitLdcInsn("In method3"); + methodVisitor.visitMethodInsn( + INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); + Label label1 = new Label(); + methodVisitor.visitLabel(label1); + methodVisitor.visitLineNumber(87, label1); + methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false); + methodVisitor.visitInsn(LCONST_0); + methodVisitor.visitInsn(LCMP); + Label label2 = new Label(); + methodVisitor.visitJumpInsn(IFLE, label2); + Label label3 = new Label(); + methodVisitor.visitLabel(label3); + methodVisitor.visitLineNumber(88, label3); + methodVisitor.visitInsn(ACONST_NULL); + methodVisitor.visitInsn(ATHROW); + methodVisitor.visitLabel(label2); + methodVisitor.visitLineNumber(90, label2); + methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(4, 2); + methodVisitor.visitEnd(); + } + { + methodVisitor = + classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "method2", "(I)V", null, null); + methodVisitor.visitCode(); + Label label0 = new Label(); + methodVisitor.visitLabel(label0); + methodVisitor.visitLineNumber(92, label0); + methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + methodVisitor.visitLdcInsn("In method2"); + methodVisitor.visitMethodInsn( + INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); + Label label1 = new Label(); + methodVisitor.visitLabel(label1); + methodVisitor.visitLineNumber(93, label1); + methodVisitor.visitInsn(ICONST_0); + methodVisitor.visitVarInsn(ISTORE, 1); + Label label2 = new Label(); + methodVisitor.visitLabel(label2); + methodVisitor.visitVarInsn(ILOAD, 1); + methodVisitor.visitIntInsn(BIPUSH, 10); + Label label3 = new Label(); + methodVisitor.visitJumpInsn(IF_ICMPLT, label3); + Label label4 = new Label(); + methodVisitor.visitLabel(label4); + methodVisitor.visitLineNumber(96, label4); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitLabel(label3); + methodVisitor.visitLineNumber(94, label3); + methodVisitor.visitFrame(Opcodes.F_CHOP, 1, null, 0, null); + methodVisitor.visitMethodInsn( + INVOKESTATIC, "com/android/tools/r8/naming/retrace/Main", "method3", "()V", false); + Label label5 = new Label(); + methodVisitor.visitLabel(label5); + methodVisitor.visitLineNumber(93, label5); + methodVisitor.visitInsn(ACONST_NULL); + methodVisitor.visitInsn(ATHROW); + methodVisitor.visitMaxs(2, 2); + methodVisitor.visitEnd(); + } + { + methodVisitor = + classWriter.visitMethod( + ACC_PUBLIC | ACC_STATIC, "method1", "(Ljava/lang/String;)V", null, null); + methodVisitor.visitCode(); + Label label0 = new Label(); + methodVisitor.visitLabel(label0); + methodVisitor.visitLineNumber(100, label0); + methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + methodVisitor.visitLdcInsn("In method1"); + methodVisitor.visitMethodInsn( + INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); + Label label1 = new Label(); + methodVisitor.visitLabel(label1); + methodVisitor.visitLineNumber(101, label1); + methodVisitor.visitInsn(ICONST_0); + methodVisitor.visitVarInsn(ISTORE, 1); + Label label2 = new Label(); + methodVisitor.visitLabel(label2); + methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[] {Opcodes.INTEGER}, 0, null); + methodVisitor.visitVarInsn(ILOAD, 1); + methodVisitor.visitIntInsn(BIPUSH, 10); + Label label3 = new Label(); + methodVisitor.visitJumpInsn(IF_ICMPGE, label3); + Label label4 = new Label(); + methodVisitor.visitLabel(label4); + methodVisitor.visitLineNumber(102, label4); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitMethodInsn( + INVOKESTATIC, "java/lang/Integer", "parseInt", "(Ljava/lang/String;)I", false); + methodVisitor.visitMethodInsn( + INVOKESTATIC, "com/android/tools/r8/naming/retrace/Main", "method2", "(I)V", false); + Label label5 = new Label(); + methodVisitor.visitLabel(label5); + methodVisitor.visitLineNumber(101, label5); + methodVisitor.visitIincInsn(1, 1); + methodVisitor.visitJumpInsn(GOTO, label2); + methodVisitor.visitLabel(label3); + methodVisitor.visitLineNumber(104, label3); + methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(2, 2); + methodVisitor.visitEnd(); + } + { + methodVisitor = + classWriter.visitMethod( + ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); + methodVisitor.visitCode(); + Label label0 = new Label(); + methodVisitor.visitLabel(label0); + methodVisitor.visitLineNumber(107, label0); + methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + methodVisitor.visitLdcInsn("In main"); + methodVisitor.visitMethodInsn( + INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); + Label label1 = new Label(); + methodVisitor.visitLabel(label1); + methodVisitor.visitLineNumber(108, label1); + methodVisitor.visitLdcInsn("1"); + methodVisitor.visitMethodInsn( + INVOKESTATIC, + "com/android/tools/r8/naming/retrace/Main", + "method1", + "(Ljava/lang/String;)V", + false); + Label label2 = new Label(); + methodVisitor.visitLabel(label2); + methodVisitor.visitLineNumber(109, label2); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(2, 1); + methodVisitor.visitEnd(); + } + classWriter.visitEnd(); + + return classWriter.toByteArray(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java index adc5cb8..b4b90a4 100644 --- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java +++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
@@ -9,6 +9,8 @@ import com.android.tools.r8.R8TestRunResult; 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.codeinspector.CodeInspector; import com.google.common.collect.ImmutableList; import java.util.Collection; import java.util.List; @@ -28,7 +30,9 @@ public StackTrace expectedStackTrace; - public void configure(R8TestBuilder builder) {} + public void configure(R8TestBuilder<?> builder) {} + + public void inspect(CodeInspector inspector) {} public Collection<Class<?>> getClasses() { return ImmutableList.of(getMainClass()); @@ -42,7 +46,7 @@ expectedStackTrace = testForJvm() .addTestClasspath() - .run(getMainClass()) + .run(CfRuntime.getSystemRuntime(), getMainClass()) .assertFailure() .map(StackTrace::extractFromJvm); } @@ -53,12 +57,13 @@ R8TestRunResult result = (compat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend())) .setMode(mode) - .enableProguardTestOptions() .addProgramClasses(getClasses()) .addKeepMainRule(getMainClass()) .addKeepRules(keepRules) .apply(this::configure) .setMinApi(parameters.getApiLevel()) + .compile() + .inspect(this::inspect) .run(parameters.getRuntime(), getMainClass()) .assertFailure();
diff --git a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsTest.java b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsTest.java new file mode 100644 index 0000000..688a96f --- /dev/null +++ b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsTest.java
@@ -0,0 +1,141 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.rewrite; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertNull; +import static junit.framework.TestCase.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.DataEntryResource; +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.origin.Origin; +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.InstructionSubject; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ServiceLoader; +import java.util.concurrent.ExecutionException; +import java.util.zip.ZipFile; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class ServiceLoaderMultipleCallsTest extends TestBase { + + private final TestParameters parameters; + private final String EXPECTED_OUTPUT = StringUtils.lines("Hello World!", "Hello World!"); + + public interface Service { + + void print(); + } + + public static class ServiceImpl implements Service { + + @Override + public void print() { + System.out.println("Hello World!"); + } + } + + public static class ServiceImpl2 implements Service { + + @Override + public void print() { + System.out.println("Hello World 2!"); + } + } + + public static class MainRunner { + + public static void main(String[] args) { + run1(); + run2(); + } + + @NeverInline + public static void run1() { + for (Service x : ServiceLoader.load(Service.class, Service.class.getClassLoader())) { + x.print(); + } + } + + @NeverInline + public static void run2() { + for (Service x : ServiceLoader.load(Service.class, Service.class.getClassLoader())) { + x.print(); + } + } + } + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public ServiceLoaderMultipleCallsTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testRewritings() throws IOException, CompilationFailedException, ExecutionException { + Path path = temp.newFile("out.zip").toPath(); + testForR8(parameters.getBackend()) + .addInnerClasses(ServiceLoaderMultipleCallsTest.class) + .addKeepMainRule(MainRunner.class) + .setMinApi(parameters.getApiLevel()) + .enableInliningAnnotations() + .addDataEntryResources( + DataEntryResource.fromBytes( + StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(), + "META-INF/services/" + Service.class.getTypeName(), + Origin.unknown())) + .compile() + .writeToZip(path) + .run(parameters.getRuntime(), MainRunner.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT) + .inspect( + inspector -> { + // Check that we have actually rewritten the calls to ServiceLoader.load. + assertEquals(0, getServiceLoaderLoads(inspector, MainRunner.class)); + // Check that the synthesize service loader class holds two methods, one for each + // context. + ClassSubject serviceLoaderMethods = inspector.clazz("$$ServiceLoaderMethods"); + assertThat(serviceLoaderMethods, isPresent()); + assertEquals(2, serviceLoaderMethods.allMethods().size()); + }); + + // Check that we have removed the service configuration from META-INF/services. + ZipFile zip = new ZipFile(path.toFile()); + assertNull(zip.getEntry("META-INF/services/" + Service.class.getTypeName())); + } + + private static long getServiceLoaderLoads(CodeInspector inspector, Class<?> clazz) { + ClassSubject classSubject = inspector.clazz(clazz); + assertTrue(classSubject.isPresent()); + return classSubject.allMethods().stream() + .mapToLong( + method -> + method + .streamInstructions() + .filter(ServiceLoaderMultipleCallsTest::isServiceLoaderLoad) + .count()) + .sum(); + } + + private static boolean isServiceLoaderLoad(InstructionSubject instruction) { + return instruction.isInvokeStatic() + && instruction.getMethod().qualifiedName().contains("ServiceLoader.load"); + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/AbstractMethodOnNonAbstractClassTest.java b/src/test/java/com/android/tools/r8/shaking/AbstractMethodOnNonAbstractClassTest.java new file mode 100644 index 0000000..2f95543 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/AbstractMethodOnNonAbstractClassTest.java
@@ -0,0 +1,131 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.shaking; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.R8TestCompileResult; +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; + +@RunWith(Parameterized.class) +public class AbstractMethodOnNonAbstractClassTest extends TestBase { + + private static final String DEX2OAT_WARNING = + "is abstract, but the declaring class is neither abstract nor an interface"; + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public AbstractMethodOnNonAbstractClassTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testCompat() throws Exception { + R8TestCompileResult compileResult = + testForR8Compat(parameters.getBackend()) + .addInnerClasses(AbstractMethodOnNonAbstractClassTest.class) + .addKeepMainRule(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .compile(); + + // A is not made abstract in compat mode. + ClassSubject classSubject = compileResult.inspector().clazz(A.class); + assertThat(classSubject, isPresent()); + assertFalse(classSubject.isAbstract()); + + // A.m() is also not made abstract in compat mode. + MethodSubject methodSubject = classSubject.uniqueMethodWithName("m"); + assertThat(methodSubject, isPresent()); + assertFalse(methodSubject.isAbstract()); + + if (parameters.isDexRuntime()) { + compileResult + .runDex2Oat(parameters.getRuntime()) + .assertStderrMatches(not(containsString(DEX2OAT_WARNING))); + } + + compileResult + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Hello world!"); + } + + @Test + public void testFull() throws Exception { + R8TestCompileResult compileResult = + testForR8(parameters.getBackend()) + .addInnerClasses(AbstractMethodOnNonAbstractClassTest.class) + .addKeepMainRule(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .compile(); + + // A is made abstract in full mode. + ClassSubject classSubject = compileResult.inspector().clazz(A.class); + assertThat(classSubject, isPresent()); + assertTrue(classSubject.isAbstract()); + + // A.m() is also made abstract in full mode. + MethodSubject methodSubject = classSubject.uniqueMethodWithName("m"); + assertThat(methodSubject, isPresent()); + assertTrue(methodSubject.isAbstract()); + + if (parameters.isDexRuntime()) { + // There is no warning due to both A and A.m() being abstract. + compileResult + .runDex2Oat(parameters.getRuntime()) + .assertStderrMatches(not(containsString(DEX2OAT_WARNING))); + } + + compileResult + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Hello world!"); + } + + static class TestClass { + + public static void main(String[] args) { + A b = System.currentTimeMillis() > 0 ? new B() : new C(); + b.m(); + } + } + + static class A { + + // Never called directly on A, thus can be made abstract. + void m() {} + } + + static class B extends A { + + @Override + void m() { + System.out.println("Hello world!"); + } + } + + static class C extends A { + + @Override + void m() { + throw new RuntimeException(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java index 105bf6b..512a9ff 100644 --- a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java +++ b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
@@ -103,8 +103,7 @@ */ "getstatic " + client.name + "/" + obj2.name + " " + itf2.getDescriptor(), "invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V", - "return" - ); + "return"); final String mainClassName = mainClass.name; String proguardConfig = StringUtils.lines(
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 241abc5..14259ca 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
@@ -4,7 +4,6 @@ package com.android.tools.r8.shaking.array; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assume.assumeTrue; @@ -48,13 +47,8 @@ assertEquals(0, countArrayLength(nonNull)); MethodSubject nullable = main.uniqueMethodWithName("isNullable"); - if (isR8) { - // Replaced with null-throwing code at the call site. - assertThat(nullable, not(isPresent())); - } else { - assertThat(nullable, isPresent()); - assertEquals(1, countArrayLength(nullable)); - } + assertThat(nullable, isPresent()); + assertEquals(isR8 ? 0 : 1, countArrayLength(nullable)); MethodSubject nullCheck = main.uniqueMethodWithName("afterNullCheck"); assertThat(nullCheck, isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForLibraryMethodTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForLibraryMethodTest.java new file mode 100644 index 0000000..632863a --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForLibraryMethodTest.java
@@ -0,0 +1,64 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.shaking.assumenosideeffects; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertTrue; + +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.MethodSubject; +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 AssumeNoSideEffectsForLibraryMethodTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public AssumeNoSideEffectsForLibraryMethodTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class) + .addKeepMainRule(TestClass.class) + .addKeepRules("-assumenosideeffects class java.lang.Object { int hashCode() return 42; }") + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect( + inspector -> { + MethodSubject mainMethodSubject = inspector.clazz(TestClass.class).mainMethod(); + assertThat(mainMethodSubject, isPresent()); + assertTrue( + mainMethodSubject + .streamInstructions() + .noneMatch( + instruction -> + instruction.isInvokeVirtual() + && instruction.getMethod().name.toString().equals("hashCode"))); + }) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("42"); + } + + static class TestClass { + + public static void main(String[] args) { + System.out.println(new Object().hashCode()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java index bc842e5..9c38214 100644 --- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java +++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java
@@ -116,7 +116,9 @@ @NeverInline private static void testInvokeInterface(LoggerInterface logger, String message) { - logger.debug(TAG, message); + if (logger != null) { + logger.debug(TAG, message); + } } @NeverInline
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java index ebf633c..71614d2 100644 --- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java +++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java
@@ -41,27 +41,32 @@ case RULE_THAT_REFERS_LIB_BASE: return StringUtils.lines( "-assumenosideeffects class " + LibraryBase.class.getTypeName() + " {", - " *;", + " throwing(...);", + " debug(...);", "}"); case RULE_THAT_REFERS_PRG_BASE: return StringUtils.lines( "-assumenosideeffects class " + ProgramBase.class.getTypeName() + " {", - " *;", + " throwing(...);", + " debug(...);", "}"); case RULE_THAT_REFERS_PRG_SUB: return StringUtils.lines( "-assumenosideeffects class " + ProgramSub.class.getTypeName() + " {", - " *;", + " throwing(...);", + " debug(...);", "}"); case RULE_WITH_EXTENDS_LIB_BASE: return StringUtils.lines( "-assumenosideeffects class * extends " + LibraryBase.class.getTypeName() + " {", - " *;", + " throwing(...);", + " debug(...);", "}"); case RULE_WITH_EXTENDS_PRG_BASE: return StringUtils.lines( "-assumenosideeffects class * extends " + ProgramBase.class.getTypeName() + " {", - " *;", + " throwing(...);", + " debug(...);", "}"); } throw new Unreachable(); @@ -112,7 +117,9 @@ @Parameterized.Parameters(name = "{0} {1}") public static Collection<Object[]> data() { - return buildParameters(getTestParameters().withAllRuntimes().build(), TestConfig.values()); + return buildParameters( + getTestParameters().withAllRuntimesAndApiLevels().withAllApiLevelsAlsoForCf().build(), + TestConfig.values()); } private final TestParameters parameters; @@ -145,7 +152,7 @@ public void testR8() throws Exception { testForR8(parameters.getBackend()) .addLibraryFiles(libJarPath) - .addLibraryFiles(ToolHelper.getDefaultAndroidJar()) + .addLibraryFiles(ToolHelper.getFirstSupportedAndroidJar(parameters.getApiLevel())) .addProgramClasses(ProgramBase.class, ProgramSub.class, MAIN) .addKeepMainRule(MAIN) .addKeepRules(config.getKeepRule()) @@ -153,7 +160,7 @@ .enableMergeAnnotations() .enableNeverClassInliningAnnotations() .enableInliningAnnotations() - .setMinApi(parameters.getRuntime()) + .setMinApi(parameters.getApiLevel()) .compile() .addRunClasspathFiles(parameters.isDexRuntime() ? libDexPath : libJarPath) .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java index 29e4588..34b1b33 100644 --- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java +++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java
@@ -138,7 +138,9 @@ @NeverInline private static void testInvokeInterface(TestLogger logger, String message) { - logger.info(TAG, message); + if (logger != null) { + logger.info(TAG, message); + } } public static void main(String... args) {
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java index fe72a94..560f6ea 100644 --- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java +++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
@@ -4,15 +4,28 @@ package com.android.tools.r8.shaking.assumenosideeffects; +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin; +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticPosition; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.fail; +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.Diagnostic; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.position.TextPosition; +import com.android.tools.r8.position.TextRange; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.InstructionSubject; +import com.google.common.collect.ImmutableList; +import org.hamcrest.Matcher; import org.junit.Assert; import org.junit.Assume; import org.junit.Test; @@ -47,13 +60,118 @@ })); } + private Matcher<Diagnostic> matchAssumeNoSideEffectsWarningMessage() { + return diagnosticMessage( + containsString( + "The -assumenosideeffects rule matches methods on `java.lang.Object` with" + + " wildcards")); + } + + private Matcher<Diagnostic> matchWarningMessageForAllProblematicMethods() { + return diagnosticMessage( + allOf( + containsString("void notify()"), + containsString("void notifyAll()"), + containsString("void wait()"), + containsString("void wait(long)"), + containsString("void wait(long, int)"))); + } + + private Matcher<Diagnostic> matchWarningMessageForWaitMethods() { + return diagnosticMessage( + allOf( + containsString("void wait()"), + containsString("void wait(long)"), + containsString("void wait(long, int)"))); + } + + private TextRange textRangeForString(String s) { + return new TextRange( + new TextPosition(0, 1, 1), new TextPosition(s.length(), 1, s.length() + 1)); + } + @Test - public void testR8() throws Exception { + public void testR8AllMatch() throws Exception { testForR8(parameters.getBackend()) - .addInnerClasses(B152492625.class) + .addProgramClasses(TestClass.class, B.class) .addKeepMainRule(TestClass.class) .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }") .setMinApi(parameters.getApiLevel()) + .allowDiagnosticWarningMessages() + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertOnlyWarnings(); + diagnostics.assertWarningsMatch(matchAssumeNoSideEffectsWarningMessage()); + diagnostics.assertWarningsMatch(matchWarningMessageForAllProblematicMethods()); + }) + .inspect(this::noCallToWait) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Hello, world"); + } + + @Test + public void testR8AllMatchMultipleRules() throws Exception { + class MyOrigin extends Origin { + private final String part; + + public MyOrigin(String part) { + super(Origin.root()); + this.part = part; + } + + @Override + public String part() { + return part; + } + } + + Origin starRuleOrigin = new MyOrigin("star rule"); + Origin methodsRuleOrigin = new MyOrigin("methods rule"); + + String starRule = "-assumenosideeffects class " + B.class.getTypeName() + " { *; }"; + String methodsRule = "-assumenosideeffects class " + B.class.getTypeName() + " { <methods>; }"; + + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class, B.class) + .addKeepMainRule(TestClass.class) + .apply( + b -> + b.getBuilder().addProguardConfiguration(ImmutableList.of(starRule), starRuleOrigin)) + .apply( + b -> + b.getBuilder() + .addProguardConfiguration(ImmutableList.of(methodsRule), methodsRuleOrigin)) + .setMinApi(parameters.getApiLevel()) + .allowDiagnosticWarningMessages() + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertOnlyWarnings(); + diagnostics.assertWarningsMatch( + ImmutableList.of( + allOf( + matchAssumeNoSideEffectsWarningMessage(), + matchWarningMessageForAllProblematicMethods(), + diagnosticOrigin(starRuleOrigin), + diagnosticPosition(textRangeForString(starRule))), + allOf( + matchAssumeNoSideEffectsWarningMessage(), + matchWarningMessageForAllProblematicMethods(), + diagnosticOrigin(methodsRuleOrigin), + diagnosticPosition(textRangeForString(methodsRule))))); + }) + .inspect(this::noCallToWait) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Hello, world"); + } + + @Test + public void testR8AllMatchDontWarn() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class, B.class) + .addKeepMainRule(TestClass.class) + .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }") + .addKeepRules("-dontwarn java.lang.Object") + .setMinApi(parameters.getApiLevel()) .compile() .inspect(this::noCallToWait) .run(parameters.getRuntime(), TestClass.class) @@ -61,11 +179,85 @@ } @Test + public void testR8AllMethodsMatch() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class, B.class) + .addKeepMainRule(TestClass.class) + .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { <methods>; }") + .setMinApi(parameters.getApiLevel()) + .allowDiagnosticWarningMessages() + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertOnlyWarnings(); + diagnostics.assertWarningsMatch(matchAssumeNoSideEffectsWarningMessage()); + diagnostics.assertWarningsMatch(matchWarningMessageForAllProblematicMethods()); + }) + .inspect(this::noCallToWait) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Hello, world"); + } + + @Test + public void testR8WaitMethodMatch() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class, B.class) + .addKeepMainRule(TestClass.class) + .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *** w*(...); }") + .setMinApi(parameters.getApiLevel()) + .allowDiagnosticWarningMessages() + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertOnlyWarnings(); + diagnostics.assertWarningsMatch(matchAssumeNoSideEffectsWarningMessage()); + diagnostics.assertWarningsMatch(matchWarningMessageForWaitMethods()); + }) + .inspect(this::noCallToWait) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Hello, world"); + } + + @Test + public void testR8WaitSpecificMethodMatch() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class, B.class) + .addKeepMainRule(TestClass.class) + .addKeepRules("-assumenosideeffects class java.lang.Object { void wait(); }") + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect(this::noCallToWait) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Hello, world"); + } + + @Test + public void testR8AssumeNoSideEffectsNotConditional() throws Exception { + try { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class, B.class) + .addKeepMainRule(TestClass.class) + .addKeepRules( + "-if class " + TestClass.class.getTypeName(), + " -assumenosideeffects class " + B.class.getTypeName() + " { *; }") + .setMinApi(parameters.getApiLevel()) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertOnlyErrors(); + diagnostics.assertErrorsMatch( + diagnosticMessage( + containsString("Expecting '-keep' option after '-if' option"))); + }); + fail("Expected failed compilation"); + } catch (CompilationFailedException e) { + // Expected. + } + } + + @Test public void testProguardNotRemovingWait() throws Exception { Assume.assumeTrue(parameters.isCfRuntime()); testForProguard() - .addInnerClasses(B152492625.class) + .addProgramClasses(TestClass.class, B.class) .addKeepMainRule(TestClass.class) .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }") .addKeepRules("-dontwarn " + B152492625.class.getTypeName()) @@ -80,7 +272,7 @@ Assume.assumeTrue(parameters.isCfRuntime()); testForProguard() - .addInnerClasses(B152492625.class) + .addProgramClasses(TestClass.class, B.class) .addKeepMainRule(TestClass.class) .addKeepRules("-assumenosideeffects class java.lang.Object { void wait(); }") .addKeepRules("-dontwarn " + B152492625.class.getTypeName())
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B157688676.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B157688676.java new file mode 100644 index 0000000..85be324 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B157688676.java
@@ -0,0 +1,53 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.shaking.assumenosideeffects; + +import com.android.tools.r8.AssumeNoSideEffects; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +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 B157688676 extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public B157688676(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class) + .addKeepMainRule(TestClass.class) + .enableAssumeNoSideEffectsAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithEmptyOutput(); + } + + static class TestClass { + + public static void main(String[] args) { + greet(); + } + + @AssumeNoSideEffects + static void greet() { + System.out.println("Hello world!"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/NullableReturnAfterNonNullableReturnTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/NullableReturnAfterNonNullableReturnTest.java new file mode 100644 index 0000000..e9b4f9f --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/NullableReturnAfterNonNullableReturnTest.java
@@ -0,0 +1,64 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.shaking.assumenosideeffects; + +import com.android.tools.r8.AssumeNoSideEffects; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.ReprocessMethod; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class NullableReturnAfterNonNullableReturnTest extends TestBase { + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public NullableReturnAfterNonNullableReturnTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class) + .addKeepMainRule(TestClass.class) + .enableAssumeNoSideEffectsAnnotations() + .enableInliningAnnotations() + .enableReprocessMethodAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile(); + } + + static class TestClass { + + public static void main(String[] args) { + System.out.println(test()); + } + + @NeverInline + @ReprocessMethod + static Object test() { + String s = System.currentTimeMillis() > 0 ? "Hello world!" : null; + checkNotNull(s); + return s; + } + + @AssumeNoSideEffects + static void checkNotNull(Object o) { + if (o == null) { + throw new RuntimeException(); + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForLibraryMethodTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForLibraryMethodTest.java new file mode 100644 index 0000000..4d7e293 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForLibraryMethodTest.java
@@ -0,0 +1,64 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.shaking.assumevalues; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertTrue; + +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.MethodSubject; +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 AssumeValuesForLibraryMethodTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public AssumeValuesForLibraryMethodTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class) + .addKeepMainRule(TestClass.class) + .addKeepRules("-assumevalues class java.lang.Object { int hashCode() return 42; }") + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect( + inspector -> { + MethodSubject mainMethodSubject = inspector.clazz(TestClass.class).mainMethod(); + assertThat(mainMethodSubject, isPresent()); + assertTrue( + mainMethodSubject + .streamInstructions() + .anyMatch( + instruction -> + instruction.isInvokeVirtual() + && instruction.getMethod().name.toString().equals("hashCode"))); + }) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("42"); + } + + static class TestClass { + + public static void main(String[] args) { + System.out.println(new Object().hashCode()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java index d4801ea..23a5616 100644 --- a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java +++ b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
@@ -4,6 +4,8 @@ package com.android.tools.r8.shaking.b134858535; +import static org.hamcrest.CoreMatchers.containsString; + import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.TestBase; import com.android.tools.r8.utils.AndroidApiLevel; @@ -26,7 +28,13 @@ .addProgramClassFileData(EventPublisher$bDump.dump()) .addKeepClassRules(Interface.class) .addKeepMainRule(Main.class) + .allowDiagnosticInfoMessages() .setMinApi(AndroidApiLevel.L) - .compile(); + .compile() + // TODO(b/157537996): Handle JStyle lambdas with private methods. + .assertAllInfoMessagesMatch( + containsString( + "Unrecognized Kotlin lambda" + + " [com.android.tools.r8.shaking.b134858535.EventPublisher$b]")); } }
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java index 198b253..1c9a3d0 100644 --- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java +++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
@@ -73,15 +73,15 @@ Assert.assertTrue(outer.getDexProgramClass().getInnerClasses().isEmpty()); ClassSubject inner = inspector.clazz("annotationremoval.OuterClass$InnerClass"); Assert.assertTrue(inner.isPresent()); - Assert.assertNull(inner.getDexProgramClass().getEnclosingMethod()); + Assert.assertNull(inner.getDexProgramClass().getEnclosingMethodAttribute()); Assert.assertTrue(inner.getDexProgramClass().getInnerClasses().isEmpty()); ClassSubject anonymous = inspector.clazz("annotationremoval.OuterClass$1"); Assert.assertTrue(anonymous.isPresent()); - Assert.assertNull(anonymous.getDexProgramClass().getEnclosingMethod()); + Assert.assertNull(anonymous.getDexProgramClass().getEnclosingMethodAttribute()); Assert.assertTrue(anonymous.getDexProgramClass().getInnerClasses().isEmpty()); ClassSubject local = inspector.clazz("annotationremoval.OuterClass$1LocalMagic"); Assert.assertTrue(local.isPresent()); - Assert.assertNull(local.getDexProgramClass().getEnclosingMethod()); + Assert.assertNull(local.getDexProgramClass().getEnclosingMethodAttribute()); Assert.assertTrue(local.getDexProgramClass().getInnerClasses().isEmpty()); }
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java index 912915b..ac43338 100644 --- a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java +++ b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
@@ -13,7 +13,6 @@ import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; import java.io.IOException; -import java.util.concurrent.ExecutionException; import org.junit.Test; public class SmaliBuildTest extends SmaliTestBase { @@ -23,7 +22,7 @@ CodeInspector inspector = new CodeInspector(application); ClassSubject clazz = inspector.clazz("java.lang.String"); assertEquals(present, clazz.isPresent()); - } catch (IOException | ExecutionException e) { + } catch (IOException e) { throw new RuntimeException(e); } }
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java index efcc42f..10962c8 100644 --- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java +++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -126,7 +126,7 @@ ClassSubject clazz = inspector.clazz(className); assertTrue(clazz.isPresent()); return clazz.getDexProgramClass(); - } catch (IOException | ExecutionException e) { + } catch (IOException e) { throw new RuntimeException(e); } } @@ -135,7 +135,7 @@ try { CodeInspector inspector = new CodeInspector(appPath); return getMethodSubject(inspector, signature); - } catch (IOException | ExecutionException e) { + } catch (IOException e) { throw new RuntimeException(e); } }
diff --git a/src/test/java/com/android/tools/r8/testing/AndroidBuildVersion.java b/src/test/java/com/android/tools/r8/testing/AndroidBuildVersion.java new file mode 100644 index 0000000..ab3ea7e --- /dev/null +++ b/src/test/java/com/android/tools/r8/testing/AndroidBuildVersion.java
@@ -0,0 +1,14 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.testing; + +/** + * Stub class to simulate having a Build.VERSION property in headless tests. + * + * <p>Use test builder addAndroidBuildVersion() methods when used in tests. + */ +public class AndroidBuildVersion { + public static final String PROPERTY = "com.android.tools.r8.testing.AndroidBuildVersion.VERSION"; + public static int VERSION = Integer.parseInt(System.getProperty(PROPERTY)); +}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java index 3f1ad69..1f1d744 100644 --- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java +++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -337,6 +337,10 @@ }); } + public ClassFileTransformer unsetAbstract() { + return setAccessFlags(ClassAccessFlags::unsetAbstract); + } + public ClassFileTransformer setAnnotation() { return setAccessFlags( accessFlags -> {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java index 39f7c44..b2edae2 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -55,7 +55,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.concurrent.ExecutionException; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -72,25 +71,25 @@ public static MethodSignature MAIN = new MethodSignature("main", "void", new String[] {"java.lang.String[]"}); - public CodeInspector(String path) throws IOException, ExecutionException { + public CodeInspector(String path) throws IOException { this(Paths.get(path)); } - public CodeInspector(Path file, String mappingFile) throws IOException, ExecutionException { + public CodeInspector(Path file, String mappingFile) throws IOException { this(Collections.singletonList(file), mappingFile, null); } - public CodeInspector(Path file) throws IOException, ExecutionException { + public CodeInspector(Path file) throws IOException { this(Collections.singletonList(file), null, null); } - public CodeInspector(List<Path> files) throws IOException, ExecutionException { + public CodeInspector(List<Path> files) throws IOException { this(files, null, null); } public CodeInspector( List<Path> files, String mappingFile, Consumer<InternalOptions> optionsConsumer) - throws IOException, ExecutionException { + throws IOException { Path mappingPath = mappingFile != null ? Paths.get(mappingFile) : null; if (mappingPath != null && Files.exists(mappingPath)) { mapping = ClassNameMapper.mapperFromFile(mappingPath); @@ -109,14 +108,14 @@ application = new ApplicationReader(input, options, timing).read(); } - public CodeInspector(AndroidApp app) throws IOException, ExecutionException { + public CodeInspector(AndroidApp app) throws IOException { this( new ApplicationReader(app, runOptionsConsumer(null), Timing.empty()) .read(app.getProguardMapOutputData())); } public CodeInspector(AndroidApp app, Consumer<InternalOptions> optionsConsumer) - throws IOException, ExecutionException { + throws IOException { this( new ApplicationReader(app, runOptionsConsumer(optionsConsumer), Timing.empty()) .read(app.getProguardMapOutputData())); @@ -130,15 +129,13 @@ return internalOptions; } - public CodeInspector(AndroidApp app, Path proguardMapFile) - throws IOException, ExecutionException { + public CodeInspector(AndroidApp app, Path proguardMapFile) throws IOException { this( new ApplicationReader(app, runOptionsConsumer(null), Timing.empty()) .read(StringResource.fromFile(proguardMapFile))); } - public CodeInspector(AndroidApp app, String proguardMapContent) - throws IOException, ExecutionException { + public CodeInspector(AndroidApp app, String proguardMapContent) throws IOException { this( new ApplicationReader(app, runOptionsConsumer(null), Timing.empty()) .read(StringResource.fromString(proguardMapContent, Origin.unknown())));
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java index c5ca6c9..bfcda02 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -50,6 +50,10 @@ boolean isConstNull(); + default boolean isConstString() { + return isConstString(JumboStringMode.ALLOW); + } + boolean isConstString(JumboStringMode jumboStringMode); boolean isConstString(String value, JumboStringMode jumboStringMode);
diff --git a/src/test/java/com/android/tools/r8/workaround/InputWithAbstractMethodOnNonAbstractClassTest.java b/src/test/java/com/android/tools/r8/workaround/InputWithAbstractMethodOnNonAbstractClassTest.java new file mode 100644 index 0000000..aca08d4 --- /dev/null +++ b/src/test/java/com/android/tools/r8/workaround/InputWithAbstractMethodOnNonAbstractClassTest.java
@@ -0,0 +1,99 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.workaround; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isAbstract; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class InputWithAbstractMethodOnNonAbstractClassTest extends TestBase { + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public InputWithAbstractMethodOnNonAbstractClassTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testD8() throws Exception { + assumeTrue(parameters.isDexRuntime()); + testForD8() + .addProgramClasses(TestClass.class) + .addProgramClassFileData(transformer(Greeter.class).unsetAbstract().transform()) + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect(this::inspect) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Hello world!"); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class) + .addProgramClassFileData(transformer(Greeter.class).unsetAbstract().transform()) + .addKeepMainRule(TestClass.class) + .addKeepClassAndMembersRules(Greeter.class) + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect(this::inspect) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Hello world!"); + } + + @Test + public void testJVM() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForJvm() + .addProgramClasses(TestClass.class) + .addProgramClassFileData(transformer(Greeter.class).unsetAbstract().transform()) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("Hello world!"); + } + + private void inspect(CodeInspector inspector) { + MethodSubject methodOfInterest = inspector.clazz(Greeter.class).uniqueMethodWithName("dead"); + assertThat(methodOfInterest, isPresent()); + if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.L)) { + assertThat(methodOfInterest, not(isAbstract())); + } else { + assertThat(methodOfInterest, isAbstract()); + } + } + + static class TestClass { + + public static void main(String[] args) { + Greeter.greet(); + } + } + + /*not-*/ abstract static class Greeter { + + static void greet() { + System.out.println("Hello world!"); + } + + abstract void dead(); + } +}
diff --git a/third_party/chrome/monochrome_public_minimal_apks/chrome_200520.tar.gz.sha1 b/third_party/chrome/monochrome_public_minimal_apks/chrome_200520.tar.gz.sha1 new file mode 100644 index 0000000..a4d4e8f --- /dev/null +++ b/third_party/chrome/monochrome_public_minimal_apks/chrome_200520.tar.gz.sha1
@@ -0,0 +1 @@ +4a9de4ba961c2f0c953c0a88675f29959e8602a7 \ No newline at end of file
diff --git a/tools/chrome_data.py b/tools/chrome_data.py index 81f2fc7..20ddb21 100644 --- a/tools/chrome_data.py +++ b/tools/chrome_data.py
@@ -10,6 +10,8 @@ V180917_BASE = os.path.join(BASE, 'chrome_180917_ffbaa8') V200430_BASE = os.path.join(BASE, 'chrome_200430') +V200520_MINIMAL_BASE = os.path.join( + BASE, 'monochrome_public_minimal_apks', 'chrome_200520') INPUT_JARS = [ 'out/Release/gen/chrome/android/monochrome_public_apk/monochrome_public_apk.jar', @@ -256,4 +258,22 @@ 'min-api': ANDROID_N_API }, }, + '200520-monochrome_public_minimal_apks': { + 'deploy' : { + 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'program.jar')], + 'features': [ + { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-1.jar')] }, + { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-2.jar')] }, + { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-3.jar')] }, + { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-4.jar')] }, + { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-5.jar')] }, + { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-6.jar')] }, + { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-7.jar')] }, + { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-8.jar')] } + ], + 'pgconf': [os.path.join(V200520_MINIMAL_BASE, 'proguard.config')], + 'libraries': [os.path.join(V200520_MINIMAL_BASE, 'library.jar')], + 'min-api': ANDROID_N_API + }, + }, }
diff --git a/tools/iosched_data.py b/tools/iosched_data.py index 10edd2b..5aac7b0 100644 --- a/tools/iosched_data.py +++ b/tools/iosched_data.py
@@ -5,6 +5,7 @@ import os import utils +ANDROID_L_API = '21' BASE = os.path.join(utils.THIRD_PARTY, 'iosched_2019') INPUT_JARS = [ @@ -168,9 +169,10 @@ VERSIONS = { '2019': { 'deploy' : { - 'inputs': [os.path.join(BASE, path) for path in INPUT_JARS], - 'pgconf': [os.path.join(BASE, 'proguard-rules.pro')], - 'libraries': [utils.get_android_jar(28)], + 'inputs': [os.path.join(BASE, path) for path in INPUT_JARS], + 'pgconf': [os.path.join(BASE, 'proguard-rules.pro')], + 'libraries': [utils.get_android_jar(28)], + 'min-api' : ANDROID_L_API, }, }, }
diff --git a/tools/r8_release.py b/tools/r8_release.py index 2f3c4fc..7b1ba0b 100755 --- a/tools/r8_release.py +++ b/tools/r8_release.py
@@ -17,7 +17,7 @@ import archive_desugar_jdk_libs import utils -R8_DEV_BRANCH = '2.1' +R8_DEV_BRANCH = '2.2' R8_VERSION_FILE = os.path.join( 'src', 'main', 'java', 'com', 'android', 'tools', 'r8', 'Version.java') THIS_FILE_RELATIVE = os.path.join('tools', 'r8_release.py')
diff --git a/tools/run_on_app.py b/tools/run_on_app.py index 809a1fe..1bd62e3 100755 --- a/tools/run_on_app.py +++ b/tools/run_on_app.py
@@ -182,6 +182,10 @@ result.add_option('--cpu-list', help='Run under \'taskset\' with these CPUs. See ' 'the \'taskset\' -c option for the format') + result.add_option('--quiet', + help='Disable compiler logging', + default=False, + action='store_true') return result.parse_args(argv) @@ -370,7 +374,7 @@ return find_min_xmx(options, args) if options.track_time_in_memory: return track_time_in_memory(options, args) - exit_code = run_with_options(options, args) + exit_code = run_with_options(options, args, quiet=options.quiet) if options.expect_oom: exit_code = 0 if exit_code == OOM_EXIT_CODE else 1 return exit_code @@ -565,6 +569,13 @@ if options.r8_flags: args.extend(options.r8_flags.split(' ')) + # Feature jars. + features = values['features'] if 'features' in values else [] + for i, feature in enumerate(features, start=1): + feature_out = os.path.join(outdir, 'feature-%d.zip' % i) + for feature_jar in feature['inputs']: + args.extend(['--feature', feature_jar, feature_out]) + args.extend(inputs) t0 = time.time()
diff --git a/tools/tachiyomi_data.py b/tools/tachiyomi_data.py index 5518cea..10fafe2 100644 --- a/tools/tachiyomi_data.py +++ b/tools/tachiyomi_data.py
@@ -5,14 +5,16 @@ import os import utils +ANDROID_J_API = '16' BASE = os.path.join(utils.THIRD_PARTY, 'tachiyomi') VERSIONS = { 'b15d2fe16864645055af6a745a62cc5566629798': { 'deploy' : { - 'inputs': [os.path.join(BASE, 'program.jar')], - 'pgconf': [os.path.join(BASE, 'proguard.config')], - 'libraries': [os.path.join(BASE, 'library.jar')], + 'inputs': [os.path.join(BASE, 'program.jar')], + 'pgconf': [os.path.join(BASE, 'proguard.config')], + 'libraries': [os.path.join(BASE, 'library.jar')], + 'min-api' : ANDROID_J_API, }, }, }