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 75a3e26..033f9e4 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
@@ -42,6 +42,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;
@@ -423,6 +424,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();
@@ -446,6 +451,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 1a2d82b..f05a1f5 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -116,6 +116,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))