package org.checkerframework.javacutil; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.Map; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.AnnotationValueVisitor; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Attribute.TypeCompound; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.TargetType; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.TypeAnnotationPosition; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.tree.JCTree.JCLambda; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Pair; /** * A collection of helper methods related to type annotation handling. * * @see AnnotationUtils */ public class TypeAnnotationUtils { // Class cannot be instantiated. private TypeAnnotationUtils() { throw new AssertionError("Class TypeAnnotationUtils cannot be instantiated."); } /** * Check whether a TypeCompound is contained in a list of TypeCompounds. * * @param list The input list of TypeCompounds. * @param tc The TypeCompound to find. * @return true, iff a TypeCompound equal to tc is contained in list. */ public static boolean isTypeCompoundContained(Types types, List<TypeCompound> list, TypeCompound tc) { for (Attribute.TypeCompound rawat : list) { if (rawat.type.tsym.name.contentEquals(tc.type.tsym.name) && // TODO: in previous line, it would be nicer to use reference equality: // rawat.type == tc.type && // or at least "isSameType": // types.isSameType(rawat.type, tc.type) && // but each fails in some cases. rawat.values.equals(tc.values) && isSameTAPosition(rawat.position, tc.position)) { return true; } } return false; } /** * Compare two TypeAnnotationPositions for equality. * * @param p1 The first position. * @param p2 The second position. * @return true, iff the two positions are equal. */ public static boolean isSameTAPosition(TypeAnnotationPosition p1, TypeAnnotationPosition p2) { if (p1.isValidOffset == p2.isValidOffset && p1.bound_index == p2.bound_index && p1.exception_index == p2.exception_index && p1.location.equals(p2.location) && Arrays.equals(p1.lvarIndex, p2.lvarIndex) && Arrays.equals(p1.lvarLength, p2.lvarLength) && Arrays.equals(p1.lvarOffset, p2.lvarOffset) && p1.offset == p2.offset && p1.onLambda == p2.onLambda && p1.parameter_index == p2.parameter_index && p1.pos == p2.pos && p1.type == p2.type && p1.type_index == p2.type_index) { return true; } return false; } /** * Returns a newly created Attribute.Compound corresponding to an * argument AnnotationMirror. * * @param am an AnnotationMirror, which may be part of an AST or an internally * created subclass. * @return a new Attribute.Compound corresponding to the AnnotationMirror */ public static Attribute.Compound createCompoundFromAnnotationMirror(ProcessingEnvironment env, AnnotationMirror am) { // Create a new Attribute to match the AnnotationMirror. List<Pair<Symbol.MethodSymbol, Attribute>> values = List.nil(); for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : am.getElementValues().entrySet()) { Attribute attribute = attributeFromAnnotationValue(env, entry.getKey(), entry.getValue()); values = values.append(new Pair<>((Symbol.MethodSymbol)entry.getKey(), attribute)); } return new Attribute.Compound((Type.ClassType)am.getAnnotationType(), values); } /** * Returns a newly created Attribute.TypeCompound corresponding to an * argument AnnotationMirror. * * @param am an AnnotationMirror, which may be part of an AST or an internally * created subclass. * @param tapos the type annotation position to use. * @return a new Attribute.TypeCompound corresponding to the AnnotationMirror */ public static Attribute.TypeCompound createTypeCompoundFromAnnotationMirror(ProcessingEnvironment env, AnnotationMirror am, TypeAnnotationPosition tapos) { // Create a new Attribute to match the AnnotationMirror. List<Pair<Symbol.MethodSymbol, Attribute>> values = List.nil(); for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : am.getElementValues().entrySet()) { Attribute attribute = attributeFromAnnotationValue(env, entry.getKey(), entry.getValue()); values = values.append(new Pair<>((Symbol.MethodSymbol)entry.getKey(), attribute)); } return new Attribute.TypeCompound((Type.ClassType)am.getAnnotationType(), values, tapos); } /** * Returns a newly created Attribute corresponding to an argument * AnnotationValue. * * @param meth the ExecutableElement that is assigned the value, needed for empty arrays. * @param av an AnnotationValue, which may be part of an AST or an internally * created subclass. * @return a new Attribute corresponding to the AnnotationValue */ public static Attribute attributeFromAnnotationValue(ProcessingEnvironment env, ExecutableElement meth, AnnotationValue av) { return av.accept(new AttributeCreator(env, meth), null); } private static class AttributeCreator implements AnnotationValueVisitor<Attribute, Void> { private final ProcessingEnvironment processingEnv; private final Types modelTypes; private final Elements elements; private final com.sun.tools.javac.code.Types javacTypes; private final ExecutableElement meth; public AttributeCreator(ProcessingEnvironment env, ExecutableElement meth) { this.processingEnv = env; Context context = ((JavacProcessingEnvironment)env).getContext(); this.elements = env.getElementUtils(); this.modelTypes = env.getTypeUtils(); this.javacTypes = com.sun.tools.javac.code.Types.instance(context); this.meth = meth; } @Override public Attribute visit(AnnotationValue av, Void p) { return av.accept(this, p); } @Override public Attribute visit(AnnotationValue av) { return visit(av, null); } @Override public Attribute visitBoolean(boolean b, Void p) { TypeMirror booleanType = modelTypes.getPrimitiveType(TypeKind.BOOLEAN); return new Attribute.Constant((Type) booleanType, b ? 1 : 0); } @Override public Attribute visitByte(byte b, Void p) { TypeMirror byteType = modelTypes.getPrimitiveType(TypeKind.BYTE); return new Attribute.Constant((Type)byteType, b); } @Override public Attribute visitChar(char c, Void p) { TypeMirror charType = modelTypes.getPrimitiveType(TypeKind.CHAR); return new Attribute.Constant((Type)charType, c); } @Override public Attribute visitDouble(double d, Void p) { TypeMirror doubleType = modelTypes.getPrimitiveType(TypeKind.DOUBLE); return new Attribute.Constant((Type)doubleType, d); } @Override public Attribute visitFloat(float f, Void p) { TypeMirror floatType = modelTypes.getPrimitiveType(TypeKind.FLOAT); return new Attribute.Constant((Type)floatType, f); } @Override public Attribute visitInt(int i, Void p) { TypeMirror intType = modelTypes.getPrimitiveType(TypeKind.INT); return new Attribute.Constant((Type)intType, i); } @Override public Attribute visitLong(long i, Void p) { TypeMirror longType = modelTypes.getPrimitiveType(TypeKind.LONG); return new Attribute.Constant((Type)longType, i); } @Override public Attribute visitShort(short s, Void p) { TypeMirror shortType = modelTypes.getPrimitiveType(TypeKind.SHORT); return new Attribute.Constant((Type)shortType, s); } @Override public Attribute visitString(String s, Void p) { TypeMirror stringType = elements.getTypeElement("java.lang.String").asType(); return new Attribute.Constant((Type)stringType, s); } @Override public Attribute visitType(TypeMirror t, Void p) { if (t instanceof Type) { return new Attribute.Class(javacTypes, (Type)t); } assert false : "Unexpected type of TypeMirror: " + t.getClass(); return null; } @Override public Attribute visitEnumConstant(VariableElement c, Void p) { if (c instanceof Symbol.VarSymbol) { Symbol.VarSymbol sym = (Symbol.VarSymbol) c; if (sym.getKind() == ElementKind.ENUM_CONSTANT) { return new Attribute.Enum(sym.type, sym); } } assert false : "Unexpected type of VariableElement: " + c.getClass(); return null; } @Override public Attribute visitAnnotation(AnnotationMirror a, Void p) { return createCompoundFromAnnotationMirror(processingEnv, a); } @Override public Attribute visitArray(java.util.List<? extends AnnotationValue> vals, Void p) { if (!vals.isEmpty()) { List<Attribute> valAttrs = List.nil(); for (AnnotationValue av : vals) { valAttrs = valAttrs.append(av.accept(this, p)); } ArrayType arrayType = modelTypes.getArrayType(valAttrs.get(0).type); return new Attribute.Array((Type)arrayType, valAttrs); } else { return new Attribute.Array((Type) meth.getReturnType(), List.<Attribute>nil()); } } @Override public Attribute visitUnknown(AnnotationValue av, Void p) { assert false : "Unexpected type of AnnotationValue: " + av.getClass(); return null; } } /** * An interface to abstract a Java 8 and a Java 9 version of how * to get a RET reference. * These methods must then be implemented using reflection in order to * compile in either setting. * Note that we cannot use lambda for this as long as we want to * support Java 7. */ interface Call8or9<RET> { RET call8() throws Throwable; RET call9() throws Throwable; } /** * Use the SourceVersion to decide whether to call the Java 8 or Java 9 version. * Catch all exceptions and abort if one occurs - the reflection code should * never break once fully debugged. * * @param ver The SourceVersion to decide what API to use. * @param tc The TAPCall abstraction to encapsulate two methods. * @return The created TypeAnnotationPosition. */ private static <RET> RET call8or9(Call8or9<RET> tc) { try { boolean hasNine; try { hasNine = SourceVersion.valueOf("RELEASE_9") != null; } catch(IllegalArgumentException iae) { hasNine = false; } if (hasNine) { return tc.call9(); } else { boolean hasEight; try { hasEight = SourceVersion.valueOf("RELEASE_8") != null; } catch(IllegalArgumentException iae) { hasEight = false; } if (hasEight) { return tc.call8(); } else { assert false : "Checker Framework needs a Java 8 or 9 javac."; return null; } } } catch (Throwable t) { assert false : "Checker Framework internal error: " + t; t.printStackTrace(); return null; } } public static TypeAnnotationPosition unknownTAPosition() { return call8or9( new Call8or9<TypeAnnotationPosition>() { @Override public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException { return TypeAnnotationPosition.class.newInstance(); } @Override public TypeAnnotationPosition call9() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { return (TypeAnnotationPosition) TypeAnnotationPosition.class .getField("unknown") .get(null); } } ); } public static TypeAnnotationPosition methodReturnTAPosition(final int pos) { return call8or9( new Call8or9<TypeAnnotationPosition>() { @Override public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.METHOD_RETURN); TypeAnnotationPosition.class.getField("pos").set(tapos, pos); return tapos; } @Override public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { return (TypeAnnotationPosition) TypeAnnotationPosition.class .getMethod("methodReturn", int.class) .invoke(null, pos); } } ); } public static TypeAnnotationPosition methodReceiverTAPosition(final int pos) { return call8or9( new Call8or9<TypeAnnotationPosition>() { @Override public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.METHOD_RECEIVER); TypeAnnotationPosition.class.getField("pos").set(tapos, pos); return tapos; } @Override public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { return (TypeAnnotationPosition) TypeAnnotationPosition.class .getMethod("methodReceiver", int.class) .invoke(null, pos); } } ); } public static TypeAnnotationPosition methodParameterTAPosition(final int pidx, final int pos) { return call8or9( new Call8or9<TypeAnnotationPosition>() { @Override public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.METHOD_FORMAL_PARAMETER); TypeAnnotationPosition.class.getField("parameter_index").set(tapos, pidx); TypeAnnotationPosition.class.getField("pos").set(tapos, pos); return tapos; } @Override public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { return (TypeAnnotationPosition) TypeAnnotationPosition.class .getMethod("methodParameter", int.class, int.class) .invoke(null, pidx, pos); } } ); } public static TypeAnnotationPosition methodThrowsTAPosition(final int tidx, final int pos) { return call8or9( new Call8or9<TypeAnnotationPosition>() { @Override public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.THROWS); TypeAnnotationPosition.class.getField("type_index").set(tapos, tidx); TypeAnnotationPosition.class.getField("pos").set(tapos, pos); return tapos; } @Override public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException { return (TypeAnnotationPosition) TypeAnnotationPosition.class .getMethod("methodThrows", List.class, JCLambda.class, int.class, int.class) .invoke(null, TypeAnnotationPosition.class.getField("emptyPath").get(null), null, tidx, pos); } } ); } public static TypeAnnotationPosition fieldTAPosition(final int pos) { return call8or9( new Call8or9<TypeAnnotationPosition>() { @Override public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.FIELD); TypeAnnotationPosition.class.getField("pos").set(tapos, pos); return tapos; } @Override public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { return (TypeAnnotationPosition) TypeAnnotationPosition.class .getMethod("field", int.class) .invoke(null, pos); } } ); } public static TypeAnnotationPosition classExtendsTAPosition(final int implidx, final int pos) { return call8or9( new Call8or9<TypeAnnotationPosition>() { @Override public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.CLASS_EXTENDS); TypeAnnotationPosition.class.getField("type_index").set(tapos, implidx); TypeAnnotationPosition.class.getField("pos").set(tapos, pos); return tapos; } @Override public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { return (TypeAnnotationPosition) TypeAnnotationPosition.class .getMethod("classExtends", int.class, int.class) .invoke(null, implidx, pos); } } ); } public static TypeAnnotationPosition typeParameterTAPosition(final int tpidx, final int pos) { return call8or9( new Call8or9<TypeAnnotationPosition>() { @Override public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.CLASS_TYPE_PARAMETER); TypeAnnotationPosition.class.getField("parameter_index").set(tapos, tpidx); TypeAnnotationPosition.class.getField("pos").set(tapos, pos); return tapos; } @Override public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException { return (TypeAnnotationPosition) TypeAnnotationPosition.class .getMethod("typeParameter", List.class, JCLambda.class, int.class, int.class) .invoke(null, TypeAnnotationPosition.class.getField("emptyPath").get(null), null, tpidx, pos); } } ); } public static TypeAnnotationPosition methodTypeParameterTAPosition(final int tpidx, final int pos) { return call8or9( new Call8or9<TypeAnnotationPosition>() { @Override public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.METHOD_TYPE_PARAMETER); TypeAnnotationPosition.class.getField("parameter_index").set(tapos, tpidx); TypeAnnotationPosition.class.getField("pos").set(tapos, pos); return tapos; } @Override public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException { return (TypeAnnotationPosition) TypeAnnotationPosition.class .getMethod("methodTypeParameter", List.class, JCLambda.class, int.class, int.class) .invoke(null, TypeAnnotationPosition.class.getField("emptyPath").get(null), null, tpidx, pos); } } ); } public static TypeAnnotationPosition typeParameterBoundTAPosition(final int tpidx, final int bndidx, final int pos) { return call8or9( new Call8or9<TypeAnnotationPosition>() { @Override public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.CLASS_TYPE_PARAMETER_BOUND); TypeAnnotationPosition.class.getField("parameter_index").set(tapos, tpidx); TypeAnnotationPosition.class.getField("bound_index").set(tapos, bndidx); TypeAnnotationPosition.class.getField("pos").set(tapos, pos); return tapos; } @Override public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException { return (TypeAnnotationPosition) TypeAnnotationPosition.class .getMethod("typeParameterBound", List.class, JCLambda.class, int.class, int.class, int.class) .invoke(null, TypeAnnotationPosition.class.getField("emptyPath").get(null), null, tpidx, bndidx, pos); } } ); } public static TypeAnnotationPosition methodTypeParameterBoundTAPosition(final int tpidx, final int bndidx, final int pos) { return call8or9( new Call8or9<TypeAnnotationPosition>() { @Override public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.METHOD_TYPE_PARAMETER_BOUND); TypeAnnotationPosition.class.getField("parameter_index").set(tapos, tpidx); TypeAnnotationPosition.class.getField("bound_index").set(tapos, bndidx); TypeAnnotationPosition.class.getField("pos").set(tapos, pos); return tapos; } @Override public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException { return (TypeAnnotationPosition) TypeAnnotationPosition.class .getMethod("methodTypeParameterBound", List.class, JCLambda.class, int.class, int.class, int.class) .invoke(null, TypeAnnotationPosition.class.getField("emptyPath").get(null), null, tpidx, bndidx, pos); } } ); } public static TypeAnnotationPosition copyTAPosition(final TypeAnnotationPosition tapos) { return call8or9( new Call8or9<TypeAnnotationPosition>() { @Override public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { return copyTAPosition8(tapos); } @Override public TypeAnnotationPosition call9() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, InvocationTargetException, NoSuchMethodException { return (TypeAnnotationPosition) TypeAnnotationPosition.class .getMethod("copy", TypeAnnotationPosition.class) .invoke(null, tapos); } } ); } private static TypeAnnotationPosition copyTAPosition8(TypeAnnotationPosition tapos) throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { TypeAnnotationPosition res = TypeAnnotationPosition.class.newInstance(); res.isValidOffset = tapos.isValidOffset; TypeAnnotationPosition.class.getField("bound_index").set(res, tapos.bound_index); res.exception_index = tapos.exception_index; res.location = List.from(tapos.location); if (tapos.lvarIndex != null) res.lvarIndex = Arrays.copyOf(tapos.lvarIndex, tapos.lvarIndex.length); if (tapos.lvarLength != null) res.lvarLength = Arrays.copyOf(tapos.lvarLength, tapos.lvarLength.length); if (tapos.lvarOffset != null) res.lvarOffset = Arrays.copyOf(tapos.lvarOffset, tapos.lvarOffset.length); res.offset = tapos.offset; TypeAnnotationPosition.class.getField("onLambda").set(res, tapos.onLambda); TypeAnnotationPosition.class.getField("parameter_index").set(res, tapos.parameter_index); TypeAnnotationPosition.class.getField("pos").set(res, tapos.pos); TypeAnnotationPosition.class.getField("type").set(res, tapos.type); TypeAnnotationPosition.class.getField("type_index").set(res, tapos.type_index); return res; } public static Type unannotatedType(final Type in) { return call8or9( new Call8or9<Type>() { @Override public Type call8() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { return (Type) Type.class .getMethod("unannotatedType") .invoke(in); } @Override public Type call9() { return in; } } ); } }