Revert "Unify first and final vertical class merging"
This reverts commit b4a9c8e75bc6bb86efef1f3a606f6e8ecd341136.
Reason for revert: Test failure
Change-Id: I3a785deb69338a4c1afb1aeabeadfa110bf15391
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 3ea094d..65933c8 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -716,14 +716,6 @@
this.kotlinMetadataLens = kotlinMetadataLens;
}
- public boolean hasInitializedClassesInInstanceMethods() {
- return initializedClassesInInstanceMethods != null;
- }
-
- public InitializedClassesInInstanceMethods getInitializedClassesInInstanceMethods() {
- return initializedClassesInInstanceMethods;
- }
-
public void setInitializedClassesInInstanceMethods(
InitializedClassesInInstanceMethods initializedClassesInInstanceMethods) {
this.initializedClassesInInstanceMethods = initializedClassesInInstanceMethods;
@@ -1254,20 +1246,6 @@
public boolean shouldRun() {
return !appView.cfByteCodePassThrough.isEmpty();
}
- },
- new ThreadTask() {
- @Override
- public void run(Timing timing) {
- appView.setInitializedClassesInInstanceMethods(
- appView
- .getInitializedClassesInInstanceMethods()
- .rewrittenWithLens(lens, appliedLens));
- }
-
- @Override
- public boolean shouldRun() {
- return appView.hasInitializedClassesInInstanceMethods();
- }
});
});
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
index ddb64d7..42ca59e 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
@@ -9,11 +9,9 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.EnqueuerWorklist;
-import com.android.tools.r8.utils.MapUtils;
import java.util.IdentityHashMap;
import java.util.Map;
@@ -52,26 +50,6 @@
// transitively.
return !subject.isInterface();
}
-
- public InitializedClassesInInstanceMethods rewrittenWithLens(
- GraphLens lens, GraphLens appliedLens) {
- return new InitializedClassesInInstanceMethods(
- appView,
- MapUtils.transform(
- mapping,
- IdentityHashMap::new,
- key -> {
- DexType rewrittenKey = lens.lookupType(key, appliedLens);
- return rewrittenKey.isPrimitiveType() ? null : rewrittenKey;
- },
- value -> {
- DexType rewrittenValue = lens.lookupType(value, appliedLens);
- return rewrittenValue.isPrimitiveType() ? null : rewrittenValue;
- },
- (key, value, otherValue) ->
- ClassTypeElement.computeLeastUpperBoundOfClasses(
- appView.appInfo(), value, otherValue)));
- }
}
private final AppView<? extends AppInfoWithClassHierarchy> appView;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 13ce76b..b4dbe9f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -87,6 +87,7 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeCustom;
+import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMultiNewArray;
import com.android.tools.r8.ir.code.InvokePolymorphic;
@@ -115,6 +116,7 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.LazyBox;
import com.android.tools.r8.verticalclassmerging.InterfaceTypeToClassTypeLensCodeRewriterHelper;
+import com.android.tools.r8.verticalclassmerging.VerticallyMergedClasses;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -395,6 +397,9 @@
assert false;
continue;
}
+ if (invoke.isInvokeDirect()) {
+ checkInvokeDirect(method.getReference(), invoke.asInvokeDirect());
+ }
MethodLookupResult lensLookup =
graphLens.lookupMethod(
invokedMethod, method.getReference(), invoke.getType(), codeLens);
@@ -1308,6 +1313,54 @@
return TypeElement.getNull();
}
+ @SuppressWarnings("ReferenceEquality")
+ // If the given invoke is on the form "invoke-direct A.<init>, v0, ..." and the definition of
+ // value v0 is "new-instance v0, B", where B is a subtype of A (see the Art800 and B116282409
+ // tests), then fail with a compilation error if A has previously been merged into B.
+ //
+ // The motivation for this is that the vertical class merger cannot easily recognize the above
+ // code pattern, since it runs prior to IR construction. Therefore, we currently allow merging
+ // A and B although this will lead to invalid code, because this code pattern does generally
+ // not occur in practice (it leads to a verification error on the JVM, but not on Art).
+ private void checkInvokeDirect(DexMethod method, InvokeDirect invoke) {
+ VerticallyMergedClasses verticallyMergedClasses = appView.getVerticallyMergedClasses();
+ if (verticallyMergedClasses == null) {
+ // No need to check the invocation.
+ return;
+ }
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ if (invokedMethod.name != factory.constructorMethodName) {
+ // Not a constructor call.
+ return;
+ }
+ if (invoke.arguments().isEmpty()) {
+ // The new instance should always be passed to the constructor call, but continue gracefully.
+ return;
+ }
+ Value receiver = invoke.arguments().get(0);
+ if (!receiver.isPhi() && receiver.definition.isNewInstance()) {
+ NewInstance newInstance = receiver.definition.asNewInstance();
+ if (newInstance.clazz != invokedMethod.holder
+ && verticallyMergedClasses.hasBeenMergedIntoSubtype(invokedMethod.holder)) {
+ // Generated code will not work. Fail with a compilation error.
+ throw appView
+ .options()
+ .reporter
+ .fatalError(
+ String.format(
+ "Unable to rewrite `invoke-direct %s.<init>(new %s, ...)` in method `%s` after "
+ + "type `%s` was merged into `%s`. Please add the following rule to your "
+ + "Proguard configuration file: `-keep,allowobfuscation class %s`.",
+ invokedMethod.holder.toSourceString(),
+ newInstance.clazz,
+ method.toSourceString(),
+ invokedMethod.holder,
+ verticallyMergedClasses.getTargetFor(invokedMethod.holder),
+ invokedMethod.holder.toSourceString()));
+ }
+ }
+ }
+
/**
* Due to class merging, it is possible that two exception classes have been merged into one. This
* function removes catch handlers where the guards ended up being the same as a previous one.
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index 3ae9270..1e63e51 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -33,13 +33,13 @@
import com.android.tools.r8.shaking.KeepReason.ReflectiveUseFrom;
import com.android.tools.r8.utils.DequeUtils;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
+import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
@@ -532,12 +532,14 @@
}
if (rule instanceof ProguardIfRule) {
ProguardIfRule ifRule = (ProguardIfRule) rule;
- assert ifRule.getPrecondition() != null;
+ assert !ifRule.getPreconditions().isEmpty();
return ruleNodes.computeIfAbsent(
ifRule,
key -> {
- Set<GraphNode> preconditions =
- SetUtils.newHashSet(getGraphNode(ifRule.getPrecondition().getReference()));
+ Set<GraphNode> preconditions = new HashSet<>(ifRule.getPreconditions().size());
+ for (DexReference condition : ifRule.getPreconditions()) {
+ preconditions.add(getGraphNode(condition));
+ }
return new KeepRuleGraphNode(ifRule, preconditions);
});
}
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index 44ba57d..32ec312 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
@@ -12,14 +14,17 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.shaking.InlineRule.InlineRuleType;
+import com.android.tools.r8.shaking.InlineRule.Type;
import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSet;
import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
import com.android.tools.r8.threading.TaskCollection;
import com.android.tools.r8.utils.InternalOptions.TestingOptions.ProguardIfRuleEvaluationData;
import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.ArrayList;
@@ -93,12 +98,48 @@
if (appView.options().testing.measureProguardIfRuleEvaluations) {
ifRuleEvaluationData.numberOfProguardIfRuleMemberEvaluations++;
}
- boolean matched = evaluateIfRuleMembersAndMaterialize(ifRule, clazz);
+ boolean matched = evaluateIfRuleMembersAndMaterialize(ifRule, clazz, clazz);
if (matched && canRemoveSubsequentKeepRule(ifRule)) {
toRemove.add(ifRule);
}
}
}
+
+ // Check if one of the types that have been merged into `clazz` satisfies the if-rule.
+ if (appView.getVerticallyMergedClasses() != null) {
+ Iterable<DexType> sources =
+ appView.getVerticallyMergedClasses().getSourcesFor(clazz.type);
+ for (DexType sourceType : sources) {
+ // Note that, although `sourceType` has been merged into `type`, the dex class for
+ // `sourceType` is still available until the second round of tree shaking. This
+ // way we can still retrieve the access flags of `sourceType`.
+ DexProgramClass sourceClass =
+ asProgramClassOrNull(
+ appView.appInfo().definitionForWithoutExistenceAssert(sourceType));
+ if (sourceClass == null) {
+ // TODO(b/266049507): The evaluation of -if rules in the final round of tree
+ // shaking and during -whyareyoukeeping should be the same. Currently the pruning
+ // of classes changes behavior.
+ assert enqueuer.getMode().isWhyAreYouKeeping();
+ continue;
+ }
+ if (appView.options().testing.measureProguardIfRuleEvaluations) {
+ ifRuleEvaluationData.numberOfProguardIfRuleClassEvaluations++;
+ }
+ if (evaluateClassForIfRule(ifRuleKey, sourceClass)) {
+ for (ProguardIfRule ifRule : ifRulesInEquivalence) {
+ registerClassCapture(ifRule, sourceClass, clazz);
+ if (appView.options().testing.measureProguardIfRuleEvaluations) {
+ ifRuleEvaluationData.numberOfProguardIfRuleMemberEvaluations++;
+ }
+ if (evaluateIfRuleMembersAndMaterialize(ifRule, sourceClass, clazz)
+ && canRemoveSubsequentKeepRule(ifRule)) {
+ toRemove.add(ifRule);
+ }
+ }
+ }
+ }
+ }
}
if (ifRulesInEquivalence.size() == toRemove.size()) {
it.remove();
@@ -162,11 +203,12 @@
return true;
}
- private boolean evaluateIfRuleMembersAndMaterialize(ProguardIfRule rule, DexProgramClass clazz)
- throws ExecutionException {
+ @SuppressWarnings("ReferenceEquality")
+ private boolean evaluateIfRuleMembersAndMaterialize(
+ ProguardIfRule rule, DexClass sourceClass, DexClass targetClass) throws ExecutionException {
Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
if (memberKeepRules.isEmpty()) {
- materializeIfRule(rule, clazz);
+ materializeIfRule(rule, ImmutableSet.of(sourceClass.getReference()));
return true;
}
@@ -174,12 +216,12 @@
Set<DexDefinition> filteredMembers = Sets.newIdentityHashSet();
Iterables.addAll(
filteredMembers,
- clazz.fields(
+ targetClass.fields(
f -> {
// Fields that are javac inlined are unsound as predicates for conditional rules.
// Ignore any such field members and record it for possible reporting later.
if (isFieldInlinedByJavaC(f)) {
- fieldsInlinedByJavaC.add(DexClassAndField.create(clazz, f));
+ fieldsInlinedByJavaC.add(DexClassAndField.create(targetClass, f));
return false;
}
// Fields referenced only by -keep may not be referenced, we therefore have to
@@ -187,24 +229,18 @@
return (enqueuer.isFieldLive(f)
|| enqueuer.isFieldReferenced(f)
|| f.getOptimizationInfo().valueHasBeenPropagated())
- && appView
- .graphLens()
- .getOriginalFieldSignature(f.getReference())
- .getHolderType()
- .isIdenticalTo(clazz.getType());
+ && (appView.graphLens().getOriginalFieldSignature(f.getReference()).holder
+ == sourceClass.type);
}));
Iterables.addAll(
filteredMembers,
- clazz.methods(
+ targetClass.methods(
m ->
(enqueuer.isMethodLive(m)
|| enqueuer.isMethodTargeted(m)
|| m.getOptimizationInfo().returnValueHasBeenPropagated())
- && appView
- .graphLens()
- .getOriginalMethodSignature(m.getReference())
- .getHolderType()
- .isIdenticalTo(clazz.getType())));
+ && appView.graphLens().getOriginalMethodSignature(m.getReference()).holder
+ == sourceClass.type));
// Check if the rule could hypothetically have matched a javac inlined field.
// If so mark the rule. Reporting happens only if the rule is otherwise unused.
@@ -235,11 +271,11 @@
Sets.combinations(filteredMembers, memberKeepRules.size())) {
Collection<DexClassAndField> fieldsInCombination =
DexDefinition.filterDexEncodedField(
- combination.stream(), field -> DexClassAndField.create(clazz, field))
+ combination.stream(), field -> DexClassAndField.create(targetClass, field))
.collect(Collectors.toList());
Collection<DexClassAndMethod> methodsInCombination =
DexDefinition.filterDexEncodedMethod(
- combination.stream(), method -> DexClassAndMethod.create(clazz, method))
+ combination.stream(), method -> DexClassAndMethod.create(targetClass, method))
.collect(Collectors.toList());
// Member rules are combined as AND logic: if found unsatisfied member rule, this
// combination of live members is not a good fit.
@@ -251,7 +287,7 @@
|| rootSetBuilder.ruleSatisfiedByMethods(
memberRule, methodsInCombination));
if (satisfied) {
- materializeIfRule(rule, clazz);
+ materializeIfRule(rule, ImmutableSet.of(sourceClass.getReference()));
if (canRemoveSubsequentKeepRule(rule)) {
return true;
}
@@ -269,17 +305,25 @@
return field.getOrComputeIsInlinableByJavaC(appView.dexItemFactory());
}
- private void materializeIfRule(ProguardIfRule rule, DexProgramClass precondition)
+ @SuppressWarnings("BadImport")
+ private void materializeIfRule(ProguardIfRule rule, Set<DexReference> preconditions)
throws ExecutionException {
DexItemFactory dexItemFactory = appView.dexItemFactory();
- ProguardIfRule materializedRule = rule.materialize(dexItemFactory, precondition);
+ ProguardIfRule materializedRule = rule.materialize(dexItemFactory, preconditions);
if (enqueuer.getMode().isInitialTreeShaking()
&& !rule.isUsed()
&& !rule.isTrivalAllClassMatch()) {
+ // We need to abort class inlining of classes that could be matched by the condition of this
+ // -if rule.
+ ClassInlineRule neverClassInlineRuleForCondition =
+ materializedRule.neverClassInlineRuleForCondition(dexItemFactory);
+ if (neverClassInlineRuleForCondition != null) {
+ rootSetBuilder.runPerRule(tasks, neverClassInlineRuleForCondition, null);
+ }
+
InlineRule neverInlineForClassInliningRuleForCondition =
- materializedRule.neverInlineRuleForCondition(
- dexItemFactory, InlineRuleType.NEVER_CLASS_INLINE);
+ materializedRule.neverInlineRuleForCondition(dexItemFactory, Type.NEVER_CLASS_INLINE);
if (neverInlineForClassInliningRuleForCondition != null) {
rootSetBuilder.runPerRule(tasks, neverInlineForClassInliningRuleForCondition, null);
}
@@ -288,18 +332,17 @@
// ensure that the subsequent rule will be applied again in the second round of tree
// shaking.
InlineRule neverInlineRuleForCondition =
- materializedRule.neverInlineRuleForCondition(dexItemFactory, InlineRuleType.NEVER);
+ materializedRule.neverInlineRuleForCondition(dexItemFactory, Type.NEVER);
if (neverInlineRuleForCondition != null) {
rootSetBuilder.runPerRule(tasks, neverInlineRuleForCondition, null);
}
- rootSetBuilder
- .getDependentMinimumKeepInfo()
- .getOrCreateUnconditionalMinimumKeepInfoFor(precondition.getType())
- .asClassJoiner()
- .disallowClassInlining()
- .disallowHorizontalClassMerging()
- .disallowVerticalClassMerging();
+ // Prevent horizontal class merging of any -if rule members.
+ NoHorizontalClassMergingRule noHorizontalClassMergingRule =
+ materializedRule.noHorizontalClassMergingRuleForCondition(dexItemFactory);
+ if (noHorizontalClassMergingRule != null) {
+ rootSetBuilder.runPerRule(tasks, noHorizontalClassMergingRule, null);
+ }
}
// Keep whatever is required by the -if rule.
diff --git a/src/main/java/com/android/tools/r8/shaking/InlineRule.java b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
index 4949e5a..cc813ad 100644
--- a/src/main/java/com/android/tools/r8/shaking/InlineRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
@@ -10,7 +10,7 @@
public class InlineRule extends ProguardConfigurationRule {
- public enum InlineRuleType {
+ public enum Type {
ALWAYS,
NEVER,
NEVER_CLASS_INLINE,
@@ -24,14 +24,14 @@
super();
}
- InlineRuleType type;
+ Type type;
@Override
public Builder self() {
return this;
}
- public Builder setType(InlineRuleType type) {
+ public Builder setType(Type type) {
this.type = type;
return this;
}
@@ -56,7 +56,7 @@
}
}
- private final InlineRuleType type;
+ private final Type type;
protected InlineRule(
Origin origin,
@@ -72,7 +72,7 @@
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules,
- InlineRuleType type) {
+ Type type) {
super(
origin,
position,
@@ -94,7 +94,7 @@
return new Builder();
}
- public InlineRuleType getType() {
+ public Type getType() {
return type;
}
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 b2dcea5..6ae7d4c 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -18,7 +18,6 @@
import com.android.tools.r8.position.Position;
import com.android.tools.r8.position.TextPosition;
import com.android.tools.r8.position.TextRange;
-import com.android.tools.r8.shaking.InlineRule.InlineRuleType;
import com.android.tools.r8.shaking.ProguardConfiguration.Builder;
import com.android.tools.r8.shaking.ProguardTypeMatcher.ClassOrType;
import com.android.tools.r8.shaking.ProguardWildcard.BackReference;
@@ -465,7 +464,7 @@
} else if (acceptString("alwaysinline")) {
InlineRule rule =
parseRuleWithClassSpec(
- optionStart, InlineRule.builder().setType(InlineRuleType.ALWAYS));
+ optionStart, InlineRule.builder().setType(InlineRule.Type.ALWAYS));
configurationBuilder.addRule(rule);
} else if (acceptString("adaptclassstrings")) {
parseClassFilter(configurationBuilder::addAdaptClassStringsPattern);
@@ -567,14 +566,14 @@
if (acceptString("neverinline")) {
InlineRule rule =
parseRuleWithClassSpec(
- optionStart, InlineRule.builder().setType(InlineRuleType.NEVER));
+ optionStart, InlineRule.builder().setType(InlineRule.Type.NEVER));
configurationBuilder.addRule(rule);
return true;
}
if (acceptString("neversinglecallerinline")) {
InlineRule rule =
parseRuleWithClassSpec(
- optionStart, InlineRule.builder().setType(InlineRuleType.NEVER_SINGLE_CALLER));
+ optionStart, InlineRule.builder().setType(InlineRule.Type.NEVER_SINGLE_CALLER));
configurationBuilder.addRule(rule);
return true;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index cb3757c..2279a8e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -5,10 +5,9 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
-import com.android.tools.r8.shaking.InlineRule.InlineRuleType;
import com.google.common.collect.Iterables;
import java.util.List;
import java.util.Map;
@@ -26,14 +25,21 @@
}
};
- private final DexProgramClass precondition;
+ private static final Origin NO_HORIZONTAL_CLASS_MERGING_ORIGIN =
+ new Origin(Origin.root()) {
+ @Override
+ public String part() {
+ return "<SYNTHETIC_NO_HORIZONTAL_CLASS_MERGING_RULE>";
+ }
+ };
+
+ private final Set<DexReference> preconditions;
final ProguardKeepRule subsequentRule;
private Map<DexField, DexField> inlinableFieldsInPrecondition = new ConcurrentHashMap<>();
- public DexProgramClass getPrecondition() {
- assert precondition != null;
- return precondition;
+ public Set<DexReference> getPreconditions() {
+ return preconditions;
}
public ProguardKeepRule getSubsequentRule() {
@@ -106,7 +112,7 @@
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules,
ProguardKeepRule subsequentRule,
- DexProgramClass precondition) {
+ Set<DexReference> preconditions) {
super(
origin,
position,
@@ -124,7 +130,7 @@
ProguardKeepRuleType.CONDITIONAL,
ProguardKeepRuleModifiers.builder().build());
this.subsequentRule = subsequentRule;
- this.precondition = precondition;
+ this.preconditions = preconditions;
}
public static Builder builder() {
@@ -147,7 +153,7 @@
}
protected ProguardIfRule materialize(
- DexItemFactory dexItemFactory, DexProgramClass precondition) {
+ DexItemFactory dexItemFactory, Set<DexReference> preconditions) {
return new ProguardIfRule(
getOrigin(),
getPosition(),
@@ -169,7 +175,27 @@
.map(memberRule -> memberRule.materialize(dexItemFactory))
.collect(Collectors.toList()),
subsequentRule.materialize(dexItemFactory),
- precondition);
+ preconditions);
+ }
+
+ protected ClassInlineRule neverClassInlineRuleForCondition(DexItemFactory dexItemFactory) {
+ return new ClassInlineRule(
+ NEVER_INLINE_ORIGIN,
+ Position.UNKNOWN,
+ null,
+ ProguardTypeMatcher.materializeList(getClassAnnotations(), dexItemFactory),
+ getClassAccessFlags(),
+ getNegatedClassAccessFlags(),
+ getClassTypeNegated(),
+ getClassType(),
+ getClassNames().materialize(dexItemFactory),
+ ProguardTypeMatcher.materializeList(getInheritanceAnnotations(), dexItemFactory),
+ getInheritanceClassName() == null
+ ? null
+ : getInheritanceClassName().materialize(dexItemFactory),
+ getInheritanceIsExtends(),
+ getMemberRules(),
+ ClassInlineRule.Type.NEVER);
}
/**
@@ -195,7 +221,7 @@
* -neverinline rule for the condition of the -if rule.
*/
protected InlineRule neverInlineRuleForCondition(
- DexItemFactory dexItemFactory, InlineRuleType type) {
+ DexItemFactory dexItemFactory, InlineRule.Type type) {
if (getMemberRules() == null || getMemberRules().isEmpty()) {
return null;
}
@@ -221,6 +247,36 @@
type);
}
+ protected NoHorizontalClassMergingRule noHorizontalClassMergingRuleForCondition(
+ DexItemFactory dexItemFactory) {
+ List<ProguardMemberRule> memberRules = null;
+ if (getMemberRules() != null) {
+ memberRules =
+ getMemberRules().stream()
+ .map(memberRule -> memberRule.materialize(dexItemFactory))
+ .collect(Collectors.toList());
+ }
+
+ return NoHorizontalClassMergingRule.builder()
+ .setOrigin(NO_HORIZONTAL_CLASS_MERGING_ORIGIN)
+ .addClassAnnotations(
+ ProguardTypeMatcher.materializeList(getClassAnnotations(), dexItemFactory))
+ .setClassAccessFlags(getClassAccessFlags())
+ .setNegatedClassAccessFlags(getNegatedClassAccessFlags())
+ .setClassType(getClassType())
+ .setClassTypeNegated(getClassTypeNegated())
+ .setClassNames(getClassNames().materialize(dexItemFactory))
+ .addInheritanceAnnotations(
+ ProguardTypeMatcher.materializeList(getInheritanceAnnotations(), dexItemFactory))
+ .setInheritanceClassName(
+ getInheritanceClassName() == null
+ ? null
+ : getInheritanceClassName().materialize(dexItemFactory))
+ .setInheritanceIsExtends(getInheritanceIsExtends())
+ .setMemberRules(memberRules)
+ .build();
+ }
+
@Override
public boolean equals(Object o) {
if (!(o instanceof ProguardIfRule)) {
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 9d30ea3..cecee47 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -173,10 +173,6 @@
null);
}
- public DependentMinimumKeepInfoCollection getDependentMinimumKeepInfo() {
- return dependentMinimumKeepInfo;
- }
-
boolean isMainDexRootSetBuilder() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index c991a7d..ed49f66 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -122,9 +122,6 @@
return;
}
V2 newValue = valueMapping.apply(key, value);
- if (newValue == null) {
- return;
- }
V2 existingValue = result.put(newKey, newValue);
if (existingValue != null) {
result.put(newKey, valueMerger.apply(newKey, existingValue, newValue));
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/IncompleteVerticalClassMergerBridgeCode.java b/src/main/java/com/android/tools/r8/verticalclassmerging/IncompleteVerticalClassMergerBridgeCode.java
index f852310..d8856f9 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/IncompleteVerticalClassMergerBridgeCode.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/IncompleteVerticalClassMergerBridgeCode.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InvokeType;
@@ -33,7 +34,7 @@
/**
* A short-lived piece of code that will be converted into {@link LirCode} using {@link
- * #toLirCode(AppView, VerticalClassMergerGraphLens, ClassMergerMode)}.
+ * #toLirCode(AppView)}.
*/
public class IncompleteVerticalClassMergerBridgeCode extends Code {
@@ -86,7 +87,10 @@
method = lens.getNextBridgeMethodSignature(method);
}
- public LirCode<?> toLirCode(AppView<AppInfoWithLiveness> appView) {
+ public LirCode<?> toLirCode(
+ AppView<AppInfoWithLiveness> appView,
+ VerticalClassMergerGraphLens lens,
+ ClassMergerMode mode) {
boolean isD8R8Synthesized = true;
LirEncodingStrategy<Value, Integer> strategy =
LirStrategy.getDefaultStrategy().getEncodingStrategy();
@@ -124,7 +128,21 @@
lirBuilder.addReturn(returnValue);
}
- return lirBuilder.build();
+ LirCode<Integer> lirCode = lirBuilder.build();
+ return mode.isFinal()
+ ? lirCode
+ : new LirCode<>(lirCode) {
+
+ @Override
+ public boolean hasExplicitCodeLens() {
+ return true;
+ }
+
+ @Override
+ public GraphLens getCodeLens(AppView<?> appView) {
+ return lens;
+ }
+ };
}
// Implement Code.
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
index 6772ced..943f954 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
@@ -143,11 +143,10 @@
rewriteCodeWithLens(executorService, timing);
// Remove merged classes from app now that the code is fully rewritten.
- removeMergedClasses(
- verticalClassMergerResult.getVerticallyMergedClasses(), executorService, timing);
+ removeMergedClasses(verticalClassMergerResult.getVerticallyMergedClasses(), timing);
// Convert the (incomplete) synthesized bridges to LIR.
- finalizeSynthesizedBridges(verticalClassMergerResult.getSynthesizedBridges(), timing);
+ finalizeSynthesizedBridges(verticalClassMergerResult.getSynthesizedBridges(), lens, timing);
// Finally update the code lens to signal that the code is fully up to date.
markRewrittenWithLens(executorService, timing);
@@ -248,6 +247,9 @@
private void rewriteCodeWithLens(ExecutorService executorService, Timing timing)
throws ExecutionException {
+ if (mode.isInitial()) {
+ return;
+ }
LirConverter.rewriteLirWithLens(appView, timing, executorService);
new IdentifierMinifier(appView).rewriteDexItemBasedConstStringInStaticFields(executorService);
}
@@ -290,14 +292,11 @@
timing.end();
}
- private void removeMergedClasses(
- VerticallyMergedClasses verticallyMergedClasses,
- ExecutorService executorService,
- Timing timing)
- throws ExecutionException {
+ private void removeMergedClasses(VerticallyMergedClasses verticallyMergedClasses, Timing timing) {
if (mode.isInitial()) {
return;
}
+
timing.begin("Remove merged classes");
DirectMappedDexApplication newApplication =
appView
@@ -306,17 +305,13 @@
.builder()
.removeProgramClasses(clazz -> verticallyMergedClasses.isMergeSource(clazz.getType()))
.build();
- PrunedItems prunedItems =
- PrunedItems.builder()
- .addRemovedClasses(verticallyMergedClasses.getSources())
- .setPrunedApp(newApplication)
- .build();
- appView.setAppInfo(appView.appInfo().prunedCopyFrom(prunedItems, executorService, timing));
+ appView.setAppInfo(appView.appInfo().rebuildWithLiveness(newApplication));
timing.end();
}
private void finalizeSynthesizedBridges(
List<IncompleteVerticalClassMergerBridgeCode> bridges,
+ VerticalClassMergerGraphLens lens,
Timing timing) {
timing.begin("Finalize synthesized bridges");
KeepInfoCollection keepInfo = appView.getKeepInfo();
@@ -328,7 +323,7 @@
assert target != null;
// Finalize code.
- bridge.setCode(code.toLirCode(appView), appView);
+ bridge.setCode(code.toLirCode(appView, lens, mode), appView);
// Copy keep info to newly synthesized methods.
keepInfo.mutate(
@@ -340,6 +335,9 @@
private void markRewrittenWithLens(ExecutorService executorService, Timing timing)
throws ExecutionException {
+ if (mode.isInitial()) {
+ return;
+ }
timing.begin("Mark rewritten with lens");
appView.clearCodeRewritings(executorService, timing);
timing.end();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java b/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java
index 0031854..6876898 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java
@@ -4,20 +4,24 @@
package com.android.tools.r8.ir.optimize;
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertFalse;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
import com.android.tools.r8.jasmin.JasminTestBase;
+import com.android.tools.r8.utils.AbortException;
import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import java.util.stream.Collectors;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
@@ -101,36 +105,79 @@
@Test
public void testR8() throws Exception {
- testForR8(parameters.getBackend())
- .addProgramClassFileData(programClassFileData)
- .addKeepMainRule("TestClass")
- .addOptionsModification(
- options ->
- options.getVerticalClassMergerOptions().setEnabled(enableVerticalClassMerging))
- .allowDiagnosticWarningMessages()
- .setMinApi(parameters)
- .compileWithExpectedDiagnostics(
- diagnostics ->
- diagnostics.assertWarningsMatch(
- allOf(
- diagnosticType(UnverifiableCfCodeDiagnostic.class),
- diagnosticMessage(
- containsString(
- "Constructor mismatch, expected constructor from B, but was"
- + " A.<init>()")))))
+ if (enableVerticalClassMerging) {
+ exception.expect(CompilationFailedException.class);
+ exception.expectCause(
+ new CustomExceptionMatcher(
+ "Unable to rewrite `invoke-direct A.<init>(new B, ...)` in method "
+ + "`void TestClass.main(java.lang.String[])` after type `A` was merged into `B`.",
+ "Please add the following rule to your Proguard configuration file: "
+ + "`-keep,allowobfuscation class A`."));
+ }
+
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(programClassFileData)
+ .addKeepMainRule("TestClass")
+ .addOptionsModification(
+ options ->
+ options.getVerticalClassMergerOptions().setEnabled(enableVerticalClassMerging))
+ .allowDiagnosticWarningMessages()
+ .setMinApi(parameters)
+ .compile();
+
+ assertFalse(enableVerticalClassMerging);
+
+ compileResult
.run(parameters.getRuntime(), "TestClass")
.applyIf(
- (parameters.isCfRuntime() || parameters.getDexRuntimeVersion().isDalvik())
- && !enableVerticalClassMerging,
+ parameters.isCfRuntime(),
runResult ->
runResult
.assertFailureWithErrorThatThrows(VerifyError.class)
.assertFailureWithErrorThatMatches(
- containsString(
- parameters.isCfRuntime()
- ? "Call to wrong initialization method"
- : "VFY: invoke-direct <init> on super only allowed for 'this' in"
- + " <init>")),
- runResult -> runResult.assertSuccessWithOutputLines("In A.<init>()", "42"));
+ containsString("Call to wrong initialization method")),
+ runResult -> {
+ if (parameters.getDexRuntimeVersion().isDalvik()) {
+ runResult.assertFailureWithErrorThatMatches(
+ containsString(
+ "VFY: invoke-direct <init> on super only allowed for 'this' in <init>"));
+ } else {
+ runResult.assertSuccessWithOutputLines("In A.<init>()", "42");
+ }
+ });
+ }
+
+ private static class CustomExceptionMatcher extends BaseMatcher<Throwable> {
+
+ private List<String> messages;
+
+ public CustomExceptionMatcher(String... messages) {
+ this.messages = Arrays.asList(messages);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description
+ .appendText("a string containing ")
+ .appendText(
+ String.join(
+ ", ", messages.stream().map(m -> "\"" + m + "\"").collect(Collectors.toList())));
+ }
+
+ @Override
+ public boolean matches(Object o) {
+ if (o instanceof AbortException) {
+ AbortException exception = (AbortException) o;
+ if (exception.getMessage() != null
+ && messages.stream().allMatch(message -> exception.getMessage().contains(message))) {
+ return true;
+ }
+ if (exception.getCause() != null) {
+ return matches(exception.getCause());
+ }
+ }
+ return false;
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/CheckDiscardedFailuresWithIfRulesAndVerticalClassMergingTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/CheckDiscardedFailuresWithIfRulesAndVerticalClassMergingTest.java
index 175b905..d422118 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/CheckDiscardedFailuresWithIfRulesAndVerticalClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/CheckDiscardedFailuresWithIfRulesAndVerticalClassMergingTest.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NoVerticalClassMerging;
@@ -15,7 +16,6 @@
import com.android.tools.r8.errors.CheckDiscardDiagnostic;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.AssertUtils;
-import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -55,7 +55,13 @@
"-if class " + A.class.getTypeName(), "-keep class " + C.class.getTypeName())
.addNoVerticalClassMergingAnnotations()
.addVerticallyMergedClassesInspector(
- VerticallyMergedClassesInspector::assertNoClassesMerged)
+ inspector -> {
+ if (enableVerticalClassMerging) {
+ inspector.assertMergedIntoSubtype(A.class);
+ } else {
+ inspector.assertNoClassesMerged();
+ }
+ })
// Intentionally fail compilation due to -checkdiscard. This triggers the
// (re)running of the Enqueuer after the final round of tree shaking, for generating
// -whyareyoukeeping output.
@@ -75,7 +81,14 @@
diagnostics.assertNoMessages();
}
})
- .inspect(inspector -> assertThat(inspector.clazz(C.class), isPresent()))
+ // TODO(b/266049507): It is questionable not to keep C when vertical class merging
+ // is enabled. A simple fix is to disable vertical class merging of classes matched
+ // by the -if condition.
+ .inspect(
+ inspector ->
+ assertThat(
+ inspector.clazz(C.class),
+ notIf(isPresent(), enableVerticalClassMerging)))
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("B"));
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
index 3247e60..f28c2a5 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
@@ -6,7 +6,9 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NoAccessModification;
@@ -87,11 +89,6 @@
public void configure(R8FullTestBuilder builder) {
super.configure(builder);
builder
- .addVerticallyMergedClassesInspector(
- inspector ->
- inspector
- .applyIf(enableVerticalClassMerging, i -> i.assertMergedIntoSubtype(A.class))
- .assertNoOtherClassesMerged())
.enableNeverClassInliningAnnotations()
.enableNoRedundantFieldLoadEliminationAnnotations();
}
@@ -119,13 +116,16 @@
assertThat(testClassSubject, isPresent());
if (enableVerticalClassMerging) {
+ // Verify that SuperTestClass has been merged into TestClass.
+ assertThat(inspector.clazz(SuperTestClass.class), not(isPresent()));
+ assertEquals(
+ "java.lang.Object", testClassSubject.getDexProgramClass().superType.toSourceString());
+
// Verify that TestClass.field has been removed.
assertEquals(1, testClassSubject.allFields().size());
- // Due to the -if rule, the SuperTestClass is only merged into TestClass after the final
- // round of tree shaking, at which point TestClass.field has already been removed.
- // Therefore, we expect no collision to have happened.
- assertEquals("field", testClassSubject.allFields().get(0).getFinalName());
+ // Verify that there was a naming conflict such that SuperTestClass.field was renamed.
+ assertNotEquals("field", testClassSubject.allFields().get(0).getFinalName());
}
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
index 1d9d03b..aa46e96 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
@@ -8,6 +8,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import com.android.tools.r8.NoAccessModification;
import com.android.tools.r8.NoHorizontalClassMerging;
@@ -86,14 +87,7 @@
@Override
public void configure(R8FullTestBuilder builder) {
super.configure(builder);
- builder
- .addVerticallyMergedClassesInspector(
- inspector ->
- inspector
- .applyIf(enableVerticalClassMerging, i -> i.assertMergedIntoSubtype(A.class))
- .assertNoOtherClassesMerged())
- .enableInliningAnnotations()
- .enableNoHorizontalClassMergingAnnotations();
+ builder.enableNoHorizontalClassMergingAnnotations();
}
@Override
@@ -131,10 +125,8 @@
.collect(Collectors.toList());
assertEquals(1, methods.size());
- // Due to the -if rule, the SuperTestClass is only merged into TestClass after the final
- // round of tree shaking, at which point TestClass.method has already been removed.
- // Therefore, we expect no collision to have happened.
- assertEquals("method", methods.get(0).getFinalName());
+ // Verify that there was a naming conflict such that SuperTestClass.method was renamed.
+ assertNotEquals("method", methods.get(0).getFinalName());
}
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java
index 93c9dec..9e98e16 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java
@@ -8,6 +8,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import com.android.tools.r8.AssumeMayHaveSideEffects;
import com.android.tools.r8.NoAccessModification;
@@ -95,15 +96,7 @@
@Override
public void configure(R8FullTestBuilder builder) {
super.configure(builder);
- builder
- .addVerticallyMergedClassesInspector(
- inspector ->
- inspector
- .applyIf(enableVerticalClassMerging, i -> i.assertMergedIntoSubtype(A.class))
- .assertNoOtherClassesMerged())
- .enableInliningAnnotations()
- .enableNoHorizontalClassMergingAnnotations()
- .enableSideEffectAnnotations();
+ builder.enableNoHorizontalClassMergingAnnotations().enableSideEffectAnnotations();
}
@Override
@@ -141,10 +134,8 @@
.collect(Collectors.toList());
assertEquals(1, methods.size());
- // Due to the -if rule, the SuperTestClass is only merged into TestClass after the final
- // round of tree shaking, at which point TestClass.method has already been removed.
- // Therefore, we expect no collision to have happened.
- assertEquals("method", methods.get(0).getFinalName());
+ // Verify that there was a naming conflict such that SuperTestClass.method was renamed.
+ assertNotEquals("method", methods.get(0).getFinalName());
}
}
}