Regression test for conditional keep rule.
Bug: 160136641
Change-Id: Ia2fe9e5621feb59d189ffe77a8b3f46235e99955
diff --git a/src/test/java/com/android/tools/r8/compatproguard/ifrules/ConditionalOnInlinedFieldTest.java b/src/test/java/com/android/tools/r8/compatproguard/ifrules/ConditionalOnInlinedFieldTest.java
new file mode 100644
index 0000000..efc344e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compatproguard/ifrules/ConditionalOnInlinedFieldTest.java
@@ -0,0 +1,122 @@
+// 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.compatproguard.ifrules;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.shaking.methods.MethodsTestBase.Shrinker;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ConditionalOnInlinedFieldTest extends TestBase {
+
+ static class A {
+
+ int field = 42;
+
+ void method(String name, int field) throws Exception {
+ if (field == 42) {
+ Class<?> clazz = Class.forName(name);
+ Object object = clazz.getDeclaredConstructor().newInstance();
+ clazz.getDeclaredMethod("method").invoke(object);
+ }
+ }
+ }
+
+ static class B {
+ void method() {
+ System.out.println("B::method");
+ }
+ }
+
+ static class MainWithFieldReference {
+
+ public static void main(String[] args) throws Exception {
+ A a = new A();
+ a.method(args[0], a.field);
+ }
+ }
+
+ static class MainWithoutFieldReference {
+
+ public static void main(String[] args) throws Exception {
+ A a = new A();
+ a.method(args[0], 42);
+ }
+ }
+
+ private static String EXPECTED = StringUtils.lines("B::method");
+
+ @Parameters(name = "{0}, {1}, ref:{2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ Shrinker.values(), getTestParameters().withCfRuntimes().build(), BooleanUtils.values());
+ }
+
+ private final Shrinker shrinker;
+ private final TestParameters parameters;
+ private final boolean withFieldReference;
+
+ public ConditionalOnInlinedFieldTest(
+ Shrinker shrinker, TestParameters parameters, boolean withFieldReference) {
+ this.shrinker = shrinker;
+ this.parameters = parameters;
+ this.withFieldReference = withFieldReference;
+ }
+
+ private Class<?> getMain() {
+ return withFieldReference ? MainWithFieldReference.class : MainWithoutFieldReference.class;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForJvm()
+ .addProgramClasses(getMain(), A.class, B.class)
+ .run(parameters.getRuntime(), getMain(), B.class.getTypeName())
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private TestShrinkerBuilder<?, ?, ?, ?, ?> buildShrinker() throws Exception {
+ TestShrinkerBuilder<?, ?, ?, ?, ?> builder;
+ if (shrinker == Shrinker.Proguard) {
+ builder =
+ testForProguard()
+ .addKeepRules("-dontwarn " + ConditionalOnInlinedFieldTest.class.getTypeName());
+ } else if (shrinker == Shrinker.R8Compat) {
+ builder = testForR8Compat(parameters.getBackend());
+ } else {
+ builder = testForR8(parameters.getBackend());
+ }
+ return builder
+ .addProgramClasses(getMain(), A.class, B.class)
+ .addKeepMainRule(getMain())
+ .addKeepRules(
+ "-if class "
+ + A.class.getTypeName()
+ + " { int field; }"
+ + " -keep class "
+ + B.class.getTypeName()
+ + " { void <init>(); void method(); }");
+ }
+
+ @Test
+ public void testConditionalOnField() throws Exception {
+ TestRunResult<?> result =
+ buildShrinker().compile().run(parameters.getRuntime(), getMain(), B.class.getTypeName());
+ if (!withFieldReference && shrinker != Shrinker.Proguard) {
+ // Without the reference we expect an error. For some reason PG keeps in any case.
+ result.assertFailureWithErrorThatThrows(ClassNotFoundException.class);
+ } else {
+ result.assertSuccessWithOutput(EXPECTED);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/ifrules/ConditionalOnInlinedTest.java b/src/test/java/com/android/tools/r8/compatproguard/ifrules/ConditionalOnInlinedTest.java
new file mode 100644
index 0000000..faa9eda
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compatproguard/ifrules/ConditionalOnInlinedTest.java
@@ -0,0 +1,124 @@
+// 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.compatproguard.ifrules;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.shaking.methods.MethodsTestBase.Shrinker;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ConditionalOnInlinedTest extends TestBase {
+
+ static class A {
+ void method(String name) throws Exception {
+ Class<?> clazz = Class.forName(name);
+ Object object = clazz.getDeclaredConstructor().newInstance();
+ clazz.getDeclaredMethod("method").invoke(object);
+ }
+ }
+
+ static class B {
+ void method() {
+ System.out.println("B::method");
+ }
+ }
+
+ static class Main {
+ public static void main(String[] args) throws Exception {
+ new A().method(args[0]);
+ }
+ }
+
+ private static Class<?> MAIN_CLASS = Main.class;
+ private static Collection<Class<?>> CLASSES = ImmutableList.of(MAIN_CLASS, A.class, B.class);
+
+ private static String EXPECTED = StringUtils.lines("B::method");
+
+ @Parameters(name = "{0}, {1}")
+ public static List<Object[]> data() {
+ return buildParameters(Shrinker.values(), getTestParameters().withCfRuntimes().build());
+ }
+
+ private final Shrinker shrinker;
+ private final TestParameters parameters;
+
+ public ConditionalOnInlinedTest(Shrinker shrinker, TestParameters parameters) {
+ this.shrinker = shrinker;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForJvm()
+ .addProgramClasses(CLASSES)
+ .run(parameters.getRuntime(), MAIN_CLASS, B.class.getTypeName())
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private TestShrinkerBuilder<?, ?, ?, ?, ?> buildShrinker() throws Exception {
+ TestShrinkerBuilder<?, ?, ?, ?, ?> builder;
+ if (shrinker == Shrinker.Proguard) {
+ builder =
+ testForProguard()
+ .addKeepRules("-dontwarn " + ConditionalOnInlinedTest.class.getTypeName());
+ } else if (shrinker == Shrinker.R8Compat) {
+ builder = testForR8Compat(parameters.getBackend());
+ } else {
+ builder = testForR8(parameters.getBackend());
+ }
+ return builder.addProgramClasses(CLASSES).addKeepMainRule(MAIN_CLASS);
+ }
+
+ @Test
+ public void testConditionalOnClass() throws Exception {
+ TestRunResult<?> result =
+ buildShrinker()
+ .addKeepRules(
+ "-if class "
+ + A.class.getTypeName()
+ + " -keep class "
+ + B.class.getTypeName()
+ + " { void <init>(); void method(); }")
+ .compile()
+ .run(parameters.getRuntime(), MAIN_CLASS, B.class.getTypeName());
+ if (shrinker != Shrinker.Proguard) {
+ // TODO(b/160136641): The conditional rule fails to apply after class A is inlined/removed.
+ result.assertFailureWithErrorThatThrows(ClassNotFoundException.class);
+ } else {
+ result.assertSuccessWithOutput(EXPECTED);
+ }
+ }
+
+ @Test
+ public void testConditionalOnClassAndMethod() throws Exception {
+ TestRunResult<?> result =
+ buildShrinker()
+ .addKeepRules(
+ "-if class "
+ + A.class.getTypeName()
+ + " { void method(java.lang.String); }"
+ + " -keep class "
+ + B.class.getTypeName()
+ + " { void <init>(); void method(); }")
+ .compile()
+ .run(parameters.getRuntime(), MAIN_CLASS, B.class.getTypeName());
+ if (shrinker == Shrinker.Proguard) {
+ // In this case PG appears to not actually keep the consequent, but it does remain renamed
+ // in the output.
+ result.assertFailureWithErrorThatThrows(ClassNotFoundException.class);
+ } else {
+ result.assertSuccessWithOutput(EXPECTED);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index ac30cb7..43ba1fb 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -48,6 +48,7 @@
import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -308,6 +309,10 @@
return builder.build();
}
+ public FieldSubject field(Field field) {
+ return field(Reference.fieldFromField(field));
+ }
+
public FieldSubject field(FieldReference field) {
ClassSubject clazz = clazz(field.getHolderClass());
if (!clazz.isPresent()) {