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()) {