Check API level for get instructions in constant canonicalizer
Avoid moving get instructions in the constant canonicalizer if the
field type is not safe to reference on all supported API levels.
The get will most likely be guarded by an API level check and
moving it can then cause java.lang.NoClassDefFoundError.
Fixes: b/331556916
Change-Id: I5e6e736492af9249971ef594e07fdc3fb3280e6c
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index c1f203a..13a06ea 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -44,6 +44,7 @@
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.passes.BranchSimplifier;
+import com.android.tools.r8.utils.AndroidApiLevelUtils;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.Sets;
@@ -453,6 +454,10 @@
if (instanceGet.instructionMayHaveSideEffects(appView, context)) {
return false;
}
+ if (!AndroidApiLevelUtils.isApiSafeForReference(
+ instanceGet.getField().getType(), appView)) {
+ return false;
+ }
NewInstance newInstance = null;
if (instanceGet.object().isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
newInstance = instanceGet.object().getDefinition().asNewInstance();
@@ -476,6 +481,10 @@
if (staticGet.instructionMayHaveSideEffects(appView, context)) {
return false;
}
+ if (!AndroidApiLevelUtils.isApiSafeForReference(
+ staticGet.getField().getType(), appView)) {
+ return false;
+ }
if (!isReadOfEffectivelyFinalFieldOutsideInitializer(staticGet)
&& !isEffectivelyFinalField(staticGet)) {
return false;
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 f448ee9..0e53313 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -117,6 +117,25 @@
return apiLevelOfOriginal.max(apiLevel).isLessThanOrEqualTo(options.getMinApiLevel()).isTrue();
}
+ public static boolean isApiSafeForReference(DexType type, AppView<?> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ DexType baseType = type.toBaseType(dexItemFactory);
+ if (baseType.isPrimitiveType()) {
+ return true;
+ }
+ DexClass baseClass = appView.definitionFor(baseType);
+ if (baseClass == null) {
+ // This could be a library class that is only available on newer api levels.
+ return false;
+ }
+ if (!baseClass.isLibraryClass()) {
+ // Program and classpath classes are not api level dependent.
+ return true;
+ }
+ LibraryClass baseLibraryClass = baseClass.asLibraryClass();
+ return isApiSafeForReference(baseLibraryClass, appView);
+ }
+
public static boolean isApiSafeForReference(LibraryDefinition definition, AppView<?> appView) {
return isApiSafeForReference(
definition, appView.apiLevelCompute(), appView.options(), appView.dexItemFactory());
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelConstantCanonicalizationTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelConstantCanonicalizationTest.java
index 20382b7..c463f1b 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelConstantCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelConstantCanonicalizationTest.java
@@ -66,10 +66,15 @@
.streamInstructions()
.filter(i -> i.isInstanceGet() | i.isIf())
.collect(Collectors.toList());
- // TODO(b/331556916): The instance get should not be moved before the if, as the type
- // of the field is not safe to reference on all API levels.
- assertTrue(filteredInstructions.get(0).isInstanceGet());
- assertTrue(filteredInstructions.get(1).isIf());
+ // The instance get can only be moved before the if, when the type of the field is
+ // safe to reference on all supported API levels.
+ if (sdkInt < 22) {
+ assertTrue(filteredInstructions.get(0).isIf());
+ assertTrue(filteredInstructions.get(1).isInstanceGet());
+ } else {
+ assertTrue(filteredInstructions.get(0).isInstanceGet());
+ assertTrue(filteredInstructions.get(1).isIf());
+ }
})
.addRunClasspathClasses(ApiLevel22.class)
.run(parameters.getRuntime(), Main.class, Integer.toString(sdkInt))