Maintain no-horizontal-merge set obtained by conditional rules
Change-Id: If522b3991e6628d169371ecf232edbcf75f873b4
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 200fbc6..1fcb8e6 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -474,6 +474,7 @@
return new ConsequentRootSet(
neverInlineDueToSingleCaller,
neverClassInline,
+ noHorizontalClassMerging,
dependentMinimumKeepInfo,
dependentKeepClassCompatRule,
Lists.newArrayList(delayedRootSetActionItems),
@@ -1793,6 +1794,7 @@
final Set<DexMethod> neverInlineDueToSingleCaller;
final Set<DexType> neverClassInline;
+ final Set<DexType> noHorizontalClassMerging;
private final DependentMinimumKeepInfoCollection dependentMinimumKeepInfo;
final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
final List<DelayedRootSetActionItem> delayedRootSetActionItems;
@@ -1801,12 +1803,14 @@
RootSetBase(
Set<DexMethod> neverInlineDueToSingleCaller,
Set<DexType> neverClassInline,
+ Set<DexType> noHorizontalClassMerging,
DependentMinimumKeepInfoCollection dependentMinimumKeepInfo,
Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
List<DelayedRootSetActionItem> delayedRootSetActionItems,
ProgramMethodMap<ProgramMethod> pendingMethodMoveInverse) {
this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
this.neverClassInline = neverClassInline;
+ this.noHorizontalClassMerging = noHorizontalClassMerging;
this.dependentMinimumKeepInfo = dependentMinimumKeepInfo;
this.dependentKeepClassCompatRule = dependentKeepClassCompatRule;
this.delayedRootSetActionItems = delayedRootSetActionItems;
@@ -1833,7 +1837,6 @@
public final PredicateSet<DexType> alwaysClassInline;
public final Set<DexType> noUnusedInterfaceRemoval;
public final Set<DexType> noVerticalClassMerging;
- public final Set<DexType> noHorizontalClassMerging;
public final Set<DexMember<?, ?>> neverPropagateValue;
public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
public final Set<DexMember<?, ?>> identifierNameStrings;
@@ -1863,6 +1866,7 @@
super(
neverInlineDueToSingleCaller,
neverClassInline,
+ noHorizontalClassMerging,
dependentMinimumKeepInfo,
dependentKeepClassCompatRule,
delayedRootSetActionItems,
@@ -1876,7 +1880,6 @@
this.alwaysClassInline = alwaysClassInline;
this.noUnusedInterfaceRemoval = noUnusedInterfaceRemoval;
this.noVerticalClassMerging = noVerticalClassMerging;
- this.noHorizontalClassMerging = noHorizontalClassMerging;
this.neverPropagateValue = neverPropagateValue;
this.mayHaveSideEffects = mayHaveSideEffects;
this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
@@ -1909,6 +1912,7 @@
void addConsequentRootSet(ConsequentRootSet consequentRootSet) {
neverInlineDueToSingleCaller.addAll(consequentRootSet.neverInlineDueToSingleCaller);
neverClassInline.addAll(consequentRootSet.neverClassInline);
+ noHorizontalClassMerging.addAll(consequentRootSet.noHorizontalClassMerging);
consequentRootSet.dependentKeepClassCompatRule.forEach(
(type, rules) ->
dependentKeepClassCompatRule
@@ -2235,6 +2239,7 @@
ConsequentRootSet(
Set<DexMethod> neverInlineDueToSingleCaller,
Set<DexType> neverClassInline,
+ Set<DexType> noHorizontalClassMerging,
DependentMinimumKeepInfoCollection dependentMinimumKeepInfo,
Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
List<DelayedRootSetActionItem> delayedRootSetActionItems,
@@ -2242,6 +2247,7 @@
super(
neverInlineDueToSingleCaller,
neverClassInline,
+ noHorizontalClassMerging,
dependentMinimumKeepInfo,
dependentKeepClassCompatRule,
delayedRootSetActionItems,
diff --git a/src/test/java/com/android/tools/r8/keepanno/doctests/ConditionalMethodRulesAndHorizontalMergingTest.java b/src/test/java/com/android/tools/r8/keepanno/doctests/ConditionalMethodRulesAndHorizontalMergingTest.java
new file mode 100644
index 0000000..ee1748d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/doctests/ConditionalMethodRulesAndHorizontalMergingTest.java
@@ -0,0 +1,139 @@
+// 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.keepanno.doctests;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.doctests.ConditionalMethodRulesAndHorizontalMergingTest.Example1.BaseClass;
+import com.android.tools.r8.keepanno.doctests.ConditionalMethodRulesAndHorizontalMergingTest.Example1.MyHiddenMethodCaller;
+import com.android.tools.r8.keepanno.doctests.ConditionalMethodRulesAndHorizontalMergingTest.Example2.MyFieldValuePrinter;
+import com.android.tools.r8.keepanno.doctests.ConditionalMethodRulesAndHorizontalMergingTest.Example2.PrintableFieldInterface;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Field;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ConditionalMethodRulesAndHorizontalMergingTest extends TestBase {
+
+ static final String EXPECTED =
+ StringUtils.lines("on Base", "on Sub", "intField = 42", "stringField = Hello!");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public ConditionalMethodRulesAndHorizontalMergingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(TestClass.class)
+ .addProgramClassesAndInnerClasses(getExampleClasses())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addKeepMainRule(TestClass.class)
+ // The following rules are what was generated by the keepanno extraction for the doc tests.
+ .addKeepRules(
+ "-if class " + typeName(MyHiddenMethodCaller.class) + " {",
+ " void callHiddenMethod(" + typeName(BaseClass.class) + ");",
+ "}",
+ "-keepclassmembers class * extends " + typeName(BaseClass.class) + " {",
+ " *** hiddenMethod();",
+ "}",
+ "-if class " + typeName(MyHiddenMethodCaller.class) + " {",
+ " void callHiddenMethod(" + typeName(BaseClass.class) + ");",
+ "}",
+ "-keepclassmembers class " + typeName(BaseClass.class) + " {",
+ " *** hiddenMethod();",
+ "}",
+ "-if class " + typeName(MyFieldValuePrinter.class) + " {",
+ " void printFieldValues(" + typeName(PrintableFieldInterface.class) + ");",
+ "}",
+ "-keepclassmembers class * extends " + typeName(PrintableFieldInterface.class) + " {",
+ " *** *;",
+ "}"
+ // Removed the rule variant on the base interface as it does not have fields.
+ )
+ .addProgramClasses(TestClass.class)
+ .addProgramClassesAndInnerClasses(getExampleClasses())
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ public List<Class<?>> getExampleClasses() {
+ return ImmutableList.of(Example1.class, Example2.class);
+ }
+
+ static class TestClass {
+ public static void main(String[] args) throws Exception {
+ Example1.run();
+ Example2.run();
+ }
+ }
+
+ static class Example1 {
+
+ static class BaseClass {
+ void hiddenMethod() {
+ System.out.println("on Base");
+ }
+ }
+
+ static class SubClass extends BaseClass {
+ void hiddenMethod() {
+ System.out.println("on Sub");
+ }
+ }
+
+ public static class MyHiddenMethodCaller {
+ public void callHiddenMethod(BaseClass base) throws Exception {
+ base.getClass().getDeclaredMethod("hiddenMethod").invoke(base);
+ }
+ }
+
+ static void run() throws Exception {
+ new MyHiddenMethodCaller().callHiddenMethod(new BaseClass());
+ new MyHiddenMethodCaller().callHiddenMethod(new SubClass());
+ }
+ }
+
+ static class Example2 {
+
+ interface PrintableFieldInterface {}
+
+ static class ClassWithFields implements PrintableFieldInterface {
+ final int intField = 42;
+ String stringField = "Hello!";
+ }
+
+ public static class MyFieldValuePrinter {
+ public void printFieldValues(PrintableFieldInterface objectWithFields) throws Exception {
+ for (Field field : objectWithFields.getClass().getDeclaredFields()) {
+ System.out.println(field.getName() + " = " + field.get(objectWithFields));
+ }
+ }
+ }
+
+ static void run() throws Exception {
+ new MyFieldValuePrinter().printFieldValues(new ClassWithFields());
+ }
+ }
+}