blob: 9d8ea284ed777925837450d145649a0360ff5d5c [file] [log] [blame]
package org.checkerframework.common.basetype;
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.Collections;
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 javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.framework.source.DiagMessage;
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.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.type.visitor.SimpleAnnotatedTypeScanner;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypeAnnotationUtils;
/**
* A visitor to validate the types in a tree.
*
* <p>Note: A TypeValidator (this class and its subclasses) cannot tell whether an annotation was
* written by a programmer or defaulted/inferred/computed by the Checker Framework, because the
* AnnotatedTypeMirror does not make distinctions about which annotations in an AnnotatedTypeMirror
* were explicitly written and which were added by a checker. To issue a warning/error only when a
* programmer writes an annotation, override {@link BaseTypeVisitor#visitAnnotatedType} and {@link
* BaseTypeVisitor#visitVariable}.
*/
public class BaseTypeValidator extends AnnotatedTypeScanner<Void, Tree> implements TypeValidator {
/** Is the type valid? This is side-effected by the visitor, and read at the end of visiting. */
protected boolean isValid = true;
/** Should the primary annotation on the top level type be checked? */
protected boolean checkTopLevelDeclaredOrPrimitiveType = true;
/** BaseTypeChecker. */
protected final BaseTypeChecker checker;
/** BaseTypeVisitor. */
protected final BaseTypeVisitor<?> visitor;
/** AnnotatedTypeFactory. */
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;
}
/**
* Validate the type against the given tree. This method both issues error messages and also
* returns a boolean value.
*
* <p>This is the entry point to the type validator. Neither this method nor visit should be
* called directly by a visitor, only use {@link BaseTypeVisitor#validateTypeOf(Tree)}.
*
* <p>This method is only called on top-level types, but it validates the entire type including
* components of a compound type. Subclasses should override this only if there is special-case
* behavior that should be performed only on top-level types.
*
* @param type the type to validate
* @param tree the tree from which the type originated. If the tree is a method tree, {@code type}
* is its return type. If the tree is a variable tree, {@code type} is the variable's type.
* @return true if the type is valid
*/
@Override
public boolean isValid(AnnotatedTypeMirror type, Tree tree) {
List<DiagMessage> diagMessages =
isValidStructurally(atypeFactory.getQualifierHierarchy(), type);
if (!diagMessages.isEmpty()) {
for (DiagMessage d : diagMessages) {
checker.report(tree, d);
}
return false;
}
this.isValid = true;
this.checkTopLevelDeclaredOrPrimitiveType =
shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree);
visit(type, tree);
return this.isValid;
}
/**
* Should the top-level declared or primitive type be checked?
*
* <p>If {@code type} is not a declared or primitive type, then this method returns true.
*
* <p>Top-level type is not checked if tree is a local variable or an expression tree.
*
* @param type AnnotatedTypeMirror being validated
* @param tree a Tree whose type is {@code type}
* @return whether or not the top-level type should be checked, if {@code type} is a declared or
* primitive type.
*/
protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType(
AnnotatedTypeMirror type, Tree tree) {
if (type.getKind() != TypeKind.DECLARED && !type.getKind().isPrimitive()) {
return true;
}
return !TreeUtils.isLocalVariable(tree)
&& (!TreeUtils.isExpressionTree(tree) || TreeUtils.isTypeTree(tree));
}
/**
* Performs some well-formedness checks on the given {@link AnnotatedTypeMirror}. Returns a list
* of failures. If successful, returns an empty list. The method will never return failures for a
* valid type, but might not catch all invalid types.
*
* <p>This method ensures that the type is structurally or lexically well-formed, but it does not
* check whether the annotations are semantically sensible. Subclasses should generally override
* visit methods such as {@link #visitDeclared} rather than this method.
*
* <p>Currently, this implementation checks the following (subclasses can extend this behavior):
*
* <ol>
* <li>There should not be multiple annotations from the same qualifier hierarchy.
* <li>There should not be more annotations than the width of the QualifierHierarchy.
* <li>If the type is not a type variable, then the number of annotations should be the same as
* the width of the QualifierHierarchy.
* <li>These properties should also hold recursively for component types of arrays and for
* bounds of type variables and wildcards.
* </ol>
*
* @param qualifierHierarchy the QualifierHierarchy
* @param type the type to test
* @return list of reasons the type is invalid, or empty list if the type is valid
*/
protected List<DiagMessage> isValidStructurally(
QualifierHierarchy qualifierHierarchy, AnnotatedTypeMirror type) {
SimpleAnnotatedTypeScanner<List<DiagMessage>, QualifierHierarchy> scanner =
new SimpleAnnotatedTypeScanner<>(
(atm, q) -> isTopLevelValidType(q, atm),
DiagMessage::mergeLists,
Collections.emptyList());
return scanner.visit(type, qualifierHierarchy);
}
/**
* Checks every property listed in {@link #isValidStructurally}, but only for the top level type.
* If successful, returns an empty list. If not successful, returns diagnostics.
*
* @param qualifierHierarchy the QualifierHierarchy
* @param type the type to be checked
* @return the diagnostics indicating failure, or an empty list if successful
*/
// This method returns a singleton or empyty list. Its return type is List rather than
// DiagMessage (with null indicting success) because its caller, isValidStructurally(), expects
// a list.
protected List<DiagMessage> isTopLevelValidType(
QualifierHierarchy qualifierHierarchy, AnnotatedTypeMirror type) {
// multiple annotations from the same hierarchy
Set<AnnotationMirror> annotations = type.getAnnotations();
Set<AnnotationMirror> seenTops = AnnotationUtils.createAnnotationSet();
for (AnnotationMirror anno : annotations) {
AnnotationMirror top = qualifierHierarchy.getTopAnnotation(anno);
if (AnnotationUtils.containsSame(seenTops, top)) {
return Collections.singletonList(
new DiagMessage(Kind.ERROR, "conflicting.annos", annotations, type));
}
seenTops.add(top);
}
boolean canHaveEmptyAnnotationSet = QualifierHierarchy.canHaveEmptyAnnotationSet(type);
// wrong number of annotations
if (!canHaveEmptyAnnotationSet && seenTops.size() < qualifierHierarchy.getWidth()) {
return Collections.singletonList(
new DiagMessage(Kind.ERROR, "too.few.annotations", annotations, type));
}
// success
return Collections.emptyList();
}
protected void reportValidityResult(
final @CompilerMessageKey String errorType, final AnnotatedTypeMirror type, final Tree p) {
checker.reportError(p, errorType, type.getAnnotations(), type.toString());
isValid = false;
}
/**
* Like {@link #reportValidityResult}, but the type is printed in the error message without
* annotations. This method would print "annotation @NonNull is not permitted on type int",
* whereas {@link #reportValidityResult} would print "annotation @NonNull is not permitted on
* type @NonNull int". In addition, when the underlying type is a compound type such as
* {@code @Bad List<String>}, the erased type will be used, i.e., "{@code List}" will print
* instead of "{@code @Bad List<String>}".
*/
protected void reportValidityResultOnUnannotatedType(
final @CompilerMessageKey String errorType, final AnnotatedTypeMirror type, final Tree p) {
TypeMirror underlying =
TypeAnnotationUtils.unannotatedType(type.getErased().getUnderlyingType());
checker.reportError(p, errorType, type.getAnnotations(), underlying.toString());
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" and print the bounds along
* with the invalid type rather than a "type.invalid".
*
* @param type the type with invalid bounds
* @param tree where to report the error
*/
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:
throw new BugInCF("Type is not bounded.%ntype=%s%ntree=%s", type, tree);
}
checker.reportError(
tree,
"bound",
label,
type.toString(),
upperBound.toString(true),
lowerBound.toString(true));
isValid = false;
}
protected void reportInvalidType(final AnnotatedTypeMirror type, final Tree p) {
reportValidityResult("type.invalid", type, p);
}
/**
* Report an "annotations.on.use" error for the given type and tree.
*
* @param type the type with invalid annotations
* @param p the tree where to report the error
*/
protected void reportInvalidAnnotationsOnUse(final AnnotatedTypeMirror type, final Tree p) {
reportValidityResultOnUnannotatedType("annotations.on.use", 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 (checkTopLevelDeclaredOrPrimitiveType && !skipChecks) {
// Ensure that type use is a subtype of the element type
// isValidUse determines the erasure of the types.
Set<AnnotationMirror> bounds =
atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType());
AnnotatedDeclaredType elemType = type.deepCopy();
elemType.clearAnnotations();
elemType.addAnnotations(bounds);
if (!visitor.isValidUse(elemType, type, tree)) {
reportInvalidAnnotationsOnUse(type, tree);
}
}
// Set checkTopLevelDeclaredType to true, because the next time visitDeclared is called,
// the type isn't the top level, so always do the check.
checkTopLevelDeclaredOrPrimitiveType = true;
/*
* 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.
break;
default:
// The parameterized type is the result of some expression tree.
// No need to do anything further.
break;
}
return Pair.of(typeargtree, type);
}
@Override
@SuppressWarnings("signature:argument") // PrimitiveType.toString(): @PrimitiveType
public Void visitPrimitive(AnnotatedPrimitiveType type, Tree tree) {
if (!checkTopLevelDeclaredOrPrimitiveType
|| checker.shouldSkipUses(type.getUnderlyingType().toString())) {
return super.visitPrimitive(type, tree);
}
if (!visitor.isValidUse(type, tree)) {
reportInvalidAnnotationsOnUse(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)) {
reportInvalidAnnotationsOnUse(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" error if
* they are not.
*
* @param type the type to check
* @param tree the type's tree
*/
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(),
element.getSimpleName(),
element.getTypeParameters());
return null;
}
@Override
public Void visitTypeVariable(AnnotatedTypeVariable type, Tree tree) {
if (visitedNodes.containsKey(type)) {
return visitedNodes.get(type);
}
if (type.isDeclaration() && !areBoundsValid(type.getUpperBound(), type.getLowerBound())) {
reportInvalidBounds(type, tree);
}
return super.visitTypeVariable(type, tree);
}
@Override
public Void visitWildcard(AnnotatedWildcardType type, Tree tree) {
if (visitedNodes.containsKey(type)) {
return visitedNodes.get(type);
}
if (!areBoundsValid(type.getExtendsBound(), type.getSuperBound())) {
reportInvalidBounds(type, tree);
}
return super.visitWildcard(type, tree);
}
/**
* Returns true if the effective annotations on the upperBound are above those on the lowerBound.
*
* @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
return true;
}
}