blob: 412311e710754b9ec6f15fa25b60fe60e57f949c [file] [log] [blame]
// 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.NeverInline;
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.StringUtils;
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.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.Parameters;
/** Regression test for compatibility with Proguard -keepclassmember{s,names}. b/119076934. */
@RunWith(Parameterized.class)
public class CompatKeepClassMemberNamesTestRunner extends TestBase {
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, NeverInline.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();
}
private final TestParameters parameters;
public CompatKeepClassMemberNamesTestRunner(TestParameters parameters) {
this.parameters = parameters;
}
// Test reference implementation.
@Test
public void testJvm() throws Exception {
testForJvm()
.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).uniqueMethodWithName("instance").isPresent());
assertTrue(
inspector
.clazz(MAIN_CLASS)
.uniqueMethodWithName("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).noMinification().compile());
}
@Test
public void testWithoutRulesPG() throws Exception {
testWithoutRules(testForProguard());
}
@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).noMinification();
}
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).uniqueFieldWithName("i").isPresent());
assertTrue(inspector.clazz(BAR_CLASS).uniqueMethodWithName("<init>").isPresent());
})
.run(parameters.getRuntime(), MAIN_CLASS)
.assertSuccessWithOutput(EXPECTED);
}
@Test
public void testWithMembersRulePG() throws Exception {
assertMembersRuleCompatResult(buildWithMembersRule(testForProguard()).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).uniqueFieldWithName("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)
.noMinification();
}
@Test
public void testWithNonStaticMembersRulePG() throws Exception {
assertBarIsAbsent(buildWithNonStaticMembersRule(testForProguard()).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);
}
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.uniqueFieldWithName("i");
assertTrue(barI.isPresent());
assertFalse(barI.isRenamed());
MethodSubject barInit = bar.uniqueMethodWithName("<init>");
assertTrue(barInit.isPresent());
assertFalse(barInit.isRenamed());
MethodSubject barInstance = bar.uniqueMethodWithName("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()).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).uniqueMethodWithName("<init>"), isPresent());
assertThat(inspector.clazz(BAR_CLASS).uniqueFieldWithName("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)
.noMinification()
.addKeepRules("-keepclassmembers class " + Bar.class.getTypeName())
.compile());
}
@Test
public void testWithMembersStarRulePG() throws Exception {
testWithMembersStarRule(testForProguard());
}
@Test
public void testWithMembersStarRuleCompatR8() throws Exception {
testWithMembersStarRule(testForR8Compat(parameters.getBackend()));
}
@Test
public void testWithMembersStarRuleFullR8() throws Exception {
testWithMembersStarRule(testForR8(parameters.getBackend()));
}
// 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).noMinification();
}
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.uniqueMethodWithName("instance").isPresent());
// Reflected on fields are not kept.
assertFalse(bar.uniqueMethodWithName("<init>").isPresent());
assertFalse(bar.uniqueFieldWithName("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()).compile());
}
@Test
public void testWithMemberNamesRuleCompatR8() throws Exception {
assertMemberNamesRuleCompatResult(
buildWithMemberNamesRule(testForR8Compat(parameters.getBackend()))
.enableInliningAnnotations()
.compile());
}
@Test
public void testWithMemberNamesRuleFullR8() throws Exception {
assertBarIsAbsent(buildWithMemberNamesRule(testForR8Compat(parameters.getBackend())).compile());
}
// 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 buildWithMemberNamesRuleEnableMinification(TestShrinkerBuilder<C, B, CR, RR, T> builder) {
return builder
.addProgramClasses(CLASSES)
.addKeepMainRule(MAIN_CLASS)
.addKeepRules("-keepclassmembernames " + KEEP_RULE);
}
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.uniqueFieldWithName("i").isPresent());
assertFalse(bar.uniqueMethodWithName("<init>").isPresent());
MethodSubject barInstance = bar.uniqueMethodWithName("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()).compile());
}
@Test
public void testWithMemberNamesRuleEnableMinificationCompatR8() throws Exception {
assertMemberNamesRuleEnableMinificationCompatResult(
buildWithMemberNamesRuleEnableMinification(testForR8Compat(parameters.getBackend()))
.enableInliningAnnotations()
.compile());
}
@Test
public void testWithMemberNamesRuleEnableMinificationFullR8() throws Exception {
assertBarIsAbsent(
buildWithMemberNamesRuleEnableMinification(testForR8(parameters.getBackend())).compile());
}
}