private Description checkParamOverriding()

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;
  }