Reproduce frame type bug when interface and null flows to a phi

Bug: b/337798768
Change-Id: I0ad0e12f54a2f19eb9c6f452e1f5dab30053d7c6
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifierDefaultAnalysisConfig.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifierDefaultAnalysisConfig.java
index db75814..f9e7860 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifierDefaultAnalysisConfig.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifierDefaultAnalysisConfig.java
@@ -18,7 +18,10 @@
   private final ProgramMethod method;
 
   CfFrameVerifierDefaultAnalysisConfig(AppView<?> appView, CfCode code, ProgramMethod method) {
-    this.assignability = new CfAssignability(appView);
+    this.assignability =
+        appView.testing().enableStrictFrameVerification
+            ? new CfSubtypingAssignability(appView.withClassHierarchy(), false)
+            : new CfAssignability(appView);
     this.code = code;
     this.method = method;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSubtypingAssignability.java b/src/main/java/com/android/tools/r8/cf/code/CfSubtypingAssignability.java
index 147e879..d2867f5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSubtypingAssignability.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSubtypingAssignability.java
@@ -11,8 +11,17 @@
 
 public class CfSubtypingAssignability extends CfAssignability {
 
+  private final boolean allowAnyAssignmentToInterfaceTypes;
+
   public CfSubtypingAssignability(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    this(appView, true);
+  }
+
+  public CfSubtypingAssignability(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      boolean allowAnyAssignmentToInterfaceTypes) {
     super(appView);
+    this.allowAnyAssignmentToInterfaceTypes = allowAnyAssignmentToInterfaceTypes;
   }
 
   @Override
@@ -24,9 +33,11 @@
     if (source.toTypeElement(appView).lessThanOrEqual(target.toTypeElement(appView), appView)) {
       return true;
     }
-    DexClass targetClass = appView.definitionFor(target);
-    if (targetClass != null && targetClass.isInterface()) {
-      return true;
+    if (allowAnyAssignmentToInterfaceTypes) {
+      DexClass targetClass = appView.definitionFor(target);
+      if (targetClass != null && targetClass.isInterface()) {
+        return true;
+      }
     }
     return false;
   }
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 683f441..23a0e46 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2341,6 +2341,7 @@
     public boolean enableDeadSwitchCaseElimination = true;
     public boolean enableInvokeSuperToInvokeVirtualRewriting = true;
     public boolean enableMultiANewArrayDesugaringForClassFiles = false;
+    public boolean enableStrictFrameVerification = false;
     public boolean enableSyntheticSharing = true;
     public boolean enableSwitchToIfRewriting = true;
     public boolean enableEnumUnboxingDebugLogs =
diff --git a/src/test/java/com/android/tools/r8/regress/Regress337798768.java b/src/test/java/com/android/tools/r8/regress/Regress337798768.java
new file mode 100644
index 0000000..567db5a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/Regress337798768.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2024, 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.regress;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class Regress337798768 extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  public Regress337798768(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  // TODO(b/337798768): should not fail verification
+  @Test(expected = CompilationFailedException.class)
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(o -> o.testing.enableStrictFrameVerification = true)
+        .addKeepClassAndMembersRules(A.class, B.class, I.class)
+        .compile();
+  }
+
+  interface I {
+    void foobar();
+  }
+
+  public static class A implements I {
+
+    @Override
+    public void foobar() {
+      System.out.println("foobar");
+    }
+  }
+
+  public static class B implements I {
+    @Override
+    public void foobar() {
+      System.out.println("B");
+    }
+  }
+
+  public static class TestClass {
+    public static void main(String[] args) {
+      I i =
+          System.currentTimeMillis() == 0
+              ? null
+              : System.currentTimeMillis() == 0 ? new B() : new A();
+      i.foobar();
+    }
+  }
+}