Check for resolved static targets when inserting forwarding methods

Bug: 171550305
Bug: 182189123
Change-Id: I96c34562f617c83e6c06535a038a42d7b59728e4
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 630a67d..04b702a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.ir.synthetic.ExceptionThrowingSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.WorkList;
@@ -533,27 +534,44 @@
   // the 'addForward' call-back is called with the target of the forward.
   private void resolveForwardForSignature(
       DexClass clazz, DexMethod method, Consumer<DexClassAndMethod> addForward) {
-    // Resolve the default method with base type as the symbolic holder as call sites are not known.
-    // The dispatch target is then looked up from the possible "instance" class.
-    // Doing so can cause an invalid invoke to become valid (at runtime resolution at a subtype
-    // might have failed which is hidden by the insertion of the forward method). However, not doing
-    // so could cause valid dispatches to become invalid by resolving to private overrides.
     AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
-    DexClassAndMethod virtualDispatchTarget =
-        appInfo
-            .resolveMethodOnInterface(method.holder, method)
-            .lookupVirtualDispatchTarget(clazz, appInfo);
-    if (virtualDispatchTarget == null) {
-      // If no target is found due to multiple default method targets, preserve ICCE behavior.
-      ResolutionResult resolutionFromSubclass = appInfo.resolveMethodOn(clazz, method);
-      if (resolutionFromSubclass.isIncompatibleClassChangeErrorResult()) {
-        addICCEThrowingMethod(method, clazz);
+    ResolutionResult resolutionResult = appInfo.resolveMethodOn(clazz, method);
+    if (resolutionResult.isFailedResolution()
+        || resolutionResult.asSuccessfulMemberResolutionResult().getResolvedMember().isStatic()) {
+      // When doing resolution we may find a static or private targets and bubble up the failed
+      // resolution to preserve ICCE even though the resolution actually succeeded, ie. finding a
+      // method with the same name and descriptor. For invoke-virtual and invoke-interface, the
+      // selected method will only consider instance methods.
+      BooleanBox staticTarget = new BooleanBox(true);
+      if (resolutionResult.isFailedResolution()) {
+        resolutionResult
+            .asFailedResolution()
+            .forEachFailureDependency(target -> staticTarget.and(target.isStatic()));
+      } else if (resolutionResult.isSuccessfulMemberResolutionResult()) {
+        staticTarget.set(
+            resolutionResult.asSuccessfulMemberResolutionResult().getResolvedMember().isStatic());
+      }
+      if (staticTarget.isAssigned() && staticTarget.isTrue()) {
+        resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+      }
+      if (resolutionResult.isFailedResolution()) {
+        if (resolutionResult.isIncompatibleClassChangeErrorResult()) {
+          addICCEThrowingMethod(method, clazz);
+          return;
+        }
+        if (resolutionResult.isNoSuchMethodErrorResult(clazz, appInfo)) {
+          addNoSuchMethodErrorThrowingMethod(method, clazz);
+          return;
+        }
+        assert resolutionResult.isIllegalAccessErrorResult(clazz, appInfo);
+        addIllegalAccessErrorThrowingMethod(method, clazz);
         return;
       }
-      assert resolutionFromSubclass.isFailedResolution()
-          || resolutionFromSubclass.getSingleTarget().isPrivateMethod();
-      return;
     }
+    assert resolutionResult.isSuccessfulMemberResolutionResult();
+    DexClassAndMethod virtualDispatchTarget =
+        resolutionResult.lookupVirtualDispatchTarget(clazz, appInfo);
+    assert virtualDispatchTarget != null;
 
     // Don't forward if the target is explicitly marked as 'dont-rewrite'
     if (dontRewrite(virtualDispatchTarget)) {
@@ -612,6 +630,18 @@
   }
 
   private void addICCEThrowingMethod(DexMethod method, DexClass clazz) {
+    addThrowingMethod(method, clazz, dexItemFactory.icceType);
+  }
+
+  private void addIllegalAccessErrorThrowingMethod(DexMethod method, DexClass clazz) {
+    addThrowingMethod(method, clazz, dexItemFactory.illegalAccessErrorType);
+  }
+
+  private void addNoSuchMethodErrorThrowingMethod(DexMethod method, DexClass clazz) {
+    addThrowingMethod(method, clazz, dexItemFactory.noSuchMethodErrorType);
+  }
+
+  private void addThrowingMethod(DexMethod method, DexClass clazz, DexType errorType) {
     if (!clazz.isProgramClass()) {
       return;
     }
@@ -625,8 +655,7 @@
             ParameterAnnotationsList.empty(),
             new SynthesizedCode(
                 callerPosition ->
-                    new ExceptionThrowingSourceCode(
-                        clazz.type, method, callerPosition, dexItemFactory.icceType)),
+                    new ExceptionThrowingSourceCode(clazz.type, method, callerPosition, errorType)),
             true);
     addSyntheticMethod(clazz.asProgramClass(), newEncodedMethod);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/BooleanBox.java b/src/main/java/com/android/tools/r8/utils/BooleanBox.java
index eff5807..5ad9494 100644
--- a/src/main/java/com/android/tools/r8/utils/BooleanBox.java
+++ b/src/main/java/com/android/tools/r8/utils/BooleanBox.java
@@ -9,6 +9,7 @@
 public class BooleanBox {
 
   private boolean value;
+  private boolean assigned = false;
 
   public BooleanBox() {}
 
@@ -39,6 +40,7 @@
   }
 
   public void set(boolean value) {
+    assigned = true;
     this.value = value;
   }
 
@@ -47,10 +49,14 @@
   }
 
   public void and(boolean value) {
-    this.value = value && this.value;
+    set(value && this.value);
   }
 
   public void or(boolean value) {
-    this.value = value || this.value;
+    set(value || this.value);
+  }
+
+  public boolean isAssigned() {
+    return assigned;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaMethodWithPrivateSuperClassTest.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaMethodWithPrivateSuperClassTest.java
index cbd1fb8..6ba2e12 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaMethodWithPrivateSuperClassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaMethodWithPrivateSuperClassTest.java
@@ -48,11 +48,12 @@
         .addInnerClasses(getClass())
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/171550305): Should be illegal access for DEX.
         .applyIf(
-            parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B),
-            r -> r.assertSuccessWithOutputLines("interface"),
-            r -> r.assertFailureWithErrorThatThrows(IllegalAccessError.class));
+            parameters.isCfRuntime()
+                || parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M),
+            r -> r.assertFailureWithErrorThatThrows(IllegalAccessError.class),
+            // TODO(b/152199517): Should be illegal access for DEX.
+            r -> r.assertSuccessWithOutputLines("interface"));
   }
 
   interface Named {
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaMethodWithPublicSuperClassTest.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaMethodWithPublicSuperClassTest.java
index 11bc898..a439984 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaMethodWithPublicSuperClassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaMethodWithPublicSuperClassTest.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.desugar;
 
+import static org.junit.Assume.assumeTrue;
+
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -20,7 +22,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withCfRuntimes().withAllApiLevelsAlsoForCf().build();
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
   public DefaultLambdaMethodWithPublicSuperClassTest(TestParameters parameters) {
@@ -29,6 +31,7 @@
 
   @Test
   public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
     testForJvm()
         .addInnerClasses(getClass())
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultDirectInvokeTest.java b/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultDirectInvokeTest.java
index 86204ee..f989728 100644
--- a/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultDirectInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultDirectInvokeTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -21,7 +22,6 @@
 public class InvokeVirtualPrivateBaseWithDefaultDirectInvokeTest extends TestBase {
 
   private final TestParameters parameters;
-  private final String INVALID_EXPECTED = "I::foo";
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -40,9 +40,7 @@
         .run(parameters.getRuntime(), Main.class)
         .applyIf(
             parameters.isCfRuntime(CfVm.JDK11),
-            // TODO(B/182148338): Figure out why JVM 11 has changed resolution for interface
-            //  lookups.
-            r -> r.assertSuccessWithOutputLines(INVALID_EXPECTED),
+            r -> r.assertSuccessWithOutputLines("I::foo"),
             r -> r.assertFailureWithErrorThatThrows(IllegalAccessError.class));
   }
 
@@ -52,14 +50,7 @@
         .addInnerClasses(getClass())
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        // TODO(B/182148338): Figure out why JVM 11 has changed resolution for interface lookups.
-        // TODO(b/171550305): Should be illegal access for DEX.
-        .applyIf(
-            parameters.isDexRuntime()
-                || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B)
-                || parameters.isCfRuntime(CfVm.JDK11),
-            r -> r.assertSuccessWithOutputLines(INVALID_EXPECTED),
-            r -> r.assertFailureWithErrorThatThrows(IllegalAccessError.class));
+        .apply(this::assertResultIsCorrect);
   }
 
   @Test
@@ -70,10 +61,23 @@
         .addKeepClassAndMembersRules(I.class)
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .compile()
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/171550305): Should be illegal access.
-        .assertSuccessWithOutputLines(INVALID_EXPECTED);
+        // TODO(b/182189123): This should have the same behavior as D8.
+        .assertSuccessWithOutputLines("I::foo");
+  }
+
+  public void assertResultIsCorrect(SingleTestRunResult<?> result) {
+    if (parameters.isCfRuntime(CfVm.JDK11)
+        && parameters.getApiLevel().isGreaterThan(AndroidApiLevel.M)) {
+      result.assertSuccessWithOutputLines("I::foo");
+      return;
+    }
+    // TODO(b/152199517): Should be illegal access for DEX.
+    if (parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThan(AndroidApiLevel.M)) {
+      result.assertSuccessWithOutputLines("I::foo");
+      return;
+    }
+    result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
   }
 
   @NoVerticalClassMerging
diff --git a/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultTest.java b/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultTest.java
index 381eb18..9c9926d 100644
--- a/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultTest.java
@@ -20,7 +20,6 @@
 public class InvokeVirtualPrivateBaseWithDefaultTest extends TestBase {
 
   private final TestParameters parameters;
-  private final String INVALID_EXPECTED = "I::foo";
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -46,11 +45,12 @@
         .addInnerClasses(getClass())
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/171550305): Should be illegal access for DEX.
         .applyIf(
-            parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B),
-            r -> r.assertSuccessWithOutputLines(INVALID_EXPECTED),
-            r -> r.assertFailureWithErrorThatThrows(IllegalAccessError.class));
+            parameters.isCfRuntime()
+                || parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M),
+            r -> r.assertFailureWithErrorThatThrows(IllegalAccessError.class),
+            // TODO(b/152199517): Should be illegal access for DEX.
+            r -> r.assertSuccessWithOutputLines("I::foo"));
   }
 
   @Test
@@ -63,11 +63,12 @@
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/171550305): Should be illegal access.
         .applyIf(
-            parameters.isDexRuntime(),
-            r -> r.assertSuccessWithOutputLines(INVALID_EXPECTED),
-            r -> r.assertFailureWithErrorThatThrows(IllegalAccessError.class));
+            parameters.isCfRuntime()
+                || parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M),
+            r -> r.assertFailureWithErrorThatThrows(IllegalAccessError.class),
+            // TODO(b/152199517): Should be illegal access for DEX.
+            r -> r.assertSuccessWithOutputLines("I::foo"));
   }
 
   @NoVerticalClassMerging