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