Reproduce horizontal class merging not respecting interface visibility

Bug: 191248536
Change-Id: Ib3698b31ffaad04119b52bf642d28990f21b15fe
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InterfacesVisibilityTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InterfacesVisibilityTest.java
new file mode 100644
index 0000000..b5d7af1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InterfacesVisibilityTest.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2021, 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.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.testclasses.InterfacesVisibilityTestClasses;
+import com.android.tools.r8.classmerging.horizontal.testclasses.InterfacesVisibilityTestClasses.ImplementingPackagePrivateInterface;
+import com.android.tools.r8.classmerging.horizontal.testclasses.InterfacesVisibilityTestClasses.Invoker;
+import org.junit.Test;
+
+public class InterfacesVisibilityTest extends HorizontalClassMergingTestBase {
+  public InterfacesVisibilityTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    String packagePrivateInterfaceName =
+        InterfacesVisibilityTestClasses.class.getTypeName() + "$PackagePrivateInterface";
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addProgramClasses(ImplementingPackagePrivateInterface.class, Invoker.class)
+        .addProgramClasses(Class.forName(packagePrivateInterfaceName))
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        // TODO(b/191248536): These classes should not be merged.
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertMergedInto(ImplementingPackagePrivateInterface.class, A.class))
+        .compile()
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(codeInspector.clazz(packagePrivateInterfaceName), isPresent());
+              assertThat(
+                  codeInspector.clazz(A.class),
+                  isImplementing(codeInspector.clazz(packagePrivateInterfaceName)));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        // TODO(b/191248536) Should be .assertSuccessWithOutputLines("foo", "bar");
+        .assertFailure();
+  }
+
+  @NeverClassInline
+  public static class A {
+    @NeverInline
+    public void bar() {
+      System.out.println("bar");
+    }
+  }
+
+  public static class Main {
+    @NeverInline
+    public static void foo(ImplementingPackagePrivateInterface o) {
+      Invoker.invokeFoo(o);
+    }
+
+    public static void main(String[] args) {
+      ImplementingPackagePrivateInterface implementingPackagePrivateInterface =
+          new ImplementingPackagePrivateInterface();
+      A a = new A();
+      Invoker.invokeFoo(implementingPackagePrivateInterface);
+      a.bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/InterfacesVisibilityTestClasses.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/InterfacesVisibilityTestClasses.java
new file mode 100644
index 0000000..d5a5c14
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/InterfacesVisibilityTestClasses.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2021, 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.classmerging.horizontal.testclasses;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+
+public class InterfacesVisibilityTestClasses {
+  public static class Invoker {
+    @NeverInline
+    public static void invokeFoo(PackagePrivateInterface i) {
+      i.foo();
+    }
+  }
+
+  @NoVerticalClassMerging
+  @NeverClassInline
+  public static class ImplementingPackagePrivateInterface implements PackagePrivateInterface {
+    @Override
+    public void foo() {
+      System.out.println("foo");
+    }
+  }
+
+  @NoHorizontalClassMerging
+  @NoVerticalClassMerging
+  interface PackagePrivateInterface {
+    void foo();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 0e2de72f..402d6b3 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
@@ -115,7 +115,7 @@
   }
 
   @Override
-  public AccessFlags<?> getAccessFlags() {
+  public ClassAccessFlags getAccessFlags() {
     throw new Unreachable("Absent class has no access flags");
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index f614059..96db056 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
 
+import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
@@ -185,6 +186,9 @@
     return dump.toString();
   }
 
+  @Override
+  public abstract ClassAccessFlags getAccessFlags();
+
   public abstract DexProgramClass getDexProgramClass();
 
   public abstract AnnotationSubject annotation(String name);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 859f58f..4ac4f95 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -356,7 +356,7 @@
   }
 
   @Override
-  public AccessFlags<?> getAccessFlags() {
+  public ClassAccessFlags getAccessFlags() {
     return getDexProgramClass().getAccessFlags();
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 5083d42..cbbbb14 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -4,9 +4,13 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.Collectors;
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.DiagnosticsMatcher;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.DexClass;
@@ -101,6 +105,28 @@
     };
   }
 
+  public static Matcher<ClassSubject> isInterface() {
+    return new TypeSafeMatcher<ClassSubject>() {
+      @Override
+      public boolean matchesSafely(ClassSubject subject) {
+        return subject.isPresent() && subject.getAccessFlags().isInterface();
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("interface");
+      }
+
+      @Override
+      public void describeMismatchSafely(ClassSubject subject, Description description) {
+        description
+            .appendText(type(subject) + " ")
+            .appendValue(name(subject))
+            .appendText(" was not");
+      }
+    };
+  }
+
   public static Matcher<Subject> isAbsent() {
     return not(isPresent());
   }
@@ -197,6 +223,21 @@
     };
   }
 
+  public static Matcher<Diagnostic> typeVariableNotInScope() {
+    return DiagnosticsMatcher.diagnosticMessage(containsString("A type variable is not in scope"));
+  }
+
+  public static Matcher<Diagnostic> invalidGenericArgumentApplicationCount() {
+    return DiagnosticsMatcher.diagnosticMessage(
+        containsString(
+            "The applied generic arguments have different count than the expected formals"));
+  }
+
+  public static Matcher<Diagnostic> proguardConfigurationRuleDoesNotMatch() {
+    return DiagnosticsMatcher.diagnosticMessage(
+        containsString("Proguard configuration rule does not match anything"));
+  }
+
   public static Matcher<Subject> isSynthetic() {
     return new TypeSafeMatcher<Subject>() {
       @Override
@@ -499,6 +540,22 @@
     };
   }
 
+  public static Matcher<ClassSubject> isImplementing(ClassSubject interfaceSubject) {
+    assertThat(interfaceSubject, isPresent());
+    assertThat(interfaceSubject, isInterface());
+    return new TypeSafeMatcher<ClassSubject>() {
+      @Override
+      public boolean matchesSafely(ClassSubject subject) {
+        return subject.isPresent() && subject.isImplementing(interfaceSubject);
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("implements ").appendText(interfaceSubject.getOriginalName());
+      }
+    };
+  }
+
   public static Matcher<RetraceFrameResult> isInlineFrame() {
     return new TypeSafeMatcher<RetraceFrameResult>() {
       @Override