[KeepAnno] Create test using extracted keep rules.
Bug: b/248408342
Change-Id: I8f55cc627a6ee280c3737b0fe71752c8c6b2ca5a
diff --git a/build.gradle b/build.gradle
index f775822..dce4a28 100644
--- a/build.gradle
+++ b/build.gradle
@@ -319,6 +319,7 @@
keepannoCompile group: 'org.ow2.asm', name: 'asm', version: asmVersion
keepannoCompile "com.google.guava:guava:$guavaVersion"
+ keepannoCompile files("third_party/openjdk/jdk8/linux-x86/lib/tools.jar")
testCompile sourceSets.keepanno.output
testRuntime sourceSets.keepanno.output
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
index 15af2fa..8307265 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
@@ -95,7 +95,14 @@
throw new Unimplemented();
}
if (!method.getReturnTypePattern().isAny()) {
- throw new Unimplemented();
+ if (exactMethodName != null
+ && (exactMethodName.getName().equals("<init>")
+ || exactMethodName.getName().equals("<clinit>"))
+ && method.getReturnTypePattern().isVoid()) {
+ // constructors have implicit void return.
+ } else {
+ throw new Unimplemented();
+ }
}
if (!method.getParametersPattern().isAny()) {
throw new Unimplemented();
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
index ffcf3bd..e00fedd 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.ast;
+import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern.KeepMethodNameExactPattern;
import java.util.Objects;
public final class KeepMethodPattern extends KeepMemberPattern {
@@ -48,6 +49,15 @@
if (namePattern == null) {
throw new KeepEdgeException("Method pattern must declar a name pattern");
}
+ KeepMethodReturnTypePattern returnTypePattern = this.returnTypePattern;
+ KeepMethodNameExactPattern exactName = namePattern.asExact();
+ if (exactName != null
+ && (exactName.getName().equals("<init>") || exactName.getName().equals("<clinit>"))) {
+ if (!this.returnTypePattern.isAny() && !this.returnTypePattern.isVoid()) {
+ throw new KeepEdgeException("Method constructor pattern must match 'void' type.");
+ }
+ returnTypePattern = KeepMethodReturnTypePattern.voidType();
+ }
return new KeepMethodPattern(
accessPattern, namePattern, returnTypePattern, parametersPattern);
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
index 1a9fc52..7c8c032 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
@@ -132,7 +132,7 @@
private static StringBuilder printParameters(
StringBuilder builder, KeepMethodParametersPattern parametersPattern) {
if (parametersPattern.isAny()) {
- return builder.append("(***)");
+ return builder.append("(...)");
}
return builder
.append('(')
@@ -160,7 +160,7 @@
private static StringBuilder printType(StringBuilder builder, KeepTypePattern typePattern) {
if (typePattern.isAny()) {
- return builder.append("*");
+ return builder.append("***");
}
throw new Unimplemented();
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java b/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
index 3455ac8..c950191 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
import com.android.tools.r8.keepanno.ast.KeepTarget;
import com.android.tools.r8.keepanno.utils.Unimplemented;
+import com.sun.tools.javac.code.Type;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@@ -130,7 +131,11 @@
AnnotationValue classConstantValue = getAnnotationValue(mirror, Target.classConstant);
if (classConstantValue != null) {
DeclaredType type = AnnotationClassValueVisitor.getType(classConstantValue);
- itemBuilder.setClassPattern(KeepQualifiedClassNamePattern.exact(type.toString()));
+ // The processor API does not expose the descriptor or typename, so we need to depend on the
+ // sun.tools package and cast to its internal type to extract it. If not, this code will not
+ // work for inner classes as we cannot recover the $ separator.
+ String typeName = ((Type) type).tsym.flatName().toString();
+ itemBuilder.setClassPattern(KeepQualifiedClassNamePattern.exact(typeName));
}
AnnotationValue methodNameValue = getAnnotationValue(mirror, Target.methodName);
if (methodNameValue != null) {
diff --git a/src/test/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractorTest.java b/src/test/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractorTest.java
new file mode 100644
index 0000000..94001ec
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractorTest.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2022, 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.keepanno.keeprules;
+
+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.keepanno.asm.KeepEdgeReader;
+import com.android.tools.r8.keepanno.ast.KeepEdge;
+import com.android.tools.r8.keepanno.testsource.KeepClassAndDefaultConstructorSource;
+import com.android.tools.r8.keepanno.testsource.KeepSourceEdges;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepRuleExtractorTest extends TestBase {
+
+ private static final Class<?> SOURCE = KeepClassAndDefaultConstructorSource.class;
+ private static final String EXPECTED = KeepSourceEdges.getExpected(SOURCE);
+ private static final Path KEEP_ANNO_PATH =
+ Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "keepanno");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public KeepRuleExtractorTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ List<String> rules = getKeepRulesForClass(SOURCE);
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(KEEP_ANNO_PATH)
+ .addProgramClassesAndInnerClasses(SOURCE)
+ .addKeepRules(rules)
+ .addKeepMainRule(SOURCE)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), SOURCE)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private List<String> getKeepRulesForClass(Class<?> clazz) throws IOException {
+ Set<KeepEdge> keepEdges = KeepEdgeReader.readKeepEdges(ToolHelper.getClassAsBytes(clazz));
+ List<String> rules = new ArrayList<>();
+ KeepRuleExtractor extractor = new KeepRuleExtractor(rules::add);
+ keepEdges.forEach(extractor::extract);
+ return rules;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessorTest.java b/src/test/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessorTest.java
index eb7b616..83f60a3 100644
--- a/src/test/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessorTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessorTest.java
@@ -36,6 +36,7 @@
private static final Path KEEP_ANNO_PATH =
Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "keepanno");
private static final Class<?> SOURCE = KeepClassAndDefaultConstructorSource.class;
+ private static final String EXPECTED = KeepSourceEdges.getExpected(SOURCE);
private final TestParameters parameters;
@@ -63,6 +64,12 @@
checkSynthesizedKeepEdgeClass(inspector, out);
// The source is added as a classpath name but not part of the compilation unit output.
assertThat(inspector.clazz(SOURCE), isAbsent());
+
+ testForJvm()
+ .addProgramClasses(SOURCE, KeepClassAndDefaultConstructorSource.A.class)
+ .addProgramFiles(out)
+ .run(parameters.getRuntime(), SOURCE)
+ .assertSuccessWithOutput(EXPECTED);
}
@Test
@@ -78,7 +85,7 @@
testForJvm()
.addProgramFiles(out)
.run(parameters.getRuntime(), SOURCE)
- .assertSuccessWithOutputLines("A is alive!")
+ .assertSuccessWithOutput(EXPECTED)
.inspect(
inspector -> {
assertThat(inspector.clazz(SOURCE), isPresent());
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepClassAndDefaultConstructorSource.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepClassAndDefaultConstructorSource.java
index ca83a46..b9fb2b6 100644
--- a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepClassAndDefaultConstructorSource.java
+++ b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepClassAndDefaultConstructorSource.java
@@ -9,9 +9,11 @@
@KeepEdge(
consequences = {
// Keep the class to allow lookup of it.
- @KeepTarget(classConstant = KeepClassAndDefaultConstructorSource.class),
+ @KeepTarget(classConstant = KeepClassAndDefaultConstructorSource.A.class),
// Keep the default constructor.
- @KeepTarget(classConstant = KeepClassAndDefaultConstructorSource.class, methodName = "<init>")
+ @KeepTarget(
+ classConstant = KeepClassAndDefaultConstructorSource.A.class,
+ methodName = "<init>")
})
public class KeepClassAndDefaultConstructorSource {
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
index 03b63c6..5637edf 100644
--- a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
+++ b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
import com.android.tools.r8.keepanno.ast.KeepTarget;
+import com.android.tools.r8.utils.StringUtils;
import java.util.Collections;
import java.util.Set;
@@ -27,8 +28,19 @@
throw new RuntimeException();
}
+ public static String getExpected(Class<?> clazz) {
+ if (clazz.equals(KeepClassAndDefaultConstructorSource.class)) {
+ return getKeepClassAndDefaultConstructorSourceExpected();
+ }
+ throw new RuntimeException();
+ }
+
+ public static String getKeepClassAndDefaultConstructorSourceExpected() {
+ return StringUtils.lines("A is alive!");
+ }
+
public static Set<KeepEdge> getKeepClassAndDefaultConstructorSourceEdges() {
- Class<?> clazz = KeepClassAndDefaultConstructorSource.class;
+ Class<?> clazz = KeepClassAndDefaultConstructorSource.A.class;
// Build the class target.
KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
KeepItemPattern classItem = KeepItemPattern.builder().setClassPattern(name).build();