Rewrite code from javac code with for JDK-8272564

Some Dalvik and Art MVs does not support interface invokes to Object
members not explicitly defined on the symbolic reference of the
interface invoke. In these cases rewrite to a virtual invoke with
the symbolic reference java.lang.Object.

Bug: 218298666
Change-Id: I38abdea8d9bbf4d004be1877373b58de161a55a7
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 686f124..3640575 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1431,6 +1431,54 @@
           || method.match(waitLong)
           || method.match(waitLongInt);
     }
+
+    public DexMethod matchingPublicObjectMember(DexMethod method) {
+      switch (method.getName().byteAt(0)) {
+        case 't':
+          if (method.match(toString)) {
+            return toString;
+          }
+          break;
+        case 'h':
+          if (method.match(hashCode)) {
+            return hashCode;
+          }
+          break;
+        case 'e':
+          if (method.match(equals)) {
+            return equals;
+          }
+          break;
+        case 'g':
+          if (method.match(getClass)) {
+            return getClass;
+          }
+          break;
+        case 'n':
+          if (method.match(notify)) {
+            return notify;
+          }
+          if (method.match(notifyAll)) {
+            return notifyAll;
+          }
+          break;
+        case 'w':
+          if (method.match(wait)) {
+            return wait;
+          }
+          if (method.match(waitLong)) {
+            return waitLong;
+          }
+          if (method.match(waitLongInt)) {
+            return waitLongInt;
+          }
+          break;
+        default:
+          // Methods finalize and clone are not public.
+          return null;
+      }
+      return null;
+    }
   }
 
   public class BufferMembers {
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 24207a2..5d27d27 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
@@ -1091,6 +1091,12 @@
       timing.end();
     }
 
+    if (!options.canHaveInvokeInterfaceToObjectMethodBug()) {
+      timing.begin("JDK-8272564 fix rewrite");
+      CodeRewriter.rewriteJdk8272564Fix(code, appView);
+      timing.end();
+    }
+
     boolean isDebugMode = options.debug || method.getOptimizationInfo().isReachabilitySensitive();
 
     if (isDebugMode) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index f4f4412..b6988e4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -70,6 +70,7 @@
 import com.android.tools.r8.ir.code.IntSwitch;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeInterface;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeNewArray;
@@ -3773,6 +3774,26 @@
     }
   }
 
+  // The javac fix for JDK-8272564 has to be rewritten back to invoke-virtual on Object if the
+  // method with an Object signature is not defined on the interface. See
+  // https://bugs.openjdk.java.net/browse/JDK-8272564
+  public static void rewriteJdk8272564Fix(IRCode code, AppView<?> appView) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    InstructionListIterator it = code.instructionListIterator();
+    while (it.hasNext()) {
+      Instruction instruction = it.next();
+      if (instruction.isInvokeInterface()) {
+        InvokeInterface invoke = instruction.asInvokeInterface();
+        DexMethod method = invoke.getInvokedMethod();
+        DexMethod objectMember = dexItemFactory.objectMembers.matchingPublicObjectMember(method);
+        if (objectMember != null && appView.definitionFor(method) == null) {
+          it.replaceCurrentInstruction(
+              new InvokeVirtual(objectMember, invoke.outValue(), invoke.arguments()));
+        }
+      }
+    }
+  }
+
   private static NewInstance findNewInstance(Phi phi) {
     Set<Phi> seen = Sets.newIdentityHashSet();
     Set<Value> values = Sets.newIdentityHashSet();
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index e9db143..1c31fbe 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2338,4 +2338,16 @@
   public boolean canHaveSuperInvokeBug() {
     return getMinApiLevel().isLessThan(AndroidApiLevel.N);
   }
+
+  // Some Dalvik and Art MVs does not support interface invokes to Object
+  // members not explicitly defined on the symbolic reference of the
+  // interface invoke. In these cases rewrite to a virtual invoke with
+  // the symbolic reference java.lang.Object.
+  //
+  // javac started generating code like this with the fix for JDK-8272564.
+  //
+  // See b/218298666.
+  public boolean canHaveInvokeInterfaceToObjectMethodBug() {
+    return isGeneratingClassFiles() || getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O);
+  }
 }
diff --git a/src/test/examplesJava18/jdk8272564/I.java b/src/test/examplesJava18/jdk8272564/I.java
index ba9e252..1694eea 100644
--- a/src/test/examplesJava18/jdk8272564/I.java
+++ b/src/test/examplesJava18/jdk8272564/I.java
@@ -6,4 +6,8 @@
 
 interface I {
   public String toString();
+
+  public int hashCode();
+
+  public boolean equals(Object other);
 }
diff --git a/src/test/examplesJava18/jdk8272564/Main.java b/src/test/examplesJava18/jdk8272564/Main.java
index cc759ca..c5963fd 100644
--- a/src/test/examplesJava18/jdk8272564/Main.java
+++ b/src/test/examplesJava18/jdk8272564/Main.java
@@ -14,6 +14,34 @@
     k.toString();
   }
 
+  // Remaining public methods on Object.
+  static void g(I i, J j, K k) throws InterruptedException {
+    i.hashCode();
+    j.hashCode();
+    k.hashCode();
+    i.equals(new Object());
+    j.equals(new Object());
+    k.equals(new Object());
+    i.getClass();
+    j.getClass();
+    k.getClass();
+    i.notify();
+    j.notify();
+    k.notify();
+    i.notifyAll();
+    j.notifyAll();
+    k.notifyAll();
+    i.wait();
+    j.wait();
+    k.wait();
+    i.wait(1L);
+    j.wait(1L);
+    k.wait(1L);
+    i.wait(1L, 1);
+    j.wait(1L, 1);
+    k.wait(1L, 1);
+  }
+
   public static void main(String[] args) {
     f(new A(), new B(), new C());
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java b/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java
index 5bce7b7..191cc22 100644
--- a/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java
@@ -12,8 +12,8 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.examples.jdk18.jdk8272564.Jdk8272564;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -42,17 +42,24 @@
   public TestParameters parameters;
 
   // With the fix for JDK-8272564 there are no invokevirtual instructions.
-  private void assertJdk8272564FixedCode(CodeInspector inspector) throws Exception {
+  private void assertJdk8272564FixedCode(CodeInspector inspector) {
     assertTrue(
         inspector
             .clazz(Jdk8272564.Main.typeName())
             .uniqueMethodWithName("f")
             .streamInstructions()
             .noneMatch(InstructionSubject::isInvokeVirtual));
+    assertTrue(
+        inspector
+            .clazz(Jdk8272564.Main.typeName())
+            .uniqueMethodWithName("g")
+            .streamInstructions()
+            .noneMatch(InstructionSubject::isInvokeVirtual));
   }
 
   // Without the fix for JDK-8272564 there is one invokeinterface and 2 invokevirtual instructions.
-  private void assertJdk8272564NotFixedCode(CodeInspector inspector) throws Exception {
+  private void assertJdk8272564NotFixedCode(
+      CodeInspector inspector, int invokeVirtualCount, int getClassCount) {
     assertEquals(
         1,
         inspector
@@ -69,6 +76,47 @@
             .streamInstructions()
             .filter(InstructionSubject::isInvokeVirtual)
             .count());
+    assertEquals(
+        2,
+        inspector
+            .clazz(Jdk8272564.Main.typeName())
+            .uniqueMethodWithName("g")
+            .streamInstructions()
+            .filter(InstructionSubject::isInvokeInterface)
+            .count());
+    assertEquals(
+        2,
+        inspector
+            .clazz(Jdk8272564.Main.typeName())
+            .uniqueMethodWithName("g")
+            .streamInstructions()
+            .filter(InstructionSubject::isInvokeInterface)
+            .count());
+    assertEquals(
+        invokeVirtualCount,
+        inspector
+            .clazz(Jdk8272564.Main.typeName())
+            .uniqueMethodWithName("g")
+            .streamInstructions()
+            .filter(InstructionSubject::isInvokeVirtual)
+            .count());
+    assertEquals(
+        getClassCount,
+        inspector
+            .clazz(Jdk8272564.Main.typeName())
+            .uniqueMethodWithName("g")
+            .streamInstructions()
+            .filter(InstructionSubject::isInvoke)
+            .filter(instruction -> instruction.getMethod().getName().toString().equals("getClass"))
+            .count());
+  }
+
+  private void assertJdk8272564NotFixedCode(CodeInspector inspector) {
+    assertJdk8272564NotFixedCode(inspector, 22, 3);
+  }
+
+  private void assertJdk8272564NotFixedCodeR8(CodeInspector inspector) {
+    assertJdk8272564NotFixedCode(inspector, 19, 0);
   }
 
   @Test
@@ -94,13 +142,10 @@
         .addProgramFiles(Jdk8272564.jar())
         .run(parameters.getRuntime(), Jdk8272564.Main.typeName())
         .applyIf(
-            parameters.getRuntime().isDex()
-                && parameters.getRuntime().asDex().getVersion().isOlderThanOrEqual(Version.V4_4_4),
-            r -> r.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
-            parameters.getRuntime().isDex()
-                && parameters.getRuntime().asDex().getVersion().isOlderThanOrEqual(Version.V7_0_0),
-            r -> r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class),
-            r -> r.assertSuccess());
+            parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+            b -> b.inspect(this::assertJdk8272564NotFixedCode),
+            b -> b.inspect(this::assertJdk8272564FixedCode))
+        .assertSuccess();
   }
 
   @Test
@@ -113,7 +158,7 @@
         .addKeepClassAndMembersRules(Jdk8272564.Main.typeName())
         .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .run(parameters.getRuntime(), Jdk8272564.Main.typeName())
-        .inspect(this::assertJdk8272564NotFixedCode)
+        .inspect(this::assertJdk8272564NotFixedCodeR8)
         .assertSuccess();
   }
 }