in nullaway/src/main/java/com/uber/nullaway/NullAway.java [783:923]
private Description checkParamOverriding(
List<VarSymbol> overridingParamSymbols,
Symbol.MethodSymbol overriddenMethod,
@Nullable LambdaExpressionTree lambdaExpressionTree,
@Nullable MemberReferenceTree memberReferenceTree,
VisitorState state,
Symbol.@Nullable MethodSymbol overridingMethod) {
com.sun.tools.javac.util.List<VarSymbol> superParamSymbols = overriddenMethod.getParameters();
boolean unboundMemberRef =
(memberReferenceTree != null)
&& ((JCTree.JCMemberReference) memberReferenceTree).kind.isUnbound();
boolean isOverriddenMethodAnnotated =
!codeAnnotationInfo.isSymbolUnannotated(overriddenMethod, config, handler);
boolean isOverridingMethodAnnotated =
(overridingMethod != null
&& !codeAnnotationInfo.isSymbolUnannotated(overridingMethod, config, handler))
|| lambdaExpressionTree != null;
// Get argument nullability for the overridden method. If overriddenMethodArgNullnessMap[i] is
// null, parameter i is treated as unannotated.
@Nullable Nullness[] overriddenMethodArgNullnessMap = new Nullness[superParamSymbols.size()];
// Collect @Nullable params of overridden method iff the overridden method is in annotated code
// (otherwise, whether we acknowledge @Nullable in unannotated code or not depends on the
// -XepOpt:NullAway:AcknowledgeRestrictiveAnnotations flag and its handler).
if (isOverriddenMethodAnnotated) {
boolean overriddenMethodIsVarArgs = overriddenMethod.isVarArgs();
for (int i = 0; i < superParamSymbols.size(); i++) {
Nullness paramNullness;
if (overriddenMethodIsVarArgs && i == superParamSymbols.size() - 1) {
// For a varargs position, we need to check if the array itself is @Nullable
paramNullness =
Nullness.varargsArrayIsNullable(superParamSymbols.get(i), config)
? Nullness.NULLABLE
: Nullness.NONNULL;
} else if (Nullness.paramHasNullableAnnotation(overriddenMethod, i, config)) {
paramNullness = Nullness.NULLABLE;
} else if (config.isJSpecifyMode()) {
// Check if the parameter type is a type variable and the corresponding generic type
// argument is @Nullable
if (memberReferenceTree != null || lambdaExpressionTree != null) {
// For a method reference or lambda, we get generic type arguments from the javac's
// inferred type for the tree, which seems to properly preserve type-use annotations
paramNullness =
GenericsChecks.getGenericMethodParameterNullness(
i,
overriddenMethod,
ASTHelpers.getType(
memberReferenceTree != null ? memberReferenceTree : lambdaExpressionTree),
state,
config);
} else {
// Use the enclosing class of the overriding method to find generic type arguments
paramNullness =
GenericsChecks.getGenericMethodParameterNullness(
i, overriddenMethod, overridingParamSymbols.get(i).owner.owner, state, config);
}
} else {
paramNullness = Nullness.NONNULL;
}
overriddenMethodArgNullnessMap[i] = paramNullness;
}
}
// Check handlers for any further/overriding nullness information
overriddenMethodArgNullnessMap =
handler.onOverrideMethodInvocationParametersNullability(
state.context,
overriddenMethod,
isOverriddenMethodAnnotated,
overriddenMethodArgNullnessMap);
// If we have an unbound method reference, the first parameter of the overridden method must be
// @NonNull, as this parameter will be used as a method receiver inside the generated lambda.
// e.g. String::length is implemented as (@NonNull s -> s.length()) when used as a
// SomeFunc<String> and thus incompatible with, for example, SomeFunc.apply(@Nullable T).
if (unboundMemberRef && Objects.equals(overriddenMethodArgNullnessMap[0], Nullness.NULLABLE)) {
String message =
"unbound instance method reference cannot be used, as first parameter of "
+ "functional interface method "
+ ASTHelpers.enclosingClass(overriddenMethod)
+ "."
+ overriddenMethod.toString()
+ " is @Nullable";
return errorBuilder.createErrorDescription(
new ErrorMessage(MessageTypes.WRONG_OVERRIDE_PARAM, message),
buildDescription(memberReferenceTree),
state,
null);
}
// for unbound member references, we need to adjust parameter indices by 1 when matching with
// overridden method
int startParam = unboundMemberRef ? 1 : 0;
for (int i = 0; i < superParamSymbols.size(); i++) {
if (!Objects.equals(overriddenMethodArgNullnessMap[i], Nullness.NULLABLE)) {
// No need to check, unless the argument of the overridden method is effectively @Nullable,
// in which case it can't be overridden by a @NonNull arg.
continue;
}
int methodParamInd = i - startParam;
VarSymbol paramSymbol = overridingParamSymbols.get(methodParamInd);
// in the case where we have a parameter of a lambda expression, we do
// *not* force the parameter to be annotated with @Nullable; instead we "inherit"
// nullability from the corresponding functional interface method.
// So, we report an error if the @Nullable annotation is missing *and*
// we don't have a lambda with implicitly typed parameters
boolean implicitlyTypedLambdaParam =
lambdaExpressionTree != null
&& NullabilityUtil.lambdaParamIsImplicitlyTyped(
lambdaExpressionTree.getParameters().get(methodParamInd));
if (!implicitlyTypedLambdaParam && paramIsNonNull(paramSymbol, isOverridingMethodAnnotated)) {
String message =
"parameter "
+ paramSymbol.name.toString()
+ (memberReferenceTree != null ? " of referenced method" : "")
+ " is @NonNull, but parameter in "
+ ((lambdaExpressionTree != null || memberReferenceTree != null)
? "functional interface "
: "superclass ")
+ "method "
+ ASTHelpers.enclosingClass(overriddenMethod)
+ "."
+ overriddenMethod.toString()
+ " is @Nullable";
Tree errorTree;
if (memberReferenceTree != null) {
errorTree = memberReferenceTree;
} else {
errorTree = getTreesInstance(state).getTree(paramSymbol);
}
return errorBuilder.createErrorDescription(
new ErrorMessage(MessageTypes.WRONG_OVERRIDE_PARAM, message),
buildDescription(errorTree),
state,
paramSymbol);
}
}
return Description.NO_MATCH;
}