// This class is a complete ClassVisitor with many hidden classes that do
// the work of parsing an AScene and inserting them into a class file, as
// the original class file is being read.
package annotations.io.classfile;
/*>>>
import org.checkerframework.checker.nullness.qual.*;
*/
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.lang.annotation.RetentionPolicy;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Handle;
import org.objectweb.asm.TypeAnnotationVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.EmptyVisitor;
import com.sun.tools.javac.code.TargetType;
import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry;
import annotations.*;
import annotations.el.*;
import annotations.field.*;
/**
* A ClassAnnotationSceneWriter is a {@link org.objectweb.asm.ClassVisitor}
* that can be used to write a class file that is the combination of an
* existing class file and annotations in an {@link AScene}. The "write"
* in <code> ClassAnnotationSceneWriter </code> refers to a class file
* being rewritten with information from a scene. Also see {@link
* ClassAnnotationSceneReader}.
*
* <p>
*
* The proper usage of this class is to construct a
* <code>ClassAnnotationSceneWriter</code> with a {@link AScene} that
* already contains all its annotations, pass this as a {@link
* org.objectweb.asm.ClassVisitor} to {@link
* org.objectweb.asm.ClassReader#accept}, and then obtain the resulting
* class, ready to be written to a file, with {@link #toByteArray}. </p>
*
* <p>
*
* All other methods are intended to be called only by
* {@link org.objectweb.asm.ClassReader#accept},
* and should not be called anywhere else, due to the order in which
* {@link org.objectweb.asm.ClassVisitor} methods should be called.
*
* <p>
*
* Throughout this class, "scene" refers to the {@link AScene} this class is
* merging into a class file.
*/
public class ClassAnnotationSceneWriter extends ClassAdapter {
// Strategy for interleaving the necessary calls to visit annotations
// from scene into the parsing done by ClassReader
// (the difficulty is that the entire call sequence to every data structure
// to visit annotations is in ClassReader, which should not be modified
// by this library):
//
// A ClassAnnotationSceneWriter is a ClassAdapter around a ClassWriter.
// - To visit the class' annotations in the scene, right before the code for
// ClassWriter.visit{InnerClass, Field, Method, End} is called,
// ensure that all extended annotations in the scene are visited once.
// - To visit every field's annotations,
// ClassAnnotationSceneWriter.visitField() returns a
// FieldAnnotationSceneWriter that in a similar fashion makes sure
// that each of that field's annotations is visited once on the call
// to visitEnd();
// - To visit every method's annotations,
// ClassAnnotationSceneWriter.visitMethod() returns a
// MethodAnnotationSceneWriter that visits all of that method's
// annotations in the scene at the first call of visit{Code, End}.
//
// Whether to output error messages for unsupported cases
private static final boolean strict = false;
// None of these classes fields should be null, except for aClass, which
// can't be vivified until the first visit() is called.
/**
* The scene from which to get additional annotations.
*/
private final AScene scene;
/**
* The representation of this class in the scene.
*/
private AClass aClass;
/**
* A list of annotations on this class that this has already visited
* in the class file.
*/
private final List<String> existingClassAnnotations;
/**
* Whether or not this has visited the corresponding annotations in scene.
*/
private boolean hasVisitedClassAnnotationsInScene;
/**
* Whether or not to overwrite existing annotations on the same element
* in a class file if a similar annotation is found in scene.
*/
private final boolean overwrite;
private final Map<String, Set<Integer>> dynamicConstructors;
private final Map<String, Set<Integer>> lambdaExpressions;
private ClassReader cr = null;
/**
* Constructs a new <code> ClassAnnotationSceneWriter </code> that will
* insert all the annotations in <code> scene </code> into the class that
* it visits. <code> scene </code> must be an {@link AScene} over the
* class that this will visit.
*
* @param cr the reader for the class being modified
* @param scene the annotation scene containing annotations to be inserted
* into the class this visits
*/
public ClassAnnotationSceneWriter(ClassReader cr, AScene scene, boolean overwrite) {
super(new ClassWriter(cr, false));
this.scene = scene;
this.hasVisitedClassAnnotationsInScene = false;
this.aClass = null;
this.existingClassAnnotations = new ArrayList<String>();
this.overwrite = overwrite;
this.dynamicConstructors = new HashMap<String, Set<Integer>>();
this.lambdaExpressions = new HashMap<String, Set<Integer>>();
this.cr = cr;
}
/**
* Returns a byte array that represents the resulting class file
* from merging all the annotations in the scene into the class file
* this has visited. This method may only be called once this has already
* completely visited a class, which is done by calling
* {@link org.objectweb.asm.ClassReader#accept}.
*
* @return a byte array of the merged class file
*/
public byte[] toByteArray() {
return ((ClassWriter) cv).toByteArray();
}
/**
* {@inheritDoc}
* @see org.objectweb.asm.ClassAdapter#visit(int, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[])
*/
@Override
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
cr.accept(new MethodCodeIndexer(), false);
super.visit(version, access, name, signature, superName, interfaces);
// class files store fully quantified class names with '/' instead of '.'
name = name.replace('/', '.');
aClass = scene.classes.vivify(name);
}
/**
* {@inheritDoc}
* @see org.objectweb.asm.ClassAdapter#visitInnerClass(java.lang.String, java.lang.String, java.lang.String, int)
*/
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access ) {
ensureVisitSceneClassAnnotations();
super.visitInnerClass(name, outerName, innerName, access);
}
/**
* {@inheritDoc}
* @see org.objectweb.asm.ClassAdapter#visitField(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object)
*/
@Override
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value) {
ensureVisitSceneClassAnnotations();
// FieldAnnotationSceneWriter ensures that the field visits all
// its annotations in the scene.
return new FieldAnnotationSceneWriter(name,
super.visitField(access, name, desc, signature, value));
}
/**
* {@inheritDoc}
* @see org.objectweb.asm.ClassAdapter#visitMethod(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[])
*/
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
ensureVisitSceneClassAnnotations();
// MethodAnnotationSceneWriter ensures that the method visits all
// its annotations in the scene.
// MethodAdapter is used here only for getting around an unsound
// "optimization" in ClassReader.
return new MethodAdapter(new MethodAnnotationSceneWriter(name, desc,
super.visitMethod(access, name, desc, signature, exceptions)));
}
/**
* {@inheritDoc}
* @see org.objectweb.asm.ClassAdapter#visitEnd()
*/
@Override
public void visitEnd() {
ensureVisitSceneClassAnnotations();
super.visitEnd();
}
/**
* {@inheritDoc}
* @see org.objectweb.asm.ClassAdapter#visitAnnotation(java.lang.String, boolean)
*/
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
existingClassAnnotations.add(desc);
// If annotation exists in scene, and in overwrite mode,
// return empty visitor, since annotation from scene will be visited later.
if (aClass.lookup(classDescToName(desc)) != null
&& overwrite) {
return new EmptyVisitor();
}
return super.visitAnnotation(desc, visible);
}
/**
* {@inheritDoc}
* @see org.objectweb.asm.ClassAdapter#visitTypeAnnotation(java.lang.String, boolean, boolean)
*/
@Override
public TypeAnnotationVisitor visitTypeAnnotation(String desc, boolean visible, boolean inCode) {
existingClassAnnotations.add(desc);
// If annotation exists in scene, and in overwrite mode,
// return empty visitor, annotation from scene will be visited later.
if (aClass.lookup(classDescToName(desc)) != null
&& overwrite) {
return new EmptyVisitor();
}
return new SafeTypeAnnotationVisitor(
super.visitTypeAnnotation(desc, visible, inCode));
}
/**
* Have this class visit the annotations in scene if and only if it has not
* already visited them.
*/
private void ensureVisitSceneClassAnnotations() {
if (!hasVisitedClassAnnotationsInScene) {
hasVisitedClassAnnotationsInScene = true;
for (Annotation tla : aClass.tlAnnotationsHere) {
// If not in overwrite mode and annotation already exists in classfile,
// ignore tla.
if ((!overwrite) && existingClassAnnotations.contains(name(tla))) {
continue;
}
AnnotationVisitor av = visitAnnotation(tla);
visitFields(av, tla);
av.visitEnd();
}
// do type parameter bound annotations
for (Map.Entry<BoundLocation, ATypeElement> e :
aClass.bounds.entrySet()) {
BoundLocation bloc = e.getKey();
ATypeElement bound = e.getValue();
for (Annotation tla : bound.tlAnnotationsHere) {
TypeAnnotationVisitor xav = visitTypeAnnotation(tla);
if (bloc.boundIndex == -1) {
visitTargetType(xav, TargetType.CLASS_TYPE_PARAMETER);
visitBound(xav, bloc);
} else {
visitTargetType(xav, TargetType.CLASS_TYPE_PARAMETER_BOUND);
visitBound(xav, bloc);
}
visitLocations(xav, InnerTypeLocation.EMPTY_INNER_TYPE_LOCATION);
visitFields(xav, tla);
xav.visitEnd();
}
for (Map.Entry<InnerTypeLocation, ATypeElement> e2 :
bound.innerTypes.entrySet()) {
InnerTypeLocation itloc = e2.getKey();
ATypeElement innerType = e2.getValue();
for (Annotation tla : innerType.tlAnnotationsHere) {
TypeAnnotationVisitor xav = visitTypeAnnotation(tla);
visitTargetType(xav, TargetType.CLASS_TYPE_PARAMETER_BOUND);
visitBound(xav, bloc);
visitLocations(xav, itloc);
visitFields(xav, tla);
xav.visitEnd();
}
}
}
for (Map.Entry<TypeIndexLocation, ATypeElement> e : aClass.extendsImplements.entrySet()) {
TypeIndexLocation idx = e.getKey();
ATypeElement aty = e.getValue();
// TODO: How is this annotation written back out?
if (strict) { System.err.println("ClassAnnotationSceneWriter: ignoring Extends/Implements annotation " + idx + " with type: " + aty); }
}
}
}
/**
* The following methods are utility methods for accessing
* information useful to asm from scene-library data structures.
*
* @return true iff tla is visible at runtime
*/
private static boolean isRuntimeRetention(Annotation tla) {
if (tla.def.retention() == null) {
return false; // TODO: temporary
}
return tla.def.retention().equals(RetentionPolicy.RUNTIME);
}
/**
* Returns the name of the annotation in the top level.
*/
private static String name(Annotation tla) {
return tla.def().name;
}
/**
* Wraps the given class name in a class descriptor.
*/
private static String classNameToDesc(String name) {
return "L" + name.replace('.', '/') + ";";
}
/**
* Unwraps the class name from the given class descriptor.
*/
private static String classDescToName(String desc) {
return desc.substring(1, desc.length() - 1).replace('/', '.');
}
/**
* Returns an AnnotationVisitor over the given top-level annotation.
*/
private AnnotationVisitor visitAnnotation(Annotation tla) {
return super.visitAnnotation(classNameToDesc(name(tla)), isRuntimeRetention(tla));
}
/**
* Returns an TypeAnnotationVisitor over the given top-level annotation.
*/
private TypeAnnotationVisitor visitTypeAnnotation(Annotation tla) {
return super.visitTypeAnnotation(classNameToDesc(name(tla)), isRuntimeRetention(tla), false);
}
/**
* Has tav visit the fields in the given annotation.
*/
private void visitFields(TypeAnnotationVisitor tav, Annotation a) {
tav.visitXNameAndArgsSize();
visitFields((AnnotationVisitor) tav, a);
}
/**
* Has av visit the fields in the given annotation.
* This method is necessary even with
* visitFields(AnnotationVisitor, Annotation)
* because a Annotation cannot be created from the Annotation
* specified to be available from the Annotation object for subannotations.
*/
private void visitFields(AnnotationVisitor av, Annotation a) {
for (String fieldName : a.def().fieldTypes.keySet()) {
Object value = a.getFieldValue(fieldName);
if (value == null) {
// hopefully a field with a default value
continue;
}
AnnotationFieldType aft = a.def().fieldTypes.get(fieldName);
if (value instanceof Annotation) {
AnnotationVisitor nav = av.visitAnnotation(fieldName, classDescToName(a.def().name));
visitFields(nav, (Annotation) a);
nav.visitEnd();
} else if (value instanceof List) {
// In order to visit an array, the AnnotationVisitor returned by
// visitArray needs to visit each element, and by specification
// the name should be null for each element.
AnnotationVisitor aav = av.visitArray(fieldName);
aft = ((ArrayAFT) aft).elementType;
for (Object o : (List<?>)value) {
if (aft instanceof EnumAFT) {
aav.visitEnum(null, ((EnumAFT) aft).typeName, o.toString());
} else {
aav.visit(null, o);
}
}
aav.visitEnd();
} else if (aft instanceof EnumAFT) {
av.visitEnum(fieldName, ((EnumAFT) aft).typeName, value.toString());
} else if (aft instanceof ClassTokenAFT) {
av.visit(fieldName, org.objectweb.asm.Type.getType((Class<?>)value));
} else {
// everything else is a string
av.visit(fieldName, value);
}
}
}
/**
* Has xav visit the given target type.
*/
private void visitTargetType(TypeAnnotationVisitor xav, TargetType t) {
xav.visitXTargetType(t.targetTypeValue());
}
/**
* Have xav visit the location length and all locations in loc.
*/
private void visitLocations(TypeAnnotationVisitor xav, InnerTypeLocation loc) {
List<TypePathEntry> location = loc.location;
xav.visitXLocationLength(location.size());
for (TypePathEntry l : location) {
xav.visitXLocation(l);
}
}
/**
* Has xav visit the local varialbe information in loc.
*/
private void visitLocalVar(TypeAnnotationVisitor xav, LocalLocation loc) {
xav.visitXNumEntries(1);
xav.visitXStartPc(loc.scopeStart);
xav.visitXLength(loc.scopeLength);
xav.visitXIndex(loc.index);
}
/**
* Has xav visit the offset.
*/
private void visitOffset(TypeAnnotationVisitor xav, int offset) {
xav.visitXOffset(offset);
}
private void visitParameterIndex(TypeAnnotationVisitor xav, int index) {
xav.visitXParamIndex(index);
}
private void visitTypeIndex(TypeAnnotationVisitor xav, int index) {
xav.visitXTypeIndex(index);
}
/**
* Has xav visit the type parameter bound information in loc.
*/
private void visitBound(TypeAnnotationVisitor xav, BoundLocation loc) {
xav.visitXParamIndex(loc.paramIndex);
if (loc.boundIndex != -1) {
xav.visitXBoundIndex(loc.boundIndex);
}
}
/**
* A FieldAnnotationSceneWriter is a wrapper class around a FieldVisitor that
* delegates all calls to its internal FieldVisitor, and on a call to
* visitEnd(), also has its internal FieldVisitor visit all the
* corresponding field annotations in scene.
*/
private class FieldAnnotationSceneWriter implements FieldVisitor {
// After being constructed, none of these fields should be null.
/**
* Internal FieldVisitor all calls are delegated to.
*/
private final FieldVisitor fv;
/**
* List of all annotations this has already visited.
*/
private final List<String> existingFieldAnnotations;
/**
* The AElement that represents this field in the AScene the
* class is visiting.
*/
private final AElement aField;
/**
* Constructs a new FieldAnnotationSceneWriter with the given name that
* wraps the given FieldVisitor.
*/
public FieldAnnotationSceneWriter(String name, FieldVisitor fv) {
this.fv = fv;
this.existingFieldAnnotations = new ArrayList<String>();
this.aField = aClass.fields.vivify(name);
}
/**
* {@inheritDoc}
* @see org.objectweb.asm.FieldVisitor#visitAnnotation(java.lang.String, boolean)
*/
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
existingFieldAnnotations.add(desc);
// If annotation exists in scene, and in overwrite mode,
// return empty visitor, annotation from scene will be visited later.
if (aField.lookup(classDescToName(desc)) != null
&& overwrite)
return new EmptyVisitor();
return fv.visitAnnotation(desc, visible);
}
/**
* {@inheritDoc}
* @see org.objectweb.asm.FieldVisitor#visitTypeAnnotation(java.lang.String, boolean)
*/
@Override
public TypeAnnotationVisitor visitTypeAnnotation(String desc, boolean visible, boolean inCode) {
existingFieldAnnotations.add(desc);
// If annotation exists in scene, and in overwrite mode,
// return empty visitor, annotation from scene will be visited later.
if (aField.lookup(classDescToName(desc)) != null
&& overwrite)
return new EmptyVisitor();
return new SafeTypeAnnotationVisitor(
fv.visitTypeAnnotation(desc, visible, inCode));
}
/** {@inheritDoc}
* @see org.objectweb.asm.FieldVisitor#visitAttribute(org.objectweb.asm.Attribute)
*/
@Override
public void visitAttribute(Attribute attr) {
fv.visitAttribute(attr);
}
/**
* Tells this to visit the end of the field in the class file,
* and also ensures that this visits all its annotations in the scene.
*
* @see org.objectweb.asm.FieldVisitor#visitEnd()
*/
@Override
public void visitEnd() {
ensureVisitSceneFieldAnnotations();
fv.visitEnd();
}
/**
* Has this visit the annotations on the corresponding field in scene.
*/
private void ensureVisitSceneFieldAnnotations() {
// First do declaration annotations on a field.
for (Annotation tla : aField.tlAnnotationsHere) {
if ((!overwrite) && existingFieldAnnotations.contains(name(tla))) {
continue;
}
AnnotationVisitor av = fv.visitAnnotation(classNameToDesc(name(tla)), isRuntimeRetention(tla));
visitFields(av, tla);
av.visitEnd();
}
// Then do the type annotations on the field
for (Annotation tla : aField.type.tlAnnotationsHere) {
if ((!overwrite) && existingFieldAnnotations.contains(name(tla))) {
continue;
}
TypeAnnotationVisitor av = fv.visitTypeAnnotation(classNameToDesc(name(tla)), isRuntimeRetention(tla), false);
visitTargetType(av, TargetType.FIELD);
visitLocations(av, InnerTypeLocation.EMPTY_INNER_TYPE_LOCATION);
visitFields(av, tla);
av.visitEnd();
}
// Now do field generics/arrays.
for (Map.Entry<InnerTypeLocation, ATypeElement> fieldInnerEntry :
aField.type.innerTypes.entrySet()) {
for (Annotation tla : fieldInnerEntry.getValue().tlAnnotationsHere) {
if ((!overwrite) && existingFieldAnnotations.contains(name(tla))) {
continue;
}
TypeAnnotationVisitor xav =
fv.visitTypeAnnotation(classNameToDesc(name(tla)), isRuntimeRetention(tla), false);
visitTargetType(xav, TargetType.FIELD);
visitLocations(xav, fieldInnerEntry.getKey());
visitFields(xav, tla);
xav.visitEnd();
}
}
}
}
/**
* A MethodAnnotationSceneWriter is to a MethodAdapter exactly
* what ClassAnnotationSceneWriter is to a ClassAdapter:
* it will ensure that the MethodVisitor behind MethodAdapter
* visits each of the extended annotations in scene in the correct
* sequence, before any of the later data is visited.
*/
private class MethodAnnotationSceneWriter extends MethodAdapter {
// basic strategy:
// ensureMethodVisitSceneAnnotation will be called, if it has not already
// been called, at the beginning of visitCode, visitEnd
/**
* The AMethod that represents this method in scene.
*/
private final AMethod aMethod;
/**
* Whether or not this has visit the method's annotations in scene.
*/
private boolean hasVisitedMethodAnnotations;
/**
* The existing annotations this method has visited.
*/
private final List<String> existingMethodAnnotations;
/**
* Constructs a new MethodAnnotationSceneWriter with the given name and
* description that wraps around the given MethodVisitor.
*
* @param name the name of the method, as in "foo"
* @param desc the method signature minus the name,
* as in "(Ljava/lang/String)V"
* @param mv the method visitor to wrap around
*/
MethodAnnotationSceneWriter(String name, String desc, MethodVisitor mv) {
super(mv);
this.hasVisitedMethodAnnotations = false;
this.aMethod = aClass.methods.vivify(name+desc);
this.existingMethodAnnotations = new ArrayList<String>();
}
/**
* {@inheritDoc}
* @see org.objectweb.asm.MethodAdapter#visitCode()
*/
@Override
public void visitCode() {
ensureVisitSceneMethodAnnotations();
super.visitCode();
}
/**
* {@inheritDoc}
* @see org.objectweb.asm.MethodAdapter#visitEnd()
*/
@Override
public void visitEnd() {
ensureVisitSceneMethodAnnotations();
super.visitEnd();
}
/**
* {@inheritDoc}
* @see org.objectweb.asm.MethodAdapter#visitAnnotation(java.lang.String, boolean)
*/
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
existingMethodAnnotations.add(desc);
// If annotation exists in scene, and in overwrite mode,
// return empty visitor, annotation from scene will be visited later.
if (shouldSkipExisting(classDescToName(desc))) {
return new EmptyVisitor();
}
return super.visitAnnotation(desc, visible);
}
/**
* {@inheritDoc}
* @see org.objectweb.asm.MethodAdapter#visitTypeAnnotation(java.lang.String, boolean)
*/
@Override
public TypeAnnotationVisitor visitTypeAnnotation(String desc, boolean visible, boolean inCode) {
existingMethodAnnotations.add(desc);
// If annotation exists in scene, and in overwrite mode,
// return empty visitor, annotation from scene will be visited later.
if (shouldSkipExisting(classDescToName(desc))) {
return new EmptyVisitor();
}
return new SafeTypeAnnotationVisitor(
super.visitTypeAnnotation(desc, visible, inCode));
}
/**
* Returns true iff the annotation in tla should not be written because it
* already exists in this method's annotations.
*/
private boolean shouldSkip(Annotation tla) {
return ((!overwrite) && existingMethodAnnotations.contains(name(tla)));
}
/**
* Returns true iff the annotation with the given name should not be written
* because it already exists in this method's annotations.
*/
private boolean shouldSkipExisting(String name) {
return ((!overwrite)
&& aMethod.lookup(name) != null);
}
/**
* Has this visit the annotation in tla, and returns the resulting visitor.
*/
private AnnotationVisitor visitAnnotation(Annotation tla) {
return super.visitAnnotation(classNameToDesc(name(tla)), isRuntimeRetention(tla));
}
/**
* Has this visit the extended annotation in tla and returns the
* resulting visitor.
*/
private TypeAnnotationVisitor
visitTypeAnnotation(Annotation tla, boolean inCode) {
return super.visitTypeAnnotation(classNameToDesc(name(tla)), isRuntimeRetention(tla), inCode);
}
/**
* Has this visit the parameter annotation in tla and returns the
* resulting visitor.
*/
private AnnotationVisitor visitParameterAnnotation(Annotation tla, int index) {
return super.visitParameterAnnotation(index, classNameToDesc(name(tla)), isRuntimeRetention(tla));
}
/**
* Has this visit the declaration annotation and the type annotations on the return type.
*/
private void ensureVisitMethodDeclarationAnnotations() {
// Annotations on method declaration.
for (Annotation tla : aMethod.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
AnnotationVisitor av = visitAnnotation(tla);
visitFields(av, tla);
av.visitEnd();
}
}
/**
* Has this visit the declaration annotations and the type annotations on the return type.
*/
private void ensureVisitReturnTypeAnnotations() {
// Standard annotations on return type.
for (Annotation tla : aMethod.returnType.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor av = visitTypeAnnotation(tla, false);
visitTargetType(av, TargetType.METHOD_RETURN);
visitLocations(av, InnerTypeLocation.EMPTY_INNER_TYPE_LOCATION);
visitFields(av, tla);
av.visitEnd();
}
// Now do generic/array information on return type
for (Map.Entry<InnerTypeLocation, ATypeElement> e :
aMethod.returnType.innerTypes.entrySet()) {
InnerTypeLocation loc = e.getKey();
ATypeElement innerType = e.getValue();
for (Annotation tla : innerType.tlAnnotationsHere) {
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, false);
visitTargetType(xav, TargetType.METHOD_RETURN);
// information for raw type (return type)
// (none)
// information for generic/array (on return type)
visitLocations(xav, loc);
visitFields(xav, tla);
xav.visitEnd();
}
}
}
/**
* Has this visit the annotations on type parameter bounds.
*/
private void ensureVisitTypeParameterBoundAnnotations() {
for (Map.Entry<BoundLocation, ATypeElement> e :
aMethod.bounds.entrySet()) {
BoundLocation bloc = e.getKey();
ATypeElement bound = e.getValue();
for (Annotation tla : bound.tlAnnotationsHere) {
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, false);
if (bloc.boundIndex == -1) {
visitTargetType(xav, TargetType.METHOD_TYPE_PARAMETER);
visitBound(xav, bloc);
} else {
visitTargetType(xav, TargetType.METHOD_TYPE_PARAMETER_BOUND);
visitBound(xav, bloc);
}
visitLocations(xav, InnerTypeLocation.EMPTY_INNER_TYPE_LOCATION);
visitFields(xav, tla);
xav.visitEnd();
}
for (Map.Entry<InnerTypeLocation, ATypeElement> e2 :
bound.innerTypes.entrySet()) {
InnerTypeLocation itloc = e2.getKey();
ATypeElement innerType = e2.getValue();
for (Annotation tla : innerType.tlAnnotationsHere) {
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, false);
visitTargetType(xav, TargetType.METHOD_TYPE_PARAMETER_BOUND);
visitBound(xav, bloc);
visitLocations(xav, itloc);
visitFields(xav, tla);
xav.visitEnd();
}
}
}
}
/**
* Has this visit the annotations on local variables in this method.
*/
private void ensureVisitLocalVariablesAnnotations() {
for (Map.Entry<LocalLocation, AField> entry :
aMethod.body.locals.entrySet()) {
LocalLocation localLocation = entry.getKey();
AElement aLocation = entry.getValue();
for (Annotation tla : aLocation.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, true);
visitTargetType(xav, TargetType.LOCAL_VARIABLE);
visitLocalVar(xav, localLocation);
visitLocations(xav, InnerTypeLocation.EMPTY_INNER_TYPE_LOCATION);
visitFields(xav, tla);
xav.visitEnd();
}
// now do annotations on inner type of aLocation (local variable)
for (Map.Entry<InnerTypeLocation, ATypeElement> e :
aLocation.type.innerTypes.entrySet()) {
InnerTypeLocation localVariableLocation = e.getKey();
ATypeElement aInnerType = e.getValue();
for (Annotation tla : aInnerType.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, true);
visitTargetType(xav, TargetType.LOCAL_VARIABLE);
// information for raw type (local variable)
visitLocalVar(xav, localLocation);
// information for generic/array (on local variable)
visitLocations(xav, localVariableLocation);
visitFields(xav, tla);
xav.visitEnd();
}
}
}
}
/**
* Has this visit the object creation (new) annotations on this method.
*/
private void ensureVisitObjectCreationAnnotations() {
for (Map.Entry<RelativeLocation, ATypeElement> entry :
aMethod.body.news.entrySet()) {
if (!entry.getKey().isBytecodeOffset()) {
// if the RelativeLocation is a source index, we cannot insert it
// into bytecode
// TODO: output a warning or translate
if (strict) { System.err.println("ClassAnnotationSceneWriter.ensureVisitObjectCreationAnnotation: no bytecode offset found!"); }
}
int offset = entry.getKey().offset;
ATypeElement aNew = entry.getValue();
for (Annotation tla : aNew.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, true);
visitTargetType(xav, TargetType.NEW);
visitOffset(xav, offset);
visitLocations(xav, InnerTypeLocation.EMPTY_INNER_TYPE_LOCATION);
visitFields(xav, tla);
xav.visitEnd();
}
// now do inner annotations on aNew (object creation)
for (Map.Entry<InnerTypeLocation, ATypeElement> e :
aNew.innerTypes.entrySet()) {
InnerTypeLocation aNewLocation = e.getKey();
ATypeElement aInnerType = e.getValue();
for (Annotation tla : aInnerType.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, true);
visitTargetType(xav, TargetType.NEW);
// information for raw type (object creation)
visitOffset(xav, offset);
// information for generic/array (on object creation)
visitLocations(xav, aNewLocation);
visitFields(xav, tla);
xav.visitEnd();
}
}
}
}
/**
* Has this visit the parameter annotations on this method.
*/
private void ensureVisitParameterAnnotations() {
for (Map.Entry<Integer, AField> entry :
aMethod.parameters.entrySet()) {
AField aParameter = entry.getValue();
int index = entry.getKey();
// First visit declaration annotations on the parameter
for (Annotation tla : aParameter.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
AnnotationVisitor av = visitParameterAnnotation(tla, index);
visitFields(av, tla);
av.visitEnd();
}
// Then handle type annotations targeting the parameter
for (Annotation tla : aParameter.type.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor av = visitTypeAnnotation(tla, false);
visitTargetType(av, TargetType.METHOD_FORMAL_PARAMETER);
visitParameterIndex(av, index);
visitLocations(av, InnerTypeLocation.EMPTY_INNER_TYPE_LOCATION);
visitFields(av, tla);
av.visitEnd();
}
// now handle inner annotations on aParameter (parameter)
for (Map.Entry<InnerTypeLocation, ATypeElement> e :
aParameter.type.innerTypes.entrySet()) {
InnerTypeLocation aParameterLocation = e.getKey();
ATypeElement aInnerType = e.getValue();
for (Annotation tla : aInnerType.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, false);
visitTargetType(xav,
TargetType.METHOD_FORMAL_PARAMETER);
// information for raw type (parameter)
// (none)
// information for generic/array (on parameter)
visitParameterIndex(xav, index);
visitLocations(xav, aParameterLocation);
visitFields(xav, tla);
xav.visitEnd();
}
}
}
}
/**
* Has this visit the receiver annotations on this method.
*/
private void ensureVisitReceiverAnnotations() {
AField aReceiver = aMethod.receiver;
// for (Annotation tla : aReceiver.tlAnnotationsHere) {
// if (shouldSkip(tla)) continue;
//
// AnnotationVisitor av = visitTypeAnnotation(tla, false); // FIXME
// visitTargetType(av, TargetType.METHOD_RECEIVER);
// visitLocations(av, InnerTypeLocation.EMPTY_INNER_TYPE_LOCATION);
// visitFields(av, tla);
// av.visitEnd();
// }
for (Annotation tla : aReceiver.type.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, false);
visitTargetType(xav, TargetType.METHOD_RECEIVER);
visitLocations(xav, InnerTypeLocation.EMPTY_INNER_TYPE_LOCATION);
visitFields(xav, tla);
xav.visitEnd();
}
// now do inner annotations of aReceiver
for (Map.Entry<InnerTypeLocation, ATypeElement> e :
aReceiver.type.innerTypes.entrySet()) {
InnerTypeLocation aReceiverLocation = e.getKey();
ATypeElement aInnerType = e.getValue();
for (Annotation tla : aInnerType.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, false);
visitTargetType(xav, TargetType.METHOD_RECEIVER);
// information for generic/array (on receiver)
visitLocations(xav, aReceiverLocation);
visitFields(xav, tla);
xav.visitEnd();
}
}
}
/**
* Has this visit the typecast annotations on this method.
*/
private void ensureVisitTypecastAnnotations() {
for (Map.Entry<RelativeLocation, ATypeElement> entry :
aMethod.body.typecasts.entrySet()) {
if (!entry.getKey().isBytecodeOffset()) {
// if the RelativeLocation is a source index, we cannot insert it
// into bytecode
// TODO: output a warning or translate
if (strict) { System.err.println("ClassAnnotationSceneWriter.ensureVisitTypecastAnnotation: no bytecode offset found!"); }
}
int offset = entry.getKey().offset;
int typeIndex = entry.getKey().type_index;
ATypeElement aTypecast = entry.getValue();
for (Annotation tla : aTypecast.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, true);
visitTargetType(xav, TargetType.CAST);
visitOffset(xav, offset);
visitTypeIndex(xav, typeIndex);
visitLocations(xav, InnerTypeLocation.EMPTY_INNER_TYPE_LOCATION);
visitFields(xav, tla);
xav.visitEnd();
}
// now do inner annotations of aTypecast (typecast)
for (Map.Entry<InnerTypeLocation, ATypeElement> e :
aTypecast.innerTypes.entrySet()) {
InnerTypeLocation aTypecastLocation = e.getKey();
ATypeElement aInnerType = e.getValue();
for (Annotation tla : aInnerType.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, true);
visitTargetType(xav, TargetType.CAST);
// information for raw type (typecast)
visitOffset(xav, offset);
visitTypeIndex(xav, typeIndex);
// information for generic/array (on typecast)
visitLocations(xav, aTypecastLocation);
visitFields(xav, tla);
xav.visitEnd();
}
}
}
}
/**
* Has this visit the typetest annotations on this method.
*/
private void ensureVisitTypeTestAnnotations() {
for (Map.Entry<RelativeLocation, ATypeElement> entry :
aMethod.body.instanceofs.entrySet()) {
if (!entry.getKey().isBytecodeOffset()) {
// if the RelativeLocation is a source index, we cannot insert it
// into bytecode
// TODO: output a warning or translate
if (strict) { System.err.println("ClassAnnotationSceneWriter.ensureVisitTypeTestAnnotation: no bytecode offset found!"); }
}
int offset = entry.getKey().offset;
ATypeElement aTypeTest = entry.getValue();
for (Annotation tla : aTypeTest.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, true);
visitTargetType(xav, TargetType.INSTANCEOF);
visitOffset(xav, offset);
visitLocations(xav, InnerTypeLocation.EMPTY_INNER_TYPE_LOCATION);
visitFields(xav, tla);
xav.visitEnd();
}
// now do inner annotations of aTypeTest (typetest)
for (Map.Entry<InnerTypeLocation, ATypeElement> e :
aTypeTest.innerTypes.entrySet()) {
InnerTypeLocation aTypeTestLocation = e.getKey();
AElement aInnerType = e.getValue();
for (Annotation tla : aInnerType.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, true);
visitTargetType(xav, TargetType.INSTANCEOF);
// information for raw type (typetest)
visitOffset(xav, offset);
// information for generic/array (on typetest)
visitLocations(xav, aTypeTestLocation);
visitFields(xav, tla);
xav.visitEnd();
}
}
}
}
private void ensureVisitLambdaExpressionAnnotations() {
for (Map.Entry<RelativeLocation, AMethod> entry :
aMethod.body.funs.entrySet()) {
if (!entry.getKey().isBytecodeOffset()) {
// if the RelativeLocation is a source index, we cannot insert it
// into bytecode
// TODO: output a warning or translate
if (strict) { System.err.println("ClassAnnotationSceneWriter.ensureMemberReferenceAnnotations: no bytecode offset found!"); }
continue;
}
// int offset = entry.getKey().offset;
// int typeIndex = entry.getKey().type_index;
AMethod aLambda = entry.getValue();
for (Map.Entry<Integer, AField> e0 : aLambda.parameters.entrySet()) {
AField aParameter = e0.getValue();
int index = e0.getKey();
for (Annotation tla : aParameter.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
AnnotationVisitor av = visitParameterAnnotation(tla, index);
visitFields(av, tla);
av.visitEnd();
}
for (Annotation tla : aParameter.type.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, false);
visitTargetType(xav, TargetType.METHOD_FORMAL_PARAMETER);
// visitOffset(xav, offset);
// visitTypeIndex(xav, typeIndex);
visitParameterIndex(xav, index);
visitLocations(xav, InnerTypeLocation.EMPTY_INNER_TYPE_LOCATION);
visitFields(xav, tla);
xav.visitEnd();
}
for (Map.Entry<InnerTypeLocation, ATypeElement> e1 :
aParameter.type.innerTypes.entrySet()) {
InnerTypeLocation aParameterLocation = e1.getKey();
ATypeElement aInnerType = e1.getValue();
for (Annotation tla : aInnerType.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, false);
visitTargetType(xav, TargetType.METHOD_FORMAL_PARAMETER);
// visitOffset(xav, offset);
// visitTypeIndex(xav, typeIndex);
visitParameterIndex(xav, index);
visitLocations(xav, aParameterLocation);
visitFields(xav, tla);
xav.visitEnd();
}
}
}
}
}
private void ensureVisitMemberReferenceAnnotations() {
for (Map.Entry<RelativeLocation, ATypeElement> entry :
aMethod.body.refs.entrySet()) {
if (!entry.getKey().isBytecodeOffset()) {
// if the RelativeLocation is a source index, we cannot insert it
// into bytecode
// TODO: output a warning or translate
if (strict) { System.err.println("ClassAnnotationSceneWriter.ensureMemberReferenceAnnotations: no bytecode offset found!"); }
continue;
}
int offset = entry.getKey().offset;
int typeIndex = entry.getKey().type_index;
ATypeElement aTypeArg = entry.getValue();
Set<Integer> lset = lambdaExpressions.get(aMethod.methodName);
if (lset.contains(offset)) { continue; } // something's wrong
Set<Integer> cset = dynamicConstructors.get(aMethod.methodName);
TargetType tt = cset != null && cset.contains(offset)
? TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
: TargetType.METHOD_REFERENCE_TYPE_ARGUMENT;
for (Annotation tla : aTypeArg.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, true);
visitTargetType(xav, tt);
visitOffset(xav, offset);
visitTypeIndex(xav, typeIndex);
visitLocations(xav, InnerTypeLocation.EMPTY_INNER_TYPE_LOCATION);
visitFields(xav, tla);
xav.visitEnd();
}
// now do inner annotations of member reference
for (Map.Entry<InnerTypeLocation, ATypeElement> e :
aTypeArg.innerTypes.entrySet()) {
InnerTypeLocation aTypeArgLocation = e.getKey();
AElement aInnerType = e.getValue();
for (Annotation tla : aInnerType.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, true);
visitTargetType(xav, tt);
visitOffset(xav, offset);
visitTypeIndex(xav, typeIndex);
visitLocations(xav, aTypeArgLocation);
visitFields(xav, tla);
xav.visitEnd();
}
}
}
}
private void ensureVisitMethodInvocationAnnotations() {
for (Map.Entry<RelativeLocation, ATypeElement>
entry : aMethod.body.calls.entrySet()) {
if (!entry.getKey().isBytecodeOffset()) {
// if the RelativeLocation is a source index, we cannot insert it
// into bytecode
// TODO: output a warning or translate
if (strict) { System.err.println("ClassAnnotationSceneWriter.ensureVisitMethodInvocationAnnotations: no bytecode offset found!"); }
}
int offset = entry.getKey().offset;
int typeIndex = entry.getKey().type_index;
ATypeElement aCall = entry.getValue();
Set<Integer> cset = dynamicConstructors.get(aMethod.methodName);
TargetType tt = cset != null && cset.contains(offset)
? TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
: TargetType.METHOD_INVOCATION_TYPE_ARGUMENT;
for (Annotation tla : aCall.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, true);
visitTargetType(xav, tt);
visitOffset(xav, offset);
visitTypeIndex(xav, typeIndex);
visitLocations(xav, InnerTypeLocation.EMPTY_INNER_TYPE_LOCATION);
visitFields(xav, tla);
xav.visitEnd();
}
// now do inner annotations of call
for (Map.Entry<InnerTypeLocation, ATypeElement> e :
aCall.innerTypes.entrySet()) {
InnerTypeLocation aCallLocation = e.getKey();
AElement aInnerType = e.getValue();
for (Annotation tla : aInnerType.tlAnnotationsHere) {
if (shouldSkip(tla)) {
continue;
}
TypeAnnotationVisitor xav = visitTypeAnnotation(tla, true);
visitTargetType(xav, TargetType.INSTANCEOF);
visitOffset(xav, offset);
visitTypeIndex(xav, typeIndex);
visitLocations(xav, aCallLocation);
visitFields(xav, tla);
xav.visitEnd();
}
}
}
}
/**
* Have this method visit the annotations in scene if and only if
* it has not visited them before.
*/
private void ensureVisitSceneMethodAnnotations() {
if (!hasVisitedMethodAnnotations) {
hasVisitedMethodAnnotations = true;
ensureVisitMethodDeclarationAnnotations();
ensureVisitReturnTypeAnnotations();
// Now iterate through method's locals, news, parameter, receiver,
// typecasts, and type argument annotations, which will all be
// extended annotations
ensureVisitTypeParameterBoundAnnotations();
ensureVisitLocalVariablesAnnotations();
ensureVisitObjectCreationAnnotations();
ensureVisitParameterAnnotations();
ensureVisitReceiverAnnotations();
ensureVisitTypecastAnnotations();
ensureVisitTypeTestAnnotations();
ensureVisitLambdaExpressionAnnotations();
ensureVisitMemberReferenceAnnotations();
ensureVisitMethodInvocationAnnotations();
// TODO: throw clauses?!
// TODO: catch clauses!?
}
}
}
class MethodCodeIndexer extends EmptyVisitor {
private int codeStart = 0;
Set<Integer> constrs; // distinguishes constructors from methods
Set<Integer> lambdas; // distinguishes lambda exprs from member refs
MethodCodeIndexer() {
int fieldCount;
// const pool size is (not lowest) upper bound of string length
codeStart = cr.header + 6;
codeStart += 2 + 2 * cr.readUnsignedShort(codeStart);
fieldCount = cr.readUnsignedShort(codeStart);
codeStart += 2;
while (--fieldCount >= 0) {
int attrCount = cr.readUnsignedShort(codeStart + 6);
codeStart += 8;
while (--attrCount >= 0) {
codeStart += 6 + cr.readInt(codeStart + 2);
}
}
codeStart += 2;
}
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
}
@Override
public void visitSource(String source, String debug) {}
@Override
public void visitOuterClass(String owner, String name, String desc) {}
@Override
public void visitInnerClass(String name, String outerName,
String innerName, int access) {
}
@Override
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value) {
return null;
}
@Override
public MethodVisitor visitMethod(int access,
String name, String desc, String signature, String[] exceptions) {
String methodDescription = name + desc;
constrs = dynamicConstructors.get(methodDescription);
if (constrs == null) {
constrs = new TreeSet<Integer>();
dynamicConstructors.put(methodDescription, constrs);
}
lambdas = lambdaExpressions.get(methodDescription);
if (lambdas == null) {
lambdas = new TreeSet<Integer>();
lambdaExpressions.put(methodDescription, lambdas);
}
return new MethodAdapter(
new MethodCodeOffsetAdapter(cr, new EmptyVisitor(), codeStart) {
@Override
public void visitInvokeDynamicInsn(String name,
String desc, Handle bsm, Object... bsmArgs) {
String methodName = ((Handle) bsmArgs[1]).getName();
int off = getMethodCodeOffset();
if ("<init>".equals(methodName)) {
constrs.add(off);
} else {
int ix = methodName.lastIndexOf('.');
if (ix >= 0) {
methodName = methodName.substring(ix+1);
}
if (methodName.startsWith("lambda$")) {
lambdas.add(off);
}
}
super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
}
});
}
}
}