in nullaway/src/main/java/com/uber/nullaway/NullAway.java [795:964]
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;
Type.MethodType jspecifyMemberReferenceMethodType = null;
if (memberReferenceTree != null) {
jspecifyMemberReferenceMethodType =
genericsChecks.getMemberReferenceMethodType(
memberReferenceTree, castToNonNull(overridingMethod), state);
}
MethodParameterNullness overriddenMethodArgumentNullness =
MethodParameterNullness.create(overriddenMethod);
// 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, try to use a type inferred by GenericsChecks for
// the tree. Fall back on the type inferred by javac.
Tree polyExprTree =
castToNonNull(
memberReferenceTree != null ? memberReferenceTree : lambdaExpressionTree);
Type functionalInterfaceType =
genericsChecks.getInferredPolyExpressionType(polyExprTree);
if (functionalInterfaceType == null) {
functionalInterfaceType = ASTHelpers.getType(polyExprTree);
}
paramNullness =
genericsChecks.getGenericMethodParameterNullness(
i, overriddenMethod, functionalInterfaceType, state);
} 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);
}
} else {
paramNullness = Nullness.NONNULL;
}
overriddenMethodArgumentNullness.setParameterNullness(i, paramNullness);
}
}
// Check handlers for any further/overriding nullness information
overriddenMethodArgumentNullness =
handler.onOverrideMethodInvocationParametersNullability(
state.context,
overriddenMethod,
isOverriddenMethodAnnotated,
overriddenMethodArgumentNullness);
// 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(
overriddenMethodArgumentNullness.getParameterNullness(0), Nullness.NULLABLE)) {
String message =
"unbound instance method reference cannot be used, as first parameter of "
+ "functional interface method "
+ ASTHelpers.enclosingClass(overriddenMethod)
+ "."
+ overriddenMethod
+ " 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++) {
Nullness overriddenParameterNullness =
overriddenMethodArgumentNullness.getParameterNullness(i);
if (!Objects.equals(overriddenParameterNullness, 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);
boolean paramIsNonNull =
paramOfOverridingMethodIsNonNull(
paramSymbol,
methodParamInd,
overridingMethod,
isOverridingMethodAnnotated,
memberReferenceTree,
jspecifyMemberReferenceMethodType);
// 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) {
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
+ " is @Nullable";
Tree errorTree;
if (memberReferenceTree != null) {
errorTree = memberReferenceTree;
} else {
errorTree = getTreesInstance(state).getTree(paramSymbol);
}
VisitorState paramState;
if (memberReferenceTree != null) {
// For nullability issues related to functional interfaces, the suppression should be on
// the use site
paramState = state;
} else {
TreePath path = getTreesInstance(state).getPath(paramSymbol);
paramState = path != null ? state.withPath(path) : state;
}
return errorBuilder.createErrorDescription(
new ErrorMessage(MessageTypes.WRONG_OVERRIDE_PARAM, message),
buildDescription(errorTree),
paramState,
paramSymbol);
}
}
return Description.NO_MATCH;
}