Fix switch map optimization NPE
- NPE happened when ignoreMissingClass is ON, and the input program
is already pre-processed to rebind the enum methods.
Bug: b/341991457
Change-Id: I2f5f3c96c5856a84b9031b1a163e3d8308499be2
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
index 5a0ec0d..a08777a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
@@ -151,7 +151,8 @@
return;
}
DexField enumField = enumGet.asStaticGet().getField();
- if (!appView.definitionFor(enumField.holder).accessFlags.isEnum()) {
+ DexClass enumClass = appView.definitionFor(enumField.holder);
+ if (enumClass == null || !enumClass.accessFlags.isEnum()) {
return;
}
if (switchMap.put(integerIndex, enumField) != null) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapMissingEnumTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapMissingEnumTest.java
new file mode 100644
index 0000000..4ade630
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapMissingEnumTest.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2024, 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.switches;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@SuppressWarnings("unchecked")
+@RunWith(Parameterized.class)
+public class SwitchMapMissingEnumTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ private Collection<byte[]> getInnerClassesWithoutEnum() throws IOException {
+ return ToolHelper.getClassFilesForInnerClasses(getClass()).stream()
+ .filter(c -> !c.toString().endsWith("MyEnum.class"))
+ .map(
+ c -> {
+ try {
+ return transformer(c, null)
+ .transformMethodInsnInMethod(
+ MethodPredicate.all(),
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ // Rewrites all the ordinal calls into Enum#ordinal() from
+ // MyEnum#ordinal().
+ if (name.equals("ordinal")) {
+ visitor.visitMethodInsn(
+ opcode, "java/lang/Enum", name, descriptor, isInterface);
+ } else {
+ visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .transform();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ })
+ .collect(Collectors.toList());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .setMinApi(parameters)
+ .addProgramClassFileData(getInnerClassesWithoutEnum())
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ parameters.isDexRuntimeVersionOlderThanOrEqual(Version.V4_4_4),
+ b -> b.assertFailureWithErrorThatThrows(VerifyError.class),
+ b -> b.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ testForR8(parameters.getBackend())
+ .addKeepMainRule(Main.class)
+ .addProgramClassFileData(getInnerClassesWithoutEnum())
+ .addOptionsModification(opt -> opt.ignoreMissingClasses = true)
+ .allowDiagnosticWarningMessages()
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ parameters.isDexRuntimeVersionOlderThanOrEqual(Version.V4_4_4),
+ b -> b.assertFailureWithErrorThatThrows(VerifyError.class),
+ b -> b.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+
+ @NeverClassInline
+ enum MyEnum {
+ A,
+ B;
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ print(MyEnum.A);
+ print(MyEnum.B);
+ }
+
+ private static void print(MyEnum e) {
+ switch (e) {
+ case A:
+ System.out.println("a");
+ break;
+ case B:
+ System.out.println("b");
+ break;
+ default:
+ System.out.println("0");
+ }
+ }
+ }
+}