Preserve IAE across isolated split boundary

Bug: b/300247439
Change-Id: I78fb2205884b0d170868a78e713da1a03a05aee2
diff --git a/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java b/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
index 9de2dad..4c5c33b 100644
--- a/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
+++ b/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
@@ -9,75 +9,45 @@
 import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.graph.Definition;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramDefinition;
-import com.android.tools.r8.graph.ProgramMember;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.synthesis.SyntheticItems;
 
 public class FeatureSplitBoundaryOptimizationUtils {
 
-  public static ConstraintWithTarget getInliningConstraintForResolvedMember(
-      DexClassAndMember<?, ?> resolvedMember,
-      DexClass initialResolutionHolder,
-      ProgramMethod context,
-      AppView<? extends AppInfoWithClassHierarchy> appView) {
-    if (!appView.options().hasFeatureSplitConfiguration()) {
-      return ConstraintWithTarget.ALWAYS;
-    }
-    // If we resolve to something outside the program then everything is fine.
-    if (!initialResolutionHolder.isProgramClass()) {
-      assert !resolvedMember.isProgramMember();
-      return ConstraintWithTarget.ALWAYS;
-    }
-    ProgramMember<?, ?> resolvedProgramMember = resolvedMember.asProgramMember();
-    DexProgramClass initialProgramResolutionHolder = initialResolutionHolder.asProgramClass();
-    return getInliningConstraintForResolvedMember(
-        resolvedProgramMember, initialProgramResolutionHolder, context, appView);
-  }
-
-  private static ConstraintWithTarget getInliningConstraintForResolvedMember(
-      ProgramMember<?, ?> resolvedMember,
-      DexProgramClass initialResolutionHolder,
-      ProgramMethod context,
-      AppView<? extends AppInfoWithClassHierarchy> appView) {
-    assert appView.options().hasFeatureSplitConfiguration();
-    ClassToFeatureSplitMap features = appView.appInfo().getClassToFeatureSplitMap();
-    // Check that whatever we resolve to is in the same feature or in base.
-    if (!features.isInBaseOrSameFeatureAs(initialResolutionHolder, context, appView)
-        || !features.isInBaseOrSameFeatureAs(resolvedMember, context, appView)) {
-      return ConstraintWithTarget.NEVER;
-    }
-    // If isolated splits are enabled then the resolved method must be in the same feature split or
-    // it must be public.
-    if (appView.options().getFeatureSplitConfiguration().isIsolatedSplitsEnabled()) {
-      if (!initialResolutionHolder.isPublic()
-          && !features.isInSameFeature(initialResolutionHolder, context, appView)) {
-        return ConstraintWithTarget.NEVER;
-      }
-      if (!resolvedMember.getAccessFlags().isPublic()
-          && !features.isInSameFeature(resolvedMember, context, appView)) {
-        return ConstraintWithTarget.NEVER;
-      }
-    }
-    return ConstraintWithTarget.ALWAYS;
-  }
-
   public static FeatureSplit getMergeKeyForHorizontalClassMerging(
       DexProgramClass clazz, AppView<? extends AppInfoWithClassHierarchy> appView) {
-    ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
-    return classToFeatureSplitMap.getFeatureSplit(clazz, appView);
+    return appView.appInfo().getClassToFeatureSplitMap().getFeatureSplit(clazz, appView);
   }
 
   public static boolean isSafeForAccess(
-      DexProgramClass accessedClass,
-      ProgramDefinition accessor,
-      ClassToFeatureSplitMap classToFeatureSplitMap,
-      SyntheticItems syntheticItems) {
-    return classToFeatureSplitMap.isInBaseOrSameFeatureAs(accessedClass, accessor, syntheticItems);
+      Definition definition,
+      ProgramDefinition context,
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return !appView.options().hasFeatureSplitConfiguration()
+        || !definition.isProgramDefinition()
+        || isSafeForAccess(definition.asProgramDefinition(), context, appView);
+  }
+
+  private static boolean isSafeForAccess(
+      ProgramDefinition definition,
+      ProgramDefinition context,
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    assert appView.options().hasFeatureSplitConfiguration();
+    ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
+    if (classToFeatureSplitMap.isInSameFeature(definition, context, appView)) {
+      return true;
+    }
+    if (!classToFeatureSplitMap.isInBase(definition, appView)) {
+      return false;
+    }
+    // If isolated splits are enabled then the resolved method must be public.
+    if (appView.options().getFeatureSplitConfiguration().isIsolatedSplitsEnabled()
+        && !definition.getAccessFlags().isPublic()) {
+      return false;
+    }
+    return true;
   }
 
   public static boolean isSafeForInlining(
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index 2d5b68a..3ed6de7 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -3,9 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.features.FeatureSplitBoundaryOptimizationUtils;
-import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.OptionalBool;
 
 /**
@@ -17,31 +15,20 @@
 public class AccessControl {
 
   public static OptionalBool isClassAccessible(
-      DexClass clazz,
-      ProgramDefinition context,
-      AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return isClassAccessible(
-        clazz,
-        context,
-        appView.appInfo().getClassToFeatureSplitMap(),
-        appView.getSyntheticItems());
+      DexClass clazz, Definition context, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return isClassAccessible(clazz, context, appView, appView.appInfo());
   }
 
   public static OptionalBool isClassAccessible(
-      DexClass clazz,
-      Definition context,
-      ClassToFeatureSplitMap classToFeatureSplitMap,
-      SyntheticItems syntheticItems) {
+      DexClass clazz, Definition context, AppView<?> appView, AppInfoWithClassHierarchy appInfo) {
+    assert appInfo != null;
     if (!clazz.isPublic() && !clazz.getType().isSamePackage(context.getContextType())) {
       return OptionalBool.FALSE;
     }
-    if (clazz.isProgramClass()
+    if (appView.hasClassHierarchy()
         && context.isProgramDefinition()
         && !FeatureSplitBoundaryOptimizationUtils.isSafeForAccess(
-            clazz.asProgramClass(),
-            context.asProgramDefinition(),
-            classToFeatureSplitMap,
-            syntheticItems)) {
+            clazz, context.asProgramDefinition(), appView.withClassHierarchy())) {
       return OptionalBool.UNKNOWN;
     }
     return OptionalBool.TRUE;
@@ -51,11 +38,13 @@
   static OptionalBool isMemberAccessible(
       SuccessfulMemberResolutionResult<?, ?> resolutionResult,
       Definition context,
+      AppView<?> appView,
       AppInfoWithClassHierarchy appInfo) {
     return isMemberAccessible(
         resolutionResult.getResolutionPair(),
         resolutionResult.getInitialResolutionHolder(),
         context,
+        appView,
         appInfo);
   }
 
@@ -64,21 +53,19 @@
       Definition initialResolutionContext,
       Definition context,
       AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return isMemberAccessible(member, initialResolutionContext, context, appView.appInfo());
+    return isMemberAccessible(
+        member, initialResolutionContext, context, appView, appView.appInfo());
   }
 
   static OptionalBool isMemberAccessible(
       DexClassAndMember<?, ?> member,
       Definition initialResolutionContext,
       Definition context,
+      AppView<?> appView,
       AppInfoWithClassHierarchy appInfo) {
     AccessFlags<?> memberFlags = member.getDefinition().getAccessFlags();
     OptionalBool classAccessibility =
-        isClassAccessible(
-            initialResolutionContext.getContextClass(),
-            context,
-            appInfo.getClassToFeatureSplitMap(),
-            appInfo.getSyntheticItems());
+        isClassAccessible(initialResolutionContext.getContextClass(), context, appView, appInfo);
     if (classAccessibility.isFalse()) {
       return OptionalBool.FALSE;
     }
@@ -91,6 +78,12 @@
       }
       return classAccessibility;
     }
+    if (appView.hasClassHierarchy()
+        && context.isProgramDefinition()
+        && !FeatureSplitBoundaryOptimizationUtils.isSafeForAccess(
+            member, context.asProgramDefinition(), appView.withClassHierarchy())) {
+      return OptionalBool.UNKNOWN;
+    }
     if (member.getHolderType().isSamePackage(context.getContextType())) {
       return classAccessibility;
     }
@@ -101,7 +94,6 @@
     return OptionalBool.FALSE;
   }
 
-  @SuppressWarnings("ReferenceEquality")
   private static boolean isNestMate(DexClass clazz, DexClass context) {
     if (clazz == context) {
       return true;
@@ -113,6 +105,6 @@
     if (!clazz.isInANest() || !context.isInANest()) {
       return false;
     }
-    return clazz.getNestHost() == context.getNestHost();
+    return clazz.getNestHost().isIdenticalTo(context.getNestHost());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
index 6bae976..a383253 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
@@ -203,7 +203,7 @@
     @Override
     public OptionalBool isAccessibleFrom(
         ProgramDefinition context, AppView<?> appView, AppInfoWithClassHierarchy appInfo) {
-      return AccessControl.isMemberAccessible(this, context, appInfo);
+      return AccessControl.isMemberAccessible(this, context, appView, appInfo);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
index 03b3932..bdc3a2a 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -501,7 +501,7 @@
     @Override
     public OptionalBool isAccessibleFrom(
         ProgramDefinition context, AppView<?> appView, AppInfoWithClassHierarchy appInfo) {
-      return AccessControl.isMemberAccessible(this, context, appInfo);
+      return AccessControl.isMemberAccessible(this, context, appView, appInfo);
     }
 
     @Override
@@ -1480,18 +1480,14 @@
                   .forEachClassResolutionResult(
                       clazz ->
                           seenNoAccess.or(
-                              AccessControl.isClassAccessible(
-                                      clazz,
-                                      context,
-                                      appInfo.getClassToFeatureSplitMap(),
-                                      appView.getSyntheticItems())
+                              AccessControl.isClassAccessible(clazz, context, appView, appInfo)
                                   .isPossiblyFalse())),
           method -> {
             DexClass holder = appView.definitionFor(method.getHolderType());
             DexClassAndMethod classAndMethod = DexClassAndMethod.create(holder, method);
             seenNoAccess.or(
                 AccessControl.isMemberAccessible(
-                        classAndMethod, initialResolutionHolder, context, appInfo)
+                        classAndMethod, initialResolutionHolder, context, appView, appInfo)
                     .isPossiblyFalse());
           });
       return seenNoAccess.get();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index b3ebdc6..a3ea046 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -346,13 +346,11 @@
       DexClass initialResolutionHolder,
       ProgramMethod context,
       DexClassAndMember<?, ?> resolvedMember) {
-    ConstraintWithTarget featureSplitInliningConstraint =
-        FeatureSplitBoundaryOptimizationUtils.getInliningConstraintForResolvedMember(
-            resolvedMember, initialResolutionHolder, context, appView);
-    assert featureSplitInliningConstraint == ConstraintWithTarget.ALWAYS
-        || featureSplitInliningConstraint.isNever();
-    if (featureSplitInliningConstraint.isNever()) {
-      return featureSplitInliningConstraint;
+    if (!FeatureSplitBoundaryOptimizationUtils.isSafeForAccess(
+            initialResolutionHolder, context, appView)
+        || !FeatureSplitBoundaryOptimizationUtils.isSafeForAccess(
+            resolvedMember, context, appView)) {
+      return ConstraintWithTarget.NEVER;
     }
     DexType resolvedHolder = resolvedMember.getHolderType();
     assert initialResolutionHolder != null;
diff --git a/src/test/java/com/android/tools/r8/features/PreserveIllegalAccessAcrossIsolatedSplitBoundaryTest.java b/src/test/java/com/android/tools/r8/features/PreserveIllegalAccessAcrossIsolatedSplitBoundaryTest.java
index d6edc0b..ecae36b 100644
--- a/src/test/java/com/android/tools/r8/features/PreserveIllegalAccessAcrossIsolatedSplitBoundaryTest.java
+++ b/src/test/java/com/android/tools/r8/features/PreserveIllegalAccessAcrossIsolatedSplitBoundaryTest.java
@@ -4,8 +4,9 @@
 package com.android.tools.r8.features;
 
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPackagePrivate;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
@@ -58,10 +59,12 @@
               ClassSubject baseClassSubject = baseInspector.clazz(Base.class);
               assertThat(baseClassSubject, isPresent());
 
-              // TODO(b/300247439): Illegal access error should be preserved.
               MethodSubject nonPublicMethodSubject =
                   baseClassSubject.uniqueMethodWithOriginalName("nonPublicMethod");
-              assertThat(nonPublicMethodSubject, isAbsent());
+              assertThat(nonPublicMethodSubject, isPresentIf(enableIsolatedSplits));
+              if (enableIsolatedSplits) {
+                assertThat(nonPublicMethodSubject, isPackagePrivate());
+              }
 
               MethodSubject otherMethodSubject =
                   baseClassSubject.uniqueMethodWithOriginalName("otherMethod");
@@ -73,7 +76,10 @@
               MethodSubject featureMethodSubject =
                   featureClassSubject.uniqueMethodWithOriginalName("test");
               assertThat(featureMethodSubject, isPresent());
-              assertThat(featureMethodSubject, invokesMethod(otherMethodSubject));
+              assertThat(
+                  featureMethodSubject,
+                  invokesMethod(
+                      enableIsolatedSplits ? nonPublicMethodSubject : otherMethodSubject));
             });
   }