Merge commit '348064ccc3ffcfff8c51db26b1c98e3396d46529' into dev-release
diff --git a/build.gradle b/build.gradle index 7f0d08f..1dc777d 100644 --- a/build.gradle +++ b/build.gradle
@@ -492,21 +492,29 @@ } } -task downloadDeps { +task downloadCloudDeps() { cloudDependencies.each { entry -> entry.value.each { entryFile -> dependsOn "${getDownloadDepsTaskName(entry.key, entryFile)}" } } - if (!project.hasProperty('no_internal')) { - x20Dependencies.each { entry -> - entry.value.each { entryFile -> - dependsOn "${getDownloadDepsTaskName(entry.key, entryFile)}" - } +} + +task downloadX20Deps() { + x20Dependencies.each { entry -> + entry.value.each { entryFile -> + dependsOn "${getDownloadDepsTaskName(entry.key, entryFile)}" } } } +task downloadDeps { + dependsOn downloadCloudDeps + if (!project.hasProperty('no_internal')) { + dependsOn downloadX20Deps + } +} + allprojects { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 @@ -538,6 +546,8 @@ } } +compileJava.dependsOn downloadCloudDeps + sourceSets.configureEach { sourceSet -> tasks.named(sourceSet.compileJavaTaskName).configure { // Default disable errorprone (enabled and setup below). @@ -727,6 +737,7 @@ } task repackageDepsNew(type: ShadowJar) { + dependsOn downloadCloudDeps configurations = [project.configurations.runtimeClasspath] mergeServiceFiles(it) exclude { it.getRelativePath().getPathString().endsWith("module-info.class") }
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java index 2fadb7d..ac5cd11 100644 --- a/src/main/java/com/android/tools/r8/D8.java +++ b/src/main/java/com/android/tools/r8/D8.java
@@ -44,7 +44,6 @@ import com.android.tools.r8.utils.ExceptionUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.InternalOptions.DesugarState; -import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization; import com.android.tools.r8.utils.LineNumberOptimizer; import com.android.tools.r8.utils.StringDiagnostic; import com.android.tools.r8.utils.StringUtils; @@ -348,8 +347,6 @@ private static ProguardMapSupplier finalizeApplication( AndroidApp inputApp, AppView<AppInfo> appView, NamingLens namingLens) { SyntheticFinalization.finalize(appView); - // TODO(b/37830524): Once D8 supports PC mapping this will need to be run for that too. - assert appView.options().lineNumberOptimization == LineNumberOptimization.OFF; if (appView.options().proguardMapConsumer == null) { return null; }
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java index f33f5bd..ee0ffb2 100644 --- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java +++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
@@ -26,6 +26,8 @@ public abstract AndroidApiLevel getApiLevel(); + public abstract int getMemberCount(); + public abstract TraversalContinuation visitFields( BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor);
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java new file mode 100644 index 0000000..124ea3f --- /dev/null +++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
@@ -0,0 +1,150 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.androidapi; + +import com.android.tools.r8.apimodel.AndroidApiDatabaseBuilder; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMember; +import com.android.tools.r8.graph.DexReference; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.Box; +import com.android.tools.r8.utils.TraversalContinuation; +import java.util.IdentityHashMap; +import java.util.Map; + +public class AndroidApiReferenceLevelCache { + + private static final int BUILD_CACHE_TRESHOLD = 20; + + private final Map<DexType, AndroidApiClass> apiTypeLookup; + private final Map<DexReference, AndroidApiLevel> apiMemberLookup = new IdentityHashMap<>(); + private final AppView<?> appView; + + private AndroidApiReferenceLevelCache(AppView<?> appView) { + this.appView = appView; + this.apiTypeLookup = new IdentityHashMap<>(); + } + + private AndroidApiReferenceLevelCache( + AppView<?> appView, Map<DexType, AndroidApiClass> apiTypeLookup) { + this.appView = appView; + this.apiTypeLookup = apiTypeLookup; + } + + public static AndroidApiReferenceLevelCache create(AppView<?> appView) { + if (!appView.options().apiModelingOptions().enableApiCallerIdentification) { + // If enableApiCallerIdentification is not enabled then override lookup to always return + // AndroidApiLevel.B. + return new AndroidApiReferenceLevelCache(appView) { + @Override + public AndroidApiLevel lookup(DexReference reference) { + return AndroidApiLevel.B; + } + }; + } + // The apiTypeLookup is build lazily except for the mocked api types that we define in tests + // externally. + Map<DexType, AndroidApiClass> apiTypeLookup = new IdentityHashMap<>(); + appView + .options() + .apiModelingOptions() + .visitMockedApiReferences( + (classReference, androidApiClass) -> + apiTypeLookup.put( + appView.dexItemFactory().createType(classReference.getDescriptor()), + androidApiClass)); + return new AndroidApiReferenceLevelCache(appView, apiTypeLookup); + } + + public AndroidApiLevel lookupMax(DexReference reference, AndroidApiLevel minApiLevel) { + return lookup(reference).max(minApiLevel); + } + + public AndroidApiLevel lookup(DexReference reference) { + DexType contextType = reference.getContextType(); + assert !contextType.isArrayType(); + if (contextType.isPrimitiveType() || contextType.isVoidType()) { + return AndroidApiLevel.B; + } + DexClass clazz = appView.definitionFor(contextType); + if (clazz == null) { + return AndroidApiLevel.UNKNOWN; + } + if (!clazz.isLibraryClass()) { + return appView.options().minApiLevel; + } + AndroidApiClass androidApiClass = + apiTypeLookup.computeIfAbsent( + contextType, type -> AndroidApiDatabaseBuilder.buildClass(type.asClassReference())); + if (androidApiClass == null) { + // This is a library class but we have no api model for it. This happens if using an older + // version of R8 to compile a new target. We simply have to disallow inlining of methods + // that has such references. + return AndroidApiLevel.UNKNOWN; + } + if (reference.isDexType()) { + return androidApiClass.getApiLevel(); + } + return androidApiClass.getMemberCount() > BUILD_CACHE_TRESHOLD + ? findMemberByCaching(reference, androidApiClass) + : findMemberByIteration(reference.asDexMember(), androidApiClass); + } + + private AndroidApiLevel findMemberByIteration( + DexMember<?, ?> reference, AndroidApiClass apiClass) { + DexItemFactory factory = appView.dexItemFactory(); + // Similar to the case for api classes we are unable to find, if the member + // is unknown we have to be conservative. + Box<AndroidApiLevel> apiLevelBox = new Box<>(AndroidApiLevel.UNKNOWN); + reference.apply( + field -> + apiClass.visitFields( + (fieldReference, apiLevel) -> { + if (factory.createField(fieldReference) == field) { + apiLevelBox.set(apiLevel); + return TraversalContinuation.BREAK; + } + return TraversalContinuation.CONTINUE; + }), + method -> + apiClass.visitMethods( + (methodReference, apiLevel) -> { + if (factory.createMethod(methodReference) == method) { + apiLevelBox.set(apiLevel); + return TraversalContinuation.BREAK; + } + return TraversalContinuation.CONTINUE; + })); + return apiLevelBox.get(); + } + + private AndroidApiLevel findMemberByCaching(DexReference reference, AndroidApiClass apiClass) { + buildCacheForMembers(reference.getContextType(), apiClass); + return apiMemberLookup.getOrDefault(reference, AndroidApiLevel.UNKNOWN); + } + + private void buildCacheForMembers(DexType context, AndroidApiClass apiClass) { + assert apiClass.getMemberCount() > BUILD_CACHE_TRESHOLD; + // Use the context type as a token for us having build a cache for it. + if (apiMemberLookup.containsKey(context)) { + return; + } + DexItemFactory factory = appView.dexItemFactory(); + apiClass.visitFields( + (fieldReference, apiLevel) -> { + apiMemberLookup.put(factory.createField(fieldReference), apiLevel); + return TraversalContinuation.CONTINUE; + }); + apiClass.visitMethods( + (methodReference, apiLevel) -> { + apiMemberLookup.put(factory.createMethod(methodReference), apiLevel); + return TraversalContinuation.CONTINUE; + }); + apiMemberLookup.put(context, AndroidApiLevel.UNKNOWN); + } +}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java index 271fd68..e054ffa 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -69,7 +69,7 @@ public abstract <T> T apply( Function<DexEncodedField, T> fieldConsumer, Function<DexEncodedMethod, T> methodConsumer); - public void apply( + public void accept( Consumer<DexEncodedField> fieldConsumer, Consumer<DexEncodedMethod> methodConsumer) { apply( field -> {
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 ae9d9fb..bffc842 100644 --- a/src/main/java/com/android/tools/r8/graph/DexMember.java +++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -5,7 +5,7 @@ import com.android.tools.r8.utils.AndroidApiLevel; import com.google.common.collect.Iterables; -import java.util.Map; +import java.util.function.BiFunction; import java.util.function.Function; public abstract class DexMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> @@ -64,12 +64,11 @@ } public AndroidApiLevel computeApiLevelForReferencedTypes( - AppView<?> appView, Map<DexReference, AndroidApiLevel> apiLevelMap) { - AndroidApiLevel minApiLevel = appView.options().minApiLevel; - AndroidApiLevel apiLevel = minApiLevel; + AppView<?> appView, BiFunction<DexReference, AndroidApiLevel, AndroidApiLevel> computeMax) { + AndroidApiLevel computedLevel = appView.options().minApiLevel; for (DexType type : getReferencedBaseTypes(appView.dexItemFactory())) { - apiLevel = apiLevel.max(apiLevelMap.getOrDefault(type, minApiLevel)); + computedLevel = computeMax.apply(type, computedLevel); } - return apiLevel; + return computedLevel; } }
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 0ebd1da..d46c103 100644 --- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -15,11 +15,13 @@ import com.android.tools.r8.graph.GenericSignature.ClassSignature; import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature; import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils; +import com.android.tools.r8.ir.optimize.info.MemberOptimizationInfo; import com.android.tools.r8.kotlin.KotlinClassLevelInfo; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.synthesis.SyntheticMarker; +import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.TraversalContinuation; import com.android.tools.r8.utils.structural.Ordered; import com.android.tools.r8.utils.structural.StructuralItem; @@ -32,6 +34,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -834,4 +837,26 @@ public ChecksumSupplier getChecksumSupplier() { return checksumSupplier; } + + public AndroidApiLevel getApiReferenceLevel( + AndroidApiLevel minApiLevel, + BiFunction<DexReference, AndroidApiLevel, AndroidApiLevel> apiLevelLookup) { + // The api level of a class is the max level of it's members, super class and interfaces. + AndroidApiLevel computedApiLevel = minApiLevel; + for (DexType superType : allImmediateSupertypes()) { + computedApiLevel = apiLevelLookup.apply(superType, computedApiLevel); + if (computedApiLevel == AndroidApiLevel.UNKNOWN) { + return AndroidApiLevel.UNKNOWN; + } + } + for (DexEncodedMember<?, ?> member : members()) { + MemberOptimizationInfo<?> optimizationInfo = member.getOptimizationInfo(); + assert optimizationInfo.hasApiReferenceLevel(); + computedApiLevel = optimizationInfo.getApiReferenceLevel(computedApiLevel); + if (computedApiLevel == AndroidApiLevel.UNKNOWN) { + return AndroidApiLevel.UNKNOWN; + } + } + return computedApiLevel; + } }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java index a3cf3a1..03f4259 100644 --- a/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java +++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
@@ -236,7 +236,7 @@ // We represent no signature as object. } else if (context.superType != appView.graphLens().lookupClassType(classSignature.superClassSignature().type())) { - assert mode.doNotVerify(); + assert mode.doNotVerify() : "Super type inconsistency in generic signature"; return INVALID_SUPER_TYPE; } signatureEvaluationResult =
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java index f93437e..65840c5 100644 --- a/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java +++ b/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
@@ -4,10 +4,10 @@ package com.android.tools.r8.graph.analysis; +import com.android.tools.r8.androidapi.AndroidApiReferenceLevelCache; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMember; import com.android.tools.r8.graph.DexMember; -import com.android.tools.r8.graph.DexReference; import com.android.tools.r8.graph.ProgramDefinition; import com.android.tools.r8.graph.ProgramField; import com.android.tools.r8.graph.ProgramMethod; @@ -16,19 +16,17 @@ import com.android.tools.r8.ir.optimize.info.MemberOptimizationInfo; import com.android.tools.r8.shaking.DefaultEnqueuerUseRegistry; import com.android.tools.r8.utils.AndroidApiLevel; -import java.util.Map; public class ApiModelAnalysis extends EnqueuerAnalysis { private final AppView<?> appView; private final AndroidApiLevel minApiLevel; - private final Map<DexReference, AndroidApiLevel> referenceToApiLevelMap; + private final AndroidApiReferenceLevelCache referenceLevelCache; - public ApiModelAnalysis( - AppView<?> appView, Map<DexReference, AndroidApiLevel> referenceToApiLevelMap) { + public ApiModelAnalysis(AppView<?> appView, AndroidApiReferenceLevelCache referenceLevelCache) { this.appView = appView; this.minApiLevel = appView.options().minApiLevel; - this.referenceToApiLevelMap = referenceToApiLevelMap; + this.referenceLevelCache = referenceLevelCache; } @Override @@ -46,6 +44,13 @@ @Override public void processTracedCode(ProgramMethod method, DefaultEnqueuerUseRegistry registry) { assert registry.getMaxApiReferenceLevel().isGreaterThanOrEqualTo(minApiLevel); + if (appView.options().apiModelingOptions().tracedMethodApiLevelCallback != null) { + appView + .options() + .apiModelingOptions() + .tracedMethodApiLevelCallback + .accept(method.getMethodReference(), registry.getMaxApiReferenceLevel()); + } setApiLevelForMember(method.getDefinition(), registry.getMaxApiReferenceLevel()); } @@ -54,7 +59,7 @@ // swap the default optimization info for one with that marks the api level to be min api. MemberOptimizationInfo<?> optimizationInfo = member.getOptimizationInfo(); if (!optimizationInfo.isMutableOptimizationInfo() && apiLevel == minApiLevel) { - member.apply( + member.accept( field -> { field.setMinApiOptimizationInfo(DefaultFieldOptimizationWithMinApiInfo.getInstance()); }, @@ -66,7 +71,7 @@ optimizationInfo.hasApiReferenceLevel() ? apiLevel.max(optimizationInfo.getApiReferenceLevel(minApiLevel)) : apiLevel; - member.apply( + member.accept( field -> { field.getMutableOptimizationInfo().setApiReferenceLevel(maxApiLevel); }, @@ -77,6 +82,6 @@ } private AndroidApiLevel computeApiLevelForReferencedTypes(DexMember<?, ?> member) { - return member.computeApiLevelForReferencedTypes(appView, referenceToApiLevelMap); + return member.computeApiLevelForReferencedTypes(appView, referenceLevelCache::lookupMax); } }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java index 0d3bf0d..4abddc6 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -21,6 +21,7 @@ import com.android.tools.r8.horizontalclassmerging.policies.NoDeadLocks; import com.android.tools.r8.horizontalclassmerging.policies.NoDefaultInterfaceMethodCollisions; import com.android.tools.r8.horizontalclassmerging.policies.NoDefaultInterfaceMethodMerging; +import com.android.tools.r8.horizontalclassmerging.policies.NoDifferentApiReferenceLevel; import com.android.tools.r8.horizontalclassmerging.policies.NoDirectRuntimeTypeChecks; import com.android.tools.r8.horizontalclassmerging.policies.NoEnums; import com.android.tools.r8.horizontalclassmerging.policies.NoIllegalInlining; @@ -199,6 +200,7 @@ new SameParentClass(), new SyntheticItemsPolicy(appView, mode), new RespectPackageBoundaries(appView), + new NoDifferentApiReferenceLevel(appView), new PreventClassMethodAndDefaultMethodCollisions(appView)); }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java new file mode 100644 index 0000000..97d8d21 --- /dev/null +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java
@@ -0,0 +1,42 @@ +// 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.horizontalclassmerging.policies; + +import com.android.tools.r8.androidapi.AndroidApiReferenceLevelCache; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy; +import com.android.tools.r8.utils.AndroidApiLevel; + +public class NoDifferentApiReferenceLevel extends MultiClassSameReferencePolicy<AndroidApiLevel> { + + private final AndroidApiReferenceLevelCache apiReferenceLevelCache; + private final AndroidApiLevel minApi; + // TODO(b/188388130): Remove when stabilized. + private final boolean enableApiCallerIdentification; + + public NoDifferentApiReferenceLevel(AppView<?> appView) { + apiReferenceLevelCache = AndroidApiReferenceLevelCache.create(appView); + minApi = appView.options().minApiLevel; + enableApiCallerIdentification = + appView.options().apiModelingOptions().enableApiCallerIdentification; + } + + @Override + public boolean shouldSkipPolicy() { + return !enableApiCallerIdentification; + } + + @Override + public String getName() { + return "NoDifferentApiReferenceLevel"; + } + + @Override + public AndroidApiLevel getMergeKey(DexProgramClass clazz) { + assert enableApiCallerIdentification; + return clazz.getApiReferenceLevel(minApi, apiReferenceLevelCache::lookupMax); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java index 2dce46e..19607ee 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java
@@ -28,9 +28,13 @@ @Override public boolean isSatisfied(InvokeMethod invoke) { - Value argument = getArgument(invoke); - assert argument.getType().isReferenceType(); - return argument.getType().isDefinitelyNull(); + // Take the root value to also deal with the following case, which may happen in dead code, + // where v1 is actually guaranteed to be null, despite the value's type being non-null: + // v0 <- ConstNumber 0 + // v1 <- AssumeNotNull v0 + Value argumentRoot = getArgument(invoke).getAliasedValue(); + assert argumentRoot.getType().isReferenceType(); + return argumentRoot.getType().isDefinitelyNull(); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java index f8aae86..ecd958d 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
@@ -20,7 +20,7 @@ import com.android.tools.r8.shaking.EnqueuerUseRegistryFactory; import com.android.tools.r8.utils.AndroidApiLevel; import java.util.ListIterator; -import java.util.Map; +import java.util.function.BiFunction; public class ProtoEnqueuerUseRegistry extends DefaultEnqueuerUseRegistry { @@ -32,7 +32,7 @@ AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod currentMethod, Enqueuer enqueuer, - Map<DexReference, AndroidApiLevel> apiLevelReferenceMap) { + BiFunction<DexReference, AndroidApiLevel, AndroidApiLevel> apiLevelReferenceMap) { super(appView, currentMethod, enqueuer, apiLevelReferenceMap); this.references = appView.protoShrinker().references; }
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 4674e14..9c70c92 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
@@ -240,6 +240,16 @@ } @Override + public Value getNonNullInput() { + return src(); + } + + @Override + public boolean throwsOnNullInput() { + return true; + } + + @Override public boolean verifyTypes(AppView<?> appView, VerifyTypesHelper verifyTypesHelper) { assert super.verifyTypes(appView, verifyTypesHelper);
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 df8f00e..a6a4704 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
@@ -6,6 +6,7 @@ import static com.android.tools.r8.ir.code.IRCode.INSTRUCTION_NUMBER_DELTA; import com.android.tools.r8.errors.CompilationError; +import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DebugLocalInfo; import com.android.tools.r8.graph.DebugLocalInfo.PrintLevel; @@ -1499,6 +1500,19 @@ return block; } + public boolean isInstructionBeforeThrowingInstruction(Instruction instruction) { + assert instruction.getBlock() == this; + for (Instruction candidate : getInstructions()) { + if (candidate == instruction) { + return true; + } + if (candidate.instructionTypeCanThrow()) { + return false; + } + } + throw new Unreachable(); + } + public boolean isTrivialGoto() { return instructions.size() == 1 && exit().isGoto(); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java index 95ee7e0..6005e1f 100644 --- a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java +++ b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
@@ -25,6 +25,10 @@ this.guard = guard; this.target = target; } + + public T getTarget() { + return target; + } } private final List<DexType> guards;
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 3f52f3d..a9e34e2 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
@@ -1168,7 +1168,8 @@ ProgramMethod method, CfInstructionDesugaringEventConsumer desugaringEventConsumer, MethodProcessingContext methodProcessingContext) { - if (options.desugarState.isOff() || !method.getDefinition().getCode().isCfCode()) { + // Due to some mandatory desugarings, we need to run desugaring even if desugaring is disabled. + if (!method.getDefinition().getCode().isCfCode()) { return false; } instructionDesugaring.scan(method, desugaringEventConsumer);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java index 78291d7..148ea56 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -28,7 +28,7 @@ if (appView.options().isGeneratingClassFiles()) { return NonEmptyCfInstructionDesugaringCollection.createForCfToCfNonDesugar(appView); } - return empty(); + return NonEmptyCfInstructionDesugaringCollection.createForCfToDexNonDesugar(appView); } public static CfInstructionDesugaringCollection empty() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java index ba7cbc0b..723e8b1 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -43,6 +43,12 @@ NonEmptyCfInstructionDesugaringCollection(AppView<?> appView) { this.appView = appView; + if (appView.options().desugarState.isOff()) { + this.nestBasedAccessDesugaring = null; + this.recordRewriter = null; + this.desugaredLibraryRetargeter = null; + return; + } this.nestBasedAccessDesugaring = NestBasedAccessDesugaring.create(appView); BackportedMethodRewriter backportedMethodRewriter = null; desugaredLibraryRetargeter = @@ -85,22 +91,25 @@ } } - // TODO(b/145775365): special constructor for cf-to-cf compilations with desugaring disabled. - // This should be removed once we can represent invoke-special instructions in the IR. - private NonEmptyCfInstructionDesugaringCollection( - AppView<?> appView, InvokeSpecialToSelfDesugaring invokeSpecialToSelfDesugaring) { - this.appView = appView; - this.nestBasedAccessDesugaring = null; - this.desugaredLibraryRetargeter = null; - this.recordRewriter = null; - desugarings.add(invokeSpecialToSelfDesugaring); - } - static NonEmptyCfInstructionDesugaringCollection createForCfToCfNonDesugar(AppView<?> appView) { assert appView.options().desugarState.isOff(); assert appView.options().isGeneratingClassFiles(); - return new NonEmptyCfInstructionDesugaringCollection( - appView, new InvokeSpecialToSelfDesugaring(appView)); + NonEmptyCfInstructionDesugaringCollection desugaringCollection = + new NonEmptyCfInstructionDesugaringCollection(appView); + // TODO(b/145775365): special constructor for cf-to-cf compilations with desugaring disabled. + // This should be removed once we can represent invoke-special instructions in the IR. + desugaringCollection.desugarings.add(new InvokeSpecialToSelfDesugaring(appView)); + return desugaringCollection; + } + + static NonEmptyCfInstructionDesugaringCollection createForCfToDexNonDesugar(AppView<?> appView) { + assert appView.options().desugarState.isOff(); + assert appView.options().isGeneratingDex(); + NonEmptyCfInstructionDesugaringCollection desugaringCollection = + new NonEmptyCfInstructionDesugaringCollection(appView); + desugaringCollection.desugarings.add(new InvokeSpecialToSelfDesugaring(appView)); + desugaringCollection.desugarings.add(new InvokeToPrivateRewriter()); + return desugaringCollection; } private void ensureCfCode(ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java index 97af733..55598d1 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -735,10 +735,18 @@ // method is expected to be in the current class since it is private, but desugaring // may move some methods or their code into other classes. assert invokeNeedsRewriting(invokedMethod, DIRECT); - return rewriteInvoke.apply( - directTarget.getDefinition().isPrivateMethod() - ? privateAsMethodOfCompanionClass(directTarget) - : defaultAsMethodOfCompanionClass(directTarget)); + DexMethod companionMethod; + if (directTarget.getDefinition().isPrivateMethod()) { + companionMethod = + directTarget.isProgramMethod() + ? ensurePrivateAsMethodOfProgramCompanionClassStub(directTarget.asProgramMethod()) + .getReference() + // TODO(b/183998768): Why does this not create a stub on the class path? + : privateAsMethodOfCompanionClass(directTarget); + } else { + companionMethod = defaultAsMethodOfCompanionClass(directTarget); + } + return rewriteInvoke.apply(companionMethod); } else { // The method can be a default method in the interface hierarchy. DexClassAndMethod virtualTarget = @@ -1196,6 +1204,35 @@ } } + ProgramMethod ensurePrivateAsMethodOfProgramCompanionClassStub(ProgramMethod method) { + DexMethod companionMethod = + privateAsMethodOfCompanionClass(method.getReference(), appView.dexItemFactory()); + DexEncodedMethod definition = method.getDefinition(); + return InterfaceProcessor.ensureCompanionMethod( + method.getHolder(), + companionMethod.getName(), + companionMethod.getProto(), + appView, + methodBuilder -> { + MethodAccessFlags newFlags = definition.getAccessFlags().copy(); + assert newFlags.isPrivate(); + newFlags.promoteToPublic(); + newFlags.promoteToStatic(); + methodBuilder + .setAccessFlags(newFlags) + .setGenericSignature(definition.getGenericSignature()) + .setAnnotations(definition.annotations()) + .setParameterAnnotationsList(definition.getParameterAnnotations()) + // TODO(b/183998768): Once R8 desugars in the enqueuer this should set an invalid + // code to ensure it is never used before desugared and installed. + .setCode( + ignored -> + appView.enableWholeProgramOptimizations() + ? definition.getCode() + : InvalidCode.getInstance()); + }); + } + // Represent a static interface method as a method of companion class. final DexMethod staticAsMethodOfCompanionClass(DexClassAndMethod method) { DexItemFactory dexItemFactory = appView.dexItemFactory(); @@ -1299,7 +1336,7 @@ // TODO(b/183998768): Once R8 desugars in the enqueuer this should set an invalid // code to ensure it is never used before desugared and installed. .setCode( - m -> + ignored -> appView.enableWholeProgramOptimizations() ? definition.getCode() : InvalidCode.getInstance());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java index ab61be0..799b584 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -288,71 +288,41 @@ getPostProcessingInterfaceInfo(iface).setHasNonClinitDirectMethods(); - MethodAccessFlags originalFlags = method.getAccessFlags(); - MethodAccessFlags newFlags = originalFlags.copy(); - if (originalFlags.isPrivate()) { - newFlags.promoteToPublic(); - } - - DexMethod oldMethod = method.getReference(); + ProgramMethod companion; if (isStaticMethod(definition)) { - assert originalFlags.isPrivate() || originalFlags.isPublic() + assert definition.isPrivate() || definition.isPublic() : "Static interface method " + method.toSourceString() + " is expected to " + "either be public or private in " + iface.origin; - ProgramMethod companion = rewriter.ensureStaticAsMethodOfProgramCompanionClassStub(method); - // TODO(b/183998768): R8 should also install an "invalid code" object until the actual code - // moves. - assert appView.enableWholeProgramOptimizations() - || InvalidCode.isInvalidCode(companion.getDefinition().getCode()); - if (definition.hasClassFileVersion()) { - companion.getDefinition().downgradeClassFileVersion(definition.getClassFileVersion()); + companion = rewriter.ensureStaticAsMethodOfProgramCompanionClassStub(method); + } else { + assert definition.isPrivate(); + companion = rewriter.ensurePrivateAsMethodOfProgramCompanionClassStub(method); + Code code = definition.getCode(); + if (code == null) { + throw new CompilationError( + "Code is missing for private instance " + + "interface method: " + + method.getReference().toSourceString(), + iface.origin); } - companion.getDefinition().setCode(definition.getCode(), appView); - getPostProcessingInterfaceInfo(iface).moveMethod(oldMethod, companion.getReference()); - definition.setCode(InvalidCode.getInstance(), appView); - continue; + DexEncodedMethod.setDebugInfoWithFakeThisParameter( + code, companion.getReference().getArity(), appView); } - assert originalFlags.isPrivate(); - - newFlags.promoteToStatic(); - - DexMethod companionMethod = - InterfaceMethodRewriter.privateAsMethodOfCompanionClass( - oldMethod, appView.dexItemFactory()); - - Code code = definition.getCode(); - if (code == null) { - throw new CompilationError( - "Code is missing for private instance " - + "interface method: " - + oldMethod.toSourceString(), - iface.origin); + // TODO(b/183998768): R8 should also install an "invalid code" object until the actual code + // moves. + assert appView.enableWholeProgramOptimizations() + || InvalidCode.isInvalidCode(companion.getDefinition().getCode()); + if (definition.hasClassFileVersion()) { + companion.getDefinition().downgradeClassFileVersion(definition.getClassFileVersion()); } - - DexEncodedMethod.setDebugInfoWithFakeThisParameter(code, companionMethod.getArity(), appView); - - ensureCompanionMethod( - iface, - companionMethod.getName(), - companionMethod.getProto(), - appView, - methodBuilder -> - methodBuilder - .setAccessFlags(newFlags) - .setGenericSignature(definition.getGenericSignature()) - .setAnnotations(definition.annotations()) - .setParameterAnnotationsList(definition.getParameterAnnotations()) - .setCode(ignored -> definition.getCode()) - .setOnBuildConsumer( - implMethod -> { - implMethod.copyMetadata(definition); - getPostProcessingInterfaceInfo(iface) - .moveMethod(oldMethod, companionMethod); - })); + companion.getDefinition().setCode(definition.getCode(), appView); + getPostProcessingInterfaceInfo(iface) + .moveMethod(method.getReference(), companion.getReference()); + definition.setCode(InvalidCode.getInstance(), appView); } }
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 36d70df..2a1ec57 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
@@ -44,6 +44,7 @@ import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.Binop; import com.android.tools.r8.ir.code.CatchHandlers; +import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler; import com.android.tools.r8.ir.code.CheckCast; import com.android.tools.r8.ir.code.ConstInstruction; import com.android.tools.r8.ir.code.ConstNumber; @@ -3108,6 +3109,7 @@ Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet(); ListIterator<BasicBlock> blockIterator = code.listIterator(); ProgramMethod context = code.context(); + boolean hasUnlinkedCatchHandlers = false; while (blockIterator.hasNext()) { BasicBlock block = blockIterator.next(); if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) { @@ -3123,7 +3125,19 @@ 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()) { + if (instruction.isAssume()) { + // If this assume is in a block with catch handlers, then the out-value can have + // usages in the catch handler if the block's throwing instruction comes after the + // assume instruction. In this case, the catch handler is also guaranteed to be dead, + // so we detach it from the current block. + if (block.hasCatchHandlers() + && block.isInstructionBeforeThrowingInstruction(instruction)) { + for (CatchHandler<BasicBlock> catchHandler : block.getCatchHandlers()) { + catchHandler.getTarget().unlinkCatchHandler(); + } + hasUnlinkedCatchHandlers = true; + } + } else if (instruction.isInstanceFieldInstruction()) { InstanceFieldInstruction instanceFieldInstruction = instruction.asInstanceFieldInstruction(); if (instanceFieldInstruction.instructionInstanceCanThrow( @@ -3183,6 +3197,9 @@ } } code.removeBlocks(blocksToRemove); + if (hasUnlinkedCatchHandlers) { + affectedValues.addAll(code.removeUnreachableBlocks()); + } assert code.getUnreachableBlocks().isEmpty(); if (!affectedValues.isEmpty()) { new TypeAnalysis(appView).narrowing(affectedValues);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java index 5f6ad1a..e653838 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -742,8 +742,12 @@ Set<BasicBlock> seen = Sets.newIdentityHashSet(); Set<Instruction> users = eligibleInstance.uniqueUsers(); for (Instruction user : users) { + if (!user.hasBlock()) { + continue; + } + BasicBlock block = user.getBlock(); - if (block == null || !seen.add(block)) { + if (!seen.add(block)) { continue; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java index 26516df..ede372a 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
@@ -29,7 +29,7 @@ default void fixup(DexEncodedMember<?, ?> member) { MemberOptimizationInfo<?> optimizationInfo = member.getOptimizationInfo(); if (optimizationInfo.isMutableOptimizationInfo()) { - member.apply( + member.accept( field -> { fixup(field, optimizationInfo.asMutableFieldOptimizationInfo()); },
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java index da86fe5..8a3a7e3 100644 --- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java +++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -19,21 +19,21 @@ import com.android.tools.r8.graph.UseRegistry; import com.android.tools.r8.utils.AndroidApiLevel; import java.util.ListIterator; -import java.util.Map; +import java.util.function.BiFunction; public class DefaultEnqueuerUseRegistry extends UseRegistry { protected final AppView<? extends AppInfoWithClassHierarchy> appView; private final ProgramMethod context; protected final Enqueuer enqueuer; - private final Map<DexReference, AndroidApiLevel> apiReferenceMapping; + private final BiFunction<DexReference, AndroidApiLevel, AndroidApiLevel> apiReferenceMapping; private AndroidApiLevel maxApiReferenceLevel; public DefaultEnqueuerUseRegistry( AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context, Enqueuer enqueuer, - Map<DexReference, AndroidApiLevel> apiReferenceMapping) { + BiFunction<DexReference, AndroidApiLevel, AndroidApiLevel> apiReferenceMapping) { super(appView.dexItemFactory()); this.appView = appView; this.context = context; @@ -188,14 +188,13 @@ private void setMaxApiReferenceLevel(DexReference reference) { if (reference.isDexMember()) { - this.maxApiReferenceLevel = + maxApiReferenceLevel = maxApiReferenceLevel.max( reference .asDexMember() .computeApiLevelForReferencedTypes(appView, apiReferenceMapping)); } - this.maxApiReferenceLevel = - maxApiReferenceLevel.max(apiReferenceMapping.getOrDefault(reference, maxApiReferenceLevel)); + maxApiReferenceLevel = apiReferenceMapping.apply(reference, maxApiReferenceLevel); } public AndroidApiLevel getMaxApiReferenceLevel() {
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 abb6523..ea9b2d7 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -15,6 +15,7 @@ import static java.util.Collections.emptySet; import com.android.tools.r8.Diagnostic; +import com.android.tools.r8.androidapi.AndroidApiReferenceLevelCache; import com.android.tools.r8.cf.code.CfInstruction; import com.android.tools.r8.cf.code.CfInvoke; import com.android.tools.r8.code.CfOrDexInstruction; @@ -120,7 +121,6 @@ import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult; import com.android.tools.r8.synthesis.SyntheticItems.SynthesizingContextOracle; import com.android.tools.r8.utils.Action; -import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.IteratorUtils; import com.android.tools.r8.utils.ListUtils; @@ -255,7 +255,7 @@ private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet(); - private final Map<DexReference, AndroidApiLevel> referenceToApiLevelMap; + private final AndroidApiReferenceLevelCache apiReferenceLevelCache; /** * Tracks the dependency between a method and the super-method it calls, if any. Used to make @@ -473,10 +473,7 @@ } else { desugaredLibraryWrapperAnalysis = null; } - referenceToApiLevelMap = new IdentityHashMap<>(); - if (options.apiModelingOptions().enableApiCallerIdentification) { - options.apiModelingOptions().appendToApiLevelMap(referenceToApiLevelMap, dexItemFactory); - } + apiReferenceLevelCache = AndroidApiReferenceLevelCache.create(appView); } private AppInfoWithClassHierarchy appInfo() { @@ -3028,7 +3025,7 @@ registerAnalysis(new GenericSignatureEnqueuerAnalysis(enqueuerDefinitionSupplier)); } if (appView.options().apiModelingOptions().enableApiCallerIdentification) { - registerAnalysis(new ApiModelAnalysis(appView, referenceToApiLevelMap)); + registerAnalysis(new ApiModelAnalysis(appView, apiReferenceLevelCache)); } if (mode.isInitialTreeShaking()) { // This is simulating the effect of the "root set" applied rules. @@ -3944,7 +3941,7 @@ void traceCode(ProgramMethod method) { DefaultEnqueuerUseRegistry registry = - useRegistryFactory.create(appView, method, this, referenceToApiLevelMap); + useRegistryFactory.create(appView, method, this, apiReferenceLevelCache::lookupMax); method.registerCodeReferences(registry); // Notify analyses. analyses.forEach(analysis -> analysis.processTracedCode(method, registry));
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java index 6bd8473..def9a41 100644 --- a/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java +++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java
@@ -9,7 +9,7 @@ import com.android.tools.r8.graph.DexReference; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.utils.AndroidApiLevel; -import java.util.Map; +import java.util.function.BiFunction; public interface EnqueuerUseRegistryFactory { @@ -17,5 +17,5 @@ AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod currentMethod, Enqueuer enqueuer, - Map<DexReference, AndroidApiLevel> apiLevelReferenceMap); + BiFunction<DexReference, AndroidApiLevel, AndroidApiLevel> apiLevelReferenceMap); }
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 d43f32a..230af5f 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -8,6 +8,7 @@ import static com.android.tools.r8.ir.code.Invoke.Type.DIRECT; import static com.android.tools.r8.ir.code.Invoke.Type.STATIC; +import com.android.tools.r8.androidapi.AndroidApiReferenceLevelCache; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; @@ -53,6 +54,7 @@ import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode; import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode; import com.android.tools.r8.logging.Log; +import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.Box; import com.android.tools.r8.utils.FieldSignatureEquivalence; import com.android.tools.r8.utils.MethodSignatureEquivalence; @@ -117,7 +119,8 @@ UNHANDLED_INVOKE_DIRECT, UNHANDLED_INVOKE_SUPER, UNSAFE_INLINING, - UNSUPPORTED_ATTRIBUTES; + UNSUPPORTED_ATTRIBUTES, + API_REFERENCE_LEVEL; public void printLogMessageForClass(DexClass clazz) { Log.info(VerticalClassMerger.class, getMessageForClass(clazz)); @@ -180,6 +183,9 @@ case UNSUPPORTED_ATTRIBUTES: message = "since inner-class attributes are not supported"; break; + case API_REFERENCE_LEVEL: + message = "since source class references a higher api-level than target"; + break; default: assert false; } @@ -201,6 +207,7 @@ private final MethodPoolCollection methodPoolCollection; private final Timing timing; private Collection<DexMethod> invokes; + private final AndroidApiReferenceLevelCache apiReferenceLevelCache; private final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance(); @@ -235,6 +242,7 @@ this.executorService = executorService; this.methodPoolCollection = new MethodPoolCollection(appView, subtypingInfo); this.lensBuilder = new VerticalClassMergerGraphLens.Builder(appView.dexItemFactory()); + this.apiReferenceLevelCache = AndroidApiReferenceLevelCache.create(appView); this.timing = timing; Iterable<DexProgramClass> classes = application.classesWithDeterministicOrder(); @@ -419,6 +427,21 @@ } return false; } + // Only merge if api reference level of source class is equal to target class. + if (appView.options().apiModelingOptions().enableApiCallerIdentification) { + AndroidApiLevel sourceApiLevel = + sourceClass.getApiReferenceLevel( + appView.options().minApiLevel, apiReferenceLevelCache::lookupMax); + AndroidApiLevel targetApiLevel = + targetClass.getApiReferenceLevel( + appView.options().minApiLevel, apiReferenceLevelCache::lookupMax); + if (sourceApiLevel != targetApiLevel) { + if (Log.ENABLED) { + AbortReason.API_REFERENCE_LEVEL.printLogMessageForClass(sourceClass); + } + return false; + } + } return true; }
diff --git a/src/main/java/com/android/tools/r8/utils/EntryUtils.java b/src/main/java/com/android/tools/r8/utils/EntryUtils.java new file mode 100644 index 0000000..e1d28b1 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/EntryUtils.java
@@ -0,0 +1,15 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.utils; + +import java.util.Map.Entry; +import java.util.function.BiFunction; + +public class EntryUtils { + + public static <K, V, R> R accept(Entry<K, V> entry, BiFunction<K, V, R> consumer) { + return consumer.apply(entry.getKey(), entry.getValue()); + } +}
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 0a1fcc7..ff1643f 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -14,6 +14,7 @@ import com.android.tools.r8.ProgramConsumer; import com.android.tools.r8.StringConsumer; import com.android.tools.r8.Version; +import com.android.tools.r8.androidapi.AndroidApiClass; import com.android.tools.r8.cf.CfVersion; import com.android.tools.r8.dex.Marker; import com.android.tools.r8.dex.Marker.Backend; @@ -37,7 +38,6 @@ import com.android.tools.r8.graph.DexLibraryClass; 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.ProgramMethod; import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses; @@ -52,10 +52,10 @@ import com.android.tools.r8.naming.MapVersion; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.position.Position; +import com.android.tools.r8.references.ClassReference; import com.android.tools.r8.references.FieldReference; import com.android.tools.r8.references.MethodReference; import com.android.tools.r8.references.Reference; -import com.android.tools.r8.references.TypeReference; import com.android.tools.r8.repackaging.Repackaging.DefaultRepackagingConfiguration; import com.android.tools.r8.repackaging.Repackaging.RepackagingConfiguration; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -84,10 +84,12 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; +import java.util.function.BiFunction; import java.util.function.BiPredicate; import java.util.function.Consumer; import java.util.function.Function; @@ -1319,21 +1321,68 @@ // A mapping from references to the api-level introducing them. public Map<MethodReference, AndroidApiLevel> methodApiMapping = new HashMap<>(); public Map<FieldReference, AndroidApiLevel> fieldApiMapping = new HashMap<>(); - public Map<TypeReference, AndroidApiLevel> typeApiMapping = new HashMap<>(); + public Map<ClassReference, AndroidApiLevel> classApiMapping = new HashMap<>(); + public BiConsumer<MethodReference, AndroidApiLevel> tracedMethodApiLevelCallback = null; public boolean enableApiCallerIdentification = false; - public void appendToApiLevelMap( - Map<DexReference, AndroidApiLevel> apiLevelMap, DexItemFactory factory) { - methodApiMapping.forEach( - (methodReference, apiLevel) -> - apiLevelMap.put(factory.createMethod(methodReference), apiLevel)); - fieldApiMapping.forEach( - (fieldReference, apiLevel) -> - apiLevelMap.put(factory.createField(fieldReference), apiLevel)); - typeApiMapping.forEach( - (typeReference, apiLevel) -> - apiLevelMap.put(factory.createType(typeReference.getDescriptor()), apiLevel)); + public void visitMockedApiReferences(BiConsumer<ClassReference, AndroidApiClass> consumer) { + if (methodApiMapping.isEmpty() && fieldApiMapping.isEmpty() && classApiMapping.isEmpty()) { + return; + } + Set<ClassReference> classReferences = new HashSet<>(classApiMapping.keySet()); + methodApiMapping + .keySet() + .forEach(methodReference -> classReferences.add(methodReference.getHolderClass())); + fieldApiMapping + .keySet() + .forEach(methodReference -> classReferences.add(methodReference.getHolderClass())); + classReferences.forEach( + classReference -> { + consumer.accept( + classReference, + new AndroidApiClass(classReference) { + @Override + public AndroidApiLevel getApiLevel() { + return classApiMapping.getOrDefault(classReference, AndroidApiLevel.B); + } + + @Override + public int getMemberCount() { + return 0; + } + + @Override + public TraversalContinuation visitFields( + BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor) { + for (Entry<FieldReference, AndroidApiLevel> entry : + fieldApiMapping.entrySet()) { + if (!entry.getKey().getHolderClass().equals(classReference)) { + continue; + } + if (EntryUtils.accept(entry, visitor).shouldBreak()) { + return TraversalContinuation.BREAK; + } + } + return TraversalContinuation.CONTINUE; + } + + @Override + public TraversalContinuation visitMethods( + BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor) { + for (Entry<MethodReference, AndroidApiLevel> entry : + methodApiMapping.entrySet()) { + if (!entry.getKey().getHolderClass().equals(classReference)) { + continue; + } + if (EntryUtils.accept(entry, visitor).shouldBreak()) { + return TraversalContinuation.BREAK; + } + } + return TraversalContinuation.CONTINUE; + } + }); + }); } } @@ -1652,7 +1701,7 @@ } public boolean canUseDexPcAsDebugInformation() { - return !debug && hasMinApi(AndroidApiLevel.O); + return lineNumberOptimization == LineNumberOptimization.ON && hasMinApi(AndroidApiLevel.O); } public boolean isInterfaceMethodDesugaringEnabled() {
diff --git a/src/test/java/com/android/tools/r8/L8TestBuilder.java b/src/test/java/com/android/tools/r8/L8TestBuilder.java index 567d471..072da5a 100644 --- a/src/test/java/com/android/tools/r8/L8TestBuilder.java +++ b/src/test/java/com/android/tools/r8/L8TestBuilder.java
@@ -134,6 +134,11 @@ return this; } + public L8TestBuilder setDesugaredLibraryConfiguration(StringResource configuration) { + this.desugaredLibraryConfiguration = configuration; + return this; + } + public L8TestBuilder setDisableL8AnnotationRemoval(boolean disableL8AnnotationRemoval) { return addOptionsModifier( options -> options.testing.disableL8AnnotationRemoval = disableL8AnnotationRemoval);
diff --git a/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java b/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java index 79dc2c9..737b0ff 100644 --- a/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java +++ b/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
@@ -227,10 +227,12 @@ } String finalGeneratedKeepRules = generatedKeepRules; try { + assert desugaredLibraryConfigurationResources.size() == 1 : "There can be only one"; return L8TestBuilder.create(minApiLevel, Backend.DEX, state) .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P)) .setDesugarJDKLibs(desugarJdkLibs) .setDesugarJDKLibsConfiguration(customConversions) + .setDesugaredLibraryConfiguration(desugaredLibraryConfigurationResources.get(0)) .applyIf( mode == CompilationMode.RELEASE, builder -> {
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java index e51cf99..d41987d 100644 --- a/src/test/java/com/android/tools/r8/TestBase.java +++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1758,6 +1758,10 @@ return AndroidApiLevel.N; } + public static AndroidApiLevel apiLevelWithPrivateInterfaceMethodsSupport() { + return AndroidApiLevel.N; + } + public static AndroidApiLevel apiLevelWithInvokeCustomSupport() { return AndroidApiLevel.O; }
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java index 8f5466c..656955e 100644 --- a/src/test/java/com/android/tools/r8/TestBuilder.java +++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8; +import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer; import com.android.tools.r8.TestBase.Backend; import com.android.tools.r8.debug.DebugTestConfig; import com.android.tools.r8.errors.Unimplemented; @@ -158,6 +159,25 @@ public abstract T addClasspathFiles(Collection<Path> files); + public T addClasspathClassFileData(byte[]... classes) { + return addClasspathClassFileData(Arrays.asList(classes)); + } + + public T addClasspathClassFileData(Collection<byte[]> classes) { + Path out; + try { + out = getState().getNewTempFolder().resolve("cp.jar"); + } catch (IOException e) { + throw new RuntimeException(e); + } + ArchiveConsumer consumer = new ArchiveConsumer(out); + for (byte[] bytes : classes) { + consumer.accept(ByteDataView.of(bytes), TestBase.extractClassDescriptor(bytes), null); + } + consumer.finished(null); + return addClasspathFiles(out); + } + public final T addTestingAnnotationsAsProgramClasses() { return addProgramClasses(getTestingAnnotations()); }
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java index bc25cd4..9aa8f4d 100644 --- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java +++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java
@@ -94,10 +94,12 @@ .setClassDescriptor(getApiClassDescriptor(apiClass)) .addMethodTransformer(getInitTransformer(apiClass)) .addMethodTransformer(getApiLevelTransformer(apiClass)) + .addMethodTransformer(getGetMemberCountTransformer(apiClass)) .addMethodTransformer(getVisitFieldsTransformer(apiClass)) .addMethodTransformer(getVisitMethodsTransformer(apiClass)) .removeMethods(MethodPredicate.onName("placeHolderForInit")) .removeMethods(MethodPredicate.onName("placeHolderForGetApiLevel")) + .removeMethods(MethodPredicate.onName("placeHolderForGetMemberCount")) .removeMethods(MethodPredicate.onName("placeHolderForVisitFields")) .removeMethods(MethodPredicate.onName("placeHolderForVisitMethods")) .transform()); @@ -170,6 +172,16 @@ }); } + // The transformer below changes AndroidApiDatabaseClassTemplate.getMemberCount from: + // return placeHolderForGetMemberCount(); + // into + // return <memberCount>; + private static MethodTransformer getGetMemberCountTransformer(ParsedApiClass apiClass) { + return replaceCode( + "placeHolderForGetMemberCount", + transformer -> transformer.visitLdcInsn(apiClass.getMemberCount())); + } + // The transformer below changes AndroidApiDatabaseClassTemplate.visitFields from: // placeHolder(); // return TraversalContinuation.CONTINUE;
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java index d85e3e5..6d46bfd 100644 --- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
@@ -225,12 +225,7 @@ ZipUtils.unzip( ToolHelper.DEPS.toString(), tempDeps.toFile(), - entry -> { - if (entry.getName().startsWith("com/android/tools/r8/apimodel/")) { - return false; - } - return true; - }); + entry -> !entry.getName().startsWith("com/android/tools/r8/apimodel/")); Path modifiedDeps = Files.createTempFile("modified_deps", ".jar"); ZipUtils.zip(modifiedDeps, tempDeps); return modifiedDeps; @@ -242,6 +237,7 @@ apiClass -> { expected.add(apiClass.getClassReference().getDescriptor()); expected.add(apiClass.getApiLevel().getName()); + expected.add(apiClass.getMemberCount() + ""); BooleanBox added = new BooleanBox(false); apiClass.visitFieldReferences( (apiLevel, fieldReferences) -> { @@ -292,6 +288,7 @@ if (apiClass != null) { System.out.println(descriptor); System.out.println(apiClass.getApiLevel().getName()); + System.out.println(apiClass.getMemberCount()); apiClass.visitFields( (reference, apiLevel) -> { System.out.println(reference.getFieldType().getDescriptor()); @@ -322,6 +319,7 @@ if (apiClass != null) { System.out.println(descriptor); System.out.println(apiClass.getApiLevel().getName()); + System.out.println(apiClass.getMemberCount()); apiClass.visitFields( (reference, apiLevel) -> { System.out.println(reference.getFieldType().getDescriptor());
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java index 2598898..faefd62 100644 --- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java +++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java
@@ -27,6 +27,12 @@ } @Override + public int getMemberCount() { + // Code added dynamically in AndroidApiDatabaseBuilderGenerator. + return placeHolderForGetMemberCount(); + } + + @Override public TraversalContinuation visitFields( BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor) { // Code added dynamically in AndroidApiDatabaseBuilderGenerator. @@ -50,6 +56,10 @@ return null; } + private static int placeHolderForGetMemberCount() { + return 0; + } + private static void placeHolderForVisitFields() {} private static void placeHolderForVisitMethods() {}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java index c904dff..dbb71d3 100644 --- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java +++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
@@ -135,6 +135,11 @@ private final TreeMap<AndroidApiLevel, List<FieldReference>> fieldReferences = new TreeMap<>(); private final Map<AndroidApiLevel, List<MethodReference>> methodReferences = new TreeMap<>(); + private ParsedApiClass(ClassReference classReference, AndroidApiLevel apiLevel) { + this.classReference = classReference; + this.apiLevel = apiLevel; + } + public ClassReference getClassReference() { return classReference; } @@ -143,9 +148,8 @@ return apiLevel; } - private ParsedApiClass(ClassReference classReference, AndroidApiLevel apiLevel) { - this.classReference = classReference; - this.apiLevel = apiLevel; + public int getMemberCount() { + return fieldReferences.size() + methodReferences.size(); } private void register(FieldReference reference, AndroidApiLevel apiLevel) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java index 5d0c0b8..cb837ac 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
@@ -4,7 +4,7 @@ package com.android.tools.r8.apimodel; -import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForType; +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass; import com.android.tools.r8.NeverInline; import com.android.tools.r8.TestBase; @@ -41,11 +41,17 @@ .setMinApi(parameters.getApiLevel()) .addKeepMainRule(Main.class) .enableInliningAnnotations() - // TODO(b/138781768): Should not be merged .addHorizontallyMergedClassesInspector( - inspector -> inspector.assertClassesMerged(A.class, B.class)) + inspector -> { + if (parameters.isDexRuntime() + && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)) { + inspector.assertClassesMerged(A.class, B.class); + } else { + inspector.assertNoClassesMerged(); + } + }) .apply(ApiModelingTestHelper::enableApiCallerIdentification) - .apply(setMockApiLevelForType(Api.class, AndroidApiLevel.L_MR1)) + .apply(setMockApiLevelForClass(Api.class, AndroidApiLevel.L_MR1)) .compile() .addRunClasspathClasses(Api.class) .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiMethodsTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiMethodsTest.java index f174546..0eeb405 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiMethodsTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiMethodsTest.java
@@ -42,9 +42,15 @@ .setMinApi(parameters.getApiLevel()) .addKeepMainRule(Main.class) .enableInliningAnnotations() - // TODO(b/138781768): Should not be merged .addHorizontallyMergedClassesInspector( - inspector -> inspector.assertClassesMerged(A.class, B.class)) + inspector -> { + if (parameters.isDexRuntime() + && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)) { + inspector.assertClassesMerged(A.class, B.class); + } else { + inspector.assertNoClassesMerged(); + } + }) .apply(ApiModelingTestHelper::enableApiCallerIdentification) .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1)) .compile()
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java index f6996e9..03b2f3e 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
@@ -4,7 +4,7 @@ package com.android.tools.r8.apimodel; -import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForType; +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass; import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.CoreMatchers.not; @@ -46,7 +46,7 @@ .setMinApi(parameters.getApiLevel()) .addKeepMainRule(Main.class) .enableNoHorizontalClassMergingAnnotations() - .apply(setMockApiLevelForType(ApiType.class, AndroidApiLevel.L_MR1)) + .apply(setMockApiLevelForClass(ApiType.class, AndroidApiLevel.L_MR1)) .apply(ApiModelingTestHelper::enableApiCallerIdentification) .compile() .addRunClasspathClasses(ApiType.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfDefaultInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfDefaultInterfaceMethodsTest.java index a19ecec..cd86ade 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfDefaultInterfaceMethodsTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfDefaultInterfaceMethodsTest.java
@@ -53,9 +53,9 @@ assertThat(aSubject, isPresent()); aSubject.forAllMethods( method -> { - // TODO(b/191013385): callApiLevel is merged into A, but not with method - // implementation. - // TODO(b/191013233): Check that the bridge is supposed to stay in R8. + // TODO(b/191013385): noApiCall is inlined into Main, but the synthesized + // A.callApiLevel that dispatches to $CC is not. This should change after + // desugaring is moved up to the enqueuer. if (method .getOriginalName() .equals(ApiCaller.class.getTypeName() + ".callApiLevel")
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java index c7b2b1c..a1ecb6a 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
@@ -4,15 +4,16 @@ package com.android.tools.r8.apimodel; +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer; import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForField; import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat; +import static com.android.tools.r8.utils.AndroidApiLevel.L_MR1; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoHorizontalClassMerging; 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 java.lang.reflect.Field; import java.lang.reflect.Method; import org.junit.Test; @@ -47,12 +48,11 @@ .addKeepMainRule(Main.class) .enableInliningAnnotations() .enableNoHorizontalClassMergingAnnotations() - .apply(setMockApiLevelForField(apiField, AndroidApiLevel.L_MR1)) + .apply(setMockApiLevelForField(apiField, L_MR1)) + .apply(setMockApiLevelForDefaultInstanceInitializer(Api.class, L_MR1)) .apply(ApiModelingTestHelper::enableApiCallerIdentification) .compile() - .inspect( - verifyThat(parameters, apiCaller) - .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1)) + .inspect(verifyThat(parameters, apiCaller).inlinedIntoFromApiLevel(apiCallerCaller, L_MR1)) .addRunClasspathClasses(Api.class) .run(parameters.getRuntime(), Main.class) .assertSuccessWithOutputLines("Hello World!");
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelSuperTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelSuperTest.java index 7d6230b..e1cf2e2 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelSuperTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelSuperTest.java
@@ -4,16 +4,19 @@ package com.android.tools.r8.apimodel; +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.addTracedApiReferenceLevelCallBack; +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer; import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod; import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat; +import static org.junit.Assert.assertEquals; import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoHorizontalClassMerging; -import com.android.tools.r8.NoVerticalClassMerging; 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.utils.AndroidApiLevel; import java.lang.reflect.Method; import org.junit.Test; @@ -41,25 +44,36 @@ Method apiCaller = ApiCaller.class.getDeclaredMethod("apiLevel22"); Method apiCallerCaller = A.class.getDeclaredMethod("noApiCall"); testForR8(parameters.getBackend()) - .addInnerClasses(getClass()) + .addProgramClasses(ApiCaller.class, A.class, Main.class) + .addLibraryClasses(Api.class) + .addDefaultRuntimeLibrary(parameters) .setMinApi(parameters.getApiLevel()) .addKeepMainRule(Main.class) .enableInliningAnnotations() .enableNoHorizontalClassMergingAnnotations() .enableNeverClassInliningAnnotations() - .enableNoVerticalClassMergingAnnotations() .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1)) + .apply(setMockApiLevelForDefaultInstanceInitializer(Api.class, AndroidApiLevel.L_MR1)) .apply(ApiModelingTestHelper::enableApiCallerIdentification) + .apply( + addTracedApiReferenceLevelCallBack( + (method, apiLevel) -> { + if (Reference.methodFromMethod(apiCaller).equals(method)) { + if (parameters.isCfRuntime()) { + assertEquals(AndroidApiLevel.L_MR1, apiLevel); + } else { + assertEquals(AndroidApiLevel.L_MR1.max(parameters.getApiLevel()), apiLevel); + } + } + })) .compile() - .inspect( - verifyThat(parameters, apiCaller) - .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1)) + // We do not inline overrides calling super. + .inspect(verifyThat(parameters, apiCaller).notInlinedInto(apiCallerCaller)) .addRunClasspathClasses(Api.class) .run(parameters.getRuntime(), Main.class) .assertSuccessWithOutputLines("A::noApiCall", "ApiCaller::apiLevel22", "Api::apiLevel22"); } - @NoVerticalClassMerging public static class Api { void apiLevel22() {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelVirtualTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelVirtualTest.java index d87ed82..f09f09d 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelVirtualTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.apimodel; import static com.android.tools.r8.apimodel.ApiModelNoInliningOfHigherApiLevelVirtualTest.ApiCaller.callVirtualMethod; +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer; import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod; import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat; @@ -48,6 +49,7 @@ .enableInliningAnnotations() .enableNoHorizontalClassMergingAnnotations() .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1)) + .apply(setMockApiLevelForDefaultInstanceInitializer(Api.class, AndroidApiLevel.L_MR1)) .apply(ApiModelingTestHelper::enableApiCallerIdentification) .compile() .inspect(
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfStaticInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfStaticInterfaceMethodsTest.java index 5ed1708..1e2fd44 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfStaticInterfaceMethodsTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfStaticInterfaceMethodsTest.java
@@ -5,17 +5,18 @@ package com.android.tools.r8.apimodel; import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod; +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat; import static com.android.tools.r8.utils.AndroidApiLevel.L_MR1; +import static com.android.tools.r8.utils.AndroidApiLevel.O; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; +import com.android.tools.r8.NeverInline; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.utils.codeinspector.ClassSubject; -import com.android.tools.r8.utils.codeinspector.CodeMatchers; -import com.android.tools.r8.utils.codeinspector.MethodSubject; +import com.android.tools.r8.utils.codeinspector.FoundClassSubject; import java.lang.reflect.Method; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,7 +30,7 @@ @Parameters(name = "{0}") public static TestParametersCollection data() { - return getTestParameters().withAllRuntimesAndApiLevels().build(); + return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(); } public ApiModelNoInliningOfStaticInterfaceMethodsTest(TestParameters parameters) { @@ -38,38 +39,52 @@ @Test public void testR8() throws Exception { - Method apiMethod = Api.class.getDeclaredMethod("apiLevel22"); - // Method apiCaller = ApiCaller.class.getDeclaredMethod("callApiLevel"); + Method apiMethod22 = Api.class.getDeclaredMethod("apiLevel22"); + Method apiMethod26 = Api.class.getDeclaredMethod("apiLevel26"); testForR8(parameters.getBackend()) .addProgramClasses(Main.class, A.class, ApiCaller.class) .addLibraryClasses(Api.class) .addDefaultRuntimeLibrary(parameters) .setMinApi(parameters.getApiLevel()) .addKeepMainRule(Main.class) - .apply(setMockApiLevelForMethod(apiMethod, L_MR1)) + .apply(setMockApiLevelForMethod(apiMethod22, L_MR1)) + .apply(setMockApiLevelForMethod(apiMethod26, O)) .apply(ApiModelingTestHelper::enableApiCallerIdentification) .noMinification() + .enableInliningAnnotations() .compile() .inspect( inspector -> { // The call to the api is moved to $-CC (or stays) and is then merged if allowed. - if (parameters.isDexRuntime() - && parameters.getApiLevel().isGreaterThanOrEqualTo(L_MR1)) { - assertEquals(1, inspector.allClasses().size()); + Method callApiLevel22 = ApiCaller.class.getDeclaredMethod("callApiLevel22"); + Method callApiLevel26 = ApiCaller.class.getDeclaredMethod("callApiLevel26"); + Method noApiCallTo22 = A.class.getDeclaredMethod("noApiCallTo22"); + Method noApiCallTo26 = A.class.getDeclaredMethod("noApiCallTo26"); + if (!parameters.canUseDefaultAndStaticInterfaceMethods()) { + ClassSubject companion = inspector.companionClassFor(ApiCaller.class); + assertThat(companion, isPresent()); + FoundClassSubject foundCompanion = companion.asFoundClassSubject(); + verifyThat(parameters, callApiLevel22) + .setHolder(foundCompanion) + .inlinedIntoFromApiLevel(noApiCallTo22, L_MR1); + verifyThat(parameters, callApiLevel26) + .setHolder(foundCompanion) + .inlinedIntoFromApiLevel(noApiCallTo26, O); } else { - assertEquals(2, inspector.allClasses().size()); - ClassSubject aSubject = inspector.clazz(A.class); - assertThat(aSubject, isPresent()); - // TODO(b/191008231): Should not invoke api here but stay on the CC class. - assertEquals(1, aSubject.allMethods().size()); - MethodSubject callApiLevel = aSubject.uniqueMethodWithName("callApiLevel"); - assertThat(callApiLevel, isPresent()); - assertThat(callApiLevel, CodeMatchers.invokesMethodWithName("apiLevel22")); + verifyThat(parameters, callApiLevel22) + .inlinedIntoFromApiLevel(noApiCallTo22, L_MR1); + verifyThat(parameters, callApiLevel26).inlinedIntoFromApiLevel(noApiCallTo26, O); } }) .addRunClasspathClasses(Api.class) .run(parameters.getRuntime(), Main.class) - .assertSuccessWithOutputLines("A::noApiCall", "ApiCaller::callApiLevel", "Api::apiLevel22"); + .assertSuccessWithOutputLines( + "A::noApiCallTo22", + "ApiCaller::callApiLevel22", + "Api::apiLevel22", + "A::noApiCallTo26", + "ApiCaller::callApiLevel26", + "Api::apiLevel26"); } public static class Api { @@ -77,27 +92,44 @@ public static void apiLevel22() { System.out.println("Api::apiLevel22"); } - } - public interface ApiCaller { - static void callApiLevel() { - System.out.println("ApiCaller::callApiLevel"); - Api.apiLevel22(); + public static void apiLevel26() { + System.out.println("Api::apiLevel26"); } } - public static class A implements ApiCaller { + public interface ApiCaller { + static void callApiLevel22() { + System.out.println("ApiCaller::callApiLevel22"); + Api.apiLevel22(); + } - public static void noApiCall() { - System.out.println("A::noApiCall"); - ApiCaller.callApiLevel(); + static void callApiLevel26() { + System.out.println("ApiCaller::callApiLevel26"); + Api.apiLevel26(); + } + } + + public static class A { + + @NeverInline + public static void noApiCallTo22() { + System.out.println("A::noApiCallTo22"); + ApiCaller.callApiLevel22(); + } + + @NeverInline + public static void noApiCallTo26() { + System.out.println("A::noApiCallTo26"); + ApiCaller.callApiLevel26(); } } public static class Main { public static void main(String[] args) { - A.noApiCall(); + A.noApiCallTo22(); + A.noApiCallTo26(); } } }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingSubReferenceApiTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingSubReferenceApiTest.java new file mode 100644 index 0000000..f01e1b0 --- /dev/null +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingSubReferenceApiTest.java
@@ -0,0 +1,119 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.apimodel; + +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod; +import static com.android.tools.r8.utils.AndroidApiLevel.L_MR1; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static junit.framework.TestCase.assertEquals; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; +import java.lang.reflect.Method; +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 ApiModelNoVerticalMergingSubReferenceApiTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public ApiModelNoVerticalMergingSubReferenceApiTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test() + public void testR8() throws Exception { + Method apiMethod = Api.class.getDeclaredMethod("apiLevel22"); + testForR8(parameters.getBackend()) + .addProgramClasses(Main.class, Base.class, Sub.class) + .addLibraryClasses(Api.class) + .addDefaultRuntimeLibrary(parameters) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(Main.class) + .apply(setMockApiLevelForMethod(apiMethod, L_MR1)) + .apply(ApiModelingTestHelper::enableApiCallerIdentification) + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .addVerticallyMergedClassesInspector( + inspector -> { + if (parameters.isDexRuntime() + && parameters.getApiLevel().isGreaterThanOrEqualTo(L_MR1)) { + inspector.assertMergedIntoSubtype(Base.class); + } else { + inspector.assertNoClassesMerged(); + } + }) + .noMinification() + .compile() + .inspect( + inspector -> { + ClassSubject base = inspector.clazz(Base.class); + if (parameters.isDexRuntime() + && parameters.getApiLevel().isGreaterThanOrEqualTo(L_MR1)) { + assertThat(base, not(isPresent())); + ClassSubject sub = inspector.clazz(Sub.class); + List<FoundMethodSubject> callApis = + sub.allMethods( + method -> + method.getOriginalName().equals(Base.class.getTypeName() + ".callApi")); + // TODO(b/191013233): Remove synthetic bridge. Remove noMinification after fixed. + assertEquals(2, callApis.size()); + } else { + assertThat(base, isPresent()); + } + }) + .addRunClasspathClasses(Api.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("Sub::callCallApi", "Base::callApi", "Api::apiLevel22"); + } + + public static class Api { + + public static void apiLevel22() { + System.out.println("Api::apiLevel22"); + } + } + + public static class Base { + + public void callApi() { + System.out.println("Base::callApi"); + } + } + + @NeverClassInline + public static class Sub extends Base { + + @NeverInline + public void callCallApi() { + System.out.println("Sub::callCallApi"); + callApi(); + Api.apiLevel22(); + } + } + + public static class Main { + + public static void main(String[] args) { + new Sub().callCallApi(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingTest.java index e7c41d8..b29e694 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingTest.java
@@ -52,20 +52,33 @@ .apply(ApiModelingTestHelper::enableApiCallerIdentification) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() + .addVerticallyMergedClassesInspector( + inspector -> { + if (parameters.isDexRuntime() + && parameters.getApiLevel().isGreaterThanOrEqualTo(L_MR1)) { + inspector.assertMergedIntoSubtype(Base.class); + } else { + inspector.assertNoClassesMerged(); + } + }) .noMinification() .compile() .inspect( inspector -> { - // TODO(b/191013385): Prevent Base merging with Sub. ClassSubject base = inspector.clazz(Base.class); - assertThat(base, not(isPresent())); - ClassSubject sub = inspector.clazz(Sub.class); - List<FoundMethodSubject> callApis = - sub.allMethods( - method -> - method.getOriginalName().equals(Base.class.getTypeName() + ".callApi")); - // TODO(b/191013233): Remove synthetic bridge. - assertEquals(2, callApis.size()); + if (parameters.isDexRuntime() + && parameters.getApiLevel().isGreaterThanOrEqualTo(L_MR1)) { + assertThat(base, not(isPresent())); + ClassSubject sub = inspector.clazz(Sub.class); + List<FoundMethodSubject> callApis = + sub.allMethods( + method -> + method.getOriginalName().equals(Base.class.getTypeName() + ".callApi")); + // TODO(b/191013233): Remove synthetic bridge. Remove noMinification after fixed. + assertEquals(2, callApis.size()); + } else { + assertThat(base, isPresent()); + } }) .addRunClasspathClasses(Api.class) .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelVerticalMergingOfSuperClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelVerticalMergingOfSuperClassTest.java index 80e213e..db7dc06 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelVerticalMergingOfSuperClassTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelVerticalMergingOfSuperClassTest.java
@@ -4,16 +4,21 @@ package com.android.tools.r8.apimodel; -import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForType; +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.addTracedApiReferenceLevelCallBack; +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass; +import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer; +import static com.android.tools.r8.utils.AndroidApiLevel.L_MR1; 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 com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.references.Reference; import com.android.tools.r8.utils.AndroidApiLevel; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,13 +49,37 @@ .addKeepMainRule(Main.class) .enableInliningAnnotations() .enableNeverClassInliningAnnotations() - .apply(setMockApiLevelForType(Api.class, AndroidApiLevel.L_MR1)) + .apply(setMockApiLevelForClass(Api.class, AndroidApiLevel.L_MR1)) + .apply(setMockApiLevelForDefaultInstanceInitializer(Api.class, AndroidApiLevel.L_MR1)) .apply(ApiModelingTestHelper::enableApiCallerIdentification) + .apply( + addTracedApiReferenceLevelCallBack( + (methodReference, apiLevel) -> { + if (methodReference.getMethodName().equals("<init>") + && methodReference + .getHolderClass() + .equals(Reference.classFromClass(Api.class))) { + assertEquals(AndroidApiLevel.L_MR1, apiLevel); + } + })) + .addVerticallyMergedClassesInspector( + inspector -> { + if (parameters.isDexRuntime() + && parameters.getApiLevel().isGreaterThanOrEqualTo(L_MR1)) { + inspector.assertMergedIntoSubtype(A.class); + } else { + inspector.assertNoClassesMerged(); + } + }) .compile() .inspect( inspector -> { - // TODO(b/138781768): We should not merge A into B. - assertThat(inspector.clazz(A.class), not(isPresent())); + if (parameters.isDexRuntime() + && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)) { + assertThat(inspector.clazz(A.class), not(isPresent())); + } else { + assertThat(inspector.clazz(A.class), isPresent()); + } }) .addRunClasspathClasses(Api.class) .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java index 33d1deb..4e02aff 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -11,14 +11,18 @@ import com.android.tools.r8.TestCompilerBuilder; import com.android.tools.r8.TestParameters; import com.android.tools.r8.ThrowableConsumer; +import com.android.tools.r8.references.MethodReference; import com.android.tools.r8.references.Reference; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.ThrowingConsumer; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.CodeMatchers; +import com.android.tools.r8.utils.codeinspector.FoundClassSubject; import com.android.tools.r8.utils.codeinspector.MethodSubject; +import com.google.common.collect.ImmutableList; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.function.BiConsumer; public abstract class ApiModelingTestHelper { @@ -36,6 +40,23 @@ } static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>> + ThrowableConsumer<T> setMockApiLevelForDefaultInstanceInitializer( + Class<?> clazz, AndroidApiLevel apiLevel) { + return compilerBuilder -> { + compilerBuilder.addOptionsModification( + options -> { + options + .apiModelingOptions() + .methodApiMapping + .put( + Reference.method( + Reference.classFromClass(clazz), "<init>", ImmutableList.of(), null), + apiLevel); + }); + }; + } + + static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>> ThrowableConsumer<T> setMockApiLevelForField(Field field, AndroidApiLevel apiLevel) { return compilerBuilder -> { compilerBuilder.addOptionsModification( @@ -48,14 +69,14 @@ }; } - static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>> ThrowableConsumer<T> setMockApiLevelForType( - Class<?> clazz, AndroidApiLevel apiLevel) { + static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>> + ThrowableConsumer<T> setMockApiLevelForClass(Class<?> clazz, AndroidApiLevel apiLevel) { return compilerBuilder -> { compilerBuilder.addOptionsModification( options -> { options .apiModelingOptions() - .typeApiMapping + .classApiMapping .put(Reference.classFromClass(clazz), apiLevel); }); }; @@ -68,28 +89,50 @@ }); } + static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>> + ThrowableConsumer<T> addTracedApiReferenceLevelCallBack( + BiConsumer<MethodReference, AndroidApiLevel> consumer) { + return compilerBuilder -> { + compilerBuilder.addOptionsModification( + options -> { + options.apiModelingOptions().tracedMethodApiLevelCallback = consumer; + }); + }; + } + static ApiModelingMethodVerificationHelper verifyThat(TestParameters parameters, Method method) { - return new ApiModelingMethodVerificationHelper(parameters, method); + return new ApiModelingMethodVerificationHelper(parameters, Reference.methodFromMethod(method)); } public static class ApiModelingMethodVerificationHelper { - private final Method methodOfInterest; + private final MethodReference methodOfInterest; private final TestParameters parameters; - public ApiModelingMethodVerificationHelper(TestParameters parameters, Method methodOfInterest) { + private ApiModelingMethodVerificationHelper( + TestParameters parameters, MethodReference methodOfInterest) { this.methodOfInterest = methodOfInterest; this.parameters = parameters; } - protected ThrowingConsumer<CodeInspector, Exception> inlinedIntoFromApiLevel( + public ApiModelingMethodVerificationHelper setHolder(FoundClassSubject classSubject) { + return new ApiModelingMethodVerificationHelper( + parameters, + Reference.method( + classSubject.getFinalReference(), + methodOfInterest.getMethodName(), + methodOfInterest.getFormalTypes(), + methodOfInterest.getReturnType())); + } + + ThrowingConsumer<CodeInspector, Exception> inlinedIntoFromApiLevel( Method method, AndroidApiLevel apiLevel) { return parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevel) ? inlinedInto(method) : notInlinedInto(method); } - private ThrowingConsumer<CodeInspector, Exception> notInlinedInto(Method method) { + ThrowingConsumer<CodeInspector, Exception> notInlinedInto(Method method) { return inspector -> { MethodSubject candidate = inspector.method(methodOfInterest); assertThat(candidate, isPresent()); @@ -99,7 +142,7 @@ }; } - public ThrowingConsumer<CodeInspector, Exception> inlinedInto(Method method) { + ThrowingConsumer<CodeInspector, Exception> inlinedInto(Method method) { return inspector -> { MethodSubject candidate = inspector.method(methodOfInterest); if (!candidate.isPresent()) {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java index c2f1811..a91f006 100644 --- a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java +++ b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.debuginfo; import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber; +import static com.android.tools.r8.utils.InternalOptions.LineNumberOptimization.ON; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertNull; import static org.hamcrest.MatcherAssert.assertThat; @@ -72,6 +73,11 @@ .addProgramClasses(MAIN) .setMinApi(parameters.getApiLevel()) .internalEnableMappingOutput() + // TODO(b/191038746): Enable LineNumberOptimization for release builds for DEX PC Output. + .applyIf( + apiLevelSupportsPcOutput(), + builder -> + builder.addOptionsModification(options -> options.lineNumberOptimization = ON)) .run(parameters.getRuntime(), MAIN) .inspectFailure( inspector -> {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ReleasedVersionsSmokeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ReleasedVersionsSmokeTest.java index a81de0a..8713609 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ReleasedVersionsSmokeTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ReleasedVersionsSmokeTest.java
@@ -35,6 +35,14 @@ "GMT", "1000", "Hello, world"); + private static final String expectedOutput_1_0_9 = + StringUtils.lines( + "true", + "Caught java.time.format.DateTimeParseException", + "true", + "1970-01-02T10:17:36.789Z", + "1000", + "Hello, world"); @Parameters(name = "{0}, {1}") public static List<Object[]> data() { @@ -61,12 +69,9 @@ .withKeepRuleConsumer() .setMode(CompilationMode.DEBUG) .build()) - .compile() - .run(parameters.getRuntime(), TestClass.class) - .applyIf( - configuration.equals(Configuration.RELEASED_1_1_5), - r -> r.assertSuccessWithOutput(expectedOutput), - r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class)); + .run(parameters.getRuntime(), TestClass.class, configuration.name()) + .assertSuccessWithOutput( + configuration != Configuration.RELEASED_1_0_9 ? expectedOutput : expectedOutput_1_0_9); } @Test @@ -82,14 +87,15 @@ .withKeepRuleConsumer() .setMode(CompilationMode.RELEASE) .build()) - .compile() - .run(parameters.getRuntime(), TestClass.class) - .assertSuccessWithOutput(expectedOutput); + .run(parameters.getRuntime(), TestClass.class, configuration.name()) + .assertSuccessWithOutput( + configuration != Configuration.RELEASED_1_0_9 ? expectedOutput : expectedOutput_1_0_9); } static class TestClass { public static void main(String[] args) { + String configurationVersion = args[0]; System.out.println(Clock.systemDefaultZone().getZone().equals(ZoneId.systemDefault())); try { java.time.LocalDate.parse(""); @@ -100,9 +106,12 @@ System.out.println( java.util.Date.from(new java.util.Date(123456789).toInstant()).toInstant()); - java.util.TimeZone timeZone = java.util.TimeZone.getTimeZone(ZoneId.of("GMT")); - System.out.println(timeZone.getID()); - System.out.println(timeZone.toZoneId().getId()); + // Support for this was added in 1.0.10. + if (!configurationVersion.equals("RELEASED_1_0_9")) { + java.util.TimeZone timeZone = java.util.TimeZone.getTimeZone(ZoneId.of("GMT")); + System.out.println(timeZone.getID()); + System.out.println(timeZone.toZoneId().getId()); + } System.out.println(Duration.ofMillis(1000).toMillis());
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestPrivateInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestPrivateInterfaceMethodsTest.java index ae2b6ae..52bc03e 100644 --- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestPrivateInterfaceMethodsTest.java +++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestPrivateInterfaceMethodsTest.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.TestRuntime.CfVm; import com.android.tools.r8.utils.StringUtils; +import java.nio.file.Path; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -47,7 +48,7 @@ .addProgramClasses(TestClass.class) .addProgramClassFileData(getClassWithNest(I.class), getClassWithNest(J.class)) .run(parameters.getRuntime(), TestClass.class) - // TODO(191115349): Nest desugar does not downgrade the classfile version. + // TODO(b/191115349): Nest desugar does not downgrade the classfile version. .applyIf( c -> DesugarTestConfiguration.isNotJavac(c) @@ -56,6 +57,38 @@ r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class)); } + @Test + public void testOnClasspath() throws Exception { + byte[] bytesI = getClassWithNest(I.class); + byte[] bytesJ = getClassWithNest(J.class); + Path outI = + testForD8(parameters.getBackend()) + .addProgramClassFileData(bytesI) + .addClasspathClassFileData(bytesJ) + .setMinApi(parameters.getApiLevel()) + .compile() + .writeToZip(); + Path outJ = + testForD8(parameters.getBackend()) + .addProgramClassFileData(bytesJ) + .addClasspathClassFileData(bytesI) + .setMinApi(parameters.getApiLevel()) + .compile() + .writeToZip(); + Path outTestClass = + testForD8(parameters.getBackend()) + .addProgramClasses(TestClass.class) + .addClasspathClassFileData(bytesI, bytesJ) + .setMinApi(parameters.getApiLevel()) + .compile() + .writeToZip(); + testForD8(parameters.getBackend()) + .addProgramFiles(outI, outJ, outTestClass) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } + interface I { default /* will be private */ void foo() { System.out.println("Hello world!");
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureVerticalMergeTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureVerticalMergeTest.java new file mode 100644 index 0000000..b8d02f6 --- /dev/null +++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureVerticalMergeTest.java
@@ -0,0 +1,102 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.graph.genericsignature; + +import static org.hamcrest.CoreMatchers.containsString; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoVerticalClassMerging; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import java.lang.reflect.Type; +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 GenericSignatureVerticalMergeTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public GenericSignatureVerticalMergeTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test(expected = CompilationFailedException.class) + public void testR8() throws Exception { + testForR8Compat(parameters.getBackend()) + .addInnerClasses(getClass()) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(Main.class) + .addKeepClassRules(I.class, J.class) + .addKeepClassAndMembersRulesWithAllowObfuscation(Base.class) + .addKeepAttributeInnerClassesAndEnclosingMethod() + .addKeepAttributeSignature() + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .enableNoVerticalClassMergingAnnotations() + .addVerticallyMergedClassesInspector( + inspector -> inspector.assertMergedIntoSubtype(A.class)) + .compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertErrorMessageThatMatches( + containsString("Super type inconsistency in generic signature")); + }); + } + + public interface I<T> { + + T t(); + } + + @NoVerticalClassMerging + public static class Base<T> {} + + public static class A<S, T> extends Base<S> implements I<T> { + + @Override + @NeverInline + public T t() { + System.out.println("I::t"); + return null; + } + } + + public interface J<R> { + + void r(R r); + } + + @NeverClassInline + public static class B<X> extends A<String, X> implements J<X> { + + @Override + @NeverInline + public void r(X x) { + System.out.println("B::r"); + } + } + + public static class Main { + + public static void main(String[] args) { + System.out.println(B.class.getGenericSuperclass()); + for (Type genericInterface : B.class.getGenericInterfaces()) { + System.out.println(genericInterface); + } + B<String> b = new B<>(); + b.r(b.t()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/regress/b191296688/A.java b/src/test/java/com/android/tools/r8/regress/b191296688/A.java new file mode 100644 index 0000000..8b6b5fa --- /dev/null +++ b/src/test/java/com/android/tools/r8/regress/b191296688/A.java
@@ -0,0 +1,10 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.regress.b191296688; + +public class A { + public void doIt(Runnable r) { + r.run(); + } +}
diff --git a/src/test/java/com/android/tools/r8/regress/b191296688/B.kt b/src/test/java/com/android/tools/r8/regress/b191296688/B.kt new file mode 100644 index 0000000..a0ed49a --- /dev/null +++ b/src/test/java/com/android/tools/r8/regress/b191296688/B.kt
@@ -0,0 +1,16 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.regress.b191296688 + +class B { + fun doIt() { + A().doIt(this::proceed) + } + + private fun proceed() { + println("hep") + } +} + +fun main() = B().doIt() \ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/regress/b191296688/Regress191296688.java b/src/test/java/com/android/tools/r8/regress/b191296688/Regress191296688.java new file mode 100644 index 0000000..dc7458c --- /dev/null +++ b/src/test/java/com/android/tools/r8/regress/b191296688/Regress191296688.java
@@ -0,0 +1,95 @@ +// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.regress.b191296688; + +import static com.android.tools.r8.ToolHelper.getKotlinAnnotationJar; +import static com.android.tools.r8.ToolHelper.getKotlinStdlibJar; +import static com.android.tools.r8.utils.codeinspector.CodeMatchers.isInvokeWithTarget; +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.KotlinTestBase; +import com.android.tools.r8.KotlinTestParameters; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime; +import com.android.tools.r8.TestRuntime.CfRuntime; +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.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.InstructionSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import java.nio.file.Path; +import java.util.Collection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class Regress191296688 extends KotlinTestBase { + + private static final String PKG = Regress191296688.class.getPackage().getName(); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}, {1}") + public static Collection<Object[]> data() { + return buildParameters( + getTestParameters().withDexRuntimes().withAllApiLevels().build(), + getKotlinTestParameters() + .withTargetVersion(KotlinTargetVersion.JAVA_8) + .withCompiler(ToolHelper.getKotlinC_1_5_0_m2()) + .build()); + } + + public Regress191296688(TestParameters parameters, KotlinTestParameters kotlinParameters) { + super(kotlinParameters); + this.parameters = parameters; + } + + @Test + public void testRegress191296688() throws Exception { + Path aLib = temp.newFolder().toPath().resolve("alib.jar"); + writeClassesToJar(aLib, A.class); + String folder = DescriptorUtils.getBinaryNameFromJavaType(PKG); + CfRuntime cfRuntime = TestRuntime.getCheckedInJdk9(); + Path ktClasses = + kotlinc(cfRuntime, kotlinc, targetVersion) + .addSourceFiles(getKotlinFileInTest(folder, "B")) + .addClasspathFiles(aLib) + .compile(); + Path desugaredJar = + testForD8(Backend.CF) + .addLibraryFiles(getKotlinStdlibJar(kotlinc), getKotlinAnnotationJar(kotlinc)) + .addProgramFiles(ktClasses) + .addProgramClasses(A.class) + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect(this::verifyVirtualCallToPrivate) + .writeToZip(); + testForD8() + .addProgramFiles(desugaredJar) + .setMinApi(parameters.getApiLevel()) + .disableDesugaring() + .run(parameters.getRuntime(), PKG + ".BKt") + .assertSuccessWithOutputLines("hep"); + } + + private void verifyVirtualCallToPrivate(CodeInspector inspector) { + ClassSubject bClassSubject = inspector.clazz(PKG + ".B"); + MethodSubject proceedMethodSubject = bClassSubject.uniqueMethodWithName("proceed"); + assertThat(proceedMethodSubject, isPresent()); + assertTrue( + bClassSubject.allMethods().stream() + .anyMatch( + method -> + method + .streamInstructions() + .filter(InstructionSubject::isInvokeVirtual) + .anyMatch(isInvokeWithTarget(proceedMethodSubject)))); + } +}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java index 8179e48..689fce0 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
@@ -80,6 +80,8 @@ public HorizontallyMergedClassesInspector assertMergedInto(Class<?> from, Class<?> target) { assertEquals( horizontallyMergedClasses.getMergeTargetOrDefault(toDexType(from)), toDexType(target)); + seen.add(toDexType(from).asClassReference()); + seen.add(toDexType(target).asClassReference()); return this; }
diff --git a/third_party/android_jar/api-database.tar.gz.sha1 b/third_party/android_jar/api-database.tar.gz.sha1 index 84abf49..6336e23 100644 --- a/third_party/android_jar/api-database.tar.gz.sha1 +++ b/third_party/android_jar/api-database.tar.gz.sha1
@@ -1 +1 @@ -e4da4b29079ac393e0012e7676dcca0799841e29 \ No newline at end of file +be72aeca006f1aba8b1fe4d9c3ff4c0e76259960 \ No newline at end of file