[ApiModel] Insert CheckCast for return values causing verification error
Bug: b/272725341
Change-Id: I2479d669217d90f057fa248fcea0f0f27faf91a6
diff --git a/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java b/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java
index 7562429..51394a9 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java
@@ -79,6 +79,8 @@
OptionalBool isLessThanOrEqualTo(ComputedApiLevel other);
+ OptionalBool isGreaterThan(AndroidApiLevel other);
+
class NotSetApiLevel implements ComputedApiLevel {
private static final NotSetApiLevel INSTANCE = new NotSetApiLevel();
@@ -98,6 +100,12 @@
}
@Override
+ public OptionalBool isGreaterThan(AndroidApiLevel other) {
+ assert false : "Cannot compute relationship for not set";
+ return OptionalBool.unknown();
+ }
+
+ @Override
public boolean isNotSetApiLevel() {
return true;
}
@@ -130,6 +138,11 @@
}
@Override
+ public OptionalBool isGreaterThan(AndroidApiLevel other) {
+ return OptionalBool.unknown();
+ }
+
+ @Override
public boolean isUnknownApiLevel() {
return true;
}
@@ -192,6 +205,11 @@
}
@Override
+ public OptionalBool isGreaterThan(AndroidApiLevel other) {
+ return OptionalBool.of(apiLevel.isGreaterThan(other));
+ }
+
+ @Override
public String toString() {
return apiLevel.toString();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index 935e9df..971e059 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -141,9 +141,16 @@
public static class Builder extends BuilderBase<Builder, Return> {
+ private Value returnValue = null;
+
+ public Builder setReturnValue(Value returnValue) {
+ this.returnValue = returnValue;
+ return self();
+ }
+
@Override
public Return build() {
- return amend(new Return());
+ return amend(returnValue == null ? new Return() : new Return(returnValue));
}
@Override
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 afa6aa1..265cccb 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
@@ -65,6 +65,7 @@
import com.android.tools.r8.ir.optimize.NaturalIntLoopRemover;
import com.android.tools.r8.ir.optimize.RedundantFieldLoadAndStoreElimination;
import com.android.tools.r8.ir.optimize.ReflectionOptimizer;
+import com.android.tools.r8.ir.optimize.RemoveVerificationErrorForUnknownReturnedValues;
import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
@@ -142,7 +143,9 @@
private final TypeChecker typeChecker;
private final ServiceLoaderRewriter serviceLoaderRewriter;
private final EnumValueOptimizer enumValueOptimizer;
- private final EnumUnboxer enumUnboxer;
+ protected final EnumUnboxer enumUnboxer;
+ protected final RemoveVerificationErrorForUnknownReturnedValues
+ removeVerificationErrorForUnknownReturnedValues;
public final AssumeInserter assumeInserter;
private final DynamicTypeOptimization dynamicTypeOptimization;
@@ -227,6 +230,7 @@
this.enumValueOptimizer = null;
this.enumUnboxer = EnumUnboxer.empty();
this.assumeInserter = null;
+ this.removeVerificationErrorForUnknownReturnedValues = null;
return;
}
this.instructionDesugaring =
@@ -237,6 +241,11 @@
options.processCovariantReturnTypeAnnotations
? new CovariantReturnTypeAnnotationTransformer(this, appView.dexItemFactory())
: null;
+ removeVerificationErrorForUnknownReturnedValues =
+ (appView.options().apiModelingOptions().enableApiCallerIdentification
+ && appView.options().canHaveVerifyErrorForUnknownUnusedReturnValue())
+ ? new RemoveVerificationErrorForUnknownReturnedValues(appView)
+ : null;
if (appView.enableWholeProgramOptimizations()) {
assert appView.appInfo().hasLiveness();
assert appView.rootSet() != null;
@@ -1324,7 +1333,7 @@
timing.begin("Split range invokes");
codeRewriter.splitRangeInvokeConstants(code);
timing.end();
- timing.begin("Propogate sparse conditionals");
+ timing.begin("Propagate sparse conditionals");
new SparseConditionalConstantPropagation(appView, code).run();
timing.end();
timing.begin("Rewrite always throwing instructions");
@@ -1451,6 +1460,10 @@
previous = printMethod(code, "IR after shorten live ranges (SSA)", previous);
}
+ if (removeVerificationErrorForUnknownReturnedValues != null) {
+ removeVerificationErrorForUnknownReturnedValues.run(context, code, timing);
+ }
+
timing.begin("Canonicalize idempotent calls");
idempotentFunctionCallCanonicalizer.canonicalize(code);
timing.end();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
index 3050bdc..c71debf 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.desugar.apimodel;
+import static com.android.tools.r8.utils.AndroidApiLevelUtils.isApiLevelLessThanOrEqualToG;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
@@ -34,7 +35,6 @@
import com.android.tools.r8.ir.synthetic.FieldAccessorBuilder;
import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.TraversalContinuation;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
@@ -114,7 +114,7 @@
ComputedApiLevel referenceApiLevel =
apiLevelCompute.computeApiLevelForLibraryReference(reference, ComputedApiLevel.unknown());
if (appView.computedMinApiLevel().isGreaterThanOrEqualTo(referenceApiLevel)
- || isApiLevelLessThanOrEqualTo9(referenceApiLevel)
+ || isApiLevelLessThanOrEqualToG(referenceApiLevel)
|| referenceApiLevel.isUnknownApiLevel()) {
return appView.computedMinApiLevel();
}
@@ -155,11 +155,6 @@
return traversalResult.isBreak() ? traversalResult.asBreak().getValue() : null;
}
- private boolean isApiLevelLessThanOrEqualTo9(ComputedApiLevel apiLevel) {
- return apiLevel.isKnownApiLevel()
- && apiLevel.asKnownApiLevel().getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.G);
- }
-
private Collection<CfInstruction> desugarLibraryCall(
UniqueContext uniqueContext,
CfInstruction instruction,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RemoveVerificationErrorForUnknownReturnedValues.java b/src/main/java/com/android/tools/r8/ir/optimize/RemoveVerificationErrorForUnknownReturnedValues.java
new file mode 100644
index 0000000..dd8f5bd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RemoveVerificationErrorForUnknownReturnedValues.java
@@ -0,0 +1,216 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import static com.android.tools.r8.utils.AndroidApiLevelUtils.isApiLevelLessThanOrEqualToG;
+
+import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
+import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.Set;
+
+/***
+ * Some Dalvik and ART runtimes have problems with verification when it comes to computing subtype
+ * relationship. Take the following code:
+ * <pre>
+ * public LibraryClass callLibraryWithDirectReturn() {
+ * if (AndroidBuildVersion.SDK_INT < 31) {
+ * return null;
+ * } else {
+ * LibrarySub sub = Outline.create();
+ * return sub;
+ * }
+ * }
+ * </pre>
+ *
+ * This can cause verification failures if LibraryClass is known but LibrarySub is unknown. It seems
+ * like the problem is that the verifier assumes that it can compute the relationship. If we add
+ * an instruction that causes soft-verification we remove the hard verification error:
+ * <pre>
+ * public LibraryClass callLibraryWithDirectReturn() {
+ * if (AndroidBuildVersion.SDK_INT < 31) {
+ * return null;
+ * } else {
+ * LibrarySub sub = Outline.create();
+ * sub.foo();
+ * return sub;
+ * }
+ * }
+ * </pre>
+ *
+ * The assumption here is that the verifier will figure out that it needs to run this by
+ * interpreting and bails out.
+ *
+ * We fix the issue here, both for our outlines and for manual outlines, by putting in a check-cast
+ * on the return value if a potential unknown library subtype flows to the return value.
+ * <pre>
+ * public LibraryClass callLibraryWithDirectReturn() {
+ * if (AndroidBuildVersion.SDK_INT < 31) {
+ * return null;
+ * } else {
+ * LibrarySub sub = Outline.create();
+ * return (LibraryClass)sub;
+ * }
+ * }
+ * </pre>
+ *
+ * See b/272725341 for more information.
+ */
+public class RemoveVerificationErrorForUnknownReturnedValues {
+
+ private final AppView<?> appView;
+ private final AndroidApiLevelCompute apiLevelCompute;
+ private final SyntheticItems syntheticItems;
+
+ public RemoveVerificationErrorForUnknownReturnedValues(AppView<?> appView) {
+ this.appView = appView;
+ this.apiLevelCompute = appView.apiLevelCompute();
+ this.syntheticItems = appView.getSyntheticItems();
+ }
+
+ private AppInfoWithClassHierarchy getAppInfoWithClassHierarchy() {
+ return appView.appInfoForDesugaring();
+ }
+
+ public void run(ProgramMethod context, IRCode code, Timing timing) {
+ timing.begin("Compute and insert checkcast on return values");
+ AppInfoWithClassHierarchy appInfoWithClassHierarchy = getAppInfoWithClassHierarchy();
+ Set<Return> returnValuesNeedingCheckCast =
+ getReturnsPotentiallyNeedingCheckCast(appInfoWithClassHierarchy, context, code);
+ insertCheckCastForReturnValues(context, code, returnValuesNeedingCheckCast);
+ timing.end();
+ }
+
+ private Set<Return> getReturnsPotentiallyNeedingCheckCast(
+ AppInfoWithClassHierarchy appInfo, ProgramMethod context, IRCode code) {
+ if (syntheticItems.isSyntheticOfKind(
+ context.getHolderType(), kinds -> kinds.API_MODEL_OUTLINE)) {
+ return Collections.emptySet();
+ }
+ DexType returnType = context.getReturnType();
+ if (!returnType.isClassType()) {
+ return Collections.emptySet();
+ }
+ // Everything is assignable to object type and the verifier do not throw an error here.
+ if (returnType == appView.dexItemFactory().objectType) {
+ return Collections.emptySet();
+ }
+ DexClass returnTypeClass = appInfo.definitionFor(returnType);
+ if (returnTypeClass == null || !returnTypeClass.isLibraryClass()) {
+ return Collections.emptySet();
+ }
+ ComputedApiLevel computedReturnApiLevel =
+ apiLevelCompute.computeApiLevelForLibraryReference(returnType, ComputedApiLevel.unknown());
+ if (computedReturnApiLevel.isUnknownApiLevel()) {
+ return Collections.emptySet();
+ }
+ Set<Value> seenSet = Sets.newIdentityHashSet();
+ Set<Return> returnsOfInterest = Sets.newIdentityHashSet();
+ code.computeNormalExitBlocks()
+ .forEach(
+ basicBlock -> {
+ Return exit = basicBlock.exit().asReturn();
+ Value aliasedReturnValue = exit.returnValue().getAliasedValue();
+ if (shouldInsertCheckCastForValue(appInfo, returnType, aliasedReturnValue, seenSet)) {
+ returnsOfInterest.add(exit);
+ }
+ });
+ return returnsOfInterest;
+ }
+
+ private boolean shouldInsertCheckCastForValue(
+ AppInfoWithClassHierarchy appInfo, DexType returnType, Value value, Set<Value> seenSet) {
+ WorkList<Value> workList = WorkList.newIdentityWorkList(value, seenSet);
+ while (workList.hasNext()) {
+ Value next = workList.next();
+ if (next.isPhi()) {
+ workList.addIfNotSeen(next.asPhi().getOperands());
+ }
+ TypeElement type = next.getType();
+ if (!type.isClassType()) {
+ assert type.isNullType() || type.isArrayType();
+ continue;
+ }
+ DexType returnValueType = type.asClassType().getClassType();
+ DexClass returnValueClass = appInfo.definitionFor(returnValueType);
+ if (returnValueClass == null || !returnValueClass.isLibraryClass()) {
+ continue;
+ }
+ if (!appInfo.isStrictSubtypeOf(returnValueType, returnType)) {
+ continue;
+ }
+ ComputedApiLevel computedValueApiLevel =
+ apiLevelCompute.computeApiLevelForLibraryReference(
+ returnValueType, ComputedApiLevel.unknown());
+ // We could in principle also bail out if the computedValueApiLevel == computedReturnApiLevel,
+ // however, if we stub the return type class we will introduce the error again. We do not know
+ // at this point if we stub the returnTypeClass.
+ ComputedApiLevel minApiLevel = appView.computedMinApiLevel();
+ if (!computedValueApiLevel.isUnknownApiLevel()
+ && !isApiLevelLessThanOrEqualToG(computedValueApiLevel)
+ && computedValueApiLevel.isGreaterThan(minApiLevel)
+ && isDalvikOrSubTypeIntroducedLaterThanAndroidR(minApiLevel, computedValueApiLevel)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Dalvik and some new ART versions have a stricter verifier that do not allow type-checking
+ // unknown return value types against a known return type.
+ private boolean isDalvikOrSubTypeIntroducedLaterThanAndroidR(
+ ComputedApiLevel minApiLevel, ComputedApiLevel subTypeApiLevel) {
+ if (minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.K_WATCH).isPossiblyTrue()) {
+ return true;
+ }
+ return subTypeApiLevel.isGreaterThan(AndroidApiLevel.R).isPossiblyTrue();
+ }
+
+ private void insertCheckCastForReturnValues(
+ ProgramMethod context, IRCode code, Set<Return> returnsNeedingCast) {
+ if (returnsNeedingCast.isEmpty()) {
+ return;
+ }
+ InstructionListIterator iterator = code.instructionListIterator();
+ while (iterator.hasNext()) {
+ Return returnInstruction = iterator.next().asReturn();
+ if (returnInstruction == null) {
+ continue;
+ }
+ DexType returnType = context.getReturnType();
+ Value returnValue = returnInstruction.returnValue();
+ CheckCast checkCast =
+ CheckCast.builder()
+ .setObject(returnValue)
+ .setFreshOutValue(
+ code, returnType.toTypeElement(appView, returnValue.getType().nullability()))
+ .setCastType(returnType)
+ .setPosition(returnInstruction.getPosition())
+ .build();
+ iterator.replaceCurrentInstruction(checkCast);
+ iterator.add(
+ Return.builder()
+ .setPosition(returnInstruction.getPosition())
+ .setReturnValue(checkCast.outValue())
+ .build());
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
index 2c3b88b..4e45bb5 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
@@ -22,6 +23,9 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
+import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.Set;
public class AndroidApiLevelUtils {
@@ -179,4 +183,147 @@
return oldBaseLibraryClass != null
&& isApiSafeForReference(newBaseLibraryClass, oldBaseLibraryClass, appView);
}
+
+ public static Pair<DexClass, ComputedApiLevel> findAndComputeApiLevelForLibraryDefinition(
+ AppView<?> appView,
+ AppInfoWithClassHierarchy appInfo,
+ DexClass holder,
+ DexMember<?, ?> reference) {
+ AndroidApiLevelCompute apiLevelCompute = appView.apiLevelCompute();
+ if (holder.isLibraryClass()) {
+ return Pair.create(
+ holder,
+ apiLevelCompute.computeApiLevelForLibraryReference(
+ reference, ComputedApiLevel.unknown()));
+ }
+ // The API database do not allow for resolving into it (since that is not stable), and it is
+ // therefore designed in a way where all members of classes can be queried on any sub-type with
+ // the api level for where it is reachable. It is therefore sufficient for us, to figure out if
+ // an instruction is a library call, to either find a program definition or to find the library
+ // frontier.
+ // Scan through the type hierarchy to find the first library class or program definition.
+ DexClass firstClassWithReferenceOrLibraryClass =
+ firstLibraryClassOrProgramClassWithDefinition(appInfo, holder, reference);
+ if (firstClassWithReferenceOrLibraryClass == null) {
+ return Pair.create(null, ComputedApiLevel.unknown());
+ }
+ if (!firstClassWithReferenceOrLibraryClass.isLibraryClass()) {
+ return Pair.create(firstClassWithReferenceOrLibraryClass, appView.computedMinApiLevel());
+ }
+ ComputedApiLevel apiLevel =
+ apiLevelCompute.computeApiLevelForLibraryReference(
+ reference.withHolder(
+ firstClassWithReferenceOrLibraryClass.getType(), appView.dexItemFactory()),
+ ComputedApiLevel.unknown());
+ if (apiLevel.isKnownApiLevel()) {
+ return Pair.create(firstClassWithReferenceOrLibraryClass, apiLevel);
+ }
+ // We were unable to find a definition in the class hierarchy, check all interfaces for a
+ // definition or the library interfaces for the first interface definition.
+ Set<DexClass> firstLibraryInterfaces =
+ findAllFirstLibraryInterfacesOrProgramClassWithDefinition(appInfo, holder, reference);
+ if (firstLibraryInterfaces.size() == 1) {
+ DexClass firstClass = firstLibraryInterfaces.iterator().next();
+ if (!firstClass.isLibraryClass()) {
+ return Pair.create(firstClass, appView.computedMinApiLevel());
+ }
+ }
+ DexClass foundClass = null;
+ ComputedApiLevel minApiLevel = ComputedApiLevel.unknown();
+ for (DexClass libraryInterface : firstLibraryInterfaces) {
+ assert libraryInterface.isLibraryClass();
+ ComputedApiLevel libraryIfaceApiLevel =
+ apiLevelCompute.computeApiLevelForLibraryReference(
+ reference.withHolder(
+ firstClassWithReferenceOrLibraryClass.getType(), appView.dexItemFactory()),
+ ComputedApiLevel.unknown());
+ if (minApiLevel.isGreaterThan(libraryIfaceApiLevel)) {
+ minApiLevel = libraryIfaceApiLevel;
+ foundClass = libraryInterface;
+ }
+ }
+ return Pair.create(foundClass, minApiLevel);
+ }
+
+ private static DexClass firstLibraryClassOrProgramClassWithDefinition(
+ AppInfoWithClassHierarchy appInfo, DexClass originalClass, DexMember<?, ?> reference) {
+ if (originalClass.isLibraryClass()) {
+ return originalClass;
+ }
+ WorkList<DexClass> workList = WorkList.newIdentityWorkList(originalClass);
+ while (workList.hasNext()) {
+ DexClass clazz = workList.next();
+ if (clazz.isLibraryClass()) {
+ return clazz;
+ } else if (clazz.lookupMember(reference) != null) {
+ return clazz;
+ } else if (clazz.getSuperType() != null) {
+ appInfo
+ .contextIndependentDefinitionForWithResolutionResult(clazz.getSuperType())
+ .forEachClassResolutionResult(workList::addIfNotSeen);
+ }
+ }
+ return null;
+ }
+
+ private static Set<DexClass> findAllFirstLibraryInterfacesOrProgramClassWithDefinition(
+ AppInfoWithClassHierarchy appInfo, DexClass originalClass, DexMember<?, ?> reference) {
+ Set<DexClass> interfaces = Sets.newLinkedHashSet();
+ WorkList<DexClass> workList = WorkList.newIdentityWorkList(originalClass);
+ while (workList.hasNext()) {
+ DexClass clazz = workList.next();
+ if (clazz.isLibraryClass()) {
+ if (clazz.isInterface()) {
+ interfaces.add(clazz);
+ }
+ } else if (clazz.lookupMember(reference) != null) {
+ return Collections.singleton(clazz);
+ } else {
+ clazz.forEachImmediateSupertype(
+ superType ->
+ appInfo
+ .contextIndependentDefinitionForWithResolutionResult(superType)
+ .forEachClassResolutionResult(workList::addIfNotSeen));
+ }
+ }
+ return interfaces;
+ }
+
+ /**
+ * A lot of functionality has already been outlined in androidx. The ordinary pattern for manual
+ * outlining is to create a class with the name ApiXXImpl where XX is the api level. This method
+ * will check the context to see if it matches this pattern in androidx and extract the api level
+ * for comparison with the computed api level.
+ */
+ public static boolean isOutlinedAtSameOrLowerLevel(
+ DexProgramClass clazz, ComputedApiLevel apiLevel) {
+ assert apiLevel.isKnownApiLevel();
+ if (!clazz.getType().getDescriptor().startsWith("Landroidx/")) {
+ return false;
+ }
+ String simpleName = clazz.getSimpleName();
+ int apiIndex = simpleName.indexOf("Api");
+ if (apiIndex < 0) {
+ return false;
+ }
+ int endApiIndex = apiIndex += 3;
+ int implIndex = simpleName.indexOf("Impl");
+ if (implIndex < 0 || implIndex < endApiIndex || (implIndex - endApiIndex) != 2) {
+ return false;
+ }
+ String apiLevelAsString = simpleName.substring(endApiIndex, implIndex);
+ if (!StringUtils.onlyContainsDigits(apiLevelAsString)) {
+ return false;
+ }
+ int apiLevelAsInt = Integer.parseInt(apiLevelAsString);
+ if (apiLevelAsInt < 10 || apiLevelAsInt > AndroidApiLevel.LATEST.getLevel()) {
+ return false;
+ }
+ return apiLevel.asKnownApiLevel().getApiLevel().getLevel() <= apiLevelAsInt;
+ }
+
+ public static boolean isApiLevelLessThanOrEqualToG(ComputedApiLevel apiLevel) {
+ return apiLevel.isKnownApiLevel()
+ && apiLevel.asKnownApiLevel().getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.G);
+ }
}
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 048a18e..6c80076 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2118,7 +2118,7 @@
* Predicate to guard against the possible presence of a VM bug.
*
* <p>Note that if the compilation is not desugaring to a min-api or targeting DEX at a min-api,
- * then the bug is assumed to be present as the CF output could be futher compiled to any target.
+ * then the bug is assumed to be present as the CF output could be further compiled to any target.
*/
private boolean canHaveBugPresentUntil(AndroidApiLevel level) {
if (desugarState.isOn() || isGeneratingDex()) {
@@ -2757,4 +2757,10 @@
public boolean canHaveIssueWithInlinedMonitors() {
return canHaveBugPresentUntil(AndroidApiLevel.N);
}
+
+ // b/272725341. ART 11 and 12 re-introduced hard verification errors when unable to compute
+ // subtype relationship when no other verification issues exists in code.
+ public boolean canHaveVerifyErrorForUnknownUnusedReturnValue() {
+ return isGeneratingDex() && canHaveBugPresentUntil(AndroidApiLevel.T);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelManualOutlineWithUnknownReturnTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelManualOutlineWithUnknownReturnTypeTest.java
index 8f921d3..e7be12b 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelManualOutlineWithUnknownReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelManualOutlineWithUnknownReturnTypeTest.java
@@ -4,10 +4,10 @@
package com.android.tools.r8.apimodel;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assume.assumeTrue;
-import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestCompileResult;
import com.android.tools.r8.TestParameters;
@@ -68,7 +68,7 @@
}
@Test
- public void testD8() throws Exception {
+ public void testD8WithModeling() throws Exception {
assumeTrue(parameters.isDexRuntime());
testForD8(parameters.getBackend())
.addProgramClasses(Main.class, ProgramClass.class, ManualOutline.class)
@@ -76,19 +76,30 @@
.addLibraryClasses(LibraryClass.class, LibrarySub.class)
.setMinApi(parameters.getApiLevel())
.addOptionsModification(options -> options.apiModelingOptions().disableMissingApiModeling())
+ .apply(setMockApiLevelForClass(LibraryClass.class, AndroidApiLevel.B))
+ .apply(setMockApiLevelForClass(LibrarySub.class, getMockApiLevel()))
+ .compile()
+ .apply(this::setupRuntime)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("ProgramClass::print");
+ }
+
+ @Test
+ public void testD8NoModeling() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ boolean willHaveVerifyError =
+ (parameters.getDexRuntimeVersion().isDalvik()
+ || parameters.isDexRuntimeVersion(Version.V12_0_0))
+ && !addedToLibraryHere;
+ testForD8(parameters.getBackend())
+ .addProgramClasses(Main.class, ProgramClass.class, ManualOutline.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .addLibraryClasses(LibraryClass.class, LibrarySub.class)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(options -> options.apiModelingOptions().disableMissingApiModeling())
.compile()
.apply(this::setupRuntime)
.run(parameters.getRuntime(), Main.class)
- .apply(this::checkOutput);
- }
-
- private void checkOutput(SingleTestRunResult<?> runResult) {
- // TODO(b/272725341): Potentially we can remove the verification error
- boolean willHaveVerifyError =
- (parameters.getDexRuntimeVersion().isDalvik()
- || parameters.isDexRuntimeVersion(Version.V12_0_0))
- && !addedToLibraryHere;
- runResult
.assertSuccessWithOutputLinesIf(!willHaveVerifyError, "ProgramClass::print")
.assertFailureWithErrorThatThrowsIf(willHaveVerifyError, VerifyError.class);
}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineWithUnknownReturnTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineWithUnknownReturnTypeTest.java
index 72f2815..928e5da 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineWithUnknownReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineWithUnknownReturnTypeTest.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.TestCompilerBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -124,14 +123,7 @@
}
private void checkOutput(SingleTestRunResult<?> runResult) {
- // TODO(b/272725341): We should not cause verify error.
- boolean willHaveVerifyError =
- (parameters.getDexRuntimeVersion().isDalvik()
- || parameters.isDexRuntimeVersion(Version.V12_0_0))
- && !addedToLibraryHere;
- runResult
- .assertSuccessWithOutputLinesIf(!willHaveVerifyError, "ProgramClass::print")
- .assertFailureWithErrorThatThrowsIf(willHaveVerifyError, VerifyError.class);
+ runResult.assertSuccessWithOutputLines("ProgramClass::print");
}
public static class LibraryClass {