Don't rebind to protected library members when generating class files
Fixes: b/367915233
Change-Id: Iedee3cd60d668810a9ba073e23234e5097ff6bec
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingHelper.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingHelper.java
index a2d0f00..649b64f 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingHelper.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingHelper.java
@@ -161,6 +161,9 @@
// `original.holder` on all API levels, in which case it is not OK to rebind to the resolved
// method.
return resolvedMethod.isLibraryMethod()
+ // Only rebind to protected methods in library when generating DEX. Dalvik/ART does not
+ // implement the full instruction verification.
+ && (options.isGeneratingDex() || !resolvedMethod.getAccessFlags().isProtected())
&& isAccessibleInAllContexts(resolvedMethod, resolutionResult, contexts)
&& !isInvokeSuperToInterfaceMethod(resolvedMethod, invokeType)
&& !isInvokeSuperToAbstractMethod(resolvedMethod, invokeType)
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/protectedaccess/MemberRebindingProtectedInLibraryHierarchyTest.java b/src/test/java/com/android/tools/r8/memberrebinding/protectedaccess/MemberRebindingProtectedInLibraryHierarchyTest.java
new file mode 100644
index 0000000..de3d6bb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/protectedaccess/MemberRebindingProtectedInLibraryHierarchyTest.java
@@ -0,0 +1,74 @@
+// 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.memberrebinding.protectedaccess;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import java.util.LinkedHashMap;
+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;
+
+// Test for B/367915233.
+@RunWith(Parameterized.class)
+public class MemberRebindingProtectedInLibraryHierarchyTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!");
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT)
+ .inspect(
+ inspector ->
+ assertTrue(
+ inspector
+ .clazz(TestClass.class)
+ .mainMethod()
+ .streamInstructions()
+ .filter(InstructionSubject::isInvokeVirtual)
+ .map(
+ instruction -> ((InvokeInstructionSubject) instruction).invokedMethod())
+ .anyMatch(
+ method ->
+ (parameters.isCfRuntime()
+ ? method
+ .getHolderType()
+ .toSourceString()
+ .equals("java.util.HashMap")
+ : method
+ .getHolderType()
+ .toSourceString()
+ .equals("java.lang.Object"))
+ && method.getName().toString().equals("clone"))));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new LinkedHashMap<>().clone();
+ System.out.println("Hello, world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/B367915233Test.java b/src/test/java/com/android/tools/r8/resolution/B367915233Test.java
deleted file mode 100644
index c01e188..0000000
--- a/src/test/java/com/android/tools/r8/resolution/B367915233Test.java
+++ /dev/null
@@ -1,50 +0,0 @@
-// 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.resolution;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.StringUtils;
-import java.util.LinkedHashMap;
-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 B367915233Test extends TestBase {
-
- @Parameter(0)
- public TestParameters parameters;
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
- }
-
- private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!");
-
- @Test
- public void testR8() throws Exception {
- testForR8(parameters.getBackend())
- .addInnerClasses(getClass())
- .addKeepMainRule(TestClass.class)
- .setMinApi(parameters)
- .run(parameters.getRuntime(), TestClass.class)
- .applyIf(
- parameters.isCfRuntime(),
- r -> r.assertFailureWithErrorThatThrows(VerifyError.class),
- r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
- }
-
- static class TestClass {
-
- public static void main(String[] args) {
- new LinkedHashMap<>().clone();
- System.out.println("Hello, world!");
- }
- }
-}