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