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