[KeepAnno] Support extraction of check removed/optimized-out
Bug: b/323815449
Change-Id: I0fbf3cf1ed2def82391b10338002a06cdf730a6d
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
index 8b9d95c..e2e9461 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotation.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotation.java
@@ -27,4 +27,8 @@
/** The extracted edge. */
KeepEdge edge();
+
+ boolean isCheckRemoved() default false;
+
+ boolean isCheckOptimizedOut() default false;
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ContextDescriptor.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ContextDescriptor.java
new file mode 100644
index 0000000..66bb6d5
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ContextDescriptor.java
@@ -0,0 +1,154 @@
+// 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.asm;
+
+import static com.android.tools.r8.keepanno.ast.KeepTypePattern.fromDescriptor;
+
+import com.android.tools.r8.keepanno.ast.KeepClassItemPattern;
+import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
+import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldTypePattern;
+import com.android.tools.r8.keepanno.ast.KeepItemPattern;
+import com.android.tools.r8.keepanno.ast.KeepMemberItemPattern;
+import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
+import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
+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.KeepQualifiedClassNamePattern;
+import com.android.tools.r8.keepanno.ast.ParsingContext;
+import org.objectweb.asm.Type;
+
+public class ContextDescriptor {
+
+ public static ContextDescriptor parse(String descriptor, ParsingContext parsingContext) {
+ int classDescriptorEnd = descriptor.indexOf(';') + 1;
+ if (classDescriptorEnd <= 0) {
+ throw parsingContext.error("Invalid descriptor: " + descriptor);
+ }
+ String classDescriptor = descriptor.substring(0, classDescriptorEnd);
+ if (classDescriptorEnd == descriptor.length()) {
+ return new ContextDescriptor(classDescriptor);
+ }
+ int memberNameEnd = descriptor.indexOf('(', classDescriptorEnd);
+ if (memberNameEnd < 0) {
+ memberNameEnd = descriptor.indexOf(':', classDescriptorEnd);
+ }
+ if (memberNameEnd < 0) {
+ throw parsingContext.error("Invalid descriptor: " + descriptor);
+ }
+ String memberName = descriptor.substring(classDescriptorEnd, memberNameEnd);
+ String memberDescriptor = descriptor.substring(memberNameEnd);
+ return new ContextDescriptor(classDescriptor, memberName, memberDescriptor);
+ }
+
+ private final String classDescriptor;
+ private final String memberName;
+ private final String memberDescriptor;
+
+ private ContextDescriptor(String classDescriptor) {
+ this(classDescriptor, null, null);
+ }
+
+ private ContextDescriptor(String classDescriptor, String memberName, String memberDescriptor) {
+ this.classDescriptor = classDescriptor;
+ this.memberName = memberName;
+ this.memberDescriptor = memberDescriptor;
+ }
+
+ public boolean isClassContext() {
+ return memberDescriptor == null;
+ }
+
+ public boolean isMethodContext() {
+ return memberDescriptor != null && memberDescriptor.charAt(0) == '(';
+ }
+
+ public boolean isFieldContext() {
+ return memberDescriptor != null && memberDescriptor.charAt(0) == ':';
+ }
+
+ public String getClassDescriptor() {
+ return classDescriptor;
+ }
+
+ public String getMemberName() {
+ return memberName;
+ }
+
+ public String getMethodDescriptor() {
+ assert isMethodContext();
+ return memberDescriptor;
+ }
+
+ public String getFieldType() {
+ assert isFieldContext();
+ return memberDescriptor.substring(1);
+ }
+
+ public KeepEdgeMetaInfo.Builder applyToMetadata(KeepEdgeMetaInfo.Builder metaInfoBuilder) {
+ if (isClassContext()) {
+ metaInfoBuilder.setContextFromClassDescriptor(getClassDescriptor());
+ } else if (isMethodContext()) {
+ metaInfoBuilder.setContextFromMethodDescriptor(
+ getClassDescriptor(), getMemberName(), getMethodDescriptor());
+ } else {
+ assert isFieldContext();
+ metaInfoBuilder.setContextFromFieldDescriptor(
+ getClassDescriptor(), getMemberName(), getFieldType());
+ }
+ return metaInfoBuilder;
+ }
+
+ public KeepItemPattern toItemPattern() {
+ KeepQualifiedClassNamePattern className =
+ KeepQualifiedClassNamePattern.exactFromDescriptor(getClassDescriptor());
+ KeepClassItemPattern classItem =
+ KeepClassItemPattern.builder().setClassNamePattern(className).build();
+ if (isClassContext()) {
+ return classItem;
+ }
+ KeepMemberPattern memberPattern;
+ if (isMethodContext()) {
+ memberPattern = createMethodPatternFromDescriptor();
+ } else {
+ assert isFieldContext();
+ memberPattern = createFieldPatternFromDescriptor();
+ }
+ return KeepMemberItemPattern.builder()
+ .setClassReference(classItem.toClassItemReference())
+ .setMemberPattern(memberPattern)
+ .build();
+ }
+
+ private KeepFieldPattern createFieldPatternFromDescriptor() {
+ return KeepFieldPattern.builder()
+ .setNamePattern(KeepFieldNamePattern.exact(getMemberName()))
+ .setTypePattern(KeepFieldTypePattern.fromType(fromDescriptor(getFieldType())))
+ .build();
+ }
+
+ private KeepMethodPattern createMethodPatternFromDescriptor() {
+ Type methodType = Type.getMethodType(getMethodDescriptor());
+
+ String returnTypeDescriptor = methodType.getReturnType().getDescriptor();
+ KeepMethodReturnTypePattern returnType =
+ returnTypeDescriptor.equals("V")
+ ? KeepMethodReturnTypePattern.voidType()
+ : KeepMethodReturnTypePattern.fromType(fromDescriptor(returnTypeDescriptor));
+
+ KeepMethodParametersPattern.Builder paramBuilder = KeepMethodParametersPattern.builder();
+ for (Type argumentType : methodType.getArgumentTypes()) {
+ paramBuilder.addParameterTypePattern(fromDescriptor(argumentType.getDescriptor()));
+ }
+
+ return KeepMethodPattern.builder()
+ .setNamePattern(KeepMethodNamePattern.exact(getMemberName()))
+ .setReturnTypePattern(returnType)
+ .setParametersPattern(paramBuilder.build())
+ .build();
+ }
+}
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 687f705..01d57c7 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
@@ -572,9 +572,11 @@
private static class ExtractedAnnotationVisitor extends AnnotationVisitorBase {
private final Parent<KeepDeclaration> parent;
- private String context = null;
+ private ContextDescriptor context = null;
private String version = null;
private KeepEdgeVisitor edgeVisitor = null;
+ private boolean isCheckRemoved = false;
+ private boolean isCheckOptimizedOut = false;
public ExtractedAnnotationVisitor(
AnnotationParsingContext parsingContext, Parent<KeepDeclaration> parent) {
@@ -594,9 +596,18 @@
version = (String) value;
return;
}
- ensureVersion(getParsingContext().property(name));
+ ParsingContext parsingContext = getParsingContext().property(name);
+ ensureVersion(parsingContext);
if (name.equals(ExtractedAnnotation.context) && value instanceof String) {
- context = (String) value;
+ context = ContextDescriptor.parse((String) value, parsingContext);
+ return;
+ }
+ if (name.equals(ExtractedAnnotation.checkRemoved) && value instanceof Boolean) {
+ isCheckRemoved = true;
+ return;
+ }
+ if (name.equals(ExtractedAnnotation.checkOptimizedOut) && value instanceof Boolean) {
+ isCheckOptimizedOut = true;
return;
}
super.visit(name, value);
@@ -616,23 +627,42 @@
@Override
public void visitEnd() {
if (version == null) {
- throw new KeepEdgeException("Invalid extracted annotation, expected a version property.");
+ throw getParsingContext()
+ .error("Invalid extracted annotation, expected a version property.");
}
if (context == null) {
- throw new KeepEdgeException("Invalid extracted annotation, expected a context property.");
+ throw getParsingContext()
+ .error("Invalid extracted annotation, expected a context property.");
}
- if (edgeVisitor == null) {
- throw new KeepEdgeException("Invalid extracted annotation, expected an edge property.");
+ if (edgeVisitor != null) {
+ if (isCheckRemoved || isCheckOptimizedOut) {
+ throw getParsingContext()
+ .error("Invalid extracted annotation, cannot be both an edge and check.");
+ }
+ parent.accept(
+ edgeVisitor
+ .builder
+ .setMetaInfo(context.applyToMetadata(edgeVisitor.metaInfoBuilder).build())
+ .build());
+ return;
}
+ if (isCheckRemoved && isCheckOptimizedOut) {
+ throw getParsingContext()
+ .error(
+ "Invalid extracted annotation, cannot be both a removed and optimized-out check.");
+ }
+ if (!isCheckRemoved && !isCheckOptimizedOut) {
+ throw getParsingContext()
+ .error(
+ "Invalid extracted annotation, must specify either an edge, a removed check, or an"
+ + " optimized-out check.");
+ }
+ KeepCheckKind kind = isCheckRemoved ? KeepCheckKind.REMOVED : KeepCheckKind.OPTIMIZED_OUT;
parent.accept(
- edgeVisitor
- .builder
- .setMetaInfo(
- edgeVisitor
- .metaInfoBuilder
- // TODO(b/323815449): This may be a method or field descriptor!
- .setContextFromClassDescriptor(context)
- .build())
+ KeepCheck.builder()
+ .setMetaInfo(context.applyToMetadata(KeepEdgeMetaInfo.builder()).build())
+ .setKind(kind)
+ .setItemPattern(context.toItemPattern())
.build());
super.visitEnd();
}
@@ -2211,4 +2241,5 @@
}
}
}
+
}
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 560b14c..a0a98c7 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
@@ -32,6 +32,7 @@
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.KeepEdgeException;
import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
import com.android.tools.r8.keepanno.ast.KeepFieldAccessPattern;
import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
@@ -145,27 +146,29 @@
}
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(
- KeepEdge edge, BiFunction<String, Boolean, AnnotationVisitorInterface> getVisitor) {
- writeEdgeInternal(edge, (descriptor, visible) -> wrap(getVisitor.apply(descriptor, visible)));
- }
-
- public static void writeEdgeInternal(
- KeepEdge edge, BiFunction<String, Boolean, AnnotationVisitor> getVisitor) {
- withNewVisitor(
- getVisitor.apply(Edge.DESCRIPTOR, false),
- visitor -> new KeepEdgeWriter().writeEdge(edge, visitor));
+ decl.match(
+ edge -> {
+ withNewVisitor(
+ visitor.visitAnnotation(ExtractedAnnotation.edge, Edge.DESCRIPTOR),
+ v -> new KeepEdgeWriter().writeEdge(edge, v));
+ return null;
+ },
+ check -> {
+ switch (check.getKind()) {
+ case REMOVED:
+ visitor.visit(ExtractedAnnotation.checkRemoved, true);
+ break;
+ case OPTIMIZED_OUT:
+ visitor.visit(ExtractedAnnotation.checkOptimizedOut, true);
+ break;
+ default:
+ throw new KeepEdgeException("Unexpected keep check kind: " + check.getKind());
+ }
+ return null;
+ });
}
private void writeEdge(KeepEdge edge, AnnotationVisitor visitor) {
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 8f539a7..81ccbc6 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
@@ -26,7 +26,10 @@
"Lcom/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotation;";
public static final String version = "version";
public static final String context = "context";
+ public static final String keepAnnotationGroup = "keep-annotation";
public static final String edge = "edge";
+ public static final String checkRemoved = "checkRemoved";
+ public static final String checkOptimizedOut = "checkOptimizedOut";
}
public static final class Edge {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepDeclaration.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepDeclaration.java
index c6f6248..4425317 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepDeclaration.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepDeclaration.java
@@ -3,11 +3,20 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.ast;
+import java.util.function.Function;
+
/** Base class for the declarations represented in the keep annoations library. */
public abstract class KeepDeclaration {
public abstract KeepEdgeMetaInfo getMetaInfo();
+ public final <T> T match(Function<KeepEdge, T> onEdge, Function<KeepCheck, T> onCheck) {
+ if (isKeepEdge()) {
+ return onEdge.apply(asKeepEdge());
+ }
+ return onCheck.apply(asKeepCheck());
+ }
+
public final boolean isKeepEdge() {
return asKeepEdge() != null;
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/CheckOptimizedOutAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/CheckOptimizedOutAnnotationTest.java
index cfe7d75..59915bc9 100644
--- a/src/test/java/com/android/tools/r8/keepanno/CheckOptimizedOutAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/CheckOptimizedOutAnnotationTest.java
@@ -57,7 +57,6 @@
public void testR8Native() throws Throwable {
assumeTrue(parameters.isR8() && parameters.isNative());
testForKeepAnno(parameters)
- .skipEdgeExtraction()
.addProgramClasses(getInputClasses())
.addKeepMainRule(TestClass.class)
.applyIfR8Native(
diff --git a/src/test/java/com/android/tools/r8/keepanno/CheckRemovedAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/CheckRemovedAnnotationTest.java
index a1a4a2c..b3bedf1 100644
--- a/src/test/java/com/android/tools/r8/keepanno/CheckRemovedAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/CheckRemovedAnnotationTest.java
@@ -56,7 +56,6 @@
public void testR8Native() throws Exception {
assumeTrue(parameters.isR8() && parameters.isNative());
testForKeepAnno(parameters)
- .skipEdgeExtraction()
.addProgramClasses(getInputClasses())
.addKeepMainRule(TestClass.class)
.applyIfR8Native(
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 ee005f6..325960e 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
@@ -1728,9 +1728,19 @@
.setDocTitle("Extraction context from which this keep annotation is generated.")
.requiredStringValue()
.generateConstants(this);
- new GroupMember("edge")
- .setDocTitle("Extracted normalized keep edge.")
- .requiredValue(KEEP_EDGE)
+ new Group("keep-annotation")
+ .addMember(
+ new GroupMember("edge")
+ .setDocTitle("Extracted normalized keep edge.")
+ .requiredValue(KEEP_EDGE))
+ .addMember(
+ new GroupMember("checkRemoved")
+ .setDocTitle("Extracted check removed.")
+ .defaultBooleanValue(false))
+ .addMember(
+ new GroupMember("checkOptimizedOut")
+ .setDocTitle("Extracted check optimized out.")
+ .defaultBooleanValue(false))
.generateConstants(this);
});
println("}");