Fix Staticizer Reprocessing

Bug: 147860220
Change-Id: Ia3c324f85977e36679e2aa574055cf7fcbc29f7a
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 1b78ff7..0fbc644 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -725,7 +725,8 @@
     }
     if (enumUnboxer != null) {
       enumUnboxer.finishAnalysis();
-      enumUnboxer.unboxEnums(postMethodProcessorBuilder, executorService, feedback);
+      enumUnboxer.unboxEnums(
+          postMethodProcessorBuilder, executorService, feedback, classStaticizer);
     }
     new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
         .run(executorService, feedback, timing);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index b117c16..0a35a6f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -48,6 +48,7 @@
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
+import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.Reporter;
@@ -283,7 +284,8 @@
   public void unboxEnums(
       PostMethodProcessor.Builder postBuilder,
       ExecutorService executorService,
-      OptimizationFeedbackDelayed feedback)
+      OptimizationFeedbackDelayed feedback,
+      ClassStaticizer classStaticizer)
       throws ExecutionException {
     // At this point the enumsToUnbox are no longer candidates, they will all be unboxed.
     if (enumsUnboxingCandidates.isEmpty()) {
@@ -299,7 +301,7 @@
           appView
               .appInfo()
               .rewrittenWithLens(appView.appInfo().app().asDirect(), enumUnboxingLens));
-
+      classStaticizer.filterCandidates();
       // Update optimization info.
       feedback.fixupOptimizationInfos(
           appView,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index 41df56a..ed3e72a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -96,6 +96,15 @@
       candidates.remove(candidate.type);
       return null;
     }
+
+    void filterMethods() {
+      Set<DexEncodedMethod> newReferencedFrom = Sets.newIdentityHashSet();
+      for (DexEncodedMethod dexEncodedMethod : referencedFrom) {
+        newReferencedFrom.add(appView.graphLense().mapDexEncodedMethod(dexEncodedMethod, appView));
+      }
+      referencedFrom.clear();
+      referencedFrom.addAll(newReferencedFrom);
+    }
   }
 
   // The map storing all the potential candidates for staticizing.
@@ -651,6 +660,17 @@
     return false;
   }
 
+  // Methods may have their signature changed in-between the IR processing rounds, leading to
+  // duplicates where one version is the outdated version. Remove these.
+  // This also ensures no unboxed enum are staticized, if that would be the case, then
+  // the candidate would need to be removed from the candidate list.
+  public void filterCandidates() {
+    for (Map.Entry<DexType, CandidateInfo> entry : candidates.entrySet()) {
+      assert !appView.unboxedEnums().containsEnum(entry.getKey());
+      entry.getValue().filterMethods();
+    }
+  }
+
   // Perform staticizing candidates:
   //
   //  1. After filtering candidates based on usage, finalize the list of candidates by
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 1d679ecc..c451bd7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -286,12 +286,22 @@
       if (getter != null) {
         singletonGetters.put(getter.method, candidate);
       }
+      assert validMethods(candidate.referencedFrom);
       referencingExtraMethods.addAll(candidate.referencedFrom);
     }
 
     referencingExtraMethods.removeAll(removedInstanceMethods);
   }
 
+  private boolean validMethods(Set<DexEncodedMethod> referencedFrom) {
+    for (DexEncodedMethod dexEncodedMethod : referencedFrom) {
+      DexClass clazz = appView.definitionForHolder(dexEncodedMethod.method);
+      assert clazz != null;
+      assert clazz.lookupMethod(dexEncodedMethod.method) == dexEncodedMethod;
+    }
+    return true;
+  }
+
   private void enqueueMethodsWithCodeOptimizations(
       Iterable<DexEncodedMethod> methods,
       Consumer<ImmutableList.Builder<BiConsumer<IRCode, MethodProcessor>>> extension) {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
new file mode 100644
index 0000000..6fb5fd1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
@@ -0,0 +1,126 @@
+// Copyright (c) 2020, 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;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EnumUnboxingClassStaticizerTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final KeepRule enumKeepRules;
+
+  @Parameterized.Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public EnumUnboxingClassStaticizerTest(
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    R8TestRunResult run =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(EnumUnboxingClassStaticizerTest.class)
+            .addKeepMainRule(TestClass.class)
+            .addKeepRules(enumKeepRules.getKeepRule())
+            .enableNeverClassInliningAnnotations()
+            .enableInliningAnnotations()
+            .noMinification() // For assertions.
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+            .allowDiagnosticInfoMessages()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspect(this::assertClassStaticized)
+            .inspectDiagnosticMessages(
+                m ->
+                    assertEnumIsUnboxed(
+                        UnboxableEnum.class,
+                        EnumUnboxingClassStaticizerTest.class.getSimpleName(),
+                        m))
+            .run(parameters.getRuntime(), TestClass.class)
+            .assertSuccess();
+    assertLines2By2Correct(run.getStdOut());
+  }
+
+  private void assertClassStaticized(CodeInspector codeInspector) {
+    if (parameters.isCfRuntime()) {
+      // There is no class staticizer in Cf.
+      assertThat(codeInspector.clazz(Companion.class).uniqueMethodWithName("method"), isPresent());
+      return;
+    }
+    MethodSubject method = codeInspector.clazz(CompanionHost.class).uniqueMethodWithName("method");
+    assertThat(method, isPresent());
+    assertEquals("int", method.getMethod().method.proto.parameters.toString());
+  }
+
+  static class TestClass {
+    public static void main(String[] args) {
+      CompanionHost.toKeep();
+      test(UnboxableEnum.A);
+      System.out.println("0");
+      test(UnboxableEnum.B);
+      System.out.println("1");
+      test(UnboxableEnum.C);
+      System.out.println("2");
+    }
+
+    @NeverInline
+    static void test(UnboxableEnum obj) {
+      // The class staticizer will move Companion.method() into CompanionHost.method().
+      // As a result of this transformation, this call site will need to be processed.
+      // To keep track of this, the staticizer will keep a reference to the method
+      // `void test(UnboxableEnum)`, but after enum unboxing this has been rewritten to
+      // `void test(int)`. Therefore, the staticizer's internal structure needs to be
+      // updated after enum unboxing.
+      CompanionHost.COMPANION.method(obj);
+    }
+  }
+
+  @NeverClassInline
+  static class CompanionHost {
+    static final Companion COMPANION = new Companion();
+
+    @NeverInline
+    static void toKeep() {
+      System.out.println("Keep me!");
+      System.out.println("Keep me!");
+    }
+  }
+
+  @NeverClassInline
+  static class Companion {
+    @NeverInline
+    public void method(UnboxableEnum obj) {
+      System.out.println(obj.ordinal());
+    }
+  }
+
+  @NeverClassInline
+  enum UnboxableEnum {
+    A,
+    B,
+    C;
+  }
+}