package checkers.util; import java.lang.annotation.Annotation; import java.util.*; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.*; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeVariable; import checkers.basetype.BaseTypeChecker; import checkers.quals.PolymorphicQualifier; import checkers.types.*; import checkers.types.AnnotatedTypeMirror.*; import checkers.types.visitors.AnnotatedTypeScanner; import checkers.types.visitors.SimpleAnnotatedTypeScanner; import checkers.types.visitors.SimpleAnnotatedTypeVisitor; import com.sun.source.tree.*; import com.sun.source.util.SimpleTreeVisitor; /** * Implements framework support for type qualifier polymorphism. Checkers that * wish to use it should add calls to * {@link #annotate(MethodInvocationTree, AnnotatedTypeMirror.AnnotatedExecutableType) annotate(MethodInvocationTree, AnnotatedExecutableType)} * {@link #annotate(Element, AnnotatedTypeMirror)} to the * {@link AnnotatedTypeFactory#annotateImplicit(Tree, AnnotatedTypeMirror)} and * {@link AnnotatedTypeFactory#annotateImplicit(Tree, AnnotatedTypeMirror)} * methods. * * <p> * * This implementation currently only supports polymorphism for method * invocations, for which the return type depends on the unification of the * parameter/receiver types. * * @see PolymorphicQualifier */ public class QualifierPolymorphism { private final AnnotatedTypeFactory factory; private final AnnotationUtils annoFactory; private final AnnotatedTypes atypes; private final Resolver resolver; private final Completer completer; /** The polymorphic qualifier. */ protected final AnnotationMirror polyQual; /** The qualifier at the root of the hierarchy. */ protected final AnnotationMirror rootQual; /** * Creates a {@link QualifierPolymorphism} instance that uses the given * checker for querying type qualifiers and the given factory for getting * annotated types. * * @param checker the current checker * @param factory the factory for the current checker */ public QualifierPolymorphism(BaseTypeChecker checker, AnnotatedTypeFactory factory) { this.factory = factory; final ProcessingEnvironment env = checker.getProcessingEnvironment(); this.atypes = new AnnotatedTypes(env, factory); this.annoFactory = AnnotationUtils.getInstance(env); AnnotationMirror poly = null; AnnotationMirror root = null; for (Class<? extends Annotation> a : checker.getSupportedTypeQualifiers()) { if (a.getAnnotation(PolymorphicQualifier.class) != null) { if (poly != null) throw new IllegalArgumentException( "checker has multiple polymorphic qualifiers"); poly = annoFactory.fromClass(a); } } root = checker.getQualifierHierarchy().getRootAnnotation(); this.polyQual = poly; this.rootQual = root; this.collector = new PolyCollector(); this.resolver = new Resolver(); this.completer = new Completer(); } /** * Resolves polymorphism annotations for the given type. * * @param elt the element associated with the type * @param type the type to annotate */ public void annotate(Element elt, AnnotatedTypeMirror type) { if (polyQual == null) return; completer.visit(type); } /** * Resolves polymorphism annotations for the given type. * * @param tree the tree associated with the type * @param type the type to annotate */ public void annotate(MethodInvocationTree tree, AnnotatedExecutableType type) { if (polyQual == null) return; List<AnnotatedTypeMirror> arguments = atypes.getAnnotatedTypes(tree.getArguments()); List<AnnotatedTypeMirror> requiredArgs = atypes.expandVarArgs(type, tree.getArguments()); Map<String, AnnotationMirror> matchingMapping = collector.visit(arguments, requiredArgs); matchingMapping = collector.reduce(matchingMapping, collector.visit(factory.getReceiver(tree), type.getReceiverType())); if (matchingMapping != null && !matchingMapping.isEmpty()) replacer.visit(type, matchingMapping.values().iterator().next()); // else // completer.visit(type); } private AnnotatedTypeScanner<Void, AnnotationMirror> replacer = new AnnotatedTypeScanner<Void, AnnotationMirror>() { @Override public Void scan(AnnotatedTypeMirror type, AnnotationMirror qual) { if (type != null && type.hasAnnotation(polyQual)) { type.removeAnnotation(polyQual); type.addAnnotation(qual); } return super.scan(type, qual); } }; /** * Completes a type by removing any unresolved polymorphic qualifiers, * replacing them with the root qualifier if one is defined. */ class Completer extends AnnotatedTypeScanner<Void, Void> { @Override protected Void scan(AnnotatedTypeMirror type, Void p) { if (type != null && type.hasAnnotation(polyQual)) type.removeAnnotation(polyQual); if (rootQual != null && type != null && !type.isAnnotated()) type.addAnnotation(rootQual); return super.scan(type, p); } } /** * Resolves the actual qualifiers to replace polymorphism qualifiers based * on the arguments of a method invocation. */ class Resolver extends SimpleTreeVisitor<Void, AnnotatedTypeMirror> { /** * Given a method invocation along with the return, parameter, and * receiver types of the invoked method, performs substitution of * polymorphism annotations based on the method invocation arguments. * * @param node the method invocation * @param type the type on which annotations should be resolved * @param ret the return type of the invoked method * @param params the parameter types of the invoked method * @param rcv the receiver type of the invoked method */ private void resolve(MethodInvocationTree node, AnnotatedTypeMirror type, AnnotatedTypeMirror ret, List<AnnotatedTypeMirror> params, AnnotatedTypeMirror rcv) { if (ret.hasAnnotation(polyQual)) { int i = 0; boolean flag = false; for (AnnotatedTypeMirror param : params) { ExpressionTree arg = node.getArguments().get(i); AnnotatedTypeMirror argType = factory.getAnnotatedType(arg); flag = resolveForType(param, argType, type, flag); if (argType instanceof AnnotatedDeclaredType && param instanceof AnnotatedDeclaredType) { AnnotatedDeclaredType argDecl = (AnnotatedDeclaredType) argType; AnnotatedDeclaredType paramDecl = (AnnotatedDeclaredType) param; for (int j = 0; j < argDecl.getTypeArguments().size(); j++) flag = resolveForType(paramDecl.getTypeArguments() .get(j), argDecl.getTypeArguments().get(j), type, flag); } // TODO else for arrays i++; } if (flag) return; if (rcv.hasAnnotation(polyQual)) { Set<AnnotationMirror> r = factory.getReceiver(node).getAnnotations(); type.clearAnnotations(); type.addAnnotations(r); } } return; } /** * Resolves annotations for a particular type, accounting for wildcards. * * @param decl the type as declared (e.g. as a method parameter) * @param actual the actual type (e.g. as an argument to a method call) * @param type the type on which to perform substitutions * @param merge if false, replace annotations on {@code type}, else * merge them * @return true if annotations were replaced */ private boolean resolveForType(AnnotatedTypeMirror decl, AnnotatedTypeMirror actual, AnnotatedTypeMirror type, boolean merge) { if (decl instanceof AnnotatedWildcardType) decl = ((AnnotatedWildcardType) decl).getExtendsBound(); if (actual instanceof AnnotatedWildcardType) actual = ((AnnotatedWildcardType) actual).getExtendsBound(); if (decl.hasAnnotation(polyQual)) { if (!merge) { type.clearAnnotations(); type.addAnnotations(actual.getAnnotations()); return true; } for (AnnotationMirror a : type.getAnnotations()) if (!actual.hasAnnotation(a)) type.removeAnnotation(a); } return false; } @Override public Void visitMethodInvocation(MethodInvocationTree node, AnnotatedTypeMirror p) { if (p == null) return null; // Get the element & type of the invoked method. ExecutableElement elt = TreeUtils.elementFromUse(node); AnnotatedExecutableType ex = factory.fromElement(elt); // Get the return, parameter, and receiver types. AnnotatedTypeMirror ret = ex.getReturnType(); List<AnnotatedTypeMirror> params = atypes.expandVarArgs(ex, node.getArguments()); AnnotatedTypeMirror rcv = ex.getReceiverType(); resolve(node, p, ret, params, rcv); if (p instanceof AnnotatedDeclaredType && ret instanceof AnnotatedDeclaredType) { AnnotatedDeclaredType pDecl = (AnnotatedDeclaredType) p; AnnotatedDeclaredType rDecl = (AnnotatedDeclaredType) ex.getReturnType(); for (int i = 0; i < pDecl.getTypeArguments().size(); i++) resolve(node, pDecl.getTypeArguments().get(i), rDecl.getTypeArguments().get(i), params, rcv); } else if (p instanceof AnnotatedArrayType && ret instanceof AnnotatedArrayType) { AnnotatedArrayType pArray = (AnnotatedArrayType) p; AnnotatedArrayType rArray = (AnnotatedArrayType) ret; resolve(node, pArray.getComponentType(), rArray.getComponentType(), params, rcv); } return null; } } private final PolyCollector collector; /** * A Helper class that tries to resolve the immutability type variable, * as the type variable is assigned to the most restricted immutability */ private class PolyCollector extends SimpleAnnotatedTypeVisitor<Map<String, AnnotationMirror>, AnnotatedTypeMirror> { private static final String KEY = "key"; private final QualifierHierarchy hierarchy = factory.getQualifierHierarchy(); public Map<String, AnnotationMirror> reduce(Map<String, AnnotationMirror> r1, Map<String, AnnotationMirror> r2) { if (r1 == null || r1.isEmpty()) return r2; if (r2 == null || r2.isEmpty()) return r1; AnnotationMirror a1Anno = r1.get(KEY); AnnotationMirror a2Anno = r2.get(KEY); AnnotationMirror lub = hierarchy.leastUpperBound(a1Anno, a2Anno); return Collections.singletonMap(KEY, lub); } public Map<String, AnnotationMirror> visit(Iterable<? extends AnnotatedTypeMirror> types, Iterable<? extends AnnotatedTypeMirror> actualTypes) { Map<String, AnnotationMirror> result = new HashMap<String, AnnotationMirror>(); Iterator<? extends AnnotatedTypeMirror> itert = types.iterator(); Iterator<? extends AnnotatedTypeMirror> itera = actualTypes.iterator(); while (itert.hasNext() && itera.hasNext()) { AnnotatedTypeMirror type = itert.next(); AnnotatedTypeMirror actualType = itera.next(); result = reduce(result, visit(type, actualType)); } return result; } @Override public Map<String, AnnotationMirror> visitDeclared( AnnotatedDeclaredType type, AnnotatedTypeMirror actualType) { if (actualType.getKind() == TypeKind.TYPEVAR) { if (typeVar.contains(actualType.getUnderlyingType())) return Collections.emptyMap(); typeVar.add((TypeVariable)actualType.getUnderlyingType()); Map<String, AnnotationMirror> result = visit(type, ((AnnotatedTypeVariable)actualType).getUpperBound()); typeVar.remove(actualType.getUnderlyingType()); return result; } if (actualType.getKind() == TypeKind.WILDCARD) return visit(type, ((AnnotatedWildcardType)actualType).getExtendsBound()); if (actualType.getKind() != type.getKind() || actualType == type) return Collections.emptyMap(); assert actualType.getKind() == type.getKind(); type = (AnnotatedDeclaredType)atypes.asSuper(type, actualType); // TODO: type is null if type is an intersection type // assert type != null; if (type == null) return new HashMap<String, AnnotationMirror>(); AnnotatedDeclaredType dcType = (AnnotatedDeclaredType)actualType; Map<String, AnnotationMirror> result = new HashMap<String, AnnotationMirror>(); if (dcType.hasAnnotation(polyQual)) { AnnotationMirror typeQual = !type.isAnnotated() ? null : type.getAnnotations().iterator().next(); result.put(KEY, typeQual); } if (type.isParameterized() && dcType.isParameterized()) result = reduce(result, visit(type.getTypeArguments(), dcType.getTypeArguments())); return result; } @Override public Map<String, AnnotationMirror> visitNull( AnnotatedNullType type, AnnotatedTypeMirror actualType) { if (actualType.hasAnnotation(polyQual)) { AnnotationMirror typeQual = !type.isAnnotated() ? null : type.getAnnotations().iterator().next(); return Collections.singletonMap(KEY, typeQual); } return super.visitNull(type, actualType); } @Override public Map<String, AnnotationMirror> visitArray( AnnotatedArrayType type, AnnotatedTypeMirror actualType) { if (actualType.getKind() == TypeKind.DECLARED) return visit(atypes.asSuper(type, actualType), actualType); if (actualType.getKind() == TypeKind.TYPEVAR) { if (typeVar.contains(actualType.getUnderlyingType())) return Collections.emptyMap(); typeVar.add((TypeVariable)actualType.getUnderlyingType()); Map<String, AnnotationMirror> result = visit(type, ((AnnotatedTypeVariable)actualType).getUpperBound()); typeVar.remove(actualType.getUnderlyingType()); return result; } if (actualType.getKind() == TypeKind.WILDCARD) return visit(type, ((AnnotatedWildcardType)actualType).getExtendsBound()); assert type.getKind() == actualType.getKind() : actualType; AnnotatedArrayType arType = (AnnotatedArrayType)actualType; Map<String, AnnotationMirror> result = new HashMap<String, AnnotationMirror>(); if (arType.hasAnnotation(polyQual)) { AnnotationMirror typeQual = !type.isAnnotated() ? null : type.getAnnotations().iterator().next(); result.put(KEY, typeQual); } result = reduce(result, visit(type.getComponentType(), arType.getComponentType())); return result; } private Set<TypeVariable> typeVar = new HashSet<TypeVariable>(); @Override public Map<String, AnnotationMirror> visitTypeVariable( AnnotatedTypeVariable type, AnnotatedTypeMirror actualType) { if (actualType.getKind() == TypeKind.WILDCARD) // give up return Collections.emptyMap(); AnnotatedTypeMirror typeSuper = findType(type, actualType); if (typeSuper.getKind() != TypeKind.TYPEVAR) return visit(typeSuper, actualType); assert typeSuper.getKind() == actualType.getKind() : actualType; assert type.getKind() == actualType.getKind() : actualType; AnnotatedTypeVariable tvType = (AnnotatedTypeVariable)typeSuper; typeVar.add(type.getUnderlyingType()); // a type variable cannot be annotated Map<String, AnnotationMirror> result = visit(type.getUpperBound(), tvType.getUpperBound()); typeVar.remove(type.getUnderlyingType()); return result; } @Override public Map<String, AnnotationMirror> visitWildcard( AnnotatedWildcardType type, AnnotatedTypeMirror actualType) { AnnotatedTypeMirror typeSuper = findType(type, actualType); if (typeSuper.getKind() != TypeKind.WILDCARD) return visit(typeSuper, actualType); assert typeSuper.getKind() == actualType.getKind() : actualType; AnnotatedWildcardType wcType = (AnnotatedWildcardType)typeSuper; if (type.getExtendsBound() != null && wcType.getExtendsBound() != null) return visit(type.getExtendsBound(), wcType.getExtendsBound()); else if (type.getSuperBound() != null && wcType.getSuperBound() != null) return visit(type.getSuperBound(), wcType.getSuperBound()); else return new HashMap<String, AnnotationMirror>(); } private AnnotatedTypeMirror findType(AnnotatedTypeMirror type, AnnotatedTypeMirror actualType) { AnnotatedTypeMirror result = atypes.asSuper(type, actualType); // result shouldn't be null, will test this hypothesis later // assert result != null; return (result != null ? result : type); } } }