blob: b76ac2e1942df8810543d2e698963d2108b14abb [file] [log] [blame]
// 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.asm;
import com.android.tools.r8.keepanno.asm.ClassNameParser.ClassNameProperty;
import com.android.tools.r8.keepanno.asm.ConstraintPropertiesParser.ConstraintsProperty;
import com.android.tools.r8.keepanno.asm.InstanceOfParser.InstanceOfProperties;
import com.android.tools.r8.keepanno.asm.StringPatternParser.StringProperty;
import com.android.tools.r8.keepanno.asm.TypeParser.TypeProperty;
import com.android.tools.r8.keepanno.ast.AccessVisibility;
import com.android.tools.r8.keepanno.ast.AnnotationConstants;
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.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;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Kind;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.MemberAccess;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.MethodAccess;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Target;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsedByReflection;
import com.android.tools.r8.keepanno.ast.KeepAnnotationPattern;
import com.android.tools.r8.keepanno.ast.KeepBindingReference;
import com.android.tools.r8.keepanno.ast.KeepBindings;
import com.android.tools.r8.keepanno.ast.KeepBindings.KeepBindingSymbol;
import com.android.tools.r8.keepanno.ast.KeepCheck;
import com.android.tools.r8.keepanno.ast.KeepCheck.KeepCheckKind;
import com.android.tools.r8.keepanno.ast.KeepClassItemPattern;
import com.android.tools.r8.keepanno.ast.KeepClassItemReference;
import com.android.tools.r8.keepanno.ast.KeepCondition;
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.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;
import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
import com.android.tools.r8.keepanno.ast.KeepFieldTypePattern;
import com.android.tools.r8.keepanno.ast.KeepInstanceOfPattern;
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
import com.android.tools.r8.keepanno.ast.KeepItemReference;
import com.android.tools.r8.keepanno.ast.KeepMemberAccessPattern;
import com.android.tools.r8.keepanno.ast.KeepMemberAccessPattern.BuilderBase;
import com.android.tools.r8.keepanno.ast.KeepMemberItemPattern;
import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodAccessPattern;
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.KeepPreconditions;
import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
import com.android.tools.r8.keepanno.ast.KeepStringPattern;
import com.android.tools.r8.keepanno.ast.KeepTarget;
import com.android.tools.r8.keepanno.ast.KeepTypePattern;
import com.android.tools.r8.keepanno.ast.OptionalPattern;
import com.android.tools.r8.keepanno.ast.ParsingContext;
import com.android.tools.r8.keepanno.ast.ParsingContext.AnnotationParsingContext;
import com.android.tools.r8.keepanno.ast.ParsingContext.ClassParsingContext;
import com.android.tools.r8.keepanno.ast.ParsingContext.FieldParsingContext;
import com.android.tools.r8.keepanno.ast.ParsingContext.MethodParsingContext;
import com.android.tools.r8.keepanno.ast.ParsingContext.PropertyParsingContext;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Supplier;
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;
public class KeepEdgeReader implements Opcodes {
public static int ASM_VERSION = ASM9;
public static boolean isClassKeepAnnotation(String descriptor, boolean visible) {
return !visible && (isExtractedAnnotation(descriptor) || isEmbeddedAnnotation(descriptor));
}
public static boolean isFieldKeepAnnotation(String descriptor, boolean visible) {
return !visible && isEmbeddedAnnotation(descriptor);
}
public static boolean isMethodKeepAnnotation(String descriptor, boolean visible) {
return !visible && isEmbeddedAnnotation(descriptor);
}
private static boolean isExtractedAnnotation(String descriptor) {
return ExtractedAnnotations.DESCRIPTOR.equals(descriptor);
}
private static boolean isEmbeddedAnnotation(String descriptor) {
switch (descriptor) {
case AnnotationConstants.Edge.DESCRIPTOR:
case AnnotationConstants.UsesReflection.DESCRIPTOR:
case AnnotationConstants.ForApi.DESCRIPTOR:
case AnnotationConstants.UsedByReflection.DESCRIPTOR:
case AnnotationConstants.UsedByNative.DESCRIPTOR:
case AnnotationConstants.CheckRemoved.DESCRIPTOR:
case AnnotationConstants.CheckOptimizedOut.DESCRIPTOR:
return true;
default:
return false;
}
}
public static List<KeepDeclaration> readKeepEdges(byte[] classFileBytes) {
return internalReadKeepEdges(classFileBytes, true, false);
}
public static List<KeepDeclaration> readExtractedKeepEdges(byte[] classFileBytes) {
return internalReadKeepEdges(classFileBytes, false, true);
}
private static List<KeepDeclaration> internalReadKeepEdges(
byte[] classFileBytes, boolean readEmbedded, boolean readExtracted) {
ClassReader reader = new ClassReader(classFileBytes);
List<KeepDeclaration> declarations = new ArrayList<>();
reader.accept(
new KeepEdgeClassVisitor(readEmbedded, readExtracted, declarations::add),
ClassReader.SKIP_CODE);
return declarations;
}
public static AnnotationVisitor createClassKeepAnnotationVisitor(
String descriptor,
boolean visible,
boolean readEmbedded,
boolean readExtracted,
String className,
AnnotationParsingContext parsingContext,
Consumer<KeepDeclaration> callback) {
return KeepEdgeClassVisitor.createAnnotationVisitor(
descriptor,
visible,
readEmbedded,
readExtracted,
callback,
parsingContext,
className,
builder -> {
builder.setContextFromClassDescriptor(
KeepEdgeReaderUtils.getDescriptorFromClassTypeName(className));
});
}
public static AnnotationVisitor createFieldKeepAnnotationVisitor(
String descriptor,
boolean visible,
boolean readEmbedded,
boolean readExtracted,
String className,
String fieldName,
String fieldTypeDescriptor,
AnnotationParsingContext parsingContext,
Consumer<KeepDeclaration> callback) {
return KeepEdgeFieldVisitor.createAnnotationVisitor(
descriptor,
visible,
readEmbedded,
readExtracted,
callback::accept,
parsingContext,
className,
fieldName,
fieldTypeDescriptor,
builder -> {
builder.setContextFromFieldDescriptor(
KeepEdgeReaderUtils.getDescriptorFromClassTypeName(className),
fieldName,
fieldTypeDescriptor);
});
}
public static AnnotationVisitor createMethodKeepAnnotationVisitor(
String descriptor,
boolean visible,
boolean readEmbedded,
boolean readExtracted,
String className,
String methodName,
String methodDescriptor,
AnnotationParsingContext parsingContext,
Consumer<KeepDeclaration> callback) {
return KeepEdgeMethodVisitor.createAnnotationVisitor(
descriptor,
visible,
readEmbedded,
readExtracted,
callback::accept,
parsingContext,
className,
methodName,
methodDescriptor,
(KeepEdgeMetaInfo.Builder builder) -> {
builder.setContextFromMethodDescriptor(
KeepEdgeReaderUtils.getDescriptorFromClassTypeName(className),
methodName,
methodDescriptor);
});
}
private static KeepClassItemReference classReferenceFromName(String className) {
return KeepClassItemReference.fromClassNamePattern(
KeepQualifiedClassNamePattern.exact(className));
}
private static KeepConstraints getClassConstraintsOrDefault(KeepConstraints constraints) {
return constraints != null ? constraints : KeepConstraints.defaultConstraints();
}
/** Internal copy of the user-facing KeepItemKind */
public enum ItemKind {
ONLY_CLASS,
ONLY_MEMBERS,
ONLY_METHODS,
ONLY_FIELDS,
CLASS_AND_MEMBERS,
CLASS_AND_METHODS,
CLASS_AND_FIELDS;
private static ItemKind fromString(String name) {
switch (name) {
case Kind.ONLY_CLASS:
return ONLY_CLASS;
case Kind.ONLY_MEMBERS:
return ONLY_MEMBERS;
case Kind.ONLY_METHODS:
return ONLY_METHODS;
case Kind.ONLY_FIELDS:
return ONLY_FIELDS;
case Kind.CLASS_AND_MEMBERS:
return CLASS_AND_MEMBERS;
case Kind.CLASS_AND_METHODS:
return CLASS_AND_METHODS;
case Kind.CLASS_AND_FIELDS:
return CLASS_AND_FIELDS;
default:
return null;
}
}
private boolean isOnlyClass() {
return equals(ONLY_CLASS);
}
private boolean requiresMembers() {
// If requiring members it is fine to have the more specific methods or fields.
return includesMembers();
}
private boolean requiresMethods() {
return equals(ONLY_METHODS) || equals(CLASS_AND_METHODS);
}
private boolean requiresFields() {
return equals(ONLY_FIELDS) || equals(CLASS_AND_FIELDS);
}
private boolean includesClassAndMembers() {
return includesClass() && includesMembers();
}
private boolean includesClass() {
return equals(ONLY_CLASS)
|| equals(CLASS_AND_MEMBERS)
|| equals(CLASS_AND_METHODS)
|| equals(CLASS_AND_FIELDS);
}
private boolean includesMembers() {
return !equals(ONLY_CLASS);
}
private boolean includesMethod() {
return equals(ONLY_MEMBERS)
|| equals(ONLY_METHODS)
|| equals(CLASS_AND_MEMBERS)
|| equals(CLASS_AND_METHODS);
}
private boolean includesField() {
return equals(ONLY_MEMBERS)
|| equals(ONLY_FIELDS)
|| equals(CLASS_AND_MEMBERS)
|| equals(CLASS_AND_FIELDS);
}
}
private static class KeepEdgeClassVisitor extends ClassVisitor {
private final boolean readEmbedded;
private final boolean readExtracted;
private final Parent<KeepDeclaration> parent;
private String className;
private ClassParsingContext parsingContext;
KeepEdgeClassVisitor(
boolean readEmbedded, boolean readExtracted, Parent<KeepDeclaration> parent) {
super(ASM_VERSION);
this.readEmbedded = readEmbedded;
this.readExtracted = readExtracted;
this.parent = parent;
}
private static String binaryNameToTypeName(String binaryName) {
return binaryName.replace('/', '.');
}
@Override
public void visit(
int version,
int access,
String name,
String signature,
String superName,
String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
className = binaryNameToTypeName(name);
parsingContext = ClassParsingContext.fromName(className);
}
private AnnotationParsingContext annotationParsingContext(String descriptor) {
return parsingContext.annotation(descriptor);
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
return createAnnotationVisitor(
descriptor,
visible,
readEmbedded,
readExtracted,
parent::accept,
annotationParsingContext(descriptor),
className,
this::setContext);
}
private static AnnotationVisitorBase createAnnotationVisitor(
String descriptor,
boolean visible,
boolean readEmbedded,
boolean readExtracted,
Consumer<KeepDeclaration> parent,
AnnotationParsingContext parsingContext,
String className,
Consumer<KeepEdgeMetaInfo.Builder> setContext) {
// Skip any visible annotations as @KeepEdge is not runtime visible.
if (visible) {
return null;
}
if (readExtracted && isExtractedAnnotation(descriptor)) {
return new ExtractedAnnotationsVisitor(parsingContext, parent::accept);
}
if (!readEmbedded || !isEmbeddedAnnotation(descriptor)) {
return null;
}
if (descriptor.equals(Edge.DESCRIPTOR)) {
return new KeepEdgeVisitor(parsingContext, parent::accept, setContext);
}
if (descriptor.equals(AnnotationConstants.UsesReflection.DESCRIPTOR)) {
KeepClassItemPattern classItem =
KeepClassItemPattern.builder()
.setClassNamePattern(KeepQualifiedClassNamePattern.exact(className))
.build();
return new UsesReflectionVisitor(parsingContext, parent::accept, setContext, classItem);
}
if (descriptor.equals(ForApi.DESCRIPTOR)) {
return new ForApiClassVisitor(parsingContext, parent::accept, setContext, className);
}
if (descriptor.equals(UsedByReflection.DESCRIPTOR)
|| descriptor.equals(AnnotationConstants.UsedByNative.DESCRIPTOR)) {
return new UsedByReflectionClassVisitor(
parsingContext, parent::accept, setContext, className);
}
if (descriptor.equals(AnnotationConstants.CheckRemoved.DESCRIPTOR)) {
return new CheckRemovedClassVisitor(
parsingContext, parent::accept, setContext, className, KeepCheckKind.REMOVED);
}
if (descriptor.equals(AnnotationConstants.CheckOptimizedOut.DESCRIPTOR)) {
return new CheckRemovedClassVisitor(
parsingContext, parent::accept, setContext, className, KeepCheckKind.OPTIMIZED_OUT);
}
return null;
}
private void setContext(KeepEdgeMetaInfo.Builder builder) {
builder.setContextFromClassDescriptor(
KeepEdgeReaderUtils.getDescriptorFromJavaType(className));
}
@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
if (readEmbedded) {
return new KeepEdgeMethodVisitor(
parsingContext, parent::accept, className, name, descriptor);
}
return null;
}
@Override
public FieldVisitor visitField(
int access, String name, String descriptor, String signature, Object value) {
if (readEmbedded) {
return new KeepEdgeFieldVisitor(
parsingContext, parent::accept, className, name, descriptor);
}
return null;
}
}
private static class KeepEdgeMethodVisitor extends MethodVisitor {
private final Parent<KeepDeclaration> parent;
private final String className;
private final String methodName;
private final String methodDescriptor;
private final MethodParsingContext parsingContext;
KeepEdgeMethodVisitor(
ClassParsingContext classParsingContext,
Parent<KeepDeclaration> parent,
String className,
String methodName,
String methodDescriptor) {
super(ASM_VERSION);
this.parent = parent;
this.className = className;
this.methodName = methodName;
this.methodDescriptor = methodDescriptor;
this.parsingContext =
new MethodParsingContext(classParsingContext, methodName, methodDescriptor);
}
private static KeepMemberItemPattern createMethodItemContext(
String className, String methodName, String methodDescriptor) {
String returnTypeDescriptor = Type.getReturnType(methodDescriptor).getDescriptor();
Type[] argumentTypes = Type.getArgumentTypes(methodDescriptor);
KeepMethodParametersPattern.Builder builder = KeepMethodParametersPattern.builder();
for (Type type : argumentTypes) {
builder.addParameterTypePattern(KeepTypePattern.fromDescriptor(type.getDescriptor()));
}
KeepMethodReturnTypePattern returnTypePattern =
"V".equals(returnTypeDescriptor)
? KeepMethodReturnTypePattern.voidType()
: KeepMethodReturnTypePattern.fromType(
KeepTypePattern.fromDescriptor(returnTypeDescriptor));
return KeepMemberItemPattern.builder()
.setClassReference(classReferenceFromName(className))
.setMemberPattern(
KeepMethodPattern.builder()
.setNamePattern(KeepMethodNamePattern.exact(methodName))
.setReturnTypePattern(returnTypePattern)
.setParametersPattern(builder.build())
.build())
.build();
}
private AnnotationParsingContext annotationParsingContext(String descriptor) {
return parsingContext.annotation(descriptor);
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
return createAnnotationVisitor(
descriptor,
visible,
true,
false,
parent::accept,
annotationParsingContext(descriptor),
className,
methodName,
methodDescriptor,
this::setContext);
}
public static AnnotationVisitor createAnnotationVisitor(
String descriptor,
boolean visible,
boolean readEmbedded,
boolean readExtracted,
Consumer<KeepDeclaration> parent,
AnnotationParsingContext parsingContext,
String className,
String methodName,
String methodDescriptor,
Consumer<KeepEdgeMetaInfo.Builder> setContext) {
// Skip any visible annotations as @KeepEdge is not runtime visible.
if (visible) {
return null;
}
if (!readEmbedded) {
// Only the embedded annotations can be on methods.
return null;
}
if (descriptor.equals(Edge.DESCRIPTOR)) {
return new KeepEdgeVisitor(parsingContext, parent::accept, setContext);
}
if (descriptor.equals(AnnotationConstants.UsesReflection.DESCRIPTOR)) {
return new UsesReflectionVisitor(
parsingContext,
parent::accept,
setContext,
createMethodItemContext(className, methodName, methodDescriptor));
}
if (descriptor.equals(AnnotationConstants.ForApi.DESCRIPTOR)) {
return new ForApiMemberVisitor(
parsingContext,
parent::accept,
setContext,
createMethodItemContext(className, methodName, methodDescriptor));
}
if (descriptor.equals(AnnotationConstants.UsedByReflection.DESCRIPTOR)
|| descriptor.equals(AnnotationConstants.UsedByNative.DESCRIPTOR)) {
return new UsedByReflectionMemberVisitor(
parsingContext,
parent::accept,
setContext,
createMethodItemContext(className, methodName, methodDescriptor));
}
if (descriptor.equals(AnnotationConstants.CheckRemoved.DESCRIPTOR)) {
return new CheckRemovedMemberVisitor(
parsingContext,
parent::accept,
setContext,
createMethodItemContext(className, methodName, methodDescriptor),
KeepCheckKind.REMOVED);
}
if (descriptor.equals(AnnotationConstants.CheckOptimizedOut.DESCRIPTOR)) {
return new CheckRemovedMemberVisitor(
parsingContext,
parent::accept,
setContext,
createMethodItemContext(className, methodName, methodDescriptor),
KeepCheckKind.OPTIMIZED_OUT);
}
return null;
}
private void setContext(KeepEdgeMetaInfo.Builder builder) {
builder.setContextFromMethodDescriptor(
KeepEdgeReaderUtils.getDescriptorFromJavaType(className), methodName, methodDescriptor);
}
}
private static class KeepEdgeFieldVisitor extends FieldVisitor {
private final Parent<KeepEdge> parent;
private final String className;
private final String fieldName;
private final String fieldTypeDescriptor;
private final FieldParsingContext parsingContext;
KeepEdgeFieldVisitor(
ClassParsingContext classParsingContext,
Parent<KeepEdge> parent,
String className,
String fieldName,
String fieldTypeDescriptor) {
super(ASM_VERSION);
this.parent = parent;
this.className = className;
this.fieldName = fieldName;
this.fieldTypeDescriptor = fieldTypeDescriptor;
this.parsingContext =
new FieldParsingContext(classParsingContext, fieldName, fieldTypeDescriptor);
}
private AnnotationParsingContext annotationParsingContext(String descriptor) {
return parsingContext.annotation(descriptor);
}
private static KeepMemberItemPattern createMemberItemContext(
String className, String fieldName, String fieldTypeDescriptor) {
KeepFieldTypePattern typePattern =
KeepFieldTypePattern.fromType(KeepTypePattern.fromDescriptor(fieldTypeDescriptor));
return KeepMemberItemPattern.builder()
.setClassReference(classReferenceFromName(className))
.setMemberPattern(
KeepFieldPattern.builder()
.setNamePattern(KeepFieldNamePattern.exact(fieldName))
.setTypePattern(typePattern)
.build())
.build();
}
private void setContext(KeepEdgeMetaInfo.Builder builder) {
builder.setContextFromFieldDescriptor(
KeepEdgeReaderUtils.getDescriptorFromJavaType(className), fieldName, fieldTypeDescriptor);
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
return createAnnotationVisitor(
descriptor,
visible,
true,
false,
parent::accept,
annotationParsingContext(descriptor),
className,
fieldName,
fieldTypeDescriptor,
this::setContext);
}
public static AnnotationVisitor createAnnotationVisitor(
String descriptor,
boolean visible,
boolean readEmbedded,
boolean readExtracted,
Consumer<KeepEdge> parent,
AnnotationParsingContext parsingContext,
String className,
String fieldName,
String fieldTypeDescriptor,
Consumer<KeepEdgeMetaInfo.Builder> setContext) {
// Skip any visible annotations as @KeepEdge is not runtime visible.
if (visible) {
return null;
}
if (!readEmbedded) {
// Only the embedded annotations can be on fields.
return null;
}
if (descriptor.equals(Edge.DESCRIPTOR)) {
return new KeepEdgeVisitor(parsingContext, parent::accept, setContext);
}
if (descriptor.equals(AnnotationConstants.UsesReflection.DESCRIPTOR)) {
return new UsesReflectionVisitor(
parsingContext,
parent::accept,
setContext,
createMemberItemContext(className, fieldName, fieldTypeDescriptor));
}
if (descriptor.equals(ForApi.DESCRIPTOR)) {
return new ForApiMemberVisitor(
parsingContext,
parent::accept,
setContext,
createMemberItemContext(className, fieldName, fieldTypeDescriptor));
}
if (descriptor.equals(UsedByReflection.DESCRIPTOR)
|| descriptor.equals(AnnotationConstants.UsedByNative.DESCRIPTOR)) {
return new UsedByReflectionMemberVisitor(
parsingContext,
parent::accept,
setContext,
createMemberItemContext(className, fieldName, fieldTypeDescriptor));
}
return null;
}
}
// Interface for providing AST result(s) for a sub-tree back up to its parent.
public interface Parent<T> {
void accept(T result);
}
private static class UserBindingsHelper {
private final KeepBindings.Builder builder = KeepBindings.builder();
private final Map<String, KeepBindingSymbol> userNames = new HashMap<>();
public KeepBindingSymbol resolveUserBinding(String name) {
return userNames.computeIfAbsent(name, builder::create);
}
public void defineUserBinding(String name, KeepItemPattern item) {
builder.addBinding(resolveUserBinding(name), item);
}
public KeepBindingSymbol defineFreshBinding(String name, KeepItemPattern item) {
KeepBindingSymbol symbol = builder.generateFreshSymbol(name);
builder.addBinding(symbol, item);
return symbol;
}
public KeepItemPattern getItemForBinding(KeepBindingReference bindingReference) {
return builder.getItemForBinding(bindingReference.getName());
}
public KeepBindings build() {
return builder.build();
}
}
public 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 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) {
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(ExtractedAnnotation.version) && value instanceof String) {
version = (String) value;
return;
}
ParsingContext parsingContext = getParsingContext().property(name);
ensureVersion(parsingContext);
if (name.equals(ExtractedAnnotation.context) && value instanceof String) {
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);
}
@Override
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.visitAnnotation(name, descriptor);
}
@Override
public void visitEnd() {
if (version == null) {
throw getParsingContext()
.error("Invalid extracted annotation, expected a version property.");
}
if (context == null) {
throw getParsingContext()
.error("Invalid extracted annotation, expected a context 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(
KeepCheck.builder()
.setMetaInfo(context.applyToMetadata(KeepEdgeMetaInfo.builder()).build())
.setKind(kind)
.setItemPattern(context.toItemPattern())
.build());
super.visitEnd();
}
}
private static class KeepEdgeVisitor extends AnnotationVisitorBase {
private final ParsingContext parsingContext;
private final Parent<KeepEdge> parent;
private final KeepEdge.Builder builder = KeepEdge.builder();
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
KeepEdgeVisitor(
AnnotationParsingContext parsingContext,
Parent<KeepEdge> parent,
Consumer<KeepEdgeMetaInfo.Builder> addContext) {
super(parsingContext);
this.parsingContext = parsingContext;
this.parent = parent;
addContext.accept(metaInfoBuilder);
}
@Override
public void visit(String name, Object value) {
if (name.equals(Edge.description) && value instanceof String) {
metaInfoBuilder.setDescription((String) value);
return;
}
super.visit(name, value);
}
@Override
public AnnotationVisitor visitArray(String name) {
PropertyParsingContext propertyParsingContext = parsingContext.property(name);
if (name.equals(Edge.bindings)) {
return new KeepBindingsVisitor(propertyParsingContext, bindingsHelper);
}
if (name.equals(Edge.preconditions)) {
return new KeepPreconditionsVisitor(
propertyParsingContext, builder::setPreconditions, bindingsHelper);
}
if (name.equals(Edge.consequences)) {
return new KeepConsequencesVisitor(
propertyParsingContext, builder::setConsequences, bindingsHelper);
}
return super.visitArray(name);
}
@Override
public void visitEnd() {
parent.accept(
builder.setMetaInfo(metaInfoBuilder.build()).setBindings(bindingsHelper.build()).build());
}
}
/**
* Parsing of @KeepForApi on a class context.
*
* <p>When used on a class context the annotation allows the member related content of a normal
* item. This parser extends the base item visitor and throws an error if any class specific
* properties are encountered.
*/
private static class ForApiClassVisitor extends KeepItemVisitorBase {
private final ParsingContext parsingContext;
private final String className;
private final Parent<KeepEdge> parent;
private final KeepEdge.Builder builder = KeepEdge.builder();
private final KeepConsequences.Builder consequences = KeepConsequences.builder();
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
ForApiClassVisitor(
AnnotationParsingContext parsingContext,
Parent<KeepEdge> parent,
Consumer<KeepEdgeMetaInfo.Builder> addContext,
String className) {
super(parsingContext);
this.parsingContext = parsingContext;
this.className = className;
this.parent = parent;
addContext.accept(metaInfoBuilder);
// The class context/holder is the annotated class.
visit(Item.className, className);
// The default kind is to target the class and its members.
visitEnum(null, Kind.DESCRIPTOR, Kind.CLASS_AND_MEMBERS);
}
@Override
public UserBindingsHelper getBindingsHelper() {
return bindingsHelper;
}
@Override
public void visit(String name, Object value) {
if (name.equals(Edge.description) && value instanceof String) {
metaInfoBuilder.setDescription((String) value);
return;
}
super.visit(name, value);
}
@Override
public AnnotationVisitor visitArray(String name) {
if (name.equals(ForApi.additionalTargets)) {
return new KeepConsequencesVisitor(
parsingContext.property(name),
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
},
bindingsHelper);
}
return super.visitArray(name);
}
@Override
public void visitEnd() {
if (!getKind().isOnlyClass() && isDefaultMemberDeclaration()) {
// If no member declarations have been made, set public & protected as the default.
AnnotationVisitor v = visitArray(Item.memberAccess);
v.visitEnum(null, MemberAccess.DESCRIPTOR, MemberAccess.PUBLIC);
v.visitEnum(null, MemberAccess.DESCRIPTOR, MemberAccess.PROTECTED);
}
super.visitEnd();
Collection<KeepItemReference> items = getItemsWithoutBinding();
for (KeepItemReference item : items) {
if (item.isBindingReference()) {
throw parsingContext.error("cannot reference bindings");
}
KeepClassItemPattern classItemPattern = item.asClassItemPattern();
if (classItemPattern == null) {
assert item.isMemberItemReference();
classItemPattern = item.asMemberItemPattern().getClassReference().asClassItemPattern();
}
String descriptor = KeepEdgeReaderUtils.getDescriptorFromClassTypeName(className);
String itemDescriptor = classItemPattern.getClassNamePattern().getExactDescriptor();
if (!descriptor.equals(itemDescriptor)) {
throw parsingContext.error("must reference its class context " + className);
}
if (classItemPattern.isMemberItemPattern() && items.size() == 1) {
throw parsingContext.error("kind must include its class");
}
if (!classItemPattern.getInstanceOfPattern().isAny()) {
throw parsingContext.error("cannot define an 'extends' pattern.");
}
consequences.addTarget(KeepTarget.builder().setItemReference(item).build());
}
parent.accept(
builder
.setMetaInfo(metaInfoBuilder.build())
.setBindings(bindingsHelper.build())
.setConsequences(consequences.build())
.build());
}
}
/**
* Parsing of @KeepForApi on a member context.
*
* <p>When used on a member context the annotation does not allow member related patterns.
*/
private static class ForApiMemberVisitor extends AnnotationVisitorBase {
private final ParsingContext parsingContext;
private final Parent<KeepEdge> parent;
private final KeepEdge.Builder builder = KeepEdge.builder();
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
private final KeepConsequences.Builder consequences = KeepConsequences.builder();
ForApiMemberVisitor(
AnnotationParsingContext parsingContext,
Parent<KeepEdge> parent,
Consumer<KeepEdgeMetaInfo.Builder> addContext,
KeepMemberItemPattern context) {
super(parsingContext);
this.parsingContext = parsingContext;
this.parent = parent;
addContext.accept(metaInfoBuilder);
// Create a binding for the context such that the class and member are shared.
KeepClassItemPattern classContext = context.getClassReference().asClassItemPattern();
KeepBindingSymbol bindingSymbol = bindingsHelper.defineFreshBinding("CONTEXT", classContext);
KeepClassItemReference classReference =
KeepBindingReference.forClass(bindingSymbol).toClassItemReference();
consequences.addTarget(
KeepTarget.builder()
.setItemPattern(
KeepMemberItemPattern.builder()
.copyFrom(context)
.setClassReference(classReference)
.build())
.build());
consequences.addTarget(KeepTarget.builder().setItemReference(classReference).build());
}
@Override
public void visit(String name, Object value) {
if (name.equals(Edge.description) && value instanceof String) {
metaInfoBuilder.setDescription((String) value);
return;
}
super.visit(name, value);
}
@Override
public AnnotationVisitor visitArray(String name) {
if (name.equals(ForApi.additionalTargets)) {
return new KeepConsequencesVisitor(
parsingContext.property(name),
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
},
bindingsHelper);
}
return super.visitArray(name);
}
@Override
public void visitEnd() {
parent.accept(
builder
.setMetaInfo(metaInfoBuilder.build())
.setBindings(bindingsHelper.build())
.setConsequences(consequences.build())
.build());
}
}
public static class ConstraintDeclarationParser extends DeclarationParser<KeepConstraints> {
private final ConstraintPropertiesParser constraintsParser;
private final ArrayPropertyParser<
KeepAnnotationPattern, AnnotationPatternParser.AnnotationProperty>
annotationsParser;
public ConstraintDeclarationParser(ParsingContext parsingContext) {
constraintsParser = new ConstraintPropertiesParser(parsingContext);
constraintsParser.setProperty(Target.constraints, ConstraintsProperty.CONSTRAINTS);
constraintsParser.setProperty(Target.constraintAdditions, ConstraintsProperty.ADDITIONS);
annotationsParser = new ArrayPropertyParser<>(parsingContext, AnnotationPatternParser::new);
annotationsParser.setProperty(
Target.constrainAnnotations, AnnotationPatternParser.AnnotationProperty.PATTERN);
annotationsParser.setValueCheck(this::verifyAnnotationList);
}
private void verifyAnnotationList(
List<KeepAnnotationPattern> annotationList, ParsingContext parsingContext) {
if (annotationList.isEmpty()) {
throw parsingContext.error("Expected non-empty array of annotation patterns");
}
}
@Override
List<Parser<?>> parsers() {
return ImmutableList.of(constraintsParser, annotationsParser);
}
public KeepConstraints getValueOrDefault(KeepConstraints defaultValue) {
return isDeclared() ? getValue() : defaultValue;
}
public KeepConstraints getValue() {
if (isDefault()) {
return null;
}
// If only the constraints are set then those are the constraints as is.
if (annotationsParser.isDefault()) {
assert constraintsParser.isDeclared();
return constraintsParser.getValue();
}
KeepConstraints.Builder builder;
if (constraintsParser.isDeclared()) {
// If constraints are set use it as the initial set.
builder = KeepConstraints.builder().copyFrom(constraintsParser.getValue());
assert builder.verifyNoAnnotations();
} else {
// If only the annotations are set, add them as an extension of the defaults.
builder = KeepConstraints.builder().copyFrom(KeepConstraints.defaultConstraints());
}
annotationsParser
.getValue()
.forEach(pattern -> builder.add(KeepConstraint.annotation(pattern)));
return builder.build();
}
}
/**
* Parsing of @UsedByReflection or @UsedByNative on a class context.
*
* <p>When used on a class context the annotation allows the member related content of a normal
* item. This parser extends the base item visitor and throws an error if any class specific
* properties are encountered.
*/
private static class UsedByReflectionClassVisitor extends KeepItemVisitorBase {
private final ParsingContext parsingContext;
private final String className;
private final Parent<KeepEdge> parent;
private final KeepEdge.Builder builder = KeepEdge.builder();
private final KeepConsequences.Builder consequences = KeepConsequences.builder();
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
private final ConstraintDeclarationParser constraintsParser;
UsedByReflectionClassVisitor(
AnnotationParsingContext parsingContext,
Parent<KeepEdge> parent,
Consumer<KeepEdgeMetaInfo.Builder> addContext,
String className) {
super(parsingContext);
this.parsingContext = parsingContext;
this.className = className;
this.parent = parent;
addContext.accept(metaInfoBuilder);
constraintsParser = new ConstraintDeclarationParser(parsingContext);
// The class context/holder is the annotated class.
visit(Item.className, className);
}
@Override
public UserBindingsHelper getBindingsHelper() {
return bindingsHelper;
}
@Override
public void visit(String name, Object value) {
if (name.equals(Edge.description) && value instanceof String) {
metaInfoBuilder.setDescription((String) value);
return;
}
super.visit(name, value);
}
@Override
public AnnotationVisitor visitArray(String name) {
PropertyParsingContext propertyParsingContext = parsingContext.property(name);
if (name.equals(Edge.preconditions)) {
return new KeepPreconditionsVisitor(
propertyParsingContext, builder::setPreconditions, bindingsHelper);
}
if (name.equals(UsedByReflection.additionalTargets)) {
return new KeepConsequencesVisitor(
propertyParsingContext,
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
},
bindingsHelper);
}
AnnotationVisitor visitor = constraintsParser.tryParseArray(name, unused -> {});
if (visitor != null) {
return visitor;
}
return super.visitArray(name);
}
@Override
public void visitEnd() {
if (getKind() == null && !isDefaultMemberDeclaration()) {
// If no explict kind is set and member declarations have been made, keep the class too.
visitEnum(null, Kind.DESCRIPTOR, Kind.CLASS_AND_MEMBERS);
}
super.visitEnd();
Collection<KeepItemReference> items = getItemsWithoutBinding();
for (KeepItemReference item : items) {
if (item.isBindingReference()) {
// TODO(b/248408342): The edge can have preconditions so it should support bindings!
throw parsingContext.error("cannot reference bindings");
}
KeepItemPattern itemPattern = item.asItemPattern();
KeepClassItemPattern holderPattern =
itemPattern.isClassItemPattern()
? itemPattern.asClassItemPattern()
: itemPattern.asMemberItemPattern().getClassReference().asClassItemPattern();
String descriptor = KeepEdgeReaderUtils.getDescriptorFromClassTypeName(className);
String itemDescriptor = holderPattern.getClassNamePattern().getExactDescriptor();
if (!descriptor.equals(itemDescriptor)) {
throw parsingContext.error("must reference its class context " + className);
}
if (!holderPattern.getInstanceOfPattern().isAny()) {
throw parsingContext.error("cannot define an 'extends' pattern.");
}
consequences.addTarget(
KeepTarget.builder()
.setItemPattern(itemPattern)
.setConstraints(
constraintsParser.getValueOrDefault(KeepConstraints.defaultConstraints()))
.build());
}
parent.accept(
builder
.setMetaInfo(metaInfoBuilder.build())
.setBindings(bindingsHelper.build())
.setConsequences(consequences.build())
.build());
}
}
/**
* Parsing of @UsedByReflection or @UsedByNative on a member context.
*
* <p>When used on a member context the annotation does not allow member related patterns.
*/
private static class UsedByReflectionMemberVisitor extends AnnotationVisitorBase {
private final ParsingContext parsingContext;
private final Parent<KeepEdge> parent;
private final KeepItemPattern context;
private final KeepEdge.Builder builder = KeepEdge.builder();
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
private final KeepConsequences.Builder consequences = KeepConsequences.builder();
private ItemKind kind = KeepEdgeReader.ItemKind.ONLY_MEMBERS;
private final ConstraintDeclarationParser constraintsParser;
UsedByReflectionMemberVisitor(
AnnotationParsingContext parsingContext,
Parent<KeepEdge> parent,
Consumer<KeepEdgeMetaInfo.Builder> addContext,
KeepItemPattern context) {
super(parsingContext);
this.parsingContext = parsingContext;
this.parent = parent;
this.context = context;
addContext.accept(metaInfoBuilder);
constraintsParser = new ConstraintDeclarationParser(parsingContext);
}
@Override
public void visit(String name, Object value) {
if (name.equals(Edge.description) && value instanceof String) {
metaInfoBuilder.setDescription((String) value);
return;
}
super.visit(name, value);
}
@Override
public void visitEnum(String name, String descriptor, String value) {
if (!descriptor.equals(AnnotationConstants.Kind.DESCRIPTOR)) {
super.visitEnum(name, descriptor, value);
}
KeepEdgeReader.ItemKind kind = KeepEdgeReader.ItemKind.fromString(value);
if (kind != null) {
this.kind = kind;
} else {
super.visitEnum(name, descriptor, value);
}
}
@Override
public AnnotationVisitor visitArray(String name) {
PropertyParsingContext propertyParsingContext = parsingContext.property(name);
if (name.equals(Edge.preconditions)) {
return new KeepPreconditionsVisitor(
propertyParsingContext, builder::setPreconditions, bindingsHelper);
}
if (name.equals(UsedByReflection.additionalTargets)) {
return new KeepConsequencesVisitor(
propertyParsingContext,
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
},
bindingsHelper);
}
AnnotationVisitor visitor = constraintsParser.tryParseArray(name, unused -> {});
if (visitor != null) {
return visitor;
}
return super.visitArray(name);
}
@Override
public void visitEnd() {
if (kind.isOnlyClass()) {
throw parsingContext.error("kind must include its member");
}
assert context.isMemberItemPattern();
KeepMemberItemPattern memberContext = context.asMemberItemPattern();
if (kind.includesClass()) {
consequences.addTarget(
KeepTarget.builder().setItemReference(memberContext.getClassReference()).build());
}
validateConsistentKind(memberContext.getMemberPattern());
consequences.addTarget(
KeepTarget.builder()
.setConstraints(
constraintsParser.getValueOrDefault(KeepConstraints.defaultConstraints()))
.setItemPattern(context)
.build());
parent.accept(
builder
.setMetaInfo(metaInfoBuilder.build())
.setBindings(bindingsHelper.build())
.setConsequences(consequences.build())
.build());
}
private void validateConsistentKind(KeepMemberPattern memberPattern) {
if (memberPattern.isGeneralMember()) {
throw parsingContext.error("Unexpected general pattern for context.");
}
if (memberPattern.isMethod() && !kind.includesMethod()) {
throw parsingContext.error("Kind " + kind + " cannot be use when annotating a method");
}
if (memberPattern.isField() && !kind.includesField()) {
throw parsingContext.error("Kind " + kind + " cannot be use when annotating a field");
}
}
}
private static class UsesReflectionVisitor extends AnnotationVisitorBase {
private final ParsingContext parsingContext;
private final Parent<KeepEdge> parent;
private final KeepEdge.Builder builder = KeepEdge.builder();
private final KeepPreconditions.Builder preconditions = KeepPreconditions.builder();
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
UsesReflectionVisitor(
AnnotationParsingContext parsingContext,
Parent<KeepEdge> parent,
Consumer<KeepEdgeMetaInfo.Builder> addContext,
KeepItemPattern context) {
super(parsingContext);
this.parsingContext = parsingContext;
this.parent = parent;
preconditions.addCondition(KeepCondition.builder().setItemPattern(context).build());
addContext.accept(metaInfoBuilder);
}
@Override
public void visit(String name, Object value) {
if (name.equals(Edge.description) && value instanceof String) {
metaInfoBuilder.setDescription((String) value);
return;
}
super.visit(name, value);
}
@Override
public AnnotationVisitor visitArray(String name) {
PropertyParsingContext propertyParsingContext = parsingContext.property(name);
if (name.equals(AnnotationConstants.UsesReflection.value)) {
return new KeepConsequencesVisitor(
propertyParsingContext, builder::setConsequences, bindingsHelper);
}
if (name.equals(AnnotationConstants.UsesReflection.additionalPreconditions)) {
return new KeepPreconditionsVisitor(
propertyParsingContext,
additionalPreconditions -> {
additionalPreconditions.forEach(preconditions::addCondition);
},
bindingsHelper);
}
return super.visitArray(name);
}
@Override
public void visitEnd() {
parent.accept(
builder
.setMetaInfo(metaInfoBuilder.build())
.setBindings(bindingsHelper.build())
.setPreconditions(preconditions.build())
.build());
}
}
private static class KeepBindingsVisitor extends AnnotationVisitorBase {
private final ParsingContext parsingContext;
private final UserBindingsHelper helper;
public KeepBindingsVisitor(PropertyParsingContext parsingContext, UserBindingsHelper helper) {
super(parsingContext);
this.parsingContext = parsingContext;
this.helper = helper;
}
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
assert name == null;
if (descriptor.equals(AnnotationConstants.Binding.DESCRIPTOR)) {
return new KeepBindingVisitor(parsingContext.annotation(descriptor), helper);
}
return super.visitAnnotation(name, descriptor);
}
}
private static class KeepPreconditionsVisitor extends AnnotationVisitorBase {
private final ParsingContext parsingContext;
private final Parent<KeepPreconditions> parent;
private final KeepPreconditions.Builder builder = KeepPreconditions.builder();
private final UserBindingsHelper bindingsHelper;
public KeepPreconditionsVisitor(
PropertyParsingContext parsingContext,
Parent<KeepPreconditions> parent,
UserBindingsHelper bindingsHelper) {
super(parsingContext);
this.parsingContext = parsingContext;
this.parent = parent;
this.bindingsHelper = bindingsHelper;
}
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
assert name == null;
if (descriptor.equals(Condition.DESCRIPTOR)) {
return new KeepConditionVisitor(
parsingContext.annotation(descriptor), builder::addCondition, bindingsHelper);
}
return super.visitAnnotation(name, descriptor);
}
@Override
public void visitEnd() {
parent.accept(builder.build());
}
}
private static class KeepConsequencesVisitor extends AnnotationVisitorBase {
private final ParsingContext parsingContext;
private final Parent<KeepConsequences> parent;
private final KeepConsequences.Builder builder = KeepConsequences.builder();
private final UserBindingsHelper bindingsHelper;
public KeepConsequencesVisitor(
PropertyParsingContext parsingContext,
Parent<KeepConsequences> parent,
UserBindingsHelper bindingsHelper) {
super(parsingContext);
this.parsingContext = parsingContext;
this.parent = parent;
this.bindingsHelper = bindingsHelper;
}
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
assert name == null;
if (descriptor.equals(Target.DESCRIPTOR)) {
return KeepTargetVisitor.create(
parsingContext.annotation(descriptor), builder::addTarget, bindingsHelper);
}
return super.visitAnnotation(name, descriptor);
}
@Override
public void visitEnd() {
parent.accept(builder.build());
}
}
/** Parsing of @CheckRemoved and @CheckOptimizedOut on a class context. */
private static class CheckRemovedClassVisitor extends AnnotationVisitorBase {
private final ParsingContext parsingContext;
private final Parent<KeepCheck> parent;
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
private final String className;
private final KeepCheckKind kind;
public CheckRemovedClassVisitor(
AnnotationParsingContext parsingContext,
Parent<KeepCheck> parent,
Consumer<KeepEdgeMetaInfo.Builder> addContext,
String className,
KeepCheckKind kind) {
super(parsingContext);
this.parsingContext = parsingContext;
this.parent = parent;
this.className = className;
this.kind = kind;
addContext.accept(metaInfoBuilder);
}
@Override
public void visit(String name, Object value) {
if (name.equals(Edge.description) && value instanceof String) {
metaInfoBuilder.setDescription((String) value);
return;
}
super.visit(name, value);
}
@Override
public void visitEnd() {
KeepItemVisitorBase itemVisitor =
new KeepItemVisitorBase(parsingContext) {
@Override
public UserBindingsHelper getBindingsHelper() {
throw parsingContext.error("bindings not supported");
}
};
itemVisitor.visit(Item.className, className);
itemVisitor.visitEnd();
parent.accept(
KeepCheck.builder()
.setMetaInfo(metaInfoBuilder.build())
.setKind(kind)
.setItemPattern(itemVisitor.getItemReference().asItemPattern())
.build());
}
}
/** Parsing of @CheckRemoved and @CheckOptimizedOut on a class context. */
private static class CheckRemovedMemberVisitor extends AnnotationVisitorBase {
private final Parent<KeepDeclaration> parent;
private final KeepItemPattern context;
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
private final KeepCheckKind kind;
CheckRemovedMemberVisitor(
AnnotationParsingContext parsingContext,
Parent<KeepDeclaration> parent,
Consumer<KeepEdgeMetaInfo.Builder> addContext,
KeepItemPattern context,
KeepCheckKind kind) {
super(parsingContext);
this.parent = parent;
this.context = context;
this.kind = kind;
addContext.accept(metaInfoBuilder);
}
@Override
public void visit(String name, Object value) {
if (name.equals(Edge.description) && value instanceof String) {
metaInfoBuilder.setDescription((String) value);
return;
}
super.visit(name, value);
}
@Override
public void visitEnd() {
super.visitEnd();
parent.accept(
KeepCheck.builder()
.setMetaInfo(metaInfoBuilder.build())
.setKind(kind)
.setItemPattern(context)
.build());
}
}
private static class ClassDeclarationParser extends DeclarationParser<KeepClassItemReference> {
private final ParsingContext parsingContext;
private final Supplier<UserBindingsHelper> getBindingsHelper;
private KeepClassItemReference boundClassItemReference = null;
private final ClassNameParser classNameParser;
private final ClassNameParser annotatedByParser;
private final InstanceOfParser instanceOfParser;
private final List<Parser<?>> parsers;
public ClassDeclarationParser(
ParsingContext parsingContext, Supplier<UserBindingsHelper> getBindingsHelper) {
this.parsingContext = parsingContext.group(Item.classGroup);
this.getBindingsHelper = getBindingsHelper;
classNameParser = new ClassNameParser(parsingContext.group(Item.classNameGroup));
classNameParser.setProperty(Item.className, ClassNameProperty.NAME);
classNameParser.setProperty(Item.classConstant, ClassNameProperty.CONSTANT);
classNameParser.setProperty(Item.classNamePattern, ClassNameProperty.PATTERN);
annotatedByParser = new ClassNameParser(parsingContext.group(Item.classAnnotatedByGroup));
annotatedByParser.setProperty(Item.classAnnotatedByClassName, ClassNameProperty.NAME);
annotatedByParser.setProperty(Item.classAnnotatedByClassConstant, ClassNameProperty.CONSTANT);
annotatedByParser.setProperty(
Item.classAnnotatedByClassNamePattern, ClassNameProperty.PATTERN);
instanceOfParser = new InstanceOfParser(parsingContext);
instanceOfParser.setProperty(Item.instanceOfPattern, InstanceOfProperties.PATTERN);
instanceOfParser.setProperty(Item.instanceOfClassName, InstanceOfProperties.NAME);
instanceOfParser.setProperty(Item.instanceOfClassConstant, InstanceOfProperties.CONSTANT);
instanceOfParser.setProperty(
Item.instanceOfClassNameExclusive, InstanceOfProperties.NAME_EXCL);
instanceOfParser.setProperty(
Item.instanceOfClassConstantExclusive, InstanceOfProperties.CONSTANT_EXCL);
parsers = ImmutableList.of(classNameParser, annotatedByParser, instanceOfParser);
}
@Override
public List<Parser<?>> parsers() {
return parsers;
}
private boolean isBindingReferenceDefined() {
return boundClassItemReference != null;
}
private boolean classPatternsAreDeclared() {
return classNameParser.isDeclared()
|| annotatedByParser.isDeclared()
|| instanceOfParser.isDeclared();
}
private void checkAllowedDefinitions() {
if (isBindingReferenceDefined() && classPatternsAreDeclared()) {
throw parsingContext.error(
"Cannot reference a class binding and class patterns for a single class item");
}
}
@Override
public boolean isDeclared() {
return isBindingReferenceDefined() || super.isDeclared();
}
public KeepClassItemReference getValue() {
checkAllowedDefinitions();
if (isBindingReferenceDefined()) {
return boundClassItemReference;
}
if (classPatternsAreDeclared()) {
return KeepClassItemPattern.builder()
.setClassNamePattern(
classNameParser.getValueOrDefault(KeepQualifiedClassNamePattern.any()))
.setAnnotatedByPattern(OptionalPattern.ofNullable(annotatedByParser.getValue()))
.setInstanceOfPattern(instanceOfParser.getValueOrDefault(KeepInstanceOfPattern.any()))
.build()
.toClassItemReference();
}
assert isDefault();
return KeepClassItemPattern.any().toClassItemReference();
}
public void setBindingReference(KeepClassItemReference bindingReference) {
if (isBindingReferenceDefined()) {
throw parsingContext.error(
"Cannot reference multiple class bindings for a single class item");
}
this.boundClassItemReference = bindingReference;
}
@Override
public boolean tryParse(String name, Object value) {
if (name.equals(Item.classFromBinding) && value instanceof String) {
KeepBindingSymbol symbol = getBindingsHelper.get().resolveUserBinding((String) value);
setBindingReference(KeepBindingReference.forClass(symbol).toClassItemReference());
return true;
}
return super.tryParse(name, value);
}
}
private static class MethodDeclarationParser extends DeclarationParser<KeepMethodPattern> {
private final ParsingContext parsingContext;
private KeepMethodAccessPattern.Builder accessBuilder = null;
private KeepMethodPattern.Builder builder = null;
private final ClassNameParser annotatedByParser;
private final StringPatternParser nameParser;
private final MethodReturnTypeParser returnTypeParser;
private final MethodParametersParser parametersParser;
private final List<Parser<?>> parsers;
private MethodDeclarationParser(ParsingContext parsingContext) {
this.parsingContext = parsingContext;
annotatedByParser = new ClassNameParser(parsingContext.group(Item.methodAnnotatedByGroup));
annotatedByParser.setProperty(Item.methodAnnotatedByClassName, ClassNameProperty.NAME);
annotatedByParser.setProperty(
Item.methodAnnotatedByClassConstant, ClassNameProperty.CONSTANT);
annotatedByParser.setProperty(
Item.methodAnnotatedByClassNamePattern, ClassNameProperty.PATTERN);
nameParser = new StringPatternParser(parsingContext.group(Item.methodNameGroup));
nameParser.setProperty(Item.methodName, StringProperty.EXACT);
nameParser.setProperty(Item.methodNamePattern, StringProperty.PATTERN);
returnTypeParser = new MethodReturnTypeParser(parsingContext.group(Item.returnTypeGroup));
returnTypeParser.setProperty(Item.methodReturnType, TypeProperty.TYPE_NAME);
returnTypeParser.setProperty(Item.methodReturnTypeConstant, TypeProperty.TYPE_CONSTANT);
returnTypeParser.setProperty(Item.methodReturnTypePattern, TypeProperty.TYPE_PATTERN);
parametersParser = new MethodParametersParser(parsingContext.group(Item.parametersGroup));
parametersParser.setProperty(Item.methodParameters, TypeProperty.TYPE_NAME);
parametersParser.setProperty(Item.methodParameterTypePatterns, TypeProperty.TYPE_PATTERN);
parsers = ImmutableList.of(annotatedByParser, nameParser, returnTypeParser, parametersParser);
}
@Override
List<Parser<?>> parsers() {
return parsers;
}
private KeepMethodPattern.Builder getBuilder() {
if (builder == null) {
builder = KeepMethodPattern.builder();
}
return builder;
}
@Override
public boolean isDeclared() {
return accessBuilder != null || builder != null || super.isDeclared();
}
public KeepMethodPattern getValue() {
if (accessBuilder != null) {
getBuilder().setAccessPattern(accessBuilder.build());
}
if (annotatedByParser.isDeclared()) {
getBuilder().setAnnotatedByPattern(OptionalPattern.of(annotatedByParser.getValue()));
}
if (nameParser.isDeclared()) {
KeepStringPattern namePattern = nameParser.getValue();
getBuilder().setNamePattern(KeepMethodNamePattern.fromStringPattern(namePattern));
}
if (returnTypeParser.isDeclared()) {
getBuilder().setReturnTypePattern(returnTypeParser.getValue());
}
if (parametersParser.isDeclared()) {
getBuilder().setParametersPattern(parametersParser.getValue());
}
return builder != null ? builder.build() : null;
}
@Override
public AnnotationVisitor tryParseArray(String name) {
if (name.equals(Item.methodAccess)) {
accessBuilder = KeepMethodAccessPattern.builder();
return new MethodAccessVisitor(parsingContext, accessBuilder);
}
return super.tryParseArray(name);
}
}
private static class FieldDeclarationParser extends DeclarationParser<KeepFieldPattern> {
private final ParsingContext parsingContext;
private final ClassNameParser annotatedByParser;
private final StringPatternParser nameParser;
private final FieldTypeParser typeParser;
private KeepFieldAccessPattern.Builder accessBuilder = null;
private KeepFieldPattern.Builder builder = null;
private final List<Parser<?>> parsers;
public FieldDeclarationParser(ParsingContext parsingContext) {
this.parsingContext = parsingContext;
annotatedByParser = new ClassNameParser(parsingContext.group(Item.fieldAnnotatedByGroup));
annotatedByParser.setProperty(Item.fieldAnnotatedByClassName, ClassNameProperty.NAME);
annotatedByParser.setProperty(Item.fieldAnnotatedByClassConstant, ClassNameProperty.CONSTANT);
annotatedByParser.setProperty(
Item.fieldAnnotatedByClassNamePattern, ClassNameProperty.PATTERN);
nameParser = new StringPatternParser(parsingContext.group(Item.fieldNameGroup));
nameParser.setProperty(Item.fieldName, StringProperty.EXACT);
nameParser.setProperty(Item.fieldNamePattern, StringProperty.PATTERN);
typeParser = new FieldTypeParser(parsingContext.group(Item.fieldTypeGroup));
typeParser.setProperty(Item.fieldTypePattern, TypeProperty.TYPE_PATTERN);
typeParser.setProperty(Item.fieldType, TypeProperty.TYPE_NAME);
typeParser.setProperty(Item.fieldTypeConstant, TypeProperty.TYPE_CONSTANT);
parsers = ImmutableList.of(annotatedByParser, nameParser, typeParser);
}
@Override
public List<Parser<?>> parsers() {
return parsers;
}
private KeepFieldPattern.Builder getBuilder() {
if (builder == null) {
builder = KeepFieldPattern.builder();
}
return builder;
}
@Override
public boolean isDeclared() {
return accessBuilder != null || builder != null || super.isDeclared();
}
public KeepFieldPattern getValue() {
if (accessBuilder != null) {
getBuilder().setAccessPattern(accessBuilder.build());
}
if (annotatedByParser.isDeclared()) {
getBuilder().setAnnotatedByPattern(OptionalPattern.of(annotatedByParser.getValue()));
}
if (nameParser.isDeclared()) {
getBuilder().setNamePattern(KeepFieldNamePattern.fromStringPattern(nameParser.getValue()));
}
if (typeParser.isDeclared()) {
getBuilder().setTypePattern(typeParser.getValue());
}
return builder != null ? builder.build() : null;
}
@Override
public AnnotationVisitor tryParseArray(String name) {
if (name.equals(Item.fieldAccess)) {
accessBuilder = KeepFieldAccessPattern.builder();
return new FieldAccessVisitor(parsingContext, accessBuilder);
}
return super.tryParseArray(name);
}
}
private static class MemberDeclarationParser extends DeclarationParser<KeepMemberPattern> {
private final ParsingContext parsingContext;
private KeepMemberAccessPattern.Builder accessBuilder = null;
private final ClassNameParser annotatedByParser;
private final MethodDeclarationParser methodDeclaration;
private final FieldDeclarationParser fieldDeclaration;
private final List<Parser<?>> parsers;
MemberDeclarationParser(ParsingContext parsingContext) {
this.parsingContext = parsingContext.group(Item.memberGroup);
annotatedByParser = new ClassNameParser(parsingContext.group(Item.memberAnnotatedByGroup));
annotatedByParser.setProperty(Item.memberAnnotatedByClassName, ClassNameProperty.NAME);
annotatedByParser.setProperty(
Item.memberAnnotatedByClassConstant, ClassNameProperty.CONSTANT);
annotatedByParser.setProperty(
Item.memberAnnotatedByClassNamePattern, ClassNameProperty.PATTERN);
methodDeclaration = new MethodDeclarationParser(parsingContext);
fieldDeclaration = new FieldDeclarationParser(parsingContext);
parsers = ImmutableList.of(annotatedByParser, methodDeclaration, fieldDeclaration);
}
@Override
public List<Parser<?>> parsers() {
return parsers;
}
@Override
public boolean isDeclared() {
return accessBuilder != null || super.isDeclared();
}
public KeepMemberPattern getValue() {
KeepMethodPattern method = methodDeclaration.getValue();
KeepFieldPattern field = fieldDeclaration.getValue();
if (accessBuilder != null || annotatedByParser.isDeclared()) {
if (method != null || field != null) {
throw parsingContext.error(
"Cannot define common member access as well as field or method pattern");
}
KeepMemberPattern.Builder builder = KeepMemberPattern.memberBuilder();
if (accessBuilder != null) {
builder.setAccessPattern(accessBuilder.build());
}
builder.setAnnotatedByPattern(OptionalPattern.ofNullable(annotatedByParser.getValue()));
return builder.build();
}
if (method != null && field != null) {
throw parsingContext.error("Cannot define both a field and a method pattern");
}
if (method != null) {
return method;
}
if (field != null) {
return field;
}
return null;
}
@Override
public AnnotationVisitor tryParseArray(String name) {
if (name.equals(Item.memberAccess)) {
accessBuilder = KeepMemberAccessPattern.memberBuilder();
return new MemberAccessVisitor(parsingContext, accessBuilder);
}
return super.tryParseArray(name);
}
}
private abstract static class KeepItemVisitorBase extends AnnotationVisitorBase {
private final ParsingContext parsingContext;
private String memberBindingReference = null;
private ItemKind kind = null;
private final ClassDeclarationParser classDeclaration;
private final MemberDeclarationParser memberDeclaration;
public abstract UserBindingsHelper getBindingsHelper();
// Constructed item available once visitEnd has been called.
private KeepItemReference itemReference = null;
KeepItemVisitorBase(ParsingContext parsingContext) {
super(parsingContext);
this.parsingContext = parsingContext;
classDeclaration = new ClassDeclarationParser(parsingContext, this::getBindingsHelper);
memberDeclaration = new MemberDeclarationParser(parsingContext);
}
public Collection<KeepItemReference> getItemsWithoutBinding() {
if (itemReference == null) {
throw parsingContext.error("Item reference not finalized. Missing call to visitEnd()");
}
if (itemReference.isBindingReference()) {
return Collections.singletonList(itemReference);
}
// Kind is only null if item is a "binding reference".
if (kind == null) {
throw parsingContext.error("Unexpected state: unknown kind for an item pattern");
}
if (kind.includesClassAndMembers()) {
assert !itemReference.isBindingReference();
KeepItemPattern itemPattern = itemReference.asItemPattern();
KeepClassItemReference classReference;
KeepMemberItemPattern memberPattern;
if (itemPattern.isClassItemPattern()) {
classReference = itemPattern.asClassItemPattern().toClassItemReference();
memberPattern =
KeepMemberItemPattern.builder()
.setClassReference(classReference)
.setMemberPattern(KeepMemberPattern.allMembers())
.build();
} else {
memberPattern = itemPattern.asMemberItemPattern();
classReference = memberPattern.getClassReference();
}
return ImmutableList.of(classReference, memberPattern.toItemReference());
} else {
return Collections.singletonList(itemReference);
}
}
public Collection<KeepItemReference> getItemsWithBinding() {
if (itemReference == null) {
throw parsingContext.error("Item reference not finalized. Missing call to visitEnd()");
}
if (itemReference.isBindingReference()) {
return Collections.singletonList(itemReference);
}
// Kind is only null if item is a "binding reference".
if (kind == null) {
throw parsingContext.error("Unexpected state: unknown kind for an item pattern");
}
if (kind.includesClassAndMembers()) {
KeepItemPattern itemPattern = itemReference.asItemPattern();
// Ensure we have a member item linked to the correct class.
KeepMemberItemPattern memberItemPattern;
if (itemPattern.isClassItemPattern()) {
memberItemPattern =
KeepMemberItemPattern.builder()
.setClassReference(itemPattern.asClassItemPattern().toClassItemReference())
.build();
} else {
memberItemPattern = itemPattern.asMemberItemPattern();
}
// If the class is not a binding, introduce the binding and rewrite the member.
KeepClassItemReference classItemReference = memberItemPattern.getClassReference();
if (classItemReference.isClassItemPattern()) {
KeepClassItemPattern classItemPattern = classItemReference.asClassItemPattern();
KeepBindingSymbol symbol =
getBindingsHelper().defineFreshBinding("CLASS", classItemPattern);
classItemReference = KeepBindingReference.forClass(symbol).toClassItemReference();
memberItemPattern =
KeepMemberItemPattern.builder()
.copyFrom(memberItemPattern)
.setClassReference(classItemReference)
.build();
}
assert classItemReference.isBindingReference();
assert memberItemPattern.getClassReference().equals(classItemReference);
return ImmutableList.of(classItemReference, memberItemPattern.toItemReference());
} else {
return Collections.singletonList(itemReference);
}
}
public KeepItemReference getItemReference() {
if (itemReference == null) {
throw parsingContext.error("Item reference not finalized. Missing call to visitEnd()");
}
return itemReference;
}
public ItemKind getKind() {
return kind;
}
public boolean isDefaultMemberDeclaration() {
return memberDeclaration.isDefault();
}
@Override
public void visitEnum(String name, String descriptor, String value) {
if (!descriptor.equals(AnnotationConstants.Kind.DESCRIPTOR)) {
super.visitEnum(name, descriptor, value);
}
ItemKind kind = ItemKind.fromString(value);
if (kind != null) {
this.kind = kind;
} else {
super.visitEnum(name, descriptor, value);
}
}
@Override
public void visit(String name, Object value) {
if (name.equals(Item.memberFromBinding) && value instanceof String) {
memberBindingReference = (String) value;
return;
}
if (classDeclaration.tryParse(name, value)
|| memberDeclaration.tryParse(name, value)) {
return;
}
super.visit(name, value);
}
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
AnnotationVisitor visitor = classDeclaration.tryParseAnnotation(name, descriptor);
if (visitor != null) {
return visitor;
}
visitor = memberDeclaration.tryParseAnnotation(name, descriptor);
if (visitor != null) {
return visitor;
}
return super.visitAnnotation(name, descriptor);
}
@Override
public AnnotationVisitor visitArray(String name) {
AnnotationVisitor visitor = memberDeclaration.tryParseArray(name);
if (visitor != null) {
return visitor;
}
return super.visitArray(name);
}
@Override
public void visitEnd() {
// Item defined by binding reference.
if (memberBindingReference != null) {
if (classDeclaration.isDeclared() || memberDeclaration.isDeclared() || kind != null) {
throw parsingContext.error(
"Cannot define an item explicitly and via a member-binding reference");
}
KeepBindingSymbol symbol = getBindingsHelper().resolveUserBinding(memberBindingReference);
itemReference = KeepBindingReference.forMember(symbol).toItemReference();
return;
}
// If no explicit kind is set, extract it based on the member pattern.
KeepMemberPattern memberPattern = memberDeclaration.getValue();
if (kind == null) {
if (memberPattern == null) {
kind = ItemKind.ONLY_CLASS;
} else if (memberPattern.isMethod()) {
kind = ItemKind.ONLY_METHODS;
} else if (memberPattern.isField()) {
kind = ItemKind.ONLY_FIELDS;
} else if (memberPattern.isGeneralMember()) {
kind = ItemKind.ONLY_MEMBERS;
} else {
assert false;
}
}
// If the pattern is only for a class set it and exit.
if (kind.isOnlyClass()) {
if (memberDeclaration.isDeclared()) {
throw parsingContext.error("Item pattern for members is incompatible with kind " + kind);
}
itemReference = classDeclaration.getValue();
return;
}
// At this point the pattern must include members.
// If no explicit member pattern is defined the implicit pattern is all members.
// Then refine the member pattern to be as precise as the specified kind.
assert kind.requiresMembers();
if (memberPattern == null) {
memberPattern = KeepMemberPattern.allMembers();
}
if (kind.requiresMethods() && !memberPattern.isMethod()) {
if (memberPattern.isGeneralMember()) {
memberPattern = KeepMethodPattern.builder().copyFromMemberPattern(memberPattern).build();
} else {
assert memberPattern.isField();
throw parsingContext.error("Item pattern for fields is incompatible with kind " + kind);
}
}
if (kind.requiresFields() && !memberPattern.isField()) {
if (memberPattern.isGeneralMember()) {
memberPattern = KeepFieldPattern.builder().copyFromMemberPattern(memberPattern).build();
} else {
assert memberPattern.isMethod();
throw parsingContext.error("Item pattern for methods is incompatible with kind " + kind);
}
}
KeepClassItemReference classReference = classDeclaration.getValue();
KeepItemPattern itemPattern =
KeepMemberItemPattern.builder()
.setClassReference(classReference)
.setMemberPattern(memberPattern)
.build();
itemReference = itemPattern.toItemReference();
}
}
private static class KeepBindingVisitor extends KeepItemVisitorBase {
private final ParsingContext parsingContext;
private final UserBindingsHelper helper;
private String bindingName;
public KeepBindingVisitor(AnnotationParsingContext parsingContext, UserBindingsHelper helper) {
super(parsingContext);
this.parsingContext = parsingContext;
this.helper = helper;
}
@Override
public UserBindingsHelper getBindingsHelper() {
return helper;
}
@Override
public void visit(String name, Object value) {
if (name.equals(Binding.bindingName) && value instanceof String) {
bindingName = (String) value;
return;
}
super.visit(name, value);
}
@Override
public void visitEnd() {
super.visitEnd();
KeepItemReference item = getItemReference();
// The language currently disallows aliasing bindings, thus a binding cannot directly be
// defined by a reference to another binding.
if (item.isBindingReference()) {
throw parsingContext.error(
"Invalid binding reference to '"
+ item.asBindingReference()
+ "' in binding definition of '"
+ bindingName
+ "'");
}
helper.defineUserBinding(bindingName, item.asItemPattern());
}
}
private static class KeepTargetVisitor extends KeepItemVisitorBase {
private final Parent<KeepTarget> parent;
private final UserBindingsHelper bindingsHelper;
private final ConstraintDeclarationParser constraintsParser;
private final KeepTarget.Builder builder = KeepTarget.builder();
static KeepTargetVisitor create(
AnnotationParsingContext parsingContext,
Parent<KeepTarget> parent,
UserBindingsHelper bindingsHelper) {
return new KeepTargetVisitor(parsingContext, parent, bindingsHelper);
}
private KeepTargetVisitor(
AnnotationParsingContext parsingContext,
Parent<KeepTarget> parent,
UserBindingsHelper bindingsHelper) {
super(parsingContext);
this.parent = parent;
this.bindingsHelper = bindingsHelper;
constraintsParser = new ConstraintDeclarationParser(parsingContext);
}
@Override
public UserBindingsHelper getBindingsHelper() {
return bindingsHelper;
}
@Override
public AnnotationVisitor visitArray(String name) {
AnnotationVisitor visitor = constraintsParser.tryParseArray(name, unused -> {});
if (visitor != null) {
return visitor;
}
return super.visitArray(name);
}
@Override
public void visitEnd() {
super.visitEnd();
builder.setConstraints(
constraintsParser.getValueOrDefault(KeepConstraints.defaultConstraints()));
for (KeepItemReference item : getItemsWithBinding()) {
parent.accept(builder.setItemReference(item).build());
}
}
}
private static class KeepConditionVisitor extends KeepItemVisitorBase {
private final Parent<KeepCondition> parent;
private final UserBindingsHelper bindingsHelper;
public KeepConditionVisitor(
AnnotationParsingContext parsingContext,
Parent<KeepCondition> parent,
UserBindingsHelper bindingsHelper) {
super(parsingContext);
this.parent = parent;
this.bindingsHelper = bindingsHelper;
}
@Override
public UserBindingsHelper getBindingsHelper() {
return bindingsHelper;
}
@Override
public void visitEnd() {
super.visitEnd();
parent.accept(KeepCondition.builder().setItemReference(getItemReference()).build());
}
}
private static class MemberAccessVisitor extends AnnotationVisitorBase {
private KeepMemberAccessPattern.BuilderBase<?, ?> builder;
public MemberAccessVisitor(ParsingContext parsingContext, BuilderBase<?, ?> builder) {
super(parsingContext);
this.builder = builder;
}
static boolean withNormalizedAccessFlag(String flag, BiPredicate<String, Boolean> fn) {
boolean allow = !flag.startsWith(MemberAccess.NEGATION_PREFIX);
return allow
? fn.test(flag, true)
: fn.test(flag.substring(MemberAccess.NEGATION_PREFIX.length()), false);
}
@Override
public void visitEnum(String ignore, String descriptor, String value) {
if (!descriptor.equals(AnnotationConstants.MemberAccess.DESCRIPTOR)) {
super.visitEnum(ignore, descriptor, value);
}
boolean handled =
withNormalizedAccessFlag(
value,
(flag, allow) -> {
AccessVisibility visibility = getAccessVisibilityFromString(flag);
if (visibility != null) {
builder.setAccessVisibility(visibility, allow);
return true;
}
switch (flag) {
case MemberAccess.STATIC:
builder.setStatic(allow);
return true;
case MemberAccess.FINAL:
builder.setFinal(allow);
return true;
case MemberAccess.SYNTHETIC:
builder.setSynthetic(allow);
return true;
default:
return false;
}
});
if (!handled) {
super.visitEnum(ignore, descriptor, value);
}
}
private AccessVisibility getAccessVisibilityFromString(String value) {
switch (value) {
case MemberAccess.PUBLIC:
return AccessVisibility.PUBLIC;
case MemberAccess.PROTECTED:
return AccessVisibility.PROTECTED;
case MemberAccess.PACKAGE_PRIVATE:
return AccessVisibility.PACKAGE_PRIVATE;
case MemberAccess.PRIVATE:
return AccessVisibility.PRIVATE;
default:
return null;
}
}
}
private static class MethodAccessVisitor extends MemberAccessVisitor {
private KeepMethodAccessPattern.Builder methodAccessBuilder;
public MethodAccessVisitor(
ParsingContext parsingContext, KeepMethodAccessPattern.Builder builder) {
super(parsingContext, builder);
this.methodAccessBuilder = builder;
}
@Override
public void visitEnum(String ignore, String descriptor, String value) {
if (!descriptor.equals(AnnotationConstants.MethodAccess.DESCRIPTOR)) {
super.visitEnum(ignore, descriptor, value);
}
boolean handled =
withNormalizedAccessFlag(
value,
(flag, allow) -> {
switch (flag) {
case MethodAccess.SYNCHRONIZED:
methodAccessBuilder.setSynchronized(allow);
return true;
case MethodAccess.BRIDGE:
methodAccessBuilder.setBridge(allow);
return true;
case MethodAccess.NATIVE:
methodAccessBuilder.setNative(allow);
return true;
case MethodAccess.ABSTRACT:
methodAccessBuilder.setAbstract(allow);
return true;
case MethodAccess.STRICT_FP:
methodAccessBuilder.setStrictFp(allow);
return true;
default:
return false;
}
});
if (!handled) {
// Continue visitation with the "member" descriptor to allow matching the common values.
super.visitEnum(ignore, MemberAccess.DESCRIPTOR, value);
}
}
}
private static class FieldAccessVisitor extends MemberAccessVisitor {
private KeepFieldAccessPattern.Builder fieldAccessBuilder;
public FieldAccessVisitor(
ParsingContext parsingContext, KeepFieldAccessPattern.Builder builder) {
super(parsingContext, builder);
this.fieldAccessBuilder = builder;
}
@Override
public void visitEnum(String ignore, String descriptor, String value) {
if (!descriptor.equals(AnnotationConstants.FieldAccess.DESCRIPTOR)) {
super.visitEnum(ignore, descriptor, value);
}
boolean handled =
withNormalizedAccessFlag(
value,
(flag, allow) -> {
switch (flag) {
case FieldAccess.VOLATILE:
fieldAccessBuilder.setVolatile(allow);
return true;
case FieldAccess.TRANSIENT:
fieldAccessBuilder.setTransient(allow);
return true;
default:
return false;
}
});
if (!handled) {
// Continue visitation with the "member" descriptor to allow matching the common values.
super.visitEnum(ignore, MemberAccess.DESCRIPTOR, value);
}
}
}
}