[KeepAnno] Add test support for extracting out keep edge annotations
Bug: b/323815449
Change-Id: Ia9dbb8db12d637d6a9f832859834b4c5ca82797e
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
new file mode 100644
index 0000000..43a91bb
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotations.java
@@ -0,0 +1,31 @@
+// 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.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@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();
+}
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 1c6c71e..2d5d253 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,6 +13,7 @@
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.FieldAccess;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.ForApi;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Item;
@@ -35,6 +36,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.KeepFieldNamePattern;
@@ -87,9 +89,19 @@
public static int ASM_VERSION = ASM9;
public static List<KeepDeclaration> readKeepEdges(byte[] classFileBytes) {
+ return internalReadKeepEdges(classFileBytes, false);
+ }
+
+ public static List<KeepDeclaration> readExtractedKeepEdges(byte[] classFileBytes) {
+ return internalReadKeepEdges(classFileBytes, true);
+ }
+
+ private static List<KeepDeclaration> internalReadKeepEdges(
+ byte[] classFileBytes, boolean onlyExtracted) {
ClassReader reader = new ClassReader(classFileBytes);
List<KeepDeclaration> declarations = new ArrayList<>();
- reader.accept(new KeepEdgeClassVisitor(declarations::add), ClassReader.SKIP_CODE);
+ reader.accept(
+ new KeepEdgeClassVisitor(onlyExtracted, declarations::add), ClassReader.SKIP_CODE);
return declarations;
}
@@ -181,12 +193,14 @@
}
private static class KeepEdgeClassVisitor extends ClassVisitor {
+ private final boolean onlyExtracted;
private final Parent<KeepDeclaration> parent;
private String className;
private ClassParsingContext parsingContext;
- KeepEdgeClassVisitor(Parent<KeepDeclaration> parent) {
+ KeepEdgeClassVisitor(boolean onlyExtracted, Parent<KeepDeclaration> parent) {
super(ASM_VERSION);
+ this.onlyExtracted = onlyExtracted;
this.parent = parent;
}
@@ -217,6 +231,18 @@
if (visible) {
return null;
}
+ if (descriptor.equals(Extracted.DESCRIPTOR)) {
+ if (!onlyExtracted) {
+ // Annotation reading always ignores extracted edges.
+ // Note that we may reconsider this if R8 is to support a mixed-mode input.
+ return null;
+ }
+ return new ExtractedAnnotationVisitor(annotationParsingContext(descriptor), parent::accept);
+ }
+ if (onlyExtracted) {
+ // When reading extracted edges ignore all non-extracted annotations.
+ return null;
+ }
if (descriptor.equals(Edge.DESCRIPTOR)) {
return new KeepEdgeVisitor(
annotationParsingContext(descriptor), parent::accept, this::setContext);
@@ -495,6 +521,86 @@
}
}
+ 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<>();
+
+ public ExtractedAnnotationVisitor(
+ AnnotationParsingContext parsingContext, Parent<KeepDeclaration> parent) {
+ super(parsingContext);
+ this.parent = parent;
+ }
+
+ private void ensureVersion(ParsingContext parsingContext) {
+ if (version == null) {
+ parsingContext.error("Property 'version' must be defined before any other property");
+ }
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ if (name.equals(Extracted.version) && value instanceof String) {
+ version = (String) value;
+ return;
+ }
+ ensureVersion(getParsingContext().property(name));
+ if (name.equals(Extracted.context) && value instanceof String) {
+ context = (String) value;
+ return;
+ }
+ super.visit(name, value);
+ }
+
+ @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);
+ }
+ };
+ }
+ return super.visitArray(name);
+ }
+
+ @Override
+ public void visitEnd() {
+ if (version == null) {
+ throw new KeepEdgeException("Invalid extracted edge, expected a version property.");
+ }
+ if (context == null) {
+ throw new KeepEdgeException("Invalid extracted edge, 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());
+ }
+ super.visitEnd();
+ }
+ }
+
private static class KeepEdgeVisitor extends AnnotationVisitorBase {
private final ParsingContext parsingContext;
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 565067d..c822c51 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,26 +3,33 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.asm;
+import com.android.tools.r8.keepanno.ast.AccessVisibility;
import com.android.tools.r8.keepanno.ast.AnnotationConstants;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.AnnotationPattern;
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.Item;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.MemberAccess;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Target;
import com.android.tools.r8.keepanno.ast.KeepClassItemPattern;
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.KeepEdgeMetaInfo;
import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
+import com.android.tools.r8.keepanno.ast.KeepMemberAccessPattern;
import com.android.tools.r8.keepanno.ast.KeepMemberItemPattern;
import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
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.ModifierPattern;
+import com.android.tools.r8.keepanno.ast.OptionalPattern;
import com.android.tools.r8.keepanno.utils.Unimplemented;
import java.util.function.BiFunction;
+import java.util.function.Consumer;
import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
@@ -78,8 +85,26 @@
};
}
- public static void writeEdge(KeepEdge edge, ClassVisitor visitor) {
- writeEdgeInternal(edge, visitor::visitAnnotation);
+ // Helper to ensure that any call creating a new annotation visitor is statically scoped with its
+ // call to visit end.
+ private static void withNewVisitor(AnnotationVisitor visitor, Consumer<AnnotationVisitor> fn) {
+ fn.accept(visitor);
+ visitor.visitEnd();
+ }
+
+ public static void writeExtractedEdge(
+ KeepEdge edge, BiFunction<String, Boolean, AnnotationVisitorInterface> getVisitor) {
+ 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)));
+ });
}
public static void writeEdge(
@@ -89,47 +114,55 @@
public static void writeEdgeInternal(
KeepEdge edge, BiFunction<String, Boolean, AnnotationVisitor> getVisitor) {
- new KeepEdgeWriter().writeEdge(edge, getVisitor.apply(Edge.DESCRIPTOR, false));
+ withNewVisitor(
+ getVisitor.apply(Edge.DESCRIPTOR, false),
+ visitor -> new KeepEdgeWriter().writeEdge(edge, visitor));
}
private void writeEdge(KeepEdge edge, AnnotationVisitor visitor) {
+ writeMetaInfo(visitor, edge.getMetaInfo());
writePreconditions(visitor, edge.getPreconditions());
writeConsequences(visitor, edge.getConsequences());
- visitor.visitEnd();
}
+ private void writeMetaInfo(AnnotationVisitor visitor, KeepEdgeMetaInfo metaInfo) {}
+
private void writePreconditions(AnnotationVisitor visitor, KeepPreconditions preconditions) {
if (preconditions.isAlways()) {
return;
}
String ignoredArrayValueName = null;
- AnnotationVisitor arrayVisitor = visitor.visitArray(Edge.preconditions);
- preconditions.forEach(
- condition -> {
- AnnotationVisitor conditionVisitor =
- arrayVisitor.visitAnnotation(ignoredArrayValueName, Condition.DESCRIPTOR);
- if (condition.getItem().isBindingReference()) {
- throw new Unimplemented();
- }
- writeItem(conditionVisitor, condition.getItem().asItemPattern());
- });
- arrayVisitor.visitEnd();
+ withNewVisitor(
+ visitor.visitArray(Edge.preconditions),
+ arrayVisitor ->
+ preconditions.forEach(
+ condition ->
+ withNewVisitor(
+ arrayVisitor.visitAnnotation(ignoredArrayValueName, Condition.DESCRIPTOR),
+ conditionVisitor -> {
+ if (condition.getItem().isBindingReference()) {
+ throw new Unimplemented();
+ }
+ writeItem(conditionVisitor, condition.getItem().asItemPattern());
+ })));
}
private void writeConsequences(AnnotationVisitor visitor, KeepConsequences consequences) {
assert !consequences.isEmpty();
String ignoredArrayValueName = null;
- AnnotationVisitor arrayVisitor = visitor.visitArray(Edge.consequences);
- consequences.forEachTarget(
- target -> {
- AnnotationVisitor targetVisitor =
- arrayVisitor.visitAnnotation(ignoredArrayValueName, Target.DESCRIPTOR);
- if (target.getItem().isBindingReference()) {
- throw new Unimplemented();
- }
- writeItem(targetVisitor, target.getItem().asItemPattern());
- });
- arrayVisitor.visitEnd();
+ withNewVisitor(
+ visitor.visitArray(Edge.consequences),
+ arrayVisitor ->
+ consequences.forEachTarget(
+ target ->
+ withNewVisitor(
+ arrayVisitor.visitAnnotation(ignoredArrayValueName, Target.DESCRIPTOR),
+ targetVisitor -> {
+ if (target.getItem().isBindingReference()) {
+ throw new Unimplemented();
+ }
+ writeItem(targetVisitor, target.getItem().asItemPattern());
+ })));
}
private void writeItem(AnnotationVisitor itemVisitor, KeepItemPattern item) {
@@ -145,7 +178,7 @@
KeepQualifiedClassNamePattern namePattern = classItemPattern.getClassNamePattern();
if (namePattern.isExact()) {
Type typeConstant = Type.getType(namePattern.getExactDescriptor());
- itemVisitor.visit(AnnotationConstants.Item.classConstant, typeConstant);
+ itemVisitor.visit(AnnotationConstants.Item.className, typeConstant.getClassName());
} else {
throw new Unimplemented();
}
@@ -161,7 +194,6 @@
}
writeClassItem(memberItemPattern.getClassReference().asClassItemPattern(), itemVisitor);
writeMember(memberItemPattern.getMemberPattern(), itemVisitor);
- itemVisitor.visitEnd();
}
private void writeMember(KeepMemberPattern memberPattern, AnnotationVisitor targetVisitor) {
@@ -173,10 +205,19 @@
} else if (memberPattern.isField()) {
writeField(memberPattern.asField(), targetVisitor);
} else {
- throw new KeepEdgeException("Unexpected member pattern: " + memberPattern);
+ writeGeneralMember(memberPattern, targetVisitor);
}
}
+ private void writeGeneralMember(KeepMemberPattern member, AnnotationVisitor targetVisitor) {
+ assert member.isGeneralMember();
+ assert !member.isField();
+ assert !member.isMethod();
+ writeAnnotatedBy(
+ Item.memberAnnotatedByClassNamePattern, member.getAnnotatedByPattern(), targetVisitor);
+ writeAccessPattern(Item.memberAccess, member.getAccessPattern(), targetVisitor);
+ }
+
private void writeField(KeepFieldPattern field, AnnotationVisitor targetVisitor) {
String exactFieldName = field.getNamePattern().asExactString();
if (exactFieldName != null) {
@@ -215,4 +256,74 @@
throw new Unimplemented();
}
}
+
+ private void writeAnnotatedBy(
+ String propertyName,
+ OptionalPattern<KeepQualifiedClassNamePattern> annotatedByPattern,
+ AnnotationVisitor targetVisitor) {
+ if (annotatedByPattern.isAbsent()) {
+ return;
+ }
+ withNewVisitor(
+ targetVisitor.visitAnnotation(propertyName, AnnotationPattern.DESCRIPTOR),
+ visitor -> {
+ throw new Unimplemented("...");
+ });
+ }
+
+ private void writeModifierEnumValue(
+ ModifierPattern pattern, String value, String desc, AnnotationVisitor arrayVisitor) {
+ if (pattern.isAny()) {
+ return;
+ }
+ if (pattern.isOnlyPositive()) {
+ arrayVisitor.visitEnum(null, desc, value);
+ }
+ assert pattern.isOnlyNegative();
+ arrayVisitor.visitEnum(null, desc, MemberAccess.NEGATION_PREFIX + value);
+ }
+
+ private void writeAccessPattern(
+ String propertyName, KeepMemberAccessPattern accessPattern, AnnotationVisitor targetVisitor) {
+ if (accessPattern.isAny()) {
+ return;
+ }
+ withNewVisitor(
+ targetVisitor.visitArray(propertyName),
+ visitor -> {
+ if (!accessPattern.isAnyVisibility()) {
+ for (AccessVisibility visibility : accessPattern.getAllowedAccessVisibilities()) {
+ switch (visibility) {
+ case PUBLIC:
+ visitor.visitEnum(null, MemberAccess.DESCRIPTOR, MemberAccess.PUBLIC);
+ break;
+ case PROTECTED:
+ visitor.visitEnum(null, MemberAccess.DESCRIPTOR, MemberAccess.PROTECTED);
+ break;
+ case PACKAGE_PRIVATE:
+ visitor.visitEnum(null, MemberAccess.DESCRIPTOR, MemberAccess.PACKAGE_PRIVATE);
+ break;
+ case PRIVATE:
+ visitor.visitEnum(null, MemberAccess.DESCRIPTOR, MemberAccess.PRIVATE);
+ break;
+ }
+ }
+ }
+ writeModifierEnumValue(
+ accessPattern.getStaticPattern(),
+ MemberAccess.STATIC,
+ MemberAccess.DESCRIPTOR,
+ visitor);
+ writeModifierEnumValue(
+ accessPattern.getFinalPattern(),
+ MemberAccess.FINAL,
+ MemberAccess.DESCRIPTOR,
+ visitor);
+ writeModifierEnumValue(
+ accessPattern.getSyntheticPattern(),
+ MemberAccess.SYNTHETIC,
+ MemberAccess.DESCRIPTOR,
+ 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 18b2784..0789197 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,6 +15,14 @@
* annotations which overlap in name with the actual semantic AST types.
*/
public final class AnnotationConstants {
+ public static final class Extracted {
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/ExtractedKeepAnnotations;";
+ public static final String version = "version";
+ public static final String context = "context";
+ public static final String edges = "edges";
+ }
+
public static final class Edge {
public static final String DESCRIPTOR = "Lcom/android/tools/r8/keepanno/annotations/KeepEdge;";
public static final String description = "description";
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 bfcad37..c6f6248 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
@@ -6,6 +6,8 @@
/** Base class for the declarations represented in the keep annoations library. */
public abstract class KeepDeclaration {
+ public abstract KeepEdgeMetaInfo getMetaInfo();
+
public final boolean isKeepEdge() {
return asKeepEdge() != null;
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdgeMetaInfo.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdgeMetaInfo.java
index 4ec248b..6d4705a 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdgeMetaInfo.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdgeMetaInfo.java
@@ -7,8 +7,17 @@
public class KeepEdgeMetaInfo {
+ public enum KeepEdgeVersion {
+ UNKNOWN;
+
+ public String toVersionString() {
+ return name();
+ }
+ }
+
private static final KeepEdgeMetaInfo NONE =
- new KeepEdgeMetaInfo(KeepEdgeContext.none(), KeepEdgeDescription.empty());
+ new KeepEdgeMetaInfo(
+ KeepEdgeVersion.UNKNOWN, KeepEdgeContext.none(), KeepEdgeDescription.empty());
public static KeepEdgeMetaInfo none() {
return NONE;
@@ -18,10 +27,13 @@
return new Builder();
}
+ private final KeepEdgeVersion version;
private final KeepEdgeContext context;
private final KeepEdgeDescription description;
- private KeepEdgeMetaInfo(KeepEdgeContext context, KeepEdgeDescription description) {
+ private KeepEdgeMetaInfo(
+ KeepEdgeVersion version, KeepEdgeContext context, KeepEdgeDescription description) {
+ this.version = version;
this.context = context;
this.description = description;
}
@@ -42,6 +54,14 @@
return !KeepEdgeContext.none().equals(context);
}
+ public boolean hasVersion() {
+ return version != KeepEdgeVersion.UNKNOWN;
+ }
+
+ public KeepEdgeVersion getVersion() {
+ return version;
+ }
+
public static class Builder {
private KeepEdgeContext context = KeepEdgeContext.none();
private KeepEdgeDescription description = KeepEdgeDescription.empty();
@@ -73,7 +93,7 @@
&& description.equals(KeepEdgeDescription.empty())) {
return none();
}
- return new KeepEdgeMetaInfo(context, description);
+ return new KeepEdgeMetaInfo(KeepEdgeVersion.UNKNOWN, context, description);
}
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
index fd9cf63..57e8499 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
@@ -82,7 +82,11 @@
@Override
public String toString() {
- return "Member{" + "annotated-by=" + annotatedByPattern + ", access=" + accessPattern + '}';
+ return "Member{"
+ + annotatedByPattern.mapOrDefault(p -> "@" + p + ", ", "")
+ + "access="
+ + accessPattern
+ + '}';
}
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/OptionalPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/OptionalPattern.java
index 18061b6..6a3832e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/OptionalPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/OptionalPattern.java
@@ -31,8 +31,12 @@
throw new KeepEdgeException("Unexpected attempt to get absent value");
}
- public <S> OptionalPattern<S> map(Function<T, S> fn) {
- return absent();
+ public final <S> OptionalPattern<S> map(Function<T, S> fn) {
+ return mapOrDefault(fn.andThen(OptionalPattern::of), absent());
+ }
+
+ public <S> S mapOrDefault(Function<T, S> fn, S defaultValue) {
+ return defaultValue;
}
private static final class Absent extends OptionalPattern {
@@ -52,6 +56,11 @@
public int hashCode() {
return System.identityHashCode(this);
}
+
+ @Override
+ public String toString() {
+ return "<absent>";
+ }
}
private static final class Some<T> extends OptionalPattern<T> {
@@ -72,8 +81,8 @@
}
@Override
- public <S> OptionalPattern<S> map(Function<T, S> fn) {
- return of(fn.apply(value));
+ public <S> S mapOrDefault(Function<T, S> fn, S defaultValue) {
+ return fn.apply(value);
}
@Override
@@ -92,5 +101,10 @@
public int hashCode() {
return value.hashCode();
}
+
+ @Override
+ public String toString() {
+ return value.toString();
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index c811323..ca3f045 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -137,6 +137,8 @@
private boolean enableMissingLibraryApiModeling = false;
private boolean enableExperimentalKeepAnnotations =
System.getProperty("com.android.tools.r8.enableKeepAnnotations") != null;
+ private boolean enableExperimentalVersionedKeepEdgeAnnotations =
+ System.getProperty("com.android.tools.r8.enableVersionedKeepEdgeAnnotations") != null;
public boolean enableStartupLayoutOptimization = true;
private SemanticVersion fakeCompilerVersion = null;
private AndroidResourceProvider androidResourceProvider = null;
@@ -504,6 +506,12 @@
return self();
}
+ @Deprecated
+ public Builder setEnableExperimentalVersionedKeepEdgeAnnotations(boolean enable) {
+ this.enableExperimentalVersionedKeepEdgeAnnotations = enable;
+ return self();
+ }
+
@Override
protected InternalProgramOutputPathConsumer createProgramOutputConsumer(
Path path,
@@ -818,24 +826,31 @@
}
private void extractKeepAnnotationRules(ProguardConfigurationParser parser) {
- if (!enableExperimentalKeepAnnotations) {
+ if (!enableExperimentalKeepAnnotations && !enableExperimentalVersionedKeepEdgeAnnotations) {
return;
}
+ assert enableExperimentalKeepAnnotations != enableExperimentalVersionedKeepEdgeAnnotations;
try {
for (ProgramResourceProvider provider : getAppBuilder().getProgramResourceProviders()) {
for (ProgramResource resource : provider.getProgramResources()) {
if (resource.getKind() == Kind.CF) {
- List<KeepDeclaration> declarations =
- KeepEdgeReader.readKeepEdges(resource.getBytes());
- KeepRuleExtractor extractor =
- new KeepRuleExtractor(
- rule -> {
- ProguardConfigurationSourceStrings source =
- new ProguardConfigurationSourceStrings(
- Collections.singletonList(rule), null, resource.getOrigin());
- parser.parse(source);
- });
- declarations.forEach(extractor::extract);
+ List<KeepDeclaration> declarations;
+ if (!enableExperimentalKeepAnnotations) {
+ declarations = KeepEdgeReader.readExtractedKeepEdges(resource.getBytes());
+ } else {
+ declarations = KeepEdgeReader.readKeepEdges(resource.getBytes());
+ }
+ if (!declarations.isEmpty()) {
+ KeepRuleExtractor extractor =
+ new KeepRuleExtractor(
+ rule -> {
+ ProguardConfigurationSourceStrings source =
+ new ProguardConfigurationSourceStrings(
+ Collections.singletonList(rule), null, resource.getOrigin());
+ parser.parse(source);
+ });
+ declarations.forEach(extractor::extract);
+ }
}
}
}
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 5beaaf2..8254a95 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
@@ -15,13 +15,24 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestShrinkerBuilder;
import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.ClassAccessFlags;
+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.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import org.junit.rules.TemporaryFolder;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
public abstract class KeepAnnoTestBuilder {
@@ -109,6 +120,10 @@
return inspectOutputConfig(System.out::println);
}
+ public KeepAnnoTestBuilder enableEdgeExtraction() {
+ return this;
+ }
+
public KeepAnnoTestBuilder inspectOutputConfig(Consumer<String> configConsumer) {
// Default to ignore the consumer.
return this;
@@ -151,6 +166,7 @@
private final R8FullTestBuilder builder;
private List<Consumer<R8TestCompileResult>> compileResultConsumers = new ArrayList<>();
+ private boolean useEdgeExtraction = false;
public R8NativeBuilder(KeepAnnoParameters params, TemporaryFolder temp) {
super(params, temp);
@@ -161,6 +177,14 @@
}
@Override
+ public KeepAnnoTestBuilder enableEdgeExtraction() {
+ useEdgeExtraction = true;
+ builder.getBuilder().setEnableExperimentalKeepAnnotations(false);
+ builder.getBuilder().setEnableExperimentalVersionedKeepEdgeAnnotations(true);
+ return this;
+ }
+
+ @Override
public KeepAnnoTestBuilder applyIfR8(
ThrowableConsumer<TestShrinkerBuilder<?, ?, ?, ?, ?>> builderConsumer) {
builderConsumer.acceptWithRuntimeException(builder);
@@ -175,16 +199,19 @@
}
@Override
- public KeepAnnoTestBuilder addProgramClasses(List<Class<?>> programClasses) {
- builder.addProgramClasses(programClasses);
+ public KeepAnnoTestBuilder addProgramClasses(List<Class<?>> programClasses) throws IOException {
+ for (Class<?> programClass : programClasses) {
+ extractAndAdd(ToolHelper.getClassAsBytes(programClass));
+ }
return this;
}
@Override
public KeepAnnoTestBuilder addProgramClassFileData(List<byte[]> programClasses)
throws IOException {
-
- builder.addProgramClassFileData(programClasses);
+ for (byte[] programClass : programClasses) {
+ extractAndAdd(programClass);
+ }
return this;
}
@@ -195,6 +222,40 @@
return this;
}
+ private void extractAndAdd(byte[] classFileData) throws IOException {
+ builder.addProgramClassFileData(classFileData);
+ if (useEdgeExtraction) {
+ List<KeepDeclaration> declarations = KeepEdgeReader.readKeepEdges(classFileData);
+ if (!declarations.isEmpty()) {
+ String binaryName =
+ DescriptorUtils.getBinaryNameFromDescriptor(
+ TestBase.extractClassDescriptor(classFileData));
+ String synthesizingTarget = binaryName + "$$ExtractedKeepEdges";
+ ClassWriter classWriter = new ClassWriter(InternalOptions.ASM_VERSION);
+ classWriter.visit(
+ Opcodes.V1_8,
+ ClassAccessFlags.createPublicFinalSynthetic().getAsCfAccessFlags(),
+ synthesizingTarget,
+ 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)));
+ }
+ }
+ classWriter.visitEnd();
+ builder.addProgramClassFileData(classWriter.toByteArray());
+ }
+ }
+ }
+
@Override
public SingleTestRunResult<?> run(Class<?> mainClass) throws Exception {
R8TestCompileResult compileResult = builder.compile();
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java
index d15896f..534c5db 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java
@@ -9,10 +9,12 @@
import com.android.tools.r8.ProguardVersion;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
+import com.android.tools.r8.keepanno.asm.KeepEdgeWriter.AnnotationVisitorInterface;
import com.android.tools.r8.keepanno.ast.KeepDeclaration;
import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor;
import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractorOptions;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import java.io.IOException;
import java.nio.file.Files;
@@ -22,6 +24,7 @@
import java.util.List;
import java.util.stream.Stream;
import org.junit.rules.TemporaryFolder;
+import org.objectweb.asm.AnnotationVisitor;
public class KeepAnnoTestUtils {
@@ -78,4 +81,47 @@
}
return rules;
}
+
+ public static AnnotationVisitorInterface wrap(AnnotationVisitor visitor) {
+ return visitor == null ? null : new WrappedAnnotationVisitor(visitor);
+ }
+
+ private static class WrappedAnnotationVisitor implements AnnotationVisitorInterface {
+
+ private final AnnotationVisitor visitor;
+
+ private WrappedAnnotationVisitor(AnnotationVisitor visitor) {
+ this.visitor = visitor;
+ }
+
+ @Override
+ public int version() {
+ return InternalOptions.ASM_VERSION;
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ visitor.visit(name, value);
+ }
+
+ @Override
+ public void visitEnum(String name, String descriptor, String value) {
+ visitor.visitEnum(name, descriptor, value);
+ }
+
+ @Override
+ public AnnotationVisitorInterface visitAnnotation(String name, String descriptor) {
+ return wrap(visitor.visitAnnotation(name, descriptor));
+ }
+
+ @Override
+ public AnnotationVisitorInterface visitArray(String name) {
+ return wrap(visitor.visitArray(name));
+ }
+
+ @Override
+ public void visitEnd() {
+ visitor.visitEnd();
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepClassApiTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepClassApiTest.java
index 4abe376..378c929 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepClassApiTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepClassApiTest.java
@@ -59,6 +59,7 @@
assertTrue(parameters.isShrinker());
Box<Path> lib = new Box<>();
testForKeepAnno(parameters)
+ .enableEdgeExtraction()
.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/utils/KeepItemAnnotationGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
index aa675f2..977afd5 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
@@ -79,6 +79,8 @@
private static final ClassReference USED_BY_NATIVE = annoClass("UsedByNative");
private static final ClassReference CHECK_REMOVED = annoClass("CheckRemoved");
private static final ClassReference CHECK_OPTIMIZED_OUT = annoClass("CheckOptimizedOut");
+ private static final ClassReference EXTRACTED_KEEP_ANNOTATIONS =
+ annoClass("ExtractedKeepAnnotations");
private static final ClassReference KEEP_EDGE = annoClass("KeepEdge");
private static final ClassReference KEEP_BINDING = annoClass("KeepBinding");
private static final ClassReference KEEP_TARGET = annoClass("KeepTarget");
@@ -1583,6 +1585,7 @@
withIndent(
() -> {
// Root annotations.
+ generateExtractedKeepAnnotationsConstants();
generateKeepEdgeConstants();
generateKeepForApiConstants();
generateUsesReflectionConstants();
@@ -1615,6 +1618,28 @@
println("public static final String DESCRIPTOR = " + quote(desc) + ";");
}
+ private void generateExtractedKeepAnnotationsConstants() {
+ println("public static final class Extracted {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(EXTRACTED_KEEP_ANNOTATIONS);
+ new GroupMember("version")
+ .setDocTitle("Extraction version used to generate this keep annotation.")
+ .requiredStringValue()
+ .generateConstants(this);
+ new GroupMember("context")
+ .setDocTitle("Extraction context from which this keep annotation is generated.")
+ .requiredStringValue()
+ .generateConstants(this);
+ new GroupMember("edges")
+ .setDocTitle("Extracted normalized keep edges.")
+ .requiredArrayValue(KEEP_EDGE)
+ .generateConstants(this);
+ });
+ println("}");
+ println();
+ }
+
private void generateKeepEdgeConstants() {
println("public static final class Edge {");
withIndent(