public MethodInvocationNode onCFGBuildPhase1AfterVisitMethodInvocation()

in nullaway/src/main/java/com/uber/nullaway/handlers/contract/ContractHandler.java [108:181]


  public MethodInvocationNode onCFGBuildPhase1AfterVisitMethodInvocation(
      NullAwayCFGBuilder.NullAwayCFGTranslationPhaseOne phase,
      MethodInvocationTree tree,
      MethodInvocationNode originalNode) {
    Preconditions.checkNotNull(storedVisitorState);
    Preconditions.checkNotNull(analysis);
    Symbol.MethodSymbol callee = ASTHelpers.getSymbol(tree);
    Preconditions.checkNotNull(callee);
    for (String clause : ContractUtils.getContractClauses(callee, config)) {
      // This method currently handles contracts of the form `(true|false) -> fail`, other
      // contracts are handled by 'onDataflowVisitMethodInvocation' which has access to more
      // dataflow information.
      if (!"fail".equals(getConsequent(clause, tree, analysis, storedVisitorState, callee))) {
        continue;
      }
      String[] antecedent =
          getAntecedent(
              clause,
              tree,
              analysis,
              storedVisitorState,
              callee,
              originalNode.getArguments().size());
      // Find a single value constraint that is not already known. If more than one argument with
      // unknown nullness affects the method's result, then ignore this clause.
      Node arg = null;
      // Set to false if the rule is detected to be one we don't yet support
      boolean supported = true;
      boolean booleanConstraint = false;

      for (int i = 0; i < antecedent.length; ++i) {
        String valueConstraint = antecedent[i].trim();
        if ("false".equals(valueConstraint) || "true".equals(valueConstraint)) {
          if (arg != null) {
            // We don't currently support contracts depending on the boolean value of more than one
            // argument using the node-insertion method.
            supported = false;
            break;
          }
          booleanConstraint = Boolean.parseBoolean(valueConstraint);
          arg = originalNode.getArgument(i);
        } else if (!valueConstraint.equals("_")) {
          // Found an unsupported type of constraint, only true, false, and '_' (wildcard) are
          // supported.
          // No need to implement complex handling here, 'onDataflowVisitMethodInvocation' will
          // validate the contract.
          supported = false;
          break;
        }
      }
      if (supported) {
        // In practice the failure may not be RuntimeException, however the
        // throw is inserted after the method invocation where we must assume that
        // any invocation is capable of throwing an unchecked throwable.
        if (runtimeExceptionType == null) {
          runtimeExceptionType = phase.classToErrorType(RuntimeException.class);
        }
        Preconditions.checkNotNull(runtimeExceptionType);
        // If arg != null, we found a single boolean antecedent that determines whether the call
        // fails, as reflected by booleanConstraint.  Otherwise, all antecedents are '_' (or there
        // are no antecedents), meaning the method fails unconditionally
        if (arg != null) {
          if (booleanConstraint) {
            phase.insertThrowOnTrue(arg, runtimeExceptionType);
          } else {
            phase.insertThrowOnFalse(arg, runtimeExceptionType);
          }
        } else {
          phase.insertUnconditionalThrow(runtimeExceptionType);
        }
      }
    }
    return originalNode;
  }