// This class is a complete ClassVisitor with many hidden classes that do // the work of reading annotations from a class file and inserting them into // an AScene. package annotations.io.classfile; /*>>> import org.checkerframework.checker.nullness.qual.*; */ import java.io.File; import java.util.*; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; import org.objectweb.asm.TypeAnnotationVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import org.objectweb.asm.commons.EmptyVisitor; import annotations.*; import annotations.el.*; import annotations.field.*; import com.sun.tools.javac.code.TargetType; import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry; /** * A <code> ClassAnnotationSceneReader </code> is a * {@link org.objectweb.asm.ClassVisitor} that will insert all annotations it * encounters while visiting a class into a given {@link AScene}. * * The "read" in <code> ClassAnnotationSceneReader </code> refers to a class * file being read into a scene. Also see {@link ClassAnnotationSceneWriter}. * * <p> * * The proper usage of this class is to construct a * <code>ClassAnnotationSceneReader}</code> with an {@link AScene} into which * annotations should be inserted, then pass this as a * {@link org.objectweb.asm.ClassVisitor} to * {@link org.objectweb.asm.ClassReader#accept} * * <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. */ public class ClassAnnotationSceneReader extends EmptyVisitor { // general strategy: // -only "Runtime[In]visible[Type]Annotations" are supported // -use an empty visitor for everything besides annotations, fields and // methods; for those three, use a special visitor that does all the work // and inserts the annotations correctly into the specified AElement // Whether to output tracing information private static final boolean trace = false; // Whether to output error messages for unsupported cases private static final boolean strict = false; // Whether to include annotations on compiler-generated methods private final boolean ignoreBridgeMethods; // The scene into which this class will insert annotations. private final AScene scene; // The AClass that represents this class in scene. private AClass aClass; private final ClassReader cr; /** * Holds definitions we've seen so far. Maps from annotation name to * the definition itself. Maps from both the qualified name and the * unqualified name. If the unqualified name is not unique, it maps * to null and the qualified name should be used instead. */ private final Map<String, AnnotationDef> adefs = initAdefs(); private static Map<String,AnnotationDef> initAdefs() { Map<String,AnnotationDef> result = new HashMap<String,AnnotationDef>(); for (AnnotationDef ad : Annotations.standardDefs) { result.put(ad.name, ad); } return result; } /** * constructs a new <code> ClassAnnotationSceneReader </code> that will * insert all the annotations in the class that it visits into * <code> scene </code> * @param cr * * @param scene the annotation scene into which annotations this visits * will be inserted * @param ignoreBridgeMethods whether to omit annotations on * compiler-generated methods */ public ClassAnnotationSceneReader(ClassReader cr, AScene scene, boolean ignoreBridgeMethods) { this.cr = cr; this.scene = scene; this.ignoreBridgeMethods = ignoreBridgeMethods; } /** * @see org.objectweb.asm.commons.EmptyVisitor#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) { aClass = scene.classes.vivify(name.replace('/', '.')); } /** * @see org.objectweb.asm.commons.EmptyVisitor#visitAnnotation(java.lang.String, boolean) */ @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { if (trace) { System.out.printf("visitAnnotation(%s, %s) in %s (%s)%n", desc, visible, this, this.getClass()); } return visitTypeAnnotation(desc, visible, false); } /** * @see org.objectweb.asm.commons.EmptyVisitor#visitTypeAnnotation(java.lang.String, boolean, boolean) */ @Override public TypeAnnotationVisitor visitTypeAnnotation(String desc, boolean visible, boolean inCode) { if (trace) { System.out.printf("visitTypeAnnotation(%s, %s, %s); aClass=%s in %s (%s)%n", desc, inCode, visible, aClass, this, this.getClass()); } return new AnnotationSceneReader(desc, visible, aClass); } /** * @see org.objectweb.asm.commons.EmptyVisitor#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 ) { if (trace) { System.out.printf("visitField(%s, %s, %s, %s, %s) in %s (%s)%n", access, name, desc, signature, value, this, this.getClass()); } AField aField = aClass.fields.vivify(name); return new FieldAnnotationSceneReader(name, desc, signature, value, aField); } /** * @see org.objectweb.asm.commons.EmptyVisitor#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) { if (ignoreBridgeMethods && (access & Opcodes.ACC_BRIDGE) != 0) { return null; } if (trace) { System.out.printf("visitMethod(%s, %s, %s, %s, %s) in %s (%s)%n", access, name, desc, signature, exceptions, this, this.getClass()); } AMethod aMethod = aClass.methods.vivify(name+desc); return new MethodAnnotationSceneReader(name, desc, signature, aMethod); } // converts JVML format to Java format private static String classDescToName(String desc) { return desc.substring(1, desc.length() - 1).replace('/', '.'); } /////////////////////////////////////////////////////////////////////////// /// Inner classes /// // Hackish workaround for odd subclassing. @SuppressWarnings("signature") String dummyDesc = "dummy"; /* * Most of the complexity behind reading annotations from a class file into * a scene is in AnnotationSceneReader, which fully implements the * TypeAnnotationVisitor interface (and therefore also implements the * AnnotationVisitor interface). It keeps an AElement of the * element into which this should insert the annotations it visits in * a class file. Thus, constructing an AnnotationSceneReader with an * AElement of the right type is sufficient for writing out annotations * to that element, which will be done once visitEnd() is called. Note that * for when inner annotations are expected, the aElement passed in must be * of the correct form (ATypeElement, or AMethod depending on the * target type of the extended annotation). */ private class AnnotationSceneReader implements TypeAnnotationVisitor { // Implementation strategy: // For field values and enums, simply pass the information // onto annotationBuilder. // For arrays, use an ArrayAnnotationBuilder that will // properly call the right annotationBuilder methods on its visitEnd(). // For nested annotations, use a NestedAnnotationSceneReader that will // properly call the right annotationBuilder methods on its visitEnd(). // For extended information, store all arguments passed in and on // this.visitEnd(), handle all the information based on target type. // The AElement into which the annotation visited should be inserted. protected AElement aElement; // Whether or not this annotation is visible at runtime. protected boolean visible; // The AnnotationBuilder used to create this annotation. private AnnotationBuilder annotationBuilder; // since AnnotationSceneReader will work for both normal // and extended annotations, all of the following information // may or may not be present, so use a list to store // information as it is received from visitX* methods, and // correctly interpret the information in visitEnd() // note that all of these should contain 0 or 1 elements, // except for xLocations, which is actually a list private final List<Integer> xTargetTypeArgs; private final List<Integer> xIndexArgs; private final List<Integer> xLengthArgs; private final List<TypePathEntry> xLocationsArgs; private final List<Integer> xLocationLengthArgs; private final List<Integer> xOffsetArgs; private final List<Integer> xStartPcArgs; private final List<Integer> xParamIndexArgs; private final List<Integer> xBoundIndexArgs; private final List<Integer> xExceptionIndexArgs; private final List<Integer> xTypeIndexArgs; // private AnnotationDef getAnnotationDef(Object o) { // if (o instanceof AnnotationDef) { // return (AnnotationDef) o; // } else if (o instanceof String) { // return getAnnotationDef((String) o); // } else { // throw new Error(String.format("bad type %s : %s", o.getClass(), o)); // } // } @SuppressWarnings("unchecked") private AnnotationDef getAnnotationDef(String jvmlClassName) { String annoTypeName = classDescToName(jvmlClassName); // It would be better to not require the .class file to be on the // classpath, but to search for it on a path that is passed to this // program. Worry about that later. Class<? extends java.lang.annotation.Annotation> annoClass; try { annoClass = (Class<? extends java.lang.annotation.Annotation>) Class.forName(annoTypeName); } catch (ClassNotFoundException e) { System.out.printf("Could not find class: %s%n", e.getMessage()); printClasspath(); if (annoTypeName.contains("+")) { return Annotations.createValueAnnotationDef(annoTypeName, Annotations.noAnnotations, BasicAFT.forType(int.class)); } throw new Error(e); } AnnotationDef ad = AnnotationDef.fromClass(annoClass, adefs); return ad; } /* * Constructs a new AnnotationScene reader with the given description and * visibility. Calling visitEnd() will ensure that this writes out the * annotation it visits into aElement. * @param desc JVML format for the field being read, or ClassAnnotationSceneReader.dummyDesc */ public AnnotationSceneReader(String desc, boolean visible, AElement aElement) { if (trace) { System.out.printf("AnnotationSceneReader(%s, %s, %s)%n", desc, visible, aElement); } this.visible = visible; this.aElement = aElement; if (desc != dummyDesc) { // interned AnnotationDef ad = getAnnotationDef(desc); AnnotationBuilder ab = AnnotationFactory.saf.beginAnnotation(ad); if (ab == null) { throw new IllegalArgumentException("bad description: " + desc); } else { this.annotationBuilder = ab; } } // For legal annotations, and except for xLocationsArgs, these should // contain at most one element. this.xTargetTypeArgs = new ArrayList<Integer>(1); this.xIndexArgs = new ArrayList<Integer>(1); this.xLengthArgs = new ArrayList<Integer>(1); this.xLocationLengthArgs = new ArrayList<Integer>(1); this.xOffsetArgs = new ArrayList<Integer>(1); this.xStartPcArgs = new ArrayList<Integer>(1); this.xLocationsArgs = new ArrayList<TypePathEntry>(); this.xParamIndexArgs = new ArrayList<Integer>(1); this.xBoundIndexArgs = new ArrayList<Integer>(1); this.xExceptionIndexArgs = new ArrayList<Integer>(1); this.xTypeIndexArgs = new ArrayList<Integer>(1); } /* * @see org.objectweb.asm.AnnotationVisitor#visit(java.lang.String, java.lang.Object) */ @Override public void visit(String name, Object value) { if (trace) { System.out.printf("visit(%s, %s) on %s%n", name, value, this); } // BasicAFT.forType(Class) expects int.class instead of Integer.class, // and so on for all primitives. String.class is ok, since it has no // primitive type. Class<?> c = value.getClass(); if (c.equals(Boolean.class)) { c = boolean.class; } else if (c.equals(Byte.class)) { c = byte.class; } else if (c.equals(Character.class)) { c = char.class; } else if (c.equals(Short.class)) { c = short.class; } else if (c.equals(Integer.class)) { c = int.class; } else if (c.equals(Long.class)) { c = long.class; } else if (c.equals(Float.class)) { c = float.class; } else if (c.equals(Double.class)) { c = double.class; } else if (c.equals(Type.class)) { try { annotationBuilder.addScalarField(name, ClassTokenAFT.ctaft, Class.forName(((Type)value).getClassName())); } catch (ClassNotFoundException e) { throw new RuntimeException("Could not load Class for Type: " + value, e); } // Return here, otherwise the annotationBuilder would be called // twice for the same value. return; } else if (!c.equals(String.class)) { // Only possible type for value is String, in which case c is already // String.class, or array of primitive c = c.getComponentType(); ArrayBuilder arrayBuilder = annotationBuilder.beginArrayField( name, new ArrayAFT(BasicAFT.forType(c))); // value is of type c[], now add in all the elements of the array for (Object o : asList(value)) { arrayBuilder.appendElement(o); } arrayBuilder.finish(); return; } // handle everything but arrays annotationBuilder.addScalarField(name, BasicAFT.forType(c),value); } /* * Method that accepts an Object whose actual type is c[], where c is a * primitive, and returns an equivalent List<Object> that contains * the same elements as in hiddenArray. */ private List<Object> asList(Object hiddenArray) { List<Object> objects = new ArrayList<Object>(); Class<?> c = hiddenArray.getClass().getComponentType(); if (c.equals(boolean.class)) { for (boolean o : (boolean[]) hiddenArray) { objects.add(o); } } else if (c.equals(byte.class)) { for (byte o : (byte[]) hiddenArray) { objects.add(o); } } else if (c.equals(char.class)) { for (char o : (char[]) hiddenArray) { objects.add(o); } } else if (c.equals(short.class)) { for (short o : (short[]) hiddenArray) { objects.add(o); } } else if (c.equals(int.class)) { for (int o : (int[]) hiddenArray) { objects.add(o); } } else if (c.equals(long.class)) { for (long o : (long[]) hiddenArray) { objects.add(o); } } else if (c.equals(float.class)) { for (float o : (float[]) hiddenArray) { objects.add(o); } } else if (c.equals(double.class)) { for (double o : (double[]) hiddenArray) { objects.add(o); } } else { throw new RuntimeException("Array has unknown type: " + hiddenArray); } return objects; } /* * @see org.objectweb.asm.AnnotationVisitor#visitEnum(java.lang.String, java.lang.String, java.lang.String) */ @Override public void visitEnum(String name, String desc, String value) { if (trace) { System.out.printf("visitEnum(%s, %s) in %s (%s)%n", name, desc, this, this.getClass()); } annotationBuilder.addScalarField(name, new EnumAFT(desc), value); } /* * @see org.objectweb.asm.AnnotationVisitor#visitAnnotation(java.lang.String, java.lang.String) */ @Override public AnnotationVisitor visitAnnotation(String name, String desc) { if (trace) { System.out.printf("visitAnnotation(%s, %s) in %s (%s)%n", name, desc, this, this.getClass()); } return new NestedAnnotationSceneReader(this, name, desc); } /* * @see org.objectweb.asm.AnnotationVisitor#visitArray(java.lang.String) */ @Override public AnnotationVisitor visitArray(String name) { if (trace) { System.out.printf("visitArray(%s) in %s (%s)%n", name, this, this.getClass()); } ArrayAFT aaft = (ArrayAFT) annotationBuilder.fieldTypes().get(name); ScalarAFT aft = aaft.elementType; return new ArrayAnnotationSceneReader(this, name, aft); } /* * @see org.objectweb.asm.TypeAnnotationVisitor#visitXTargetType(int) */ @Override public void visitXTargetType(int target_type) { xTargetTypeArgs.add(target_type); } /* * @see org.objectweb.asm.TypeAnnotationVisitor#visitXIndex(int) */ @Override public void visitXIndex(int index) { xIndexArgs.add(index); } /* * @see org.objectweb.asm.TypeAnnotationVisitor#visitXLength(int) */ @Override public void visitXLength(int length) { xLengthArgs.add(length); } /* * @see org.objectweb.asm.TypeAnnotationVisitor#visitXLocation(TypePathEntry) */ @Override public void visitXLocation(TypePathEntry location) { xLocationsArgs.add(location); } /* * @see org.objectweb.asm.TypeAnnotationVisitor#visitXLocationLength(int) */ @Override public void visitXLocationLength(int location_length) { xLocationLengthArgs.add(location_length); } /* * @see org.objectweb.asm.TypeAnnotationVisitor#visitXOffset(int) */ @Override public void visitXOffset(int offset) { xOffsetArgs.add(offset); } @Override public void visitXNumEntries(int num_entries) { } /* * @see org.objectweb.asm.TypeAnnotationVisitor#visitXStartPc(int) */ @Override public void visitXStartPc(int start_pc) { xStartPcArgs.add(start_pc); } /* * @see org.objectweb.asm.TypeAnnotationVisitor#visitXBoundIndex(int) */ @Override public void visitXParamIndex(int param_index) { xParamIndexArgs.add(param_index); } /* * @see org.objectweb.asm.TypeAnnotationVisitor#visitXBoundIndex(int) */ @Override public void visitXBoundIndex(int bound_index) { xBoundIndexArgs.add(bound_index); } @Override public void visitXTypeIndex(int type_index) { xTypeIndexArgs.add(type_index); } @Override public void visitXExceptionIndex(int exception_index) { xExceptionIndexArgs.add(exception_index); } @Override public void visitXNameAndArgsSize() { } /* * Visits the end of the annotation, and actually writes out the * annotation into aElement. * * @see org.objectweb.asm.TypeAnnotationVisitor#visitEnd() */ @Override public void visitEnd() { if (trace) { System.out.printf("visitEnd on %s (%s)%n", this, this.getClass()); } if (xTargetTypeArgs.size() >= 1) { TargetType target = TargetType.fromTargetTypeValue(xTargetTypeArgs.get(0)); // TEMP // If the expression used to initialize a field contains annotations // on instanceofs, typecasts, or news, the extended compiler enters // those annotations on the field. If we get such an annotation and // aElement is a field, skip the annotation for now to avoid crashing. switch(target) { case FIELD: handleField(aElement); break; case LOCAL_VARIABLE: case RESOURCE_VARIABLE: handleMethodLocalVariable((AMethod) aElement); break; case NEW: if (aElement instanceof AMethod) { handleMethodObjectCreation((AMethod) aElement); } else { // TODO: in field initializers if (strict) { System.err.println("Unhandled NEW annotation for " + aElement); } } break; case METHOD_FORMAL_PARAMETER: handleMethodParameterType((AMethod) aElement); break; case METHOD_RECEIVER: handleMethodReceiver((AMethod) aElement); break; case CAST: if (aElement instanceof AMethod) { handleMethodTypecast((AMethod) aElement); } else { // TODO: in field initializers if (strict) { System.err.println("Unhandled TYPECAST annotation for " + aElement); } } break; case METHOD_RETURN: handleMethodReturnType((AMethod) aElement); break; case INSTANCEOF: if (aElement instanceof AMethod) { handleMethodInstanceOf((AMethod) aElement); } else { // TODO: in field initializers if (strict) { System.err.println("Unhandled INSTANCEOF annotation for " + aElement); } } break; case CLASS_TYPE_PARAMETER_BOUND: handleClassTypeParameterBound((AClass) aElement); break; case METHOD_TYPE_PARAMETER_BOUND: handleMethodTypeParameterBound((AMethod) aElement); break; case CLASS_EXTENDS: handleClassExtends((AClass) aElement); break; case THROWS: handleThrows((AMethod) aElement); break; case CONSTRUCTOR_REFERENCE: // TODO case METHOD_REFERENCE: handleMethodReference((AMethod) aElement); break; case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: // TODO case METHOD_REFERENCE_TYPE_ARGUMENT: handleReferenceTypeArgument((AMethod) aElement); break; case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: // TODO case METHOD_INVOCATION_TYPE_ARGUMENT: handleCallTypeArgument((AMethod) aElement); break; case METHOD_TYPE_PARAMETER: handleMethodTypeParameter((AMethod) aElement); break; case CLASS_TYPE_PARAMETER: handleClassTypeParameter((AClass) aElement); break; // TODO: ensure all cases covered. default: // Rather than throw an error here, since a declaration annotation // is being used as an extended annotation, just make the // annotation and place it in the given aElement as usual. // aElement.tlAnnotationsHere.add(makeAnnotation()); Annotation a = makeAnnotation(); aElement.tlAnnotationsHere.add(a); } } else { // This is not an extended annotation visitor, so just // make the annotation and place it in the given AElement, // possibly moving it to a type annotation location instead. Annotation a = makeAnnotation(); if (a.def.isTypeAnnotation() && (aElement instanceof AMethod)) { AMethod m = (AMethod) aElement; m.returnType.tlAnnotationsHere.add(a); // There is not currently a separate location for field/parameter // type annotations; they are mixed in with the declaration // annotations. This should be fixed in the future. // Also, fields/parameters are just represented as AElement. // } else if (a.def.isTypeAnnotation() && (aElement instanceof AField)) { } else { aElement.tlAnnotationsHere.add(a); } } } // The following are utility methods to facilitate creating all the // necessary data structures in the scene library. /* * Returns an annotation, ready to be placed into the scene, from * the information visited. */ public Annotation makeAnnotation() { return annotationBuilder.finish(); } /* * Returns a LocalLocation for this annotation. */ private LocalLocation makeLocalLocation() { int index = xIndexArgs.get(0); int length = xLengthArgs.get(0); int start = xStartPcArgs.get(0); return new LocalLocation(index, start, length); } /* * Returns an InnerTypeLocation for this annotation. */ private InnerTypeLocation makeInnerTypeLocation() { return new InnerTypeLocation(xLocationsArgs); } /* * Returns the offset for this annotation. */ private RelativeLocation makeOffset(boolean needTypeIndex) { int offset = xOffsetArgs.get(0); int typeIndex = needTypeIndex ? xTypeIndexArgs.get(0) : 0; return RelativeLocation.createOffset(offset, typeIndex); } /* * Returns the index for this annotation. */ /* private int makeIndex() { return xIndexArgs.get(0); } */ /* * Returns the bound location for this annotation. */ private BoundLocation makeTypeParameterLocation() { if (!xParamIndexArgs.isEmpty()) { return new BoundLocation(xParamIndexArgs.get(0), -1); } else { if (strict) { System.err.println("makeTypeParameterLocation with empty xParamIndexArgs!"); } return new BoundLocation(Integer.MAX_VALUE, -1); } } /* * Returns the bound location for this annotation. * @see #makeTypeParameterLocation() */ private BoundLocation makeBoundLocation() { // TODO: Give up on unbounded wildcards for now! if (!xParamIndexArgs.isEmpty()) { return new BoundLocation(xParamIndexArgs.get(0), xBoundIndexArgs.get(0)); } else { if (strict) { System.err.println("makeBoundLocation with empty xParamIndexArgs!"); } return new BoundLocation(Integer.MAX_VALUE, Integer.MAX_VALUE); } } private TypeIndexLocation makeTypeIndexLocation() { return new TypeIndexLocation(xTypeIndexArgs.get(0)); } // TODO: makeExceptionIndexLocation? /* * Creates the inner annotation on aElement.innerTypes. */ private void handleField(AElement aElement) { if (xLocationsArgs.isEmpty()) { // TODO: resolve issue once classfile format is finalized if (aElement instanceof AClass) { // handleFieldOnClass((AClass) aElement); if (strict) { System.err.println("Unhandled FIELD annotation for " + aElement); } } else if (aElement instanceof ATypeElement) { aElement.tlAnnotationsHere.add(makeAnnotation()); } else { throw new RuntimeException("Unknown FIELD aElement: " + aElement); } } else { // TODO: resolve issue once classfile format is finalized if (aElement instanceof AClass) { // handleFieldGenericArrayOnClass((AClass) aElement); if (strict) { System.err.println("Unhandled FIELD_COMPONENT annotation for " + aElement); } } else if (aElement instanceof ATypeElement) { ATypeElement aTypeElement = (ATypeElement) aElement; aTypeElement .innerTypes.vivify(makeInnerTypeLocation()). tlAnnotationsHere.add(makeAnnotation()); } else { throw new RuntimeException("Unknown FIELD_COMPONENT: " + aElement); } } } /* * Creates the method receiver annotation on aMethod. */ private void handleMethodReceiver(AMethod aMethod) { if (xLocationsArgs.isEmpty()) { aMethod.receiver.type .tlAnnotationsHere.add(makeAnnotation()); } else { aMethod.receiver.type .innerTypes.vivify(makeInnerTypeLocation()) .tlAnnotationsHere.add(makeAnnotation()); } } /* * Creates the local variable annotation on aMethod. */ private void handleMethodLocalVariable(AMethod aMethod) { if (xLocationsArgs.isEmpty()) { aMethod.body.locals.vivify(makeLocalLocation()) .tlAnnotationsHere.add(makeAnnotation()); } else { aMethod.body.locals.vivify(makeLocalLocation()) .type.innerTypes.vivify(makeInnerTypeLocation()) .tlAnnotationsHere.add(makeAnnotation()); } } /* * Creates the object creation annotation on aMethod. */ private void handleMethodObjectCreation(AMethod aMethod) { if (xLocationsArgs.isEmpty()) { aMethod.body.news.vivify(makeOffset(false)) .tlAnnotationsHere.add(makeAnnotation()); } else { aMethod.body.news.vivify(makeOffset(false)) .innerTypes.vivify(makeInnerTypeLocation()) .tlAnnotationsHere.add(makeAnnotation()); } } private int makeParamIndex() { return xParamIndexArgs.get(0); } /* * Creates the method parameter type generic/array annotation on aMethod. */ private void handleMethodParameterType(AMethod aMethod) { if (xLocationsArgs.isEmpty()) { aMethod.parameters.vivify(makeParamIndex()).type.tlAnnotationsHere.add(makeAnnotation()); } else { aMethod.parameters.vivify(makeParamIndex()).type.innerTypes.vivify( makeInnerTypeLocation()).tlAnnotationsHere.add(makeAnnotation()); } } /* * Creates the typecast annotation on aMethod. */ private void handleMethodTypecast(AMethod aMethod) { if (xLocationsArgs.isEmpty()) { aMethod.body.typecasts.vivify(makeOffset(true)) .tlAnnotationsHere.add(makeAnnotation()); } else { aMethod.body.typecasts.vivify(makeOffset(true)) .innerTypes.vivify(makeInnerTypeLocation()) .tlAnnotationsHere.add(makeAnnotation()); } } /* * Creates the method return type generic/array annotation on aMethod. */ private void handleMethodReturnType(AMethod aMethod) { // TODO: why is this traced and not other stuff? if (trace) { System.out.printf("handleMethodReturnType(%s)%n", aMethod); } if (xLocationsArgs.isEmpty()) { aMethod.returnType .tlAnnotationsHere.add(makeAnnotation()); } else { aMethod.returnType .innerTypes.vivify(makeInnerTypeLocation()) .tlAnnotationsHere.add(makeAnnotation()); } } /* * Creates the method instance of annotation on aMethod. */ private void handleMethodInstanceOf(AMethod aMethod) { if (xLocationsArgs.isEmpty()) { aMethod.body.instanceofs.vivify(makeOffset(false)) .tlAnnotationsHere.add(makeAnnotation()); } else { aMethod.body.typecasts.vivify(makeOffset(false)) .innerTypes.vivify(makeInnerTypeLocation()) .tlAnnotationsHere.add(makeAnnotation()); } } /* * Creates the class type parameter bound annotation on aClass. */ private void handleClassTypeParameter(AClass aClass) { aClass.bounds.vivify(makeTypeParameterLocation()) .tlAnnotationsHere.add(makeAnnotation()); } /* * Creates the class type parameter bound annotation on aClass. */ private void handleClassTypeParameterBound(AClass aClass) { if (xLocationsArgs.isEmpty()) { aClass.bounds.vivify(makeBoundLocation()) .tlAnnotationsHere.add(makeAnnotation()); } else { aClass.bounds.vivify(makeBoundLocation()) .innerTypes.vivify(makeInnerTypeLocation()) .tlAnnotationsHere.add(makeAnnotation()); } } /* * Creates the class type parameter bound annotation on aClass. */ private void handleMethodTypeParameterBound(AMethod aMethod) { if (xLocationsArgs.isEmpty()) { aMethod.bounds.vivify(makeBoundLocation()) .tlAnnotationsHere.add(makeAnnotation()); } else { aMethod.bounds.vivify(makeBoundLocation()) .innerTypes.vivify(makeInnerTypeLocation()) .tlAnnotationsHere.add(makeAnnotation()); } } private void handleClassExtends(AClass aClass) { if (xLocationsArgs.isEmpty()) { aClass.extendsImplements.vivify(makeTypeIndexLocation()) .tlAnnotationsHere.add(makeAnnotation()); } else { aClass.extendsImplements.vivify(makeTypeIndexLocation()) .innerTypes.vivify(makeInnerTypeLocation()) .tlAnnotationsHere.add(makeAnnotation()); } } private void handleThrows(AMethod aMethod) { aMethod.throwsException.vivify(makeTypeIndexLocation()) .tlAnnotationsHere.add(makeAnnotation()); } private void handleNewTypeArgument(AMethod aMethod) { if (xLocationsArgs.isEmpty()) { // aMethod.news.vivify(makeOffset()).innerTypes.vivify(); // makeInnerTypeLocation()).tlAnnotationsHere.add(makeAnnotation()); if (strict) { System.err.println("Unhandled handleNewTypeArgument on aMethod: " + aMethod); } } else { // if (strict) { System.err.println("Unhandled handleNewTypeArgumentGenericArray on aMethod: " + aMethod); } } } private void handleMethodReference(AMethod aMethod) { if (xLocationsArgs.isEmpty()) { aMethod.body.refs.vivify(makeOffset(false)) .tlAnnotationsHere.add(makeAnnotation()); } else { aMethod.body.refs.vivify(makeOffset(false)) .innerTypes.vivify(makeInnerTypeLocation()) .tlAnnotationsHere.add(makeAnnotation()); } } private void handleReferenceTypeArgument(AMethod aMethod) { if (xLocationsArgs.isEmpty()) { aMethod.body.refs.vivify(makeOffset(true)) .tlAnnotationsHere.add(makeAnnotation()); } else { aMethod.body.refs.vivify(makeOffset(true)) .innerTypes.vivify(makeInnerTypeLocation()) .tlAnnotationsHere.add(makeAnnotation()); } } private void handleCallTypeArgument(AMethod aMethod) { if (xLocationsArgs.isEmpty()) { aMethod.body.calls.vivify(makeOffset(true)) .tlAnnotationsHere.add(makeAnnotation()); } else { aMethod.body.calls.vivify(makeOffset(true)) .innerTypes.vivify(makeInnerTypeLocation()) .tlAnnotationsHere.add(makeAnnotation()); } } private void handleMethodTypeParameter(AMethod aMethod) { // TODO: throw new RuntimeException("METHOD_TYPE_PARAMETER: to do"); } /* * Hook for NestedAnnotationSceneReader; overridden by * ArrayAnnotationSceneReader to add an array element instead of a field */ void supplySubannotation(String fieldName, Annotation annotation) { annotationBuilder.addScalarField(fieldName, new AnnotationAFT(annotation.def()), annotation); } @Override public String toString() { return String.format("(AnnotationSceneReader: %s %s %s)", aElement, visible, annotationBuilder); } } /* * A NestedAnnotationSceneReader is an AnnotationSceneReader * that will read in an entire annotation on a field (of type annotation) * of its parent, and once it has fully visited that annotation, it will * call its parent annotation builder to include that field, so after * its parent constructs and returns this as an AnnotationVisitor * (visitAnnotation()), it no longer needs to worry about that field. */ private class NestedAnnotationSceneReader extends AnnotationSceneReader { private final AnnotationSceneReader parent; private final String name; // private final String desc; public NestedAnnotationSceneReader(AnnotationSceneReader parent, String name, String desc) { super(desc, parent.visible, parent.aElement); if (trace) { System.out.printf("NestedAnnotationSceneReader(%s, %s, %s)%n", parent, name, desc); } this.parent = parent; this.name = name; // this.desc = desc; } @Override public void visitEnd() { // Do not call super, as that already builds the annotation, causing an exception. // super.visitEnd(); if (trace) { System.out.printf("visitEnd on %s (%s)%n", this, this.getClass()); } Annotation a = super.makeAnnotation(); parent.supplySubannotation(name, a); } } /* * An ArrayAnnotationSceneReader is an AnnotationSceneReader * that reads all elements of an array field * of its parent, and once it has fully visited the array, it will * call its parent annotation builder to include that array, so after * its parent constructs and returns this as an AnnotationVisitor * (visitArray()), it no longer needs to worry about that array. * * Note that by specification of AnnotationVisitor.visitArray(), the only * methods that should be called on this are visit(String name, Object value) * and visitEnd(). */ // An AnnotationSceneReader reads an annotation. An // ArrayAnnotationSceneReader reads an arbitrary array field, but not an // entire annotation. So why is ArrayAnnotationSceneReader a subclass of // AnnotationSceneReader? Pass ClassAnnotationSceneReader.dummyDesc // in the superclass constructor to // disable superclass behaviors that would otherwise cause trouble. private class ArrayAnnotationSceneReader extends AnnotationSceneReader { private final AnnotationSceneReader parent; private ArrayBuilder arrayBuilder; // private ScalarAFT elementType; private final String arrayName; // The element type may be unknown when this is called. // But AnnotationSceneReader expects to know the element type. public ArrayAnnotationSceneReader(AnnotationSceneReader parent, String fieldName, AnnotationFieldType eltType) { super(dummyDesc, parent.visible, parent.aElement); if (trace) { System.out.printf("ArrayAnnotationSceneReader(%s, %s)%n", parent, fieldName); } this.parent = parent; this.arrayName = fieldName; this.arrayBuilder = null; } private void prepareForElement(ScalarAFT elementType) { if (trace) { System.out.printf("prepareForElement(%s) in %s (%s)%n", elementType, this, this.getClass()); } assert elementType != null; // but, does this happen when reading from classfile? if (arrayBuilder == null) { // this.elementType = elementType; arrayBuilder = parent.annotationBuilder.beginArrayField(arrayName, new ArrayAFT(elementType)); } } // There are only so many different array types that are permitted in // an annotation. (I'm not sure how relevant that is here.) @Override public void visit(String name, Object value) { if (trace) { System.out.printf("visit(%s, %s) (%s) in %s (%s)%n", name, value, value.getClass(), this, this.getClass()); } ScalarAFT aft; if (value.getClass().equals(org.objectweb.asm.Type.class)) { // What if it's an annotation? aft = ClassTokenAFT.ctaft; try { value = Class.forName(((org.objectweb.asm.Type) value).getClassName()); } catch (ClassNotFoundException e) { throw new RuntimeException("Could not load Class for Type: " + value, e); } } else { Class<?> vc = value.getClass(); aft = BasicAFT.forType(vc); // or: aft = (ScalarAFT) AnnotationFieldType.fromClass(vc, null); } assert aft != null; prepareForElement(aft); assert arrayBuilder != null; arrayBuilder.appendElement(value); } @Override public void visitEnum(String name, String desc, String value) { if (trace) { System.out.printf("visitEnum(%s, %s, %s) in %s (%s)%n", name, desc, value, this, this.getClass()); } prepareForElement(new EnumAFT(classDescToName(desc))); assert arrayBuilder != null; arrayBuilder.appendElement(value); } @Override public AnnotationVisitor visitArray(String name) { throw new AssertionError("Multidimensional array in annotation!"); } @Override public AnnotationVisitor visitAnnotation(String name, String desc) { if (trace) { System.out.printf("visitAnnotation(%s, %s) in %s (%s)%n", name, desc, this, this.getClass()); } // The NASR will regurgitate the name we pass here when it calls // supplySubannotation. Since we ignore the name there, it doesn't // matter what name we pass here. return new NestedAnnotationSceneReader(this, name, desc); } @Override public void visitEnd() { if (trace) { System.out.printf("visitEnd on %s (%s)%n", this, this.getClass()); } if (arrayBuilder != null) { arrayBuilder.finish(); } else { // This was a zero-element array parent.annotationBuilder.addEmptyArrayField(arrayName); } } @Override void supplySubannotation(String fieldName, Annotation annotation) { prepareForElement(new AnnotationAFT(annotation.def())); assert arrayBuilder != null; arrayBuilder.appendElement(annotation); } } /* * A FieldAnnotationSceneReader is a FieldVisitor that only cares about * visiting [extended]annotations. Attributes are ignored and visitEnd() has * no effect. An AnnotationSceneReader is returned for declaration and type * AnnotationVisitors. The AnnotationSceneReaders have a reference to * an ATypeElement that this is visiting, and they will write out * all the information to that ATypeElement after visiting each annotation. */ private class FieldAnnotationSceneReader extends EmptyVisitor implements FieldVisitor { /* private final String name; private final String desc; private final String signature; private final Object value; */ private final AElement aField; public FieldAnnotationSceneReader( String name, String desc, String signature, Object value, AElement aField) { /* this.name = name; this.desc = desc; this.signature = signature; this.value = value; */ this.aField = aField; } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { if (trace) { System.out.printf("visitAnnotation(%s, %s) in %s (%s)%n", desc, visible, this, this.getClass()); } return new AnnotationSceneReader(desc, visible, aField); } @Override public TypeAnnotationVisitor visitTypeAnnotation(String desc, boolean visible, boolean inCode) { if (trace) { System.out.printf("visitTypeAnnotation(%s, %s, %s); aField=%s, aField.type=%s in %s (%s)%n", desc, visible, inCode, aField, aField.type, this, this.getClass()); } return new AnnotationSceneReader(desc, visible, aField.type); } } /* * Similarly to FieldAnnotationSceneReader, this is a MethodVisitor that * only cares about visiting [extended]annotations. Attributes other than * BootstrapMethods are ignored, all code is ignored, and visitEnd() has no * effect. An AnnotationSceneReader * is returned for declaration and type AnnotationVisitors. The * AnnotationSceneReaders have a reference to an AMethod that this is * visiting, and they will write out all the information to that * AMethod after visiting each annotation. */ private class MethodAnnotationSceneReader extends EmptyVisitor implements MethodVisitor { // private final String name; // private final String desc; // private final String signature; private final AElement aMethod; public MethodAnnotationSceneReader(String name, String desc, String signature, AElement aMethod) { // this.name = name; // this.desc = desc; // this.signature = signature; this.aMethod = aMethod; } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { if (trace) { System.out.printf("visitAnnotation(%s, %s) in %s (%s)%n", desc, visible, this, this.getClass()); } return visitTypeAnnotation(desc, visible, false); } @Override public TypeAnnotationVisitor visitTypeAnnotation(String desc, boolean visible, boolean inCode) { if (trace) { System.out.printf("visitTypeAnnotation(%s, %s) method=%s in %s (%s)%n", desc, visible, inCode, aMethod, this, this.getClass()); } return new AnnotationSceneReader(desc, visible, aMethod); } @Override public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { if (trace) { System.out.printf("visitParameterAnnotation(%s, %s, %s) in %s (%s)%n", parameter, desc, visible, this, this.getClass()); } return new AnnotationSceneReader(desc, visible, ((AMethod) aMethod).parameters.vivify(parameter)); } @Override public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { // TODO! } // TODO: visit code! } public static void printClasspath() { System.out.println("\nClasspath:"); StringTokenizer tokenizer = new StringTokenizer(System.getProperty("java.class.path"), File.pathSeparator); while (tokenizer.hasMoreTokens()) { System.out.println(" " + tokenizer.nextToken()); } } }