[KeepAnno] Relax annotation contexts and support preconditions.
Bug: b/248408342
Change-Id: I47291a9cb764508c3a835299849e892e793910b3
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 9d0265e..6d66d0a 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
@@ -8,6 +8,28 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+/**
+ * A condition for a keep edge.
+ *
+ * <p>The condition denotes a keep item:
+ *
+ * <ul>
+ * <li>a class, or pattern on classes;
+ * <li>a method, or pattern on methods; or
+ * <li>a field, or pattern on fields.
+ * </ul>
+ *
+ * <p>The structure of a condition item is the same as for a target item but without a notion of
+ * "keep options".
+ */
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.CLASS)
-public @interface KeepCondition {}
+public @interface KeepCondition {
+ Class<?> classConstant() default Object.class;
+
+ String classTypeName() default "";
+
+ String methodName() default "";
+
+ String fieldName() 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 5b6de6e..ca219b2 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
@@ -26,11 +26,20 @@
public static final String consequences = "consequences";
}
- public static final class Target {
- public static final Class<KeepTarget> CLASS = KeepTarget.class;
- public static final String DESCRIPTOR = getDescriptor(CLASS);
+ // Implicit hidden item which is "super type" of Condition and Target.
+ public static final class Item {
public static final String classConstant = "classConstant";
public static final String methodName = "methodName";
public static final String fieldName = "fieldName";
}
+
+ public static final class Condition {
+ public static final Class<KeepCondition> CLASS = KeepCondition.class;
+ public static final String DESCRIPTOR = getDescriptor(CLASS);
+ }
+
+ public static final class Target {
+ public static final Class<KeepTarget> CLASS = KeepTarget.class;
+ public static final String DESCRIPTOR = getDescriptor(CLASS);
+ }
}
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 19b1a4a..fb9a772 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)
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface KeepEdge {
KeepCondition[] preconditions() default {};
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 cc6e4ee..dd5be82 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
@@ -3,8 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.asm;
+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.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;
@@ -22,6 +25,8 @@
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
@@ -52,6 +57,54 @@
}
return null;
}
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String descriptor, String signature, String[] exceptions) {
+ return new KeepEdgeMethodVisitor(parent);
+ }
+
+ @Override
+ public FieldVisitor visitField(
+ int access, String name, String descriptor, String signature, Object value) {
+ return new KeepEdgeFieldVisitor(parent);
+ }
+ }
+
+ private static class KeepEdgeMethodVisitor extends MethodVisitor {
+ private final Parent<KeepEdge> parent;
+
+ KeepEdgeMethodVisitor(Parent<KeepEdge> parent) {
+ super(ASM_VERSION);
+ this.parent = parent;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ // Skip any visible annotations as @KeepEdge is not runtime visible.
+ if (!visible && descriptor.equals(Edge.DESCRIPTOR)) {
+ return new KeepEdgeVisitor(parent);
+ }
+ return null;
+ }
+ }
+
+ private static class KeepEdgeFieldVisitor extends FieldVisitor {
+ private final Parent<KeepEdge> parent;
+
+ KeepEdgeFieldVisitor(Parent<KeepEdge> parent) {
+ super(ASM_VERSION);
+ this.parent = parent;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ // Skip any visible annotations as @KeepEdge is not runtime visible.
+ if (!visible && descriptor.equals(Edge.DESCRIPTOR)) {
+ return new KeepEdgeVisitor(parent);
+ }
+ return null;
+ }
}
// Interface for providing AST result(s) for a sub-tree back up to its parent.
@@ -113,10 +166,24 @@
private static class KeepPreconditionsVisitor extends AnnotationVisitorBase {
private final Parent<KeepPreconditions> parent;
+ private final KeepPreconditions.Builder builder = KeepPreconditions.builder();
public KeepPreconditionsVisitor(Parent<KeepPreconditions> parent) {
this.parent = parent;
}
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ if (descriptor.equals(Condition.DESCRIPTOR)) {
+ return new KeepConditionVisitor(builder::addCondition);
+ }
+ return super.visitAnnotation(name, descriptor);
+ }
+
+ @Override
+ public void visitEnd() {
+ parent.accept(builder.build());
+ }
}
private static class KeepConsequencesVisitor extends AnnotationVisitorBase {
@@ -141,27 +208,28 @@
}
}
- private static class KeepTargetVisitor extends AnnotationVisitorBase {
- private final Parent<KeepTarget> parent;
+ private abstract static class KeepItemVisitorBase extends AnnotationVisitorBase {
+ private final Parent<KeepItemPattern> parent;
+
private KeepQualifiedClassNamePattern classNamePattern = null;
private KeepMethodNamePattern methodName = null;
private KeepFieldNamePattern fieldName = null;
- public KeepTargetVisitor(Parent<KeepTarget> parent) {
+ public KeepItemVisitorBase(Parent<KeepItemPattern> parent) {
this.parent = parent;
}
@Override
public void visit(String name, Object value) {
- if (name.equals(Target.classConstant) && value instanceof Type) {
+ if (name.equals(Item.classConstant) && value instanceof Type) {
classNamePattern = KeepQualifiedClassNamePattern.exact(((Type) value).getClassName());
return;
}
- if (name.equals(Target.methodName) && value instanceof String) {
+ if (name.equals(Item.methodName) && value instanceof String) {
methodName = KeepMethodNamePattern.exact((String) value);
return;
}
- if (name.equals(Target.fieldName) && value instanceof String) {
+ if (name.equals(Item.fieldName) && value instanceof String) {
fieldName = KeepFieldNamePattern.exact((String) value);
return;
}
@@ -184,8 +252,21 @@
if (fieldName != null) {
itemBuilder.setMemberPattern(KeepFieldPattern.builder().setNamePattern(fieldName).build());
}
- KeepTarget target = KeepTarget.builder().setItem(itemBuilder.build()).build();
- parent.accept(target);
+ parent.accept(itemBuilder.build());
+ }
+ }
+
+ private static class KeepTargetVisitor extends KeepItemVisitorBase {
+
+ public KeepTargetVisitor(Parent<KeepTarget> parent) {
+ super(item -> parent.accept(KeepTarget.builder().setItem(item).build()));
+ }
+ }
+
+ private static class KeepConditionVisitor extends KeepItemVisitorBase {
+
+ public KeepConditionVisitor(Parent<KeepCondition> parent) {
+ super(item -> parent.accept(KeepCondition.builder().setItem(item).build()));
}
}
}
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 0a0ccc8..a8f631a 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
@@ -3,7 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.asm;
+import com.android.tools.r8.keepanno.annotations.KeepConstants;
+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.Target;
import com.android.tools.r8.keepanno.ast.KeepConsequences;
import com.android.tools.r8.keepanno.ast.KeepEdge;
@@ -44,7 +47,15 @@
if (preconditions.isAlways()) {
return;
}
- throw new Unimplemented();
+ String ignoredArrayValueName = null;
+ AnnotationVisitor arrayVisitor = visitor.visitArray(Edge.preconditions);
+ preconditions.forEach(
+ condition -> {
+ AnnotationVisitor conditionVisitor =
+ arrayVisitor.visitAnnotation(ignoredArrayValueName, Condition.DESCRIPTOR);
+ writeItem(conditionVisitor, condition.getItemPattern());
+ });
+ arrayVisitor.visitEnd();
}
private void writeConsequences(AnnotationVisitor visitor, KeepConsequences consequences) {
@@ -59,26 +70,29 @@
if (!target.getOptions().isKeepAll()) {
throw new Unimplemented();
}
- KeepItemPattern item = target.getItem();
- if (item.isAny()) {
- throw new Unimplemented();
- }
- KeepQualifiedClassNamePattern namePattern = item.getClassNamePattern();
- if (namePattern.isExact()) {
- Type typeConstant = Type.getType(namePattern.getExactDescriptor());
- targetVisitor.visit(Target.classConstant, typeConstant);
- } else {
- throw new Unimplemented();
- }
- if (!item.getExtendsPattern().isAny()) {
- throw new Unimplemented();
- }
- writeMember(item.getMemberPattern(), targetVisitor);
- targetVisitor.visitEnd();
+ writeItem(targetVisitor, target.getItem());
});
arrayVisitor.visitEnd();
}
+ private void writeItem(AnnotationVisitor itemVisitor, KeepItemPattern item) {
+ if (item.isAny()) {
+ throw new Unimplemented();
+ }
+ KeepQualifiedClassNamePattern namePattern = item.getClassNamePattern();
+ if (namePattern.isExact()) {
+ Type typeConstant = Type.getType(namePattern.getExactDescriptor());
+ itemVisitor.visit(KeepConstants.Item.classConstant, typeConstant);
+ } else {
+ throw new Unimplemented();
+ }
+ if (!item.getExtendsPattern().isAny()) {
+ throw new Unimplemented();
+ }
+ writeMember(item.getMemberPattern(), itemVisitor);
+ itemVisitor.visitEnd();
+ }
+
private void writeMember(KeepMemberPattern memberPattern, AnnotationVisitor targetVisitor) {
if (memberPattern.isNone()) {
// Default is "no methods".
@@ -99,7 +113,7 @@
private void writeField(KeepFieldPattern field, AnnotationVisitor targetVisitor) {
KeepFieldNameExactPattern exactFieldName = field.getNamePattern().asExact();
if (exactFieldName != null) {
- targetVisitor.visit(Target.fieldName, exactFieldName.getName());
+ targetVisitor.visit(Item.fieldName, exactFieldName.getName());
} else {
throw new Unimplemented();
}
@@ -114,7 +128,7 @@
private void writeMethod(KeepMethodPattern method, AnnotationVisitor targetVisitor) {
KeepMethodNameExactPattern exactMethodName = method.getNamePattern().asExact();
if (exactMethodName != null) {
- targetVisitor.visit(Target.methodName, exactMethodName.getName());
+ targetVisitor.visit(Item.methodName, exactMethodName.getName());
} else {
throw new Unimplemented();
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCondition.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCondition.java
index 43b4176..9909097 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCondition.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCondition.java
@@ -36,10 +36,33 @@
private final KeepItemPattern itemPattern;
private KeepCondition(KeepItemPattern itemPattern) {
+ assert itemPattern != null;
this.itemPattern = itemPattern;
}
public KeepItemPattern getItemPattern() {
return itemPattern;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ KeepCondition that = (KeepCondition) o;
+ return itemPattern.equals(that.itemPattern);
+ }
+
+ @Override
+ public int hashCode() {
+ return itemPattern.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return itemPattern.toString();
+ }
}
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 18d3932..fe49466 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
@@ -9,9 +9,10 @@
import com.android.tools.r8.keepanno.annotations.KeepConstants;
import com.android.tools.r8.keepanno.annotations.KeepConstants.Edge;
-import com.android.tools.r8.keepanno.annotations.KeepConstants.Target;
+import com.android.tools.r8.keepanno.annotations.KeepConstants.Item;
import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
import com.android.tools.r8.keepanno.asm.KeepEdgeWriter;
+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.KeepEdge.Builder;
@@ -21,12 +22,15 @@
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
+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.utils.Unimplemented;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Consumer;
import javax.annotation.processing.AbstractProcessor;
@@ -58,26 +62,33 @@
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
- for (Element rootElement : roundEnv.getRootElements()) {
- TypeElement typeElement = getEnclosingTypeElement(rootElement);
- KeepEdge edge = processKeepEdge(typeElement, roundEnv);
- if (edge != null) {
- String edgeTargetClass =
- getClassTypeNameForSynthesizedEdges(typeElement.getQualifiedName().toString());
- byte[] writtenEdge = writeEdge(edge, edgeTargetClass);
- Filer filer = processingEnv.getFiler();
- try {
- JavaFileObject classFile = filer.createClassFile(edgeTargetClass);
- classFile.openOutputStream().write(writtenEdge);
- } catch (IOException e) {
- error(e.getMessage());
+ Map<String, List<KeepEdge>> collectedEdges = new HashMap<>();
+ for (TypeElement annotation : annotations) {
+ for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
+ KeepEdge edge = processKeepEdge(element, roundEnv);
+ if (edge != null) {
+ TypeElement enclosingType = getEnclosingTypeElement(element);
+ String enclosingTypeName = enclosingType.getQualifiedName().toString();
+ collectedEdges.computeIfAbsent(enclosingTypeName, k -> new ArrayList<>()).add(edge);
}
}
}
+ for (Entry<String, List<KeepEdge>> entry : collectedEdges.entrySet()) {
+ String enclosingTypeName = entry.getKey();
+ String edgeTargetClass = getClassTypeNameForSynthesizedEdges(enclosingTypeName);
+ byte[] writtenEdge = writeEdges(entry.getValue(), edgeTargetClass);
+ Filer filer = processingEnv.getFiler();
+ try {
+ JavaFileObject classFile = filer.createClassFile(edgeTargetClass);
+ classFile.openOutputStream().write(writtenEdge);
+ } catch (IOException e) {
+ error(e.getMessage());
+ }
+ }
return true;
}
- private static byte[] writeEdge(KeepEdge edge, String classTypeName) {
+ private static byte[] writeEdges(List<KeepEdge> edges, String classTypeName) {
String classBinaryName = KeepConstants.getBinaryNameFromClassTypeName(classTypeName);
ClassWriter classWriter = new ClassWriter(0);
classWriter.visit(
@@ -88,13 +99,15 @@
"java/lang/Object",
null);
classWriter.visitSource("SynthesizedKeepEdge", null);
- KeepEdgeWriter.writeEdge(edge, classWriter);
+ for (KeepEdge edge : edges) {
+ KeepEdgeWriter.writeEdge(edge, classWriter);
+ }
classWriter.visitEnd();
return classWriter.toByteArray();
}
- private KeepEdge processKeepEdge(TypeElement keepEdge, RoundEnvironment roundEnv) {
- AnnotationMirror mirror = getAnnotationMirror(keepEdge, KeepConstants.Edge.CLASS);
+ private KeepEdge processKeepEdge(Element element, RoundEnvironment roundEnv) {
+ AnnotationMirror mirror = getAnnotationMirror(element, KeepConstants.Edge.CLASS);
if (mirror == null) {
return null;
}
@@ -109,7 +122,15 @@
if (preconditions == null) {
return;
}
- throw new Unimplemented();
+ KeepPreconditions.Builder preconditionsBuilder = KeepPreconditions.builder();
+ new AnnotationListValueVisitor(
+ value -> {
+ KeepCondition.Builder conditionBuilder = KeepCondition.builder();
+ processCondition(conditionBuilder, AnnotationMirrorValueVisitor.getMirror(value));
+ preconditionsBuilder.addCondition(conditionBuilder.build());
+ })
+ .onValue(preconditions);
+ edgeBuilder.setPreconditions(preconditionsBuilder.build());
}
private void processConsequences(Builder edgeBuilder, AnnotationMirror mirror) {
@@ -141,31 +162,41 @@
}
}
+ private void processCondition(KeepCondition.Builder builder, AnnotationMirror mirror) {
+ KeepItemPattern.Builder itemBuilder = KeepItemPattern.builder();
+ processItem(itemBuilder, mirror);
+ builder.setItem(itemBuilder.build());
+ }
+
private void processTarget(KeepTarget.Builder builder, AnnotationMirror mirror) {
KeepItemPattern.Builder itemBuilder = KeepItemPattern.builder();
- AnnotationValue classConstantValue = getAnnotationValue(mirror, Target.classConstant);
+ processItem(itemBuilder, mirror);
+ builder.setItem(itemBuilder.build());
+ }
+
+ private void processItem(KeepItemPattern.Builder builder, AnnotationMirror mirror) {
+ AnnotationValue classConstantValue = getAnnotationValue(mirror, Item.classConstant);
if (classConstantValue != null) {
DeclaredType type = AnnotationClassValueVisitor.getType(classConstantValue);
String typeName = getTypeNameForClassConstantElement(type);
- itemBuilder.setClassPattern(KeepQualifiedClassNamePattern.exact(typeName));
+ builder.setClassPattern(KeepQualifiedClassNamePattern.exact(typeName));
}
- AnnotationValue methodNameValue = getAnnotationValue(mirror, Target.methodName);
- AnnotationValue fieldNameValue = getAnnotationValue(mirror, Target.fieldName);
+ AnnotationValue methodNameValue = getAnnotationValue(mirror, Item.methodName);
+ AnnotationValue fieldNameValue = getAnnotationValue(mirror, Item.fieldName);
if (methodNameValue != null && fieldNameValue != null) {
throw new KeepEdgeException("Cannot define both a method and a field name pattern");
}
if (methodNameValue != null) {
String methodName = AnnotationStringValueVisitor.getString(methodNameValue);
- itemBuilder.setMemberPattern(
+ builder.setMemberPattern(
KeepMethodPattern.builder()
.setNamePattern(KeepMethodNamePattern.exact(methodName))
.build());
} else if (fieldNameValue != null) {
String fieldName = AnnotationStringValueVisitor.getString(fieldNameValue);
- itemBuilder.setMemberPattern(
+ builder.setMemberPattern(
KeepFieldPattern.builder().setNamePattern(KeepFieldNamePattern.exact(fieldName)).build());
}
- builder.setItem(itemBuilder.build());
}
private void error(String message) {
@@ -181,9 +212,9 @@
}
}
- private static AnnotationMirror getAnnotationMirror(TypeElement typeElement, Class<?> clazz) {
+ private static AnnotationMirror getAnnotationMirror(Element element, Class<?> clazz) {
String clazzName = clazz.getName();
- for (AnnotationMirror m : typeElement.getAnnotationMirrors()) {
+ for (AnnotationMirror m : element.getAnnotationMirrors()) {
if (m.getAnnotationType().toString().equals(clazzName)) {
return m;
}
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 fcdab96..01ec5ac 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor;
import com.android.tools.r8.keepanno.processor.KeepEdgeProcessor;
import com.android.tools.r8.keepanno.testsource.KeepClassAndDefaultConstructorSource;
+import com.android.tools.r8.keepanno.testsource.KeepDependentFieldSource;
import com.android.tools.r8.keepanno.testsource.KeepFieldSource;
import com.android.tools.r8.keepanno.testsource.KeepSourceEdges;
import com.android.tools.r8.references.ClassReference;
@@ -42,7 +43,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.objectweb.asm.AnnotationVisitor;
@RunWith(Parameterized.class)
public class KeepEdgeAnnotationsTest extends TestBase {
@@ -66,7 +66,10 @@
Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "keepanno");
private static List<Class<?>> getTestClasses() {
- return ImmutableList.of(KeepClassAndDefaultConstructorSource.class, KeepFieldSource.class);
+ return ImmutableList.of(
+ KeepClassAndDefaultConstructorSource.class,
+ KeepFieldSource.class,
+ KeepDependentFieldSource.class);
}
private final TestParameters parameters;
@@ -145,14 +148,9 @@
// Strip out all the annotations to ensure they are actually added again.
byte[] stripped =
transformer(source)
- .addClassTransformer(
- new ClassTransformer() {
- @Override
- public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
- // Ignore all input annotations.
- return null;
- }
- })
+ .removeClassAnnotations()
+ .removeMethodAnnotations()
+ .removeFieldAnnotations()
.transform();
// Manually add in the expected edges again.
byte[] readded =
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepDependentFieldSource.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepDependentFieldSource.java
new file mode 100644
index 0000000..4a0df32
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepDependentFieldSource.java
@@ -0,0 +1,41 @@
+// 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.testsource;
+
+import com.android.tools.r8.keepanno.annotations.KeepCondition;
+import com.android.tools.r8.keepanno.annotations.KeepEdge;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import java.lang.reflect.Field;
+
+public class KeepDependentFieldSource {
+
+ public static class A {
+
+ public int f;
+
+ public A(int x) {
+ f = x;
+ }
+ }
+
+ // The keep edge is context independent, but natural to place close to the reflection usage.
+ @KeepEdge(
+ preconditions = {
+ // The edge is only needed if the main method that uses reflection is actually present.
+ @KeepCondition(classConstant = KeepDependentFieldSource.class, methodName = "main")
+ },
+ consequences = {
+ // Keep the reflectively accessed field.
+ @KeepTarget(classConstant = KeepDependentFieldSource.A.class, fieldName = "f")
+ })
+ public static void main(String[] args) throws Exception {
+ int x = 42 + args.length;
+ Object o = System.nanoTime() > 0 ? new A(x) : null;
+ Field f = o.getClass().getDeclaredField("f");
+ int y = f.getInt(o);
+ if (x == y) {
+ System.out.println("The values match!");
+ }
+ }
+}
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 7af9aee..d4c7121 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
@@ -3,17 +3,23 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.testsource;
+import com.android.tools.r8.keepanno.ast.KeepCondition;
import com.android.tools.r8.keepanno.ast.KeepConsequences;
+import com.android.tools.r8.keepanno.ast.KeepConsequences.Builder;
import com.android.tools.r8.keepanno.ast.KeepEdge;
import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
+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.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import java.util.Set;
/**
@@ -23,22 +29,50 @@
*/
public class KeepSourceEdges {
- public static Set<KeepEdge> getExpectedEdges(Class<?> clazz) {
- if (clazz.equals(KeepClassAndDefaultConstructorSource.class)) {
- return getKeepClassAndDefaultConstructorSourceEdges();
+ private static class SourceData {
+ final Class<?> clazz;
+ final String expected;
+ final Set<KeepEdge> edges;
+
+ public SourceData(Class<?> clazz, String expected, Set<KeepEdge> edges) {
+ this.clazz = clazz;
+ this.expected = expected;
+ this.edges = edges;
}
- if (clazz.equals(KeepFieldSource.class)) {
- return getKeepFieldSourceEdges();
+ }
+
+ private static final List<SourceData> SOURCES = new ArrayList<>();
+
+ static {
+ SOURCES.add(
+ new SourceData(
+ KeepClassAndDefaultConstructorSource.class,
+ getKeepClassAndDefaultConstructorSourceExpected(),
+ getKeepClassAndDefaultConstructorSourceEdges()));
+ SOURCES.add(
+ new SourceData(
+ KeepFieldSource.class, getKeepFieldSourceExpected(), getKeepFieldSourceEdges()));
+ SOURCES.add(
+ new SourceData(
+ KeepDependentFieldSource.class,
+ getKeepDependentFieldSourceExpected(),
+ getKeepDependentFieldSourceEdges()));
+ }
+
+ public static Set<KeepEdge> getExpectedEdges(Class<?> clazz) {
+ for (SourceData source : SOURCES) {
+ if (source.clazz == clazz) {
+ return source.edges;
+ }
}
throw new RuntimeException();
}
public static String getExpected(Class<?> clazz) {
- if (clazz.equals(KeepClassAndDefaultConstructorSource.class)) {
- return getKeepClassAndDefaultConstructorSourceExpected();
- }
- if (clazz.equals(KeepFieldSource.class)) {
- return getKeepFieldSourceExpected();
+ for (SourceData source : SOURCES) {
+ if (source.clazz == clazz) {
+ return source.expected;
+ }
}
throw new RuntimeException();
}
@@ -49,21 +83,8 @@
public static Set<KeepEdge> getKeepClassAndDefaultConstructorSourceEdges() {
Class<?> clazz = KeepClassAndDefaultConstructorSource.A.class;
- // Build the class target.
- KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
- KeepItemPattern classItem = KeepItemPattern.builder().setClassPattern(name).build();
- KeepTarget classTarget = KeepTarget.builder().setItem(classItem).build();
- // Build the constructor target.
- KeepMethodPattern constructorMethod =
- KeepMethodPattern.builder().setNamePattern(KeepMethodNamePattern.exact("<init>")).build();
- KeepItemPattern constructorItem =
- KeepItemPattern.builder().setClassPattern(name).setMemberPattern(constructorMethod).build();
- KeepTarget constructorTarget = KeepTarget.builder().setItem(constructorItem).build();
- // The consequet set is the class an its constructor.
- KeepConsequences consequences =
- KeepConsequences.builder().addTarget(classTarget).addTarget(constructorTarget).build();
- KeepEdge edge = KeepEdge.builder().setConsequences(consequences).build();
- return Collections.singleton(edge);
+ return Collections.singleton(
+ mkEdge(mkConsequences(mkTarget(mkClass(clazz)), mkTarget(mkMethod(clazz, "<init>")))));
}
public static String getKeepFieldSourceExpected() {
@@ -71,15 +92,71 @@
}
public static Set<KeepEdge> getKeepFieldSourceEdges() {
- Class<?> clazz = KeepFieldSource.A.class;
+ return Collections.singleton(
+ mkEdge(mkConsequences(mkTarget(mkField(KeepFieldSource.A.class, "f")))));
+ }
+
+ public static String getKeepDependentFieldSourceExpected() {
+ return getKeepFieldSourceExpected();
+ }
+
+ public static Set<KeepEdge> getKeepDependentFieldSourceEdges() {
+ return Collections.singleton(
+ mkDepEdge(
+ mkPreconditions(mkCondition(mkMethod(KeepDependentFieldSource.class, "main"))),
+ mkConsequences(mkTarget(mkField(KeepDependentFieldSource.A.class, "f")))));
+ }
+
+ // Ast helpers.
+
+ static KeepItemPattern mkClass(Class<?> clazz) {
+ KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
+ return KeepItemPattern.builder().setClassPattern(name).build();
+ }
+
+ static KeepItemPattern mkMethod(Class<?> clazz, String methodName) {
+ KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
+ KeepMethodPattern methodPattern =
+ KeepMethodPattern.builder().setNamePattern(KeepMethodNamePattern.exact(methodName)).build();
+ KeepItemPattern methodItem =
+ KeepItemPattern.builder().setClassPattern(name).setMemberPattern(methodPattern).build();
+ return methodItem;
+ }
+
+ static KeepItemPattern mkField(Class<?> clazz, String fieldName) {
KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
KeepFieldPattern fieldPattern =
- KeepFieldPattern.builder().setNamePattern(KeepFieldNamePattern.exact("f")).build();
+ KeepFieldPattern.builder().setNamePattern(KeepFieldNamePattern.exact(fieldName)).build();
KeepItemPattern fieldItem =
KeepItemPattern.builder().setClassPattern(name).setMemberPattern(fieldPattern).build();
- KeepTarget fieldTarget = KeepTarget.builder().setItem(fieldItem).build();
- KeepConsequences consequences = KeepConsequences.builder().addTarget(fieldTarget).build();
- KeepEdge edge = KeepEdge.builder().setConsequences(consequences).build();
- return Collections.singleton(edge);
+ return fieldItem;
+ }
+
+ static KeepTarget mkTarget(KeepItemPattern item) {
+ return KeepTarget.builder().setItem(item).build();
+ }
+
+ static KeepCondition mkCondition(KeepItemPattern item) {
+ return KeepCondition.builder().setItem(item).build();
+ }
+
+ static KeepConsequences mkConsequences(KeepTarget... targets) {
+ Builder builder = KeepConsequences.builder();
+ Arrays.asList(targets).forEach(builder::addTarget);
+ return builder.build();
+ }
+
+ static KeepPreconditions mkPreconditions(KeepCondition... conditions) {
+ KeepPreconditions.Builder builder = KeepPreconditions.builder();
+ Arrays.asList(conditions).forEach(builder::addCondition);
+ return builder.build();
+ }
+
+ static KeepEdge mkEdge(KeepConsequences consequences) {
+ return KeepEdge.builder().setConsequences(consequences).build();
+ }
+
+ static KeepEdge mkDepEdge(KeepPreconditions preconditions, KeepConsequences consequences) {
+ return KeepEdge.builder().setPreconditions(preconditions).setConsequences(consequences).build();
}
}
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 8108730..7aa8301 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -1463,4 +1463,42 @@
}
});
}
+
+ public ClassFileTransformer removeClassAnnotations() {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ // Ignore all input annotations.
+ return null;
+ }
+ });
+ }
+
+ public ClassFileTransformer removeMethodAnnotations() {
+ return addMethodTransformer(
+ new MethodTransformer() {
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ return null;
+ }
+ });
+ }
+
+ public ClassFileTransformer removeFieldAnnotations() {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public FieldVisitor visitField(
+ int access, String name, String descriptor, String signature, Object value) {
+ FieldVisitor fv = visitField(access, name, descriptor, signature, value);
+ return new FieldVisitor(ASM_VERSION, fv) {
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ return null;
+ }
+ };
+ }
+ });
+ }
}