| // Copyright (c) 2019, 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; | |
| import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; | |
| import static org.hamcrest.MatcherAssert.assertThat; | |
| import static org.hamcrest.core.AllOf.allOf; | |
| import static org.hamcrest.core.StringContains.containsString; | |
| import static org.junit.Assert.assertFalse; | |
| import static org.junit.Assert.assertTrue; | |
| import com.android.tools.r8.BaseCompilerCommand; | |
| import com.android.tools.r8.ProguardVersion; | |
| import com.android.tools.r8.TestBase; | |
| import com.android.tools.r8.TestCompileResult; | |
| import com.android.tools.r8.TestParameters; | |
| import com.android.tools.r8.TestParametersCollection; | |
| import com.android.tools.r8.TestRunResult; | |
| import com.android.tools.r8.TestShrinkerBuilder; | |
| import com.android.tools.r8.compatproguard.CompatKeepClassMemberNamesTest.Bar; | |
| import com.android.tools.r8.utils.codeinspector.ClassSubject; | |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; | |
| import com.android.tools.r8.utils.codeinspector.FieldSubject; | |
| import com.android.tools.r8.utils.codeinspector.MethodSubject; | |
| import com.android.tools.r8.utils.internal.StringUtils; | |
| import com.google.common.collect.ImmutableList; | |
| import java.util.Collection; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.Parameterized; | |
| import org.junit.runners.Parameterized.Parameter; | |
| import org.junit.runners.Parameterized.Parameters; | |
| /** Regression test for compatibility with Proguard -keepclassmember{s,names}. b/119076934. */ | |
| @RunWith(Parameterized.class) | |
| public class CompatKeepClassMemberNamesTestRunner extends TestBase { | |
| private static final ProguardVersion PG = ProguardVersion.getLatest(); | |
| private static Class<?> MAIN_CLASS = CompatKeepClassMemberNamesTest.class; | |
| private static Class<?> BAR_CLASS = CompatKeepClassMemberNamesTest.Bar.class; | |
| private static Collection<Class<?>> CLASSES = ImmutableList.of(MAIN_CLASS, BAR_CLASS); | |
| private static String KEEP_RULE = | |
| "class " | |
| + Bar.class.getTypeName() | |
| + " { static " | |
| + Bar.class.getTypeName() | |
| + " instance(); void <init>(); int i; }"; | |
| private static String KEEP_RULE_NON_STATIC = | |
| "class " + Bar.class.getTypeName() + " { void <init>(); int i; }"; | |
| private static String EXPECTED = StringUtils.lines("42", "null"); | |
| @Parameters(name = "{0}") | |
| public static TestParametersCollection data() { | |
| return getTestParameters().withCfRuntimes().build(); | |
| } | |
| @Parameter(0) | |
| public TestParameters parameters; | |
| // Test reference implementation. | |
| @Test | |
| public void testJvm() throws Exception { | |
| parameters.assumeJvmTestParameters(); | |
| testForJvm(parameters) | |
| .addProgramClasses(CLASSES) | |
| .run(parameters.getRuntime(), MAIN_CLASS) | |
| .assertSuccessWithOutput(EXPECTED); | |
| } | |
| // Helpers to check that the Bar is absent or the Bar.instance() call has not been inlined. | |
| private <CR extends TestCompileResult<CR, RR>, RR extends TestRunResult<RR>> | |
| void assertBarIsAbsent(CR compileResult) throws Exception { | |
| compileResult | |
| .inspect( | |
| inspector -> { | |
| assertTrue(inspector.clazz(MAIN_CLASS).isPresent()); | |
| assertFalse(inspector.clazz(BAR_CLASS).isPresent()); | |
| }) | |
| .run(parameters.getRuntime(), MAIN_CLASS) | |
| .assertFailureWithErrorThatMatches( | |
| allOf( | |
| containsString("ClassNotFoundException"), containsString(BAR_CLASS.getTypeName()))); | |
| } | |
| private static void assertBarGetInstanceIsNotInlined(CodeInspector inspector) { | |
| assertTrue(inspector.clazz(BAR_CLASS).uniqueMethodWithOriginalName("instance").isPresent()); | |
| assertTrue( | |
| inspector | |
| .clazz(MAIN_CLASS) | |
| .uniqueMethodWithOriginalName("main") | |
| .streamInstructions() | |
| .anyMatch(i -> i.isInvoke() && i.getMethod().qualifiedName().contains("instance"))); | |
| } | |
| // Tests with just keep main and no additional rules. | |
| private < | |
| C extends BaseCompilerCommand, | |
| B extends BaseCompilerCommand.Builder<C, B>, | |
| CR extends TestCompileResult<CR, RR>, | |
| RR extends TestRunResult<RR>, | |
| T extends TestShrinkerBuilder<C, B, CR, RR, T>> | |
| void testWithoutRules(TestShrinkerBuilder<C, B, CR, RR, T> builder) throws Exception { | |
| assertBarIsAbsent( | |
| builder | |
| .addProgramClasses(CLASSES) | |
| .addKeepMainRule(MAIN_CLASS) | |
| .addInliningAnnotations() | |
| .addDontObfuscate() | |
| .compile()); | |
| } | |
| @Test | |
| public void testWithoutRulesPG() throws Exception { | |
| testWithoutRules(testForProguard(PG)); | |
| } | |
| @Test | |
| public void testWithoutRulesCompatR8() throws Exception { | |
| testWithoutRules(testForR8Compat(parameters.getBackend())); | |
| } | |
| @Test | |
| public void testWithoutRulesFullR8() throws Exception { | |
| // Running without rules is the same in full mode as for compat mode. The class is removed. | |
| testWithoutRules(testForR8(parameters.getBackend())); | |
| } | |
| // Tests for -keepclassmembers and *no* minification. | |
| private < | |
| C extends BaseCompilerCommand, | |
| B extends BaseCompilerCommand.Builder<C, B>, | |
| CR extends TestCompileResult<CR, RR>, | |
| RR extends TestRunResult<RR>, | |
| T extends TestShrinkerBuilder<C, B, CR, RR, T>> | |
| T buildWithMembersRule(TestShrinkerBuilder<C, B, CR, RR, T> builder) { | |
| return buildWithMembersRuleEnableMinification(builder).addDontObfuscate(); | |
| } | |
| private <CR extends TestCompileResult<CR, RR>, RR extends TestRunResult<RR>> | |
| void assertMembersRuleCompatResult(CR compileResult) throws Exception { | |
| compileResult | |
| .inspect( | |
| inspector -> { | |
| assertTrue(inspector.clazz(MAIN_CLASS).isPresent()); | |
| assertTrue(inspector.clazz(BAR_CLASS).isPresent()); | |
| assertBarGetInstanceIsNotInlined(inspector); | |
| assertTrue(inspector.clazz(BAR_CLASS).uniqueFieldWithOriginalName("i").isPresent()); | |
| assertTrue( | |
| inspector.clazz(BAR_CLASS).uniqueMethodWithOriginalName("<init>").isPresent()); | |
| }) | |
| .run(parameters.getRuntime(), MAIN_CLASS) | |
| .assertSuccessWithOutput(EXPECTED); | |
| } | |
| @Test | |
| public void testWithMembersRulePG() throws Exception { | |
| assertMembersRuleCompatResult(buildWithMembersRule(testForProguard(PG)).compile()); | |
| } | |
| @Test | |
| public void testWithMembersRuleCompatR8() throws Exception { | |
| assertMembersRuleCompatResult( | |
| buildWithMembersRule(testForR8Compat(parameters.getBackend())).compile()); | |
| } | |
| @Test | |
| public void testWithMembersRuleFullR8() throws Exception { | |
| // When a class is only referenced (not instantiated), full mode R8 will only keep the static | |
| // members specified by the -keepclassmembers rule. | |
| buildWithMembersRule(testForR8(parameters.getBackend())) | |
| .compile() | |
| .inspect( | |
| inspector -> { | |
| assertTrue(inspector.clazz(MAIN_CLASS).isPresent()); | |
| assertTrue(inspector.clazz(BAR_CLASS).isPresent()); | |
| assertBarGetInstanceIsNotInlined(inspector); | |
| assertThat(inspector.clazz(BAR_CLASS).init(), isPresent()); | |
| assertThat(inspector.clazz(BAR_CLASS).uniqueFieldWithOriginalName("i"), isPresent()); | |
| }); | |
| } | |
| @Test | |
| public void testWithMembersRuleAndKeepBarRuleFullR8() throws Exception { | |
| // If we keep the Bar class too, we get the same behavior in full as in PG/Compat. | |
| assertMembersRuleCompatResult( | |
| buildWithMembersRule(testForR8(parameters.getBackend())) | |
| .addKeepClassRules(BAR_CLASS) | |
| .compile()); | |
| } | |
| // Tests for non-static -keepclassmembers and *no* minification. | |
| private < | |
| C extends BaseCompilerCommand, | |
| B extends BaseCompilerCommand.Builder<C, B>, | |
| CR extends TestCompileResult<CR, RR>, | |
| RR extends TestRunResult<RR>, | |
| T extends TestShrinkerBuilder<C, B, CR, RR, T>> | |
| T buildWithNonStaticMembersRule(TestShrinkerBuilder<C, B, CR, RR, T> builder) { | |
| return builder | |
| .addProgramClasses(CLASSES) | |
| .addKeepMainRule(MAIN_CLASS) | |
| .addKeepRules("-keepclassmembers " + KEEP_RULE_NON_STATIC) | |
| .addInliningAnnotations() | |
| .addDontObfuscate(); | |
| } | |
| @Test | |
| public void testWithNonStaticMembersRulePG() throws Exception { | |
| assertBarIsAbsent(buildWithNonStaticMembersRule(testForProguard(PG)).compile()); | |
| } | |
| @Test | |
| public void testWithNonStaticMembersRuleCompatR8() throws Exception { | |
| assertBarIsAbsent( | |
| buildWithNonStaticMembersRule(testForR8Compat(parameters.getBackend())).compile()); | |
| } | |
| @Test | |
| public void testWithNonStaticMembersRuleFullR8() throws Exception { | |
| assertBarIsAbsent(buildWithNonStaticMembersRule(testForR8(parameters.getBackend())).compile()); | |
| } | |
| // Tests for -keepclassmembers and minification. | |
| private < | |
| C extends BaseCompilerCommand, | |
| B extends BaseCompilerCommand.Builder<C, B>, | |
| CR extends TestCompileResult<CR, RR>, | |
| RR extends TestRunResult<RR>, | |
| T extends TestShrinkerBuilder<C, B, CR, RR, T>> | |
| T buildWithMembersRuleEnableMinification(TestShrinkerBuilder<C, B, CR, RR, T> builder) { | |
| return builder | |
| .addProgramClasses(CLASSES) | |
| .addKeepMainRule(MAIN_CLASS) | |
| .addKeepRules("-keepclassmembers " + KEEP_RULE) | |
| .addInliningAnnotations(); | |
| } | |
| private <CR extends TestCompileResult<CR, RR>, RR extends TestRunResult<RR>> | |
| void assertMembersRuleEnableMinificationCompatResult(CR compileResult) throws Exception { | |
| compileResult | |
| .inspect( | |
| inspector -> { | |
| assertTrue(inspector.clazz(MAIN_CLASS).isPresent()); | |
| assertBarGetInstanceIsNotInlined(inspector); | |
| // Bar is renamed but its member names are kept. | |
| ClassSubject bar = inspector.clazz(BAR_CLASS); | |
| assertTrue(bar.isRenamed()); | |
| FieldSubject barI = bar.uniqueFieldWithOriginalName("i"); | |
| assertTrue(barI.isPresent()); | |
| assertFalse(barI.isRenamed()); | |
| MethodSubject barInit = bar.uniqueMethodWithOriginalName("<init>"); | |
| assertTrue(barInit.isPresent()); | |
| assertFalse(barInit.isRenamed()); | |
| MethodSubject barInstance = bar.uniqueMethodWithOriginalName("instance"); | |
| assertTrue(barInstance.isPresent()); | |
| assertFalse(barInstance.isRenamed()); | |
| }) | |
| .run(parameters.getRuntime(), MAIN_CLASS) | |
| // Allowing minification will rename Bar, failing the reflective get. | |
| .assertFailureWithErrorThatMatches( | |
| allOf( | |
| containsString("ClassNotFoundException"), containsString(BAR_CLASS.getTypeName()))); | |
| } | |
| @Test | |
| public void testWithMembersRuleEnableMinificationPG() throws Exception { | |
| assertMembersRuleEnableMinificationCompatResult( | |
| buildWithMembersRuleEnableMinification(testForProguard(PG)).compile()); | |
| } | |
| @Test | |
| public void testWithMembersRuleEnableMinificationCompatR8() throws Exception { | |
| assertMembersRuleEnableMinificationCompatResult( | |
| buildWithMembersRuleEnableMinification(testForR8Compat(parameters.getBackend())).compile()); | |
| } | |
| @Test | |
| public void testWithMembersRuleEnableMinificationFullR8() throws Exception { | |
| // When a class is only referenced (not instantiated), full mode R8 will only keep the static | |
| // members specified by the -keepclassmembers rule. | |
| buildWithMembersRuleEnableMinification(testForR8(parameters.getBackend())) | |
| .compile() | |
| .inspect( | |
| inspector -> { | |
| assertTrue(inspector.clazz(MAIN_CLASS).isPresent()); | |
| assertTrue(inspector.clazz(BAR_CLASS).isPresent()); | |
| assertBarGetInstanceIsNotInlined(inspector); | |
| assertThat( | |
| inspector.clazz(BAR_CLASS).uniqueMethodWithOriginalName("<init>"), isPresent()); | |
| assertThat(inspector.clazz(BAR_CLASS).uniqueFieldWithOriginalName("i"), isPresent()); | |
| }); | |
| } | |
| // Tests for "-keepclassmembers class Bar", i.e, with no members specified. | |
| private < | |
| C extends BaseCompilerCommand, | |
| B extends BaseCompilerCommand.Builder<C, B>, | |
| CR extends TestCompileResult<CR, RR>, | |
| RR extends TestRunResult<RR>, | |
| T extends TestShrinkerBuilder<C, B, CR, RR, T>> | |
| void testWithMembersStarRule(TestShrinkerBuilder<C, B, CR, RR, T> builder) throws Exception { | |
| assertBarIsAbsent( | |
| builder | |
| .addProgramClasses(CLASSES) | |
| .addKeepMainRule(MAIN_CLASS) | |
| .addInliningAnnotations() | |
| .addDontObfuscate() | |
| .addKeepRules("-keepclassmembers class " + Bar.class.getTypeName()) | |
| .compile()); | |
| } | |
| @Test | |
| public void testWithMembersStarRulePG() throws Exception { | |
| testWithMembersStarRule(testForProguard(PG)); | |
| } | |
| @Test | |
| public void testWithMembersStarRuleCompatR8() throws Exception { | |
| testWithMembersStarRule(testForR8Compat(parameters.getBackend())); | |
| } | |
| @Test | |
| public void testWithMembersStarRuleFullR8() throws Exception { | |
| testWithMembersStarRule( | |
| testForR8(parameters.getBackend()).allowUnusedProguardConfigurationRules()); | |
| } | |
| // Tests for "-keepclassmembernames" and *no* minification. | |
| private < | |
| C extends BaseCompilerCommand, | |
| B extends BaseCompilerCommand.Builder<C, B>, | |
| CR extends TestCompileResult<CR, RR>, | |
| RR extends TestRunResult<RR>, | |
| T extends TestShrinkerBuilder<C, B, CR, RR, T>> | |
| T buildWithMemberNamesRule(TestShrinkerBuilder<C, B, CR, RR, T> builder) { | |
| return buildWithMemberNamesRuleEnableMinification(builder).addDontObfuscate(); | |
| } | |
| private <CR extends TestCompileResult<CR, RR>, RR extends TestRunResult<RR>> | |
| void assertMemberNamesRuleCompatResult(CR compileResult) throws Exception { | |
| compileResult | |
| .inspect( | |
| inspector -> { | |
| assertTrue(inspector.clazz(MAIN_CLASS).isPresent()); | |
| assertBarGetInstanceIsNotInlined(inspector); | |
| ClassSubject bar = inspector.clazz(BAR_CLASS); | |
| assertTrue(bar.isPresent()); | |
| assertTrue(bar.uniqueMethodWithOriginalName("instance").isPresent()); | |
| // Reflected on fields are not kept. | |
| assertFalse(bar.uniqueMethodWithOriginalName("<init>").isPresent()); | |
| assertFalse(bar.uniqueFieldWithOriginalName("i").isPresent()); | |
| }) | |
| .run(parameters.getRuntime(), MAIN_CLASS) | |
| // Keeping the instance and its accessed members does not keep the reflected <init> and i. | |
| .assertFailureWithErrorThatMatches( | |
| allOf( | |
| containsString("NoSuchMethodException"), | |
| containsString(BAR_CLASS.getTypeName() + ".<init>()"))); | |
| } | |
| @Test | |
| public void testWithMemberNamesRulePG() throws Exception { | |
| assertMemberNamesRuleCompatResult(buildWithMemberNamesRule(testForProguard(PG)).compile()); | |
| } | |
| @Test | |
| public void testWithMemberNamesRuleCompatR8() throws Exception { | |
| assertMemberNamesRuleCompatResult( | |
| buildWithMemberNamesRule(testForR8Compat(parameters.getBackend())) | |
| .enableInliningAnnotations() | |
| .compile()); | |
| } | |
| @Test | |
| public void testWithMemberNamesRuleFullR8() throws Exception { | |
| assertMemberNamesRuleCompatResult( | |
| buildWithMemberNamesRule(testForR8(parameters.getBackend())).compile()); | |
| } | |
| // Tests for "-keepclassmembernames" and minification. | |
| private < | |
| C extends BaseCompilerCommand, | |
| B extends BaseCompilerCommand.Builder<C, B>, | |
| CR extends TestCompileResult<CR, RR>, | |
| RR extends TestRunResult<RR>, | |
| T extends TestShrinkerBuilder<C, B, CR, RR, T>> | |
| T buildWithMemberNamesRuleEnableMinification(TestShrinkerBuilder<C, B, CR, RR, T> builder) { | |
| return builder | |
| .addProgramClasses(CLASSES) | |
| .addKeepMainRule(MAIN_CLASS) | |
| .addKeepRules("-keepclassmembernames " + KEEP_RULE) | |
| .addInliningAnnotations(); | |
| } | |
| private <CR extends TestCompileResult<CR, RR>, RR extends TestRunResult<RR>> | |
| void assertMemberNamesRuleEnableMinificationCompatResult(CR compileResult) throws Exception { | |
| compileResult | |
| .inspect( | |
| inspector -> { | |
| assertTrue(inspector.clazz(MAIN_CLASS).isPresent()); | |
| assertBarGetInstanceIsNotInlined(inspector); | |
| ClassSubject bar = inspector.clazz(BAR_CLASS); | |
| assertTrue(bar.isPresent()); | |
| assertTrue(bar.isRenamed()); | |
| assertFalse(bar.uniqueFieldWithOriginalName("i").isPresent()); | |
| assertFalse(bar.uniqueMethodWithOriginalName("<init>").isPresent()); | |
| MethodSubject barInstance = bar.uniqueMethodWithOriginalName("instance"); | |
| assertTrue(barInstance.isPresent()); | |
| assertFalse(barInstance.isRenamed()); | |
| }) | |
| .run(parameters.getRuntime(), MAIN_CLASS) | |
| // Allowing minification will rename Bar, failing the reflective get. | |
| .assertFailureWithErrorThatMatches( | |
| allOf( | |
| containsString("ClassNotFoundException"), containsString(BAR_CLASS.getTypeName()))); | |
| } | |
| @Test | |
| public void testWithMemberNamesRuleEnableMinificationPG() throws Exception { | |
| assertMemberNamesRuleEnableMinificationCompatResult( | |
| buildWithMemberNamesRuleEnableMinification(testForProguard(PG)).compile()); | |
| } | |
| @Test | |
| public void testWithMemberNamesRuleEnableMinificationCompatR8() throws Exception { | |
| assertMemberNamesRuleEnableMinificationCompatResult( | |
| buildWithMemberNamesRuleEnableMinification(testForR8Compat(parameters.getBackend())) | |
| .enableInliningAnnotations() | |
| .compile()); | |
| } | |
| @Test | |
| public void testWithMemberNamesRuleEnableMinificationFullR8() throws Exception { | |
| assertMemberNamesRuleEnableMinificationCompatResult( | |
| buildWithMemberNamesRuleEnableMinification(testForR8(parameters.getBackend())).compile()); | |
| } | |
| } |