in nullaway/src/main/java/com/uber/nullaway/NullAway.java [2664:2786]
private boolean mayBeNullExpr(VisitorState state, ExpressionTree expr) {
expr = NullabilityUtil.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,
NEW_ARRAY,
ARRAY_TYPE,
// Lambdas may return null, but the lambda literal itself should not be null
LAMBDA_EXPRESSION,
// These cannot be null; the compiler would catch it
MEMBER_REFERENCE,
// result of compound assignment cannot be null
MULTIPLY_ASSIGNMENT,
DIVIDE_ASSIGNMENT,
REMAINDER_ASSIGNMENT,
PLUS_ASSIGNMENT,
MINUS_ASSIGNMENT,
LEFT_SHIFT_ASSIGNMENT,
RIGHT_SHIFT_ASSIGNMENT,
UNSIGNED_RIGHT_SHIFT_ASSIGNMENT,
AND_ASSIGNMENT,
XOR_ASSIGNMENT,
OR_ASSIGNMENT,
// rest are for auto-boxing
PLUS,
MINUS,
MULTIPLY,
DIVIDE,
REMAINDER,
CONDITIONAL_AND,
CONDITIONAL_OR,
BITWISE_COMPLEMENT,
LOGICAL_COMPLEMENT,
INSTANCE_OF,
PREFIX_INCREMENT,
PREFIX_DECREMENT,
POSTFIX_DECREMENT,
POSTFIX_INCREMENT,
EQUAL_TO,
NOT_EQUAL_TO,
GREATER_THAN,
GREATER_THAN_EQUAL,
LESS_THAN,
LESS_THAN_EQUAL,
UNARY_MINUS,
UNARY_PLUS,
AND,
OR,
XOR,
LEFT_SHIFT,
RIGHT_SHIFT,
UNSIGNED_RIGHT_SHIFT -> {
// clearly not null
return false;
}
default -> {}
}
// 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);
}
}
}
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);
}
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;
}
}
case METHOD_INVOCATION -> {
if (!(exprSymbol instanceof Symbol.MethodSymbol methodSymbol)) {
throw new IllegalStateException(
"unexpected symbol "
+ exprSymbol
+ " for method invocation "
+ state.getSourceForNode(expr));
}
exprMayBeNull = mayBeNullMethodCall(methodSymbol, (MethodInvocationTree) expr, state);
}
case CONDITIONAL_EXPRESSION, ASSIGNMENT, SWITCH_EXPRESSION -> exprMayBeNull = true;
default ->
throw new RuntimeException(
"whoops, better handle " + expr.getKind() + " " + state.getSourceForNode(expr));
}
exprMayBeNull = handler.onOverrideMayBeNullExpr(this, expr, exprSymbol, state, exprMayBeNull);
return exprMayBeNull && nullnessFromDataflow(state, expr);
}