private Description handleInvocation()

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