public void onDataflowVisitReturn()

in nullaway/src/main/java/com/uber/nullaway/handlers/contract/fieldcontract/EnsuresNonNullIfHandler.java [160:239]


  public void onDataflowVisitReturn(
      ReturnTree returnTree, VisitorState state, NullnessStore thenStore, NullnessStore elseStore) {
    // We only explore return statements that is inside
    // the method under validation
    if (!returnTreesInMethodUnderAnalysis.contains(returnTree)) {
      return;
    }

    // Get the declared configuration of the EnsureNonNullIf method under analysis
    Symbol.MethodSymbol methodSymbolUnderAnalysis =
        NullabilityUtil.castToNonNull(methodAnalysisContextUnderAnalysis).methodSymbol();

    Set<String> fieldNames = getAnnotationValueArray(methodSymbolUnderAnalysis, annotName, false);
    if (fieldNames == null) {
      throw new RuntimeException("List of field names shouldn't be null");
    }

    boolean trueIfNonNull = getResultValueFromAnnotation(methodSymbolUnderAnalysis);

    // We extract all the fields that are considered non-null by the data-flow engine
    // We pick the "thenStore" case in case result is set to true
    // or "elseStore" in case result is set to false
    // and check whether the non-full fields match the ones in the annotation parameter
    NullnessStore chosenStore = trueIfNonNull ? thenStore : elseStore;
    Set<String> nonNullFieldsInPath =
        chosenStore.getNonNullReceiverFields().stream()
            .map(e -> e.getSimpleName().toString())
            .collect(Collectors.toSet());
    Set<String> nonNullStaticFieldsInPath =
        chosenStore.getNonNullStaticFields().stream()
            .map(e -> e.getSimpleName().toString())
            .collect(Collectors.toSet());
    nonNullFieldsInPath.addAll(nonNullStaticFieldsInPath);
    boolean allFieldsAreNonNull = nonNullFieldsInPath.containsAll(fieldNames);

    // Whether the return true expression evaluates to a boolean literal or not.  If null, then not
    // a boolean literal.
    Boolean expressionAsBoolean = null;
    if (returnTree.getExpression() instanceof LiteralTree) {
      LiteralTree expressionAsLiteral = (LiteralTree) returnTree.getExpression();
      if (expressionAsLiteral.getValue() instanceof Boolean) {
        expressionAsBoolean = (Boolean) expressionAsLiteral.getValue();
      }
    }

    /*
     * Identify whether the expression is a boolean literal and whether
     * it evaluates to the correct literal.
     * - If result param in annotation is set to true, then expression should return true.
     * - If result param in annotation is set to false, then expression should return false.
     */
    boolean isBooleanLiteral = expressionAsBoolean != null;
    boolean evaluatesToNonNullLiteral =
        expressionAsBoolean != null && (trueIfNonNull == expressionAsBoolean);

    /*
     * Decide whether the semantics of this ReturnTree are correct.
     * The decision is as follows:
     *
     * If all fields in the path are verified:
     * - Semantics are valid
     *
     * If fields in path aren't verified:
     * - If the literal boolean is the opposite of the configured non-null boolean, semantics
     * are then correct, as the method correctly returns in case the semantics don't hold.
     * - Otherwise, semantics are wrong, as the method incorrectly returns.
     * - If the expression isn't a literal boolean, then semantics are wrong, as we
     * assume it is possible that the configured non-null boolean can be returned.
     */
    if (!allFieldsAreNonNull) {
      if (evaluatesToNonNullLiteral || !isBooleanLiteral) {
        fieldNames.removeAll(nonNullFieldsInPath);
        String message =
            String.format(
                "Method is annotated with @EnsuresNonNullIf but does not ensure fields %s",
                fieldNames);
        raiseError(returnTree, state, message);
      }
    }
  }