package org.checkerframework.common.basetype; /*>>> import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; */ import com.sun.source.tree.AnnotatedTypeTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.ParameterizedTypeTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import java.util.List; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; import org.checkerframework.framework.qual.PolyAll; import org.checkerframework.framework.source.Result; 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.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.AnnotatedTypeParameterBounds; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner; import org.checkerframework.framework.util.AnnotatedTypes; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.ErrorReporter; import org.checkerframework.javacutil.Pair; import org.checkerframework.javacutil.TreeUtils; /** A visitor to validate the types in a tree. */ public class BaseTypeValidator extends AnnotatedTypeScanner<Void, Tree> implements TypeValidator { protected boolean isValid = true; protected final BaseTypeChecker checker; protected final BaseTypeVisitor<?> visitor; protected final AnnotatedTypeFactory atypeFactory; // TODO: clean up coupling between components public BaseTypeValidator( BaseTypeChecker checker, BaseTypeVisitor<?> visitor, AnnotatedTypeFactory atypeFactory) { this.checker = checker; this.visitor = visitor; this.atypeFactory = atypeFactory; } /** * The entry point to the type validator. Validate the type against the given tree. Neither this * method nor visit should be called directly by a visitor, only use {@link * BaseTypeVisitor#validateTypeOf(Tree)}. * * @param type the type to validate * @param tree the tree from which the type originated. Note that the tree might be a method * tree -- the return type should then be validated. Note that the tree might be a variable * tree -- the field type should then be validated. * @return true, iff the type is valid */ @Override public boolean isValid(AnnotatedTypeMirror type, Tree tree) { this.isValid = true; visit(type, tree); return this.isValid; } protected void reportValidityResult( final /*@CompilerMessageKey*/ String errorType, final AnnotatedTypeMirror type, final Tree p) { checker.report(Result.failure(errorType, type.getAnnotations(), type.toString()), p); isValid = false; } /** * Most errors reported by this class are of the form type.invalid. This method reports when the * bounds of a wildcard or type variable don't make sense. Bounds make sense when the effective * annotations on the upper bound are supertypes of those on the lower bounds for all * hierarchies. To ensure that this subtlety is not lost on users, we report * "bound.type.incompatible" and print the bounds along with the invalid type rather than a * "type.invalid". */ protected void reportInvalidBounds(final AnnotatedTypeMirror type, final Tree tree) { final String label; final AnnotatedTypeMirror upperBound; final AnnotatedTypeMirror lowerBound; switch (type.getKind()) { case TYPEVAR: label = "type parameter"; upperBound = ((AnnotatedTypeVariable) type).getUpperBound(); lowerBound = ((AnnotatedTypeVariable) type).getLowerBound(); break; case WILDCARD: label = "wildcard"; upperBound = ((AnnotatedWildcardType) type).getExtendsBound(); lowerBound = ((AnnotatedWildcardType) type).getSuperBound(); break; default: ErrorReporter.errorAbort( "Type is not bounded.\n" + "type=" + type + "\n" + "tree=" + tree); label = null; // dead code upperBound = null; lowerBound = null; } checker.report( Result.failure( "bound.type.incompatible", label, type.toString(), upperBound.toString(true), lowerBound.toString(true)), tree); isValid = false; } protected void reportError(final AnnotatedTypeMirror type, final Tree p) { reportValidityResult("type.invalid", type, p); } @Override public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { if (visitedNodes.containsKey(type)) { return visitedNodes.get(type); } final boolean skipChecks = checker.shouldSkipUses(type.getUnderlyingType().asElement()); if (!skipChecks) { // Ensure that type use is a subtype of the element type // isValidUse determines the erasure of the types. AnnotatedDeclaredType elemType = (AnnotatedDeclaredType) atypeFactory.getAnnotatedType(type.getUnderlyingType().asElement()); if (!visitor.isValidUse(elemType, type, tree)) { reportError(type, tree); } } /* * Try to reconstruct the ParameterizedTypeTree from the given tree. * TODO: there has to be a nicer way to do this... */ Pair<ParameterizedTypeTree, AnnotatedDeclaredType> p = extractParameterizedTypeTree(tree, type); ParameterizedTypeTree typeArgTree = p.first; type = p.second; if (typeArgTree == null) { return super.visitDeclared(type, tree); } // else // We put this here because we don't want to put it in visitedNodes before calling // super (in the else branch) because that would cause the super implementation // to detect that we've already visited type and to immediately return visitedNodes.put(type, null); // We have a ParameterizedTypeTree -> visit it. visitParameterizedType(type, typeArgTree); /* * Instead of calling super with the unchanged "tree", adapt the * second argument to be the corresponding type argument tree. This * ensures that the first and second parameter to this method always * correspond. visitDeclared is the only method that had this * problem. */ List<? extends AnnotatedTypeMirror> tatypes = type.getTypeArguments(); if (tatypes == null) { return null; } // May be zero for a "diamond" (inferred type args in constructor // invocation). int numTypeArgs = typeArgTree.getTypeArguments().size(); if (numTypeArgs != 0) { // TODO: this should be an equality, but in // http://buffalo.cs.washington.edu:8080/job/jdk6-daikon-typecheck/2061/console // it failed with: // daikon/Debug.java; message: size mismatch for type arguments: // @NonNull Object and Class<?> // but I didn't manage to reduce it to a test case. assert tatypes.size() <= numTypeArgs || skipChecks : "size mismatch for type arguments: " + type + " and " + typeArgTree; for (int i = 0; i < tatypes.size(); ++i) { scan(tatypes.get(i), typeArgTree.getTypeArguments().get(i)); } } // Don't call the super version, because it creates a mismatch // between // the first and second parameters. // return super.visitDeclared(type, tree); return null; } private Pair<ParameterizedTypeTree, AnnotatedDeclaredType> extractParameterizedTypeTree( Tree tree, AnnotatedDeclaredType type) { ParameterizedTypeTree typeargtree = null; switch (tree.getKind()) { case VARIABLE: Tree lt = ((VariableTree) tree).getType(); if (lt instanceof ParameterizedTypeTree) { typeargtree = (ParameterizedTypeTree) lt; } else { // System.out.println("Found a: " + lt); } break; case PARAMETERIZED_TYPE: typeargtree = (ParameterizedTypeTree) tree; break; case NEW_CLASS: NewClassTree nct = (NewClassTree) tree; ExpressionTree nctid = nct.getIdentifier(); if (nctid.getKind() == Tree.Kind.PARAMETERIZED_TYPE) { typeargtree = (ParameterizedTypeTree) nctid; /* * This is quite tricky... for anonymous class instantiations, * the type at this point has no type arguments. By doing the * following, we get the type arguments again. */ type = (AnnotatedDeclaredType) atypeFactory.getAnnotatedType(typeargtree); } break; case ANNOTATED_TYPE: AnnotatedTypeTree tr = (AnnotatedTypeTree) tree; ExpressionTree undtr = tr.getUnderlyingType(); if (undtr instanceof ParameterizedTypeTree) { typeargtree = (ParameterizedTypeTree) undtr; } else if (undtr instanceof IdentifierTree) { // @Something D -> Nothing to do } else { // TODO: add more test cases to ensure that nested types are // handled correctly, // e.g. @Nullable() List<@Nullable Object>[][] Pair<ParameterizedTypeTree, AnnotatedDeclaredType> p = extractParameterizedTypeTree(undtr, type); typeargtree = p.first; type = p.second; } break; case IDENTIFIER: case ARRAY_TYPE: case NEW_ARRAY: case MEMBER_SELECT: case UNBOUNDED_WILDCARD: case EXTENDS_WILDCARD: case SUPER_WILDCARD: case TYPE_PARAMETER: // Nothing to do. // System.out.println("Found a: " + (tree instanceof // ParameterizedTypeTree)); break; default: // the parameterized type is the result of some expression tree. // No need to do anything further. break; // System.err.printf("TypeValidator.visitDeclared unhandled tree: %s of kind %s\n", // tree, tree.getKind()); } return Pair.of(typeargtree, type); } @Override public Void visitPrimitive(AnnotatedPrimitiveType type, Tree tree) { if (checker.shouldSkipUses(type.getUnderlyingType().toString())) { return super.visitPrimitive(type, tree); } if (!visitor.isValidUse(type, tree)) { reportError(type, tree); } return super.visitPrimitive(type, tree); } @Override public Void visitArray(AnnotatedArrayType type, Tree tree) { // TODO: is there already or add a helper method // to determine the non-array component type AnnotatedTypeMirror comp = type; do { comp = ((AnnotatedArrayType) comp).getComponentType(); } while (comp.getKind() == TypeKind.ARRAY); if (comp.getKind() == TypeKind.DECLARED && checker.shouldSkipUses( ((AnnotatedDeclaredType) comp).getUnderlyingType().asElement())) { return super.visitArray(type, tree); } if (!visitor.isValidUse(type, tree)) { reportError(type, tree); } return super.visitArray(type, tree); } /** * Checks that the annotations on the type arguments supplied to a type or a method invocation * are within the bounds of the type variables as declared, and issues the * "type.argument.type.incompatible" error if they are not. * * <p>This method used to be visitParameterizedType, which incorrectly handles the main * annotation on generic types. */ protected Void visitParameterizedType(AnnotatedDeclaredType type, ParameterizedTypeTree tree) { // System.out.printf("TypeValidator.visitParameterizedType: type: %s, tree: %s\n", // type, tree); if (TreeUtils.isDiamondTree(tree)) { return null; } final TypeElement element = (TypeElement) type.getUnderlyingType().asElement(); if (checker.shouldSkipUses(element)) { return null; } List<AnnotatedTypeParameterBounds> bounds = atypeFactory.typeVariablesFromUse(type, element); visitor.checkTypeArguments(tree, bounds, type.getTypeArguments(), tree.getTypeArguments()); return null; } @Override public Void visitTypeVariable(AnnotatedTypeVariable type, Tree tree) { if (visitedNodes.containsKey(type)) { return visitedNodes.get(type); } // TODO why is this not needed? // visitedNodes.put(type, null); if (type.isDeclaration() && !areBoundsValid(type.getUpperBound(), type.getLowerBound())) { reportInvalidBounds(type, tree); } // Keep in sync with visitWildcard Set<AnnotationMirror> onVar = type.getAnnotations(); if (!onVar.isEmpty()) { // System.out.printf("BaseTypeVisitor.TypeValidator.visitTypeVariable(type: %s, tree: %s)%n", // type, tree); // TODO: the following check should not be necessary, once we are // able to // recurse on type parameters in AnnotatedTypes.isValidType (see // todo there). { // Check whether multiple qualifiers from the same hierarchy // appear. checkConflictingPrimaryAnnos(type, tree); } // TODO: because of the way AnnotatedTypeMirror fixes up the bounds, // i.e. an annotation on the type variable always replaces a // corresponding // annotation in the bound, some of these checks are not actually // meaningful. /*if (type.getUpperBoundField() != null) { AnnotatedTypeMirror upper = type.getUpperBoundField(); for (AnnotationMirror aOnVar : onVar) { if (upper.isAnnotatedInHierarchy(aOnVar) && !checker.getQualifierHierarchy().isSubtype(aOnVar, upper.findAnnotationInHierarchy(aOnVar))) { this.reportError(type, tree); } } upper.replaceAnnotations(onVar); }*/ } return super.visitTypeVariable(type, tree); } @Override public Void visitWildcard(AnnotatedWildcardType type, Tree tree) { if (visitedNodes.containsKey(type)) { return visitedNodes.get(type); } // TODO why is this not neede? // visitedNodes.put(type, null); if (!areBoundsValid(type.getExtendsBound(), type.getSuperBound())) { reportInvalidBounds(type, tree); } // Keep in sync with visitTypeVariable Set<AnnotationMirror> onVar = type.getAnnotations(); if (!onVar.isEmpty()) { // System.out.printf("BaseTypeVisitor.TypeValidator.visitWildcard(type: %s, tree: %s)", // type, tree); // TODO: the following check should not be necessary, once we are // able to // recurse on type parameters in AnnotatedTypes.isValidType (see // todo there). { // Check whether multiple qualifiers from the same hierarchy // appear. checkConflictingPrimaryAnnos(type, tree); } /* TODO: see note with visitTypeVariable if (type.getExtendsBoundField() != null) { AnnotatedTypeMirror upper = type.getExtendsBoundField(); for (AnnotationMirror aOnVar : onVar) { if (upper.isAnnotatedInHierarchy(aOnVar) && !atypeFactory.getQualifierHierarchy().isSubtype(aOnVar, upper.findAnnotationInHierarchy(aOnVar))) { this.reportError(type, tree); } } upper.replaceAnnotations(onVar); } */ if (type.getSuperBoundField() != null) { AnnotatedTypeMirror lower = type.getSuperBoundField(); for (AnnotationMirror aOnVar : onVar) { if (lower.isAnnotatedInHierarchy(aOnVar) && !atypeFactory .getQualifierHierarchy() .isSubtype(lower.getAnnotationInHierarchy(aOnVar), aOnVar)) { this.reportError(type, tree); } } lower.replaceAnnotations(onVar); } } return super.visitWildcard(type, tree); } @Override public Void visitNull(final AnnotatedNullType type, final Tree tree) { checkConflictingPrimaryAnnos(type, tree); return super.visitNull(type, tree); } /** * @return true if the effective annotations on the upperBound are above those on the lowerBound */ public boolean areBoundsValid( final AnnotatedTypeMirror upperBound, final AnnotatedTypeMirror lowerBound) { final QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy(); final Set<AnnotationMirror> upperBoundAnnos = AnnotatedTypes.findEffectiveAnnotations(qualifierHierarchy, upperBound); final Set<AnnotationMirror> lowerBoundAnnos = AnnotatedTypes.findEffectiveAnnotations(qualifierHierarchy, lowerBound); if (upperBoundAnnos.size() == lowerBoundAnnos.size()) { return qualifierHierarchy.isSubtype(lowerBoundAnnos, upperBoundAnnos); } // else // When upperBoundAnnos.size() != lowerBoundAnnos.size() one of the two bound types will // be reported as invalid. Therefore, we do not do any other comparisons nor do we report // a bound.type.incompatible return true; } /** * Determines if there are multiple qualifiers from a single hierarchy in type's primary * annotations. If so, report an error. * * @param type the type to check * @param tree tree on which an error is reported * @return true if an error was reported */ public boolean checkConflictingPrimaryAnnos(final AnnotatedTypeMirror type, final Tree tree) { boolean error = false; Set<AnnotationMirror> seenTops = AnnotationUtils.createAnnotationSet(); for (AnnotationMirror aOnVar : type.getAnnotations()) { if (AnnotationUtils.areSameByClass(aOnVar, PolyAll.class)) { continue; } AnnotationMirror top = atypeFactory.getQualifierHierarchy().getTopAnnotation(aOnVar); if (seenTops.contains(top)) { this.reportError(type, tree); error = true; } seenTops.add(top); } return error; } }