| package org.checkerframework.framework.type; |
| |
| import com.sun.source.tree.ClassTree; |
| import com.sun.source.tree.MethodTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.tree.TypeParameterTree; |
| import com.sun.source.tree.VariableTree; |
| 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.Symbol.MethodSymbol; |
| import com.sun.tools.javac.code.Symbol.VarSymbol; |
| import com.sun.tools.javac.code.Type; |
| import com.sun.tools.javac.code.TypeAnnotationPosition; |
| import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry; |
| import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntryKind; |
| import com.sun.tools.javac.tree.JCTree; |
| import com.sun.tools.javac.util.List; |
| import com.sun.tools.javac.util.ListBuffer; |
| import javax.annotation.processing.ProcessingEnvironment; |
| import javax.lang.model.element.AnnotationMirror; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.util.Types; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; |
| import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner; |
| import org.checkerframework.javacutil.BugInCF; |
| import org.checkerframework.javacutil.TreeUtils; |
| import org.checkerframework.javacutil.TypeAnnotationUtils; |
| |
| /** |
| * A helper class that puts the annotations from an AnnotatedTypeMirrors back into the corresponding |
| * Elements, so that they get stored in the bytecode by the compiler. |
| * |
| * <p>This has kind-of the symmetric function to {@code TypeFromElement}. |
| * |
| * <p>This class deals with javac internals and liberally imports such classes. |
| */ |
| public class TypesIntoElements { |
| |
| /** |
| * The entry point. |
| * |
| * @param processingEnv the environment |
| * @param atypeFactory the type factory |
| * @param tree the ClassTree to process |
| */ |
| public static void store( |
| ProcessingEnvironment processingEnv, AnnotatedTypeFactory atypeFactory, ClassTree tree) { |
| Symbol.ClassSymbol csym = (Symbol.ClassSymbol) TreeUtils.elementFromDeclaration(tree); |
| Types types = processingEnv.getTypeUtils(); |
| |
| storeTypeParameters(processingEnv, types, atypeFactory, tree.getTypeParameters(), csym); |
| |
| /* TODO: storing extends/implements types results in |
| * a strange error e.g. from the Nullness Checker. |
| * I think somewhere we take the annotations on extends/implements as |
| * the receiver annotation on a constructor, breaking logic there. |
| * I assume that the problem is the default that we use for these locations. |
| * Once we've decided the defaulting, enable this. |
| * See example of code that fails when this is enabled in |
| * checker/jtreg/nullness/annotationsOnExtends. Also, see |
| * https://github.com/typetools/checker-framework/pull/876 for |
| * a better implementation (though it also causes the error). |
| storeClassExtends(processingEnv, types, atypeFactory, tree.getExtendsClause(), csym, -1); |
| { |
| int implidx = 0; |
| for (Tree imp : tree.getImplementsClause()) { |
| storeClassExtends(processingEnv, types, atypeFactory, imp, csym, implidx); |
| ++implidx; |
| } |
| } |
| */ |
| |
| for (Tree mem : tree.getMembers()) { |
| if (mem.getKind() == Tree.Kind.METHOD) { |
| storeMethod(processingEnv, types, atypeFactory, (MethodTree) mem); |
| } else if (mem.getKind() == Tree.Kind.VARIABLE) { |
| storeVariable(processingEnv, types, atypeFactory, (VariableTree) mem); |
| } else { |
| // System.out.println("Unhandled member tree: " + mem); |
| } |
| } |
| } |
| |
| private static void storeMethod( |
| ProcessingEnvironment processingEnv, |
| Types types, |
| AnnotatedTypeFactory atypeFactory, |
| MethodTree meth) { |
| AnnotatedExecutableType mtype = atypeFactory.getAnnotatedType(meth); |
| MethodSymbol sym = (MethodSymbol) TreeUtils.elementFromDeclaration(meth); |
| TypeAnnotationPosition tapos; |
| List<Attribute.TypeCompound> tcs = List.nil(); |
| |
| storeTypeParameters(processingEnv, types, atypeFactory, meth.getTypeParameters(), sym); |
| |
| { |
| // return type |
| JCTree ret = ((JCTree.JCMethodDecl) meth).getReturnType(); |
| if (ret != null) { |
| tapos = TypeAnnotationUtils.methodReturnTAPosition(ret.pos); |
| tcs = tcs.appendList(generateTypeCompounds(processingEnv, mtype.getReturnType(), tapos)); |
| } |
| } |
| { |
| // receiver |
| JCTree receiverTree = ((JCTree.JCMethodDecl) meth).getReceiverParameter(); |
| if (receiverTree != null) { |
| tapos = TypeAnnotationUtils.methodReceiverTAPosition(receiverTree.pos); |
| tcs = tcs.appendList(generateTypeCompounds(processingEnv, mtype.getReceiverType(), tapos)); |
| } |
| } |
| { |
| // parameters |
| int pidx = 0; |
| java.util.List<AnnotatedTypeMirror> ptypes = mtype.getParameterTypes(); |
| for (JCTree param : ((JCTree.JCMethodDecl) meth).getParameters()) { |
| tapos = TypeAnnotationUtils.methodParameterTAPosition(pidx, param.pos); |
| tcs = tcs.appendList(generateTypeCompounds(processingEnv, ptypes.get(pidx), tapos)); |
| ++pidx; |
| } |
| } |
| { |
| // throws clauses |
| int tidx = 0; |
| java.util.List<AnnotatedTypeMirror> ttypes = mtype.getThrownTypes(); |
| for (JCTree thr : ((JCTree.JCMethodDecl) meth).getThrows()) { |
| tapos = TypeAnnotationUtils.methodThrowsTAPosition(tidx, thr.pos); |
| tcs = tcs.appendList(generateTypeCompounds(processingEnv, ttypes.get(tidx), tapos)); |
| ++tidx; |
| } |
| } |
| |
| addUniqueTypeCompounds(types, sym, tcs); |
| } |
| |
| private static void storeVariable( |
| ProcessingEnvironment processingEnv, |
| Types types, |
| AnnotatedTypeFactory atypeFactory, |
| VariableTree var) { |
| VarSymbol sym = (VarSymbol) TreeUtils.elementFromDeclaration(var); |
| AnnotatedTypeMirror type; |
| if (atypeFactory instanceof GenericAnnotatedTypeFactory) { |
| // TODO: this is rather ugly: we do not want refinement from the |
| // initializer of the field. We need a general way to get |
| // the "defaulted" type of a variable. |
| type = ((GenericAnnotatedTypeFactory<?, ?, ?, ?>) atypeFactory).getAnnotatedTypeLhs(var); |
| } else { |
| type = atypeFactory.getAnnotatedType(var); |
| } |
| |
| TypeAnnotationPosition tapos = TypeAnnotationUtils.fieldTAPosition(((JCTree) var).pos); |
| |
| List<Attribute.TypeCompound> tcs; |
| tcs = generateTypeCompounds(processingEnv, type, tapos); |
| addUniqueTypeCompounds(types, sym, tcs); |
| } |
| |
| @SuppressWarnings("unused") // TODO: see usage in comments above |
| private static void storeClassExtends( |
| ProcessingEnvironment processingEnv, |
| Types types, |
| AnnotatedTypeFactory atypeFactory, |
| Tree ext, |
| Symbol.ClassSymbol csym, |
| int implidx) { |
| |
| AnnotatedTypeMirror type; |
| int pos; |
| if (ext == null) { |
| // The implicit superclass is always java.lang.Object. |
| // TODO: is this a good way to get the type? |
| type = atypeFactory.fromElement(csym.getSuperclass().asElement()); |
| pos = -1; |
| } else { |
| type = atypeFactory.getAnnotatedTypeFromTypeTree(ext); |
| pos = ((JCTree) ext).pos; |
| } |
| |
| TypeAnnotationPosition tapos = TypeAnnotationUtils.classExtendsTAPosition(implidx, pos); |
| |
| List<Attribute.TypeCompound> tcs; |
| tcs = generateTypeCompounds(processingEnv, type, tapos); |
| addUniqueTypeCompounds(types, csym, tcs); |
| } |
| |
| private static void storeTypeParameters( |
| ProcessingEnvironment processingEnv, |
| Types types, |
| AnnotatedTypeFactory atypeFactory, |
| java.util.List<? extends TypeParameterTree> tps, |
| Symbol sym) { |
| boolean isClassOrInterface = sym.getKind().isClass() || sym.getKind().isInterface(); |
| List<Attribute.TypeCompound> tcs = List.nil(); |
| |
| int tpidx = 0; |
| for (TypeParameterTree tp : tps) { |
| AnnotatedTypeVariable typeVar = |
| (AnnotatedTypeVariable) atypeFactory.getAnnotatedTypeFromTypeTree(tp); |
| // System.out.println("The Type for type parameter " + tp + " is " + type); |
| |
| TypeAnnotationPosition tapos; |
| // Note: we use the type parameter pos also for the bounds; |
| // the bounds may not be explicit and we couldn't look up separate pos. |
| if (isClassOrInterface) { |
| tapos = TypeAnnotationUtils.typeParameterTAPosition(tpidx, ((JCTree) tp).pos); |
| } else { |
| tapos = TypeAnnotationUtils.methodTypeParameterTAPosition(tpidx, ((JCTree) tp).pos); |
| } |
| |
| { // This block is essentially direct annotations, perhaps we should refactor that |
| // method out |
| List<Attribute.TypeCompound> res = List.nil(); |
| for (AnnotationMirror am : typeVar.getLowerBound().getAnnotations()) { |
| Attribute.TypeCompound tc = |
| TypeAnnotationUtils.createTypeCompoundFromAnnotationMirror(am, tapos, processingEnv); |
| res = res.prepend(tc); |
| } |
| tcs = tcs.appendList(res); |
| } |
| |
| AnnotatedTypeMirror tpbound = typeVar.getUpperBound(); |
| java.util.List<? extends AnnotatedTypeMirror> bounds; |
| if (tpbound.getKind() == TypeKind.INTERSECTION) { |
| bounds = ((AnnotatedIntersectionType) tpbound).getBounds(); |
| } else { |
| bounds = List.of(tpbound); |
| } |
| |
| int bndidx = 0; |
| for (AnnotatedTypeMirror bound : bounds) { |
| if (bndidx == 0 && ((Type) bound.getUnderlyingType()).isInterface()) { |
| // If the first bound is an interface, there is an implicit java.lang.Object |
| ++bndidx; |
| } |
| |
| if (isClassOrInterface) { |
| tapos = |
| TypeAnnotationUtils.typeParameterBoundTAPosition(tpidx, bndidx, ((JCTree) tp).pos); |
| } else { |
| tapos = |
| TypeAnnotationUtils.methodTypeParameterBoundTAPosition( |
| tpidx, bndidx, ((JCTree) tp).pos); |
| } |
| |
| tcs = tcs.appendList(generateTypeCompounds(processingEnv, bound, tapos)); |
| ++bndidx; |
| } |
| ++tpidx; |
| } |
| |
| // System.out.println("Adding " + tcs + " to " + sym); |
| addUniqueTypeCompounds(types, sym, tcs); |
| } |
| |
| private static void addUniqueTypeCompounds(Types types, Symbol sym, List<TypeCompound> tcs) { |
| List<TypeCompound> raw = sym.getRawTypeAttributes(); |
| List<Attribute.TypeCompound> res = List.nil(); |
| |
| for (Attribute.TypeCompound tc : tcs) { |
| if (!TypeAnnotationUtils.isTypeCompoundContained(raw, tc, types)) { |
| res = res.append(tc); |
| } |
| } |
| // That method only uses reference equality. isTypeCompoundContained does a deep comparison. |
| sym.appendUniqueTypeAttributes(res); |
| } |
| |
| // Do not return null. Return List.nil() if there are no TypeCompounds to return. |
| private static List<Attribute.TypeCompound> generateTypeCompounds( |
| ProcessingEnvironment processingEnv, AnnotatedTypeMirror type, TypeAnnotationPosition tapos) { |
| return new TCConvert(processingEnv).scan(type, tapos); |
| } |
| |
| /** |
| * Convert an AnnotatedTypeMirror and a TypeAnnotationPosition into the corresponding |
| * TypeCompounds. |
| */ |
| private static class TCConvert |
| extends AnnotatedTypeScanner<List<Attribute.TypeCompound>, TypeAnnotationPosition> { |
| |
| /** ProcessingEnvironment. */ |
| private final ProcessingEnvironment processingEnv; |
| |
| /** |
| * Creates a {@link TCConvert}. |
| * |
| * @param processingEnv ProcessEnvironment |
| */ |
| TCConvert(ProcessingEnvironment processingEnv) { |
| super(List.nil()); |
| this.processingEnv = processingEnv; |
| } |
| |
| @Override |
| public List<TypeCompound> scan(AnnotatedTypeMirror type, TypeAnnotationPosition pos) { |
| if (pos == null) { |
| throw new BugInCF("TypesIntoElements: invalid usage, null pos with type: " + type); |
| } |
| List<TypeCompound> res = super.scan(type, pos); |
| return res; |
| } |
| |
| @Override |
| public List<TypeCompound> reduce(List<TypeCompound> r1, List<TypeCompound> r2) { |
| if (r1 == null) { |
| return r2; |
| } |
| if (r2 == null) { |
| return r1; |
| } |
| return r1.appendList(r2); |
| } |
| |
| private List<TypeCompound> directAnnotations( |
| AnnotatedTypeMirror type, TypeAnnotationPosition tapos) { |
| List<Attribute.TypeCompound> res = List.nil(); |
| |
| for (AnnotationMirror am : type.getAnnotations()) { |
| // TODO: I BELIEVE THIS ISN'T TRUE BECAUSE PARAMETERS MAY HAVE ANNOTATIONS THAT CAME |
| // FROM THE ELEMENT OF THE CLASS WHICH PREVIOUSLY WAS WRITTEN OUT BY |
| // TYPESINTOELEMENT. |
| // if (am instanceof Attribute.TypeCompound) { |
| // // If it is a TypeCompound it was already present in source |
| // (right?), |
| // // so there is nothing to do. |
| // // System.out.println(" found TypeComound: " + am + " pos: " |
| // + ((Attribute.TypeCompound)am).position); |
| // } else { |
| // TODO: DOES THIS LEAD TO DOUBLING UP ON THE SAME ANNOTATION IN THE ELEMENT? |
| Attribute.TypeCompound tc = |
| TypeAnnotationUtils.createTypeCompoundFromAnnotationMirror(am, tapos, processingEnv); |
| res = res.prepend(tc); |
| // } |
| } |
| return res; |
| } |
| |
| @Override |
| public List<TypeCompound> visitDeclared( |
| AnnotatedDeclaredType type, TypeAnnotationPosition tapos) { |
| if (visitedNodes.containsKey(type)) { |
| return visitedNodes.get(type); |
| } |
| // Hack for termination |
| visitedNodes.put(type, List.nil()); |
| List<Attribute.TypeCompound> res; |
| |
| TypeAnnotationPosition oldpos = TypeAnnotationUtils.copyTAPosition(tapos); |
| locateNestedTypes(type, tapos); |
| |
| res = directAnnotations(type, tapos); |
| |
| // we sometimes fix-up raw types with wildcards, do not write these into the bytecode as there |
| // are no corresponding type arguments and therefore no location to actually add them to |
| if (!type.wasRaw()) { |
| int arg = 0; |
| for (AnnotatedTypeMirror ta : type.getTypeArguments()) { |
| TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); |
| newpos.location = |
| tapos.location.append(new TypePathEntry(TypePathEntryKind.TYPE_ARGUMENT, arg)); |
| res = scanAndReduce(ta, newpos, res); |
| ++arg; |
| } |
| } |
| |
| AnnotatedTypeMirror encl = type.getEnclosingType(); |
| if (encl != null && encl.getKind() != TypeKind.NONE && encl.getKind() != TypeKind.ERROR) { |
| // use original tapos |
| res = scanAndReduce(encl, oldpos, res); |
| } |
| visitedNodes.put(type, res); |
| return res; |
| } |
| |
| /* Modeled after |
| * {@link com.sun.tools.javac.code.TypeAnnotations.TypeAnnotationPositions#locateNestedTypes(Type, TypeAnnotationPosition)} |
| */ |
| private void locateNestedTypes(AnnotatedDeclaredType type, TypeAnnotationPosition p) { |
| // The number of "steps" to get from the full type to the |
| // left-most outer type. |
| ListBuffer<TypePathEntry> depth = new ListBuffer<>(); |
| |
| Type encl = (Type) type.getUnderlyingType().getEnclosingType(); |
| while (encl != null && encl.getKind() != TypeKind.NONE && encl.getKind() != TypeKind.ERROR) { |
| depth = depth.append(TypePathEntry.INNER_TYPE); |
| encl = encl.getEnclosingType(); |
| } |
| |
| if (depth.nonEmpty()) { |
| p.location = p.location.appendList(depth.toList()); |
| } |
| } |
| |
| @Override |
| public List<TypeCompound> visitIntersection( |
| AnnotatedIntersectionType type, TypeAnnotationPosition tapos) { |
| if (visitedNodes.containsKey(type)) { |
| return visitedNodes.get(type); |
| } |
| visitedNodes.put(type, List.nil()); |
| List<Attribute.TypeCompound> res; |
| res = directAnnotations(type, tapos); |
| |
| int arg = 0; |
| for (AnnotatedTypeMirror bound : type.getBounds()) { |
| TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); |
| newpos.location = |
| tapos.location.append(new TypePathEntry(TypePathEntryKind.TYPE_ARGUMENT, arg)); |
| res = scanAndReduce(bound, newpos, res); |
| ++arg; |
| } |
| visitedNodes.put(type, res); |
| return res; |
| } |
| |
| @Override |
| public List<TypeCompound> visitUnion(AnnotatedUnionType type, TypeAnnotationPosition tapos) { |
| // We should never need to write a union type, so raise an error. |
| throw new BugInCF( |
| "TypesIntoElement: encountered union type: " + type + " at position: " + tapos); |
| } |
| |
| @Override |
| public List<TypeCompound> visitArray(AnnotatedArrayType type, TypeAnnotationPosition tapos) { |
| List<Attribute.TypeCompound> res; |
| res = directAnnotations(type, tapos); |
| |
| TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); |
| newpos.location = tapos.location.append(TypePathEntry.ARRAY); |
| |
| return reduce(super.visitArray(type, newpos), res); |
| } |
| |
| @Override |
| public List<TypeCompound> visitPrimitive( |
| AnnotatedPrimitiveType type, TypeAnnotationPosition tapos) { |
| List<Attribute.TypeCompound> res; |
| res = directAnnotations(type, tapos); |
| return res; |
| } |
| |
| @Override |
| public List<TypeCompound> visitTypeVariable( |
| AnnotatedTypeVariable type, TypeAnnotationPosition tapos) { |
| List<Attribute.TypeCompound> res; |
| res = directAnnotations(type, tapos); |
| // Do not call super. The bound will be visited separately. |
| return res; |
| } |
| |
| @Override |
| public List<TypeCompound> visitWildcard( |
| AnnotatedWildcardType type, TypeAnnotationPosition tapos) { |
| if (this.visitedNodes.containsKey(type)) { |
| return List.nil(); |
| } |
| // Hack for termination, otherwise we'll visit one type too far (the same recursive |
| // wildcard twice and generate extra type annos) |
| visitedNodes.put(type, List.nil()); |
| List<Attribute.TypeCompound> res; |
| |
| // Note: By default, an Unbound wildcard will return true for both isExtendsBound and |
| // isSuperBound |
| if (((Type.WildcardType) type.getUnderlyingType()).isExtendsBound()) { |
| res = directAnnotations(type.getSuperBound(), tapos); |
| |
| AnnotatedTypeMirror ext = type.getExtendsBound(); |
| if (ext != null) { |
| TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); |
| newpos.location = tapos.location.append(TypePathEntry.WILDCARD); |
| res = scanAndReduce(ext, newpos, res); |
| } |
| |
| } else { |
| res = directAnnotations(type.getExtendsBound(), tapos); |
| AnnotatedTypeMirror sup = type.getSuperBoundField(); |
| if (sup != null) { |
| TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); |
| newpos.location = tapos.location.append(TypePathEntry.WILDCARD); |
| res = scanAndReduce(sup, newpos, res); |
| } |
| } |
| visitedNodes.put(type, res); |
| return res; |
| } |
| } |
| } |