Fix jdk21 switch on enum with extra cases

Bug: b/336510513
Change-Id: Ie0661b9f28467730786cd7fa6ca3739fb617df17
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchDesugaring.java
index 2dc50c6..495b0aa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchDesugaring.java
@@ -11,6 +11,7 @@
 
 import com.android.tools.r8.cf.code.CfArrayStore;
 import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfInstruction;
@@ -73,6 +74,8 @@
   public DesugarDescription compute(CfInstruction instruction, ProgramMethod context) {
     if (!instruction.isInvokeDynamic()) {
       // We need to replace the new MatchException with RuntimeException.
+      // TODO(b/340800750): Consider using a more specific exception than RuntimeException, such
+      //  as com.android.tools.r8.DesugarMatchException.
       if (instruction.isNew() && instruction.asNew().getType().isIdenticalTo(matchException)) {
         return DesugarDescription.builder()
             .setDesugarRewrite(
@@ -232,7 +235,7 @@
             DexField enumField =
                 getEnumField(
                     bootstrapArg.asDexValueString().getValue(), enumType, context, appView);
-            cfInstructions.add(new CfStaticFieldRead(enumField));
+            pushEnumField(cfInstructions, enumField);
           } else {
             throw new CompilationError(
                 "Invalid bootstrap arg for enum switch " + bootstrapArg, context.getOrigin());
@@ -258,7 +261,7 @@
           } else if (bootstrapArg.isDexValueConstDynamic()) {
             DexField enumField =
                 extractEnumField(bootstrapArg.asDexValueConstDynamic(), context, appView);
-            cfInstructions.add(new CfStaticFieldRead(enumField));
+            pushEnumField(cfInstructions, enumField);
           } else {
             throw new CompilationError(
                 "Invalid bootstrap arg for type switch " + bootstrapArg, context.getOrigin());
@@ -266,6 +269,15 @@
         });
   }
 
+  private void pushEnumField(List<CfInstruction> cfInstructions, DexField enumField) {
+    if (enumField == null) {
+      // Extremely rare case where the compilation is invalid, the case is unreachable.
+      cfInstructions.add(new CfConstNull());
+    } else {
+      cfInstructions.add(new CfStaticFieldRead(enumField));
+    }
+  }
+
   private void generateSwitchLoadArguments(
       List<CfInstruction> cfInstructions, DexCallSite callSite, Consumer<DexValue> adder) {
     // We need to call the method with the bootstrap args as parameters.
@@ -281,6 +293,4 @@
       cfInstructions.add(new CfArrayStore(MemberType.OBJECT));
     }
   }
-
-
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchDesugaringHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchDesugaringHelper.java
index fbb5e8f..5815e6b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchDesugaringHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchDesugaringHelper.java
@@ -110,8 +110,9 @@
     }
     DexEncodedField dexEncodedField = enumClass.lookupUniqueStaticFieldWithName(fieldName);
     if (dexEncodedField == null) {
-      throw throwEnumFieldConstantDynamic(
-          "Missing enum field " + fieldName + " in " + enumType, context);
+      // If the field is missing, but the class is there, the case is considered unreachable and
+      // effectively removed from the switch.
+      return null;
     }
     return dexEncodedField.getReference();
   }
diff --git a/src/test/examplesJava21/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java
new file mode 100644
index 0000000..935f4b6
--- /dev/null
+++ b/src/test/examplesJava21/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java
@@ -0,0 +1,172 @@
+// 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 switchpatternmatching;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static switchpatternmatching.SwitchTestHelper.hasJdk21EnumSwitch;
+import static switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EnumLessCasesAtRuntimeSwitchTest extends TestBase {
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public static String EXPECTED_OUTPUT =
+      StringUtils.lines("TYPE", "null", "E1", "E3", "E5", "a C", "ENUM", "null", "1", "3", "0");
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
+    assertTrue(
+        hasJdk21EnumSwitch(inspector.clazz(Main.class).uniqueMethodWithOriginalName("enumSwitch")));
+    assertTrue(
+        hasJdk21TypeSwitch(inspector.clazz(Main.class).uniqueMethodWithOriginalName("typeSwitch")));
+
+    parameters.assumeJvmTestParameters();
+    testForJvm(parameters)
+        .apply(this::addModifiedProgramClasses)
+        .run(parameters.getRuntime(), Main.class)
+        .applyIf(
+            parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK21),
+            r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+            r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+  }
+
+  private <T extends TestBuilder<?, T>> void addModifiedProgramClasses(
+      TestBuilder<?, T> testBuilder) throws Exception {
+    testBuilder
+        .addStrippedOuter(getClass())
+        .addProgramClasses(FakeI.class, C.class, I.class, Main.class)
+        .addProgramClassFileData(
+            transformer(CompileTimeE.class)
+                .setImplements(FakeI.class)
+                .setClassDescriptor(RuntimeE.class.descriptorString())
+                .transform())
+        .addProgramClassFileData(
+            transformer(RuntimeE.class)
+                .setImplements(I.class)
+                .setClassDescriptor(CompileTimeE.class.descriptorString())
+                .transform());
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    parameters.assumeDexRuntime();
+    testForD8()
+        .apply(this::addModifiedProgramClasses)
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Assume.assumeTrue("For Cf we should compile with Jdk 21 library", parameters.isDexRuntime());
+    testForR8(parameters.getBackend())
+        .apply(this::addModifiedProgramClasses)
+        .setMinApi(parameters)
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  sealed interface I permits CompileTimeE, C {}
+
+  public enum CompileTimeE implements I {
+    E1,
+    E2, // Case missing at runtime.
+    E3,
+    E4, // Case missing at runtime.
+    E5
+  }
+
+  interface FakeI {}
+
+  public enum RuntimeE implements FakeI {
+    E1,
+    E3,
+    E5
+  }
+
+  static final class C implements I {}
+
+  static class Main {
+
+    static void typeSwitch(I i) {
+      switch (i) {
+        case CompileTimeE.E1 -> {
+          System.out.println("E1");
+        }
+        case CompileTimeE.E2 -> { // Case missing at runtime.
+          System.out.println("E2");
+        }
+        case CompileTimeE.E3 -> {
+          System.out.println("E3");
+        }
+        case CompileTimeE.E4 -> { // Case missing at runtime.
+          System.out.println("E4");
+        }
+        case CompileTimeE.E5 -> {
+          System.out.println("E5");
+        }
+        case C c -> {
+          System.out.println("a C");
+        }
+      }
+    }
+
+    static void enumSwitch(CompileTimeE e) {
+      switch (e) {
+        case null -> System.out.println("null");
+        case CompileTimeE.E1 -> System.out.println("1");
+        case CompileTimeE.E2 -> System.out.println("2"); // Case missing at runtime.
+        case CompileTimeE t when t == CompileTimeE.E3 -> System.out.println("3");
+        case CompileTimeE t when t.name().equals("E4") ->
+            System.out.println("4"); // Case missing at runtime.
+        case CompileTimeE t -> System.out.println("0");
+      }
+    }
+
+    public static void main(String[] args) {
+      System.out.println("TYPE");
+      try {
+        typeSwitch(null);
+      } catch (NullPointerException e) {
+        System.out.println("null");
+      }
+      typeSwitch(CompileTimeE.E1);
+      typeSwitch(CompileTimeE.E3);
+      typeSwitch(CompileTimeE.E5);
+      typeSwitch(new C());
+
+      System.out.println("ENUM");
+      enumSwitch(null);
+      enumSwitch(CompileTimeE.E1);
+      enumSwitch(CompileTimeE.E3);
+      enumSwitch(CompileTimeE.E5);
+    }
+  }
+}
diff --git a/src/test/examplesJava21/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java
new file mode 100644
index 0000000..5d6802f
--- /dev/null
+++ b/src/test/examplesJava21/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java
@@ -0,0 +1,222 @@
+// 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 switchpatternmatching;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static switchpatternmatching.SwitchTestHelper.hasJdk21EnumSwitch;
+import static switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EnumMoreCasesAtRuntimeSwitchTest extends TestBase {
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public static String EXPECTED_OUTPUT =
+      StringUtils.lines(
+          "TYPE",
+          "null",
+          "E1",
+          "class %s",
+          "E3",
+          "class %s",
+          "E5",
+          "a C",
+          "ENUM",
+          "null",
+          "1",
+          "0",
+          "3",
+          "4",
+          "0");
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
+    assertTrue(
+        hasJdk21EnumSwitch(inspector.clazz(Main.class).uniqueMethodWithOriginalName("enumSwitch")));
+    assertTrue(
+        hasJdk21TypeSwitch(inspector.clazz(Main.class).uniqueMethodWithOriginalName("typeSwitch")));
+
+    parameters.assumeJvmTestParameters();
+    testForJvm(parameters)
+        .apply(this::addModifiedProgramClasses)
+        .run(parameters.getRuntime(), Main.class)
+        .applyIf(
+            parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK21),
+            r ->
+                r.assertSuccessWithOutput(
+                    String.format(
+                        EXPECTED_OUTPUT, "java.lang.MatchException", "java.lang.MatchException")),
+            r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+  }
+
+  private <T extends TestBuilder<?, T>> void addModifiedProgramClasses(
+      TestBuilder<?, T> testBuilder) throws Exception {
+    testBuilder
+        .addStrippedOuter(getClass())
+        .addProgramClasses(FakeI.class, C.class, I.class)
+        .addProgramClassFileData(
+            transformer(CompileTimeE.class)
+                .setImplements(FakeI.class)
+                .setClassDescriptor(RuntimeE.class.descriptorString())
+                .transform())
+        .addProgramClassFileData(
+            transformer(RuntimeE.class)
+                .setImplements(I.class)
+                .setClassDescriptor(CompileTimeE.class.descriptorString())
+                .transform())
+        .addProgramClassFileData(
+            transformer(Main.class)
+                .transformFieldInsnInMethod(
+                    "getE2",
+                    (opcode, owner, name, descriptor, visitor) -> {
+                      visitor.visitFieldInsn(opcode, owner, "E2", descriptor);
+                    })
+                .transformFieldInsnInMethod(
+                    "getE4",
+                    (opcode, owner, name, descriptor, visitor) -> {
+                      visitor.visitFieldInsn(opcode, owner, "E4", descriptor);
+                    })
+                .transform());
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    parameters.assumeDexRuntime();
+    testForD8()
+        .apply(this::addModifiedProgramClasses)
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(
+            String.format(
+                EXPECTED_OUTPUT, "java.lang.RuntimeException", "java.lang.RuntimeException"));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Assume.assumeTrue("For Cf we should compile with Jdk 21 library", parameters.isDexRuntime());
+    testForR8(parameters.getBackend())
+        .apply(this::addModifiedProgramClasses)
+        .setMinApi(parameters)
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(
+            String.format(
+                EXPECTED_OUTPUT, "java.lang.RuntimeException", "java.lang.RuntimeException"));
+  }
+
+  sealed interface I permits CompileTimeE, C {}
+
+  public enum RuntimeE implements FakeI {
+    E1,
+    E2, // Case present at runtime.
+    E3,
+    E4, // Case present at runtime.
+    E5
+  }
+
+  interface FakeI {}
+
+  public enum CompileTimeE implements I {
+    E1,
+    E3,
+    E5
+  }
+
+  static final class C implements I {}
+
+  static class Main {
+
+    static void typeSwitch(I i) {
+      switch (i) {
+        case CompileTimeE.E1 -> {
+          System.out.println("E1");
+        }
+        case CompileTimeE.E3 -> {
+          System.out.println("E3");
+        }
+        case CompileTimeE.E5 -> {
+          System.out.println("E5");
+        }
+        case C c -> {
+          System.out.println("a C");
+        }
+      }
+    }
+
+    static void enumSwitch(CompileTimeE e) {
+      switch (e) {
+        case null -> System.out.println("null");
+        case CompileTimeE.E1 -> System.out.println("1");
+        case CompileTimeE t when t == CompileTimeE.E3 -> System.out.println("3");
+        case CompileTimeE t when t.name().equals("E4") ->
+            System.out.println("4"); // Case present at runtime.
+        case CompileTimeE t -> System.out.println("0");
+      }
+    }
+
+    public static CompileTimeE getE2() {
+      // Replaced by RuntimeE.E2;
+      return CompileTimeE.E1;
+    }
+
+    public static CompileTimeE getE4() {
+      // Replaced by RuntimeE.E4;
+      return CompileTimeE.E1;
+    }
+
+    public static void main(String[] args) {
+      System.out.println("TYPE");
+      try {
+        typeSwitch(null);
+      } catch (NullPointerException e) {
+        System.out.println("null");
+      }
+      typeSwitch(CompileTimeE.E1);
+      try {
+        typeSwitch(getE2());
+      } catch (Exception e) {
+        System.out.println(e.getClass().toString());
+      }
+      typeSwitch(CompileTimeE.E3);
+      try {
+        typeSwitch(getE4());
+      } catch (Exception e) {
+        System.out.println(e.getClass().toString());
+      }
+      typeSwitch(CompileTimeE.E5);
+      typeSwitch(new C());
+
+      System.out.println("ENUM");
+      enumSwitch(null);
+      enumSwitch(CompileTimeE.E1);
+      enumSwitch(getE2());
+      enumSwitch(CompileTimeE.E3);
+      enumSwitch(getE4());
+      enumSwitch(CompileTimeE.E5);
+    }
+  }
+}
diff --git a/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java
index b2c99cb..85eadfc 100644
--- a/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java
@@ -3,8 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package switchpatternmatching;
 
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
+import static switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
 
 import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.TestBase;
@@ -15,14 +16,12 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
-import switchpatternmatching.StringSwitchTest.Main;
 
 @RunWith(Parameterized.class)
 public class EnumSwitchTest extends TestBase {
@@ -41,33 +40,8 @@
   public void testJvm() throws Exception {
     assumeTrue(parameters.isCfRuntime());
     CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
-    // javac generated an invokedynamic using bootstrap method
-    // java.lang.runtime.SwitchBootstraps.typeSwitch.
-    assertEquals(
-        1,
-        inspector
-            .clazz(Main.class)
-            .uniqueMethodWithOriginalName("enumSwitch")
-            .streamInstructions()
-            .filter(InstructionSubject::isInvokeDynamic)
-            .map(
-                instruction ->
-                    instruction
-                        .asCfInstruction()
-                        .getInstruction()
-                        .asInvokeDynamic()
-                        .getCallSite()
-                        .getBootstrapMethod()
-                        .member
-                        .asDexMethod())
-            .filter(
-                method ->
-                    method
-                        .getHolderType()
-                        .toString()
-                        .contains("java.lang.runtime.SwitchBootstraps"))
-            .filter(method -> method.toString().contains("typeSwitch"))
-            .count());
+    assertTrue(
+        hasJdk21TypeSwitch(inspector.clazz(Main.class).uniqueMethodWithOriginalName("enumSwitch")));
 
     parameters.assumeJvmTestParameters();
     testForJvm(parameters)
diff --git a/src/test/examplesJava21/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethod.java b/src/test/examplesJava21/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethod.java
index ce82a66..582712f 100644
--- a/src/test/examplesJava21/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethod.java
+++ b/src/test/examplesJava21/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethod.java
@@ -3,8 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package switchpatternmatching;
 
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
+import static switchpatternmatching.SwitchTestHelper.hasJdk21EnumSwitch;
 
 import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.TestBase;
@@ -14,7 +15,6 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -46,33 +46,8 @@
   public void testJvm() throws Exception {
     assumeTrue(parameters.isCfRuntime());
     CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
-    // javac generated an invokedynamic using bootstrap method
-    // java.lang.runtime.SwitchBootstraps.enumSwitch.
-    assertEquals(
-        1,
-        inspector
-            .clazz(Main.class)
-            .uniqueMethodWithOriginalName("enumSwitch")
-            .streamInstructions()
-            .filter(InstructionSubject::isInvokeDynamic)
-            .map(
-                instruction ->
-                    instruction
-                        .asCfInstruction()
-                        .getInstruction()
-                        .asInvokeDynamic()
-                        .getCallSite()
-                        .getBootstrapMethod()
-                        .member
-                        .asDexMethod())
-            .filter(
-                method ->
-                    method
-                        .getHolderType()
-                        .toString()
-                        .contains("java.lang.runtime.SwitchBootstraps"))
-            .filter(method -> method.toString().contains("enumSwitch"))
-            .count());
+    assertTrue(
+        hasJdk21EnumSwitch(inspector.clazz(Main.class).uniqueMethodWithOriginalName("enumSwitch")));
 
     parameters.assumeJvmTestParameters();
     testForJvm(parameters)
diff --git a/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java
index 7b1fb6f..496faa6 100644
--- a/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java
@@ -3,8 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package switchpatternmatching;
 
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
+import static switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
 
 import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.TestBase;
@@ -14,7 +15,6 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -41,33 +41,9 @@
   public void testJvm() throws Exception {
     assumeTrue(parameters.isCfRuntime());
     CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
-    // javac generated an invokedynamic using bootstrap method
-    // java.lang.runtime.SwitchBootstraps.typeSwitch.
-    assertEquals(
-        1,
-        inspector
-            .clazz(Main.class)
-            .uniqueMethodWithOriginalName("stringSwitch")
-            .streamInstructions()
-            .filter(InstructionSubject::isInvokeDynamic)
-            .map(
-                instruction ->
-                    instruction
-                        .asCfInstruction()
-                        .getInstruction()
-                        .asInvokeDynamic()
-                        .getCallSite()
-                        .getBootstrapMethod()
-                        .member
-                        .asDexMethod())
-            .filter(
-                method ->
-                    method
-                        .getHolderType()
-                        .toString()
-                        .contains("java.lang.runtime.SwitchBootstraps"))
-            .filter(method -> method.toString().contains("typeSwitch"))
-            .count());
+    assertTrue(
+        hasJdk21TypeSwitch(
+            inspector.clazz(Main.class).uniqueMethodWithOriginalName("stringSwitch")));
 
     parameters.assumeJvmTestParameters();
     testForJvm(parameters)
diff --git a/src/test/examplesJava21/switchpatternmatching/SwitchTestHelper.java b/src/test/examplesJava21/switchpatternmatching/SwitchTestHelper.java
new file mode 100644
index 0000000..1cc4f82
--- /dev/null
+++ b/src/test/examplesJava21/switchpatternmatching/SwitchTestHelper.java
@@ -0,0 +1,42 @@
+// 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 switchpatternmatching;
+
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+
+public class SwitchTestHelper {
+
+  public static boolean hasJdk21TypeSwitch(MethodSubject methodSubject) {
+    return hasJdk21Switch(methodSubject, "typeSwitch");
+  }
+
+  public static boolean hasJdk21EnumSwitch(MethodSubject methodSubject) {
+    return hasJdk21Switch(methodSubject, "enumSwitch");
+  }
+
+  private static boolean hasJdk21Switch(MethodSubject methodSubject, String switchMethod) {
+    assert methodSubject.isPresent();
+    // javac generated an invokedynamic using bootstrap method
+    // java.lang.runtime.SwitchBootstraps.typeSwitch|enumSwitch.
+    return methodSubject
+        .streamInstructions()
+        .filter(InstructionSubject::isInvokeDynamic)
+        .map(
+            instruction ->
+                instruction
+                    .asCfInstruction()
+                    .getInstruction()
+                    .asInvokeDynamic()
+                    .getCallSite()
+                    .getBootstrapMethod()
+                    .member
+                    .asDexMethod())
+        .filter(
+            method ->
+                method.getHolderType().toString().contains("java.lang.runtime.SwitchBootstraps"))
+        .anyMatch(method -> method.toString().contains(switchMethod));
+  }
+}
diff --git a/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
index fa474c0..3e91d39 100644
--- a/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
@@ -3,8 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package switchpatternmatching;
 
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
+import static switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
 
 import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.TestBase;
@@ -14,13 +15,13 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
+import switchpatternmatching.StringSwitchTest.Main;
 
 @RunWith(Parameterized.class)
 public class TypeSwitchTest extends TestBase {
@@ -40,41 +41,8 @@
   public void testJvm() throws Exception {
     assumeTrue(parameters.isCfRuntime());
     CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
-    // javac generated an invokedynamic using bootstrap method argument of an arrya type (sort 9
-    // is org.objectweb.asm.Type.ARRAY).
-    inspector
-        .clazz(Main.class)
-        .uniqueMethodWithOriginalName("typeSwitch")
-        .streamInstructions()
-        .filter(InstructionSubject::isInvokeDynamic)
-        .count();
-    // javac generated an invokedynamic using bootstrap method
-    // java.lang.runtime.SwitchBootstraps.typeSwitch.
-    assertEquals(
-        1,
-        inspector
-            .clazz(Main.class)
-            .uniqueMethodWithOriginalName("typeSwitch")
-            .streamInstructions()
-            .filter(InstructionSubject::isInvokeDynamic)
-            .map(
-                instruction ->
-                    instruction
-                        .asCfInstruction()
-                        .getInstruction()
-                        .asInvokeDynamic()
-                        .getCallSite()
-                        .getBootstrapMethod()
-                        .member
-                        .asDexMethod())
-            .filter(
-                method ->
-                    method
-                        .getHolderType()
-                        .toString()
-                        .contains("java.lang.runtime.SwitchBootstraps"))
-            .filter(method -> method.toString().contains("typeSwitch"))
-            .count());
+    assertTrue(
+        hasJdk21TypeSwitch(inspector.clazz(Main.class).uniqueMethodWithOriginalName("typeSwitch")));
 
     parameters.assumeJvmTestParameters();
     testForJvm(parameters)
diff --git a/src/test/testbase/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/testbase/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 4348acf..8ca1934 100644
--- a/src/test/testbase/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/testbase/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -1316,7 +1316,7 @@
                   descriptor,
                   redirectVisitFieldInsn(this, super::visitFieldInsn));
             } else {
-              super.visitMethodInsn(opcode, owner, name, descriptor);
+              super.visitFieldInsn(opcode, owner, name, descriptor);
             }
           }
         });