Collect evidence for return type from return statements.
Records Nonnull, Nullable, and Unknown return values.
PiperOrigin-RevId: 549329488
Change-Id: I3c95554d674e5555a25cc99d771509162b7cb78b
diff --git a/nullability/inference/collect_evidence.cc b/nullability/inference/collect_evidence.cc
index 2f91725..9c423a3 100644
--- a/nullability/inference/collect_evidence.cc
+++ b/nullability/inference/collect_evidence.cc
@@ -93,9 +93,8 @@
}
void collectEvidenceFromDereference(
- std::vector<std::pair<PointerTypeNullability, Slot>> InferrableSlots,
- const CFGElement &Element, const PointerNullabilityLattice &Lattice,
- const dataflow::Environment &Env,
+ std::vector<std::pair<PointerTypeNullability, Slot>> &InferrableSlots,
+ const CFGElement &Element, const dataflow::Environment &Env,
llvm::function_ref<EvidenceEmitter> Emit) {
// Is this CFGElement a dereference of a pointer?
auto CFGStmt = Element.getAs<clang::CFGStmt>();
@@ -132,10 +131,22 @@
}
}
+const dataflow::Formula *getInferrableSlotsUnknownConstraint(
+ std::vector<std::pair<PointerTypeNullability, Slot>> &InferrableSlots,
+ const dataflow::Environment &Env) {
+ dataflow::Arena &A = Env.getDataflowAnalysisContext().arena();
+ const dataflow::Formula *CallerSlotsUnknown = &A.makeLiteral(true);
+ for (auto &[Nullability, Slot] : InferrableSlots) {
+ CallerSlotsUnknown = &A.makeAnd(
+ *CallerSlotsUnknown, A.makeAnd(A.makeNot(Nullability.isNullable(A)),
+ A.makeNot(Nullability.isNonnull(A))));
+ }
+ return CallerSlotsUnknown;
+}
+
void collectEvidenceFromCallExpr(
- std::vector<std::pair<PointerTypeNullability, Slot>> InferrableCallerSlots,
- const CFGElement &Element, const PointerNullabilityLattice &Lattice,
- const dataflow::Environment &Env,
+ std::vector<std::pair<PointerTypeNullability, Slot>> &InferrableCallerSlots,
+ const CFGElement &Element, const dataflow::Environment &Env,
llvm::function_ref<EvidenceEmitter> Emit) {
// Is this CFGElement a call to a function?
auto CFGStmt = Element.getAs<clang::CFGStmt>();
@@ -169,16 +180,9 @@
// nullability based on InferrableSlots for the caller being assigned to
// Unknown, to reflect the current annotations and not all possible
// annotations for them.
- dataflow::Arena &A = Env.getDataflowAnalysisContext().arena();
- const dataflow::Formula *CallerSlotsUnknown = &A.makeLiteral(true);
- for (auto &[Nullability, Slot] : InferrableCallerSlots) {
- CallerSlotsUnknown = &A.makeAnd(
- *CallerSlotsUnknown, A.makeAnd(A.makeNot(Nullability.isNullable(A)),
- A.makeNot(Nullability.isNonnull(A))));
- }
-
- NullabilityKind ArgNullability =
- getNullability(*PV, Env, CallerSlotsUnknown);
+ NullabilityKind ArgNullability = getNullability(
+ *PV, Env,
+ getInferrableSlotsUnknownConstraint(InferrableCallerSlots, Env));
Evidence::Kind ArgEvidenceKind;
switch (ArgNullability) {
case NullabilityKind::Nullable:
@@ -195,12 +199,43 @@
}
}
+void collectEvidenceFromReturn(
+ std::vector<std::pair<PointerTypeNullability, Slot>> &InferrableSlots,
+ const CFGElement &Element, const dataflow::Environment &Env,
+ llvm::function_ref<EvidenceEmitter> Emit) {
+ // Is this CFGElement a return statement?
+ auto CFGStmt = Element.getAs<clang::CFGStmt>();
+ if (!CFGStmt) return;
+ auto *ReturnStmt = dyn_cast_or_null<clang::ReturnStmt>(CFGStmt->getStmt());
+ if (!ReturnStmt) return;
+ auto *ReturnExpr = ReturnStmt->getRetValue();
+ if (!ReturnExpr || !ReturnExpr->getType()->isPointerType()) return;
+
+ NullabilityKind ReturnNullability =
+ getNullability(ReturnExpr, Env,
+ getInferrableSlotsUnknownConstraint(InferrableSlots, Env));
+ Evidence::Kind ReturnEvidenceKind;
+ switch (ReturnNullability) {
+ case NullabilityKind::Nullable:
+ ReturnEvidenceKind = Evidence::NULLABLE_RETURN;
+ break;
+ case NullabilityKind::NonNull:
+ ReturnEvidenceKind = Evidence::NONNULL_RETURN;
+ break;
+ default:
+ ReturnEvidenceKind = Evidence::UNKNOWN_RETURN;
+ }
+ Emit(*Env.getCurrentFunc(), SLOT_RETURN_TYPE, ReturnEvidenceKind,
+ ReturnExpr->getExprLoc());
+}
+
void collectEvidenceFromElement(
std::vector<std::pair<PointerTypeNullability, Slot>> InferrableSlots,
- const CFGElement &Element, const PointerNullabilityLattice &Lattice,
- const Environment &Env, llvm::function_ref<EvidenceEmitter> Emit) {
- collectEvidenceFromDereference(InferrableSlots, Element, Lattice, Env, Emit);
- collectEvidenceFromCallExpr(InferrableSlots, Element, Lattice, Env, Emit);
+ const CFGElement &Element, const Environment &Env,
+ llvm::function_ref<EvidenceEmitter> Emit) {
+ collectEvidenceFromDereference(InferrableSlots, Element, Env, Emit);
+ collectEvidenceFromCallExpr(InferrableSlots, Element, Env, Emit);
+ collectEvidenceFromReturn(InferrableSlots, Element, Env, Emit);
// TODO: add location information.
// TODO: add more heuristic collections here
}
@@ -257,8 +292,8 @@
[&](const CFGElement &Element,
const dataflow::DataflowAnalysisState<PointerNullabilityLattice>
&State) {
- collectEvidenceFromElement(InferrableSlots, Element, State.Lattice,
- State.Env, Emit);
+ collectEvidenceFromElement(InferrableSlots, Element, State.Env,
+ Emit);
});
return llvm::Error::success();
diff --git a/nullability/inference/collect_evidence_test.cc b/nullability/inference/collect_evidence_test.cc
index d8eefaf..eeac263 100644
--- a/nullability/inference/collect_evidence_test.cc
+++ b/nullability/inference/collect_evidence_test.cc
@@ -326,6 +326,53 @@
EXPECT_THAT(collectEvidenceFromTargetFunction(Src), IsEmpty());
}
+TEST(CollectEvidenceFromImplementationTest, NullableReturn) {
+ static constexpr llvm::StringRef Src = R"cc(
+ int* target() { return nullptr; }
+ )cc";
+ EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
+ Contains(evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_RETURN,
+ functionNamed("target"))));
+}
+
+TEST(CollectEvidenceFromImplementationTest, NonnullReturn) {
+ static constexpr llvm::StringRef Src = R"cc(
+ int* target(Nonnull<int*> p) {
+ return p;
+ }
+ )cc";
+ EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
+ Contains(evidence(SLOT_RETURN_TYPE, Evidence::NONNULL_RETURN,
+ functionNamed("target"))));
+}
+
+TEST(CollectEvidenceFromImplementationTest, UnknownReturn) {
+ static constexpr llvm::StringRef Src = R"cc(
+ int* target(int* p) { return p; }
+ )cc";
+ EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
+ Contains(evidence(SLOT_RETURN_TYPE, Evidence::UNKNOWN_RETURN,
+ functionNamed("target"))));
+}
+
+TEST(CollectEvidenceFromImplementationTest, MultipleReturns) {
+ static constexpr llvm::StringRef Src = R"cc(
+ int* target(Nonnull<int*> p, Nullable<int*> q, bool b, bool c) {
+ if (b) return q;
+ if (c) return nullptr;
+ return p;
+ }
+ )cc";
+ EXPECT_THAT(
+ collectEvidenceFromTargetFunction(Src),
+ UnorderedElementsAre(evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_RETURN,
+ functionNamed("target")),
+ evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_RETURN,
+ functionNamed("target")),
+ evidence(SLOT_RETURN_TYPE, Evidence::NONNULL_RETURN,
+ functionNamed("target"))));
+}
+
TEST(CollectEvidenceFromDeclarationTest, VariableDeclIgnored) {
llvm::StringLiteral Src = "Nullable<int *> target;";
EXPECT_THAT(collectEvidenceFromTargetDecl(Src), IsEmpty());
diff --git a/nullability/inference/inference.proto b/nullability/inference/inference.proto
index 56ae3df..f79533f 100644
--- a/nullability/inference/inference.proto
+++ b/nullability/inference/inference.proto
@@ -66,6 +66,12 @@
NONNULL_ARGUMENT = 5;
// A value with Unknown nullability was passed as an argument.
UNKNOWN_ARGUMENT = 6;
+ // A Nullable value was returned.
+ NULLABLE_RETURN = 7;
+ // A Nonnull value was returned.
+ NONNULL_RETURN = 8;
+ // A value with Unknown nullability was returned.
+ UNKNOWN_RETURN = 9;
}
}
diff --git a/nullability/pointer_nullability.h b/nullability/pointer_nullability.h
index 3644148..7dfef80 100644
--- a/nullability/pointer_nullability.h
+++ b/nullability/pointer_nullability.h
@@ -119,9 +119,11 @@
/// Returns the strongest provable assertion we can make about the value of
/// `E` in `Env`.
-inline clang::NullabilityKind getNullability(const Expr *E,
- const dataflow::Environment &Env) {
- if (auto *P = getPointerValueFromExpr(E, Env)) return getNullability(*P, Env);
+inline clang::NullabilityKind getNullability(
+ const Expr *E, const dataflow::Environment &Env,
+ const dataflow::Formula *AdditionalConstraints = nullptr) {
+ if (auto *P = getPointerValueFromExpr(E, Env))
+ return getNullability(*P, Env, AdditionalConstraints);
return clang::NullabilityKind::Unspecified;
}