blob: f823fcce9d6ec0ff8067e6cc291285842d4f4a92 [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 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.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
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);
private static String EXPLICIT_RULE =
"class "
+ Bar.class.getTypeName()
+ " { static "
+ Bar.class.getTypeName()
+ " instance(); 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
public void testJvm() throws Exception {
testForJvm()
.addProgramClasses(CLASSES)
.run(parameters.getRuntime(), MAIN_CLASS)
.assertSuccessWithOutput(EXPECTED);
}
@Test
public void testWithoutRulesPG() throws Exception {
testForProguard()
.addProgramClasses(CLASSES)
.addKeepMainRule(MAIN_CLASS)
.noMinification()
.compile()
.inspect(
inspector -> {
assertTrue(inspector.clazz(MAIN_CLASS).isPresent());
// Bar.instance() is inlined away allowing Bar to be removed fully.
assertFalse(inspector.clazz(BAR_CLASS).isPresent());
})
.run(parameters.getRuntime(), MAIN_CLASS)
.assertFailureWithErrorThatMatches(
allOf(
containsString("ClassNotFoundException"), containsString(BAR_CLASS.getTypeName())));
}
@Test
public void testWithMembersRulePG() throws Exception {
testForProguard()
.addProgramClasses(CLASSES)
.addKeepMainRule(MAIN_CLASS)
.noMinification()
.addKeepRules("-keepclassmembers " + EXPLICIT_RULE)
.compile()
.inspect(
inspector -> {
assertTrue(inspector.clazz(MAIN_CLASS).isPresent());
assertTrue(inspector.clazz(BAR_CLASS).isPresent());
assertBarGetInstanceIsNotInlined(inspector);
assertTrue(inspector.clazz(BAR_CLASS).uniqueFieldWithName("i").isPresent());
})
.run(parameters.getRuntime(), MAIN_CLASS)
.assertSuccessWithOutput(EXPECTED);
}
@Test
public void testWithMembersRuleAllowRenamingPG() throws Exception {
testForProguard()
.addProgramClasses(CLASSES)
.addKeepMainRule(MAIN_CLASS)
.addKeepRules("-keepclassmembers " + EXPLICIT_RULE)
.compile()
.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 testWithMembersStarRulePG() throws Exception {
testForProguard()
.addProgramClasses(CLASSES)
.addKeepMainRule(MAIN_CLASS)
.noMinification()
.addKeepRules("-keepclassmembers class " + Bar.class.getTypeName())
.compile()
.inspect(
inspector -> {
assertTrue(inspector.clazz(MAIN_CLASS).isPresent());
// A rule without body does not imply { *; }, thus Bar is removed.
assertFalse(inspector.clazz(BAR_CLASS).isPresent());
})
.run(parameters.getRuntime(), MAIN_CLASS)
.assertFailureWithErrorThatMatches(
allOf(
containsString("ClassNotFoundException"), containsString(BAR_CLASS.getTypeName())));
}
@Test
public void testWithMemberNamesRulePG() throws Exception {
testForProguard()
.addProgramClasses(CLASSES)
.addKeepMainRule(MAIN_CLASS)
.noMinification()
.addKeepRules("-keepclassmembernames " + EXPLICIT_RULE)
.compile()
.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 testWithMemberNamesRuleAllowRenamingPG() throws Exception {
testForProguard()
.addProgramClasses(CLASSES)
.addKeepMainRule(MAIN_CLASS)
.addKeepRules("-keepclassmembernames " + EXPLICIT_RULE)
.compile()
.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())));
}
private static void assertBarGetInstanceIsNotInlined(CodeInspector inspector) {
assertTrue(
inspector
.clazz(MAIN_CLASS)
.uniqueMethodWithName("main")
.streamInstructions()
.anyMatch(i -> i.isInvoke() && i.getMethod().qualifiedName().contains("instance")));
}
}