in nullaway/src/main/java/com/uber/nullaway/NullAway.java [2040:2149]
private Description handleInvocation(
Tree tree,
VisitorState state,
Symbol.MethodSymbol methodSymbol,
List<? extends ExpressionTree> actualParams) {
List<VarSymbol> formalParams = methodSymbol.getParameters();
InvocationArguments invArgs = new InvocationArguments(tree, methodSymbol.type.asMethodType());
// always do unboxing checks, whether or not the invoked method is annotated
invArgs.forEach(
(actual, argPos, formalParamType, varArgsPassedAsArray) -> {
if (formalParamType.isPrimitive()) {
doUnboxingCheck(state, actual);
}
});
boolean isMethodAnnotated =
!codeAnnotationInfo.isSymbolUnannotated(methodSymbol, config, handler);
MethodParameterNullness argumentNullness = MethodParameterNullness.create(methodSymbol);
if (isMethodAnnotated) {
// compute which arguments are @NonNull
for (int i = 0; i < formalParams.size(); i++) {
VarSymbol param = formalParams.get(i);
if (param.type.isPrimitive()) {
argumentNullness.setParameterNullness(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.
argumentNullness.setParameterNullness(i, Nullness.NULLABLE);
} else {
// we need to call paramHasNullableAnnotation here since the invoked method may be defined
// in a class file
argumentNullness.setParameterNullness(
i,
Nullness.paramHasNullableAnnotation(methodSymbol, i, config)
? Nullness.NULLABLE
: ((config.isJSpecifyMode()
&& (tree instanceof MethodInvocationTree || tree instanceof NewClassTree))
? genericsChecks.getGenericParameterNullnessAtInvocation(
i, methodSymbol, tree, state)
: Nullness.NONNULL));
}
}
if (methodSymbol.isVarArgs()) {
VarSymbol varargsFormalParam = formalParams.get(formalParams.size() - 1);
argumentNullness.setVarargsArrayNullness(
Nullness.varargsArrayIsNullable(varargsFormalParam, config)
? Nullness.NULLABLE
: Nullness.NONNULL);
}
// perform generics checks for calls to annotated methods in JSpecify mode
if (config.isJSpecifyMode()) {
genericsChecks.compareGenericTypeParameterNullabilityForCall(methodSymbol, tree, state);
if (!methodSymbol.getTypeParameters().isEmpty()) {
genericsChecks.checkGenericMethodCallTypeArguments(tree, state);
}
}
}
// Allow handlers to override the list of non-null argument positions.
MethodParameterNullness finalArgumentNullness =
handler.onOverrideMethodInvocationParametersNullability(
state.context, methodSymbol, isMethodAnnotated, argumentNullness);
// now actually check the arguments
// NOTE: the case of an invocation on a possibly-null reference
// is handled by matchMemberSelect()
invArgs.forEach(
(actual, argPos, formalParamType, varArgsPassedAsArray) -> {
if (argPos >= formalParams.size()) {
// extra varargs argument; nullness info stored in the varargs parameter slot
argPos = formalParams.size() - 1;
}
boolean argIsNonNull =
Objects.equals(Nullness.NONNULL, finalArgumentNullness.getParameterNullness(argPos));
if (varArgsPassedAsArray) {
// This is the case where an array is explicitly passed in the position of the
// varargs parameter
argIsNonNull =
Objects.equals(Nullness.NONNULL, finalArgumentNullness.getVarargsArrayNullness());
}
if (!argIsNonNull) {
// argument can be @Nullable, so nothing to check
return;
}
boolean 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);
}