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);
}
}
});