[KeepAnno] Introduce a reusable parser structure
The added parser differs from "declarations" by having a mapping of
property names to the logical parser cases. This allows sharing the
parsing of the annotation `TypePattern.name` with the external
properties such as `fieldType` and `methodReturnType`.
Follow-up CLs will extend the parser for composition and then replace
all the "declarations" and annotation visitors.
Bug: b/248408342
Change-Id: Ia95e0fa662f0b73c677c64c454c240d10644dcba
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/AnnotationVisitorBase.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/AnnotationVisitorBase.java
new file mode 100644
index 0000000..e15d4e7
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/AnnotationVisitorBase.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2023, 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 com.android.tools.r8.keepanno.ast.KeepEdgeException;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Type;
+
+public abstract class AnnotationVisitorBase extends AnnotationVisitor {
+
+ AnnotationVisitorBase() {
+ super(KeepEdgeReader.ASM_VERSION);
+ }
+
+ public abstract String getAnnotationName();
+
+ private String errorMessagePrefix() {
+ return "@" + getAnnotationName() + ": ";
+ }
+
+ private String getTypeName(String descriptor) {
+ return Type.getType(descriptor).getClassName();
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ throw new KeepEdgeException(
+ "Unexpected value in " + errorMessagePrefix() + name + " = " + value);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ throw new KeepEdgeException(
+ "Unexpected annotation in "
+ + errorMessagePrefix()
+ + name
+ + " for annotation: "
+ + getTypeName(descriptor));
+ }
+
+ @Override
+ public void visitEnum(String name, String descriptor, String value) {
+ throw new KeepEdgeException(
+ "Unexpected enum in "
+ + errorMessagePrefix()
+ + name
+ + " for enum: "
+ + getTypeName(descriptor)
+ + " with value: "
+ + value);
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ throw new KeepEdgeException("Unexpected array in " + errorMessagePrefix() + name);
+ }
+}
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 cee27a8..2ba3cbf 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
@@ -416,40 +416,6 @@
void accept(T result);
}
- private abstract static class AnnotationVisitorBase extends AnnotationVisitor {
-
- AnnotationVisitorBase() {
- super(ASM_VERSION);
- }
-
- public abstract String getAnnotationName();
-
- private String errorMessagePrefix() {
- return " @" + getAnnotationName() + ": ";
- }
-
- @Override
- public void visit(String name, Object value) {
- throw new KeepEdgeException(
- "Unexpected value in" + errorMessagePrefix() + name + " = " + value);
- }
-
- @Override
- public AnnotationVisitor visitAnnotation(String name, String descriptor) {
- throw new KeepEdgeException("Unexpected annotation in" + errorMessagePrefix() + name);
- }
-
- @Override
- public void visitEnum(String name, String descriptor, String value) {
- throw new KeepEdgeException("Unexpected enum in" + errorMessagePrefix() + name);
- }
-
- @Override
- public AnnotationVisitor visitArray(String name) {
- throw new KeepEdgeException("Unexpected array in" + errorMessagePrefix() + name);
- }
- }
-
private static class UserBindingsHelper {
private final KeepBindings.Builder builder = KeepBindings.builder();
private final Map<String, KeepBindingSymbol> userNames = new HashMap<>();
@@ -1491,9 +1457,16 @@
extends SingleDeclaration<KeepMethodReturnTypePattern> {
private final Supplier<String> annotationName;
+ private final TypeParser typeParser;
private MethodReturnTypeDeclaration(Supplier<String> annotationName) {
this.annotationName = annotationName;
+ typeParser =
+ new TypeParser()
+ .setKind("return type")
+ .enableTypePattern(Item.methodReturnTypePattern)
+ .enableTypeName(Item.methodReturnType)
+ .enableTypeConstant(Item.methodReturnTypeConstant);
}
@Override
@@ -1506,26 +1479,27 @@
return KeepMethodReturnTypePattern.any();
}
- @Override
- KeepMethodReturnTypePattern parse(String name, Object value) {
- if (name.equals(Item.methodReturnType) && value instanceof String) {
- return KeepEdgeReaderUtils.methodReturnTypeFromTypeName((String) value);
+ KeepMethodReturnTypePattern fromType(KeepTypePattern typePattern) {
+ if (typePattern == null) {
+ return null;
}
- if (name.equals(Item.methodReturnTypeConstant) && value instanceof Type) {
- Type type = (Type) value;
- return KeepEdgeReaderUtils.methodReturnTypeFromTypeDescriptor(type.getDescriptor());
+ // Special-case method return types to allow void.
+ String descriptor = typePattern.getDescriptor();
+ if (descriptor.equals("V") || descriptor.equals("Lvoid;")) {
+ return KeepMethodReturnTypePattern.voidType();
}
- return null;
+ return KeepMethodReturnTypePattern.fromType(typePattern);
}
@Override
- AnnotationVisitor parseAnnotation(
+ public KeepMethodReturnTypePattern parse(String name, Object value) {
+ return fromType(typeParser.tryParse(name, value));
+ }
+
+ @Override
+ public AnnotationVisitor parseAnnotation(
String name, String descriptor, Consumer<KeepMethodReturnTypePattern> setValue) {
- if (name.equals(Item.methodReturnTypePattern) && descriptor.equals(TypePattern.DESCRIPTOR)) {
- return new TypePatternVisitor(
- annotationName, t -> setValue.accept(KeepMethodReturnTypePattern.fromType(t)));
- }
- return super.parseAnnotation(name, descriptor, setValue);
+ return typeParser.tryParseAnnotation(name, descriptor, t -> setValue.accept(fromType(t)));
}
}
@@ -1666,9 +1640,16 @@
private static class FieldTypeDeclaration extends SingleDeclaration<KeepFieldTypePattern> {
private final Supplier<String> annotationName;
+ private final TypeParser typeParser;
private FieldTypeDeclaration(Supplier<String> annotationName) {
this.annotationName = annotationName;
+ this.typeParser =
+ new TypeParser()
+ .setKind("field type")
+ .enableTypePattern(Item.fieldTypePattern)
+ .enableTypeName(Item.fieldType)
+ .enableTypeConstant(Item.fieldTypeConstant);
}
@Override
@@ -1682,26 +1663,19 @@
}
@Override
- KeepFieldTypePattern parse(String name, Object value) {
- if (name.equals(Item.fieldType) && value instanceof String) {
- return KeepFieldTypePattern.fromType(
- KeepEdgeReaderUtils.typePatternFromString((String) value));
- }
- if (name.equals(Item.fieldTypeConstant) && value instanceof Type) {
- String descriptor = ((Type) value).getDescriptor();
- return KeepFieldTypePattern.fromType(KeepTypePattern.fromDescriptor(descriptor));
+ public KeepFieldTypePattern parse(String name, Object value) {
+ KeepTypePattern typePattern = typeParser.tryParse(name, value);
+ if (typePattern != null) {
+ return KeepFieldTypePattern.fromType(typePattern);
}
return null;
}
@Override
- AnnotationVisitor parseAnnotation(
+ public AnnotationVisitor parseAnnotation(
String name, String descriptor, Consumer<KeepFieldTypePattern> setValue) {
- if (name.equals(Item.fieldTypePattern) && descriptor.equals(TypePattern.DESCRIPTOR)) {
- return new TypePatternVisitor(
- annotationName, t -> setValue.accept(KeepFieldTypePattern.fromType(t)));
- }
- return super.parseAnnotation(name, descriptor, setValue);
+ return typeParser.tryParseAnnotation(
+ name, descriptor, t -> setValue.accept(KeepFieldTypePattern.fromType(t)));
}
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ParserVisitor.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ParserVisitor.java
new file mode 100644
index 0000000..e95494b
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ParserVisitor.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2023, 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 java.util.function.Consumer;
+import org.objectweb.asm.AnnotationVisitor;
+
+/** Convert a parser into an annotation visitor. */
+public class ParserVisitor<T, P, S> extends AnnotationVisitorBase {
+
+ private final String annotationDescriptor;
+ private final SingleValuePropertyParser<T, P, S> declaration;
+ private final Consumer<T> callback;
+
+ public ParserVisitor(
+ String annotationDescriptor,
+ SingleValuePropertyParser<T, P, S> declaration,
+ Consumer<T> callback) {
+ this.annotationDescriptor = annotationDescriptor;
+ this.declaration = declaration;
+ this.callback = callback;
+ }
+
+ @Override
+ public String getAnnotationName() {
+ int start = annotationDescriptor.lastIndexOf('/') + 1;
+ int end = annotationDescriptor.length() - 1;
+ return annotationDescriptor.substring(start, end);
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ if (declaration.tryParse(name, value, unused -> {})) {
+ return;
+ }
+ super.visit(name, value);
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ AnnotationVisitor visitor = declaration.tryParseArray(name, unused -> {});
+ if (visitor != null) {
+ return visitor;
+ }
+ return super.visitArray(name);
+ }
+
+ @Override
+ public void visitEnum(String name, String descriptor, String value) {
+ super.visitEnum(name, descriptor, value);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ AnnotationVisitor visitor = declaration.tryParseAnnotation(name, descriptor, unused -> {});
+ if (visitor != null) {
+ return visitor;
+ }
+ return super.visitAnnotation(name, descriptor);
+ }
+
+ @Override
+ public void visitEnd() {
+ if (declaration.isDeclared()) {
+ callback.accept(declaration.getSingleValue());
+ }
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParser.java
new file mode 100644
index 0000000..70160a3
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParser.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2023, 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 java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+import org.objectweb.asm.AnnotationVisitor;
+
+public abstract class PropertyParser<T, P, S> {
+
+ private String kind;
+ private final Map<String, P> mapping = new HashMap<>();
+
+ abstract S self();
+
+ abstract boolean tryProperty(P property, String name, Object value, Consumer<T> setValue);
+
+ abstract AnnotationVisitor tryPropertyArray(P property, String name, Consumer<T> setValue);
+
+ abstract AnnotationVisitor tryPropertyAnnotation(
+ P property, String name, String descriptor, Consumer<T> setValue);
+
+ String kind() {
+ return kind != null ? kind : "";
+ }
+
+ public S setKind(String kind) {
+ this.kind = kind;
+ return self();
+ }
+
+ /** Add property parsing for the given property-name. */
+ public S setProperty(P property, String name) {
+ P old = mapping.put(name, property);
+ if (old != null) {
+ throw new IllegalArgumentException("Unexpected attempt to redefine property " + name);
+ }
+ return self();
+ }
+
+ /** Parse a property. Returns true if the property-name triggered parsing. */
+ public final boolean tryParse(String name, Object value, Consumer<T> setValue) {
+ P prop = mapping.get(name);
+ if (prop != null) {
+ return tryProperty(prop, name, value, setValue);
+ }
+ return false;
+ }
+
+ /** Parse a property. Returns non-null if the property-name triggered parsing. */
+ public final AnnotationVisitor tryParseArray(String name, Consumer<T> setValue) {
+ P prop = mapping.get(name);
+ if (prop != null) {
+ return tryPropertyArray(prop, name, setValue);
+ }
+ return null;
+ }
+
+ /** Parse a property. Returns non-null if the property-name triggered parsing. */
+ public final AnnotationVisitor tryParseAnnotation(
+ String name, String descriptor, Consumer<T> setValue) {
+ P prop = mapping.get(name);
+ if (prop != null) {
+ return tryPropertyAnnotation(prop, name, descriptor, setValue);
+ }
+ return null;
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/SingleValuePropertyParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/SingleValuePropertyParser.java
new file mode 100644
index 0000000..dcb8088
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/SingleValuePropertyParser.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2023, 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 com.android.tools.r8.keepanno.ast.KeepEdgeException;
+import java.util.function.Consumer;
+import org.objectweb.asm.AnnotationVisitor;
+
+/** Special case of a property parser allowing only a single value callback. */
+public abstract class SingleValuePropertyParser<T, P, S> extends PropertyParser<T, P, S> {
+
+ private String resultPropertyName = null;
+ private T resultValue = null;
+
+ abstract boolean trySingleProperty(P property, String name, Object value, Consumer<T> setValue);
+
+ abstract AnnotationVisitor trySinglePropertyArray(P property, String name, Consumer<T> setValue);
+
+ abstract AnnotationVisitor trySinglePropertyAnnotation(
+ P property, String name, String descriptor, Consumer<T> setValue);
+
+ private Consumer<T> wrap(String propertyName, Consumer<T> setValue) {
+ return value -> {
+ assert value != null;
+ if (resultPropertyName != null) {
+ assert resultValue != null;
+ error(propertyName);
+ } else {
+ resultPropertyName = propertyName;
+ resultValue = value;
+ setValue.accept(value);
+ }
+ };
+ }
+
+ private void error(String name) {
+ throw new KeepEdgeException(
+ "Multiple properties defining "
+ + kind()
+ + ": '"
+ + resultPropertyName
+ + "' and '"
+ + name
+ + "'");
+ }
+
+ public final boolean isDeclared() {
+ assert (resultPropertyName != null) == (resultValue != null);
+ return resultPropertyName != null;
+ }
+
+ public T getSingleValue() {
+ assert (resultPropertyName != null) == (resultValue != null);
+ return resultValue;
+ }
+
+ /** Helper for parsing directly. Returns non-null if the property-name triggered parsing. */
+ public final T tryParse(String name, Object value) {
+ boolean triggered = tryParse(name, value, unused -> {});
+ assert triggered == (resultValue != null);
+ return resultValue;
+ }
+
+ @Override
+ final boolean tryProperty(P property, String name, Object value, Consumer<T> setValue) {
+ return trySingleProperty(property, name, value, wrap(name, setValue));
+ }
+
+ @Override
+ final AnnotationVisitor tryPropertyArray(P property, String name, Consumer<T> setValue) {
+ return trySinglePropertyArray(property, name, wrap(name, setValue));
+ }
+
+ @Override
+ final AnnotationVisitor tryPropertyAnnotation(
+ P property, String name, String descriptor, Consumer<T> setValue) {
+ return trySinglePropertyAnnotation(property, name, descriptor, wrap(name, setValue));
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
new file mode 100644
index 0000000..dd9b475
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2023, 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 com.android.tools.r8.keepanno.asm.TypeParser.Properties;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.TypePattern;
+import com.android.tools.r8.keepanno.ast.KeepTypePattern;
+import com.android.tools.r8.keepanno.utils.Unimplemented;
+import java.util.function.Consumer;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Type;
+
+public class TypeParser extends SingleValuePropertyParser<KeepTypePattern, Properties, TypeParser> {
+
+ public enum Properties {
+ SELF_PATTERN,
+ TYPE_NAME,
+ TYPE_CONSTANT,
+ CLASS_NAME_PATTERN
+ }
+
+ public TypeParser enableTypePattern(String propertyName) {
+ return setProperty(Properties.SELF_PATTERN, propertyName);
+ }
+
+ public TypeParser enableTypeName(String propertyName) {
+ return setProperty(Properties.TYPE_NAME, propertyName);
+ }
+
+ public TypeParser enableTypeConstant(String propertyName) {
+ return setProperty(Properties.TYPE_CONSTANT, propertyName);
+ }
+
+ public TypeParser enableTypeClassNamePattern(String propertyName) {
+ return setProperty(Properties.CLASS_NAME_PATTERN, propertyName);
+ }
+
+ @Override
+ TypeParser self() {
+ return this;
+ }
+
+ @Override
+ public boolean trySingleProperty(
+ Properties property, String name, Object value, Consumer<KeepTypePattern> setValue) {
+ switch (property) {
+ case TYPE_NAME:
+ setValue.accept(KeepEdgeReaderUtils.typePatternFromString((String) value));
+ return true;
+ case TYPE_CONSTANT:
+ setValue.accept(KeepTypePattern.fromDescriptor(((Type) value).getDescriptor()));
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public AnnotationVisitor trySinglePropertyArray(
+ Properties property, String name, Consumer<KeepTypePattern> setValue) {
+ return null;
+ }
+
+ @Override
+ public AnnotationVisitor trySinglePropertyAnnotation(
+ Properties property, String name, String descriptor, Consumer<KeepTypePattern> setValue) {
+ switch (property) {
+ case SELF_PATTERN:
+ return new ParserVisitor<>(
+ descriptor,
+ new TypeParser()
+ .setKind(kind())
+ .enableTypeName(TypePattern.name)
+ .enableTypeConstant(TypePattern.constant)
+ .enableTypeClassNamePattern(TypePattern.classNamePattern),
+ setValue);
+ case CLASS_NAME_PATTERN:
+ throw new Unimplemented("Non-exact class patterns are not unimplemented yet");
+ default:
+ return null;
+ }
+ }
+}