[KeepAnno] Syntax for extends patterns and keep options.
Bug: b/248408342
Change-Id: I274aa8293e1538098a99807b6a565b10f479fcf3
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
index 01c493f..8b76c13 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
@@ -29,6 +29,10 @@
String classTypeName() default "";
+ String extendsClassTypeName() default "";
+
+ Class<?> extendsClassConstant() default Object.class;
+
String methodName() default "";
String methodReturnType() default "";
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
index 0018dfc..faa5587 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
@@ -19,6 +19,16 @@
return classTypeName.replace('.', '/');
}
+ public static boolean isKeepAnnotation(String descriptor, boolean visible) {
+ if (visible) {
+ return false;
+ }
+ return descriptor.equals(Edge.DESCRIPTOR)
+ || descriptor.equals(UsesReflection.DESCRIPTOR)
+ || descriptor.equals(Condition.DESCRIPTOR)
+ || descriptor.equals(Target.DESCRIPTOR);
+ }
+
public static final class Edge {
public static final Class<KeepEdge> CLASS = KeepEdge.class;
public static final String DESCRIPTOR = getDescriptor(CLASS);
@@ -38,6 +48,9 @@
public static final class Item {
public static final String classConstant = "classConstant";
+ public static final String extendsClassConstant = "extendsClassConstant";
+ public static final String extendsTypeName = "extendsTypeName";
+
public static final String methodName = "methodName";
public static final String methodReturnType = "methodReturnType";
public static final String methodParameters = "methodParameters";
@@ -64,5 +77,19 @@
public static final class Target {
public static final Class<KeepTarget> CLASS = KeepTarget.class;
public static final String DESCRIPTOR = getDescriptor(CLASS);
+
+ public static final String allow = "allow";
+ public static final String disallow = "disallow";
+ }
+
+ public static final class Option {
+ public static final Class<KeepOption> CLASS = KeepOption.class;
+ public static final String DESCRIPTOR = getDescriptor(CLASS);
+
+ public static final String SHRINKING = "SHRINKING";
+ public static final String OBFUSCATION = "OBFUSCATION";
+ public static final String OPTIMIZATION = "OPTIMIZATION";
+ public static final String ACCESS_MODIFICATION = "ACCESS_MODIFICATION";
+ public static final String ANNOTATION_REMOVAL = "ANNOTATION_REMOVAL";
}
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepEdge.java
index fb9a772..d2b0356 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepEdge.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepEdge.java
@@ -8,7 +8,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
+@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.CLASS)
public @interface KeepEdge {
KeepCondition[] preconditions() default {};
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepOption.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepOption.java
new file mode 100644
index 0000000..e3c6d31
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepOption.java
@@ -0,0 +1,12 @@
+// 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.annotations;
+
+public enum KeepOption {
+ SHRINKING,
+ OPTIMIZATION,
+ OBFUSCATION,
+ ACCESS_MODIFICATION,
+ ANNOTATION_REMOVAL,
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
index 73559d6..8a01df9 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
@@ -25,10 +25,23 @@
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface KeepTarget {
+
+ // KeepTarget only content (keep options) =========
+
+ KeepOption[] allow() default {};
+
+ KeepOption[] disallow() default {};
+
+ // Shared KeepItem content ========================
+
Class<?> classConstant() default Object.class;
String classTypeName() default "";
+ String extendsClassTypeName() default "";
+
+ Class<?> extendsClassConstant() default Object.class;
+
String methodName() default "";
String methodReturnType() default "";
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java
index 22fcb36..ca6f32c 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java
@@ -53,7 +53,7 @@
* }
* </pre>
*/
-@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
+@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.CLASS)
public @interface UsesReflection {
KeepTarget[] value();
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
index 672a99e7..ca700b0 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -7,11 +7,13 @@
import com.android.tools.r8.keepanno.annotations.KeepConstants.Condition;
import com.android.tools.r8.keepanno.annotations.KeepConstants.Edge;
import com.android.tools.r8.keepanno.annotations.KeepConstants.Item;
+import com.android.tools.r8.keepanno.annotations.KeepConstants.Option;
import com.android.tools.r8.keepanno.annotations.KeepConstants.Target;
import com.android.tools.r8.keepanno.ast.KeepCondition;
import com.android.tools.r8.keepanno.ast.KeepConsequences;
import com.android.tools.r8.keepanno.ast.KeepEdge;
import com.android.tools.r8.keepanno.ast.KeepEdgeException;
+import com.android.tools.r8.keepanno.ast.KeepExtendsPattern;
import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
import com.android.tools.r8.keepanno.ast.KeepFieldTypePattern;
@@ -21,12 +23,15 @@
import com.android.tools.r8.keepanno.ast.KeepMethodParametersPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodReturnTypePattern;
+import com.android.tools.r8.keepanno.ast.KeepOptions;
+import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption;
import com.android.tools.r8.keepanno.ast.KeepPreconditions;
import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
import com.android.tools.r8.keepanno.ast.KeepTarget;
import com.android.tools.r8.keepanno.ast.KeepTypePattern;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -325,7 +330,7 @@
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
if (descriptor.equals(Target.DESCRIPTOR)) {
- return new KeepTargetVisitor(builder::addTarget);
+ return KeepTargetVisitor.create(builder::addTarget);
}
return super.visitAnnotation(name, descriptor);
}
@@ -343,6 +348,9 @@
private KeepMethodPattern.Builder lazyMethodBuilder = null;
private KeepFieldPattern.Builder lazyFieldBuilder = null;
+ private KeepExtendsPattern extendsPattern = null;
+ private String extendPatternDeclaration = null;
+
public KeepItemVisitorBase(Parent<KeepItemPattern> parent) {
this.parent = parent;
}
@@ -373,6 +381,9 @@
classNamePattern = KeepQualifiedClassNamePattern.exact(((Type) value).getClassName());
return;
}
+ if (tryParseExtendsPattern(name, value)) {
+ return;
+ }
if (name.equals(Item.methodName) && value instanceof String) {
String methodName = (String) value;
if (!Item.methodNameDefaultValue.equals(methodName)) {
@@ -408,6 +419,38 @@
super.visit(name, value);
}
+ private boolean tryParseExtendsPattern(String name, Object value) {
+ if (name.equals(Item.extendsClassConstant) && value instanceof Type) {
+ checkExtendsDeclaration(name);
+ extendsPattern =
+ KeepExtendsPattern.builder()
+ .classPattern(KeepQualifiedClassNamePattern.exact(((Type) value).getClassName()))
+ .build();
+ return true;
+ }
+ if (name.equals(Item.extendsTypeName) && value instanceof String) {
+ checkExtendsDeclaration(name);
+ extendsPattern =
+ KeepExtendsPattern.builder()
+ .classPattern(KeepQualifiedClassNamePattern.exact(((String) value)))
+ .build();
+ return true;
+ }
+ return false;
+ }
+
+ private void checkExtendsDeclaration(String declaration) {
+ if (extendPatternDeclaration != null) {
+ throw new KeepEdgeException(
+ "Multiple declarations defining an extends pattern: '"
+ + extendPatternDeclaration
+ + "' and '"
+ + declaration
+ + "'");
+ }
+ extendPatternDeclaration = declaration;
+ }
+
@Override
public AnnotationVisitor visitArray(String name) {
if (name.equals(Item.methodParameters)) {
@@ -433,6 +476,9 @@
if (classNamePattern != null) {
itemBuilder.setClassPattern(classNamePattern);
}
+ if (extendsPattern != null) {
+ itemBuilder.setExtendsPattern(extendsPattern);
+ }
if (lazyMethodBuilder != null) {
itemBuilder.setMemberPattern(lazyMethodBuilder.build());
}
@@ -470,8 +516,25 @@
private static class KeepTargetVisitor extends KeepItemVisitorBase {
- public KeepTargetVisitor(Parent<KeepTarget> parent) {
- super(item -> parent.accept(KeepTarget.builder().setItem(item).build()));
+ private final KeepTarget.Builder builder;
+
+ static KeepTargetVisitor create(Parent<KeepTarget> parent) {
+ KeepTarget.Builder builder = KeepTarget.builder();
+ return new KeepTargetVisitor(parent, builder);
+ }
+
+ private KeepTargetVisitor(Parent<KeepTarget> parent, KeepTarget.Builder builder) {
+ super(item -> parent.accept(builder.setItem(item).build()));
+ this.builder = builder;
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ if (name.equals(KeepConstants.Target.disallow)) {
+ return new KeepOptionsVisitor(
+ options -> builder.setOptions(KeepOptions.disallowBuilder().addAll(options).build()));
+ }
+ return super.visitArray(name);
}
}
@@ -481,4 +544,49 @@
super(item -> parent.accept(KeepCondition.builder().setItem(item).build()));
}
}
+
+ private static class KeepOptionsVisitor extends AnnotationVisitorBase {
+
+ private final Parent<Collection<KeepOption>> parent;
+ private final Set<KeepOption> options = new HashSet<>();
+
+ public KeepOptionsVisitor(Parent<Collection<KeepOption>> parent) {
+ this.parent = parent;
+ }
+
+ @Override
+ public void visitEnum(String ignore, String descriptor, String value) {
+ if (!descriptor.equals(KeepConstants.Option.DESCRIPTOR)) {
+ super.visitEnum(ignore, descriptor, value);
+ }
+ KeepOption option;
+ switch (value) {
+ case Option.SHRINKING:
+ option = KeepOption.SHRINKING;
+ break;
+ case Option.OPTIMIZATION:
+ option = KeepOption.OPTIMIZING;
+ break;
+ case Option.OBFUSCATION:
+ option = KeepOption.OBFUSCATING;
+ break;
+ case Option.ACCESS_MODIFICATION:
+ option = KeepOption.ACCESS_MODIFICATION;
+ break;
+ case Option.ANNOTATION_REMOVAL:
+ option = KeepOption.ANNOTATION_REMOVAL;
+ break;
+ default:
+ super.visitEnum(ignore, descriptor, value);
+ return;
+ }
+ options.add(option);
+ }
+
+ @Override
+ public void visitEnd() {
+ parent.accept(options);
+ super.visitEnd();
+ }
+ }
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
index e1d6d4e..0049c90 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
@@ -27,7 +27,7 @@
* CONSEQUENCES ::= TARGET+
* TARGET ::= OPTIONS ITEM_PATTERN
* OPTIONS ::= keep-all | OPTION+
- * OPTION ::= shrinking | optimizing | obfuscating | access-modifying
+ * OPTION ::= shrinking | optimizing | obfuscating | access-modification | annotation-removal
*
* ITEM_PATTERN
* ::= any
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepExtendsPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepExtendsPattern.java
index 02c8768..ea30422 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepExtendsPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepExtendsPattern.java
@@ -7,57 +7,34 @@
public abstract class KeepExtendsPattern {
public static KeepExtendsPattern any() {
- return Any.getInstance();
+ return Some.getAnyInstance();
}
public static class Builder {
- private KeepExtendsPattern pattern;
+ private KeepExtendsPattern pattern = KeepExtendsPattern.any();
private Builder() {}
- public Builder any() {
- pattern = Any.getInstance();
- return this;
- }
-
public Builder classPattern(KeepQualifiedClassNamePattern pattern) {
this.pattern = new Some(pattern);
return this;
}
- }
- private static class Any extends KeepExtendsPattern {
-
- private static final Any INSTANCE = new Any();
-
- public static Any getInstance() {
- return INSTANCE;
- }
-
- @Override
- public boolean isAny() {
- return true;
- }
-
- @Override
- public boolean equals(Object obj) {
- return this == obj;
- }
-
- @Override
- public int hashCode() {
- return System.identityHashCode(this);
- }
-
- @Override
- public String toString() {
- return "*";
+ public KeepExtendsPattern build() {
+ return pattern;
}
}
private static class Some extends KeepExtendsPattern {
+ private static final KeepExtendsPattern ANY_INSTANCE =
+ new Some(KeepQualifiedClassNamePattern.any());
+
+ private static KeepExtendsPattern getAnyInstance() {
+ return ANY_INSTANCE;
+ }
+
private final KeepQualifiedClassNamePattern pattern;
public Some(KeepQualifiedClassNamePattern pattern) {
@@ -71,6 +48,11 @@
}
@Override
+ public KeepQualifiedClassNamePattern asClassNamePattern() {
+ return pattern;
+ }
+
+ @Override
public boolean equals(Object o) {
if (this == o) {
return true;
@@ -100,4 +82,6 @@
private KeepExtendsPattern() {}
public abstract boolean isAny();
+
+ public abstract KeepQualifiedClassNamePattern asClassNamePattern();
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
index 50d3921..7f84090 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
@@ -29,7 +29,7 @@
public static class Builder {
- private KeepQualifiedClassNamePattern classNamePattern;
+ private KeepQualifiedClassNamePattern classNamePattern = KeepQualifiedClassNamePattern.any();
private KeepExtendsPattern extendsPattern = KeepExtendsPattern.any();
private KeepMemberPattern memberPattern = KeepMemberPattern.none();
@@ -58,9 +58,6 @@
}
public KeepItemPattern build() {
- if (classNamePattern == null) {
- throw new KeepEdgeException("Class pattern must define a class name pattern.");
- }
return new KeepItemPattern(classNamePattern, extendsPattern, memberPattern);
}
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepOptions.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepOptions.java
index 8e1b2e9..ba647eb 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepOptions.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepOptions.java
@@ -22,7 +22,8 @@
SHRINKING,
OPTIMIZING,
OBFUSCATING,
- ACCESS_MODIFYING,
+ ACCESS_MODIFICATION,
+ ANNOTATION_REMOVAL,
}
public static KeepOptions keepAll() {
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 286c07a..f20459e 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
@@ -6,6 +6,7 @@
import com.android.tools.r8.keepanno.ast.KeepConsequences;
import com.android.tools.r8.keepanno.ast.KeepEdge;
import com.android.tools.r8.keepanno.ast.KeepEdgeException;
+import com.android.tools.r8.keepanno.ast.KeepExtendsPattern;
import com.android.tools.r8.keepanno.ast.KeepFieldAccessPattern;
import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
@@ -103,8 +104,10 @@
private static StringBuilder printItem(StringBuilder builder, KeepItemPattern clazzPattern) {
builder.append("class ");
printClassName(builder, clazzPattern.getClassNamePattern());
- if (!clazzPattern.getExtendsPattern().isAny()) {
- throw new Unimplemented();
+ KeepExtendsPattern extendsPattern = clazzPattern.getExtendsPattern();
+ if (!extendsPattern.isAny()) {
+ builder.append(" extends ");
+ printClassName(builder, extendsPattern.asClassNamePattern());
}
KeepMemberPattern member = clazzPattern.getMemberPattern();
if (member.isNone()) {
@@ -247,8 +250,10 @@
return "optimization";
case OBFUSCATING:
return "obfuscation";
- case ACCESS_MODIFYING:
+ case ACCESS_MODIFICATION:
return "accessmodification";
+ case ANNOTATION_REMOVAL:
+ return "annotationremoval";
default:
throw new Unimplemented();
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnotationViaSuperTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnotationViaSuperTest.java
new file mode 100644
index 0000000..5f2237d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnotationViaSuperTest.java
@@ -0,0 +1,151 @@
+// 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;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.KeepOption;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+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.google.common.collect.ImmutableList;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepAnnotationViaSuperTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("42");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public KeepAnnotationViaSuperTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getInputClasses())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testWithRuleExtraction() throws Exception {
+ List<String> rules = getExtractedKeepRules();
+ System.out.println(rules);
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getInputClassesWithoutKeepAnnotations())
+ .addKeepRules(rules)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRuntimeVisibleAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkOutput);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(
+ TestClass.class,
+ Base.class,
+ SubA.class,
+ SubB.class,
+ SubC.class,
+ Anno.class,
+ UnusedAnno.class);
+ }
+
+ public List<byte[]> getInputClassesWithoutKeepAnnotations() throws Exception {
+ return KeepEdgeAnnotationsTest.getInputClassesWithoutKeepAnnotations(getInputClasses());
+ }
+
+ public List<String> getExtractedKeepRules() throws Exception {
+ List<Class<?>> classes = getInputClasses();
+ List<String> rules = new ArrayList<>();
+ for (Class<?> clazz : classes) {
+ rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz));
+ }
+ return rules;
+ }
+
+ private void checkOutput(CodeInspector inspector) {
+ assertThat(inspector.clazz(Base.class), isPresent());
+
+ ClassSubject classA = inspector.clazz(SubA.class);
+ assertThat(classA, isPresent());
+ assertThat(classA.annotation(Anno.class), isPresent());
+ assertThat(classA.annotation(UnusedAnno.class), isAbsent());
+
+ ClassSubject classB = inspector.clazz(SubB.class);
+ assertThat(classB, isPresent());
+ assertThat(classB.annotation(Anno.class), isPresent());
+
+ assertThat(inspector.clazz(SubC.class), isAbsent());
+ }
+
+ @Target({ElementType.TYPE})
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface Anno {
+ int value();
+ }
+
+ @Target({ElementType.TYPE})
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface UnusedAnno {
+ int value();
+ }
+
+ abstract static class Base {
+
+ @UsesReflection({
+ @KeepTarget(
+ extendsClassConstant = Base.class,
+ disallow = {KeepOption.ANNOTATION_REMOVAL})
+ })
+ public Base() {
+ Anno annotation = getClass().getAnnotation(Anno.class);
+ System.out.println(annotation.value());
+ }
+ }
+
+ @Anno(42)
+ @UnusedAnno(123)
+ static class SubA extends Base {}
+
+ @Anno(7)
+ static class SubB extends Base {}
+
+ // Unused.
+ @Anno(-1)
+ static class SubC extends Base {}
+
+ static class TestClass {
+
+ public static void main(String[] args) throws Exception {
+ Base b = System.nanoTime() > 0 ? new SubA() : new SubB();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
index 8743b48..f99f3bf 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.keepanno.annotations.KeepConstants;
import com.android.tools.r8.keepanno.annotations.KeepConstants.Edge;
import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
import com.android.tools.r8.keepanno.asm.KeepEdgeWriter;
@@ -36,6 +37,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -138,6 +140,16 @@
});
}
+ public static List<byte[]> getInputClassesWithoutKeepAnnotations(Collection<Class<?>> classes)
+ throws Exception {
+ List<byte[]> transformed = new ArrayList<>(classes.size());
+ for (Class<?> clazz : classes) {
+ transformed.add(
+ transformer(clazz).removeAnnotations(KeepConstants::isKeepAnnotation).transform());
+ }
+ return transformed;
+ }
+
@Test
public void testAsmReader() throws Exception {
assumeTrue(parameters.isCfRuntime());
@@ -147,11 +159,7 @@
byte[] original = ToolHelper.getClassAsBytes(source);
// Strip out all the annotations to ensure they are actually added again.
byte[] stripped =
- transformer(source)
- .removeClassAnnotations()
- .removeMethodAnnotations()
- .removeFieldAnnotations()
- .transform();
+ getInputClassesWithoutKeepAnnotations(Collections.singletonList(source)).get(0);
// Manually add in the expected edges again.
byte[] readded =
transformer(stripped, clazz)
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java
index f9d98e3..d692791 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java
@@ -65,12 +65,7 @@
}
public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
- List<Class<?>> classes = getInputClasses();
- List<byte[]> transformed = new ArrayList<>(classes.size());
- for (Class<?> clazz : classes) {
- transformed.add(transformer(clazz).removeAllAnnotations().transform());
- }
- return transformed;
+ return KeepEdgeAnnotationsTest.getInputClassesWithoutKeepAnnotations(getInputClasses());
}
public List<String> getExtractedKeepRules() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationWithAdditionalPreconditionTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationWithAdditionalPreconditionTest.java
index 732f2d2..33f2531 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationWithAdditionalPreconditionTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationWithAdditionalPreconditionTest.java
@@ -64,12 +64,7 @@
}
public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
- List<Class<?>> classes = getInputClasses();
- List<byte[]> transformed = new ArrayList<>(classes.size());
- for (Class<?> clazz : classes) {
- transformed.add(transformer(clazz).removeAllAnnotations().transform());
- }
- return transformed;
+ return KeepEdgeAnnotationsTest.getInputClassesWithoutKeepAnnotations(getInputClasses());
}
public List<String> getExtractedKeepRules() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java
index b6cc560..bfc7fb5 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java
@@ -64,12 +64,7 @@
}
public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
- List<Class<?>> classes = getInputClasses();
- List<byte[]> transformed = new ArrayList<>(classes.size());
- for (Class<?> clazz : classes) {
- transformed.add(transformer(clazz).removeAllAnnotations().transform());
- }
- return transformed;
+ return KeepEdgeAnnotationsTest.getInputClassesWithoutKeepAnnotations(getInputClasses());
}
public List<String> getExtractedKeepRules() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java
index 6739144..b362802 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java
@@ -66,12 +66,7 @@
}
public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
- List<Class<?>> classes = getInputClasses();
- List<byte[]> transformed = new ArrayList<>(classes.size());
- for (Class<?> clazz : classes) {
- transformed.add(transformer(clazz).removeAllAnnotations().transform());
- }
- return transformed;
+ return KeepEdgeAnnotationsTest.getInputClassesWithoutKeepAnnotations(getInputClasses());
}
public List<String> getExtractedKeepRules() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
index d9a6eaf..0386448 100644
--- a/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
@@ -11,6 +11,8 @@
import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption;
import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor;
import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -62,10 +64,10 @@
.build())
.build();
// Disallow will issue the full inverse of the known options, e.g., 'allowaccessmodification'.
- assertEquals(
- StringUtils.unixLines(
- "-keep,allowshrinking,allowobfuscation,allowaccessmodification class * { *; }"),
- extract(edge));
+ List<String> options =
+ ImmutableList.of("shrinking", "obfuscation", "accessmodification", "annotationremoval");
+ String allows = String.join(",allow", options);
+ assertEquals(StringUtils.unixLines("-keep,allow" + allows + " class * { *; }"), extract(edge));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 1974bcd..7742119 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -1463,32 +1463,51 @@
});
}
- public ClassFileTransformer removeAllAnnotations() {
- return removeClassAnnotations().removeMethodAnnotations().removeFieldAnnotations();
+ public interface AnnotationPredicate {
+ boolean test(String descriptor, boolean visible);
+
+ static AnnotationPredicate any() {
+ return (descriptor, visible) -> true;
+ }
}
- public ClassFileTransformer removeClassAnnotations() {
+ public ClassFileTransformer removeAllAnnotations() {
+ return removeAnnotations(AnnotationPredicate.any());
+ }
+
+ public ClassFileTransformer removeAnnotations(AnnotationPredicate predicate) {
+ return removeClassAnnotations(predicate)
+ .removeMethodAnnotations(predicate)
+ .removeFieldAnnotations(predicate);
+ }
+
+ public ClassFileTransformer removeClassAnnotations(AnnotationPredicate predicate) {
return addClassTransformer(
new ClassTransformer() {
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
- // Ignore all input annotations.
- return null;
+ if (predicate.test(descriptor, visible)) {
+ return null;
+ }
+ return super.visitAnnotation(descriptor, visible);
}
});
}
- public ClassFileTransformer removeMethodAnnotations() {
+ public ClassFileTransformer removeMethodAnnotations(AnnotationPredicate predicate) {
return addMethodTransformer(
new MethodTransformer() {
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
- return null;
+ if (predicate.test(descriptor, visible)) {
+ return null;
+ }
+ return super.visitAnnotation(descriptor, visible);
}
});
}
- public ClassFileTransformer removeFieldAnnotations() {
+ public ClassFileTransformer removeFieldAnnotations(AnnotationPredicate predicate) {
return addClassTransformer(
new ClassTransformer() {
@Override
@@ -1498,7 +1517,10 @@
return new FieldVisitor(ASM_VERSION, fv) {
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
- return null;
+ if (predicate.test(descriptor, visible)) {
+ return null;
+ }
+ return super.visitAnnotation(descriptor, visible);
}
};
}