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