Test warnings for package private access across isolated split

Bug: b/300247439
Change-Id: Ibe735397e38d45b9bbe44423cc50c56964707994
diff --git a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
index 0e79887..a20cfef 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.PrunedItems;
@@ -128,17 +129,16 @@
   public FeatureSplit getFeatureSplit(
       ProgramDefinition definition,
       SyntheticItems syntheticItems) {
-    return getFeatureSplit(definition.getContextType(), syntheticItems);
+    return getFeatureSplit(definition.getReference(), syntheticItems);
   }
 
   public FeatureSplit getFeatureSplit(
-      DexType type, AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return getFeatureSplit(type, appView.getSyntheticItems());
+      DexReference reference, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return getFeatureSplit(reference, appView.getSyntheticItems());
   }
 
-  public FeatureSplit getFeatureSplit(
-      DexType type,
-      SyntheticItems syntheticItems) {
+  public FeatureSplit getFeatureSplit(DexReference reference, SyntheticItems syntheticItems) {
+    DexType type = reference.getContextType();
     if (syntheticItems == null) {
       // Called from AndroidApp.dumpProgramResources().
       return classToFeatureSplitMap.getOrDefault(type, FeatureSplit.BASE);
@@ -216,6 +216,16 @@
     return !isInBase(clazz, syntheticItems);
   }
 
+  public boolean isInSameFeature(
+      ProgramDefinition definition, ProgramDefinition other, AppView<?> appView) {
+    return isInSameFeature(definition, other, appView.getSyntheticItems());
+  }
+
+  public boolean isInSameFeature(
+      ProgramDefinition definition, ProgramDefinition other, SyntheticItems syntheticItems) {
+    return getFeatureSplit(definition, syntheticItems) == getFeatureSplit(other, syntheticItems);
+  }
+
   public ClassToFeatureSplitMap rewrittenWithLens(GraphLens lens, Timing timing) {
     return timing.time("Rewrite ClassToFeatureSplitMap", () -> rewrittenWithLens(lens));
   }
diff --git a/src/main/java/com/android/tools/r8/features/IsolatedFeatureSplitsChecker.java b/src/main/java/com/android/tools/r8/features/IsolatedFeatureSplitsChecker.java
index 5708818..dbef2b3 100644
--- a/src/main/java/com/android/tools/r8/features/IsolatedFeatureSplitsChecker.java
+++ b/src/main/java/com/android/tools/r8/features/IsolatedFeatureSplitsChecker.java
@@ -79,7 +79,7 @@
   }
 
   private void checkAccess(ProgramDefinition accessedItem, ProgramMethod context) {
-    if (!features.isInBaseOrSameFeatureAs(accessedItem, context, appView)
+    if (!features.isInSameFeature(accessedItem, context, appView)
         && !accessedItem.getAccessFlags().isPublic()) {
       appView
           .reporter()
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index abafdc8..0e08d8f 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -75,6 +75,7 @@
 
   private AllowedDiagnosticMessages allowedDiagnosticMessages = AllowedDiagnosticMessages.NONE;
   private boolean allowUnusedProguardConfigurationRules = false;
+  private boolean enableIsolatedSplits = false;
   private boolean enableMissingLibraryApiModeling = true;
   private boolean enableStartupLayoutOptimization = true;
   private CollectingGraphConsumer graphConsumer = null;
@@ -152,6 +153,7 @@
     ToolHelper.addSyntheticProguardRulesConsumerForTesting(
         builder, rules -> box.syntheticProguardRules = rules);
     libraryDesugaringTestConfiguration.configure(builder);
+    builder.setEnableExperimentalIsolatedSplits(enableIsolatedSplits);
     builder.setEnableExperimentalMissingLibraryApiModeling(enableMissingLibraryApiModeling);
     builder.setEnableStartupLayoutOptimization(enableStartupLayoutOptimization);
     StringBuilder pgConfOutput = wrapProguardConfigConsumer(builder);
@@ -856,6 +858,11 @@
     return self();
   }
 
+  public T enableIsolatedSplits(boolean enableIsolatedSplits) {
+    this.enableIsolatedSplits = enableIsolatedSplits;
+    return self();
+  }
+
   public T addArtProfileForRewriting(ArtProfileProvider artProfileProvider) {
     return addArtProfileForRewriting(
         artProfileProvider,
diff --git a/src/test/java/com/android/tools/r8/features/PackagePrivateIsolatedSplitCrossBoundaryReferenceTest.java b/src/test/java/com/android/tools/r8/features/PackagePrivateIsolatedSplitCrossBoundaryReferenceTest.java
new file mode 100644
index 0000000..1b24d61
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/features/PackagePrivateIsolatedSplitCrossBoundaryReferenceTest.java
@@ -0,0 +1,151 @@
+// Copyright (c) 2023, 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.features;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilationIf;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder.DiagnosticsConsumer;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.features.diagnostic.IllegalAccessWithIsolatedFeatureSplitsDiagnostic;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PackagePrivateIsolatedSplitCrossBoundaryReferenceTest extends TestBase {
+
+  @Parameter(0)
+  public boolean enableIsolatedSplits;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, isolated splits: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimesAndAllApiLevels().build());
+  }
+
+  @Test
+  public void testPublicToPublic() throws Exception {
+    runTest("testPublicToPublic", TestDiagnosticMessages::assertNoMessages);
+  }
+
+  @Test
+  public void testPublicToNonPublic() throws Exception {
+    MethodReference accessedMethod =
+        Reference.methodFromMethod(NonPublicBase.class.getDeclaredMethod("nonPublicMethod"));
+    MethodReference context =
+        Reference.methodFromMethod(Feature.class.getDeclaredMethod("testPublicToNonPublic"));
+    assertFailsCompilationIf(
+        enableIsolatedSplits,
+        () ->
+            runTest(
+                "testPublicToNonPublic",
+                diagnostics ->
+                    diagnostics.assertErrorsMatch(
+                        allOf(
+                            diagnosticType(IllegalAccessWithIsolatedFeatureSplitsDiagnostic.class),
+                            diagnosticMessage(
+                                equalTo(getExpectedDiagnosticMessage(accessedMethod, context)))))));
+  }
+
+  @Test
+  public void testNonPublicToPublic() throws Exception {
+    ClassReference accessedClass = Reference.classFromClass(NonPublicBaseSub.class);
+    MethodReference context =
+        Reference.methodFromMethod(Feature.class.getDeclaredMethod("testNonPublicToPublic"));
+    assertFailsCompilationIf(
+        enableIsolatedSplits,
+        () ->
+            runTest(
+                "testNonPublicToPublic",
+                diagnostics ->
+                    diagnostics.assertErrorsMatch(
+                        allOf(
+                            diagnosticType(IllegalAccessWithIsolatedFeatureSplitsDiagnostic.class),
+                            diagnosticMessage(
+                                equalTo(getExpectedDiagnosticMessage(accessedClass, context)))))));
+  }
+
+  private static String getExpectedDiagnosticMessage(
+      ClassReference accessedClass, MethodReference context) {
+    return "Unexpected illegal access to non-public class in another feature split "
+        + "(accessed: "
+        + accessedClass.getTypeName()
+        + ", context: "
+        + MethodReferenceUtils.toSourceString(context)
+        + ").";
+  }
+
+  private static String getExpectedDiagnosticMessage(
+      MethodReference accessedMethod, MethodReference context) {
+    return "Unexpected illegal access to non-public method in another feature split "
+        + "(accessed: "
+        + MethodReferenceUtils.toSourceString(accessedMethod)
+        + ", context: "
+        + MethodReferenceUtils.toSourceString(context)
+        + ").";
+  }
+
+  private void runTest(String name, DiagnosticsConsumer inspector) throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(NonPublicBase.class, PublicBaseSub.class, NonPublicBaseSub.class)
+        .addKeepClassAndMembersRules(
+            NonPublicBase.class, PublicBaseSub.class, NonPublicBaseSub.class)
+        .addFeatureSplit(Feature.class)
+        .addKeepRules("-keep class " + Feature.class.getTypeName() + " { void " + name + "(); }")
+        .enableIsolatedSplits(enableIsolatedSplits)
+        .setMinApi(parameters)
+        .compileWithExpectedDiagnostics(enableIsolatedSplits ? inspector : this::assertNoMessages);
+  }
+
+  private void assertNoMessages(TestDiagnosticMessages diagnostics) {
+    diagnostics.assertNoMessages();
+  }
+
+  static class NonPublicBase {
+
+    public static void publicMethod() {
+      // Intentionally empty.
+    }
+
+    static void nonPublicMethod() {
+      // Intentionally empty.
+    }
+  }
+
+  public static class PublicBaseSub extends NonPublicBase {}
+
+  static class NonPublicBaseSub extends NonPublicBase {}
+
+  public static class Feature {
+
+    public static void testPublicToPublic() {
+      PublicBaseSub.publicMethod();
+    }
+
+    public static void testPublicToNonPublic() {
+      PublicBaseSub.nonPublicMethod();
+    }
+
+    public static void testNonPublicToPublic() {
+      NonPublicBaseSub.publicMethod();
+    }
+  }
+}