[KeepAnno] Allow multiple extracted edges per type
Bug: b/323815449
Change-Id: I40f0f4f9037ac749626d861fea63703700492602
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotation.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotation.java
new file mode 100644
index 0000000..8b9d95c
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotation.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2024, 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;
+
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.CLASS)
+@Repeatable(ExtractedKeepAnnotations.class)
+public @interface ExtractedKeepAnnotation {
+ /**
+ * The version defining this extracted keep annotation.
+ *
+ * <p>Note: this version property must be the first property defined. Its content may determine
+ * the subsequent parsing.
+ */
+ String version();
+
+ /**
+ * The context giving rise to this extracted keep annotation.
+ *
+ * <p>The context must be a class descriptor, method descriptor or field descriptor.
+ */
+ String context();
+
+ /** The extracted edge. */
+ KeepEdge edge();
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotations.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotations.java
index 43a91bb..f6855cc 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotations.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotations.java
@@ -8,24 +8,15 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+/**
+ * Collection of extracted keep annotations.
+ *
+ * <p>This annotation is just a collection of the extracted annotations. It is version independent
+ * and is assumed to never change. Any version specific changes are to be made within the single
+ * element structure of {@link ExtractedKeepAnnotation}.
+ */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface ExtractedKeepAnnotations {
- /**
- * The version of defining this extracted keep annotation.
- *
- * <p>Note: this version property must be the first property defined. Its content may determine
- * the subsequent parsing.
- */
- String version();
-
- /**
- * The context giving rise to this extracted keep annotation.
- *
- * <p>The context must be a class descriptor, method descriptor or field descriptor.
- */
- String context();
-
- /** The extracted edges. */
- KeepEdge[] edges();
+ ExtractedKeepAnnotation[] 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 e1c5ace..91d9c2e 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
@@ -13,7 +13,8 @@
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Binding;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Condition;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Edge;
-import com.android.tools.r8.keepanno.ast.AnnotationConstants.Extracted;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.ExtractedAnnotation;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.ExtractedAnnotations;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.FieldAccess;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.ForApi;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Item;
@@ -235,8 +236,9 @@
if (visible) {
return null;
}
- if (readExtracted && descriptor.equals(Extracted.DESCRIPTOR)) {
- return new ExtractedAnnotationVisitor(annotationParsingContext(descriptor), parent::accept);
+ if (readExtracted && descriptor.equals(ExtractedAnnotations.DESCRIPTOR)) {
+ return new ExtractedAnnotationsVisitor(
+ annotationParsingContext(descriptor), parent::accept);
}
if (!readEmbedded) {
return null;
@@ -527,12 +529,52 @@
}
}
+ private static class ExtractedAnnotationsVisitor extends AnnotationVisitorBase {
+
+ private final Parent<KeepDeclaration> parent;
+ private List<KeepDeclaration> declarations = new ArrayList<>();
+
+ public ExtractedAnnotationsVisitor(
+ AnnotationParsingContext parsingContext, Parent<KeepDeclaration> parent) {
+ super(parsingContext);
+ this.parent = parent;
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ if (name.equals(ExtractedAnnotations.value)) {
+ PropertyParsingContext parsingContext = getParsingContext().property(name);
+ return new AnnotationVisitorBase(parsingContext) {
+ @Override
+ public AnnotationVisitor visitAnnotation(String nullName, String descriptor) {
+ assert nullName == null;
+ if (descriptor.equals(ExtractedAnnotation.DESCRIPTOR)) {
+ return new ExtractedAnnotationVisitor(
+ parsingContext.annotation(descriptor), declarations::add);
+ }
+ return super.visitAnnotation(nullName, descriptor);
+ }
+ };
+ }
+ return super.visitArray(name);
+ }
+
+ @Override
+ public void visitEnd() {
+ if (declarations.isEmpty()) {
+ throw new KeepEdgeException("Invalid extracted annotation set, expected non-empty.");
+ }
+ declarations.forEach(parent::accept);
+ super.visitEnd();
+ }
+ }
+
private static class ExtractedAnnotationVisitor extends AnnotationVisitorBase {
private final Parent<KeepDeclaration> parent;
private String context = null;
private String version = null;
- private List<KeepEdgeVisitor> edgeVisitors = new ArrayList<>();
+ private KeepEdgeVisitor edgeVisitor = null;
public ExtractedAnnotationVisitor(
AnnotationParsingContext parsingContext, Parent<KeepDeclaration> parent) {
@@ -548,12 +590,12 @@
@Override
public void visit(String name, Object value) {
- if (name.equals(Extracted.version) && value instanceof String) {
+ if (name.equals(ExtractedAnnotation.version) && value instanceof String) {
version = (String) value;
return;
}
ensureVersion(getParsingContext().property(name));
- if (name.equals(Extracted.context) && value instanceof String) {
+ if (name.equals(ExtractedAnnotation.context) && value instanceof String) {
context = (String) value;
return;
}
@@ -561,48 +603,37 @@
}
@Override
- public AnnotationVisitor visitArray(String name) {
- if (name.equals(Extracted.edges)) {
- PropertyParsingContext parsingContext = getParsingContext().property(name);
- ensureVersion(parsingContext);
- return new AnnotationVisitorBase(parsingContext) {
- @Override
- public AnnotationVisitor visitAnnotation(String nullName, String descriptor) {
- assert nullName == null;
- if (descriptor.equals(Edge.DESCRIPTOR)) {
- KeepEdgeVisitor visitor =
- new KeepEdgeVisitor(
- parsingContext.annotation(descriptor), edge -> {}, builder -> {});
- edgeVisitors.add(visitor);
- return visitor;
- }
- return super.visitAnnotation(nullName, descriptor);
- }
- };
+ public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ if (name.equals(ExtractedAnnotation.edge) && descriptor.equals(Edge.DESCRIPTOR)) {
+ edgeVisitor =
+ new KeepEdgeVisitor(
+ getParsingContext().annotation(descriptor), edge -> {}, builder -> {});
+ return edgeVisitor;
}
- return super.visitArray(name);
+ return super.visitAnnotation(name, descriptor);
}
@Override
public void visitEnd() {
if (version == null) {
- throw new KeepEdgeException("Invalid extracted edge, expected a version property.");
+ throw new KeepEdgeException("Invalid extracted annotation, expected a version property.");
}
if (context == null) {
- throw new KeepEdgeException("Invalid extracted edge, expected a context property.");
+ throw new KeepEdgeException("Invalid extracted annotation, expected a context property.");
}
- for (KeepEdgeVisitor visitor : edgeVisitors) {
- parent.accept(
- visitor
- .builder
- .setMetaInfo(
- visitor
- .metaInfoBuilder
- // TODO(b/323815449): This may be a method or field descriptor!
- .setContextFromClassDescriptor(context)
- .build())
- .build());
+ if (edgeVisitor == null) {
+ throw new KeepEdgeException("Invalid extracted annotation, expected an edge property.");
}
+ parent.accept(
+ edgeVisitor
+ .builder
+ .setMetaInfo(
+ edgeVisitor
+ .metaInfoBuilder
+ // TODO(b/323815449): This may be a method or field descriptor!
+ .setContextFromClassDescriptor(context)
+ .build())
+ .build());
super.visitEnd();
}
}
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 8145c50..dbbfa7c 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
@@ -11,7 +11,8 @@
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Condition;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Constraints;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Edge;
-import com.android.tools.r8.keepanno.ast.AnnotationConstants.Extracted;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.ExtractedAnnotation;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.ExtractedAnnotations;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.FieldAccess;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Item;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Kind;
@@ -28,6 +29,7 @@
import com.android.tools.r8.keepanno.ast.KeepConsequences;
import com.android.tools.r8.keepanno.ast.KeepConstraint;
import com.android.tools.r8.keepanno.ast.KeepConstraints;
+import com.android.tools.r8.keepanno.ast.KeepDeclaration;
import com.android.tools.r8.keepanno.ast.KeepEdge;
import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
import com.android.tools.r8.keepanno.ast.KeepFieldAccessPattern;
@@ -121,19 +123,35 @@
visitor.visitEnd();
}
- public static void writeExtractedEdge(
- KeepEdge edge, BiFunction<String, Boolean, AnnotationVisitorInterface> getVisitor) {
+ public static void writeExtractedEdges(
+ List<KeepDeclaration> declarations,
+ BiFunction<String, Boolean, AnnotationVisitorInterface> getVisitor) {
+ if (declarations.isEmpty()) {
+ return;
+ }
withNewVisitor(
- wrap(getVisitor.apply(Extracted.DESCRIPTOR, false)),
- extractVisitor -> {
- extractVisitor.visit("version", edge.getMetaInfo().getVersion().toVersionString());
- extractVisitor.visit("context", edge.getMetaInfo().getContextDescriptorString());
- withNewVisitor(
- extractVisitor.visitArray("edges"),
- edgeVisitor ->
- writeEdgeInternal(
- edge, (desc, visible) -> edgeVisitor.visitAnnotation(null, desc)));
- });
+ wrap(getVisitor.apply(ExtractedAnnotations.DESCRIPTOR, false)),
+ containerVisitor ->
+ withNewVisitor(
+ containerVisitor.visitArray(ExtractedAnnotations.value),
+ arrayVisitor ->
+ declarations.forEach(
+ decl ->
+ withNewVisitor(
+ arrayVisitor.visitAnnotation(null, ExtractedAnnotation.DESCRIPTOR),
+ extractVisitor -> writeExtractedEdge(extractVisitor, decl)))));
+ }
+
+ private static void writeExtractedEdge(AnnotationVisitor visitor, KeepDeclaration decl) {
+ if (decl.isKeepCheck()) {
+ throw new Unimplemented("Checks not yet supported for extraction");
+ }
+ KeepEdgeMetaInfo metaInfo = decl.getMetaInfo();
+ visitor.visit(ExtractedAnnotation.version, metaInfo.getVersion().toVersionString());
+ visitor.visit(ExtractedAnnotation.context, metaInfo.getContextDescriptorString());
+ writeEdgeInternal(
+ decl.asKeepEdge(),
+ (desc, visible) -> visitor.visitAnnotation(ExtractedAnnotation.edge, desc));
}
public static void writeEdge(
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
index 0789197..d1e20ef 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
@@ -15,12 +15,18 @@
* annotations which overlap in name with the actual semantic AST types.
*/
public final class AnnotationConstants {
- public static final class Extracted {
+ public static final class ExtractedAnnotations {
public static final String DESCRIPTOR =
"Lcom/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotations;";
+ public static final String value = "value";
+ }
+
+ public static final class ExtractedAnnotation {
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotation;";
public static final String version = "version";
public static final String context = "context";
- public static final String edges = "edges";
+ public static final String edge = "edge";
}
public static final class Edge {
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
index 9488245..51ca386 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
@@ -20,9 +20,7 @@
import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
import com.android.tools.r8.keepanno.asm.KeepEdgeWriter;
import com.android.tools.r8.keepanno.ast.KeepDeclaration;
-import com.android.tools.r8.keepanno.ast.KeepEdge;
import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractorOptions;
-import com.android.tools.r8.keepanno.utils.Unimplemented;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -242,17 +240,10 @@
null,
"java/lang/Object",
null);
- for (KeepDeclaration decl : declarations) {
- if (!decl.isKeepEdge()) {
- throw new Unimplemented("Support check declarations...");
- } else {
- KeepEdge edge = decl.asKeepEdge();
- KeepEdgeWriter.writeExtractedEdge(
- edge,
- (descriptor, visible) ->
- KeepAnnoTestUtils.wrap(classWriter.visitAnnotation(descriptor, visible)));
- }
- }
+ KeepEdgeWriter.writeExtractedEdges(
+ declarations,
+ (descriptor, visible) ->
+ KeepAnnoTestUtils.wrap(classWriter.visitAnnotation(descriptor, visible)));
classWriter.visitEnd();
builder
.getBuilder()
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepMembersApiTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepMembersApiTest.java
index fe9b59c..c6d3b7d 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepMembersApiTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepMembersApiTest.java
@@ -58,7 +58,6 @@
assertTrue(parameters.isShrinker());
Box<Path> lib = new Box<>();
testForKeepAnno(parameters)
- .skipEdgeExtraction()
.addProgramClasses(getLibraryClasses())
.setExcludedOuterClass(getClass())
.applyIfShrinker(b -> lib.set(b.compile().inspect(this::checkLibraryOutput).writeToZip()));
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepTargetClassAndMemberKindTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepTargetClassAndMemberKindTest.java
index e3a9569..9afb8b1 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepTargetClassAndMemberKindTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepTargetClassAndMemberKindTest.java
@@ -36,7 +36,6 @@
@Test
public void test() throws Exception {
testForKeepAnno(parameters)
- .skipEdgeExtraction()
.addProgramClasses(getInputClasses())
.addKeepMainRule(TestClass.class)
.setExcludedOuterClass(getClass())
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 988e441..1c0313a 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java
@@ -35,7 +35,6 @@
@Test
public void test() throws Exception {
testForKeepAnno(parameters)
- .skipEdgeExtraction()
.addProgramClasses(getInputClasses())
.addKeepMainRule(TestClass.class)
.setExcludedOuterClass(getClass())
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
index 977afd5..ed4a86d 100644
--- a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
@@ -81,6 +81,8 @@
private static final ClassReference CHECK_OPTIMIZED_OUT = annoClass("CheckOptimizedOut");
private static final ClassReference EXTRACTED_KEEP_ANNOTATIONS =
annoClass("ExtractedKeepAnnotations");
+ private static final ClassReference EXTRACTED_KEEP_ANNOTATION =
+ annoClass("ExtractedKeepAnnotation");
private static final ClassReference KEEP_EDGE = annoClass("KeepEdge");
private static final ClassReference KEEP_BINDING = annoClass("KeepBinding");
private static final ClassReference KEEP_TARGET = annoClass("KeepTarget");
@@ -1619,10 +1621,21 @@
}
private void generateExtractedKeepAnnotationsConstants() {
- println("public static final class Extracted {");
+ println("public static final class ExtractedAnnotations {");
withIndent(
() -> {
generateAnnotationConstants(EXTRACTED_KEEP_ANNOTATIONS);
+ new GroupMember("value")
+ .setDocTitle("Extracted normalized keep edges.")
+ .requiredArrayValue(KEEP_EDGE)
+ .generateConstants(this);
+ });
+ println("}");
+ println();
+ println("public static final class ExtractedAnnotation {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(EXTRACTED_KEEP_ANNOTATION);
new GroupMember("version")
.setDocTitle("Extraction version used to generate this keep annotation.")
.requiredStringValue()
@@ -1631,9 +1644,9 @@
.setDocTitle("Extraction context from which this keep annotation is generated.")
.requiredStringValue()
.generateConstants(this);
- new GroupMember("edges")
- .setDocTitle("Extracted normalized keep edges.")
- .requiredArrayValue(KEEP_EDGE)
+ new GroupMember("edge")
+ .setDocTitle("Extracted normalized keep edge.")
+ .requiredValue(KEEP_EDGE)
.generateConstants(this);
});
println("}");