in nullaway/src/main/java/com/uber/nullaway/NullAway.java [2552:2683]
private boolean mayBeNullExpr(VisitorState state, ExpressionTree expr) {
expr = stripParensAndCasts(expr);
if (ASTHelpers.constValue(expr) != null) {
// This should include literals such as "true" or a string
// obviously not null
return false;
}
// return early for expressions that no handler overrides and will not need dataflow analysis
switch (expr.getKind()) {
case NULL_LITERAL:
// obviously null
return true;
case NEW_CLASS:
case NEW_ARRAY:
case ARRAY_TYPE:
// Lambdas may return null, but the lambda literal itself should not be null
case LAMBDA_EXPRESSION:
// These cannot be null; the compiler would catch it
case MEMBER_REFERENCE:
// result of compound assignment cannot be null
case MULTIPLY_ASSIGNMENT:
case DIVIDE_ASSIGNMENT:
case REMAINDER_ASSIGNMENT:
case PLUS_ASSIGNMENT:
case MINUS_ASSIGNMENT:
case LEFT_SHIFT_ASSIGNMENT:
case RIGHT_SHIFT_ASSIGNMENT:
case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
case AND_ASSIGNMENT:
case XOR_ASSIGNMENT:
case OR_ASSIGNMENT:
// rest are for auto-boxing
case PLUS:
case MINUS:
case MULTIPLY:
case DIVIDE:
case REMAINDER:
case CONDITIONAL_AND:
case CONDITIONAL_OR:
case BITWISE_COMPLEMENT:
case LOGICAL_COMPLEMENT:
case INSTANCE_OF:
case PREFIX_INCREMENT:
case PREFIX_DECREMENT:
case POSTFIX_DECREMENT:
case POSTFIX_INCREMENT:
case EQUAL_TO:
case NOT_EQUAL_TO:
case GREATER_THAN:
case GREATER_THAN_EQUAL:
case LESS_THAN:
case LESS_THAN_EQUAL:
case UNARY_MINUS:
case UNARY_PLUS:
case AND:
case OR:
case XOR:
case LEFT_SHIFT:
case RIGHT_SHIFT:
case UNSIGNED_RIGHT_SHIFT:
// clearly not null
return false;
default:
break;
}
// the logic here is to avoid doing dataflow analysis whenever possible
Symbol exprSymbol = ASTHelpers.getSymbol(expr);
boolean exprMayBeNull;
switch (expr.getKind()) {
case ARRAY_ACCESS:
// Outside JSpecify mode, we assume array contents are always non-null
exprMayBeNull = false;
if (config.isJSpecifyMode()) {
// In JSpecify mode, we check if the array element type is nullable
ArrayAccessTree arrayAccess = (ArrayAccessTree) expr;
ExpressionTree arrayExpr = arrayAccess.getExpression();
Symbol arraySymbol = ASTHelpers.getSymbol(arrayExpr);
if (arraySymbol != null) {
exprMayBeNull = NullabilityUtil.isArrayElementNullable(arraySymbol, config);
}
}
break;
case MEMBER_SELECT:
if (exprSymbol == null) {
throw new IllegalStateException(
"unexpected null symbol for dereference expression " + state.getSourceForNode(expr));
}
exprMayBeNull =
NullabilityUtil.mayBeNullFieldFromType(exprSymbol, config, handler, codeAnnotationInfo);
break;
case IDENTIFIER:
if (exprSymbol == null) {
throw new IllegalStateException(
"unexpected null symbol for identifier " + state.getSourceForNode(expr));
}
if (exprSymbol.getKind() == ElementKind.FIELD) {
exprMayBeNull =
NullabilityUtil.mayBeNullFieldFromType(
exprSymbol, config, handler, codeAnnotationInfo);
} else {
// rely on dataflow analysis for local variables
exprMayBeNull = true;
}
break;
case METHOD_INVOCATION:
if (!(exprSymbol instanceof Symbol.MethodSymbol)) {
throw new IllegalStateException(
"unexpected symbol "
+ exprSymbol
+ " for method invocation "
+ state.getSourceForNode(expr));
}
exprMayBeNull =
mayBeNullMethodCall(
(Symbol.MethodSymbol) exprSymbol, (MethodInvocationTree) expr, state);
break;
case CONDITIONAL_EXPRESSION:
case ASSIGNMENT:
exprMayBeNull = true;
break;
default:
// match switch expressions by comparing strings, so the code compiles on JDK versions < 12
if (expr.getKind().name().equals("SWITCH_EXPRESSION")) {
exprMayBeNull = true;
} else {
throw new RuntimeException(
"whoops, better handle " + expr.getKind() + " " + state.getSourceForNode(expr));
}
}
exprMayBeNull = handler.onOverrideMayBeNullExpr(this, expr, exprSymbol, state, exprMayBeNull);
return exprMayBeNull && nullnessFromDataflow(state, expr);
}