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