in nullaway/src/main/java/com/uber/nullaway/NullAway.java [1887:2019]
private Description handleInvocation(
Tree tree,
VisitorState state,
Symbol.MethodSymbol methodSymbol,
List<? extends ExpressionTree> actualParams) {
List<VarSymbol> formalParams = methodSymbol.getParameters();
boolean varArgsMethod = methodSymbol.isVarArgs();
// always do unboxing checks, whether or not the invoked method is annotated
for (int i = 0; i < formalParams.size() && i < actualParams.size(); i++) {
if (formalParams.get(i).type.isPrimitive()) {
doUnboxingCheck(state, actualParams.get(i));
}
}
boolean isMethodAnnotated =
!codeAnnotationInfo.isSymbolUnannotated(methodSymbol, config, handler);
// If argumentPositionNullness[i] == null, parameter i is unannotated
@Nullable Nullness[] argumentPositionNullness = new Nullness[formalParams.size()];
if (isMethodAnnotated) {
// compute which arguments are @NonNull
for (int i = 0; i < formalParams.size(); i++) {
VarSymbol param = formalParams.get(i);
if (param.type.isPrimitive()) {
argumentPositionNullness[i] = Nullness.NONNULL;
} else if (ASTHelpers.isSameType(
param.type, Suppliers.JAVA_LANG_VOID_TYPE.get(state), state)) {
// Temporarily treat a Void argument type as if it were @Nullable Void. Handling of Void
// without special-casing, as recommended by JSpecify might: a) require generics support
// and, b) require checking that third-party libraries considered annotated adopt
// JSpecify semantics.
// See the suppression in https://github.com/uber/NullAway/pull/608 for an example of why
// this is needed.
argumentPositionNullness[i] = Nullness.NULLABLE;
} else {
// we need to call paramHasNullableAnnotation here since the invoked method may be defined
// in a class file
argumentPositionNullness[i] =
Nullness.paramHasNullableAnnotation(methodSymbol, i, config)
? Nullness.NULLABLE
: ((config.isJSpecifyMode() && tree instanceof MethodInvocationTree)
? genericsChecks.getGenericParameterNullnessAtInvocation(
i, methodSymbol, (MethodInvocationTree) tree, state, config)
: Nullness.NONNULL);
}
}
if (config.isJSpecifyMode()) {
genericsChecks.compareGenericTypeParameterNullabilityForCall(
methodSymbol, tree, actualParams, varArgsMethod, this, state);
if (!methodSymbol.getTypeParameters().isEmpty()) {
GenericsChecks.checkGenericMethodCallTypeArguments(tree, state, this, config, handler);
}
}
}
// Allow handlers to override the list of non-null argument positions
argumentPositionNullness =
handler.onOverrideMethodInvocationParametersNullability(
state.context, methodSymbol, isMethodAnnotated, argumentPositionNullness);
// now actually check the arguments
// NOTE: the case of an invocation on a possibly-null reference
// is handled by matchMemberSelect()
for (int argPos = 0; argPos < argumentPositionNullness.length; argPos++) {
boolean varargPosition = varArgsMethod && argPos == formalParams.size() - 1;
boolean argIsNonNull = Objects.equals(Nullness.NONNULL, argumentPositionNullness[argPos]);
if (!varargPosition && !argIsNonNull) {
continue;
}
ExpressionTree actual;
boolean mayActualBeNull = false;
if (varargPosition) {
// Check all vararg actual arguments for nullability
// This is the case where no actual parameter is passed for the var args parameter
// (i.e. it defaults to an empty array)
if (actualParams.size() <= argPos) {
continue;
}
actual = actualParams.get(argPos);
VarSymbol formalParamSymbol = formalParams.get(formalParams.size() - 1);
boolean isVarArgsCall = isVarArgsCall(tree);
if (isVarArgsCall) {
// This is the case were varargs are being passed individually, as 1 or more actual
// arguments starting at the position of the var args formal.
// If the formal var args accepts `@Nullable`, then there is nothing for us to check.
if (!argIsNonNull) {
continue;
}
// TODO report all varargs errors in a single build; this code only reports the first
// error
for (ExpressionTree arg : actualParams.subList(argPos, actualParams.size())) {
actual = arg;
mayActualBeNull = mayBeNullExpr(state, actual);
if (mayActualBeNull) {
break;
}
}
} else {
// This is the case where an array is explicitly passed in the position of the var args
// parameter
// Only check for a nullable varargs array if the method is annotated, or a @NonNull
// restrictive annotation is present in legacy mode (as previously the annotation was
// applied to both the array itself and the elements), or a JetBrains @NotNull declaration
// annotation is present (due to https://github.com/uber/NullAway/issues/720)
boolean checkForNullableVarargsArray =
isMethodAnnotated
|| (config.isLegacyAnnotationLocation() && argIsNonNull)
|| NullabilityUtil.hasJetBrainsNotNullDeclarationAnnotation(formalParamSymbol);
if (checkForNullableVarargsArray) {
// If varargs array itself is not @Nullable, cannot pass @Nullable array
if (!Nullness.varargsArrayIsNullable(formalParams.get(argPos), config)) {
mayActualBeNull = mayBeNullExpr(state, actual);
}
}
}
} else { // not the vararg position
actual = actualParams.get(argPos);
mayActualBeNull = mayBeNullExpr(state, actual);
}
if (mayActualBeNull) {
String message =
"passing @Nullable parameter '"
+ state.getSourceForNode(actual)
+ "' where @NonNull is required";
ErrorMessage errorMessage = new ErrorMessage(MessageTypes.PASS_NULLABLE, message);
state.reportMatch(
errorBuilder.createErrorDescriptionForNullAssignment(
errorMessage, actual, buildDescription(actual), state, formalParams.get(argPos)));
}
}
// Check for @NonNull being passed to castToNonNull (if configured)
return checkCastToNonNullTakesNullable(tree, state, methodSymbol, actualParams);
}