blob: 8c0f9e33fdecb5569f1dc33b9b467ffe5da0aff3 [file] [log] [blame]
package org.checkerframework.checker.nullness;
import com.sun.source.tree.AnnotatedTypeTree;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.ArrayTypeTree;
import com.sun.source.tree.AssertTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.CatchTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.DoWhileLoopTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.ForLoopTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.IfTree;
import com.sun.source.tree.InstanceOfTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.SynchronizedTree;
import com.sun.source.tree.ThrowTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.tree.WhileLoopTree;
import com.sun.source.util.TreePath;
import java.lang.annotation.Annotation;
import java.util.List;
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.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.checker.formatter.qual.FormatMethod;
import org.checkerframework.checker.initialization.InitializationVisitor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeValidator;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.common.basetype.TypeValidator;
import org.checkerframework.framework.flow.CFCFGBuilder;
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.AnnotatedPrimitiveType;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
/** The visitor for the nullness type-system. */
public class NullnessVisitor
extends InitializationVisitor<NullnessAnnotatedTypeFactory, NullnessValue, NullnessStore> {
// Error message keys
// private static final @CompilerMessageKey String ASSIGNMENT_TYPE_INCOMPATIBLE =
// "assignment.type.incompatible";
private static final @CompilerMessageKey String UNBOXING_OF_NULLABLE = "unboxing.of.nullable";
private static final @CompilerMessageKey String LOCKING_NULLABLE = "locking.nullable";
private static final @CompilerMessageKey String THROWING_NULLABLE = "throwing.nullable";
private static final @CompilerMessageKey String ACCESSING_NULLABLE = "accessing.nullable";
private static final @CompilerMessageKey String CONDITION_NULLABLE = "condition.nullable";
private static final @CompilerMessageKey String ITERATING_NULLABLE = "iterating.over.nullable";
private static final @CompilerMessageKey String SWITCHING_NULLABLE = "switching.nullable";
private static final @CompilerMessageKey String DEREFERENCE_OF_NULLABLE =
"dereference.of.nullable";
// Annotation and type constants
private final AnnotationMirror NONNULL, NULLABLE, MONOTONIC_NONNULL;
private final TypeMirror stringType;
/** The element for java.util.Collection.size(). */
private final ExecutableElement collectionSize;
/** The element for java.util.Collection.toArray(T). */
private final ExecutableElement collectionToArray;
/** The System.clearProperty(String) method. */
private final ExecutableElement systemClearProperty;
/** The System.setProperties(String) method. */
private final ExecutableElement systemSetProperties;
/** True if checked code may clear system properties. */
private final boolean permitClearProperty;
/**
* Create a new NullnessVisitor.
*
* @param checker the checker to which this visitor belongs
*/
public NullnessVisitor(BaseTypeChecker checker) {
super(checker);
NONNULL = atypeFactory.NONNULL;
NULLABLE = atypeFactory.NULLABLE;
MONOTONIC_NONNULL = atypeFactory.MONOTONIC_NONNULL;
stringType = elements.getTypeElement(String.class.getCanonicalName()).asType();
ProcessingEnvironment env = checker.getProcessingEnvironment();
this.collectionSize = TreeUtils.getMethod("java.util.Collection", "size", 0, env);
this.collectionToArray = TreeUtils.getMethod("java.util.Collection", "toArray", env, "T[]");
systemClearProperty = TreeUtils.getMethod("java.lang.System", "clearProperty", 1, env);
systemSetProperties = TreeUtils.getMethod("java.lang.System", "setProperties", 1, env);
this.permitClearProperty =
checker.getLintOption(
NullnessChecker.LINT_PERMITCLEARPROPERTY,
NullnessChecker.LINT_DEFAULT_PERMITCLEARPROPERTY);
}
@Override
public NullnessAnnotatedTypeFactory createTypeFactory() {
return new NullnessAnnotatedTypeFactory(checker);
}
@Override
public boolean isValidUse(AnnotatedPrimitiveType type, Tree tree) {
// The Nullness Checker issues a more comprehensible "nullness.on.primitive" error rather
// than the "annotations.on.use" error this method would issue.
return true;
}
private boolean containsSameByName(
Set<Class<? extends Annotation>> quals, AnnotationMirror anno) {
for (Class<? extends Annotation> q : quals) {
if (atypeFactory.areSameByClass(anno, q)) {
return true;
}
}
return false;
}
@Override
protected void commonAssignmentCheck(
Tree varTree,
ExpressionTree valueExp,
@CompilerMessageKey String errorKey,
Object... extraArgs) {
// Allow a MonotonicNonNull field to be initialized to null at its declaration, in a
// constructor, or in an initializer block. (The latter two are, strictly speaking, unsound
// because the constructor or initializer block might have previously set the field to a
// non-null value. Maybe add an option to disable that behavior.)
Element elem = initializedElement(varTree);
if (elem != null
&& atypeFactory.fromElement(elem).hasEffectiveAnnotation(MONOTONIC_NONNULL)
&& !checker.getLintOption(
NullnessChecker.LINT_NOINITFORMONOTONICNONNULL,
NullnessChecker.LINT_DEFAULT_NOINITFORMONOTONICNONNULL)) {
return;
}
super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs);
}
/**
* Returns the variable element, if the argument is an initialization; otherwise returns null.
*
* @param varTree an assignment LHS
* @return the initialized element, or null
*/
@SuppressWarnings("UnusedMethod")
private Element initializedElement(Tree varTree) {
switch (varTree.getKind()) {
case VARIABLE:
// It's a variable declaration.
return TreeUtils.elementFromDeclaration((VariableTree) varTree);
case MEMBER_SELECT:
MemberSelectTree mst = (MemberSelectTree) varTree;
ExpressionTree receiver = mst.getExpression();
// This recognizes "this.fieldname = ..." but not "MyClass.fieldname = ..." or
// "MyClass.this.fieldname = ...". The latter forms are probably rare in a constructor.
// Note that this method should return non-null only for fields of this class, not fields of
// any other class, including outer classes.
if (receiver.getKind() != Tree.Kind.IDENTIFIER
|| !((IdentifierTree) receiver).getName().contentEquals("this")) {
return null;
}
// fallthrough
case IDENTIFIER:
TreePath path = getCurrentPath();
if (TreePathUtil.inConstructor(path)) {
return TreeUtils.elementFromUse((ExpressionTree) varTree);
} else {
return null;
}
default:
return null;
}
}
@Override
protected void commonAssignmentCheck(
AnnotatedTypeMirror varType,
ExpressionTree valueExp,
@CompilerMessageKey String errorKey,
Object... extraArgs) {
// Use the valueExp as the context because data flow will have a value for that tree. It might
// not have a value for the var tree. This is sound because if data flow has determined
// @PolyNull is @Nullable at the RHS, then it is also @Nullable for the LHS.
atypeFactory.replacePolyQualifier(varType, valueExp);
super.commonAssignmentCheck(varType, valueExp, errorKey, extraArgs);
}
@Override
@FormatMethod
protected void commonAssignmentCheck(
AnnotatedTypeMirror varType,
AnnotatedTypeMirror valueType,
Tree valueTree,
@CompilerMessageKey String errorKey,
Object... extraArgs) {
if (TypesUtils.isPrimitive(varType.getUnderlyingType())
&& !TypesUtils.isPrimitive(valueType.getUnderlyingType())) {
boolean succeed = checkForNullability(valueType, valueTree, UNBOXING_OF_NULLABLE);
if (!succeed) {
// Only issue the unboxing of nullable error.
return;
}
}
super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs);
}
/** Case 1: Check for null dereferencing. */
@Override
public Void visitMemberSelect(MemberSelectTree node, Void p) {
Element e = TreeUtils.elementFromTree(node);
if (e.getKind() == ElementKind.CLASS) {
if (atypeFactory.containsNullnessAnnotation(null, node.getExpression())) {
checker.reportError(node, "nullness.on.outer");
}
} else if (!(TreeUtils.isSelfAccess(node)
|| node.getExpression().getKind() == Kind.PARAMETERIZED_TYPE
// case 8. static member access
|| ElementUtils.isStatic(e))) {
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(), ITERATING_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);
}
@Override
public Void visitNewArray(NewArrayTree node, Void p) {
AnnotatedArrayType type = atypeFactory.getAnnotatedType(node);
AnnotatedTypeMirror componentType = type.getComponentType();
if (componentType.hasEffectiveAnnotation(NONNULL)
&& !isNewArrayAllZeroDims(node)
&& !isNewArrayInToArray(node)
&& !TypesUtils.isPrimitive(componentType.getUnderlyingType())
&& (checker.getLintOption("soundArrayCreationNullness", false)
// temporary, for backward compatibility
|| checker.getLintOption("forbidnonnullarraycomponents", false))) {
checker.reportError(node, "new.array", componentType.getAnnotations(), type.toString());
}
return super.visitNewArray(node, p);
}
/**
* Determine whether all dimensions given in a new array expression have zero as length. For
* example "new Object[0][0];". Also true for empty dimensions, as in "new Object[] {...}".
*/
private static boolean isNewArrayAllZeroDims(NewArrayTree node) {
boolean isAllZeros = true;
for (ExpressionTree dim : node.getDimensions()) {
if (dim instanceof LiteralTree) {
Object val = ((LiteralTree) dim).getValue();
if (!(val instanceof Number) || !Integer.valueOf(0).equals(val)) {
isAllZeros = false;
break;
}
} else {
isAllZeros = false;
break;
}
}
return isAllZeros;
}
/**
* Return true if the given node is "new X[]", in the context "toArray(new X[])".
*
* @param node a node to test
* @return true if the node is a new array within acall to toArray()
*/
private boolean isNewArrayInToArray(NewArrayTree node) {
if (node.getDimensions().size() != 1) {
return false;
}
ExpressionTree dim = node.getDimensions().get(0);
ProcessingEnvironment env = checker.getProcessingEnvironment();
if (!TreeUtils.isMethodInvocation(dim, collectionSize, env)) {
return false;
}
ExpressionTree rcvsize = ((MethodInvocationTree) dim).getMethodSelect();
if (!(rcvsize instanceof MemberSelectTree)) {
return false;
}
rcvsize = ((MemberSelectTree) rcvsize).getExpression();
if (!(rcvsize instanceof IdentifierTree)) {
return false;
}
Tree encl = getCurrentPath().getParentPath().getLeaf();
if (!TreeUtils.isMethodInvocation(encl, collectionToArray, env)) {
return false;
}
ExpressionTree rcvtoarray = ((MethodInvocationTree) encl).getMethodSelect();
if (!(rcvtoarray instanceof MemberSelectTree)) {
return false;
}
rcvtoarray = ((MemberSelectTree) rcvtoarray).getExpression();
if (!(rcvtoarray instanceof IdentifierTree)) {
return false;
}
return ((IdentifierTree) rcvsize).getName() == ((IdentifierTree) rcvtoarray).getName();
}
/** Case 4: Check for thrown exception nullness. */
@Override
protected void checkThrownExpression(ThrowTree node) {
checkForNullability(node.getExpression(), THROWING_NULLABLE);
}
/** Case 5: Check for synchronizing locks. */
@Override
public Void visitSynchronized(SynchronizedTree node, Void p) {
checkForNullability(node.getExpression(), LOCKING_NULLABLE);
return super.visitSynchronized(node, p);
}
@Override
public Void visitAssert(AssertTree node, Void p) {
// See also
// org.checkerframework.dataflow.cfg.builder.CFGBuilder.CFGTranslationPhaseOne.visitAssert
// In cases where neither assumeAssertionsAreEnabled nor assumeAssertionsAreDisabled are turned
// on and @AssumeAssertions is not used, checkForNullability is still called since the
// CFGBuilder will have generated one branch for which asserts are assumed to be enabled.
boolean doVisitAssert = true;
if (checker.hasOption("assumeAssertionsAreEnabled")
|| CFCFGBuilder.assumeAssertionsActivatedForAssertTree(checker, node)) {
doVisitAssert = true;
} else if (checker.hasOption("assumeAssertionsAreDisabled")) {
doVisitAssert = false;
}
if (doVisitAssert) {
checkForNullability(node.getCondition(), CONDITION_NULLABLE);
return super.visitAssert(node, p);
}
return null;
}
@Override
public Void visitIf(IfTree node, Void p) {
checkForNullability(node.getCondition(), CONDITION_NULLABLE);
return super.visitIf(node, p);
}
@Override
public Void visitInstanceOf(InstanceOfTree node, Void p) {
// The "reference type" is the type after "instanceof".
Tree refTypeTree = node.getType();
if (refTypeTree.getKind() == Kind.ANNOTATED_TYPE) {
List<? extends AnnotationMirror> annotations =
TreeUtils.annotationsFromTree((AnnotatedTypeTree) refTypeTree);
if (AnnotationUtils.containsSame(annotations, NULLABLE)) {
checker.reportError(node, "instanceof.nullable");
}
if (AnnotationUtils.containsSame(annotations, NONNULL)) {
checker.reportWarning(node, "instanceof.nonnull.redundant");
}
}
return super.visitInstanceOf(node, p);
}
/**
* Reports an error if a comparison of a @NonNull expression with the null literal is performed.
*/
protected void checkForRedundantTests(BinaryTree node) {
final ExpressionTree leftOp = node.getLeftOperand();
final ExpressionTree rightOp = node.getRightOperand();
// respect command-line option
if (!checker.getLintOption(
NullnessChecker.LINT_REDUNDANTNULLCOMPARISON,
NullnessChecker.LINT_DEFAULT_REDUNDANTNULLCOMPARISON)) {
return;
}
// equality tests
if ((node.getKind() == Tree.Kind.EQUAL_TO || node.getKind() == Tree.Kind.NOT_EQUAL_TO)) {
AnnotatedTypeMirror left = atypeFactory.getAnnotatedType(leftOp);
AnnotatedTypeMirror right = atypeFactory.getAnnotatedType(rightOp);
if (leftOp.getKind() == Tree.Kind.NULL_LITERAL && right.hasEffectiveAnnotation(NONNULL)) {
checker.reportWarning(node, "nulltest.redundant", rightOp.toString());
} else if (rightOp.getKind() == Tree.Kind.NULL_LITERAL
&& left.hasEffectiveAnnotation(NONNULL)) {
checker.reportWarning(node, "nulltest.redundant", leftOp.toString());
}
}
}
/** 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())) {
if (!checkForNullability(node.getExpression(), UNBOXING_OF_NULLABLE)) {
// If unboxing of nullable is issued, don't issue any other errors.
return null;
}
}
return super.visitTypeCast(node, p);
}
@Override
public Void visitMethod(MethodTree node, Void p) {
if (TreeUtils.isConstructor(node)) {
List<? extends AnnotationTree> annoTrees = node.getModifiers().getAnnotations();
if (atypeFactory.containsNullnessAnnotation(annoTrees)) {
checker.reportError(node, "nullness.on.constructor");
}
}
VariableTree receiver = node.getReceiverParameter();
if (receiver != null) {
List<? extends AnnotationTree> annoTrees = receiver.getModifiers().getAnnotations();
Tree type = receiver.getType();
if (atypeFactory.containsNullnessAnnotation(annoTrees, type)) {
checker.reportError(node, "nullness.on.receiver");
}
}
return super.visitMethod(node, p);
}
@Override
public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
if (!permitClearProperty) {
ProcessingEnvironment env = checker.getProcessingEnvironment();
if (TreeUtils.isMethodInvocation(node, systemClearProperty, env)) {
String literal = literalFirstArgument(node);
if (literal == null
|| SystemGetPropertyHandler.predefinedSystemProperties.contains(literal)) {
checker.reportError(node, "clear.system.property");
}
}
if (TreeUtils.isMethodInvocation(node, systemSetProperties, env)) {
checker.reportError(node, "clear.system.property");
}
}
return super.visitMethodInvocation(node, p);
}
/**
* If the first argument of a method call is a literal, return it; otherwise return null.
*
* @param tree a method invocation whose first formal parameter is of String type
* @return the first argument if it is a literal, otherwise null
*/
/*package-private*/ static @Nullable String literalFirstArgument(MethodInvocationTree tree) {
List<? extends ExpressionTree> args = tree.getArguments();
assert args.size() > 0;
ExpressionTree arg = args.get(0);
if (arg.getKind() == Tree.Kind.STRING_LITERAL) {
String literal = (String) ((LiteralTree) arg).getValue();
return literal;
}
return null;
}
@Override
public void processClassTree(ClassTree classTree) {
Tree extendsClause = classTree.getExtendsClause();
if (extendsClause != null) {
reportErrorIfSupertypeContainsNullnessAnnotation(extendsClause);
}
for (Tree implementsClause : classTree.getImplementsClause()) {
reportErrorIfSupertypeContainsNullnessAnnotation(implementsClause);
}
if (classTree.getKind() == Tree.Kind.ENUM) {
for (Tree member : classTree.getMembers()) {
if (member.getKind() == Tree.Kind.VARIABLE
&& TreeUtils.elementFromDeclaration((VariableTree) member).getKind()
== ElementKind.ENUM_CONSTANT) {
VariableTree varDecl = (VariableTree) member;
List<? extends AnnotationTree> annoTrees = varDecl.getModifiers().getAnnotations();
Tree type = varDecl.getType();
if (atypeFactory.containsNullnessAnnotation(annoTrees, type)) {
checker.reportError(member, "nullness.on.enum");
}
}
}
}
super.processClassTree(classTree);
}
/**
* Report "nullness.on.supertype" error if a supertype has a nullness annotation.
*
* @param typeTree a supertype tree, from an {@code extends} or {@code implements} clause
*/
private void reportErrorIfSupertypeContainsNullnessAnnotation(Tree typeTree) {
if (typeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) {
List<? extends AnnotationTree> annoTrees = ((AnnotatedTypeTree) typeTree).getAnnotations();
if (atypeFactory.containsNullnessAnnotation(annoTrees)) {
checker.reportError(typeTree, "nullness.on.supertype");
}
}
}
// ///////////// Utility methods //////////////////////////////
/**
* Issues the error message if the type of the tree is not of a {@link NonNull} type.
*
* @param tree the tree where the error is to reported
* @param errMsg the error message (must be {@link CompilerMessageKey})
* @return whether or not the check succeeded
*/
private boolean checkForNullability(ExpressionTree tree, @CompilerMessageKey String errMsg) {
AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree);
return checkForNullability(type, tree, errMsg);
}
/**
* Issues the error message if an expression with this type may be null.
*
* @param type annotated type
* @param tree the tree where the error is to reported
* @param errMsg the error message (must be {@link CompilerMessageKey})
* @return whether or not the check succeeded
*/
private boolean checkForNullability(
AnnotatedTypeMirror type, Tree tree, @CompilerMessageKey String errMsg) {
if (!type.hasEffectiveAnnotation(NONNULL)) {
checker.reportError(tree, errMsg, tree);
return false;
}
return true;
}
@Override
protected void checkMethodInvocability(
AnnotatedExecutableType method, MethodInvocationTree node) {
if (!TreeUtils.isSelfAccess(node)
&&
// Static methods don't have a receiver
method.getReceiverType() != null) {
// TODO: should all or some constructors be excluded?
// method.getElement().getKind() != ElementKind.CONSTRUCTOR) {
Set<AnnotationMirror> receiverAnnos = atypeFactory.getReceiverType(node).getAnnotations();
AnnotatedTypeMirror methodReceiver = method.getReceiverType().getErased();
AnnotatedTypeMirror treeReceiver = methodReceiver.shallowCopy(false);
AnnotatedTypeMirror rcv = atypeFactory.getReceiverType(node);
treeReceiver.addAnnotations(rcv.getEffectiveAnnotations());
// If receiver is Nullable, then we don't want to issue a warning about method invocability
// (we'd rather have only the "dereference.of.nullable" message).
if (treeReceiver.hasAnnotation(NULLABLE) || receiverAnnos.contains(MONOTONIC_NONNULL)) {
return;
}
}
super.checkMethodInvocability(method, node);
}
/** @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) {
// it is valid to check equality between two reference types, even
// if one (or both) of them is null
return isPrimitive(tree.getLeftOperand()) != isPrimitive(tree.getRightOperand());
} else {
// All BinaryTree's are of type String, a primitive type or the reference type equivalent of a
// primitive type. Furthermore, Strings don't have a primitive type, and therefore only
// BinaryTrees that aren't String can cause unboxing.
return !isString(tree);
}
}
/** @return true if the type of the tree is a super of String */
private final boolean isString(ExpressionTree tree) {
TypeMirror type = TreeUtils.typeOf(tree);
return types.isAssignable(stringType, type);
}
/** @return true if the type of the tree is a primitive */
private static final boolean isPrimitive(ExpressionTree tree) {
return TreeUtils.typeOf(tree).getKind().isPrimitive();
}
@Override
public Void visitSwitch(SwitchTree node, Void p) {
checkForNullability(node.getExpression(), SWITCHING_NULLABLE);
return super.visitSwitch(node, p);
}
@Override
public Void visitForLoop(ForLoopTree node, Void p) {
if (node.getCondition() != null) {
// Condition is null e.g. in "for (;;) {...}"
checkForNullability(node.getCondition(), CONDITION_NULLABLE);
}
return super.visitForLoop(node, p);
}
@Override
public Void visitNewClass(NewClassTree node, Void p) {
AnnotatedDeclaredType type = atypeFactory.getAnnotatedType(node);
ExpressionTree identifier = node.getIdentifier();
if (identifier instanceof AnnotatedTypeTree) {
AnnotatedTypeTree t = (AnnotatedTypeTree) identifier;
for (AnnotationMirror a : atypeFactory.getAnnotatedType(t).getAnnotations()) {
// is this an annotation of the nullness checker?
boolean nullnessCheckerAnno = containsSameByName(atypeFactory.getNullnessAnnotations(), a);
if (nullnessCheckerAnno && !AnnotationUtils.areSame(NONNULL, a)) {
// The type is not non-null => warning
checker.reportWarning(node, "new.class", type.getAnnotations());
// Note that other consistency checks are made by isValid.
}
}
if (t.toString().contains("@PolyNull")) {
// TODO: this is a hack, but PolyNull gets substituted
// afterwards
checker.reportWarning(node, "new.class", type.getAnnotations());
}
}
// TODO: It might be nicer to introduce a framework-level
// isValidNewClassType or some such.
return super.visitNewClass(node, p);
}
@Override
public Void visitWhileLoop(WhileLoopTree node, Void p) {
checkForNullability(node.getCondition(), CONDITION_NULLABLE);
return super.visitWhileLoop(node, p);
}
@Override
public Void visitDoWhileLoop(DoWhileLoopTree node, Void p) {
checkForNullability(node.getCondition(), CONDITION_NULLABLE);
return super.visitDoWhileLoop(node, p);
}
@Override
public Void visitConditionalExpression(ConditionalExpressionTree node, Void p) {
checkForNullability(node.getCondition(), CONDITION_NULLABLE);
return super.visitConditionalExpression(node, p);
}
@Override
protected void checkExceptionParameter(CatchTree node) {
VariableTree param = node.getParameter();
List<? extends AnnotationTree> annoTrees = param.getModifiers().getAnnotations();
Tree paramType = param.getType();
if (atypeFactory.containsNullnessAnnotation(annoTrees, paramType)) {
// This is a warning rather than an error because writing `@Nullable` could make sense
// if the catch block re-assigns the variable to null. (That would be bad style.)
checker.reportWarning(param, "nullness.on.exception.parameter");
}
// Don't call super.
// BasetypeVisitor forces annotations on exception parameters to be top, but because exceptions
// can never be null, the Nullness Checker does not require this check.
}
@Override
public Void visitAnnotation(AnnotationTree node, Void p) {
// All annotation arguments are non-null and initialized, so no need to check them.
return null;
}
@Override
public void visitAnnotatedType(
@Nullable List<? extends AnnotationTree> annoTrees, Tree typeTree) {
// Look for a MEMBER_SELECT or PRIMITIVE within the type.
Tree t = typeTree;
while (t != null) {
switch (t.getKind()) {
case MEMBER_SELECT:
Tree expr = ((MemberSelectTree) t).getExpression();
if (atypeFactory.containsNullnessAnnotation(annoTrees, expr)) {
checker.reportError(expr, "nullness.on.outer");
}
t = null;
break;
case PRIMITIVE_TYPE:
if (atypeFactory.containsNullnessAnnotation(annoTrees, t)) {
checker.reportError(t, "nullness.on.primitive");
}
t = null;
break;
case ANNOTATED_TYPE:
AnnotatedTypeTree at = ((AnnotatedTypeTree) t);
Tree underlying = at.getUnderlyingType();
if (underlying.getKind() == Tree.Kind.PRIMITIVE_TYPE) {
if (atypeFactory.containsNullnessAnnotation(null, at)) {
checker.reportError(t, "nullness.on.primitive");
}
t = null;
} else {
t = underlying;
}
break;
case ARRAY_TYPE:
t = ((ArrayTypeTree) t).getType();
break;
case PARAMETERIZED_TYPE:
t = ((ParameterizedTypeTree) t).getType();
break;
default:
t = null;
break;
}
}
super.visitAnnotatedType(annoTrees, typeTree);
}
@Override
protected TypeValidator createTypeValidator() {
return new NullnessValidator(checker, this, atypeFactory);
}
/**
* Check that primitive types are annotated with {@code @NonNull} even if they are the type of a
* local variable.
*/
private static class NullnessValidator extends BaseTypeValidator {
/**
* Create NullnessValidator.
*
* @param checker checker
* @param visitor visitor
* @param atypeFactory factory
*/
public NullnessValidator(
BaseTypeChecker checker, BaseTypeVisitor<?> visitor, AnnotatedTypeFactory atypeFactory) {
super(checker, visitor, atypeFactory);
}
@Override
protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType(
AnnotatedTypeMirror type, Tree tree) {
if (type.getKind().isPrimitive()) {
return true;
}
return super.shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree);
}
}
}