Reland "Fix enum merging with abstract method error"
This reverts commit 63507f057b56d22c7f9f3cdeae7652f3c5258f5e.
Change-Id: I95c1fce94f36a4fc5c7ca20f5abb27158706b921
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 32f0cbd..2db3697 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -327,6 +327,8 @@
createString("Ljava/lang/IllegalAccessError;");
public final DexString illegalArgumentExceptionDescriptor =
createString("Ljava/lang/IllegalArgumentException;");
+ public final DexString abstractMethodErrorDescriptor =
+ createString("Ljava/lang/AbstractMethodError;");
public final DexString icceDescriptor = createString("Ljava/lang/IncompatibleClassChangeError;");
public final DexString exceptionInInitializerErrorDescriptor =
createString("Ljava/lang/ExceptionInInitializerError;");
@@ -573,6 +575,8 @@
createStaticallyKnownType(illegalAccessErrorDescriptor);
public final DexType illegalArgumentExceptionType =
createStaticallyKnownType(illegalArgumentExceptionDescriptor);
+ public final DexType abstractMethodErrorType =
+ createStaticallyKnownType(abstractMethodErrorDescriptor);
public final DexType icceType = createStaticallyKnownType(icceDescriptor);
public final DexType exceptionInInitializerErrorType =
createStaticallyKnownType(exceptionInInitializerErrorDescriptor);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 6ba6ad1..7449cee 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import static com.android.tools.r8.ir.optimize.enums.EnumUnboxerImpl.ordinalToUnboxedInt;
+import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClassAndMethod;
@@ -60,8 +61,9 @@
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
+import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider.CfCodeWithLens;
import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider.EnumUnboxingMethodDispatchCfCodeProvider;
-import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider.EnumUnboxingMethodDispatchCfCodeProvider.CfCodeWithLens;
+import com.android.tools.r8.ir.synthetic.ThrowCfCodeProvider;
import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ImmutableArrayUtils;
@@ -654,10 +656,13 @@
// and at least one override.
DexMethod reference = nonPrivateVirtualMethod.withHolder(unboxedEnum.getType(), factory);
ProgramMethodSet subimplementations = ProgramMethodSet.create();
+ boolean allImplements = true;
for (DexProgramClass subEnum : subEnums) {
ProgramMethod subMethod = subEnum.lookupProgramMethod(reference);
if (subMethod != null) {
subimplementations.add(subMethod);
+ } else {
+ allImplements = false;
}
}
DexClassAndMethod superMethod = unboxedEnum.lookupProgramMethod(reference);
@@ -666,60 +671,73 @@
superMethod = appView.appInfo().lookupSuperTarget(reference, unboxedEnum, appView);
assert superMethod == null || superMethod.getReference() == factory.enumMembers.toString;
}
- if (superMethod == null || subimplementations.isEmpty()) {
- // No emulated dispatch is required, just move everything.
- // If an abstract method with no implementors is found, effectively don't do anything.
- if (superMethod != null && !superMethod.getAccessFlags().isAbstract()) {
- assert superMethod.isProgramMethod();
- directMoveAndMap(localUtilityClass, localUtilityMethods, superMethod.asProgramMethod());
- }
+ if (superMethod == null) {
+ // No effective virtual dispatch is required, just move each subimplementation.
for (ProgramMethod override : subimplementations) {
+ assert !override.getAccessFlags().isAbstract();
directMoveAndMap(localUtilityClass, localUtilityMethods, override);
}
return;
}
- if (superMethod.getDefinition().isAbstract() && subimplementations.size() == 1) {
- // No emulated dispatch is required, just forward everything to the unique implementation.
- ProgramMethod override = subimplementations.iterator().next();
- DexMethod uniqueUtility = directMoveAndMap(localUtilityClass, localUtilityMethods, override);
- lensBuilder.mapToDispatch(superMethod.getReference(), uniqueUtility);
+ if (superMethod.getAccessFlags().isAbstract()) {
+ if (subimplementations.isEmpty()) {
+ // Abstract method with no implementors: rewrite to abstract method error.
+ directMoveAndMap(
+ localUtilityClass, localUtilityMethods, superMethod.asProgramMethod(), true);
+ } else if (!allImplements) {
+ // The abstract method is missing implementors, so we need to remap all missing
+ // implementation to an abstract method error.
+ emulatedDispatchMoveAndMap(
+ localUtilityClass, localUtilityMethods, superMethod, subimplementations, true);
+ } else if (subimplementations.size() == 1) {
+ // Single implementor, no emulated dispatch is required, just forward everything to the
+ // unique implementation.
+ assert allImplements;
+ ProgramMethod override = subimplementations.iterator().next();
+ DexMethod uniqueUtility =
+ directMoveAndMap(localUtilityClass, localUtilityMethods, override);
+ lensBuilder.mapToDispatch(superMethod.getReference(), uniqueUtility);
+ } else {
+ // Multiple implementors, the abstract method is entirely implemented, no need to
+ // introduce the call to the abstract method error.
+ emulatedDispatchMoveAndMap(
+ localUtilityClass, localUtilityMethods, superMethod, subimplementations, false);
+ }
return;
}
- // These methods require emulated dispatch.
+ assert !superMethod.getAccessFlags().isAbstract();
+ // No override, no effective virtual dispatch, just forward to the unique implementation.
+ if (subimplementations.isEmpty()) {
+ assert superMethod.isProgramMethod();
+ directMoveAndMap(localUtilityClass, localUtilityMethods, superMethod.asProgramMethod());
+ return;
+ }
+ // Emulated dispatch with a default case on the super enum.
emulatedDispatchMoveAndMap(
- localUtilityClass, localUtilityMethods, superMethod, subimplementations);
+ localUtilityClass, localUtilityMethods, superMethod, subimplementations, false);
}
private void emulatedDispatchMoveAndMap(
LocalEnumUnboxingUtilityClass localUtilityClass,
Map<DexMethod, DexEncodedMethod> localUtilityMethods,
DexClassAndMethod superMethod,
- ProgramMethodSet unorderedSubimplementations) {
+ ProgramMethodSet unorderedSubimplementations,
+ boolean needsAbstractMethodErrorCase) {
assert !unorderedSubimplementations.isEmpty();
DexMethod superUtilityMethod;
List<ProgramMethod> sortedSubimplementations = new ArrayList<>(unorderedSubimplementations);
sortedSubimplementations.sort(Comparator.comparing(ProgramMethod::getHolderType));
- if (superMethod.isProgramMethod()) {
- superUtilityMethod =
- installLocalUtilityMethod(
- localUtilityClass, localUtilityMethods, superMethod.asProgramMethod());
- } else {
- // All methods but toString() are final or non-virtual.
- // We could support other cases by setting correctly the superUtilityMethod here.
- assert superMethod.getReference().match(factory.enumMembers.toString);
- ProgramMethod toString = localUtilityClass.ensureToStringMethod(appView);
- superUtilityMethod = toString.getReference();
- for (ProgramMethod context : sortedSubimplementations) {
- // If the utility method is used only from the dispatch method, we have to process it and
- // add it to the ArtProfile.
- methodsToProcess.add(toString);
- profileCollectionAdditions.addMethodIfContextIsInProfile(toString, context);
- }
- }
+ superUtilityMethod =
+ computeSuperUtilityMethod(
+ localUtilityClass,
+ localUtilityMethods,
+ superMethod,
+ sortedSubimplementations,
+ needsAbstractMethodErrorCase);
Map<DexMethod, DexMethod> overrideToUtilityMethods = new IdentityHashMap<>();
for (ProgramMethod subMethod : sortedSubimplementations) {
DexMethod subEnumLocalUtilityMethod =
- installLocalUtilityMethod(localUtilityClass, localUtilityMethods, subMethod);
+ installLocalUtilityMethod(localUtilityClass, localUtilityMethods, subMethod, false);
assert subEnumLocalUtilityMethod != null;
overrideToUtilityMethods.put(subMethod.getReference(), subEnumLocalUtilityMethod);
}
@@ -748,13 +766,58 @@
}
}
+ private DexMethod computeSuperUtilityMethod(
+ LocalEnumUnboxingUtilityClass localUtilityClass,
+ Map<DexMethod, DexEncodedMethod> localUtilityMethods,
+ DexClassAndMethod superMethod,
+ List<ProgramMethod> sortedSubimplementations,
+ boolean needsAbstractMethodErrorCase) {
+ DexMethod superUtilityMethod;
+ if (superMethod.isProgramMethod()) {
+ if (needsAbstractMethodErrorCase) {
+ assert superMethod.getAccessFlags().isAbstract();
+ superUtilityMethod =
+ installLocalUtilityMethod(
+ localUtilityClass, localUtilityMethods, superMethod.asProgramMethod(), true);
+ } else if (!superMethod.getAccessFlags().isAbstract()) {
+ superUtilityMethod =
+ installLocalUtilityMethod(
+ localUtilityClass, localUtilityMethods, superMethod.asProgramMethod(), false);
+ } else {
+ assert superMethod.getAccessFlags().isAbstract();
+ superUtilityMethod = null;
+ }
+ } else {
+ // All methods but toString() are final or non-virtual.
+ // We could support other cases by setting correctly the superUtilityMethod here.
+ assert superMethod.getReference().match(factory.enumMembers.toString);
+ ProgramMethod toString = localUtilityClass.ensureToStringMethod(appView);
+ superUtilityMethod = toString.getReference();
+ for (ProgramMethod context : sortedSubimplementations) {
+ // If the utility method is used only from the dispatch method, we have to process it and
+ // add it to the ArtProfile.
+ methodsToProcess.add(toString);
+ profileCollectionAdditions.addMethodIfContextIsInProfile(toString, context);
+ }
+ }
+ return superUtilityMethod;
+ }
+
private DexMethod directMoveAndMap(
LocalEnumUnboxingUtilityClass localUtilityClass,
Map<DexMethod, DexEncodedMethod> localUtilityMethods,
ProgramMethod method) {
- assert !method.getAccessFlags().isAbstract();
+ return directMoveAndMap(localUtilityClass, localUtilityMethods, method, false);
+ }
+
+ private DexMethod directMoveAndMap(
+ LocalEnumUnboxingUtilityClass localUtilityClass,
+ Map<DexMethod, DexEncodedMethod> localUtilityMethods,
+ ProgramMethod method,
+ boolean abstractMethodError) {
DexMethod utilityMethod =
- installLocalUtilityMethod(localUtilityClass, localUtilityMethods, method);
+ installLocalUtilityMethod(
+ localUtilityClass, localUtilityMethods, method, abstractMethodError);
assert utilityMethod != null;
lensBuilder.moveAndMap(method.getReference(), utilityMethod, method.getDefinition().isStatic());
return utilityMethod;
@@ -829,15 +892,15 @@
private DexMethod installLocalUtilityMethod(
LocalEnumUnboxingUtilityClass localUtilityClass,
Map<DexMethod, DexEncodedMethod> localUtilityMethods,
- ProgramMethod method) {
- if (method.getAccessFlags().isAbstract()) {
- return null;
- }
+ ProgramMethod method,
+ boolean abstractMethodError) {
+ assert abstractMethodError || !method.getAccessFlags().isAbstract();
+ Predicate<DexMethod> isFresh =
+ newMethodSignature -> !localUtilityMethods.containsKey(newMethodSignature);
DexEncodedMethod newLocalUtilityMethod =
- createLocalUtilityMethod(
- method,
- localUtilityClass,
- newMethodSignature -> !localUtilityMethods.containsKey(newMethodSignature));
+ abstractMethodError
+ ? createAbstractMethodErrorLocalUtilityMethod(method, localUtilityClass, isFresh)
+ : createLocalUtilityMethod(method, localUtilityClass, isFresh);
assert !localUtilityMethods.containsKey(newLocalUtilityMethod.getReference());
localUtilityMethods.put(newLocalUtilityMethod.getReference(), newLocalUtilityMethod);
return newLocalUtilityMethod.getReference();
@@ -847,40 +910,78 @@
ProgramMethod method,
LocalEnumUnboxingUtilityClass localUtilityClass,
Predicate<DexMethod> availableMethodSignatures) {
- DexMethod methodReference = method.getReference();
-
- // Create a new, fresh method signature on the local utility class. We prefix the method by "_"
- // such that this does not collide with the utility methods we synthesize for unboxing.
+ assert !method.getAccessFlags().isAbstract();
DexMethod newMethod =
- method.getDefinition().isClassInitializer()
- ? factory.createClassInitializer(localUtilityClass.getType())
- : factory.createFreshMethodNameWithoutHolder(
- "_" + method.getName().toString(),
- fixupProto(
- method.getAccessFlags().isStatic()
- ? method.getProto()
- : factory.prependHolderToProto(methodReference)),
- localUtilityClass.getType(),
- availableMethodSignatures);
-
+ createFreshMethodSignature(
+ method, localUtilityClass, availableMethodSignatures, method.getReference());
return method
.getDefinition()
.toTypeSubstitutedMethod(
newMethod,
builder ->
- builder
- .clearAllAnnotations()
- .modifyAccessFlags(
- accessFlags -> {
- if (method.getDefinition().isClassInitializer()) {
- assert accessFlags.isStatic();
- } else {
- accessFlags.promoteToPublic();
- accessFlags.promoteToStatic();
- }
- })
- .setCompilationState(method.getDefinition().getCompilationState())
- .unsetIsLibraryMethodOverride());
+ transformMethodForLocalUtility(builder, method)
+ .setCompilationState(method.getDefinition().getCompilationState()));
+ }
+
+ private DexEncodedMethod createAbstractMethodErrorLocalUtilityMethod(
+ ProgramMethod method,
+ LocalEnumUnboxingUtilityClass localUtilityClass,
+ Predicate<DexMethod> availableMethodSignatures) {
+ assert method.getAccessFlags().isAbstract();
+ DexMethod newMethod =
+ createFreshMethodSignature(
+ method, localUtilityClass, availableMethodSignatures, method.getReference());
+ DexEncodedMethod dexEncodedMethod =
+ method
+ .getDefinition()
+ .toTypeSubstitutedMethod(
+ newMethod,
+ builder ->
+ transformMethodForLocalUtility(builder, method)
+ .modifyAccessFlags(MethodAccessFlags::unsetAbstract)
+ .setCode(
+ new ThrowCfCodeProvider(
+ appView, newMethod, factory.abstractMethodErrorType)
+ .generateCfCode())
+ .setClassFileVersion(CfVersion.V1_8)
+ .setApiLevelForDefinition(appView.computedMinApiLevel())
+ .setApiLevelForCode(appView.computedMinApiLevel()));
+ methodsToProcess.add(new ProgramMethod(localUtilityClass.getDefinition(), dexEncodedMethod));
+ return dexEncodedMethod;
+ }
+
+ private DexEncodedMethod.Builder transformMethodForLocalUtility(
+ DexEncodedMethod.Builder builder, ProgramMethod method) {
+ builder
+ .clearAllAnnotations()
+ .modifyAccessFlags(
+ accessFlags -> {
+ if (method.getDefinition().isClassInitializer()) {
+ assert accessFlags.isStatic();
+ } else {
+ accessFlags.promoteToPublic();
+ accessFlags.promoteToStatic();
+ }
+ })
+ .unsetIsLibraryMethodOverride();
+ return builder;
+ }
+
+ private DexMethod createFreshMethodSignature(
+ ProgramMethod method,
+ LocalEnumUnboxingUtilityClass localUtilityClass,
+ Predicate<DexMethod> availableMethodSignatures,
+ DexMethod methodReference) {
+ return method.getDefinition().isClassInitializer()
+ ? factory.createClassInitializer(localUtilityClass.getType())
+ : factory.createFreshMethodNameWithoutHolder(
+ "_" + method.getName().toString(),
+ fixupProto(
+ method.getAccessFlags().isStatic()
+ ? method.getProto()
+ : factory.prependHolderToProto(methodReference)),
+ localUtilityClass.getType(),
+ availableMethodSignatures);
}
private boolean isPrunedAfterEnumUnboxing(ProgramField field, EnumData enumData) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index 112ffdd..f60fe64 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -169,25 +169,6 @@
? new CfReturnVoid()
: new CfReturn(ValueType.fromDexType(method.getReturnType())));
}
-
- public static class CfCodeWithLens extends CfCode {
- private GraphLens codeLens;
-
- public void setCodeLens(GraphLens codeLens) {
- this.codeLens = codeLens;
- }
-
- public CfCodeWithLens(
- DexType originalHolder, int maxStack, int maxLocals, List<CfInstruction> instructions) {
- super(originalHolder, maxStack, maxLocals, instructions);
- }
-
- @Override
- public GraphLens getCodeLens(AppView<?> appView) {
- assert codeLens != null;
- return codeLens;
- }
- }
}
public static class EnumUnboxingInstanceFieldCfCodeProvider extends EnumUnboxingCfCodeProvider {
@@ -322,4 +303,24 @@
return standardCfCodeFromInstructions(instructions);
}
}
+
+ public static class CfCodeWithLens extends CfCode {
+
+ private GraphLens codeLens;
+
+ public void setCodeLens(GraphLens codeLens) {
+ this.codeLens = codeLens;
+ }
+
+ public CfCodeWithLens(
+ DexType originalHolder, int maxStack, int maxLocals, List<CfInstruction> instructions) {
+ super(originalHolder, maxStack, maxLocals, instructions);
+ }
+
+ @Override
+ public GraphLens getCodeLens(AppView<?> appView) {
+ assert codeLens != null;
+ return codeLens;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ThrowCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/ThrowCfCodeProvider.java
new file mode 100644
index 0000000..becf6d3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ThrowCfCodeProvider.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2023, 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.synthetic;
+
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Generates a method that just throws a exception with empty <init></init> with *any* signature
+ * passed, so the method can be inserted in a hierarchy and be called with normal virtual dispatch.
+ */
+public class ThrowCfCodeProvider extends SyntheticCfCodeProvider {
+
+ private final DexMethod method;
+ private final DexType exceptionType;
+
+ public ThrowCfCodeProvider(AppView<?> appView, DexMethod method, DexType exceptionType) {
+ super(appView, method.getHolderType());
+ this.method = method;
+ this.exceptionType = exceptionType;
+ }
+
+ @Override
+ public CfCode generateCfCode() {
+ List<CfInstruction> instructions = new ArrayList<>();
+ instructions.add(new CfNew(exceptionType));
+ instructions.add(new CfStackInstruction(Opcode.Dup));
+ DexMethod init = appView.dexItemFactory().createInstanceInitializer(exceptionType);
+ instructions.add(new CfInvoke(INVOKESPECIAL, init, false));
+ instructions.add(new CfThrow());
+ return standardCfCodeFromInstructions(instructions);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/enummerging/AbstractMethodErrorEnumMergingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/AbstractMethodErrorEnumMergingTest.java
new file mode 100644
index 0000000..7937ef7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/AbstractMethodErrorEnumMergingTest.java
@@ -0,0 +1,174 @@
+// Copyright (c) 2023, 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.enumunboxing.enummerging;
+
+import static com.android.tools.r8.ToolHelper.getClassFilesForInnerClasses;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.enumunboxing.EnumUnboxingTestBase;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AbstractMethodErrorEnumMergingTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final EnumKeepRules enumKeepRules;
+ private final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "class java.lang.AbstractMethodError",
+ "74",
+ "class java.lang.AbstractMethodError",
+ "44",
+ "class java.lang.AbstractMethodError",
+ "class java.lang.AbstractMethodError");
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public AbstractMethodErrorEnumMergingTest(
+ TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(inputProgram())
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(inputProgram())
+ .addKeepMainRule(Main.class)
+ .addKeepRules(enumKeepRules.getKeepRules())
+ .addEnumUnboxingInspector(
+ inspector -> inspector.assertUnboxed(MyEnum2Cases.class, MyEnum1Case.class))
+ .enableInliningAnnotations()
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ private List<byte[]> inputProgram() throws Exception {
+ Collection<Path> files = getClassFilesForInnerClasses(getClass());
+ List<byte[]> result = new ArrayList<>();
+ int changed = 0;
+ for (Path file : files) {
+ String fileName = file.getFileName().toString();
+ if (fileName.equals("AbstractMethodErrorEnumMergingTest$MyEnum1Case$1.class")
+ || fileName.equals("AbstractMethodErrorEnumMergingTest$MyEnum2Cases$1.class")) {
+ result.add(transformer(file, null).removeMethodsWithName("operate").transform());
+ changed++;
+ } else {
+ result.add(Files.readAllBytes(file));
+ }
+ }
+ assertEquals(2, changed);
+ return result;
+ }
+
+ enum MyEnum2Cases {
+ A(8) {
+ // Will be removed by transformation before compilation.
+ @NeverInline
+ @Override
+ public long operate(long another) {
+ throw new RuntimeException("Should have been removed");
+ }
+ },
+ B(32) {
+ @NeverInline
+ @Override
+ public long operate(long another) {
+ return num + another;
+ }
+ };
+ final long num;
+
+ MyEnum2Cases(long num) {
+ this.num = num;
+ }
+
+ public abstract long operate(long another);
+ }
+
+ enum MyEnum1Case {
+ A(8) {
+ // Will be removed by transformation before compilation.
+ @NeverInline
+ @Override
+ public long operate(long another) {
+ throw new RuntimeException("Should have been removed");
+ }
+ };
+ final long num;
+
+ MyEnum1Case(long num) {
+ this.num = num;
+ }
+
+ public abstract long operate(long another);
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ try {
+ System.out.println(MyEnum2Cases.A.operate(42));
+ } catch (Throwable t) {
+ System.out.println(t.getClass());
+ }
+ System.out.println(MyEnum2Cases.B.operate(42));
+ try {
+ System.out.println(indirect(MyEnum2Cases.A));
+ } catch (Throwable t) {
+ System.out.println(t.getClass());
+ }
+ System.out.println(indirect(MyEnum2Cases.B));
+
+ try {
+ System.out.println(MyEnum1Case.A.operate(42));
+ } catch (Throwable t) {
+ System.out.println(t.getClass());
+ }
+ try {
+ System.out.println(indirect(MyEnum1Case.A));
+ } catch (Throwable t) {
+ System.out.println(t.getClass());
+ }
+ }
+
+ @NeverInline
+ public static long indirect(MyEnum2Cases e) {
+ return e.operate(12);
+ }
+
+ @NeverInline
+ public static long indirect(MyEnum1Case e) {
+ return e.operate(7);
+ }
+ }
+}