Reland "Unify first and final vertical class merging"
This reverts commit a2b282df67c4540b245b9347bd0db6ebcb07f17b.
Change-Id: I768778adc984b6b7bdfc7f46de74f7b403492f51
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 65933c8..3ea094d 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -716,6 +716,14 @@
this.kotlinMetadataLens = kotlinMetadataLens;
}
+ public boolean hasInitializedClassesInInstanceMethods() {
+ return initializedClassesInInstanceMethods != null;
+ }
+
+ public InitializedClassesInInstanceMethods getInitializedClassesInInstanceMethods() {
+ return initializedClassesInInstanceMethods;
+ }
+
public void setInitializedClassesInInstanceMethods(
InitializedClassesInInstanceMethods initializedClassesInInstanceMethods) {
this.initializedClassesInInstanceMethods = initializedClassesInInstanceMethods;
@@ -1246,6 +1254,20 @@
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 42ca59e..ddb64d7 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,9 +9,11 @@
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;
@@ -50,6 +52,26 @@
// 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 b4dbe9f..13ce76b 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,7 +87,6 @@
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;
@@ -116,7 +115,6 @@
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;
@@ -397,9 +395,6 @@
assert false;
continue;
}
- if (invoke.isInvokeDirect()) {
- checkInvokeDirect(method.getReference(), invoke.asInvokeDirect());
- }
MethodLookupResult lensLookup =
graphLens.lookupMethod(
invokedMethod, method.getReference(), invoke.getType(), codeLens);
@@ -1313,54 +1308,6 @@
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 1e63e51..3ae9270 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,14 +532,12 @@
}
if (rule instanceof ProguardIfRule) {
ProguardIfRule ifRule = (ProguardIfRule) rule;
- assert !ifRule.getPreconditions().isEmpty();
+ assert ifRule.getPrecondition() != null;
return ruleNodes.computeIfAbsent(
ifRule,
key -> {
- Set<GraphNode> preconditions = new HashSet<>(ifRule.getPreconditions().size());
- for (DexReference condition : ifRule.getPreconditions()) {
- preconditions.add(getGraphNode(condition));
- }
+ Set<GraphNode> preconditions =
+ SetUtils.newHashSet(getGraphNode(ifRule.getPrecondition().getReference()));
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 32ec312..44ba57d 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -3,8 +3,6 @@
// 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;
@@ -14,17 +12,14 @@
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.Type;
+import com.android.tools.r8.shaking.InlineRule.InlineRuleType;
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;
@@ -98,48 +93,12 @@
if (appView.options().testing.measureProguardIfRuleEvaluations) {
ifRuleEvaluationData.numberOfProguardIfRuleMemberEvaluations++;
}
- boolean matched = evaluateIfRuleMembersAndMaterialize(ifRule, clazz, clazz);
+ boolean matched = evaluateIfRuleMembersAndMaterialize(ifRule, 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();
@@ -203,12 +162,11 @@
return true;
}
- @SuppressWarnings("ReferenceEquality")
- private boolean evaluateIfRuleMembersAndMaterialize(
- ProguardIfRule rule, DexClass sourceClass, DexClass targetClass) throws ExecutionException {
+ private boolean evaluateIfRuleMembersAndMaterialize(ProguardIfRule rule, DexProgramClass clazz)
+ throws ExecutionException {
Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
if (memberKeepRules.isEmpty()) {
- materializeIfRule(rule, ImmutableSet.of(sourceClass.getReference()));
+ materializeIfRule(rule, clazz);
return true;
}
@@ -216,12 +174,12 @@
Set<DexDefinition> filteredMembers = Sets.newIdentityHashSet();
Iterables.addAll(
filteredMembers,
- targetClass.fields(
+ clazz.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(targetClass, f));
+ fieldsInlinedByJavaC.add(DexClassAndField.create(clazz, f));
return false;
}
// Fields referenced only by -keep may not be referenced, we therefore have to
@@ -229,18 +187,24 @@
return (enqueuer.isFieldLive(f)
|| enqueuer.isFieldReferenced(f)
|| f.getOptimizationInfo().valueHasBeenPropagated())
- && (appView.graphLens().getOriginalFieldSignature(f.getReference()).holder
- == sourceClass.type);
+ && appView
+ .graphLens()
+ .getOriginalFieldSignature(f.getReference())
+ .getHolderType()
+ .isIdenticalTo(clazz.getType());
}));
Iterables.addAll(
filteredMembers,
- targetClass.methods(
+ clazz.methods(
m ->
(enqueuer.isMethodLive(m)
|| enqueuer.isMethodTargeted(m)
|| m.getOptimizationInfo().returnValueHasBeenPropagated())
- && appView.graphLens().getOriginalMethodSignature(m.getReference()).holder
- == sourceClass.type));
+ && appView
+ .graphLens()
+ .getOriginalMethodSignature(m.getReference())
+ .getHolderType()
+ .isIdenticalTo(clazz.getType())));
// 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.
@@ -271,11 +235,11 @@
Sets.combinations(filteredMembers, memberKeepRules.size())) {
Collection<DexClassAndField> fieldsInCombination =
DexDefinition.filterDexEncodedField(
- combination.stream(), field -> DexClassAndField.create(targetClass, field))
+ combination.stream(), field -> DexClassAndField.create(clazz, field))
.collect(Collectors.toList());
Collection<DexClassAndMethod> methodsInCombination =
DexDefinition.filterDexEncodedMethod(
- combination.stream(), method -> DexClassAndMethod.create(targetClass, method))
+ combination.stream(), method -> DexClassAndMethod.create(clazz, 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.
@@ -287,7 +251,7 @@
|| rootSetBuilder.ruleSatisfiedByMethods(
memberRule, methodsInCombination));
if (satisfied) {
- materializeIfRule(rule, ImmutableSet.of(sourceClass.getReference()));
+ materializeIfRule(rule, clazz);
if (canRemoveSubsequentKeepRule(rule)) {
return true;
}
@@ -305,25 +269,17 @@
return field.getOrComputeIsInlinableByJavaC(appView.dexItemFactory());
}
- @SuppressWarnings("BadImport")
- private void materializeIfRule(ProguardIfRule rule, Set<DexReference> preconditions)
+ private void materializeIfRule(ProguardIfRule rule, DexProgramClass precondition)
throws ExecutionException {
DexItemFactory dexItemFactory = appView.dexItemFactory();
- ProguardIfRule materializedRule = rule.materialize(dexItemFactory, preconditions);
+ ProguardIfRule materializedRule = rule.materialize(dexItemFactory, precondition);
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, Type.NEVER_CLASS_INLINE);
+ materializedRule.neverInlineRuleForCondition(
+ dexItemFactory, InlineRuleType.NEVER_CLASS_INLINE);
if (neverInlineForClassInliningRuleForCondition != null) {
rootSetBuilder.runPerRule(tasks, neverInlineForClassInliningRuleForCondition, null);
}
@@ -332,17 +288,18 @@
// ensure that the subsequent rule will be applied again in the second round of tree
// shaking.
InlineRule neverInlineRuleForCondition =
- materializedRule.neverInlineRuleForCondition(dexItemFactory, Type.NEVER);
+ materializedRule.neverInlineRuleForCondition(dexItemFactory, InlineRuleType.NEVER);
if (neverInlineRuleForCondition != null) {
rootSetBuilder.runPerRule(tasks, neverInlineRuleForCondition, null);
}
- // Prevent horizontal class merging of any -if rule members.
- NoHorizontalClassMergingRule noHorizontalClassMergingRule =
- materializedRule.noHorizontalClassMergingRuleForCondition(dexItemFactory);
- if (noHorizontalClassMergingRule != null) {
- rootSetBuilder.runPerRule(tasks, noHorizontalClassMergingRule, null);
- }
+ rootSetBuilder
+ .getDependentMinimumKeepInfo()
+ .getOrCreateUnconditionalMinimumKeepInfoFor(precondition.getType())
+ .asClassJoiner()
+ .disallowClassInlining()
+ .disallowHorizontalClassMerging()
+ .disallowVerticalClassMerging();
}
// 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 cc813ad..4949e5a 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 Type {
+ public enum InlineRuleType {
ALWAYS,
NEVER,
NEVER_CLASS_INLINE,
@@ -24,14 +24,14 @@
super();
}
- Type type;
+ InlineRuleType type;
@Override
public Builder self() {
return this;
}
- public Builder setType(Type type) {
+ public Builder setType(InlineRuleType type) {
this.type = type;
return this;
}
@@ -56,7 +56,7 @@
}
}
- private final Type type;
+ private final InlineRuleType type;
protected InlineRule(
Origin origin,
@@ -72,7 +72,7 @@
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules,
- Type type) {
+ InlineRuleType type) {
super(
origin,
position,
@@ -94,7 +94,7 @@
return new Builder();
}
- public Type getType() {
+ public InlineRuleType 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 6ae7d4c..b2dcea5 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -18,6 +18,7 @@
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;
@@ -464,7 +465,7 @@
} else if (acceptString("alwaysinline")) {
InlineRule rule =
parseRuleWithClassSpec(
- optionStart, InlineRule.builder().setType(InlineRule.Type.ALWAYS));
+ optionStart, InlineRule.builder().setType(InlineRuleType.ALWAYS));
configurationBuilder.addRule(rule);
} else if (acceptString("adaptclassstrings")) {
parseClassFilter(configurationBuilder::addAdaptClassStringsPattern);
@@ -566,14 +567,14 @@
if (acceptString("neverinline")) {
InlineRule rule =
parseRuleWithClassSpec(
- optionStart, InlineRule.builder().setType(InlineRule.Type.NEVER));
+ optionStart, InlineRule.builder().setType(InlineRuleType.NEVER));
configurationBuilder.addRule(rule);
return true;
}
if (acceptString("neversinglecallerinline")) {
InlineRule rule =
parseRuleWithClassSpec(
- optionStart, InlineRule.builder().setType(InlineRule.Type.NEVER_SINGLE_CALLER));
+ optionStart, InlineRule.builder().setType(InlineRuleType.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 2279a8e..cb3757c 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -5,9 +5,10 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexProgramClass;
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;
@@ -25,21 +26,14 @@
}
};
- 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;
+ private final DexProgramClass precondition;
final ProguardKeepRule subsequentRule;
private Map<DexField, DexField> inlinableFieldsInPrecondition = new ConcurrentHashMap<>();
- public Set<DexReference> getPreconditions() {
- return preconditions;
+ public DexProgramClass getPrecondition() {
+ assert precondition != null;
+ return precondition;
}
public ProguardKeepRule getSubsequentRule() {
@@ -112,7 +106,7 @@
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules,
ProguardKeepRule subsequentRule,
- Set<DexReference> preconditions) {
+ DexProgramClass precondition) {
super(
origin,
position,
@@ -130,7 +124,7 @@
ProguardKeepRuleType.CONDITIONAL,
ProguardKeepRuleModifiers.builder().build());
this.subsequentRule = subsequentRule;
- this.preconditions = preconditions;
+ this.precondition = precondition;
}
public static Builder builder() {
@@ -153,7 +147,7 @@
}
protected ProguardIfRule materialize(
- DexItemFactory dexItemFactory, Set<DexReference> preconditions) {
+ DexItemFactory dexItemFactory, DexProgramClass precondition) {
return new ProguardIfRule(
getOrigin(),
getPosition(),
@@ -175,27 +169,7 @@
.map(memberRule -> memberRule.materialize(dexItemFactory))
.collect(Collectors.toList()),
subsequentRule.materialize(dexItemFactory),
- 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);
+ precondition);
}
/**
@@ -221,7 +195,7 @@
* -neverinline rule for the condition of the -if rule.
*/
protected InlineRule neverInlineRuleForCondition(
- DexItemFactory dexItemFactory, InlineRule.Type type) {
+ DexItemFactory dexItemFactory, InlineRuleType type) {
if (getMemberRules() == null || getMemberRules().isEmpty()) {
return null;
}
@@ -247,36 +221,6 @@
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 cecee47..9d30ea3 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -173,6 +173,10 @@
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 ed49f66..c991a7d 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -122,6 +122,9 @@
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 d8856f9..f852310 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/IncompleteVerticalClassMergerBridgeCode.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/IncompleteVerticalClassMergerBridgeCode.java
@@ -17,7 +17,6 @@
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;
@@ -34,7 +33,7 @@
/**
* A short-lived piece of code that will be converted into {@link LirCode} using {@link
- * #toLirCode(AppView)}.
+ * #toLirCode(AppView, VerticalClassMergerGraphLens, ClassMergerMode)}.
*/
public class IncompleteVerticalClassMergerBridgeCode extends Code {
@@ -87,10 +86,7 @@
method = lens.getNextBridgeMethodSignature(method);
}
- public LirCode<?> toLirCode(
- AppView<AppInfoWithLiveness> appView,
- VerticalClassMergerGraphLens lens,
- ClassMergerMode mode) {
+ public LirCode<?> toLirCode(AppView<AppInfoWithLiveness> appView) {
boolean isD8R8Synthesized = true;
LirEncodingStrategy<Value, Integer> strategy =
LirStrategy.getDefaultStrategy().getEncodingStrategy();
@@ -128,21 +124,7 @@
lirBuilder.addReturn(returnValue);
}
- 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;
- }
- };
+ return lirBuilder.build();
}
// 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 943f954..6772ced 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
@@ -143,10 +143,11 @@
rewriteCodeWithLens(executorService, timing);
// Remove merged classes from app now that the code is fully rewritten.
- removeMergedClasses(verticalClassMergerResult.getVerticallyMergedClasses(), timing);
+ removeMergedClasses(
+ verticalClassMergerResult.getVerticallyMergedClasses(), executorService, timing);
// Convert the (incomplete) synthesized bridges to LIR.
- finalizeSynthesizedBridges(verticalClassMergerResult.getSynthesizedBridges(), lens, timing);
+ finalizeSynthesizedBridges(verticalClassMergerResult.getSynthesizedBridges(), timing);
// Finally update the code lens to signal that the code is fully up to date.
markRewrittenWithLens(executorService, timing);
@@ -247,9 +248,6 @@
private void rewriteCodeWithLens(ExecutorService executorService, Timing timing)
throws ExecutionException {
- if (mode.isInitial()) {
- return;
- }
LirConverter.rewriteLirWithLens(appView, timing, executorService);
new IdentifierMinifier(appView).rewriteDexItemBasedConstStringInStaticFields(executorService);
}
@@ -292,11 +290,14 @@
timing.end();
}
- private void removeMergedClasses(VerticallyMergedClasses verticallyMergedClasses, Timing timing) {
+ private void removeMergedClasses(
+ VerticallyMergedClasses verticallyMergedClasses,
+ ExecutorService executorService,
+ Timing timing)
+ throws ExecutionException {
if (mode.isInitial()) {
return;
}
-
timing.begin("Remove merged classes");
DirectMappedDexApplication newApplication =
appView
@@ -305,13 +306,17 @@
.builder()
.removeProgramClasses(clazz -> verticallyMergedClasses.isMergeSource(clazz.getType()))
.build();
- appView.setAppInfo(appView.appInfo().rebuildWithLiveness(newApplication));
+ PrunedItems prunedItems =
+ PrunedItems.builder()
+ .addRemovedClasses(verticallyMergedClasses.getSources())
+ .setPrunedApp(newApplication)
+ .build();
+ appView.setAppInfo(appView.appInfo().prunedCopyFrom(prunedItems, executorService, timing));
timing.end();
}
private void finalizeSynthesizedBridges(
List<IncompleteVerticalClassMergerBridgeCode> bridges,
- VerticalClassMergerGraphLens lens,
Timing timing) {
timing.begin("Finalize synthesized bridges");
KeepInfoCollection keepInfo = appView.getKeepInfo();
@@ -323,7 +328,7 @@
assert target != null;
// Finalize code.
- bridge.setCode(code.toLirCode(appView, lens, mode), appView);
+ bridge.setCode(code.toLirCode(appView), appView);
// Copy keep info to newly synthesized methods.
keepInfo.mutate(
@@ -335,9 +340,6 @@
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 6876898..0031854 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,24 +4,20 @@
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;
@@ -105,79 +101,36 @@
@Test
public void testR8() throws Exception {
- 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
+ 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>()")))))
.run(parameters.getRuntime(), "TestClass")
.applyIf(
- parameters.isCfRuntime(),
+ (parameters.isCfRuntime() || parameters.getDexRuntimeVersion().isDalvik())
+ && !enableVerticalClassMerging,
runResult ->
runResult
.assertFailureWithErrorThatThrows(VerifyError.class)
.assertFailureWithErrorThatMatches(
- 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;
- }
+ 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"));
}
}
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 d422118..175b905 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,7 +6,6 @@
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;
@@ -16,6 +15,7 @@
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,13 +55,7 @@
"-if class " + A.class.getTypeName(), "-keep class " + C.class.getTypeName())
.addNoVerticalClassMergingAnnotations()
.addVerticallyMergedClassesInspector(
- inspector -> {
- if (enableVerticalClassMerging) {
- inspector.assertMergedIntoSubtype(A.class);
- } else {
- inspector.assertNoClassesMerged();
- }
- })
+ VerticallyMergedClassesInspector::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.
@@ -81,14 +75,7 @@
diagnostics.assertNoMessages();
}
})
- // 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)))
+ .inspect(inspector -> assertThat(inspector.clazz(C.class), isPresent()))
.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 f28c2a5..3247e60 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,9 +6,7 @@
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;
@@ -89,6 +87,11 @@
public void configure(R8FullTestBuilder builder) {
super.configure(builder);
builder
+ .addVerticallyMergedClassesInspector(
+ inspector ->
+ inspector
+ .applyIf(enableVerticalClassMerging, i -> i.assertMergedIntoSubtype(A.class))
+ .assertNoOtherClassesMerged())
.enableNeverClassInliningAnnotations()
.enableNoRedundantFieldLoadEliminationAnnotations();
}
@@ -116,16 +119,13 @@
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());
- // Verify that there was a naming conflict such that SuperTestClass.field was renamed.
- assertNotEquals("field", testClassSubject.allFields().get(0).getFinalName());
+ // 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());
}
}
}
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 aa46e96..eb770fe 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,7 +8,6 @@
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;
@@ -87,7 +86,13 @@
@Override
public void configure(R8FullTestBuilder builder) {
super.configure(builder);
- builder.enableNoHorizontalClassMergingAnnotations();
+ builder
+ .addVerticallyMergedClassesInspector(
+ inspector ->
+ inspector
+ .applyIf(enableVerticalClassMerging, i -> i.assertMergedIntoSubtype(A.class))
+ .assertNoOtherClassesMerged())
+ .enableNoHorizontalClassMergingAnnotations();
}
@Override
@@ -125,8 +130,10 @@
.collect(Collectors.toList());
assertEquals(1, methods.size());
- // Verify that there was a naming conflict such that SuperTestClass.method was renamed.
- assertNotEquals("method", methods.get(0).getFinalName());
+ // 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());
}
}
}
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 9e98e16..7bb0f91 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,7 +8,6 @@
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;
@@ -96,7 +95,14 @@
@Override
public void configure(R8FullTestBuilder builder) {
super.configure(builder);
- builder.enableNoHorizontalClassMergingAnnotations().enableSideEffectAnnotations();
+ builder
+ .addVerticallyMergedClassesInspector(
+ inspector ->
+ inspector
+ .applyIf(enableVerticalClassMerging, i -> i.assertMergedIntoSubtype(A.class))
+ .assertNoOtherClassesMerged())
+ .enableNoHorizontalClassMergingAnnotations()
+ .enableSideEffectAnnotations();
}
@Override
@@ -134,8 +140,10 @@
.collect(Collectors.toList());
assertEquals(1, methods.size());
- // Verify that there was a naming conflict such that SuperTestClass.method was renamed.
- assertNotEquals("method", methods.get(0).getFinalName());
+ // 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());
}
}
}