Allow merging classes with different interfaces
Change-Id: I3048f517e0df31cacab795cc441c8a1555bac8e6
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 5215ada..dbc2868 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -130,6 +130,10 @@
return interfaces;
}
+ public void setInterfaces(DexTypeList interfaces) {
+ this.interfaces = interfaces;
+ }
+
public DexString getSourceFile() {
return sourceFile;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
index a641039..e4462ac 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import static com.android.tools.r8.utils.PredicateUtils.not;
+
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.errors.Unreachable;
@@ -13,8 +15,10 @@
import com.android.tools.r8.utils.structural.StructuralItem;
import com.google.common.collect.Iterators;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Iterator;
import java.util.function.Consumer;
+import java.util.function.Predicate;
import java.util.stream.Stream;
public class DexTypeList extends DexItem implements Iterable<DexType>, StructuralItem<DexTypeList> {
@@ -36,12 +40,36 @@
this.values = values;
}
+ public DexTypeList(Collection<DexType> values) {
+ this(values.toArray(DexType.EMPTY_ARRAY));
+ }
+
+ public static DexTypeList create(DexType[] values) {
+ return values.length == 0 ? DexTypeList.empty() : new DexTypeList(values);
+ }
+
+ public static DexTypeList create(Collection<DexType> values) {
+ return values.isEmpty() ? DexTypeList.empty() : new DexTypeList(values);
+ }
+
@Override
public StructuralAccept<DexTypeList> getStructuralAccept() {
// Structural accept is never accessed as all accept methods are defined directly.
throw new Unreachable();
}
+ public DexTypeList keepIf(Predicate<DexType> predicate) {
+ DexType[] filtered = ArrayUtils.filter(DexType[].class, values, predicate);
+ if (filtered != values) {
+ return DexTypeList.create(filtered);
+ }
+ return this;
+ }
+
+ public DexTypeList removeIf(Predicate<DexType> predicate) {
+ return keepIf(not(predicate));
+ }
+
@Override
public DexTypeList self() {
return this;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index f75f969..dc0310c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
import com.android.tools.r8.graph.ProgramMethod;
@@ -25,6 +26,7 @@
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.ArrayList;
@@ -34,6 +36,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* The class merger is responsible for moving methods from {@link ClassMerger#toMergeGroup} into the
@@ -194,6 +197,16 @@
}
}
+ private void mergeInterfaces() {
+ Set<DexType> interfaces = Sets.newLinkedHashSet(target.getInterfaces());
+ for (DexProgramClass clazz : toMergeGroup) {
+ Iterables.addAll(interfaces, clazz.getInterfaces());
+ }
+ if (interfaces.size() > target.getInterfaces().size()) {
+ target.setInterfaces(new DexTypeList(interfaces));
+ }
+ }
+
void mergeInstanceFields() {
toMergeGroup.forEach(
clazz -> {
@@ -207,6 +220,8 @@
fixAccessFlags();
appendClassIdField();
+ mergeInterfaces();
+
mergeVirtualMethods();
mergeDirectMethods(syntheticArgumentClass);
classMethodsBuilder.setClassMethods(target);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 69be609..097bb58 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -9,7 +9,6 @@
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated;
import com.android.tools.r8.horizontalclassmerging.policies.CheckAbstractClasses;
-import com.android.tools.r8.horizontalclassmerging.policies.ClassesHaveSameInterfaces;
import com.android.tools.r8.horizontalclassmerging.policies.DontInlinePolicy;
import com.android.tools.r8.horizontalclassmerging.policies.DontMergeIntoLessVisible;
import com.android.tools.r8.horizontalclassmerging.policies.DontMergeSynchronizedClasses;
@@ -17,14 +16,15 @@
import com.android.tools.r8.horizontalclassmerging.policies.LimitGroups;
import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotations;
import com.android.tools.r8.horizontalclassmerging.policies.NoClassesOrMembersWithAnnotations;
+import com.android.tools.r8.horizontalclassmerging.policies.NoDirectRuntimeTypeChecks;
import com.android.tools.r8.horizontalclassmerging.policies.NoEnums;
+import com.android.tools.r8.horizontalclassmerging.policies.NoIndirectRuntimeTypeChecks;
import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces;
import com.android.tools.r8.horizontalclassmerging.policies.NoKeepRules;
import com.android.tools.r8.horizontalclassmerging.policies.NoKotlinLambdas;
import com.android.tools.r8.horizontalclassmerging.policies.NoKotlinMetadata;
import com.android.tools.r8.horizontalclassmerging.policies.NoNativeMethods;
-import com.android.tools.r8.horizontalclassmerging.policies.NoRuntimeTypeChecks;
import com.android.tools.r8.horizontalclassmerging.policies.NoServiceLoaders;
import com.android.tools.r8.horizontalclassmerging.policies.NoStaticClassInitializer;
import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
@@ -109,7 +109,6 @@
new NotMatchedByNoHorizontalClassMerging(appView),
new SameFields(),
new NoInterfaces(),
- new ClassesHaveSameInterfaces(),
new NoAnnotations(),
new NoEnums(appView),
new CheckAbstractClasses(appView),
@@ -123,7 +122,8 @@
new NoKotlinLambdas(appView),
new NoServiceLoaders(appView),
new NotVerticallyMergedIntoSubtype(appView),
- new NoRuntimeTypeChecks(runtimeTypeCheckInfo),
+ new NoDirectRuntimeTypeChecks(runtimeTypeCheckInfo),
+ new NoIndirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo),
new PreventMethodImplementation(appView),
new DontInlinePolicy(appView, mainDexTracingResult),
new PreventMergeIntoMainDex(appView, mainDexTracingResult),
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/ClassesHaveSameInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/ClassesHaveSameInterfaces.java
deleted file mode 100644
index 060e1a6..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/ClassesHaveSameInterfaces.java
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) 2020, 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.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
-
-public class ClassesHaveSameInterfaces extends MultiClassSameReferencePolicy<DexTypeList> {
-
- @Override
- public DexTypeList getMergeKey(DexProgramClass clazz) {
- return clazz.interfaces.getSorted();
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoRuntimeTypeChecks.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java
similarity index 75%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoRuntimeTypeChecks.java
rename to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java
index 010fee8..b0f3766 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoRuntimeTypeChecks.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java
@@ -8,16 +8,15 @@
import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
-public class NoRuntimeTypeChecks extends SingleClassPolicy {
+public class NoDirectRuntimeTypeChecks extends SingleClassPolicy {
private final RuntimeTypeCheckInfo runtimeTypeCheckInfo;
- public NoRuntimeTypeChecks(RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+ public NoDirectRuntimeTypeChecks(RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
this.runtimeTypeCheckInfo = runtimeTypeCheckInfo;
}
@Override
public boolean canMerge(DexProgramClass clazz) {
- // We currently assume we only merge classes that implement the same set of interfaces.
return !runtimeTypeCheckInfo.isRuntimeCheckType(clazz);
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIndirectRuntimeTypeChecks.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIndirectRuntimeTypeChecks.java
new file mode 100644
index 0000000..a7e9ee8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIndirectRuntimeTypeChecks.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2020, 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
+import it.unimi.dsi.fastutil.objects.Reference2BooleanMap;
+import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
+
+public class NoIndirectRuntimeTypeChecks extends MultiClassSameReferencePolicy<DexTypeList> {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final RuntimeTypeCheckInfo runtimeTypeCheckInfo;
+
+ private final Reference2BooleanMap<DexType> cache = new Reference2BooleanOpenHashMap<>();
+
+ public NoIndirectRuntimeTypeChecks(
+ AppView<AppInfoWithLiveness> appView, RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+ this.appView = appView;
+ this.runtimeTypeCheckInfo = runtimeTypeCheckInfo;
+ }
+
+ @Override
+ public DexTypeList getMergeKey(DexProgramClass clazz) {
+ // Require that classes that implement an interface that has a runtime type check (directly or
+ // indirectly on a parent interface) are only merged with classes that implement the same
+ // interfaces.
+ return clazz
+ .getInterfaces()
+ .keepIf(this::computeInterfaceHasDirectOrIndirectRuntimeTypeCheck)
+ .getSorted();
+ }
+
+ private boolean computeInterfaceHasDirectOrIndirectRuntimeTypeCheck(DexType type) {
+ if (cache.containsKey(type)) {
+ return cache.getBoolean(type);
+ }
+ DexClass clazz = appView.definitionFor(type);
+ if (!clazz.isInterface()) {
+ cache.put(type, true);
+ return true;
+ }
+ if (!clazz.isProgramClass()) {
+ // Conservatively return true because there could be a type check in the library.
+ cache.put(type, true);
+ return true;
+ }
+ if (runtimeTypeCheckInfo.isRuntimeCheckType(clazz.asProgramClass())) {
+ cache.put(type, true);
+ return true;
+ }
+ for (DexType parentType : clazz.getInterfaces()) {
+ if (computeInterfaceHasDirectOrIndirectRuntimeTypeCheck(parentType)) {
+ cache.put(type, true);
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 8a8d2d4..a179513 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -265,6 +265,14 @@
}
if (inliner.neverInline(invoke, resolutionResult, singleTarget, whyAreYouNotInliningReporter)) {
+ if (singleTarget.getDefinition().getOptimizationInfo().forceInline()) {
+ throw new Unreachable(
+ "Unexpected attempt to force inline method `"
+ + singleTarget.toSourceString()
+ + "` in `"
+ + context.toSourceString()
+ + "`.");
+ }
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 3abbf9b..b26bb34 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -7,7 +7,6 @@
import static com.google.common.base.Predicates.not;
import com.android.tools.r8.androidapi.AvailableApiExceptions;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AccessFlags;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -125,11 +124,6 @@
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod singleTargetReference = singleTarget.getReference();
- if (singleTarget.getDefinition().getOptimizationInfo().forceInline()
- && appInfo.isNeverInlineMethod(singleTargetReference)) {
- throw new Unreachable();
- }
-
if (appInfo.isPinned(singleTargetReference)) {
whyAreYouNotInliningReporter.reportPinned();
return true;
@@ -151,7 +145,7 @@
if (appInfo.noSideEffects.containsKey(invoke.getInvokedMethod())
|| appInfo.noSideEffects.containsKey(resolutionResult.getResolvedMethod().getReference())
|| appInfo.noSideEffects.containsKey(singleTargetReference)) {
- return true;
+ return !singleTarget.getDefinition().getOptimizationInfo().forceInline();
}
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index be88541..b0d1fa8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -573,7 +573,7 @@
return null;
}
if (singleTarget.isInstanceInitializer() && invoke.getReceiver() == receiver) {
- if (builder.hasParent()) {
+ if (builder.hasParent() && builder.getParent() != singleTarget.getReference()) {
return null;
}
// java.lang.Enum.<init>() and java.lang.Object.<init>() are considered trivial.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
index ee08c65..75c74dd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
@@ -189,8 +189,12 @@
return parent != null;
}
+ public DexMethod getParent() {
+ return parent;
+ }
+
public Builder setParent(DexMethod parent) {
- assert !hasParent();
+ assert !hasParent() || getParent() == parent;
this.parent = parent;
return this;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
index ddfea43..fb3868a 100644
--- a/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
@@ -49,17 +49,24 @@
@Override
public void traceCheckCast(DexType type, ProgramMethod context) {
- checkCastTypes.add(type.toBaseType(factory));
+ add(type, checkCastTypes);
}
@Override
public void traceInstanceOf(DexType type, ProgramMethod context) {
- instanceOfTypes.add(type.toBaseType(factory));
+ add(type, instanceOfTypes);
}
@Override
public void traceExceptionGuard(DexType guard, ProgramMethod context) {
- exceptionGuardTypes.add(guard);
+ add(guard, exceptionGuardTypes);
+ }
+
+ private void add(DexType type, Set<DexType> set) {
+ DexType baseType = type.toBaseType(factory);
+ if (baseType.isClassType()) {
+ set.add(baseType);
+ }
}
public void attach(Enqueuer enqueuer) {
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 058c2e5..34d573a 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -484,6 +484,10 @@
return self();
}
+ public T addNoHorizontalClassMergingRule(String clazz) {
+ return addKeepRules("-nohorizontalclassmerging class " + clazz);
+ }
+
public T enableNoHorizontalClassMergingAnnotations() {
if (!enableNoHorizontalClassMergingAnnotations) {
enableNoHorizontalClassMergingAnnotations = true;
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
index 8624277..8cd7860 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
@@ -165,6 +165,7 @@
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
.enableMemberValuePropagationAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.noMinification()
.addKeepRules(
"-checkdiscard class " + Base.class.getCanonicalName() + "{",
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
index deb2559..c832fe7 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
@@ -5,8 +5,10 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
@NeverClassInline
+@NoHorizontalClassMerging
public class Sub1 extends Base implements Itf1 {
@Override
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
index 15cc549..ffdeec6 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
@@ -5,8 +5,10 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
@NeverClassInline
+@NoHorizontalClassMerging
public class Sub2 extends Base implements Itf2 {
@Override
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentInterfacesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentInterfacesTest.java
index d1333a3..3c9d066 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentInterfacesTest.java
@@ -5,13 +5,13 @@
package com.android.tools.r8.classmerging.horizontal;
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.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import org.junit.Test;
public class ClassesWithDifferentInterfacesTest extends HorizontalClassMergingTestBase {
@@ -31,8 +31,8 @@
.enableNeverClassInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
- .addHorizontallyMergedClassesInspector(
- HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ .addHorizontallyMergedClassesInspectorIf(
+ enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(Z.class, Y.class))
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("bar", "foo y", "bar")
.inspect(
@@ -40,7 +40,8 @@
assertThat(codeInspector.clazz(I.class), isPresent());
assertThat(codeInspector.clazz(X.class), isPresent());
assertThat(codeInspector.clazz(Y.class), isPresent());
- assertThat(codeInspector.clazz(Z.class), isPresent());
+ assertThat(
+ codeInspector.clazz(Z.class), notIf(isPresent(), enableHorizontalClassMerging));
});
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java
index 5c03d3b..2bab975 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java
@@ -30,14 +30,17 @@
.enableNeverClassInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.addHorizontallyMergedClassesInspectorIf(
- enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(Z.class, Y.class))
+ enableHorizontalClassMerging,
+ inspector ->
+ inspector.assertMergedInto(Y.class, X.class).assertMergedInto(Z.class, X.class))
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("bar", "foo y", "foo z")
.inspect(
codeInspector -> {
assertThat(codeInspector.clazz(I.class), isPresent());
assertThat(codeInspector.clazz(X.class), isPresent());
- assertThat(codeInspector.clazz(Y.class), isPresent());
+ assertThat(
+ codeInspector.clazz(Y.class), notIf(isPresent(), enableHorizontalClassMerging));
assertThat(
codeInspector.clazz(Z.class), notIf(isPresent(), enableHorizontalClassMerging));
});
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ConflictWasDetectedTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ConflictWasDetectedTest.java
new file mode 100644
index 0000000..8b24e99
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ConflictWasDetectedTest.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2020, 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.classmerging.vertical;
+
+import com.android.tools.r8.KeepUnusedArguments;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ConflictWasDetectedTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ConflictWasDetectedTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addVerticallyMergedClassesInspector(
+ VerticallyMergedClassesInspector::assertNoClassesMerged)
+ .enableInliningAnnotations()
+ // .enableNoHorizontalClassMergingAnnotations()
+ .enableUnusedArgumentAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class);
+ }
+
+ static class Main {
+
+ public static void main(String... args) {
+ ConflictingInterfaceImpl impl = new ConflictingInterfaceImpl();
+ callMethodOnIface(impl);
+
+ // Ensure that the instantiations are not dead code eliminated.
+ escape(impl);
+ }
+
+ private static void callMethodOnIface(ConflictingInterface iface) {
+ System.out.println(iface.method());
+ System.out.println(ClassWithConflictingMethod.conflict(null));
+ System.out.println(OtherClassWithConflictingMethod.conflict(null));
+ }
+
+ @NeverInline
+ private static void escape(Object o) {
+ if (System.currentTimeMillis() < 0) {
+ System.out.println(o);
+ }
+ }
+ }
+
+ public interface ConflictingInterface {
+
+ String method();
+ }
+
+ public static class ConflictingInterfaceImpl implements ConflictingInterface {
+
+ @Override
+ public String method() {
+ return "ConflictingInterfaceImpl::method";
+ }
+ }
+
+ public static class ClassWithConflictingMethod {
+
+ @KeepUnusedArguments
+ public static int conflict(ConflictingInterface item) {
+ return 123;
+ }
+ }
+
+ public static class OtherClassWithConflictingMethod {
+
+ @KeepUnusedArguments
+ public static int conflict(ConflictingInterfaceImpl item) {
+ return 321;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/LambdaRewritingTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/LambdaRewritingTest.java
new file mode 100644
index 0000000..4cb9707
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/LambdaRewritingTest.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2020, 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.classmerging.vertical;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LambdaRewritingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public LambdaRewritingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addVerticallyMergedClassesInspector(
+ inspector -> inspector.assertMergedIntoSubtype(Interface.class))
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class);
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ Interface obj = new InterfaceImpl();
+
+ // Leads to an invoke-custom instruction that mentions the type of `obj` since it is captured.
+ invoke(() -> obj.foo());
+
+ FunctionImpl functionImpl = new FunctionImpl();
+ if (System.currentTimeMillis() < 0) {
+ System.out.println(functionImpl);
+ }
+ }
+
+ @NeverInline
+ private static void invoke(Function f) {
+ f.accept();
+ }
+ }
+
+ // Cannot be merged as it has two subtypes: FunctionImpl and a lambda.
+ public interface Function {
+
+ void accept();
+ }
+
+ public static class FunctionImpl implements Function {
+
+ @Override
+ public void accept() {
+ System.out.println("In FunctionImpl.accept()");
+ }
+ }
+
+ // Will be merged into InterfaceImpl.
+ public interface Interface {
+
+ void foo();
+ }
+
+ public static class InterfaceImpl implements Interface {
+
+ @Override
+ public void foo() {
+ System.out.println("In InterfaceImpl.foo()");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index d7d31c9..2fb36cd 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -305,13 +305,6 @@
}
@Test
- public void testConflictWasDetected() throws Throwable {
- runR8(EXAMPLE_KEEP, this::configure);
- assertThat(inspector.clazz("classmerging.ConflictingInterface"), isPresent());
- assertThat(inspector.clazz("classmerging.ConflictingInterfaceImpl"), isPresent());
- }
-
- @Test
public void testFieldCollision() throws Throwable {
String main = "classmerging.FieldCollisionTest";
Path[] programFiles =
@@ -334,34 +327,6 @@
}
@Test
- public void testLambdaRewriting() throws Throwable {
- String main = "classmerging.LambdaRewritingTest";
- Path[] programFiles =
- new Path[] {
- JAVA8_CF_DIR.resolve("LambdaRewritingTest.class"),
- JAVA8_CF_DIR.resolve("LambdaRewritingTest$Function.class"),
- JAVA8_CF_DIR.resolve("LambdaRewritingTest$FunctionImpl.class"),
- JAVA8_CF_DIR.resolve("LambdaRewritingTest$Interface.class"),
- JAVA8_CF_DIR.resolve("LambdaRewritingTest$InterfaceImpl.class")
- };
- Set<String> preservedClassNames =
- ImmutableSet.of(
- "classmerging.LambdaRewritingTest",
- "classmerging.LambdaRewritingTest$Function",
- "classmerging.LambdaRewritingTest$FunctionImpl",
- "classmerging.LambdaRewritingTest$InterfaceImpl");
- runTestOnInput(
- testForR8(parameters.getBackend())
- .addKeepRules(getProguardConfig(JAVA8_EXAMPLE_KEEP))
- .addOptionsModification(this::configure)
- .addOptionsModification(options -> options.enableClassInlining = false)
- .allowUnusedProguardConfigurationRules(),
- main,
- readProgramFiles(programFiles),
- name -> preservedClassNames.contains(name) || name.contains("$Lambda$"));
- }
-
- @Test
public void testMergeInterfaceWithoutInlining() throws Throwable {
String main = "classmerging.ConflictingInterfaceSignaturesTest";
Path[] programFiles =
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index cd743a9..d88e281 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -82,9 +82,13 @@
.addKeepMainRule("proto2.TestClass")
.addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
.addKeepRules(allGeneratedMessageLiteSubtypesAreInstantiatedRule())
+ // TODO(b/173340579): This rule should not be needed to allow shrinking of
+ // PartiallyUsed$Enum.
+ .addNoHorizontalClassMergingRule(PARTIALLY_USED + "$Enum$1")
.allowAccessModification(allowAccessModification)
.allowDiagnosticMessages()
.allowUnusedProguardConfigurationRules()
+ .enableProguardTestOptions()
.enableProtoShrinking()
.minification(enableMinification)
.setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index cf52085..e0266bd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.optimize.classinliner;
import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
@@ -88,9 +89,15 @@
.enableSideEffectAnnotations()
.addKeepMainRule(main)
.addKeepAttributes("LineNumberTable")
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector
+ .assertMergedInto(Iface1Impl.class, CycleReferenceBA.class)
+ .assertMergedInto(Iface2Impl.class, CycleReferenceBA.class))
.allowAccessModification()
.noMinification()
- .run(main)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), main)
.assertSuccessWithOutput(javaOutput);
CodeInspector inspector = result.inspector();
@@ -143,7 +150,7 @@
"com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB"),
collectTypes(inspector.clazz(CycleReferenceAB.class).uniqueMethodWithName("foo")));
- assertFalse(inspector.clazz(CycleReferenceBA.class).isPresent());
+ assertThat(inspector.clazz(CycleReferenceBA.class), isAbsent());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
index 8dbe868..3e28157 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
@@ -37,6 +37,7 @@
@NoHorizontalClassMerging
static class EffectivelyFinal {}
+ @NoHorizontalClassMerging
static class Reflection implements Callable<Class<?>> {
@ForceInline
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
index bae2760..c6efef7 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
@@ -99,6 +99,7 @@
.addProgramClasses(CLASSES)
.addProgramClassFileData(ASM_CLASSES)
.addKeepMainRule(Main.class)
+ .enableNoHorizontalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutput(EXPECTED);
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/one/SubSubClassOne.java b/src/test/java/com/android/tools/r8/resolution/singletarget/one/SubSubClassOne.java
index ff585af..c140fed 100644
--- a/src/test/java/com/android/tools/r8/resolution/singletarget/one/SubSubClassOne.java
+++ b/src/test/java/com/android/tools/r8/resolution/singletarget/one/SubSubClassOne.java
@@ -3,6 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.resolution.singletarget.one;
+import com.android.tools.r8.NoHorizontalClassMerging;
+
+@NoHorizontalClassMerging
public class SubSubClassOne extends AbstractSubClass implements IrrelevantInterfaceWithDefault {
// Avoid SubSubClassOne.class.getCanonicalName() as it may change during shrinking.
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/one/SubSubClassTwo.java b/src/test/java/com/android/tools/r8/resolution/singletarget/one/SubSubClassTwo.java
index bd4c45c..1534f36 100644
--- a/src/test/java/com/android/tools/r8/resolution/singletarget/one/SubSubClassTwo.java
+++ b/src/test/java/com/android/tools/r8/resolution/singletarget/one/SubSubClassTwo.java
@@ -3,6 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.resolution.singletarget.one;
+import com.android.tools.r8.NoHorizontalClassMerging;
+
+@NoHorizontalClassMerging
public class SubSubClassTwo extends AbstractSubClass {
@Override