Collect evidence from assignments.
Evidence collection involving binding values to types does not yet incorporate information from previous inferences input or from InferableSlotsConstraint. That's coming soon in a separate change.
PiperOrigin-RevId: 578905076
Change-Id: If1b9878be063dd5a2d2127ea111b3d5543eee894
diff --git a/nullability/inference/collect_evidence.cc b/nullability/inference/collect_evidence.cc
index f726a05..d3d8ce5 100644
--- a/nullability/inference/collect_evidence.cc
+++ b/nullability/inference/collect_evidence.cc
@@ -199,17 +199,20 @@
return *Constraint;
}
-void collectEvidenceFromParamAnnotation(
- TypeNullability &ParamNullability, const dataflow::PointerValue &ArgPV,
- std::vector<std::pair<PointerTypeNullability, Slot>> &InferableCallerSlots,
- const dataflow::Environment &Env, SourceLocation ArgLoc,
+void collectEvidenceFromBindingToType(
+ TypeNullability &TypeNullability,
+ const dataflow::PointerValue &PointerValue,
+ std::vector<std::pair<PointerTypeNullability, Slot>>
+ &InferableSlotsFromValueContext,
+ const dataflow::Environment &Env, SourceLocation ValueLoc,
llvm::function_ref<EvidenceEmitter> Emit) {
// TODO: Account for variance and each layer of nullability when we handle
// more than top-level pointers.
- if (ParamNullability.empty()) return;
- if (ParamNullability[0].concrete() == NullabilityKind::NonNull) {
- collectMustBeNonnullEvidence(ArgPV, Env, ArgLoc, InferableCallerSlots,
- Evidence::PASSED_TO_NONNULL, Emit);
+ if (TypeNullability.empty()) return;
+ if (TypeNullability[0].concrete() == NullabilityKind::NonNull) {
+ collectMustBeNonnullEvidence(PointerValue, Env, ValueLoc,
+ InferableSlotsFromValueContext,
+ Evidence::BOUND_TO_NONNULL, Emit);
}
}
@@ -257,8 +260,8 @@
// Collect evidence from the binding of the argument to the parameter's
// nullability, if known.
- collectEvidenceFromParamAnnotation(ParamNullability, *PV,
- InferableCallerSlots, Env, ArgLoc, Emit);
+ collectEvidenceFromBindingToType(ParamNullability, *PV,
+ InferableCallerSlots, Env, ArgLoc, Emit);
// Emit evidence of the parameter's nullability. First, calculate that
// nullability based on InferableSlots for the caller being assigned to
@@ -315,6 +318,45 @@
ReturnExpr->getExprLoc());
}
+void collectEvidenceFromAssignment(
+ std::vector<std::pair<PointerTypeNullability, Slot>> &InferableSlots,
+ const CFGElement &Element, const dataflow::Environment &Env,
+ llvm::function_ref<EvidenceEmitter> Emit) {
+ auto CFGStmt = Element.getAs<clang::CFGStmt>();
+ if (!CFGStmt) return;
+
+ // Initialization of new decl.
+ if (auto *DeclStmt = dyn_cast_or_null<clang::DeclStmt>(CFGStmt->getStmt())) {
+ for (auto *Decl : DeclStmt->decls()) {
+ if (auto *VarDecl = dyn_cast_or_null<clang::VarDecl>(Decl);
+ VarDecl && isSupportedPointerType(VarDecl->getType()) &&
+ VarDecl->hasInit()) {
+ auto *PV = getPointerValueFromExpr(VarDecl->getInit(), Env);
+ if (!PV) return;
+ TypeNullability TypeNullability =
+ getNullabilityAnnotationsFromType(VarDecl->getType());
+ collectEvidenceFromBindingToType(TypeNullability, *PV, InferableSlots,
+ Env, VarDecl->getInit()->getExprLoc(),
+ Emit);
+ }
+ }
+ }
+
+ // Assignment to existing decl.
+ if (auto *BinaryOperator =
+ dyn_cast_or_null<clang::BinaryOperator>(CFGStmt->getStmt());
+ BinaryOperator && BinaryOperator->isAssignmentOp() &&
+ isSupportedPointerType(BinaryOperator->getLHS()->getType())) {
+ auto *PV = getPointerValueFromExpr(BinaryOperator->getRHS(), Env);
+ if (!PV) return;
+ TypeNullability TypeNullability =
+ getNullabilityAnnotationsFromType(BinaryOperator->getLHS()->getType());
+ collectEvidenceFromBindingToType(TypeNullability, *PV, InferableSlots, Env,
+ BinaryOperator->getRHS()->getExprLoc(),
+ Emit);
+ }
+}
+
void collectEvidenceFromElement(
std::vector<std::pair<PointerTypeNullability, Slot>> InferableSlots,
const Formula &InferableSlotsConstraint, const CFGElement &Element,
@@ -324,6 +366,7 @@
Env, Emit);
collectEvidenceFromReturn(InferableSlots, InferableSlotsConstraint, Element,
Env, Emit);
+ collectEvidenceFromAssignment(InferableSlots, Element, Env, Emit);
// TODO: add more heuristic collections here
}
diff --git a/nullability/inference/collect_evidence_test.cc b/nullability/inference/collect_evidence_test.cc
index 814ee15..58ed6ca 100644
--- a/nullability/inference/collect_evidence_test.cc
+++ b/nullability/inference/collect_evidence_test.cc
@@ -458,9 +458,29 @@
void target(int* p) { callee(p); }
)cc";
- EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
- Contains(evidence(paramSlot(0), Evidence::PASSED_TO_NONNULL,
- functionNamed("target"))));
+ EXPECT_THAT(
+ collectEvidenceFromTargetFunction(Src),
+ UnorderedElementsAre(evidence(paramSlot(0), Evidence::BOUND_TO_NONNULL,
+ functionNamed("target")),
+ evidence(paramSlot(0), Evidence::UNKNOWN_ARGUMENT,
+ functionNamed("callee"))));
+}
+
+TEST(CollectEvidenceFromImplementationTest, AssignedToNonnull) {
+ static constexpr llvm::StringRef Src = R"cc(
+ void target(int* p, int* q, int* r) {
+ Nonnull<int*> a = p, b = q;
+ a = r;
+ }
+ )cc";
+ EXPECT_THAT(
+ collectEvidenceFromTargetFunction(Src),
+ UnorderedElementsAre(evidence(paramSlot(0), Evidence::BOUND_TO_NONNULL,
+ functionNamed("target")),
+ evidence(paramSlot(1), Evidence::BOUND_TO_NONNULL,
+ functionNamed("target")),
+ evidence(paramSlot(2), Evidence::BOUND_TO_NONNULL,
+ functionNamed("target"))));
}
// A crash repro involving callable parameters.
diff --git a/nullability/inference/inference.proto b/nullability/inference/inference.proto
index f096210..7a1d612 100644
--- a/nullability/inference/inference.proto
+++ b/nullability/inference/inference.proto
@@ -72,8 +72,8 @@
NONNULL_RETURN = 8;
// A value with Unknown nullability was returned.
UNKNOWN_RETURN = 9;
- // A value was passed to a Nonnull parameter.
- PASSED_TO_NONNULL = 10;
+ // A value was bound to a Nonnull declaration.
+ BOUND_TO_NONNULL = 10;
}
}
diff --git a/nullability/inference/merge.cc b/nullability/inference/merge.cc
index 5f60a54..33f7a06 100644
--- a/nullability/inference/merge.cc
+++ b/nullability/inference/merge.cc
@@ -127,7 +127,7 @@
if (Counts[Evidence::NONNULL_RETURN] && !Counts[Evidence::NULLABLE_RETURN] &&
!Counts[Evidence::UNKNOWN_RETURN])
update(Result, Inference::NONNULL);
- if (Counts[Evidence::PASSED_TO_NONNULL]) update(Result, Inference::NONNULL);
+ if (Counts[Evidence::BOUND_TO_NONNULL]) update(Result, Inference::NONNULL);
if (Result) return *Result;
// Optional "soft" inference heuristics.
diff --git a/nullability/inference/merge_test.cc b/nullability/inference/merge_test.cc
index c3a5ad2..f28f1fa 100644
--- a/nullability/inference/merge_test.cc
+++ b/nullability/inference/merge_test.cc
@@ -244,7 +244,7 @@
}
TEST_F(InferTest, PassedToNonnull) {
- add(Evidence::PASSED_TO_NONNULL);
+ add(Evidence::BOUND_TO_NONNULL);
EXPECT_EQ(Inference::NONNULL, infer());
}