public void onMatchMethod()

in nullaway/src/main/java/com/uber/nullaway/handlers/contract/ContractCheckHandler.java [89:208]


  public void onMatchMethod(MethodTree tree, MethodAnalysisContext methodAnalysisContext) {
    Symbol.MethodSymbol callee = ASTHelpers.getSymbol(tree);
    Preconditions.checkNotNull(callee);
    // Check to see if this method has an @Contract annotation
    String contractString = ContractUtils.getContractString(callee, config);
    if (contractString != null) {
      // Found a contract, lets parse it.
      String[] clauses = contractString.split(";");
      if (clauses.length != 1) {
        return;
      }

      String clause = clauses[0];
      NullAway analysis = methodAnalysisContext.analysis();
      VisitorState state = methodAnalysisContext.state();
      String[] antecedent =
          getAntecedent(clause, tree, analysis, state, callee, tree.getParameters().size());
      String consequent = getConsequent(clause, tree, analysis, state, callee);

      boolean checkMethodBody = config.checkContracts();

      for (int i = 0; i < antecedent.length; ++i) {
        String valueConstraint = antecedent[i].trim();
        if (!allValidValueConstraints.contains(valueConstraint)) {
          String errorMessage =
              "Invalid @Contract annotation detected for method "
                  + callee
                  + ". It contains the following unparseable clause: "
                  + clause
                  + " (unknown value constraint: "
                  + valueConstraint
                  + ", see https://www.jetbrains.com/help/idea/contract-annotations.html).";
          state.reportMatch(
              analysis
                  .getErrorBuilder()
                  .createErrorDescription(
                      new ErrorMessage(
                          ErrorMessage.MessageTypes.ANNOTATION_VALUE_INVALID, errorMessage),
                      tree,
                      analysis.buildDescription(tree),
                      state,
                      null));
          checkMethodBody = false;
        } else if (!checkableValueConstraints.contains(valueConstraint)) {
          checkMethodBody = false;
        }
      }

      if (!consequent.equals("!null")) {
        checkMethodBody = false;
      }

      if (!checkMethodBody) {
        return;
      }

      // we scan the method tree for the return nodes and check the contract
      new TreePathScanner<@Nullable Void, @Nullable Void>() {

        @Override
        public @Nullable Void visitLambdaExpression(
            LambdaExpressionTree node, @Nullable Void unused) {
          // do not scan into lambdas
          return null;
        }

        @Override
        public @Nullable Void visitClass(ClassTree node, @Nullable Void unused) {
          // do not scan into local/anonymous classes
          return null;
        }

        @Override
        public @Nullable Void visitReturn(ReturnTree returnTree, @Nullable Void unused) {

          ExpressionTree returnExpression = returnTree.getExpression();
          if (returnExpression == null) {
            // this should only be possible with an invalid @Contract on a void-returning method
            return null;
          }
          TreePath returnExpressionPath = new TreePath(getCurrentPath(), returnExpression);
          AccessPathNullnessAnalysis nullnessAnalysis = analysis.getNullnessAnalysis(state);
          List<TreePath> allPossiblyReturnedExpressions = new ArrayList<>();
          collectNestedReturnedExpressions(returnExpressionPath, allPossiblyReturnedExpressions);

          boolean contractViolated = false;
          for (TreePath expressionPath : allPossiblyReturnedExpressions) {
            Nullness nullness =
                nullnessAnalysis.getNullnessForContractDataflow(expressionPath, state.context);
            if (nullness == Nullness.NULLABLE || nullness == Nullness.NULL) {
              if (nullnessAnalysis.hasBottomAccessPathForContractDataflow(
                  expressionPath, state.context)) {
                // if any access path is mapped to bottom, this branch is unreachable
                continue;
              }
              contractViolated = true;
              break;
            }
          }

          if (contractViolated) {
            String errorMessage =
                getErrorMessageForViolatedContract(antecedent, callee, contractString, tree);

            state.reportMatch(
                analysis
                    .getErrorBuilder()
                    .createErrorDescription(
                        new ErrorMessage(
                            ErrorMessage.MessageTypes.ANNOTATION_VALUE_INVALID, errorMessage),
                        returnTree,
                        analysis.buildDescription(returnTree),
                        state,
                        null));
          }
          return super.visitReturn(returnTree, null);
        }
      }.scan(state.getPath(), null);
    }
  }