blob: 056419719f1aaf957cf2e62fec126ef1716aa0d8 [file] [log] [blame]
// 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.shaking;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticException;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.R8;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.android.tools.r8.utils.graphinspector.GraphInspector;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
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 KeepAnnotatedMemberTest extends TestBase {
private static final Path R8_JAR = Paths.get(ToolHelper.THIRD_PARTY_DIR, "r8", "r8.jar");
private static final String ABSENT_ANNOTATION = "com.android.tools.r8.MissingAnnotation";
private static final String PRESENT_ANNOTATION =
"com.android.tools.r8.com.google.common.annotations.VisibleForTesting";
private static final String CLASS_WITH_ANNOTATED_METHOD =
"com.android.tools.r8.com.google.common.math.IntMath";
private static final String ANNOTATED_METHOD = "lessThanBranchFree";
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withNoneRuntime().build();
}
public KeepAnnotatedMemberTest(TestParameters parameters) {
parameters.assertNoneRuntime();
}
@Test
public void testPresence() throws Exception {
CodeInspector inspector = new CodeInspector(R8_JAR);
assertThat(inspector.clazz(ABSENT_ANNOTATION), not(isPresent()));
assertThat(inspector.clazz(PRESENT_ANNOTATION), isPresent());
ClassSubject clazz = inspector.clazz(CLASS_WITH_ANNOTATED_METHOD);
MethodSubject method = clazz.uniqueMethodWithName(ANNOTATED_METHOD);
assertThat(method, isPresent());
}
// TODO(b/159966986): A general keep rule should not cause compiler assertion errors.
@Test(expected = CompilationFailedException.class)
public void testPresentAnnotation() throws Exception {
testForR8(Backend.CF)
.addProgramFiles(R8_JAR)
.addKeepRules("-keep class * { @" + PRESENT_ANNOTATION + " *; }")
.addDontWarnGoogle()
.addDontWarnJavax()
.addDontWarn("org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement")
.compileWithExpectedDiagnostics(
diagnostics ->
diagnostics.assertErrorsMatch(diagnosticException(AssertionError.class)));
}
@Test
public void testPresentAnnotationSplit() throws Exception {
// These rules should be equivalent to the above.
testForR8(Backend.CF)
.addProgramFiles(R8_JAR)
.addKeepRules(
"-keep class *", "-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *; }")
.addDontWarnGoogle()
.addDontWarnJavax()
.addDontWarn("org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement")
.compile();
}
@Test
public void testWithMembersAbsentAnnotation() throws Exception {
testForR8(Backend.CF)
.addProgramFiles(R8_JAR)
.allowUnusedProguardConfigurationRules()
.addKeepRules("-keepclasseswithmembers class * { @" + ABSENT_ANNOTATION + " *; }")
.compile()
.inspect(inspector -> assertEquals(0, inspector.allClasses().size()));
}
@Test
public void testWithMembersPresentAnnotation() throws Exception {
testForR8(Backend.CF)
.addProgramFiles(R8_JAR)
.addKeepRules("-keepclasseswithmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
.addDontWarnGoogle()
.addDontWarnJavax()
.addDontWarn("org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement")
.compile()
.inspect(
inspector -> {
ClassSubject clazz = inspector.clazz(CLASS_WITH_ANNOTATED_METHOD);
assertThat(clazz, isPresent());
assertThat(clazz.uniqueMethodWithName(ANNOTATED_METHOD), isPresent());
});
}
@Test
public void testUnsatisfiedClassMembersPresentAnnotation() throws Exception {
testForR8(Backend.CF)
.addProgramFiles(R8_JAR)
// TODO(b/159971974): Technically this rule does not hit anything and should fail due to
// missing allowUnusedProguardConfigurationRules()
.addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
.compile()
.inspect(inspector -> assertEquals(0, inspector.allClasses().size()));
}
@Test
public void testSatisfiedClassMembersPresentAnnotation() throws Exception {
testForR8(Backend.CF)
.addProgramFiles(R8_JAR)
.addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
.addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
.compile()
.inspect(
inspector -> {
assertEquals(1, inspector.allClasses().size());
List<FoundMethodSubject> methods =
inspector.clazz(CLASS_WITH_ANNOTATED_METHOD).allMethods();
assertEquals(
1, methods.stream().filter(m -> m.getOriginalName().equals("<init>")).count());
assertEquals(
1,
methods.stream()
.filter(m -> m.getOriginalName().equals(ANNOTATED_METHOD))
.count());
assertEquals(2, methods.size());
});
}
@Test
public void testUnsatisfiedConditionalPresentAnnotation() throws Exception {
testForR8(Backend.CF)
.addProgramFiles(R8_JAR)
.allowUnusedProguardConfigurationRules()
.addKeepRules("-if class * -keep class <1> { @" + PRESENT_ANNOTATION + " *** *(...); }")
.compile()
.inspect(inspector -> assertEquals(0, inspector.allClasses().size()));
}
@Test
public void testSatisfiedConditionalPresentAnnotation() throws Exception {
testForR8(Backend.CF)
.addProgramFiles(R8_JAR)
.addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
.addKeepRules("-if class * -keep class <1> { @" + PRESENT_ANNOTATION + " *** *(...); }")
.compile()
.inspect(
inspector -> {
assertEquals(1, inspector.allClasses().size());
List<FoundMethodSubject> methods =
inspector.clazz(CLASS_WITH_ANNOTATED_METHOD).allMethods();
assertEquals(
1, methods.stream().filter(m -> m.getOriginalName().equals("<init>")).count());
// TODO(b/132318609): This should have the annotated method, but does not due to the
// annotation being removed.
assertEquals(
0,
methods.stream()
.filter(m -> m.getOriginalName().equals(ANNOTATED_METHOD))
.count());
assertEquals(1, methods.size());
});
}
@Test
public void testConditionalEqualsKeepClassMembers() throws Exception {
GraphInspector referenceInspector =
testForR8(Backend.CF)
.enableGraphInspector()
.addProgramFiles(R8_JAR)
.addKeepMainRule(R8.class)
.addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
// TODO(b/132318609): Remove keep annotation once fixed.
.addKeepClassRules(PRESENT_ANNOTATION)
.addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
.addDontWarnGoogle()
.addDontWarnJavaxNullableAnnotation()
.compile()
.graphInspector();
GraphInspector ifThenKeepClassMembersInspector =
testForR8(Backend.CF)
.enableGraphInspector()
.addProgramFiles(R8_JAR)
.addKeepMainRule(R8.class)
.addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
// TODO(b/132318609): Remove keep annotation once fixed.
.addKeepClassRules(PRESENT_ANNOTATION)
.addKeepRules(
"-if class * "
+ "-keepclassmembers class <1> { @"
+ PRESENT_ANNOTATION
+ " *** *(...); }")
.addDontWarnGoogle()
.addDontWarnJavaxNullableAnnotation()
.compile()
.graphInspector();
assertRetainedClassesEqual(referenceInspector, ifThenKeepClassMembersInspector, false, false);
GraphInspector ifThenKeepClassesWithMembersInspector =
testForR8(Backend.CF)
.enableGraphInspector()
.addProgramFiles(R8_JAR)
.addKeepMainRule(R8.class)
.addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
// TODO(b/132318609): Remove keep annotation once fixed.
.addKeepClassRules(PRESENT_ANNOTATION)
.addKeepRules(
"-if class * "
+ "-keepclasseswithmembers class <1> { @"
+ PRESENT_ANNOTATION
+ " *** *(...); }")
.addDontWarnGoogle()
.addDontWarnJavaxNullableAnnotation()
.compile()
.graphInspector();
assertRetainedClassesEqual(
referenceInspector, ifThenKeepClassesWithMembersInspector, false, false);
GraphInspector ifHasMemberThenKeepClassInspector =
testForR8(Backend.CF)
.enableGraphInspector()
.addProgramFiles(R8_JAR)
.addKeepMainRule(R8.class)
.addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
// TODO(b/132318609): Remove keep annotation once fixed.
.addKeepClassRules(PRESENT_ANNOTATION)
.addKeepRules(
"-if class * { @"
+ PRESENT_ANNOTATION
+ " *** *(...); } "
+ "-keep class <1> { @"
+ PRESENT_ANNOTATION
+ " *** <2>(...); }")
.addDontWarnGoogle()
.addDontWarnJavaxNullableAnnotation()
.compile()
.graphInspector();
// TODO(b/159418523): Should the reference be equal to the result with the conditional rule?
assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector, true, true);
}
private void assertRetainedClassesEqual(
GraphInspector referenceResult,
GraphInspector conditionalResult,
boolean expectReferenceIsLarger,
boolean expectConditionalIsLarger) {
Set<String> referenceClasses =
new TreeSet<>(
referenceResult.codeInspector().allClasses().stream()
.map(c -> c.getOriginalName())
.collect(Collectors.toSet()));
Set<String> conditionalClasses =
conditionalResult.codeInspector().allClasses().stream()
.map(c -> c.getOriginalName())
.collect(Collectors.toSet());
{
SetView<String> notInReference = Sets.difference(conditionalClasses, referenceClasses);
if (expectConditionalIsLarger) {
assertFalse("Expected classes in -if rule to retain more.", notInReference.isEmpty());
} else {
assertEquals(
"Classes in -if rule that are not in -keepclassmembers rule",
Collections.emptySet(),
notInReference);
}
}
{
SetView<String> notInConditional = Sets.difference(referenceClasses, conditionalClasses);
if (expectReferenceIsLarger) {
assertFalse(
"Expected classes in -keepclassmembers rule to retain more.",
notInConditional.isEmpty());
} else {
assertEquals(
"Classes in -keepclassmembers rule that are not in -if rule",
Collections.emptySet(),
new TreeSet<>(notInConditional));
}
}
}
}