Version 2.1.64
Cherry pick: Disable class merging from base to feature
CL: https://r8-review.googlesource.com/c/r8/+/52963
Cherry pick: Disable devirtualization across feature splits
CL: https://r8-review.googlesource.com/c/r8/+/53024
Cherry pick: Add tests for vertical class merging in presence of feature splits
CL: https://r8-review.googlesource.com/c/r8/+/52962
Bug: 164937965, 165324486
Change-Id: Ibf315981ff3ef840e232e7459d4ae1559bebe393
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 69ccb47..8a1d666 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "2.1.63";
+ public static final String LABEL = "2.1.64";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
index 610104a..74ef8d9 100644
--- a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
+++ b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
@@ -120,7 +120,7 @@
return !isInFeature(clazz);
}
- public boolean inSameFeatureOrBase(DexMethod a, DexMethod b){
+ public boolean inSameFeatureOrBase(DexMethod a, DexMethod b) {
return inSameFeatureOrBase(a.holder, b.holder);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 7752d6c..ad2c233 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
+import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -23,7 +24,6 @@
import com.android.tools.r8.ir.code.InvokeSuper;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -165,10 +165,9 @@
if (holderClass == null || holderClass.isInterface()) {
continue;
}
+
// Due to the potential downcast below, make sure the new target holder is visible.
- ConstraintWithTarget visibility =
- ConstraintWithTarget.classIsVisible(context.getHolder(), holderType, appView);
- if (visibility == ConstraintWithTarget.NEVER) {
+ if (AccessControl.isClassAccessible(holderClass, context, appView).isPossiblyFalse()) {
continue;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index da9038d..92cf1b7 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -8,6 +8,7 @@
import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.features.FeatureSplitConfiguration;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
@@ -359,10 +360,14 @@
.map(DexEncodedMember::toReference)
.noneMatch(appInfo::isPinned);
- if (appView.options().featureSplitConfiguration != null
- && appView.options().featureSplitConfiguration.isInFeature(sourceClass)) {
+ FeatureSplitConfiguration featureSplitConfiguration =
+ appView.options().featureSplitConfiguration;
+ if (featureSplitConfiguration != null) {
// TODO(b/141452765): Allow class merging between classes in features.
- return false;
+ if (featureSplitConfiguration.isInFeature(sourceClass)
+ || featureSplitConfiguration.isInFeature(targetClass)) {
+ return false;
+ }
}
if (appView.appServices().allServiceTypes().contains(sourceClass.type)
&& appInfo.isPinned(targetClass.type)) {
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 2675329..64751df 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -3,11 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import static com.android.tools.r8.dexsplitter.SplitterTestBase.simpleSplitProvider;
import static org.hamcrest.CoreMatchers.containsString;
import com.android.tools.r8.R8Command.Builder;
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
+import com.android.tools.r8.dexsplitter.SplitterTestBase.RunInterface;
+import com.android.tools.r8.dexsplitter.SplitterTestBase.SplitRunner;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.origin.Origin;
@@ -63,6 +66,7 @@
private List<String> keepRules = new ArrayList<>();
private List<Path> mainDexRulesFiles = new ArrayList<>();
private List<String> applyMappingMaps = new ArrayList<>();
+ private final List<Path> features = new ArrayList<>();
@Override
R8TestCompileResult internalCompile(
@@ -136,7 +140,8 @@
box.syntheticProguardRules,
proguardMapBuilder.toString(),
graphConsumer,
- builder.getMinApiLevel());
+ builder.getMinApiLevel(),
+ features);
switch (allowedDiagnosticMessages) {
case ALL:
compileResult.assertDiagnosticThatMatches(new IsAnything<>());
@@ -553,8 +558,22 @@
return self();
}
+ public T addFeatureSplitRuntime() {
+ addProgramClasses(SplitRunner.class, RunInterface.class);
+ addKeepClassAndMembersRules(SplitRunner.class, RunInterface.class);
+ return self();
+ }
+
public T addFeatureSplit(Function<FeatureSplit.Builder, FeatureSplit> featureSplitBuilder) {
builder.addFeatureSplit(featureSplitBuilder);
return self();
}
+
+ public T addFeatureSplit(Class<?>... classes) throws IOException {
+ Path path = getState().getNewTempFile("feature.zip");
+ builder.addFeatureSplit(
+ builder -> simpleSplitProvider(builder, path, getState().getTempFolder(), classes));
+ features.add(path);
+ return self();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index 5dac954..b217591 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -3,12 +3,19 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.dexsplitter.SplitterTestBase.SplitRunner;
import com.android.tools.r8.shaking.CollectingGraphConsumer;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.graphinspector.GraphInspector;
import java.io.IOException;
@@ -23,6 +30,7 @@
private final String proguardMap;
private final CollectingGraphConsumer graphConsumer;
private final int minApiLevel;
+ private final List<Path> features;
R8TestCompileResult(
TestState state,
@@ -32,13 +40,15 @@
List<ProguardConfigurationRule> syntheticProguardRules,
String proguardMap,
CollectingGraphConsumer graphConsumer,
- int minApiLevel) {
+ int minApiLevel,
+ List<Path> features) {
super(state, app, outputMode);
this.proguardConfiguration = proguardConfiguration;
this.syntheticProguardRules = syntheticProguardRules;
this.proguardMap = proguardMap;
this.graphConsumer = graphConsumer;
this.minApiLevel = minApiLevel;
+ this.features = features;
}
@Override
@@ -56,6 +66,10 @@
return self();
}
+ public Path getFeature(int index) {
+ return features.get(index);
+ }
+
@Override
public String getStdout() {
return state.getStdout();
@@ -71,6 +85,21 @@
return new CodeInspector(app, proguardMap);
}
+ private CodeInspector featureInspector(Path feature) throws IOException {
+ return new CodeInspector(
+ AndroidApp.builder().addProgramFile(feature).setProguardMapOutputData(proguardMap).build());
+ }
+
+ public <E extends Throwable> R8TestCompileResult inspect(
+ ThrowingConsumer<CodeInspector, E>... consumers) throws IOException, E {
+ assertEquals(1 + features.size(), consumers.length);
+ consumers[0].accept(inspector());
+ for (int i = 0; i < features.size(); i++) {
+ consumers[i + 1].accept(featureInspector(features.get(i)));
+ }
+ return self();
+ }
+
public GraphInspector graphInspector() throws IOException {
assert graphConsumer != null;
return new GraphInspector(graphConsumer, inspector());
@@ -101,6 +130,37 @@
return new R8TestRunResult(app, runtime, result, proguardMap, this::graphInspector);
}
+ public R8TestRunResult runFeature(TestRuntime runtime, Class<?> mainFeatureClass)
+ throws IOException {
+ return runFeature(runtime, mainFeatureClass, features.get(0));
+ }
+
+ public R8TestRunResult runFeature(
+ TestRuntime runtime, Class<?> mainFeatureClass, Path feature, Path... featureDependencies)
+ throws IOException {
+ assert getBackend() == runtime.getBackend();
+ ClassSubject mainClassSubject = inspector().clazz(SplitRunner.class);
+ assertThat("Did you forget a keep rule for the main method?", mainClassSubject, isPresent());
+ assertThat(
+ "Did you forget a keep rule for the main method?",
+ mainClassSubject.mainMethod(),
+ isPresent());
+ ClassSubject mainFeatureClassSubject = featureInspector(feature).clazz(mainFeatureClass);
+ assertThat(
+ "Did you forget a keep rule for the run method?", mainFeatureClassSubject, isPresent());
+ assertThat(
+ "Did you forget a keep rule for the run method?",
+ mainFeatureClassSubject.uniqueMethodWithName("run"),
+ isPresent());
+ String[] args = new String[2 + featureDependencies.length];
+ args[0] = mainFeatureClassSubject.getFinalName();
+ args[1] = feature.toString();
+ for (int i = 2; i < args.length; i++) {
+ args[i] = featureDependencies[i - 2].toString();
+ }
+ return runArt(runtime, additionalRunClassPath, mainClassSubject.getFinalName(), args);
+ }
+
public String getProguardMap() {
return proguardMap;
}
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 0816496..3294828 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -419,7 +419,7 @@
return createRunResult(runtime, result);
}
- private RR runArt(
+ RR runArt(
TestRuntime runtime, List<Path> additionalClassPath, String mainClass, String... arguments)
throws IOException {
DexVm vm = runtime.asDex().getVm();
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index b9020eb..a088c00 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -5,6 +5,7 @@
package com.android.tools.r8;
import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.dexsplitter.SplitterTestBase.RunInterface;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.TypeReference;
import java.io.IOException;
@@ -171,6 +172,31 @@
return self();
}
+ public T addKeepFeatureMainRule(Class<?> mainClass) {
+ return addKeepFeatureMainRule(mainClass.getTypeName());
+ }
+
+ public T addKeepFeatureMainRules(Class<?>... mainClasses) {
+ for (Class<?> mainClass : mainClasses) {
+ this.addKeepFeatureMainRule(mainClass);
+ }
+ return self();
+ }
+
+ public T addKeepFeatureMainRule(String mainClass) {
+ return addKeepRules(
+ "-keep public class " + mainClass,
+ " implements " + RunInterface.class.getTypeName() + " {",
+ " public void <init>();",
+ " public void run();",
+ "}");
+ }
+
+ public T addKeepFeatureMainRules(List<String> mainClasses) {
+ mainClasses.forEach(this::addKeepFeatureMainRule);
+ return self();
+ }
+
public T addKeepMethodRules(Class<?> clazz, String... methodSignatures) {
StringBuilder sb = new StringBuilder();
sb.append("-keep class " + clazz.getTypeName() + " {\n");
diff --git a/src/test/java/com/android/tools/r8/TestState.java b/src/test/java/com/android/tools/r8/TestState.java
index 78388bc..6ba75d7 100644
--- a/src/test/java/com/android/tools/r8/TestState.java
+++ b/src/test/java/com/android/tools/r8/TestState.java
@@ -19,10 +19,18 @@
this.temp = temp;
}
+ public TemporaryFolder getTempFolder() {
+ return temp;
+ }
+
public Path getNewTempFolder() throws IOException {
return temp.newFolder().toPath();
}
+ public Path getNewTempFile(String name) throws IOException {
+ return getNewTempFolder().resolve(name);
+ }
+
DiagnosticsHandler getDiagnosticsHandler() {
return messages;
}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DevirtualizationAcrossFeatureSplitTest.java b/src/test/java/com/android/tools/r8/dexsplitter/DevirtualizationAcrossFeatureSplitTest.java
new file mode 100644
index 0000000..538f764
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DevirtualizationAcrossFeatureSplitTest.java
@@ -0,0 +1,74 @@
+// 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.dexsplitter;
+
+import com.android.tools.r8.NeverInline;
+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 DevirtualizationAcrossFeatureSplitTest extends SplitterTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public DevirtualizationAcrossFeatureSplitTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(BaseClass.class, BaseInterface.class)
+ .addFeatureSplitRuntime()
+ .addFeatureSplit(FeatureMain.class, BaseInterfaceImpl.class)
+ .addKeepFeatureMainRules(FeatureMain.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .runFeature(parameters.getRuntime(), FeatureMain.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ // Base.
+
+ public static class BaseClass {
+
+ @NeverInline
+ public static void run(BaseInterface instance) {
+ instance.greet();
+ }
+ }
+
+ public interface BaseInterface {
+
+ void greet();
+ }
+
+ // Feature.
+
+ public static class FeatureMain implements RunInterface {
+
+ @Override
+ public void run() {
+ BaseClass.run(new BaseInterfaceImpl());
+ }
+ }
+
+ public static class BaseInterfaceImpl implements BaseInterface {
+
+ @Override
+ public void greet() {
+ System.out.println("Hello world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index 16291c4..fbe1111 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -269,27 +269,30 @@
void run();
}
- static class SplitRunner {
+ public static class SplitRunner {
/* We support two different modes:
* - One argument to main:
* Pass in the class to be loaded, must implement RunInterface, run will be called
- * - Two arguments to main:
+ * - Two or more arguments to main:
* Pass in the class to be loaded, must implement RunInterface, run will be called
- * Pass in the feature split that we class load
- *
+ * Pass in the feature split that we class load, and an optional list of other feature
+ * splits that must be loaded before the given feature split.
*/
public static void main(String[] args) {
- if (args.length < 1 || args.length > 2) {
+ if (args.length < 1) {
throw new RuntimeException("Unsupported number of arguments");
}
String classToRun = args[0];
ClassLoader loader = SplitRunner.class.getClassLoader();
- // In the case where we simulate splits, we pass in the feature as the second argument
- if (args.length == 2) {
- try {
- loader = new PathClassLoader(args[1], SplitRunner.class.getClassLoader());
- } catch (MalformedURLException e) {
- throw new RuntimeException("Failed reading input URL");
+ // In the case where we simulate splits, the second argument is the feature to load, followed
+ // by all the other features that it depends on.
+ if (args.length >= 2) {
+ for (int i = args.length - 1; i >= 1; i--) {
+ try {
+ loader = new PathClassLoader(args[i], loader);
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("Failed reading input URL");
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingAcrossFeatureSplitTest.java b/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingAcrossFeatureSplitTest.java
new file mode 100644
index 0000000..4ff8f4b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingAcrossFeatureSplitTest.java
@@ -0,0 +1,116 @@
+// 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.dexsplitter;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dexsplitter.VerticalClassMergingInFeatureSplitTest.BaseClass;
+import com.android.tools.r8.dexsplitter.VerticalClassMergingInFeatureSplitTest.Feature1Class;
+import com.android.tools.r8.dexsplitter.VerticalClassMergingInFeatureSplitTest.Feature2Class;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class VerticalClassMergingAcrossFeatureSplitTest extends SplitterTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public VerticalClassMergingAcrossFeatureSplitTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(BaseClass.class)
+ .addFeatureSplitRuntime()
+ .addFeatureSplit(Feature1Class.class)
+ .addFeatureSplit(Feature2Main.class, Feature2Class.class)
+ .addKeepFeatureMainRule(Feature2Main.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspectBase, this::inspectFeature1, this::inspectFeature2);
+
+ // Run feature 2 on top of feature 1.
+ compileResult
+ .runFeature(
+ parameters.getRuntime(),
+ Feature2Main.class,
+ compileResult.getFeature(1),
+ compileResult.getFeature(0))
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspectBase(CodeInspector inspector) {
+ assertThat(inspector.clazz(BaseClass.class), isPresent());
+ }
+
+ private void inspectFeature1(CodeInspector inspector) {
+ assertThat(inspector.clazz(Feature1Class.class), isPresent());
+ }
+
+ private void inspectFeature2(CodeInspector inspector) {
+ assertThat(inspector.clazz(Feature2Class.class), isPresent());
+ }
+
+ // Base.
+
+ public static class BaseClass {
+
+ @NeverInline
+ public void greet() {
+ System.out.println("world!");
+ }
+ }
+
+ // Feature 1.
+
+ public static class Feature1Class extends BaseClass {
+
+ @NeverInline
+ @Override
+ public void greet() {
+ System.out.print(" ");
+ super.greet();
+ }
+ }
+
+ // Feature 2.
+
+ public static class Feature2Main implements RunInterface {
+
+ @Override
+ public void run() {
+ new Feature2Class().greet();
+ }
+ }
+
+ @NeverClassInline
+ static class Feature2Class extends Feature1Class {
+
+ @NeverInline
+ @Override
+ public void greet() {
+ System.out.print("Hello");
+ super.greet();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingInFeatureSplitTest.java b/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingInFeatureSplitTest.java
new file mode 100644
index 0000000..1a5d43b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingInFeatureSplitTest.java
@@ -0,0 +1,159 @@
+// 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.dexsplitter;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class VerticalClassMergingInFeatureSplitTest extends SplitterTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public VerticalClassMergingInFeatureSplitTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(BaseClass.class, BaseClassWithBaseSubclass.class)
+ .addFeatureSplitRuntime()
+ .addFeatureSplit(
+ Feature1Main.class, Feature1Class.class, Feature1ClassWithSameFeatureSubclass.class)
+ .addFeatureSplit(
+ Feature2Main.class, Feature2Class.class, Feature2ClassWithSameFeatureSubclass.class)
+ .addKeepFeatureMainRules(Feature1Main.class, Feature2Main.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspectBase, this::inspectFeature1, this::inspectFeature2);
+
+ compileResult
+ .runFeature(parameters.getRuntime(), Feature1Main.class, compileResult.getFeature(0))
+ .assertSuccessWithOutputLines("Hello world!");
+
+ compileResult
+ .runFeature(parameters.getRuntime(), Feature2Main.class, compileResult.getFeature(1))
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspectBase(CodeInspector inspector) {
+ assertThat(inspector.clazz(BaseClass.class), isPresent());
+ assertThat(inspector.clazz(BaseClassWithBaseSubclass.class), not(isPresent()));
+ }
+
+ private void inspectFeature1(CodeInspector inspector) {
+ assertThat(inspector.clazz(Feature1Class.class), isPresent());
+ // TODO(b/141452765): Should be absent.
+ assertThat(inspector.clazz(Feature1ClassWithSameFeatureSubclass.class), isPresent());
+ }
+
+ private void inspectFeature2(CodeInspector inspector) {
+ assertThat(inspector.clazz(Feature2Class.class), isPresent());
+ // TODO(b/141452765): Should be absent.
+ assertThat(inspector.clazz(Feature2ClassWithSameFeatureSubclass.class), isPresent());
+ }
+
+ // Base.
+
+ static class BaseClassWithBaseSubclass {
+
+ @NeverInline
+ public void greet() {
+ System.out.print(" ");
+ }
+ }
+
+ @NeverClassInline
+ public static class BaseClass extends BaseClassWithBaseSubclass {
+
+ @NeverInline
+ @Override
+ public void greet() {
+ System.out.print("Hello");
+ super.greet();
+ }
+ }
+
+ // Feature 1.
+
+ public static class Feature1Main implements RunInterface {
+
+ @Override
+ public void run() {
+ new BaseClass().greet();
+ new Feature1Class().greet();
+ }
+ }
+
+ static class Feature1ClassWithSameFeatureSubclass {
+
+ @NeverInline
+ public void greet() {
+ System.out.println("!");
+ }
+ }
+
+ @NeverClassInline
+ static class Feature1Class extends Feature1ClassWithSameFeatureSubclass {
+
+ @NeverInline
+ @Override
+ public void greet() {
+ System.out.print("world");
+ super.greet();
+ }
+ }
+
+ // Feature 2.
+
+ public static class Feature2Main implements RunInterface {
+
+ @NeverInline
+ @Override
+ public void run() {
+ new BaseClass().greet();
+ new Feature2Class().greet();
+ }
+ }
+
+ static class Feature2ClassWithSameFeatureSubclass {
+
+ @NeverInline
+ public void greet() {
+ System.out.println("!");
+ }
+ }
+
+ @NeverClassInline
+ static class Feature2Class extends Feature2ClassWithSameFeatureSubclass {
+
+ @NeverInline
+ @Override
+ public void greet() {
+ System.out.print("world");
+ super.greet();
+ }
+ }
+}