Fix enum unboxing bug causing VerifyError

Bug: b/446814391
Change-Id: Ifef42ef87d02b17be6b1e2c805b80521a8ee1032
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 4abdb68..ff4f333 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -346,7 +346,10 @@
 
   @Override
   public DexMethod withHolder(DexReference reference, DexItemFactory dexItemFactory) {
-    return dexItemFactory.createMethod(reference.getContextType(), proto, name);
+    DexType newHolder = reference.getContextType();
+    return newHolder.isIdenticalTo(holder)
+        ? this
+        : dexItemFactory.createMethod(newHolder, proto, name);
   }
 
   public DexMethod withName(String name, DexItemFactory dexItemFactory) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index 3154d72..297ebe1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -1407,6 +1407,9 @@
       // Check if this is a checkNotNull() user. In this case, we can create a copy of the method
       // that takes an int instead of java.lang.Object and call that method instead.
       if (singleTarget != null) {
+        if (singleTarget.getAccessFlags().isAbstract()) {
+          return Reason.INVALID_INVOKE;
+        }
         EnumUnboxerMethodClassification classification =
             singleTarget.getOptimizationInfo().getEnumUnboxerMethodClassification();
         if (classification.isCheckNotNullClassification()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
index 5068f5a..3b7abb1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -50,7 +50,6 @@
   private final AbstractValueFactory abstractValueFactory;
   private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod;
   private final EnumDataMap unboxedEnums;
-  private final Set<DexMethod> dispatchMethods;
 
   EnumUnboxingLens(
       AppView<?> appView,
@@ -58,14 +57,12 @@
       BidirectionalOneToManyRepresentativeMap<DexMethod, DexMethod> renamedSignatures,
       BidirectionalManyToOneRepresentativeMap<DexType, DexType> typeMap,
       Map<DexMethod, DexMethod> methodMap,
-      Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod,
-      Set<DexMethod> dispatchMethods) {
+      Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod) {
     super(appView, fieldMap, methodMap, typeMap, renamedSignatures);
     assert !appView.unboxedEnums().isEmpty();
     this.abstractValueFactory = appView.abstractValueFactory();
     this.prototypeChangesPerMethod = prototypeChangesPerMethod;
     this.unboxedEnums = appView.unboxedEnums();
-    this.dispatchMethods = dispatchMethods;
   }
 
   @Override
@@ -108,15 +105,12 @@
 
   public DexMethod lookupRefinedDispatchMethod(
       DexMethod method,
-      DexMethod context,
-      InvokeType type,
-      GraphLens codeLens,
       AbstractValue unboxedEnumValue,
       DexType enumType) {
-    assert codeLens == getPrevious();
-    DexMethod reference = lookupMethod(method, context, type, codeLens).getReference();
-    if (!dispatchMethods.contains(reference) || !unboxedEnumValue.isSingleNumberValue()) {
-      return null;
+    DexMethod enumMethod = method.withHolder(enumType, dexItemFactory());
+    DexMethod rewrittenEnumMethod = newMethodSignatures.getRepresentativeValue(enumMethod);
+    if (!unboxedEnumValue.isSingleNumberValue()) {
+      return rewrittenEnumMethod;
     }
     // We know the exact type of enum, so there is no need to go for the dispatch method. Instead,
     // we compute the exact target from the enum instance.
@@ -126,11 +120,12 @@
             .get(enumType)
             .valuesTypes
             .getOrDefault(unboxedIntToOrdinal(unboxedEnum), enumType);
+    if (instanceType.isIdenticalTo(enumType)) {
+      return rewrittenEnumMethod;
+    }
     DexMethod specializedMethod = method.withHolder(instanceType, dexItemFactory());
-    DexMethod superEnumMethod = method.withHolder(enumType, dexItemFactory());
     DexMethod refined =
-        newMethodSignatures.getRepresentativeValueOrDefault(
-            specializedMethod, newMethodSignatures.getRepresentativeValue(superEnumMethod));
+        newMethodSignatures.getRepresentativeValueOrDefault(specializedMethod, rewrittenEnumMethod);
     assert refined != null;
     return refined;
   }
@@ -408,8 +403,7 @@
           originalCheckNotNullMethodSignature, checkNotNullMethod.getReference());
     }
 
-    public EnumUnboxingLens build(
-        AppView<AppInfoWithLiveness> appView, Set<DexMethod> dispatchMethods) {
+    public EnumUnboxingLens build(AppView<AppInfoWithLiveness> appView) {
       assert !typeMap.isEmpty();
       return new EnumUnboxingLens(
           appView,
@@ -417,8 +411,7 @@
           newMethodSignatures,
           typeMap,
           methodMap,
-          ImmutableMap.copyOf(prototypeChangesPerMethod),
-          dispatchMethods);
+          ImmutableMap.copyOf(prototypeChangesPerMethod));
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 7cca071..0c5c644 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -200,6 +200,7 @@
           rewriteInvokeMethodWithReceiver(
               code,
               eventConsumer,
+              affectedPhis,
               convertedEnums,
               blocks,
               block,
@@ -438,6 +439,7 @@
   private void rewriteInvokeMethodWithReceiver(
       IRCode code,
       EnumUnboxerMethodProcessorEventConsumer eventConsumer,
+      Set<Phi> affectedPhis,
       Map<Instruction, DexType> convertedEnums,
       BasicBlockIterator blocks,
       BasicBlock block,
@@ -499,18 +501,23 @@
       } else if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
         DexMethod refinedDispatchMethodReference =
             enumUnboxingLens.lookupRefinedDispatchMethod(
-                invokedMethod,
-                context.getReference(),
-                invoke.getType(),
-                enumUnboxingLens.getPrevious(),
-                invoke.getArgument(0).getAbstractValue(appView, context),
-                enumType);
+                invokedMethod, invoke.getReceiver().getAbstractValue(appView, context), enumType);
         if (refinedDispatchMethodReference != null) {
           DexClassAndMethod refinedDispatchMethod =
               appView.definitionFor(refinedDispatchMethodReference);
           assert refinedDispatchMethod != null;
           assert refinedDispatchMethod.isProgramMethod();
-          replaceEnumInvoke(iterator, invoke, refinedDispatchMethod.asProgramMethod());
+          InvokeStatic replacement =
+              replaceEnumInvoke(iterator, invoke, refinedDispatchMethod.asProgramMethod());
+          if (replacement.hasOutValue()
+              && refinedDispatchMethodReference.getReturnType().isIntType()
+              && !invokedMethod.getReturnType().isIntType()) {
+            Value rewrittenOutValue = code.createValue(TypeElement.getInt());
+            replacement.outValue().replaceUsers(rewrittenOutValue);
+            replacement.setOutValue(rewrittenOutValue);
+            affectedPhis.addAll(rewrittenOutValue.uniquePhiUsers());
+            convertedEnums.put(replacement, enumType);
+          }
         }
       }
     } else if (invokedMethod == factory.stringBuilderMethods.appendObject
@@ -859,12 +866,12 @@
         .ensureGetInstanceFieldMethod(appView, field, context, eventConsumer);
   }
 
-  private void replaceEnumInvoke(
+  private InvokeStatic replaceEnumInvoke(
       InstructionListIterator iterator, InvokeMethod invoke, ProgramMethod method) {
-    replaceEnumInvoke(iterator, invoke, method, invoke.arguments());
+    return replaceEnumInvoke(iterator, invoke, method, invoke.arguments());
   }
 
-  private void replaceEnumInvoke(
+  private InvokeStatic replaceEnumInvoke(
       InstructionListIterator iterator,
       InvokeMethod invoke,
       ProgramMethod method,
@@ -877,6 +884,7 @@
     assert !replacement.hasOutValue()
         || !replacement.getInvokedMethod().getReturnType().isVoidType();
     iterator.replaceCurrentInstruction(replacement);
+    return replacement;
   }
 
   private boolean validateArrayAccess(ArrayAccess arrayAccess) {
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 a6bda7d..53051d0 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
@@ -158,9 +158,7 @@
         .fixupClassesConcurrentlyByConnectedProgramComponents(Timing.empty(), executorService);
 
     // Install the new graph lens before processing any checkNotZero() methods.
-    Set<DexMethod> dispatchMethodReferences = Sets.newIdentityHashSet();
-    dispatchMethods.forEach((method, code) -> dispatchMethodReferences.add(method.getReference()));
-    EnumUnboxingLens lens = lensBuilder.build(appView, dispatchMethodReferences);
+    EnumUnboxingLens lens = lensBuilder.build(appView);
     appView.rewriteWithLens(lens, executorService, timing);
 
     // Rewrite outliner with lens.
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/InvokeInterfaceNoDevirtualizationEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/InvokeInterfaceNoDevirtualizationEnumUnboxingTest.java
index 0d04b20..3eb7953 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/InvokeInterfaceNoDevirtualizationEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/InvokeInterfaceNoDevirtualizationEnumUnboxingTest.java
@@ -3,10 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.enumunboxing;
 
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoAccessModification;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.codeinspector.AssertUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -22,7 +27,7 @@
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
-    return enumUnboxingTestParameters();
+    return enumUnboxingTestParameters(getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
   public InvokeInterfaceNoDevirtualizationEnumUnboxingTest(
@@ -33,43 +38,66 @@
   }
 
   @Test
-  public void test() throws Exception {
-    AssertUtils.assertFailsCompilation(
-        () ->
-            testForR8(parameters)
-                .addInnerClasses(getClass())
-                .addKeepMainRule(Main.class)
-                .addKeepRules(enumKeepRules.getKeepRules())
-                // Disable devirtualization so that the enum unboxing rewriter sees the call to
-                // I#greet instead of MyEnum#greet.
-                .addOptionsModification(options -> options.enableDevirtualization = false)
-                .addOptionsModification(
-                    options -> enableEnumOptions(options, enumValueOptimization))
-                .enableInliningAnnotations()
-                .enableNoVerticalClassMergingAnnotations()
-                .compile()
-                .run(parameters.getRuntime(), Main.class)
-                .assertSuccessWithOutputLines("Hello, world!"));
+  public void testJvm() throws Exception {
+    parameters.assumeJvmTestParameters();
+    assumeFalse(enumValueOptimization);
+    assumeTrue(enumKeepRules.isNone());
+    testForJvm(parameters)
+        .addProgramClasses(I.class)
+        .addProgramClassFileData(getProgramClassFileData())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters)
+        .addProgramClasses(I.class)
+        .addProgramClassFileData(getProgramClassFileData())
+        .addKeepMainRule(Main.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addOptionsModification(options -> enableEnumOptions(options, enumValueOptimization))
+        .enableInliningAnnotations()
+        .enableNoAccessModificationAnnotationsForClasses()
+        .enableNoVerticalClassMergingAnnotations()
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  private static List<byte[]> getProgramClassFileData() throws IOException {
+    return ImmutableList.of(
+        transformer(Main.class)
+            .replaceClassDescriptorInMethodInstructions(
+                descriptor(MyEnumAccessor.class), "LMyEnumAccessor;")
+            .transform(),
+        transformer(MyEnum.class).setClassDescriptor("LMyEnum;").transform(),
+        transformer(MyEnumAccessor.class)
+            .setClassDescriptor("LMyEnumAccessor;")
+            .replaceClassDescriptorInMethodInstructions(descriptor(MyEnum.class), "LMyEnum;")
+            .transform());
   }
 
   static class Main {
 
     public static void main(String[] args) {
-      MyEnum e = System.currentTimeMillis() > 0 ? MyEnum.A : MyEnum.B;
-      greet(e);
+      greet(MyEnumAccessor.get());
     }
 
     static void greet(I i) {
+      // Cannot be rewritten to call MyEnum#greet since MyEnum is not public and in another package.
       i.greet();
     }
   }
 
   @NoVerticalClassMerging
-  interface I {
+  public interface I {
 
     void greet();
   }
 
+  // Moved to separate package by transformer.
+  @NoAccessModification
   enum MyEnum implements I {
     A,
     B;
@@ -80,4 +108,12 @@
       System.out.println("Hello, world!");
     }
   }
+
+  // Moved to separate package by transformer.
+  public static class MyEnumAccessor {
+
+    public static I get() {
+      return System.currentTimeMillis() > 0 ? MyEnum.A : MyEnum.B;
+    }
+  }
 }
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
index 7937ef7..b11d6de 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/enummerging/AbstractMethodErrorEnumMergingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/AbstractMethodErrorEnumMergingTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.enumunboxing.EnumUnboxingTestBase;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.EnumUnboxingInspector;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -49,25 +50,22 @@
 
   @Test
   public void testReference() throws Exception {
-    testForD8(parameters.getBackend())
+    testForD8(parameters)
         .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())
+    testForR8(parameters)
         .addProgramClassFileData(inputProgram())
         .addKeepMainRule(Main.class)
         .addKeepRules(enumKeepRules.getKeepRules())
-        .addEnumUnboxingInspector(
-            inspector -> inspector.assertUnboxed(MyEnum2Cases.class, MyEnum1Case.class))
+        .addEnumUnboxingInspector(EnumUnboxingInspector::assertNoEnumsUnboxed)
         .enableInliningAnnotations()
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-        .setMinApi(parameters)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }