Treat base->member as a dereference of base, for inference

PiperOrigin-RevId: 565356557
Change-Id: I6dde05cf3bd2180835e7340179ac20dce4c6646d
diff --git a/nullability/inference/collect_evidence.cc b/nullability/inference/collect_evidence.cc
index d0552c1..d2a6a64 100644
--- a/nullability/inference/collect_evidence.cc
+++ b/nullability/inference/collect_evidence.cc
@@ -93,27 +93,37 @@
 
 namespace {
 
+// If Element is a dereference, returns its target and location.
+std::pair<Expr *, SourceLocation> describeDereference(
+    const CFGElement &Element) {
+  if (auto CFGStmt = Element.getAs<clang::CFGStmt>()) {
+    if (auto *Op = dyn_cast<UnaryOperator>(CFGStmt->getStmt());
+        Op && Op->getOpcode() == UO_Deref) {
+      return {Op->getSubExpr(), Op->getOperatorLoc()};
+    }
+    if (auto *ME = dyn_cast<MemberExpr>(CFGStmt->getStmt());
+        ME && ME->isArrow()) {
+      return {ME->getBase(), ME->getOperatorLoc()};
+    }
+  }
+  return {nullptr, SourceLocation()};
+}
+
 void collectEvidenceFromDereference(
     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>();
-  if (!CFGStmt) return;
-  auto *Op = dyn_cast_or_null<UnaryOperator>(CFGStmt->getStmt());
-  if (!Op || Op->getOpcode() != UO_Deref) return;
-  auto *DereferencedExpr = Op->getSubExpr();
-  if (!DereferencedExpr || !DereferencedExpr->getType()->isPointerType())
-    return;
+  auto [Target, Loc] = describeDereference(Element);
+  if (!Target || !Target->getType()->isPointerType()) return;
 
   // It is a dereference of a pointer. Now gather evidence from it.
 
-  // Skip gathering evidence about the current function if the current function
-  // is not an inference target.
+  // Skip gathering evidence about the current function if the current
+  // function is not an inference target.
   if (!isInferenceTarget(*Env.getCurrentFunc())) return;
 
   dataflow::PointerValue *DereferencedValue =
-      getPointerValueFromExpr(DereferencedExpr, Env);
+      getPointerValueFromExpr(Target, Env);
   if (!DereferencedValue) return;
   auto &A = Env.getDataflowAnalysisContext().arena();
   auto &NotIsNull =
@@ -132,8 +142,7 @@
     auto &SlotNonnullImpliesDerefValueNonnull =
         A.makeImplies(Nullability.isNonnull(A), NotIsNull);
     if (Env.flowConditionImplies(SlotNonnullImpliesDerefValueNonnull))
-      Emit(*Env.getCurrentFunc(), Slot, Evidence::UNCHECKED_DEREFERENCE,
-           Op->getOperatorLoc());
+      Emit(*Env.getCurrentFunc(), Slot, Evidence::UNCHECKED_DEREFERENCE, Loc);
   }
 }
 
diff --git a/nullability/inference/collect_evidence_test.cc b/nullability/inference/collect_evidence_test.cc
index 696a7cd..4fca9e2 100644
--- a/nullability/inference/collect_evidence_test.cc
+++ b/nullability/inference/collect_evidence_test.cc
@@ -123,6 +123,22 @@
               UnorderedElementsAre(
                   evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE)));
 }
+TEST(CollectEvidenceFromImplementationTest, DerefArrow) {
+  static constexpr llvm::StringRef Src = R"cc(
+    struct S {
+      int x;
+      int y();
+    };
+    void target(S *a, S *b) {
+      a->x;
+      b->y();
+    }
+  )cc";
+  EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
+              UnorderedElementsAre(
+                  evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE),
+                  evidence(paramSlot(1), Evidence::UNCHECKED_DEREFERENCE)));
+}
 TEST(InferAnnotationsTest, DerefOfNonnull) {
   static constexpr llvm::StringRef Src = R"cc(
     void target(Nonnull<int *> p) {