Merge "Add methods for writing TestRunResult parts to a PrintStream"
diff --git a/.gitignore b/.gitignore
index f15c3c1..51e0c02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,6 +45,8 @@
third_party/gradle/gradle
third_party/gradle-plugin
third_party/gradle-plugin.tar.gz
+third_party/jacoco/*
+third_party/jacoco.tar.gz
third_party/jasmin.tar.gz
third_party/jasmin
third_party/jdwp-tests.tar.gz
diff --git a/build.gradle b/build.gradle
index cfaacdb..4231129 100644
--- a/build.gradle
+++ b/build.gradle
@@ -339,6 +339,7 @@
"gmscore/gmscore_v9",
"gmscore/gmscore_v10",
"gmscore/latest",
+ "jacoco",
"nest/nest_20180926_7c6cfb",
"photos/2017-06-06",
"youtube/youtube.android_12.10",
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 8df5030..45a1548 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -66,8 +66,11 @@
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- for (DexType type : values) {
- builder.append(' ').append(type);
+ if (values.length > 0) {
+ builder.append(values[0]);
+ for (int i = 1; i < values.length; i++) {
+ builder.append(' ').append(values[i]);
+ }
}
return builder.toString();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
index 2aff5df..1d970a3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
@@ -230,6 +230,10 @@
return inheritanceIsExtends;
}
+ public boolean getInheritanceIsImplements() {
+ return !inheritanceIsExtends;
+ }
+
public boolean hasInheritanceClassName() {
return inheritanceClassName != null;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index e117462..432ae10 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -47,7 +47,6 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
-import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -80,7 +79,7 @@
public RootSetBuilder(
AppView<? extends AppInfo> appView,
DexApplication application,
- List<ProguardConfigurationRule> rules,
+ Collection<? extends ProguardConfigurationRule> rules,
InternalOptions options) {
this.appView = appView;
this.application = application.asDirect();
@@ -89,64 +88,10 @@
}
RootSetBuilder(
- AppView<? extends AppInfo> appView, Set<ProguardIfRule> ifRules, InternalOptions options) {
- this.appView = appView;
- this.application = appView.appInfo().app.asDirect();
- this.rules = Collections.unmodifiableCollection(ifRules);
- this.options = options;
- }
-
- private boolean anySuperTypeMatches(
- DexType type,
- Function<DexType, DexClass> definitionFor,
- ProguardTypeMatcher name,
- ProguardTypeMatcher annotation) {
- while (type != null) {
- DexClass clazz = definitionFor.apply(type);
- if (clazz == null) {
- // TODO(herhut): Warn about broken supertype chain?
- return false;
- }
- // TODO(b/110141157): Should the vertical class merger move annotations from the source to
- // the target class? If so, it is sufficient only to apply the annotation-matcher to the
- // annotations of `class`.
- if (name.matches(clazz.type, appView) && containsAnnotation(annotation, clazz.annotations)) {
- return true;
- }
- type = clazz.superType;
- }
- return false;
- }
-
- private boolean anyImplementedInterfaceMatches(
- DexClass clazz,
- Function<DexType, DexClass> definitionFor,
- ProguardTypeMatcher className,
- ProguardTypeMatcher annotation) {
- if (clazz == null) {
- return false;
- }
- for (DexType iface : clazz.interfaces.values) {
- DexClass ifaceClass = definitionFor.apply(iface);
- if (ifaceClass == null) {
- // TODO(herhut): Warn about broken supertype chain?
- return false;
- }
- // TODO(herhut): Maybe it would be better to do this breadth first.
- if ((className.matches(iface) && containsAnnotation(annotation, ifaceClass.annotations))
- || anyImplementedInterfaceMatches(ifaceClass, definitionFor, className, annotation)) {
- return true;
- }
- }
- if (clazz.superType == null) {
- return false;
- }
- DexClass superClass = definitionFor.apply(clazz.superType);
- if (superClass == null) {
- // TODO(herhut): Warn about broken supertype chain?
- return false;
- }
- return anyImplementedInterfaceMatches(superClass, definitionFor, className, annotation);
+ AppView<? extends AppInfo> appView,
+ Collection<ProguardIfRule> ifRules,
+ InternalOptions options) {
+ this(appView, appView.appInfo().app, ifRules, options);
}
// Process a class with the keep rule.
@@ -168,8 +113,7 @@
// seems not to care, so users have started to use this inconsistently. We are thus
// inconsistent, as well, but tell them.
// TODO(herhut): One day make this do what it says.
- if (rule.hasInheritanceClassName()
- && !satisfyInheritanceRule(clazz, application::definitionFor, rule)) {
+ if (rule.hasInheritanceClassName() && !satisfyInheritanceRule(clazz, rule)) {
return;
}
@@ -180,7 +124,7 @@
switch (((ProguardKeepRule) rule).getType()) {
case KEEP_CLASS_MEMBERS: {
// Members mentioned at -keepclassmembers always depend on their holder.
- preconditionSupplier = ImmutableMap.of((definition -> true), clazz);
+ preconditionSupplier = ImmutableMap.of(definition -> true, clazz);
markMatchingVisibleMethods(clazz, memberKeepRules, rule, preconditionSupplier);
markMatchingFields(clazz, memberKeepRules, rule, preconditionSupplier);
break;
@@ -395,7 +339,7 @@
if (rule.hasInheritanceClassName()) {
// Note that, in presence of vertical class merging, we check if the resulting class
// (i.e., the target class) satisfies the implements/extends-matcher.
- if (!satisfyInheritanceRule(targetClass, this::definitionForWithLiveTypes, rule)) {
+ if (!satisfyInheritanceRule(targetClass, rule)) {
// Try another live type since the current one doesn't satisfy the inheritance rule.
return;
}
@@ -458,12 +402,6 @@
ProguardIfRule materializedRule = rule.materialize();
runPerRule(executorService, futures, materializedRule.subsequentRule, materializedRule);
}
-
- private DexClass definitionForWithLiveTypes(DexType type) {
- assert appView.verticallyMergedClasses() == null
- || !appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(type);
- return liveTypes.contains(type) ? appView.appInfo().definitionFor(type) : null;
- }
}
private static DexDefinition testAndGetPrecondition(
@@ -593,27 +531,23 @@
return containsAnnotation(rule.getClassAnnotation(), clazz.annotations);
}
- private boolean satisfyInheritanceRule(
- DexClass clazz, Function<DexType, DexClass> definitionFor, ProguardConfigurationRule rule) {
- ProguardTypeMatcher inheritanceClassName = rule.getInheritanceClassName();
- ProguardTypeMatcher inheritanceAnnotation = rule.getInheritanceAnnotation();
- boolean extendsExpected =
- satisfyExtendsRule(clazz, definitionFor, inheritanceClassName, inheritanceAnnotation);
+ private boolean satisfyInheritanceRule(DexClass clazz, ProguardConfigurationRule rule) {
+ boolean extendsExpected = satisfyExtendsRule(clazz, rule);
boolean implementsExpected = false;
if (!extendsExpected) {
- implementsExpected =
- anyImplementedInterfaceMatches(
- clazz, definitionFor, inheritanceClassName, inheritanceAnnotation);
+ implementsExpected = satisfyImplementsRule(clazz, rule);
}
if (extendsExpected || implementsExpected) {
// Warn if users got it wrong, but only warn once.
if (rule.getInheritanceIsExtends()) {
if (implementsExpected && rulesThatUseExtendsOrImplementsWrong.add(rule)) {
+ assert options.testing.allowProguardRulesThatUseExtendsOrImplementsWrong;
options.reporter.warning(
new StringDiagnostic(
"The rule `" + rule + "` uses extends but actually matches implements."));
}
} else if (extendsExpected && rulesThatUseExtendsOrImplementsWrong.add(rule)) {
+ assert options.testing.allowProguardRulesThatUseExtendsOrImplementsWrong;
options.reporter.warning(
new StringDiagnostic(
"The rule `" + rule + "` uses implements but actually matches extends."));
@@ -623,27 +557,90 @@
return false;
}
- private boolean satisfyExtendsRule(
- DexClass clazz,
- Function<DexType, DexClass> definitionFor,
- ProguardTypeMatcher inheritanceClassName,
- ProguardTypeMatcher inheritanceAnnotation) {
- if (anySuperTypeMatches(
- clazz.superType, definitionFor, inheritanceClassName, inheritanceAnnotation)) {
+ private boolean satisfyExtendsRule(DexClass clazz, ProguardConfigurationRule rule) {
+ if (anySuperTypeMatchesExtendsRule(clazz.superType, rule)) {
return true;
}
-
// It is possible that this class used to inherit from another class X, but no longer does it,
// because X has been merged into `clazz`.
- if (appView.verticallyMergedClasses() != null) {
- // TODO(b/110141157): Figure out what to do with annotations. Should the annotations of
- // the DexClass corresponding to `sourceType` satisfy the `annotation`-matcher?
- return appView.verticallyMergedClasses().getSourcesFor(clazz.type).stream()
- .anyMatch(inheritanceClassName::matches);
+ return anySourceMatchesInheritanceRuleDirectly(clazz, rule, false);
+ }
+
+ private boolean anySuperTypeMatchesExtendsRule(DexType type, ProguardConfigurationRule rule) {
+ while (type != null) {
+ DexClass clazz = application.definitionFor(type);
+ if (clazz == null) {
+ // TODO(herhut): Warn about broken supertype chain?
+ return false;
+ }
+ // TODO(b/110141157): Should the vertical class merger move annotations from the source to
+ // the target class? If so, it is sufficient only to apply the annotation-matcher to the
+ // annotations of `class`.
+ if (rule.getInheritanceClassName().matches(clazz.type, appView)
+ && containsAnnotation(rule.getInheritanceAnnotation(), clazz.annotations)) {
+ return true;
+ }
+ type = clazz.superType;
}
return false;
}
+ private boolean satisfyImplementsRule(DexClass clazz, ProguardConfigurationRule rule) {
+ if (anyImplementedInterfaceMatchesImplementsRule(clazz, rule)) {
+ return true;
+ }
+ // It is possible that this class used to implement an interface I, but no longer does it,
+ // because I has been merged into `clazz`.
+ return anySourceMatchesInheritanceRuleDirectly(clazz, rule, true);
+ }
+
+ private boolean anyImplementedInterfaceMatchesImplementsRule(
+ DexClass clazz, ProguardConfigurationRule rule) {
+ // TODO(herhut): Maybe it would be better to do this breadth first.
+ if (clazz == null) {
+ return false;
+ }
+ for (DexType iface : clazz.interfaces.values) {
+ DexClass ifaceClass = application.definitionFor(iface);
+ if (ifaceClass == null) {
+ // TODO(herhut): Warn about broken supertype chain?
+ return false;
+ }
+ // TODO(b/110141157): Should the vertical class merger move annotations from the source to
+ // the target class? If so, it is sufficient only to apply the annotation-matcher to the
+ // annotations of `ifaceClass`.
+ if (rule.getInheritanceClassName().matches(iface, appView)
+ && containsAnnotation(rule.getInheritanceAnnotation(), ifaceClass.annotations)) {
+ return true;
+ }
+ if (anyImplementedInterfaceMatchesImplementsRule(ifaceClass, rule)) {
+ return true;
+ }
+ }
+ if (clazz.superType == null) {
+ return false;
+ }
+ DexClass superClass = application.definitionFor(clazz.superType);
+ if (superClass == null) {
+ // TODO(herhut): Warn about broken supertype chain?
+ return false;
+ }
+ return anyImplementedInterfaceMatchesImplementsRule(superClass, rule);
+ }
+
+ private boolean anySourceMatchesInheritanceRuleDirectly(
+ DexClass clazz, ProguardConfigurationRule rule, boolean isInterface) {
+ // TODO(b/110141157): Figure out what to do with annotations. Should the annotations of
+ // the DexClass corresponding to `sourceType` satisfy the `annotation`-matcher?
+ return appView.verticallyMergedClasses() != null
+ && appView.verticallyMergedClasses().getSourcesFor(clazz.type).stream()
+ .filter(
+ sourceType ->
+ appView.appInfo().definitionFor(sourceType).accessFlags.isInterface()
+ == isInterface)
+ .anyMatch(rule.getInheritanceClassName()::matches);
+ }
+
private boolean allRulesSatisfied(Collection<ProguardMemberRule> memberKeepRules,
DexClass clazz) {
for (ProguardMemberRule rule : memberKeepRules) {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index e6fd878..d83ad75 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -453,6 +453,7 @@
? NondeterministicIROrdering.getInstance()
: IdentityIROrdering.getInstance();
+ public boolean allowProguardRulesThatUseExtendsOrImplementsWrong = true;
public boolean alwaysUsePessimisticRegisterAllocation = false;
public boolean invertConditionals = false;
public boolean placeExceptionalBlocksLast = false;
diff --git a/src/test/java/com/android/tools/r8/JacocoRegressionTest.java b/src/test/java/com/android/tools/r8/JacocoRegressionTest.java
new file mode 100644
index 0000000..235b7c6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/JacocoRegressionTest.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/** Jacoco does invalid instrumentation when R8 clobber locals of arguments: TODO(b/117589870) */
+public class JacocoRegressionTest extends TestBase implements Opcodes {
+
+ @Test
+ public void test() throws Exception {
+ Path output = temp.newFolder().toPath();
+ String name = "Test";
+ String desc = DescriptorUtils.javaTypeToDescriptor(name);
+ byte[] bytes = dump();
+ Path path = output.resolve("out.jar");
+ ArchiveConsumer archiveConsumer = new ArchiveConsumer(path);
+ archiveConsumer.accept(ByteDataView.of(bytes), desc, null);
+ archiveConsumer.finished(null);
+
+ String expected = "15" + System.lineSeparator();
+ ProcessResult result = ToolHelper.runJava(path, name);
+ assertEquals(expected, result.stdout);
+
+ Path agentOutput = output.resolve("agent.out");
+ ProcessResult result1 =
+ ToolHelper.runJava(
+ path,
+ String.format(
+ "-javaagent:%s=destfile=%s,dumponexit=true,output=file",
+ ToolHelper.JACOCO_AGENT, agentOutput),
+ name);
+ assertEquals(1, result1.exitCode);
+ assertTrue(result1.toString().contains("java.lang.VerifyError: Bad local variable type"));
+ }
+
+ public static byte[] dump() throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+ AnnotationVisitor annotationVisitor0;
+
+ classWriter.visit(
+ V1_8, ACC_FINAL | ACC_SUPER | ACC_PUBLIC, "Test", null, "java/lang/Object", null);
+ classWriter.visitSource("Test.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(32, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable("this", "LTest;", null, label0, label1, 0);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+
+ // This is the problematic part for Jacoco, where we clobber local 1 with long_2nd
+ {
+ methodVisitor = classWriter.visitMethod(ACC_STATIC, "foo", "(I)I", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ILOAD, 0);
+ methodVisitor.visitInsn(I2L);
+ methodVisitor.visitVarInsn(LSTORE, 0);
+ methodVisitor.visitIntInsn(BIPUSH, 15);
+ methodVisitor.visitInsn(IRETURN);
+ methodVisitor.visitMaxs(4, 4);
+ methodVisitor.visitEnd();
+ }
+
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(6, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitIntInsn(BIPUSH, 42);
+ methodVisitor.visitMethodInsn(INVOKESTATIC, "Test", "foo", "(I)I", false);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(7, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 8ae0dad..fa8ca13 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -104,6 +104,7 @@
private static final String PROGUARD5_2_1 = "third_party/proguard/proguard5.2.1/bin/proguard";
private static final String PROGUARD6_0_1 = "third_party/proguard/proguard6.0.1/bin/proguard";
private static final String PROGUARD = PROGUARD5_2_1;
+ public static final String JACOCO_AGENT = "third_party/jacoco/org.jacoco.agent-0.8.2-runtime.jar";
private static final String RETRACE6_0_1 = "third_party/proguard/proguard6.0.1/bin/retrace";
private static final String RETRACE = RETRACE6_0_1;
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
index 18783750..b026c31 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
@@ -60,7 +60,6 @@
// TODO(b/110141157):
// - Add tests where fields and methods get renamed due to naming conflicts.
-// - Add tests where the type in a implements clause has changed.
// - Add tests where the then-clause of an -if rule keeps a class that has been merged into another.
@RunWith(Parameterized.class)
public class IfRuleWithVerticalClassMerging extends TestBase {
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeDirectlyTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeDirectlyTest.java
new file mode 100644
index 0000000..6e8b394
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeDirectlyTest.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.verticalclassmerging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+
+public class ImplementsMergedTypeDirectlyTest extends MergedTypeBaseTest {
+
+ static class TestClass implements K {
+
+ public static void main(String[] args) {
+ System.out.print("Hello world");
+ }
+ }
+
+ public ImplementsMergedTypeDirectlyTest(Backend backend, boolean enableVerticalClassMerging) {
+ super(backend, enableVerticalClassMerging);
+ }
+
+ @Override
+ public Class<?> getTestClass() {
+ return TestClass.class;
+ }
+
+ @Override
+ public String getConditionForProguardIfRule() {
+ // After class merging, TestClass will no longer implement K, but we should still keep the
+ // class Unused in the output.
+ return "-if class **$TestClass implements **$K";
+ }
+
+ @Override
+ public String getExpectedStdout() {
+ return "Hello world";
+ }
+
+ public void inspect(CodeInspector inspector) {
+ super.inspect(inspector);
+
+ if (enableVerticalClassMerging) {
+ // Check that TestClass no longer implements K.
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
+ assertTrue(testClassSubject.getDexClass().interfaces.isEmpty());
+
+ // Check that K is no longer present.
+ assertThat(inspector.clazz(K.class), not(isPresent()));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeIndirectlyTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeIndirectlyTest.java
new file mode 100644
index 0000000..4583978
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeIndirectlyTest.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.verticalclassmerging;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+
+public class ImplementsMergedTypeIndirectlyTest extends MergedTypeBaseTest {
+
+ static class TestClass implements J {
+
+ public static void main(String[] args) {
+ System.out.print("Hello world");
+ }
+ }
+
+ public ImplementsMergedTypeIndirectlyTest(Backend backend, boolean enableVerticalClassMerging) {
+ super(backend, enableVerticalClassMerging);
+ }
+
+ @Override
+ public Class<?> getTestClass() {
+ return TestClass.class;
+ }
+
+ @Override
+ public String getAdditionalKeepRules() {
+ // Keep interface J to prevent it from being merged into TestClass.
+ return "-keep class **$J";
+ }
+
+ @Override
+ public String getConditionForProguardIfRule() {
+ // After class merging, J will no longer extend I (and therefore, TestClass will no longer
+ // implement I indirectly), but we should still keep the class Unused in the output.
+ return "-if class **$TestClass implements **$I";
+ }
+
+ @Override
+ public String getExpectedStdout() {
+ return "Hello world";
+ }
+
+ public void inspect(CodeInspector inspector) {
+ super.inspect(inspector);
+
+ // Verify that TestClass still implements J.
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertEquals(J.class.getTypeName(), testClassSubject.getDexClass().interfaces.toSourceString());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
index d2ce210..ac341eb 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
@@ -28,7 +28,8 @@
public abstract class MergedTypeBaseTest extends TestBase {
private final List<Class> CLASSES =
- ImmutableList.of(A.class, B.class, C.class, Unused.class, getTestClass());
+ ImmutableList.of(
+ A.class, B.class, C.class, I.class, J.class, K.class, Unused.class, getTestClass());
static class A {}
@@ -36,6 +37,12 @@
static class C {}
+ interface I {}
+
+ interface J extends I {}
+
+ interface K {}
+
static class Unused {}
final Backend backend;
@@ -58,6 +65,10 @@
public abstract Class<?> getTestClass();
+ public String getAdditionalKeepRules() {
+ return "";
+ }
+
public abstract String getConditionForProguardIfRule();
public abstract String getExpectedStdout();
@@ -65,9 +76,10 @@
public void inspect(CodeInspector inspector) {
assertThat(inspector.clazz(Unused.class), isPresent());
- // Verify that A is no longer present when vertical class merging is enabled.
+ // Verify that A and I are no longer present when vertical class merging is enabled.
if (enableVerticalClassMerging) {
assertThat(inspector.clazz(A.class), not(isPresent()));
+ assertThat(inspector.clazz(I.class), not(isPresent()));
}
}
@@ -82,7 +94,8 @@
" public static void main(java.lang.String[]);",
"}",
getConditionForProguardIfRule(),
- "-keep class " + Unused.class.getTypeName());
+ "-keep class " + Unused.class.getTypeName(),
+ getAdditionalKeepRules());
AndroidApp output = compileWithR8(readClasses(CLASSES), config, this::configure, backend);
assertEquals(expected, runOnVM(output, getTestClass(), backend));
inspect(new CodeInspector(output));
@@ -92,6 +105,10 @@
options.enableMinification = false;
options.enableVerticalClassMerging = enableVerticalClassMerging;
+ // To ensure that the handling of extends and implements rules work as intended,
+ // and that we don't end up keeping `Unused` only because one of the two implementations work.
+ options.testing.allowProguardRulesThatUseExtendsOrImplementsWrong = false;
+
// TODO(b/110148109): Allow ordinary method inlining when -if rules work with inlining.
options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
}
diff --git a/third_party/jacoco.tar.gz.sha1 b/third_party/jacoco.tar.gz.sha1
new file mode 100644
index 0000000..8a0462f
--- /dev/null
+++ b/third_party/jacoco.tar.gz.sha1
@@ -0,0 +1 @@
+b968df99f88434e2a2a35445ac860ee6a2fa16e3
\ No newline at end of file