blob: a0a37e321e4ac3ac6225f5a81ff29362494c036e [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.ast.AccessVisibility;
import com.android.tools.r8.keepanno.ast.AnnotationConstants;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.AnnotationPattern;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Binding;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.ClassNamePattern;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Condition;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Constraints;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Edge;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.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.InstanceOfPattern;
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.StringPattern;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Target;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.TypePattern;
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.KeepClassItemPattern;
import com.android.tools.r8.keepanno.ast.KeepClassItemReference;
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.KeepFieldPattern;
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.KeepMemberItemPattern;
import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodAccessPattern;
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.KeepPackagePattern;
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.KeepUnqualfiedClassNamePattern;
import com.android.tools.r8.keepanno.ast.ModifierPattern;
import com.android.tools.r8.keepanno.ast.OptionalPattern;
import com.android.tools.r8.keepanno.utils.Unimplemented;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
public class KeepEdgeWriter implements Opcodes {
/** Annotation visitor interface to allow usage from tests without type conflicts in r8lib. */
public interface AnnotationVisitorInterface {
int version();
void visit(String name, Object value);
void visitEnum(String name, String descriptor, String value);
AnnotationVisitorInterface visitAnnotation(String name, String descriptor);
AnnotationVisitorInterface visitArray(String name);
void visitEnd();
}
private static AnnotationVisitor wrap(AnnotationVisitorInterface visitor) {
if (visitor == null) {
return null;
}
return new AnnotationVisitor(visitor.version()) {
@Override
public void visit(String name, Object value) {
visitor.visit(name, value);
}
@Override
public void visitEnum(String name, String descriptor, String value) {
visitor.visitEnum(name, descriptor, value);
}
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
AnnotationVisitorInterface v = visitor.visitAnnotation(name, descriptor);
return v == visitor ? this : wrap(v);
}
@Override
public AnnotationVisitor visitArray(String name) {
AnnotationVisitorInterface v = visitor.visitArray(name);
return v == visitor ? this : wrap(v);
}
@Override
public void visitEnd() {
visitor.visitEnd();
}
};
}
// Helper to ensure that any call creating a new annotation visitor is statically scoped with its
// call to visit end.
private static void withNewVisitor(AnnotationVisitor visitor, Consumer<AnnotationVisitor> fn) {
fn.accept(visitor);
visitor.visitEnd();
}
public static void writeExtractedEdges(
List<KeepDeclaration> declarations,
BiFunction<String, Boolean, AnnotationVisitorInterface> getVisitor) {
if (declarations.isEmpty()) {
return;
}
withNewVisitor(
wrap(getVisitor.apply(ExtractedAnnotations.DESCRIPTOR, false)),
containerVisitor ->
withNewVisitor(
containerVisitor.visitArray(ExtractedAnnotations.value),
arrayVisitor ->
declarations.forEach(
decl ->
withNewVisitor(
arrayVisitor.visitAnnotation(null, ExtractedAnnotation.DESCRIPTOR),
extractVisitor -> writeExtractedEdge(extractVisitor, decl)))));
}
private static void writeExtractedEdge(AnnotationVisitor visitor, KeepDeclaration decl) {
KeepEdgeMetaInfo metaInfo = decl.getMetaInfo();
visitor.visit(ExtractedAnnotation.version, metaInfo.getVersion().toVersionString());
visitor.visit(ExtractedAnnotation.context, metaInfo.getContextDescriptorString());
decl.match(
edge -> {
withNewVisitor(
visitor.visitAnnotation(ExtractedAnnotation.edge, Edge.DESCRIPTOR),
v -> new KeepEdgeWriter().writeEdge(edge, v));
},
check -> {
switch (check.getKind()) {
case REMOVED:
visitor.visit(ExtractedAnnotation.checkRemoved, true);
break;
case OPTIMIZED_OUT:
visitor.visit(ExtractedAnnotation.checkOptimizedOut, true);
break;
default:
throw new KeepEdgeException("Unexpected keep check kind: " + check.getKind());
}
});
}
private void writeEdge(KeepEdge edge, AnnotationVisitor visitor) {
writeMetaInfo(visitor, edge.getMetaInfo());
writeBindings(visitor, edge.getBindings());
writePreconditions(visitor, edge.getPreconditions());
writeConsequences(visitor, edge.getConsequences());
}
private void writeMetaInfo(AnnotationVisitor visitor, KeepEdgeMetaInfo metaInfo) {
// The edge version and context is written in the extraction header.
if (metaInfo.hasDescription()) {
visitor.visit(Edge.description, metaInfo.getDescriptionString());
}
}
private void writeBindings(AnnotationVisitor visitor, KeepBindings bindings) {
if (bindings.isEmpty()) {
return;
}
withNewVisitor(
visitor.visitArray(Edge.bindings),
arrayVisitor -> {
bindings.forEach(
(symbol, item) -> {
withNewVisitor(
arrayVisitor.visitAnnotation(null, Binding.DESCRIPTOR),
bindingVisitor -> {
bindingVisitor.visit(Binding.bindingName, symbol.toString());
// The item is written directly into the binding annotation.
writeItem(bindingVisitor, item);
});
});
});
}
private void writeStringPattern(
KeepStringPattern stringPattern, String propertyName, AnnotationVisitor visitor) {
withNewVisitor(
visitor.visitAnnotation(propertyName, AnnotationConstants.StringPattern.DESCRIPTOR),
v -> {
if (stringPattern.isAny()) {
// The emtpy pattern matches any string.
return;
}
if (stringPattern.isExact()) {
v.visit(StringPattern.exact, stringPattern.asExactString());
return;
}
if (stringPattern.hasPrefix()) {
v.visit(StringPattern.startsWith, stringPattern.getPrefixString());
}
if (stringPattern.hasSuffix()) {
v.visit(StringPattern.endsWith, stringPattern.getSuffixString());
}
});
}
private void writePreconditions(AnnotationVisitor visitor, KeepPreconditions preconditions) {
if (preconditions.isAlways()) {
return;
}
String ignoredArrayValueName = null;
withNewVisitor(
visitor.visitArray(Edge.preconditions),
arrayVisitor ->
preconditions.forEach(
condition ->
withNewVisitor(
arrayVisitor.visitAnnotation(ignoredArrayValueName, Condition.DESCRIPTOR),
conditionVisitor ->
writeItemReference(conditionVisitor, condition.getItem()))));
}
private void writeConsequences(AnnotationVisitor visitor, KeepConsequences consequences) {
assert !consequences.isEmpty();
String ignoredArrayValueName = null;
withNewVisitor(
visitor.visitArray(Edge.consequences),
arrayVisitor ->
consequences.forEachTarget(
target ->
withNewVisitor(
arrayVisitor.visitAnnotation(ignoredArrayValueName, Target.DESCRIPTOR),
targetVisitor -> writeTarget(target, targetVisitor))));
}
private void writeTarget(KeepTarget target, AnnotationVisitor visitor) {
writeConstraints(visitor, target.getConstraints(), target.getItem());
writeItemReference(visitor, target.getItem());
}
private void writeConstraints(
AnnotationVisitor visitor, KeepConstraints constraints, KeepItemReference item) {
Set<KeepConstraint> typedConstraints;
if (item.isClassItemReference()) {
typedConstraints = constraints.getClassConstraints();
} else if (item.isMemberItemPattern()) {
KeepMemberPattern memberPattern = item.asMemberItemPattern().getMemberPattern();
if (memberPattern.isMethod()) {
typedConstraints = constraints.getMethodConstraints();
} else if (memberPattern.isField()) {
typedConstraints = constraints.getFieldConstraints();
} else {
typedConstraints = constraints.getMemberConstraints();
}
} else {
typedConstraints = constraints.getMemberConstraints();
}
List<String> constraintEnumValues = new ArrayList<>();
List<KeepAnnotationPattern> annotationConstraints = new ArrayList<>();
for (KeepConstraint constraint : typedConstraints) {
String value = constraint.getEnumValue();
if (value != null) {
constraintEnumValues.add(value);
continue;
}
KeepAnnotationPattern annotationPattern = constraint.asAnnotationPattern();
if (annotationPattern != null) {
annotationConstraints.add(annotationPattern);
continue;
}
throw new Unimplemented("Missing: " + constraint.getClass().toString());
}
// The default constraints is *not* the empty set, so always write it as defined.
constraintEnumValues.sort(String::compareTo);
withNewVisitor(
visitor.visitArray(Target.constraints),
arrayVisitor ->
constraintEnumValues.forEach(
c -> arrayVisitor.visitEnum(null, Constraints.DESCRIPTOR, c)));
if (!annotationConstraints.isEmpty()) {
if (annotationConstraints.size() > 1) {
annotationConstraints.sort(
Comparator.comparing((KeepAnnotationPattern p) -> p.getNamePattern().toString())
.thenComparingInt(p -> p.includesClassRetention() ? 1 : 0)
.thenComparingInt(p -> p.includesRuntimeRetention() ? 1 : 0));
}
withNewVisitor(
visitor.visitArray(Target.constrainAnnotations),
arrayVisitor ->
annotationConstraints.forEach(
annotation -> {
withNewVisitor(
arrayVisitor.visitAnnotation(null, AnnotationPattern.DESCRIPTOR),
annoVisitor -> {
writeClassNamePattern(
annotation.getNamePattern(),
AnnotationPattern.namePattern,
annoVisitor);
assert annotation.includesClassRetention()
|| annotation.includesRuntimeRetention();
withNewVisitor(
annoVisitor.visitArray(AnnotationPattern.retention),
retentionArrayVisitor -> {
if (annotation.includesClassRetention()) {
retentionArrayVisitor.visitEnum(
null,
"Ljava/lang/annotation/RetentionPolicy;",
RetentionPolicy.CLASS.name());
}
if (annotation.includesRuntimeRetention()) {
retentionArrayVisitor.visitEnum(
null,
"Ljava/lang/annotation/RetentionPolicy;",
RetentionPolicy.RUNTIME.name());
}
});
});
}));
}
}
private void writeItemReference(AnnotationVisitor visitor, KeepItemReference itemReference) {
if (itemReference.isBindingReference()) {
KeepBindingReference bindingReference = itemReference.asBindingReference();
String bindingProperty =
bindingReference.isClassType() ? Item.classFromBinding : Item.memberFromBinding;
visitor.visit(bindingProperty, bindingReference.getName().toString());
} else {
writeItem(visitor, itemReference.asItemPattern());
}
}
private void writeItem(AnnotationVisitor itemVisitor, KeepItemPattern item) {
if (item.isClassItemPattern()) {
writeClassItem(item.asClassItemPattern(), itemVisitor);
} else {
writeMemberItem(item.asMemberItemPattern(), itemVisitor);
}
}
private void writeClassItem(
KeepClassItemPattern classItemPattern, AnnotationVisitor itemVisitor) {
writeAnnotatedBy(
Item.classAnnotatedByClassNamePattern,
classItemPattern.getAnnotatedByPattern(),
itemVisitor);
writeClassNamePattern(
classItemPattern.getClassNamePattern(), Item.classNamePattern, itemVisitor);
writeInstanceOfPattern(classItemPattern.getInstanceOfPattern(), itemVisitor);
}
private void writeInstanceOfPattern(
KeepInstanceOfPattern instanceOfPattern, AnnotationVisitor visitor) {
if (instanceOfPattern.isAny()) {
return;
}
withNewVisitor(
visitor.visitAnnotation(Item.instanceOfPattern, InstanceOfPattern.DESCRIPTOR),
v -> {
v.visit(InstanceOfPattern.inclusive, instanceOfPattern.isInclusive());
writeClassNamePattern(
instanceOfPattern.getClassNamePattern(), InstanceOfPattern.classNamePattern, v);
});
}
private void writeMemberItem(
KeepMemberItemPattern memberItemPattern, AnnotationVisitor itemVisitor) {
KeepClassItemReference classReference = memberItemPattern.getClassReference();
if (classReference.isBindingReference()) {
KeepBindingReference bindingReference = classReference.asBindingReference();
itemVisitor.visit(Item.classFromBinding, bindingReference.getName().toString());
} else {
writeClassItem(classReference.asClassItemPattern(), itemVisitor);
}
writeMember(memberItemPattern.getMemberPattern(), itemVisitor);
}
private void writeMember(KeepMemberPattern memberPattern, AnnotationVisitor targetVisitor) {
if (memberPattern.isAllMembers()) {
// Due to the empty default being a class, we need to set the kind to members.
targetVisitor.visitEnum(Target.kind, Kind.DESCRIPTOR, Kind.ONLY_MEMBERS);
} else if (memberPattern.isMethod()) {
writeMethod(memberPattern.asMethod(), targetVisitor);
} else if (memberPattern.isField()) {
writeField(memberPattern.asField(), targetVisitor);
} else {
writeGeneralMember(memberPattern, targetVisitor);
}
}
private void writeGeneralMember(KeepMemberPattern member, AnnotationVisitor targetVisitor) {
assert member.isGeneralMember();
assert !member.isField();
assert !member.isMethod();
writeAnnotatedBy(
Item.memberAnnotatedByClassNamePattern, member.getAnnotatedByPattern(), targetVisitor);
writeGeneralMemberAccessPattern(Item.memberAccess, member.getAccessPattern(), targetVisitor);
}
private void writeField(KeepFieldPattern field, AnnotationVisitor targetVisitor) {
writeAnnotatedBy(
Item.fieldAnnotatedByClassNamePattern, field.getAnnotatedByPattern(), targetVisitor);
writeFieldAccessPattern(Item.fieldAccess, field.getAccessPattern(), targetVisitor);
writeStringPattern(
field.getNamePattern().asStringPattern(), Item.fieldNamePattern, targetVisitor);
if (!field.getTypePattern().isAny()) {
writeTypePattern(field.getTypePattern().asType(), Item.fieldTypePattern, targetVisitor);
}
}
private void writeMethod(KeepMethodPattern method, AnnotationVisitor targetVisitor) {
writeAnnotatedBy(
Item.methodAnnotatedByClassNamePattern, method.getAnnotatedByPattern(), targetVisitor);
writeMethodAccessPattern(Item.methodAccess, method.getAccessPattern(), targetVisitor);
writeStringPattern(
method.getNamePattern().asStringPattern(), Item.methodNamePattern, targetVisitor);
writeMethodReturnType(method.getReturnTypePattern(), targetVisitor);
writeMethodParameters(method.getParametersPattern(), targetVisitor);
}
private void writeMethodParameters(
KeepMethodParametersPattern parametersPattern, AnnotationVisitor targetVisitor) {
if (parametersPattern.isAny()) {
return;
}
withNewVisitor(
targetVisitor.visitArray(Item.methodParameterTypePatterns),
v ->
parametersPattern
.asList()
.forEach(parameterPattern -> writeTypePattern(parameterPattern, null, v)));
}
private void writeMethodReturnType(
KeepMethodReturnTypePattern returnTypePattern, AnnotationVisitor targetVisitor) {
if (returnTypePattern.isAny()) {
return;
}
if (returnTypePattern.isVoid()) {
targetVisitor.visit(Item.methodReturnType, "void");
return;
}
assert returnTypePattern.isType();
writeTypePattern(returnTypePattern.asType(), Item.methodReturnTypePattern, targetVisitor);
}
private void writeTypePattern(
KeepTypePattern typePattern, String propertyName, AnnotationVisitor targetVisitor) {
withNewVisitor(
targetVisitor.visitAnnotation(propertyName, TypePattern.DESCRIPTOR),
v ->
typePattern.match(
() -> {
// The empty type pattern matches any type.
},
primitive -> {
if (primitive.isAny()) {
throw new Unimplemented("No support for any-primitive.");
}
v.visit(TypePattern.name, Type.getType(primitive.getDescriptor()).getClassName());
},
array -> {
v.visit(TypePattern.name, Type.getType(array.getDescriptor()).getClassName());
},
clazz -> {
writeClassNamePattern(clazz, TypePattern.classNamePattern, v);
}));
}
private void writeClassNamePattern(
KeepQualifiedClassNamePattern clazz, String propertyName, AnnotationVisitor visitor) {
withNewVisitor(
visitor.visitAnnotation(propertyName, ClassNamePattern.DESCRIPTOR),
v -> {
if (clazz.isAny()) {
// The empty pattern matches any class-name
return;
}
KeepPackagePattern packagePattern = clazz.getPackagePattern();
if (!packagePattern.isAny()) {
assert packagePattern.isExact();
v.visit(ClassNamePattern.packageName, packagePattern.getExactPackageAsString());
}
KeepUnqualfiedClassNamePattern simpleNamePattern = clazz.getNamePattern();
if (!simpleNamePattern.isAny()) {
assert simpleNamePattern.isExact();
v.visit(
ClassNamePattern.simpleName, simpleNamePattern.asExact().getExactNameAsString());
}
});
}
private void writeAnnotatedBy(
String propertyName,
OptionalPattern<KeepQualifiedClassNamePattern> annotatedByPattern,
AnnotationVisitor targetVisitor) {
if (annotatedByPattern.isPresent()) {
writeClassNamePattern(annotatedByPattern.get(), propertyName, targetVisitor);
}
}
private void writeModifierEnumValue(
ModifierPattern pattern, String value, String desc, AnnotationVisitor arrayVisitor) {
if (pattern.isAny()) {
return;
}
if (pattern.isOnlyPositive()) {
arrayVisitor.visitEnum(null, desc, value);
return;
}
assert pattern.isOnlyNegative();
arrayVisitor.visitEnum(null, desc, MemberAccess.NEGATION_PREFIX + value);
}
private void writeGeneralMemberAccessPattern(
String propertyName, KeepMemberAccessPattern accessPattern, AnnotationVisitor targetVisitor) {
internalWriteAccessPattern(
propertyName, accessPattern, targetVisitor, MemberAccess.DESCRIPTOR, v -> {});
}
private void writeFieldAccessPattern(
String propertyName, KeepFieldAccessPattern accessPattern, AnnotationVisitor targetVisitor) {
String enumDescriptor = FieldAccess.DESCRIPTOR;
internalWriteAccessPattern(
propertyName,
accessPattern,
targetVisitor,
enumDescriptor,
visitor -> {
writeModifierEnumValue(
accessPattern.getVolatilePattern(), FieldAccess.VOLATILE, enumDescriptor, visitor);
writeModifierEnumValue(
accessPattern.getTransientPattern(), FieldAccess.TRANSIENT, enumDescriptor, visitor);
});
}
private void writeMethodAccessPattern(
String propertyName, KeepMethodAccessPattern accessPattern, AnnotationVisitor targetVisitor) {
String enumDescriptor = MethodAccess.DESCRIPTOR;
internalWriteAccessPattern(
propertyName,
accessPattern,
targetVisitor,
enumDescriptor,
visitor -> {
writeModifierEnumValue(
accessPattern.getSynchronizedPattern(),
MethodAccess.SYNCHRONIZED,
enumDescriptor,
visitor);
writeModifierEnumValue(
accessPattern.getBridgePattern(), MethodAccess.BRIDGE, enumDescriptor, visitor);
writeModifierEnumValue(
accessPattern.getNativePattern(), MethodAccess.NATIVE, enumDescriptor, visitor);
writeModifierEnumValue(
accessPattern.getAbstractPattern(), MethodAccess.ABSTRACT, enumDescriptor, visitor);
writeModifierEnumValue(
accessPattern.getStrictFpPattern(), MethodAccess.STRICT_FP, enumDescriptor, visitor);
});
}
private void internalWriteAccessPattern(
String propertyName,
KeepMemberAccessPattern accessPattern,
AnnotationVisitor targetVisitor,
String enumDescriptor,
Consumer<AnnotationVisitor> typeSpecificSettings) {
if (accessPattern.isAny()) {
return;
}
withNewVisitor(
targetVisitor.visitArray(propertyName),
visitor -> {
if (!accessPattern.isAnyVisibility()) {
for (AccessVisibility visibility : accessPattern.getAllowedAccessVisibilities()) {
switch (visibility) {
case PUBLIC:
visitor.visitEnum(null, enumDescriptor, MemberAccess.PUBLIC);
break;
case PROTECTED:
visitor.visitEnum(null, enumDescriptor, MemberAccess.PROTECTED);
break;
case PACKAGE_PRIVATE:
visitor.visitEnum(null, enumDescriptor, MemberAccess.PACKAGE_PRIVATE);
break;
case PRIVATE:
visitor.visitEnum(null, enumDescriptor, MemberAccess.PRIVATE);
break;
}
}
}
writeModifierEnumValue(
accessPattern.getStaticPattern(), MemberAccess.STATIC, enumDescriptor, visitor);
writeModifierEnumValue(
accessPattern.getFinalPattern(), MemberAccess.FINAL, enumDescriptor, visitor);
writeModifierEnumValue(
accessPattern.getSyntheticPattern(), MemberAccess.SYNTHETIC, enumDescriptor, visitor);
typeSpecificSettings.accept(visitor);
});
}
}