blob: 1b4ad0fa1128f8d43df6d87987540c317cd05b76 [file] [log] [blame]
// 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)
|| syntheticItems.isSyntheticOfKind(
context.getHolderType(), kinds -> kinds.API_MODEL_OUTLINE_WITHOUT_GLOBAL_MERGING)) {
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());
}
}
}