package checkers.basetype; import java.util.*; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.MirroredTypeException; import javax.lang.model.type.TypeKind; import javax.lang.model.util.ElementFilter; import com.sun.source.tree.*; import com.sun.source.util.*; import checkers.compilermsgs.quals.CompilerMessageKey; import checkers.nullness.NullnessChecker; import checkers.quals.Unused; import checkers.source.*; import checkers.types.*; import checkers.types.AnnotatedTypeMirror.*; import checkers.types.visitors.AnnotatedTypeScanner; import checkers.util.*; /** * A {@link SourceVisitor} that performs assignment and pseudo-assignment * checking, method invocation checking, and assignability checking. * * <p> * * This implementation uses the {@link AnnotatedTypeFactory} implementation * provided by an associated {@link BaseTypeChecker}; its visitor methods will * invoke this factory on parts of the AST to determine the "annotated type" of * an expression. Then, the visitor methods will check the types in assignments * and pseudo-assignments using {@link #commonAssignmentCheck}, which * ultimately calls the {@link BaseTypeChecker#isSubtype} method and reports * errors that violate Java's rules of assignment. * * <p> * * Note that since this implementation only performs assignment and * pseudo-assignment checking, other rules for custom type systems must be added * in subclasses (e.g., dereference checking in the {@link NullnessChecker} is * implemented in the {@link NullnessChecker}'s * {@link TreeScanner#visitMemberSelect} method). * * <p> * * This implementation does the following checks: * 1. <b>Assignment and Pseudo-Assignment Check</b>: * It verifies that any assignment type check, using * {@code Checker.isSubtype} method. This includes method invocation and * method overriding checks. * * 2. <b>Type Validity Check</b>: * It verifies that any user-supplied type is a valid type, using * {@code Checker.isValidUse} method. * * 3. <b>(Re-)Assignability Check</b>: * It verifies that any assignment is valid, using * {@code Checker.isAssignable} method. * * @see "JLS $4" * @see BaseTypeChecker#isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror) * @see AnnotatedTypeFactory */ public class BaseTypeVisitor<R, P> extends SourceVisitor<R, P> { /** The checker corresponding to this visitor. */ protected final BaseTypeChecker checker; /** The annotation factory to use for creating annotations. */ protected final AnnotationUtils annoFactory; /** The options that were provided to the checker using this visitor. */ private final Map<String, String> options; /** For obtaining line numbers in -Ashowchecks debugging output. */ private final SourcePositions positions; /** utilities class for annotated types **/ protected final AnnotatedTypes annoTypes; /** For storing visitor state**/ protected final VisitorState visitorState; protected final AnnotatedTypeFactory plainFactory; /** * @param checker the typechecker associated with this visitor (for * callbacks to {@link BaseTypeChecker#isSubtype}) * @param root the root of the AST that this visitor operates on */ public BaseTypeVisitor(BaseTypeChecker checker, CompilationUnitTree root) { super(checker, root); this.checker = checker; ProcessingEnvironment env = checker.getProcessingEnvironment(); this.annoFactory = AnnotationUtils.getInstance(env); this.options = env.getOptions(); this.positions = trees.getSourcePositions(); this.annoTypes = new AnnotatedTypes(checker.getProcessingEnvironment(), atypeFactory); this.visitorState = atypeFactory.getVisitorState(); this.plainFactory = new AnnotatedTypeFactory(checker.getProcessingEnvironment(), null, root, null); } // ********************************************************************** // Responsible for updating the factory for the location (for performance) // ********************************************************************** @Override public R scan(Tree tree, P p) { if (tree != null && getCurrentPath() != null) this.visitorState.setPath(new TreePath(getCurrentPath(), tree)); return super.scan(tree, p); } private boolean hasExplicitConstructor(ClassTree node) { TypeElement elem = TreeUtils.elementFromDeclaration(node); return !ElementFilter.constructorsIn(elem.getEnclosedElements()).isEmpty(); } @Override public R visitClass(ClassTree node, P p) { AnnotatedDeclaredType preACT = visitorState.getClassType(); ClassTree preCT = visitorState.getClassTree(); AnnotatedDeclaredType preAMT = visitorState.getMethodReceiver(); MethodTree preMT = visitorState.getMethodTree(); visitorState.setClassType(atypeFactory.getAnnotatedType(node)); visitorState.setClassTree(node); visitorState.setMethodReceiver(null); visitorState.setMethodTree(null); try { if (!hasExplicitConstructor(node)) { checkDefaultConstructor(node); } return super.visitClass(node, p); } finally { this.visitorState.setClassType(preACT); this.visitorState.setClassTree(preCT); this.visitorState.setMethodReceiver(preAMT); this.visitorState.setMethodTree(preMT); } } protected void checkDefaultConstructor(ClassTree node) { } /** * Performs pseudo-assignment check: checks that the method obeys override * and subtype rules to all overridden methods. * * The override rule specifies that a method, m1, may override a method * m2 only if: * <ul> * <li> m1 return type is a subtype of m2 </li> * <li> m1 receiver type is a supertype of m2 <li> * <li> m1 parameters are supertypes of corresponding m2 parameters </li> * </ul> * * Also, it issues a "missing.this" error for static method annotated * receivers. */ @Override public R visitMethod(MethodTree node, P p) { AnnotatedExecutableType methodType = atypeFactory.getAnnotatedType(node); AnnotatedDeclaredType preMRT = visitorState.getMethodReceiver(); MethodTree preMT = visitorState.getMethodTree(); visitorState.setMethodReceiver(methodType.getReceiverType()); visitorState.setMethodTree(node); try { Element elt = InternalUtils.symbol(node); assert elt != null : "no symbol for method"; if (InternalUtils.isAnonymousConstructor(node)) // We shouldn't dig deeper return null; // constructor return types are null if (node.getReturnType() != null) typeValidator.visit(methodType.getReturnType(), node.getReturnType()); ExecutableElement methodElement = TreeUtils.elementFromDeclaration(node); AnnotatedDeclaredType enclosingType = (AnnotatedDeclaredType)atypeFactory.getAnnotatedType( methodElement.getEnclosingElement()); // Find which method this overrides! Map<AnnotatedDeclaredType, ExecutableElement> overridenMethods = annoTypes.overriddenMethods(methodElement); for (Map.Entry<AnnotatedDeclaredType, ExecutableElement> pair: overridenMethods.entrySet()) { AnnotatedDeclaredType overriddenType = pair.getKey(); AnnotatedExecutableType overriddenMethod = annoTypes.asMemberOf(overriddenType, pair.getValue()); checkOverride(node, enclosingType, overriddenMethod, overriddenType, p); } return super.visitMethod(node, p); } finally { visitorState.setMethodReceiver(preMRT); visitorState.setMethodTree(preMT); } } // ********************************************************************** // Assignment checkers and pseudo-assignments // ********************************************************************** @Override public R visitVariable(VariableTree node, P p) { validateTypeOf(node); // If there's no assignment in this variable declaration, skip it. if (node.getInitializer() == null) return super.visitVariable(node, p); commonAssignmentCheck(node, node.getInitializer(), "assignment.type.incompatible", p); return super.visitVariable(node, p); } /** * Performs two checks: subtyping and assignability checks, using * {@link #commonAssignmentCheck(Tree, ExpressionTree, String, Object)}. * * If the subtype check fails, it issues a "assignment.type.incompatible" error. */ @Override public R visitAssignment(AssignmentTree node, P p) { commonAssignmentCheck(node.getVariable(), node.getExpression(), "assignment.type.incompatible", p); return super.visitAssignment(node, p); } /** * Performs a subtype check, to test whether the node expression * iterable type is a subtype of the variable type in the enhanced for * loop. * * If the subtype check fails, it issues a "type.incompatible" error. */ @Override public R visitEnhancedForLoop(EnhancedForLoopTree node, P p) { AnnotatedTypeMirror var = atypeFactory.getAnnotatedType(node.getVariable()); AnnotatedTypeMirror iterableType = atypeFactory.getAnnotatedType(node.getExpression()); AnnotatedTypeMirror iteratedType = annoTypes.getIteratedType(iterableType); validateTypeOf(node.getVariable()); commonAssignmentCheck(var, iteratedType, node.getExpression(), "enhancedfor.type.incompatible", p); return super.visitEnhancedForLoop(node, p); } private boolean isSuperInvocation(MethodInvocationTree node) { return (node.getMethodSelect().getKind() == Tree.Kind.IDENTIFIER && ((IdentifierTree)node.getMethodSelect()).getName().contentEquals("super")); } /** * Performs a method invocation check. * * An invocation of a method, m, on the receiver, r is valid only if: * <ul> * <li> passed arguments are subtypes of corresponding m parameters </li> * <li> r is a subtype of m receiver type </li> * <li> if m is generic, passed type arguments are subtypes * of m type variables <li> * </ul> */ @Override public R visitMethodInvocation(MethodInvocationTree node, P p) { // Skip calls to the Enum constructor (they're generated by javac and // hard to check). if (isEnumSuper(node)) return super.visitMethodInvocation(node, p); if (shouldSkip(node)) return super.visitMethodInvocation(node, p); AnnotatedExecutableType invokedMethod = atypeFactory.methodFromUse(node); // Get type arguments as passed to the invocation. List<AnnotatedTypeMirror> typeargs = new LinkedList<AnnotatedTypeMirror>(); for (Tree tree : node.getTypeArguments()) typeargs.add(atypeFactory.getAnnotatedTypeFromTypeTree(tree)); checkTypeArguments(invokedMethod.getTypeVariables(), typeargs, node.getTypeArguments(), p); List<AnnotatedTypeMirror> params = annoTypes.expandVarArgs(invokedMethod, node.getArguments()); checkArguments(params, node.getArguments(), p); if (isVectorCopyInto(invokedMethod)) { typeCheckVectorCopyIntoArgument(node, params); } if (!ElementUtils.isStatic(invokedMethod.getElement()) && !isSuperInvocation(node)) checkMethodInvocability(invokedMethod, node); return super.visitMethodInvocation(node, p); } // Handle case Vector.copyInto() private final AnnotatedDeclaredType vectorType = atypeFactory.fromElement(elements.getTypeElement("java.util.Vector")); /** * Returns true if the method symbol represents {@code Vector.copyInto} */ protected boolean isVectorCopyInto(AnnotatedExecutableType method) { ExecutableElement elt = method.getElement(); if (elt.getSimpleName().contentEquals("copyInto") && elt.getParameters().size() == 1) return true; return false; } /** * Type checks the method arguments of {@code Vector.copyInto()}. * * The Checker Framework special-cases the method invocation, as it is * type safety cannot be expressed by Java's type system. * * For a Vector {@code v} of type {@code Vectory<E>}, the method * invocation {@code v.copyInto(arr)} is type-safe iff {@code arr} * is a array of type {@code T[]}, where {@code T} is a subtype of * {@code E}. * * In other words, this method checks that the type argument of the * receiver method is a subtype of the component type of the passed array * argument. * * @param node a method invocation of {@code Vector.copyInto()} * @param params the types of the parameters of {@code Vectory.copyInto()} * */ protected void typeCheckVectorCopyIntoArgument(MethodInvocationTree node, List<? extends AnnotatedTypeMirror> params) { assert params.size() == 1; assert node.getArguments().size() == 1; AnnotatedTypeMirror passed = atypeFactory.getAnnotatedType(node.getArguments().get(0)); AnnotatedArrayType passedAsArray = (AnnotatedArrayType)passed; AnnotatedTypeMirror receiver = atypeFactory.getReceiver(node); AnnotatedDeclaredType receiverAsVector = (AnnotatedDeclaredType)annoTypes.asSuper(receiver, vectorType); if (receiverAsVector == null || receiverAsVector.getTypeArguments().isEmpty()) return; commonAssignmentCheck( passedAsArray.getComponentType(), receiverAsVector.getTypeArguments().get(0), node.getArguments().get(0), "vector.copyinto.type.incompatible", null); } /** * Performs a new class invocation check. * * An invocation of a constructor, c, is valid only if: * <ul> * <li> passed arguments are subtypes of corresponding c parameters </li> * <li> if c is generic, passed type arguments are subtypes * of c type variables <li> * </ul> */ @Override public R visitNewClass(NewClassTree node, P p) { if (shouldSkip(InternalUtils.constructor(node))) return super.visitNewClass(node, p); AnnotatedExecutableType constructor = atypeFactory.constructorFromUse(node); List<? extends ExpressionTree> passedArguments = node.getArguments(); List<AnnotatedTypeMirror> params = annoTypes.expandVarArgs(constructor, passedArguments); checkArguments(params, passedArguments, p); // Get the constructor type. AnnotatedExecutableType type = atypeFactory.getAnnotatedType(InternalUtils.constructor(node)); // Get the type args to the constructor. List<AnnotatedTypeMirror> typeargs = new LinkedList<AnnotatedTypeMirror>(); for (Tree tree : node.getTypeArguments()) typeargs.add(atypeFactory.getAnnotatedTypeFromTypeTree(tree)); checkTypeArguments(type.getTypeVariables(), typeargs, node.getTypeArguments(), p); AnnotatedDeclaredType dt = atypeFactory.getAnnotatedType(node); checkConstructorInvocation(dt, constructor, node); validateTypeOf(node); return super.visitNewClass(node, p); } /** * Checks that the type of the return expression is a subtype of the * enclosing method required return type. If not, it issues a * "return.type.incompatible" error. */ @Override public R visitReturn(ReturnTree node, P p) { // Don't try to check return expressions for void methods. if (node.getExpression() == null) return super.visitReturn(node, p); MethodTree enclosingMethod = TreeUtils.enclosingMethod(getCurrentPath()); AnnotatedExecutableType methodType = atypeFactory.getAnnotatedType(enclosingMethod); commonAssignmentCheck(methodType.getReturnType(), node.getExpression(), "return.type.incompatible", p); return super.visitReturn(node, p); } // ********************************************************************** // Check for illegal re-assignment // ********************************************************************** /** * Performs assignability check using * {@link #checkAssignability(AnnotatedTypeMirror, Tree)}. */ @Override public R visitUnary(UnaryTree node, P p) { if ((node.getKind() == Tree.Kind.PREFIX_DECREMENT) || (node.getKind() == Tree.Kind.PREFIX_INCREMENT) || (node.getKind() == Tree.Kind.POSTFIX_DECREMENT) || (node.getKind() == Tree.Kind.POSTFIX_INCREMENT)) { AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node.getExpression()); checkAssignability(type, node.getExpression()); } return super.visitUnary(node, p); } /** * Performs assignability check using * {@link #checkAssignability(AnnotatedTypeMirror, Tree)}. */ @Override public R visitCompoundAssignment(CompoundAssignmentTree node, P p) { AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node.getVariable()); checkAssignability(type, node.getVariable()); return super.visitCompoundAssignment(node, p); } // ********************************************************************** // Check for invalid types inserted by the user // ********************************************************************** @Override public R visitNewArray(NewArrayTree node, P p) { validateTypeOf(node); if (node.getType() != null && node.getInitializers() != null) { AnnotatedArrayType arrayType = atypeFactory.getAnnotatedType(node); checkArrayInitialization(arrayType.getComponentType(), node.getInitializers(), p); } return super.visitNewArray(node, p); } /** * 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 "generic.argument.invalid" error if they are * not. */ @Override public R visitParameterizedType(ParameterizedTypeTree node, P p) { if (TreeUtils.isDiamondTree(node)) return super.visitParameterizedType(node, p); AnnotatedTypeMirror type = atypeFactory.getAnnotatedTypeFromTypeTree(node); if (type.getKind() != TypeKind.DECLARED) return super.visitParameterizedType(node, p); AnnotatedDeclaredType declared = (AnnotatedDeclaredType)type; final TypeElement element = (TypeElement)declared.getUnderlyingType().asElement(); if (shouldSkip(element)) return super.visitParameterizedType(node, p); AnnotatedDeclaredType generic = atypeFactory.getAnnotatedType(element); checkTypeArguments(generic.getTypeArguments(), declared.getTypeArguments(), node.getTypeArguments(), p); return super.visitParameterizedType(node, p); } protected void checkTypecastRedundancy(TypeCastTree node, P p) { if (!checker.getLintOption("cast:redundant", false)) return; AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(node); AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(node.getExpression()); if (annoTypes.areSame(castType, exprType)) { checker.report(Result.warning("cast.redundant", castType), node); } } protected void checkTypecastSafety(TypeCastTree node, P p) { if (!checker.getLintOption("cast:unsafe", true)) return; boolean isSubtype = false; // We cannot do a simple test of casting, as isSubtypeOf requires // the input types to be subtypes according to Java AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(node); if (castType.getKind() == TypeKind.DECLARED) { // eliminate false positives, where the annotations are // implicitly added by the declared type declaration AnnotatedDeclaredType castDeclared = (AnnotatedDeclaredType)castType; AnnotatedDeclaredType elementType = atypeFactory.fromElement((TypeElement)castDeclared.getUnderlyingType().asElement()); if (AnnotationUtils.areSame(castDeclared.getAnnotations(), elementType.getAnnotations())) isSubtype = true; } AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(node.getExpression()); if (!isSubtype) isSubtype = checker.getQualifierHierarchy().isSubtype(exprType.getAnnotations(), castType.getAnnotations()); // TODO: Test type arguments and array components types if (!isSubtype) { checker.report(Result.warning("cast.unsafe", exprType, castType), node); } } @Override public R visitTypeCast(TypeCastTree node, P p) { validateTypeOf(node.getType()); checkTypecastSafety(node, p); checkTypecastRedundancy(node, p); return super.visitTypeCast(node, p); } @Override public R visitInstanceOf(InstanceOfTree node, P p) { validateTypeOf(node.getType()); return super.visitInstanceOf(node, p); } // ********************************************************************** // Helper methods to provide a single overriding point // ********************************************************************** /** * Checks the validity of an assignment (or pseudo-assignment) from a value * to a variable and emits an error message (through the compiler's * messaging interface) if it does. * * @param varTree the AST node for the variable * @param valueExp the AST node for the value * @param errorKey the error message to use if the check fails * @param p a checker-specified parameter */ protected void commonAssignmentCheck(Tree varTree, ExpressionTree valueExp, @CompilerMessageKey String errorKey, P p) { AnnotatedTypeMirror var = atypeFactory.getAnnotatedType(varTree); assert var != null; checkAssignability(var, varTree); commonAssignmentCheck(var, valueExp, errorKey, p); } /** * Checks the validity of an assignment (or pseudo-assignment) from a value * to a variable and emits an error message (through the compiler's * messaging interface) if it does. * * @param varType the annotated type of the variable * @param valueExp the AST node for the value * @param errorKey the error message to use if the check fails * @param p a checker-specified parameter */ protected void commonAssignmentCheck(AnnotatedTypeMirror varType, ExpressionTree valueExp, @CompilerMessageKey String errorKey, P p) { if (shouldSkip(valueExp)) return; if (varType.getKind() == TypeKind.ARRAY && valueExp instanceof NewArrayTree && ((NewArrayTree)valueExp).getType() == null) { AnnotatedTypeMirror compType = ((AnnotatedArrayType)varType).getComponentType(); NewArrayTree arrayTree = (NewArrayTree)valueExp; assert arrayTree.getInitializers() != null; checkArrayInitialization(compType, arrayTree.getInitializers(), p); } AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedType(valueExp); assert valueType != null; commonAssignmentCheck(varType, valueType, valueExp, errorKey, p); } /** * Checks the validity of an assignment (or pseudo-assignment) from a value * to a variable and emits an error message (through the compiler's * messaging interface) if it does. * * @param varType the annotated type of the variable * @param valueType the annotated type of the value * @param valueTree the location to use when reporting the error message * @param errorKey the error message to use if the check fails * @param p a checker-specified parameter */ protected void commonAssignmentCheck(AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree, @CompilerMessageKey String errorKey, P p) { boolean success = checker.isSubtype(valueType, varType); if (options.containsKey("showchecks")) { long valuePos = positions.getStartPosition(root, valueTree); final String lineSeparator = System.getProperty("line.separator"); System.out.printf( " %s (line %3d): %s %s%s actual: %s %s%s expected: %s %s%s", (success ? "success" : "FAILURE"), root.getLineMap().getLineNumber(valuePos), valueTree.getKind(), valueTree, lineSeparator, valueType.getKind(), valueType, lineSeparator, varType.getKind(), varType, lineSeparator); } // Use an error key only if it's overridden by a checker. if (!success) { checker.report(Result.failure(errorKey, valueType.toString(), varType.toString()), valueTree); } } protected void checkArrayInitialization(AnnotatedTypeMirror type, List<? extends ExpressionTree> initializers, P p) { for (ExpressionTree init : initializers) commonAssignmentCheck(type, init, "type.incompatible", p); } /** * 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 "generic.argument.invalid" error if they are * not. * * @param typevars the type variables from a class or method declaration * @param typeargs the type arguments from the type or method invocation * @param typeargTrees the type arguments as trees, used for error reporting * @param p */ protected void checkTypeArguments( List<? extends AnnotatedTypeMirror> typevars, List<? extends AnnotatedTypeMirror> typeargs, List<? extends Tree> typeargTrees, P p) { // If there are no type arguments, do nothing. if (typeargs.isEmpty()) return; Iterator<? extends AnnotatedTypeMirror> varIter = typevars.iterator(); Iterator<? extends AnnotatedTypeMirror> argIter = typeargs.iterator(); while (varIter.hasNext()) { AnnotatedTypeMirror var = varIter.next(); assert var.getKind() == TypeKind.TYPEVAR : var.getKind(); AnnotatedTypeVariable typeVar = (AnnotatedTypeVariable) var; assert argIter.hasNext() : typevars + " / " + typeargs; AnnotatedTypeMirror typearg = argIter.next(); // TODO skip wildcards for now to prevent a crash if (typearg.getKind() == TypeKind.WILDCARD) continue; if (typeVar.getUpperBound() != null) { // Framework does not enrich upper bounds with the root annotations if (!(TypesUtils.isObject(typeVar.getUpperBound().getUnderlyingType()) && !typeVar.getUpperBound().isAnnotated())) { commonAssignmentCheck(typeVar.getUpperBound(), typearg, typeargTrees.get(typeargs.indexOf(typearg)), "generic.argument.invalid", p); } } if (!typeVar.getAnnotationsOnTypeVar().isEmpty()) { if (!typearg.getAnnotations().equals(typeVar.getAnnotationsOnTypeVar())) { checker.report(Result.failure("generic.argument.invalid", typearg, typeVar), typeargTrees.get(typeargs.indexOf(typearg))); } } } } /** * Tests whether the method can be invoked using the receiver of the 'node' * method invocation, and issues a "method.invocation.invalid" if the * invocation is invalid. * * This implementation tests whether the receiver in the method invocation * is a subtype of the method receiver type. * * @param method the type of the invoked method * @param node the method invocation node * @return true iff the call of 'node' is a valid call */ protected boolean checkMethodInvocability(AnnotatedExecutableType method, MethodInvocationTree node) { AnnotatedTypeMirror methodReceiver = method.getReceiverType().getErased(); AnnotatedTypeMirror treeReceiver = methodReceiver.getCopy(false); treeReceiver.addAnnotations(atypeFactory.getReceiver(node).getAnnotations()); if (!checker.isSubtype(treeReceiver, methodReceiver)) { checker.report(Result.failure("method.invocation.invalid", TreeUtils.elementFromUse(node), treeReceiver.toString(), methodReceiver.toString()), node); return false; } return true; } protected boolean checkConstructorInvocation(AnnotatedDeclaredType dt, AnnotatedExecutableType constructor, Tree src) { Collection<AnnotationMirror> dtAnno = dt.getAnnotations(); Collection<AnnotationMirror> receiverAnno = constructor.getReceiverType().getAnnotations(); final QualifierHierarchy hierarchy = checker.getQualifierHierarchy(); boolean b = hierarchy.isSubtype(dtAnno, receiverAnno) || hierarchy.isSubtype(receiverAnno, dtAnno); if (!b) { checker.report(Result.failure("constructor.invocation.invalid", constructor.toString(), dt, constructor.getReceiverType()), src); } return b; } /** * A helper method to check that each passed argument is a subtype of the * corresponding required argument, and issues "argument.invalid" error * for each passed argument that not a subtype of the required one. * * Note this method requires the lists to have the same length, as it * does not handle cases like var args. * * @param requiredArgs the required types * @param passedArgs the expressions passed to the corresponding types * @param p */ // This really should have a private final method // Unfortunately Javari override it! protected void checkArguments(List<? extends AnnotatedTypeMirror> requiredArgs, List<? extends ExpressionTree> passedArgs, P p) { assert requiredArgs.size() == passedArgs.size(); for (int i = 0; i < requiredArgs.size(); ++i) commonAssignmentCheck(requiredArgs.get(i), passedArgs.get(i), "argument.type.incompatible", p); } /** * Checks that an overriding method's return type, parameter types, and * receiver type are correct with respect to the annotations on the * overridden method's return type, parameter types, and receiver type. * * <p> * * This method returns the result of the check, but also emits error * messages as a side effect. * * @param overriderTree the AST node of the overriding method * @param enclosingType the declared type enclosing the overrider method * @param overridden the type of the overridden method * @param overriddenType the declared type enclosing the overridden method * @param p an optional parameter (as supplied to visitor methods) * @return true if the override check passed, false otherwise */ protected boolean checkOverride(MethodTree overriderTree, AnnotatedDeclaredType enclosingType, AnnotatedExecutableType overridden, AnnotatedDeclaredType overriddenType, P p) { if (shouldSkip(overriddenType.getElement())) return true; // Get the type of the overriding method. AnnotatedExecutableType overrider = atypeFactory.getAnnotatedType(overriderTree); boolean result = true; if (overrider.getTypeVariables().isEmpty() && !overridden.getTypeVariables().isEmpty()) { overridden = overridden.getErased(); } String overriderMeth = overrider.getElement().toString(); String overriderTyp = enclosingType.getUnderlyingType().asElement().toString(); String overridenMeth = overridden.getElement().toString(); String overridenTyp = overriddenType.getUnderlyingType().asElement().toString(); // Check the return value. if ((overrider.getReturnType().getKind() != TypeKind.VOID) && !checker.isSubtype(overrider.getReturnType(), overridden.getReturnType())) { checker.report(Result.failure("override.return.invalid", overriderMeth, overriderTyp, overridenMeth, overridenTyp, overrider.getReturnType().toString(), overridden.getReturnType().toString()), overriderTree.getReturnType()); // emit error message result = false; } // Check parameter values. (FIXME varargs) List<AnnotatedTypeMirror> overriderParams = overrider.getParameterTypes(); List<AnnotatedTypeMirror> overriddenParams = overridden.getParameterTypes(); for (int i = 0; i < overriderParams.size(); ++i) { if (!checker.isSubtype(overriddenParams.get(i), overriderParams.get(i))) { checker.report(Result.failure("override.param.invalid", overriderMeth, overriderTyp, overridenMeth, overridenTyp, overriderParams.get(i).toString(), overriddenParams.get(i).toString() ), overriderTree.getParameters().get(i)); // emit error message result = false; } } // Check the receiver type. // isSubtype() requires its arguments to be actual subtypes with // respect to JLS, but overrider receiver is not a subtype of the // overriden receiver. Hence copying the annotations AnnotatedTypeMirror overridenReceiver = overrider.getReceiverType().getErased().getCopy(false); overridenReceiver.addAnnotations(overridden.getReceiverType().getAnnotations()); if (!checker.isSubtype(overridenReceiver, overrider.getReceiverType().getErased())) { checker.report(Result.failure("override.receiver.invalid", overriderMeth, overriderTyp, overridenMeth, overridenTyp, overrider.getReceiverType(), overridden.getReceiverType()), overriderTree); result = false; } return result; } /** * Tests, for a re-assignment, whether the variable is assignable or not. * If not, it emits an assignability.invalid error. * * @param varType the type of the variable being re-assigned * @param varTree the tree used to access the variable in the assignment */ protected void checkAssignability(AnnotatedTypeMirror varType, Tree varTree) { if (varTree instanceof ExpressionTree && !checker.isAssignable(varType, atypeFactory.getReceiver((ExpressionTree)varTree), varTree)) { checker.report( Result.failure("assignability.invalid", InternalUtils.symbol(varTree), atypeFactory.getReceiver((ExpressionTree)varTree)), varTree); } } protected MemberSelectTree enclosingMemberSelect() { TreePath path = this.getCurrentPath(); assert path.getLeaf().getKind() == Tree.Kind.IDENTIFIER; if (path.getParentPath().getLeaf().getKind() == Tree.Kind.MEMBER_SELECT) return (MemberSelectTree)path.getParentPath().getLeaf(); else return null; } protected Tree enclosingStatement(Tree tree) { TreePath path = this.getCurrentPath(); while (path != null && path.getLeaf() != tree) path = path.getParentPath(); if (path != null) return path.getParentPath().getLeaf(); else return null; } public R visitIdentifier(IdentifierTree node, P p) { checkAccess(node, p); return super.visitIdentifier(node, p); } protected void checkAccess(IdentifierTree node, P p) { MemberSelectTree memberSel = enclosingMemberSelect(); ExpressionTree tree; Element elem; if (memberSel == null) { tree = node; elem = TreeUtils.elementFromUse(node); } else { tree = memberSel; elem = TreeUtils.elementFromUse(memberSel); } if (elem == null || !elem.getKind().isField()) return; AnnotatedTypeMirror receiver = plainFactory.getReceiver(tree); if (!isAccessAllowed(elem, receiver, tree)) { checker.report(Result.failure("unallowed.access", elem, receiver), node); } } protected boolean isAccessAllowed(Element field, AnnotatedTypeMirror receiver, ExpressionTree accessTree) { Unused unused = field.getAnnotation(Unused.class); if (unused == null) return true; try { unused.when(); } catch (MirroredTypeException exp) { Name whenName = TypesUtils.getQualifiedName((DeclaredType)exp.getTypeMirror()); if (receiver.getAnnotation(whenName) == null) return true; Tree tree = this.enclosingStatement(accessTree); // assigning unused to null is OK return (tree != null && tree.getKind() == Tree.Kind.ASSIGNMENT && ((AssignmentTree)tree).getVariable() == accessTree && ((AssignmentTree)tree).getExpression().getKind() == Tree.Kind.NULL_LITERAL); } assert false : "Cannot be here"; return false; } /** * Tests whether the tree expressed by the passed type tree is a valid type, * and emits an error if that is not the case (e.g. '@Mutable String'). * * @param tree the AST type supplied by the user */ public void validateTypeOf(Tree tree) { AnnotatedTypeMirror type; // It's quite annoying that there is no TypeTree switch (tree.getKind()) { case PRIMITIVE_TYPE: case PARAMETERIZED_TYPE: case TYPE_PARAMETER: case ARRAY_TYPE: case UNBOUNDED_WILDCARD: case EXTENDS_WILDCARD: case SUPER_WILDCARD: type = atypeFactory.getAnnotatedTypeFromTypeTree(tree); break; default: type = atypeFactory.getAnnotatedType(tree); } typeValidator.visit(type, tree); } // This is a test to ensure that all types are valid private AnnotatedTypeScanner<Void, Tree> typeValidator = createTypeValidator(); protected TypeValidator createTypeValidator() { return new TypeValidator(); } protected class TypeValidator extends AnnotatedTypeScanner<Void, Tree> { protected void reportError(AnnotatedTypeMirror type, Tree p) { checker.report(Result.failure("type.invalid", type.getAnnotations(), type.toString()), p); } @Override public Void visitDeclared(AnnotatedDeclaredType type, Tree p) { if (shouldSkip(type.getElement())) return super.visitDeclared(type, p); // Ensure that type use is a subtype of the element type AnnotatedDeclaredType useType = type.getErased(); AnnotatedDeclaredType elemType = (AnnotatedDeclaredType) atypeFactory.getAnnotatedType( useType.getUnderlyingType().asElement()).getErased(); if (!checker.isValidUse(elemType, useType)) { reportError(useType, p); } return super.visitDeclared(type, p); } } // ********************************************************************** // Random helper methods // ********************************************************************** /** * @param node the method invocation to check * @return true if this is a super call to the {@link Enum} constructor */ private boolean isEnumSuper(MethodInvocationTree node) { ExecutableElement ex = TreeUtils.elementFromUse(node); Name name = ElementUtils.getQualifiedClassName(ex); return "java.lang.Enum".contentEquals(name); } /** * Tests whether the expression should not be checked because of the tree * referring to unannotated classes, as specified in * the {@code checker.skipClasses} property. * * It returns true if exprTree is a method invocation or a field access * to a class whose qualified name matches @{link checker.skipClasses} * expression. It also return true for conditional expressions where * the true or false expressions should be skipped. * * @param exprTree any expression tree * @return true if checker should not test exprTree */ protected final boolean shouldSkip(ExpressionTree exprTree) { if (exprTree instanceof ConditionalExpressionTree) { ConditionalExpressionTree condTree = (ConditionalExpressionTree)exprTree; return (shouldSkip(condTree.getTrueExpression()) || shouldSkip(condTree.getFalseExpression())); } Element elm = InternalUtils.symbol(exprTree); return shouldSkip(elm); } /** * Tests whether the class owner of the passed element is an unannotated * class and matches the pattern specified in the * {@code checker.skipClasses} property. * * @param element an element * @return true iff the enclosing class of element should be skipped */ protected final boolean shouldSkip(Element element) { if (element == null) return false; TypeElement typeElement = ElementUtils.enclosingClass(element); String name = typeElement.getQualifiedName().toString(); return checker.getShouldSkip().matcher(name).find(); } // ********************************************************************** // Overriding to avoid visit part of the tree // ********************************************************************** @Override public R visitAnnotation(AnnotationTree node, P p) { // Skip checking inside annotations. return null; } /** * Override Compilation Unit so we won't visit package names or imports */ @Override public R visitCompilationUnit(CompilationUnitTree node, P p) { R r = scan(node.getPackageAnnotations(), p); // r = reduce(scan(node.getPackageName(), p), r); // r = reduce(scan(node.getImports(), p), r); r = reduce(scan(node.getTypeDecls(), p), r); return r; } }