// 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); } }); } } }