package checkers.nullness; import java.util.HashSet; import java.util.Set; import javax.lang.model.element.*; import javax.lang.model.type.TypeMirror; import checkers.basetype.*; import checkers.nullness.quals.LazyNonNull; import checkers.nullness.quals.NonNull; import checkers.nullness.quals.Nullable; import checkers.source.Result; import checkers.types.AnnotatedTypeMirror; import checkers.types.AnnotatedTypeMirror.*; import checkers.util.*; import com.sun.source.tree.*; /** * A type-checking visitor for the Nullness type system. * This visitor reports errors ("dereference.of.nullable") or * warnings for violations for the following cases: * * <ol> * <li value="1">the receiver of a member dereference is not NonNull * <li value="2">the receiver of an implicit ".iterator()" dereference in an enhanced * for loop is not NonNull * <li value="3">an accessed array is not NonNull * <li value="4">a thrown exception is not NonNull * <li value="5">the lock in a synchronized block is not NonNull * <li value="6">a NonNull reference is checked for nullness * <li value="7">a value in implicit unboxed operation is not NonNull * </ol> * * Self-accesses (via {@code this} or {@code super}) can always be dereferenced. * * @see NullnessSubchecker */ public class NullnessVisitor extends BaseTypeVisitor<Void, Void> { /** The {@link NonNull} annotation */ private final AnnotationMirror NONNULL, NULLABLE; private final TypeMirror stringType; /** * Creates a new visitor for type-checking {@link NonNull}. * * @param checker the checker to use * @param root the root of the input program's AST to check */ public NullnessVisitor(NullnessSubchecker checker, CompilationUnitTree root) { super(checker, root); NONNULL = this.annoFactory.fromClass(NonNull.class); NULLABLE = this.annoFactory.fromClass(Nullable.class); stringType = elements.getTypeElement("java.lang.String").asType(); } /** Case 1: Check for null dereferecing */ @Override public Void visitMemberSelect(MemberSelectTree node, Void p) { if (!TreeUtils.isSelfAccess(node)) checkForNullability(node.getExpression(), "dereference.of.nullable"); return super.visitMemberSelect(node, p); } /** Case 2: Check for implicit {@code .iterator} call */ @Override public Void visitEnhancedForLoop(EnhancedForLoopTree node, Void p) { checkForNullability(node.getExpression(), "dereference.of.nullable"); return super.visitEnhancedForLoop(node, p); } /** Case 3: Check for array dereferencing */ @Override public Void visitArrayAccess(ArrayAccessTree node, Void p) { checkForNullability(node.getExpression(), "accessing.nullable"); return super.visitArrayAccess(node, p); } /** Case 4: Check for thrown exception nullness */ @Override public Void visitThrow(ThrowTree node, Void p) { checkForNullability(node.getExpression(), "throwing.nullable"); return super.visitThrow(node, p); } /** Case 5: Check for synchronizing locks */ @Override public Void visitSynchronized(SynchronizedTree node, Void p) { //checkForNullability(node.getExpression(), "locking.nullable"); // raw is suffecient AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node.getExpression()); if (type.hasAnnotation(NULLABLE)) checker.report(Result.failure("locking.nullable", node), node); return super.visitSynchronized(node, p); } // Variable to skip redundant nullness tests when in assert private boolean isInAssert = false; @Override public Void visitAssert(AssertTree node, Void p) { boolean beforeAssert = isInAssert; try { isInAssert = true; return super.visitAssert(node, p); } finally { isInAssert = beforeAssert; } } @Override public Void visitIf(IfTree node, Void p) { boolean beforeAssert = isInAssert; try { isInAssert = TreeUtils.firstStatement(node.getThenStatement()).getKind() == Tree.Kind.THROW && node.getElseStatement() == null; return super.visitIf(node, p); } finally { isInAssert = beforeAssert; } } protected void checkForRedundantTests(BinaryTree node) { if (isInAssert) return; final ExpressionTree leftOp = node.getLeftOperand(); final ExpressionTree rightOp = node.getRightOperand(); // equality tests if ((node.getKind() == Tree.Kind.EQUAL_TO || node.getKind() == Tree.Kind.NOT_EQUAL_TO) && checker.getLintOption("nulltest", false)) { AnnotatedTypeMirror left = atypeFactory.getAnnotatedType(leftOp); AnnotatedTypeMirror right = atypeFactory.getAnnotatedType(rightOp); if (leftOp.getKind() == Tree.Kind.NULL_LITERAL && right.hasAnnotation(NONNULL)) checker.report(Result.warning("known.nonnull", rightOp.toString()), node); else if (rightOp.getKind() == Tree.Kind.NULL_LITERAL && left.hasAnnotation(NONNULL)) checker.report(Result.warning("known.nonnull", leftOp.toString()), node); } } /** * Case 6: Check for redundant nullness tests * Case 7: unboxing case: primitive operations */ @Override public Void visitBinary(BinaryTree node, Void p) { final ExpressionTree leftOp = node.getLeftOperand(); final ExpressionTree rightOp = node.getRightOperand(); if (isUnboxingOperation(node)) { checkForNullability(leftOp, "unboxing.of.nullable"); checkForNullability(rightOp, "unboxing.of.nullable"); } checkForRedundantTests(node); return super.visitBinary(node, p); } /** Case 7: unboxing case: primitive operation */ @Override public Void visitUnary(UnaryTree node, Void p) { checkForNullability(node.getExpression(), "unboxing.of.nullable"); return super.visitUnary(node, p); } /** Case 7: unboxing case: primitive operation */ @Override public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) { // ignore String concatenation if (!isString(node)) { checkForNullability(node.getVariable(), "unboxing.of.nullable"); checkForNullability(node.getExpression(), "unboxing.of.nullable"); } return super.visitCompoundAssignment(node, p); } /** Case 7: unboxing case: casting to a primitive */ @Override public Void visitTypeCast(TypeCastTree node, Void p) { if (isPrimitive(node) && !isPrimitive(node.getExpression())) checkForNullability(node.getExpression(), "unboxing.of.nullable"); return super.visitTypeCast(node, p); } @Override protected void commonAssignmentCheck(Tree varTree, ExpressionTree valueExp, String errorKey, Void p) { // allow LazyNonNull to be initalized to null at declaration if (varTree.getKind() == Tree.Kind.VARIABLE) { Element elem = TreeUtils.elementFromDeclaration((VariableTree)varTree); if (elem.getAnnotation(LazyNonNull.class) != null) return; } super.commonAssignmentCheck(varTree, valueExp, errorKey, p); } //////////////////////// Field Initializations ////////////////////////// /** Case 8: field initialization */ private Set<VariableElement> nonInitializedFields = null; @Override public Void visitMethod(MethodTree node, Void p) { if (TreeUtils.isConstructor(node) && !TreeUtils.containsThisConstructorInvocation(node)) { Set<VariableElement> oldFields = nonInitializedFields; try { nonInitializedFields = getUninitializedFields(TreeUtils.enclosingClass(getCurrentPath())); return super.visitMethod(node, p); } finally { if (!nonInitializedFields.isEmpty()) { if (checker.getLintOption("uninitialized", false)) { checker.report(Result.warning("fields.uninitialized", nonInitializedFields), node); } // TODO: warn against uninitialized fields } nonInitializedFields = oldFields; } } return super.visitMethod(node, p); } @Override protected void checkDefaultConstructor(ClassTree node) { if (!checker.getLintOption("uninitialized", false)) return; Set<VariableElement> fields = getUninitializedFields(node); if (!fields.isEmpty()) { checker.report(Result.warning("fields.uninitialized", fields), node); } } @Override public Void visitAssignment(AssignmentTree node, Void p) { if (nonInitializedFields != null) nonInitializedFields.remove(InternalUtils.symbol(node.getVariable())); return super.visitAssignment(node, p); } private Set<VariableElement> getUninitializedFields(ClassTree classTree) { Set<VariableElement> fields = new HashSet<VariableElement>(); for (Tree member : classTree.getMembers()) { if (!(member instanceof VariableTree)) continue; VariableTree var = (VariableTree)member; VariableElement varElt = TreeUtils.elementFromDeclaration(var); // only consider fields that are uninitialized at the declaration // and are qualified as nonnull if (var.getInitializer() == null && atypeFactory.getAnnotatedType(var).hasAnnotation(NONNULL) && !varElt.getModifiers().contains(Modifier.STATIC)) fields.add(varElt); } return fields; } /** Special casing NonNull and Raw method calls */ @Override protected boolean checkMethodInvocability(AnnotatedExecutableType method, MethodInvocationTree node) { if (TreeUtils.isSelfAccess(node)) { // It's OK to call 'this' when all fields are initialized if (nonInitializedFields != null && nonInitializedFields.isEmpty()) return true; } else { // Claim that methods with a @NonNull receiver are invokable so that // visitMemberSelect issues dereference errors instead. if (method.getReceiverType().hasAnnotation(NONNULL)) return true; } return super.checkMethodInvocability(method, node); } /** * Issues a 'dereference.of.nullable' if the type is not of a * {@link NonNull} type. * * @param type type to be checked nullability * @param tree the tree where the error is to reported */ private void checkForNullability(ExpressionTree tree, String errMsg) { AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree); if (!type.hasAnnotation(NONNULL)) checker.report(Result.failure(errMsg, tree), tree); } /////////////// Utility methods ////////////////////////////// /** @return true if binary operation could cause an unboxing operation */ private final boolean isUnboxingOperation(BinaryTree tree) { if (tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO) return isPrimitive(tree.getLeftOperand()) != isPrimitive(tree.getRightOperand()); else return !isString(tree); } /** * @return true if the type of the tree is a super of String * */ private final boolean isString(ExpressionTree tree) { TypeMirror type = InternalUtils.typeOf(tree); return types.isAssignable(stringType, type); } /** * @return true if the type of the tree is a primitive */ private final boolean isPrimitive(ExpressionTree tree) { return InternalUtils.typeOf(tree).getKind().isPrimitive(); } }