Merge "Add @NeverMerge annotation for testing"
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java b/src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java
new file mode 100644
index 0000000..43bfe4b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2018, 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.shaking;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class ClassMergingRule extends ProguardConfigurationRule {
+
+ public enum Type {
+ NEVER
+ }
+
+ public static class Builder extends ProguardConfigurationRule.Builder<ClassMergingRule, Builder> {
+
+ private Builder() {
+ super();
+ }
+
+ Type type;
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ public Builder setType(Type type) {
+ this.type = type;
+ return this;
+ }
+
+ @Override
+ public ClassMergingRule build() {
+ return new ClassMergingRule(origin, getPosition(), source, classAnnotation, classAccessFlags,
+ negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
+ inheritanceClassName, inheritanceIsExtends, memberRules, type);
+ }
+ }
+
+ private final Type type;
+
+ private ClassMergingRule(
+ Origin origin,
+ Position position,
+ String source,
+ ProguardTypeMatcher classAnnotation,
+ ProguardAccessFlags classAccessFlags,
+ ProguardAccessFlags negatedClassAccessFlags,
+ boolean classTypeNegated,
+ ProguardClassType classType,
+ ProguardClassNameList classNames,
+ ProguardTypeMatcher inheritanceAnnotation,
+ ProguardTypeMatcher inheritanceClassName,
+ boolean inheritanceIsExtends,
+ List<ProguardMemberRule> memberRules,
+ Type type) {
+ super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+ classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+ inheritanceIsExtends, memberRules);
+ this.type = type;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ @Override
+ String typeString() {
+ switch (type) {
+ case NEVER:
+ return "nevermerge";
+ }
+ throw new Unreachable("Unknown class merging type " + type);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index e0cf415..8574e6d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1813,6 +1813,10 @@
*/
public final Set<DexMethod> neverInline;
/**
+ * All types that *must* never be merged due to a configuration directive (testing only).
+ */
+ public final Set<DexType> neverMerge;
+ /**
* All items with -identifiernamestring rule.
* Bound boolean value indicates the rule is explicitly specified by users (<code>true</code>)
* or not, i.e., implicitly added by R8 (<code>false</code>).
@@ -1871,6 +1875,7 @@
this.alwaysInline = enqueuer.rootSet.alwaysInline;
this.forceInline = enqueuer.rootSet.forceInline;
this.neverInline = enqueuer.rootSet.neverInline;
+ this.neverMerge = enqueuer.rootSet.neverMerge;
this.identifierNameStrings = joinIdentifierNameStrings(
enqueuer.rootSet.identifierNameStrings, enqueuer.identifierNameStrings);
this.prunedTypes = Collections.emptySet();
@@ -1913,6 +1918,7 @@
this.alwaysInline = previous.alwaysInline;
this.forceInline = previous.forceInline;
this.neverInline = previous.neverInline;
+ this.neverMerge = previous.neverMerge;
this.identifierNameStrings = previous.identifierNameStrings;
this.prunedTypes = mergeSets(previous.prunedTypes, removedClasses);
this.switchMaps = previous.switchMaps;
@@ -1965,6 +1971,10 @@
this.alwaysInline = previous.alwaysInline;
this.forceInline = lense.rewriteMethodsWithRenamedSignature(previous.forceInline);
this.neverInline = lense.rewriteMethodsWithRenamedSignature(previous.neverInline);
+ assert lense.assertDefinitionNotModified(
+ previous.neverMerge.stream().map(this::definitionFor).filter(Objects::nonNull)
+ .collect(Collectors.toList()));
+ this.neverMerge = previous.neverMerge;
this.identifierNameStrings =
lense.rewriteReferencesConservatively(previous.identifierNameStrings);
// Switchmap classes should never be affected by renaming.
@@ -2008,6 +2018,7 @@
this.alwaysInline = previous.alwaysInline;
this.forceInline = previous.forceInline;
this.neverInline = previous.neverInline;
+ this.neverMerge = previous.neverMerge;
this.identifierNameStrings = previous.identifierNameStrings;
this.prunedTypes = previous.prunedTypes;
this.switchMaps = switchMaps;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index faf5317..7d5f9a5 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -364,6 +364,9 @@
} else if (allowTestOptions && acceptString("neverinline")) {
InlineRule rule = parseInlineRule(Type.NEVER, optionStart);
configurationBuilder.addRule(rule);
+ } else if (allowTestOptions && acceptString("nevermerge")) {
+ ClassMergingRule rule = parseClassMergingRule(ClassMergingRule.Type.NEVER, optionStart);
+ configurationBuilder.addRule(rule);
} else if (acceptString("useuniqueclassmembernames")) {
configurationBuilder.setUseUniqueClassMemberNames(true);
} else if (acceptString("adaptclassstrings")) {
@@ -594,6 +597,17 @@
return keepRuleBuilder.build();
}
+ private ClassMergingRule parseClassMergingRule(ClassMergingRule.Type type, Position start)
+ throws ProguardRuleParserException {
+ ClassMergingRule.Builder keepRuleBuilder =
+ ClassMergingRule.builder().setOrigin(origin).setStart(start).setType(type);
+ parseClassSpec(keepRuleBuilder, false);
+ Position end = getPosition();
+ keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+ keepRuleBuilder.setEnd(end);
+ return keepRuleBuilder.build();
+ }
+
private InlineRule parseInlineRule(InlineRule.Type type, Position start)
throws ProguardRuleParserException {
InlineRule.Builder keepRuleBuilder = InlineRule.builder()
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 432ae10..19eb127 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -66,6 +66,7 @@
private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
+ private final Set<DexType> neverMerge = Sets.newIdentityHashSet();
private final Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking =
new IdentityHashMap<>();
private final Map<DexDefinition, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
@@ -173,6 +174,10 @@
} else if (rule instanceof ProguardAssumeNoSideEffectRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
markMatchingFields(clazz, memberKeepRules, rule, null);
+ } else if (rule instanceof ClassMergingRule) {
+ if (allRulesSatisfied(memberKeepRules, clazz)) {
+ markClass(clazz, rule);
+ }
} else if (rule instanceof InlineRule) {
markMatchingMethods(clazz, memberKeepRules, rule, null);
} else if (rule instanceof ProguardAssumeValuesRule) {
@@ -245,6 +250,7 @@
alwaysInline,
forceInline,
neverInline,
+ neverMerge,
noSideEffects,
assumedValues,
dependentNoShrinking,
@@ -844,6 +850,16 @@
default:
throw new Unreachable();
}
+ } else if (context instanceof ClassMergingRule) {
+ switch (((ClassMergingRule) context).getType()) {
+ case NEVER:
+ if (item.isDexClass()) {
+ neverMerge.add(item.asDexClass().type);
+ }
+ break;
+ default:
+ throw new Unreachable();
+ }
} else if (context instanceof ProguardIdentifierNameStringRule) {
if (item.isDexEncodedField()) {
identifierNameStrings.add(item.asDexEncodedField().field);
@@ -864,6 +880,7 @@
public final Set<DexMethod> alwaysInline;
public final Set<DexMethod> forceInline;
public final Set<DexMethod> neverInline;
+ public final Set<DexType> neverMerge;
public final Map<DexDefinition, ProguardMemberRule> noSideEffects;
public final Map<DexDefinition, ProguardMemberRule> assumedValues;
private final Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking;
@@ -880,6 +897,7 @@
Set<DexMethod> alwaysInline,
Set<DexMethod> forceInline,
Set<DexMethod> neverInline,
+ Set<DexType> neverMerge,
Map<DexDefinition, ProguardMemberRule> noSideEffects,
Map<DexDefinition, ProguardMemberRule> assumedValues,
Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking,
@@ -894,6 +912,7 @@
this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
this.forceInline = Collections.unmodifiableSet(forceInline);
this.neverInline = Collections.unmodifiableSet(neverInline);
+ this.neverMerge = Collections.unmodifiableSet(neverMerge);
this.noSideEffects = Collections.unmodifiableMap(noSideEffects);
this.assumedValues = Collections.unmodifiableMap(assumedValues);
this.dependentNoShrinking = dependentNoShrinking;
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index 5d88374..71a249f 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -132,6 +132,9 @@
}
public boolean satisfiesMergeCriteria(DexProgramClass clazz) {
+ if (appView.appInfo().neverMerge.contains(clazz.type)) {
+ return false;
+ }
if (clazz.accessFlags.isInterface()) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 2824718..df4c786 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -353,7 +353,8 @@
if (appInfo.instantiatedTypes.contains(clazz.type)
|| appInfo.instantiatedLambdas.contains(clazz.type)
|| appInfo.isPinned(clazz.type)
- || pinnedTypes.contains(clazz.type)) {
+ || pinnedTypes.contains(clazz.type)
+ || appInfo.neverMerge.contains(clazz.type)) {
return false;
}
// Note that the property "singleSubtype == null" cannot change during merging, since we visit
diff --git a/src/test/java/com/android/tools/r8/NeverMerge.java b/src/test/java/com/android/tools/r8/NeverMerge.java
new file mode 100644
index 0000000..7fc97b9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/NeverMerge.java
@@ -0,0 +1,6 @@
+// Copyright (c) 2018, 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;
+
+public @interface NeverMerge {}
diff --git a/src/test/java/com/android/tools/r8/naming/b116840216/ReserveOuterClassNameTest.java b/src/test/java/com/android/tools/r8/naming/b116840216/ReserveOuterClassNameTest.java
index b694949..d83c891 100644
--- a/src/test/java/com/android/tools/r8/naming/b116840216/ReserveOuterClassNameTest.java
+++ b/src/test/java/com/android/tools/r8/naming/b116840216/ReserveOuterClassNameTest.java
@@ -10,12 +10,12 @@
import com.android.tools.r8.CompatProguardCommandBuilder;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -27,8 +27,10 @@
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
+@NeverMerge
class Outer {
+ @NeverMerge
static class Inner {
@NeverInline
static void foo() {
@@ -80,12 +82,12 @@
// Note that reproducing b/116840216 relies on the order of following rules that cause
// the visiting of classes during class minification to be Outer$Inner before Outer.
"-keepnames class " + Outer.class.getCanonicalName() + "$Inner",
- keepOuterName ? "-keepnames class " + Outer.class.getCanonicalName() : ""
- ),
+ keepOuterName ? "-keepnames class " + Outer.class.getCanonicalName() : "",
+ "-nevermerge @com.android.tools.r8.NeverMerge class *"),
Origin.unknown());
ToolHelper.allowTestProguardOptions(builder);
- AndroidApp processedApp = ToolHelper.runR8(builder.build(), this::configure);
+ AndroidApp processedApp = ToolHelper.runR8(builder.build());
CodeInspector inspector = new CodeInspector(processedApp);
ClassSubject mainSubject = inspector.clazz(mainClass);
@@ -110,12 +112,6 @@
assertThat(foo, isRenamed());
}
- private void configure(InternalOptions options) {
- // Disable horizontal class merging to avoid that all members of Outer are moved to Outer$Inner
- // or vice versa (both Outer and Outer$Inner are merge candidates for the static class merger).
- options.enableHorizontalClassMerging = false;
- }
-
@Test
public void test_keepOuterName() throws Exception {
runTest(true);
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/A.java b/src/test/java/com/android/tools/r8/shaking/testrules/A.java
index af05562..cf97eeb 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/A.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/A.java
@@ -4,6 +4,9 @@
package com.android.tools.r8.shaking.testrules;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
public class A {
public static int m(int a, int b) {
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/C.java b/src/test/java/com/android/tools/r8/shaking/testrules/C.java
index 5ee4d53..79370a4 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/C.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/C.java
@@ -4,6 +4,9 @@
package com.android.tools.r8.shaking.testrules;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
public class C {
private static int i;
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
index 1b7ebe9..96894df 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
@@ -20,7 +20,6 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
@@ -62,21 +61,17 @@
.addLibraryFiles(library);
ToolHelper.allowTestProguardOptions(builder);
builder.addProguardConfiguration(proguardConfiguration, Origin.unknown());
- return new CodeInspector(ToolHelper.runR8(builder.build(), this::configure));
- }
-
- private void configure(InternalOptions options) {
- // Disable horizontal class merging to prevent that A and C are merged (both classes are
- // candidates for the static class merger).
- options.enableHorizontalClassMerging = false;
+ return new CodeInspector(ToolHelper.runR8(builder.build()));
}
@Test
public void testDefaultInlining() throws Exception {
- CodeInspector inspector = runTest(ImmutableList.of(
- "-keep class **.Main { *; }",
- "-dontobfuscate"
- ));
+ CodeInspector inspector =
+ runTest(
+ ImmutableList.of(
+ "-keep class **.Main { *; }",
+ "-nevermerge @com.android.tools.r8.NeverMerge class *",
+ "-dontobfuscate"));
ClassSubject classA = inspector.clazz(A.class);
ClassSubject classB = inspector.clazz(B.class);
@@ -99,12 +94,14 @@
@Test
public void testNeverInline() throws Exception {
- CodeInspector inspector = runTest(ImmutableList.of(
- "-neverinline class **.A { method(); }",
- "-neverinline class **.B { method(); }",
- "-keep class **.Main { *; }",
- "-dontobfuscate"
- ));
+ CodeInspector inspector =
+ runTest(
+ ImmutableList.of(
+ "-neverinline class **.A { method(); }",
+ "-neverinline class **.B { method(); }",
+ "-keep class **.Main { *; }",
+ "-nevermerge @com.android.tools.r8.NeverMerge class *",
+ "-dontobfuscate"));
ClassSubject classA = inspector.clazz(A.class);
ClassSubject classB = inspector.clazz(B.class);
@@ -124,12 +121,14 @@
@Test
public void testForceInline() throws Exception {
- CodeInspector inspector = runTest(ImmutableList.of(
- "-forceinline class **.A { int m(int, int); }",
- "-forceinline class **.B { int m(int, int); }",
- "-keep class **.Main { *; }",
- "-dontobfuscate"
- ));
+ CodeInspector inspector =
+ runTest(
+ ImmutableList.of(
+ "-forceinline class **.A { int m(int, int); }",
+ "-forceinline class **.B { int m(int, int); }",
+ "-keep class **.Main { *; }",
+ "-nevermerge @com.android.tools.r8.NeverMerge class *",
+ "-dontobfuscate"));
ClassSubject classA = inspector.clazz(A.class);
ClassSubject classB = inspector.clazz(B.class);
@@ -146,11 +145,13 @@
@Test
public void testForceInlineFails() throws Exception {
try {
- CodeInspector inspector = runTest(ImmutableList.of(
- "-forceinline class **.A { int x(); }",
- "-keep class **.Main { *; }",
- "-dontobfuscate"
- ));
+ CodeInspector inspector =
+ runTest(
+ ImmutableList.of(
+ "-forceinline class **.A { int x(); }",
+ "-keep class **.Main { *; }",
+ "-nevermerge @com.android.tools.r8.NeverMerge class *",
+ "-dontobfuscate"));
fail("Force inline of non-inlinable method succeeded");
} catch (Throwable t) {
// Ignore assertion error.