Fix spurious warning for protected access with isolated splits
Bug: b/300247439
Change-Id: I76efaa337a63ad0c1399bead9fc6a84e9b7d8f75
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 4c5c33b..9ac1bd5 100644
--- a/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
+++ b/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
@@ -42,10 +42,21 @@
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;
+ // If isolated splits are enabled then the resolved item must be either (1) public or (2)
+ // a protected member and the access is in a subclass of the resolved member's holder.
+ if (appView.options().getFeatureSplitConfiguration().isIsolatedSplitsEnabled()) {
+ if (definition.isClass()) {
+ if (!definition.getAccessFlags().isPublic()) {
+ return false;
+ }
+ } else if (definition.getAccessFlags().isPackagePrivateOrProtected()) {
+ if (definition.getAccessFlags().isPackagePrivate()
+ || !appView
+ .appInfo()
+ .isSubtype(context.getContextClass(), definition.getContextClass())) {
+ return false;
+ }
+ }
}
return true;
}
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 1043723..376acee 100644
--- a/src/main/java/com/android/tools/r8/features/IsolatedFeatureSplitsChecker.java
+++ b/src/main/java/com/android/tools/r8/features/IsolatedFeatureSplitsChecker.java
@@ -80,6 +80,10 @@
|| features.isInSameFeature(accessedItem, context, appView)) {
return;
}
+ if (accessedItem.getAccessFlags().isProtected()
+ && appView.appInfo().isSubtype(context.getContextClass(), accessedItem.getContextClass())) {
+ return;
+ }
DontWarnConfiguration dontWarnConfiguration = appView.getDontWarnConfiguration();
if (dontWarnConfiguration.matches(accessedItem) || dontWarnConfiguration.matches(context)) {
return;
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 3ed6de7..2cceb1a 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -78,20 +78,19 @@
}
return classAccessibility;
}
+ if (!member.getHolderType().isSamePackage(context.getContextType())) {
+ if (memberFlags.isPackagePrivate()
+ || !appInfo.isSubtype(context.getContextType(), member.getHolderType())) {
+ return OptionalBool.FALSE;
+ }
+ }
if (appView.hasClassHierarchy()
&& context.isProgramDefinition()
&& !FeatureSplitBoundaryOptimizationUtils.isSafeForAccess(
member, context.asProgramDefinition(), appView.withClassHierarchy())) {
return OptionalBool.UNKNOWN;
}
- if (member.getHolderType().isSamePackage(context.getContextType())) {
- return classAccessibility;
- }
- if (memberFlags.isProtected()
- && appInfo.isSubtype(context.getContextType(), member.getHolderType())) {
- return classAccessibility;
- }
- return OptionalBool.FALSE;
+ return classAccessibility;
}
private static boolean isNestMate(DexClass clazz, DexClass context) {
diff --git a/src/test/java/com/android/tools/r8/features/PackagePrivateIsolatedSplitCrossBoundaryReferenceTest.java b/src/test/java/com/android/tools/r8/features/PackagePrivateIsolatedSplitCrossBoundaryReferenceTest.java
index 1b24d61..10afe1b 100644
--- a/src/test/java/com/android/tools/r8/features/PackagePrivateIsolatedSplitCrossBoundaryReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/features/PackagePrivateIsolatedSplitCrossBoundaryReferenceTest.java
@@ -42,45 +42,43 @@
}
@Test
+ public void testPublicToProtected() throws Exception {
+ MethodReference accessedMethod =
+ Reference.methodFromMethod(NonPublicBase.class.getDeclaredMethod("protectedMethod"));
+ MethodReference main =
+ Reference.methodFromMethod(Feature.class.getDeclaredMethod("testPublicToProtected"));
+ runTestWithExpectedFailure(main, getExpectedDiagnosticMessage(accessedMethod, main));
+ }
+
+ @Test
+ public void testPublicToProtectedFromSubclass() throws Exception {
+ MethodReference main =
+ Reference.methodFromMethod(FeatureSub.class.getDeclaredMethod("testPublicToProtected"));
+ runTest(main, TestDiagnosticMessages::assertNoMessages);
+ }
+
+ @Test
public void testPublicToPublic() throws Exception {
- runTest("testPublicToPublic", TestDiagnosticMessages::assertNoMessages);
+ MethodReference main =
+ Reference.methodFromMethod(Feature.class.getDeclaredMethod("testPublicToPublic"));
+ runTest(main, TestDiagnosticMessages::assertNoMessages);
}
@Test
public void testPublicToNonPublic() throws Exception {
MethodReference accessedMethod =
Reference.methodFromMethod(NonPublicBase.class.getDeclaredMethod("nonPublicMethod"));
- MethodReference context =
+ MethodReference main =
Reference.methodFromMethod(Feature.class.getDeclaredMethod("testPublicToNonPublic"));
- assertFailsCompilationIf(
- enableIsolatedSplits,
- () ->
- runTest(
- "testPublicToNonPublic",
- diagnostics ->
- diagnostics.assertErrorsMatch(
- allOf(
- diagnosticType(IllegalAccessWithIsolatedFeatureSplitsDiagnostic.class),
- diagnosticMessage(
- equalTo(getExpectedDiagnosticMessage(accessedMethod, context)))))));
+ runTestWithExpectedFailure(main, getExpectedDiagnosticMessage(accessedMethod, main));
}
@Test
public void testNonPublicToPublic() throws Exception {
ClassReference accessedClass = Reference.classFromClass(NonPublicBaseSub.class);
- MethodReference context =
+ MethodReference main =
Reference.methodFromMethod(Feature.class.getDeclaredMethod("testNonPublicToPublic"));
- assertFailsCompilationIf(
- enableIsolatedSplits,
- () ->
- runTest(
- "testNonPublicToPublic",
- diagnostics ->
- diagnostics.assertErrorsMatch(
- allOf(
- diagnosticType(IllegalAccessWithIsolatedFeatureSplitsDiagnostic.class),
- diagnosticMessage(
- equalTo(getExpectedDiagnosticMessage(accessedClass, context)))))));
+ runTestWithExpectedFailure(main, getExpectedDiagnosticMessage(accessedClass, main));
}
private static String getExpectedDiagnosticMessage(
@@ -103,13 +101,32 @@
+ ").";
}
- private void runTest(String name, DiagnosticsConsumer inspector) throws Exception {
+ private void runTestWithExpectedFailure(MethodReference main, String expectedDiagnosticMessage)
+ throws Exception {
+ assertFailsCompilationIf(
+ enableIsolatedSplits,
+ () ->
+ runTest(
+ main,
+ diagnostics ->
+ diagnostics.assertErrorsMatch(
+ allOf(
+ diagnosticType(IllegalAccessWithIsolatedFeatureSplitsDiagnostic.class),
+ diagnosticMessage(equalTo(expectedDiagnosticMessage))))));
+ }
+
+ private void runTest(MethodReference main, 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 + "(); }")
+ .addFeatureSplit(Feature.class, FeatureSub.class)
+ .addKeepRules(
+ "-keep class "
+ + main.getHolderClass().getTypeName()
+ + " { void "
+ + main.getMethodName()
+ + "(); }")
.enableIsolatedSplits(enableIsolatedSplits)
.setMinApi(parameters)
.compileWithExpectedDiagnostics(enableIsolatedSplits ? inspector : this::assertNoMessages);
@@ -121,6 +138,10 @@
static class NonPublicBase {
+ protected static void protectedMethod() {
+ // Intentionally empty.
+ }
+
public static void publicMethod() {
// Intentionally empty.
}
@@ -136,6 +157,10 @@
public static class Feature {
+ public static void testPublicToProtected() {
+ PublicBaseSub.protectedMethod();
+ }
+
public static void testPublicToPublic() {
PublicBaseSub.publicMethod();
}
@@ -148,4 +173,11 @@
NonPublicBaseSub.publicMethod();
}
}
+
+ public static class FeatureSub extends NonPublicBase {
+
+ public static void testPublicToProtected() {
+ PublicBaseSub.protectedMethod();
+ }
+ }
}