package org.checkerframework.framework.util; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.Name; 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 org.checkerframework.framework.qual.PolyAll; import org.checkerframework.framework.qual.PolymorphicQualifier; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; 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.AnnotatedNullType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner; import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeVisitor; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.ErrorReporter; import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; /** * Implements framework support for type qualifier polymorphism. Checkers that wish to use it should * add calls to {@link #annotate(MethodInvocationTree, AnnotatedTypeMirror.AnnotatedExecutableType)} * to the {@link AnnotatedTypeFactory#addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)} and * {@link AnnotatedTypeFactory#addComputedTypeAnnotations(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 Types types; private final AnnotatedTypeFactory atypeFactory; private final Completer completer; /** * The polymorphic qualifiers: mapping from the top of a qualifier hierarchy to the polymorphic * qualifier of that hierarchy. Field always non-null but might be an empty mapping. The "null" * key, if present, always maps to PolyAll. */ protected final Map<AnnotationMirror, AnnotationMirror> polyQuals; /** The qualifiers at the top of the qualifier hierarchy. */ protected final Set<? extends AnnotationMirror> topQuals; /** The qualifier hierarchy to use. */ protected final QualifierHierarchy qualhierarchy; private final AnnotationMirror POLYALL; /** * Creates a {@link QualifierPolymorphism} instance that uses the given checker for querying * type qualifiers and the given factory for getting annotated types. * * @param env the processing environment * @param factory the factory for the current checker */ public QualifierPolymorphism(ProcessingEnvironment env, AnnotatedTypeFactory factory) { this.atypeFactory = factory; this.types = env.getTypeUtils(); Elements elements = env.getElementUtils(); POLYALL = AnnotationUtils.fromClass(elements, PolyAll.class); this.qualhierarchy = factory.getQualifierHierarchy(); Map<AnnotationMirror, AnnotationMirror> polys = new HashMap<AnnotationMirror, AnnotationMirror>(); for (AnnotationMirror aam : qualhierarchy.getTypeQualifiers()) { if (isPolyAll(aam)) { polys.put(null, aam); continue; } for (AnnotationMirror aa : aam.getAnnotationType().asElement().getAnnotationMirrors()) { if (aa.getAnnotationType() .toString() .equals(PolymorphicQualifier.class.getCanonicalName())) { Name plval = AnnotationUtils.getElementValueClassName(aa, "value", true); AnnotationMirror ttreetop; if (PolymorphicQualifier.class.getCanonicalName().contentEquals(plval)) { Set<? extends AnnotationMirror> tops = qualhierarchy.getTopAnnotations(); if (tops.size() != 1) { ErrorReporter.errorAbort( "QualifierPolymorphism: PolymorphicQualifier has to specify type hierarchy, if more than one exist; top types: " + tops); } ttreetop = tops.iterator().next(); } else { AnnotationMirror ttree = AnnotationUtils.fromName(elements, plval); ttreetop = qualhierarchy.getTopAnnotation(ttree); } if (polys.containsKey(ttreetop)) { ErrorReporter.errorAbort( "QualifierPolymorphism: checker has multiple polymorphic qualifiers: " + polys.get(ttreetop) + " and " + aam); } polys.put(ttreetop, aam); } } } this.polyQuals = polys; this.topQuals = qualhierarchy.getTopAnnotations(); this.collector = new PolyCollector(); this.completer = new Completer(); } public static AnnotationMirror getPolymorphicQualifier(AnnotationMirror qual) { if (qual == null) { return null; } Element qualElt = qual.getAnnotationType().asElement(); for (AnnotationMirror am : qualElt.getAnnotationMirrors()) { if (am.getAnnotationType() .toString() .equals(PolymorphicQualifier.class.getCanonicalName())) { return am; } } return null; } public static boolean isPolymorphicQualified(AnnotationMirror qual) { return getPolymorphicQualifier(qual) != null; } public static boolean isPolyAll(AnnotationMirror qual) { return AnnotationUtils.areSameByClass(qual, PolyAll.class); } /** * Returns null if the qualifier is not polymorphic. Returns the (given) top of the type * hierarchy, in which it is polymorphic, otherwise. The top qualifier is given by the * programmer, so must be normalized to ensure its the real top. */ public static Class<? extends Annotation> getPolymorphicQualifierTop( Elements elements, AnnotationMirror qual) { AnnotationMirror poly = getPolymorphicQualifier(qual); // System.out.println("poly: " + poly + " pq: " + PolymorphicQualifier.class.getCanonicalName()); if (poly == null) { return null; } @SuppressWarnings("unchecked") Class<? extends Annotation> ret = (Class<? extends Annotation>) AnnotationUtils.getElementValueClass(poly, "value", true); return ret; } /** * 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 (polyQuals.isEmpty()) { return; } // javac produces enum super calls with zero arguments even though the // method element requires two. // See also BaseTypeVisitor.visitMethodInvocation and // CFGBuilder.CFGTranslationPhaseOne.visitMethodInvocation if (TreeUtils.isEnumSuper(tree)) { return; } List<AnnotatedTypeMirror> requiredArgs = AnnotatedTypes.expandVarArgs(atypeFactory, type, tree.getArguments()); List<AnnotatedTypeMirror> arguments = AnnotatedTypes.getAnnotatedTypes(atypeFactory, requiredArgs, tree.getArguments()); Map<AnnotationMirror, Set<? extends AnnotationMirror>> matchingMapping = collector.visit(arguments, requiredArgs); // for super() and this() method calls, getReceiverType(tree) does not return the correct // type. So, just skip those. This is consistent with skipping receivers of constructors // below. if (type.getReceiverType() != null && !TreeUtils.isSuperCall(tree) && !TreeUtils.isThisCall(tree)) { matchingMapping = collector.reduce( matchingMapping, collector.visit( atypeFactory.getReceiverType(tree), type.getReceiverType())); } if (matchingMapping != null && !matchingMapping.isEmpty()) { replacer.visit(type, matchingMapping); } else { completer.visit(type); } } public void annotate(NewClassTree tree, AnnotatedExecutableType type) { if (polyQuals.isEmpty()) { return; } List<AnnotatedTypeMirror> requiredArgs = AnnotatedTypes.expandVarArgs(atypeFactory, type, tree.getArguments()); List<AnnotatedTypeMirror> arguments = AnnotatedTypes.getAnnotatedTypes(atypeFactory, requiredArgs, tree.getArguments()); Map<AnnotationMirror, Set<? extends AnnotationMirror>> matchingMapping = collector.visit(arguments, requiredArgs); // TODO: poly on receiver for constructors? // matchingMapping = collector.reduce(matchingMapping, // collector.visit(factory.getReceiverType(tree), type.getReceiverType())); if (matchingMapping != null && !matchingMapping.isEmpty()) { replacer.visit(type, matchingMapping); } else { completer.visit(type); } } public void annotate( AnnotatedExecutableType functionalInterface, AnnotatedExecutableType memberReference) { for (AnnotationMirror type : functionalInterface.getReturnType().getAnnotations()) { if (isPolymorphicQualified(type)) { // functional interface has a polymorphic qualifier, so they should not be resolved on memberReference. return; } } List<AnnotatedTypeMirror> args = functionalInterface.getParameterTypes(); List<AnnotatedTypeMirror> requiredArgs = memberReference.getParameterTypes(); if (args.size() == requiredArgs.size() + 1) { // If the member reference is a reference to an instance method of an arbitrary // object, then first parameter of the functional interface corresponds to the // receiver of the member reference. List<AnnotatedTypeMirror> newRequiredArgs = new ArrayList<>(); newRequiredArgs.add(memberReference.getReceiverType()); newRequiredArgs.addAll(requiredArgs); requiredArgs = newRequiredArgs; } // Deal with varargs if (memberReference.isVarArgs() && !functionalInterface.isVarArgs()) { requiredArgs = AnnotatedTypes.expandVarArgsFromTypes(memberReference, args); } Map<AnnotationMirror, Set<? extends AnnotationMirror>> matchingMapping = collector.visit(args, requiredArgs); if (matchingMapping != null && !matchingMapping.isEmpty()) { replacer.visit(memberReference, matchingMapping); } else { //TODO: Do we need this (return type?) completer.visit(memberReference); } } private final AnnotatedTypeScanner<Void, Map<AnnotationMirror, Set<? extends AnnotationMirror>>> replacer = new AnnotatedTypeScanner< Void, Map<AnnotationMirror, Set<? extends AnnotationMirror>>>() { @Override public Void scan( AnnotatedTypeMirror type, Map<AnnotationMirror, Set<? extends AnnotationMirror>> matches) { if (type != null) { for (Map.Entry<AnnotationMirror, Set<? extends AnnotationMirror>> pqentry : matches.entrySet()) { AnnotationMirror poly = pqentry.getKey(); if (poly != null && type.hasAnnotation(poly)) { type.removeAnnotation(poly); Set<? extends AnnotationMirror> quals = pqentry.getValue(); type.replaceAnnotations(quals); } } } return super.scan(type, matches); } }; /** * Completes a type by removing any unresolved polymorphic qualifiers, replacing them with the * top qualifiers. */ class Completer extends AnnotatedTypeScanner<Void, Void> { @Override protected Void scan(AnnotatedTypeMirror type, Void p) { if (type != null) { for (Map.Entry<AnnotationMirror, AnnotationMirror> pqentry : polyQuals.entrySet()) { AnnotationMirror top = pqentry.getKey(); AnnotationMirror poly = pqentry.getValue(); if (type.hasAnnotation(poly)) { type.removeAnnotation(poly); if (top == null) { // poly is PolyAll -> add all tops not explicitly given type.addMissingAnnotations(topQuals); } else if (type.getKind() != TypeKind.TYPEVAR && type.getKind() != TypeKind.WILDCARD) { // Do not add the top qualifiers to type variables and wildcards type.addAnnotation(top); } } } } return super.scan(type, p); } } private final PolyCollector collector; /** * A Helper class that tries to resolve the polymorhpic qualifiers with the most restricted * qualifier. The mapping is from the polymorhpic qualifier to the substitution for that * qualifier, which is a set of qualifiers. For most polymorphic qualifiers this will be a * singleton set. For the @PolyAll qualifier, this might be a set of qualifiers. */ private class PolyCollector extends SimpleAnnotatedTypeVisitor< Map<AnnotationMirror, Set<? extends AnnotationMirror>>, AnnotatedTypeMirror> { public Map<AnnotationMirror, Set<? extends AnnotationMirror>> reduce( Map<AnnotationMirror, Set<? extends AnnotationMirror>> r1, Map<AnnotationMirror, Set<? extends AnnotationMirror>> r2) { if (r1 == null || r1.isEmpty()) { return r2; } if (r2 == null || r2.isEmpty()) { return r1; } Map<AnnotationMirror, Set<? extends AnnotationMirror>> res = new HashMap<AnnotationMirror, Set<? extends AnnotationMirror>>(r1.size()); // Ensure that all qualifiers from r1 and r2 are visited. Set<AnnotationMirror> r2remain = AnnotationUtils.createAnnotationSet(); r2remain.addAll(r2.keySet()); for (Map.Entry<AnnotationMirror, Set<? extends AnnotationMirror>> kv1 : r1.entrySet()) { AnnotationMirror key1 = kv1.getKey(); Set<? extends AnnotationMirror> a1Annos = kv1.getValue(); Set<? extends AnnotationMirror> a2Annos = r2.get(key1); if (a2Annos != null && !a2Annos.isEmpty()) { r2remain.remove(key1); Set<? extends AnnotationMirror> lubs = qualhierarchy.leastUpperBounds(a1Annos, a2Annos); res.put(key1, lubs); } else { res.put(key1, a1Annos); } } for (AnnotationMirror key2 : r2remain) { res.put(key2, r2.get(key2)); } return res; } public Map<AnnotationMirror, Set<? extends AnnotationMirror>> visit( Iterable<? extends AnnotatedTypeMirror> types, Iterable<? extends AnnotatedTypeMirror> actualTypes) { Map<AnnotationMirror, Set<? extends AnnotationMirror>> result = new HashMap<AnnotationMirror, Set<? extends 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<AnnotationMirror, Set<? extends AnnotationMirror>> visitDeclared( AnnotatedDeclaredType type, AnnotatedTypeMirror actualType) { if (actualType.getKind() == TypeKind.INTERSECTION) { // We don't support poly annotations on intersection types // See Issue 744 // https://github.com/typetools/checker-framework/issues/744 return Collections.emptyMap(); } if (actualType.getKind() == TypeKind.TYPEVAR) { if (visited.contains(actualType.getUnderlyingType())) { return Collections.emptyMap(); } visited.add(actualType.getUnderlyingType()); Map<AnnotationMirror, Set<? extends AnnotationMirror>> result = visit(type, ((AnnotatedTypeVariable) actualType).getUpperBound()); visited.remove(actualType.getUnderlyingType()); return result; } if (actualType.getKind() == TypeKind.WILDCARD) { if (visited.contains(actualType.getUnderlyingType())) { return Collections.emptyMap(); } AnnotatedWildcardType wctype = (AnnotatedWildcardType) actualType; visited.add(actualType.getUnderlyingType()); Map<AnnotationMirror, Set<? extends AnnotationMirror>> result; if (wctype.getUnderlyingType().getExtendsBound() != null) { result = visit(type, wctype.getExtendsBound()); } else if (wctype.getUnderlyingType().getSuperBound() != null) { if (TypesUtils.isErasedSubtype( types, type.getUnderlyingType(), wctype.getSuperBound().getUnderlyingType())) { result = visit(type, wctype.getSuperBound()); } else if (wctype.getSuperBound().getKind() == TypeKind.NULL) { //TODO: poly annotation on wildcards need to be reviewed. This prevents // a crash in asSuper. result = Collections.emptyMap(); } else { AnnotatedTypeMirror superBoundAsSuper = AnnotatedTypes.asSuper(atypeFactory, wctype.getSuperBound(), type); result = visit(type, superBoundAsSuper); } } else { result = Collections.emptyMap(); } visited.remove(actualType.getUnderlyingType()); return result; } if (actualType.getKind() != type.getKind() || actualType == type) { return Collections.emptyMap(); } assert actualType.getKind() == type.getKind(); AnnotatedDeclaredType dcType = (AnnotatedDeclaredType) actualType; type = AnnotatedTypes.asSuper(atypeFactory, type, dcType); Map<AnnotationMirror, Set<? extends AnnotationMirror>> result = new HashMap<AnnotationMirror, Set<? extends AnnotationMirror>>(); for (Map.Entry<AnnotationMirror, AnnotationMirror> kv : polyQuals.entrySet()) { AnnotationMirror top = kv.getKey(); AnnotationMirror poly = kv.getValue(); if (top == null && dcType.hasAnnotation(POLYALL)) { // PolyAll qualifier result.put(poly, type.getAnnotations()); } else if (dcType.hasAnnotation(poly)) { AnnotationMirror typeQual = type.getAnnotationInHierarchy(top); result.put(poly, Collections.singleton(typeQual)); } } if (!type.wasRaw() && !dcType.wasRaw()) { result = reduce(result, visit(type.getTypeArguments(), dcType.getTypeArguments())); } return result; } @Override public Map<AnnotationMirror, Set<? extends AnnotationMirror>> visitPrimitive( AnnotatedPrimitiveType type, AnnotatedTypeMirror actualType) { Map<AnnotationMirror, Set<? extends AnnotationMirror>> result = new HashMap<AnnotationMirror, Set<? extends AnnotationMirror>>(); for (Map.Entry<AnnotationMirror, AnnotationMirror> kv : polyQuals.entrySet()) { AnnotationMirror top = kv.getKey(); AnnotationMirror poly = kv.getValue(); if (top == null && actualType.hasAnnotation(POLYALL)) { // PolyAll qualifier result.put(poly, type.getAnnotations()); } else if (actualType.hasAnnotation(poly)) { AnnotationMirror typeQual = type.getAnnotationInHierarchy(top); result.put(poly, Collections.singleton(typeQual)); } } return result; } @Override public Map<AnnotationMirror, Set<? extends AnnotationMirror>> visitNull( AnnotatedNullType type, AnnotatedTypeMirror actualType) { Map<AnnotationMirror, Set<? extends AnnotationMirror>> result = new HashMap<AnnotationMirror, Set<? extends AnnotationMirror>>(); for (Map.Entry<AnnotationMirror, AnnotationMirror> kv : polyQuals.entrySet()) { AnnotationMirror top = kv.getKey(); AnnotationMirror poly = kv.getValue(); if (top == null) { // PolyAll qualifier result.put(poly, type.getAnnotations()); } else if (actualType.hasAnnotation(poly)) { AnnotationMirror typeQual = type.getAnnotationInHierarchy(top); result.put(poly, Collections.singleton(typeQual)); } } if (!result.isEmpty()) { return result; } else { return super.visitNull(type, actualType); } } @Override public Map<AnnotationMirror, Set<? extends AnnotationMirror>> visitArray( AnnotatedArrayType type, AnnotatedTypeMirror actualType) { if (actualType.getKind() == TypeKind.INTERSECTION) { // We don't support poly annotations on intersection types // See Issue 744 // https://github.com/typetools/checker-framework/issues/744 return Collections.emptyMap(); } if (actualType.getKind() == TypeKind.DECLARED) { return visit(AnnotatedTypes.asSuper(atypeFactory, type, actualType), actualType); } if (actualType.getKind() == TypeKind.TYPEVAR) { if (visited.contains(actualType.getUnderlyingType())) { return Collections.emptyMap(); } visited.add(actualType.getUnderlyingType()); Map<AnnotationMirror, Set<? extends AnnotationMirror>> result = visit(type, ((AnnotatedTypeVariable) actualType).getUpperBound()); visited.remove(actualType.getUnderlyingType()); return result; } if (actualType.getKind() == TypeKind.WILDCARD) { if (visited.contains(actualType.getUnderlyingType())) { return Collections.emptyMap(); } visited.add(actualType.getUnderlyingType()); Map<AnnotationMirror, Set<? extends AnnotationMirror>> result = visit(type, ((AnnotatedWildcardType) actualType).getExtendsBound()); visited.remove(actualType.getUnderlyingType()); return result; } assert type.getKind() == actualType.getKind() : actualType; AnnotatedArrayType arType = (AnnotatedArrayType) actualType; Map<AnnotationMirror, Set<? extends AnnotationMirror>> result = new HashMap<AnnotationMirror, Set<? extends AnnotationMirror>>(); for (Map.Entry<AnnotationMirror, AnnotationMirror> kv : polyQuals.entrySet()) { AnnotationMirror top = kv.getKey(); AnnotationMirror poly = kv.getValue(); if (arType.hasAnnotation(poly)) { Set<AnnotationMirror> typeQuals; if (top == null) { // PolyAll qualifier typeQuals = type.getAnnotations(); } else { typeQuals = Collections.singleton(type.getAnnotationInHierarchy(top)); } result.put(poly, typeQuals); } } result = reduce(result, visit(type.getComponentType(), arType.getComponentType())); return result; } private final Set<TypeMirror> visited = new HashSet<TypeMirror>(); @Override public Map<AnnotationMirror, Set<? extends AnnotationMirror>> visitTypeVariable( AnnotatedTypeVariable type, AnnotatedTypeMirror actualType) { if (actualType.getKind() == TypeKind.WILDCARD) { // give up return Collections.emptyMap(); } AnnotatedTypeMirror typeSuper = AnnotatedTypes.asSuper(atypeFactory, type, actualType); if (typeSuper.getKind() != TypeKind.TYPEVAR) { return visit(typeSuper, actualType); } if (typeSuper.getKind() == actualType.getKind() && type.getKind() == actualType.getKind()) { // I've preserved the old logic here, I am not sure the actual reasoning // however, please see the else case as to where it fails AnnotatedTypeVariable tvType = (AnnotatedTypeVariable) typeSuper; if (visited.contains(actualType.getUnderlyingType())) { return Collections.emptyMap(); } visited.add(type.getUnderlyingType()); // a type variable cannot be annotated Map<AnnotationMirror, Set<? extends AnnotationMirror>> result = visit(type.getUpperBound(), tvType.getUpperBound()); visited.remove(type.getUnderlyingType()); return result; } else { // When using the polyCollector we compare the formal parameters to the actual // arguments but, when the formal parameters are uses of method type parameters // then the declared formal parameters may not actually be supertypes of their arguments // (though they should be if we substituted them for the method call's type arguments) // For an example of this see framework/tests/all-system/PolyCollectorTypeVars.java return visit(type.getUpperBound(), actualType); } } @Override public Map<AnnotationMirror, Set<? extends AnnotationMirror>> visitWildcard( AnnotatedWildcardType type, AnnotatedTypeMirror actualType) { if (type.isUninferredTypeArgument() || !TypesUtils.isErasedSubtype( types, type.getUnderlyingType(), actualType.getUnderlyingType())) { return Collections.emptyMap(); } AnnotatedTypeMirror typeSuper = AnnotatedTypes.asSuper(atypeFactory, type, actualType); if (typeSuper.getKind() != TypeKind.WILDCARD) { return visit(typeSuper, actualType); } // TODO: hack against unbound wildcard introduced by // separate compilation. Test against Issue 257 test. if (((com.sun.tools.javac.code.Type.WildcardType) typeSuper.getUnderlyingType()) .isUnbound()) { return Collections.emptyMap(); } if (actualType.getKind() != TypeKind.WILDCARD && actualType.getKind() != TypeKind.TYPEVAR) { // currently because the default action of inferTypeArgs is to use a wildcard when we fail // to infer a type, the actualType might not be a wildcard return Collections.emptyMap(); } AnnotatedWildcardType wcType = (AnnotatedWildcardType) typeSuper; if (visited.contains(actualType.getUnderlyingType())) { return Collections.emptyMap(); } visited.add(type.getUnderlyingType()); Map<AnnotationMirror, Set<? extends AnnotationMirror>> result; if (type.getExtendsBound() != null && wcType.getExtendsBound() != null) { result = visit(type.getExtendsBound(), wcType.getExtendsBound()); } else if (type.getSuperBound() != null && wcType.getSuperBound() != null) { result = visit(type.getSuperBound(), wcType.getSuperBound()); } else { result = new HashMap<AnnotationMirror, Set<? extends AnnotationMirror>>(); } visited.remove(type.getUnderlyingType()); return result; } } }