Generalize support for const member functions

Treat consecutive const member function calls as idempotent, even when they aren't simple accessors/the implementation is not visible.

PiperOrigin-RevId: 580596960
Change-Id: Id75da4b24ccf48e63b6d4e8060dbaf261c690169
diff --git a/nullability/pointer_nullability_lattice.h b/nullability/pointer_nullability_lattice.h
index 932fbb3..6457ca5 100644
--- a/nullability/pointer_nullability_lattice.h
+++ b/nullability/pointer_nullability_lattice.h
@@ -12,9 +12,13 @@
 #include "absl/container/flat_hash_map.h"
 #include "absl/log/check.h"
 #include "nullability/type_nullability.h"
+#include "clang/AST/DeclCXX.h"
 #include "clang/AST/Expr.h"
 #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
 #include "clang/Analysis/FlowSensitive/DataflowLattice.h"
+#include "clang/Analysis/FlowSensitive/StorageLocation.h"
+#include "clang/Analysis/FlowSensitive/Value.h"
 #include "clang/Basic/LLVM.h"
 #include "llvm/ADT/FunctionExtras.h"
 
@@ -62,6 +66,25 @@
     return Iterator->second;
   }
 
+  // Gets the PointerValue associated with the RecordStorageLocation and
+  // MethodDecl of the CallExpr, creating one if it doesn't yet exist. Requires
+  // the CXXMemberCallExpr to have a supported pointer type.
+  dataflow::PointerValue *getConstMethodReturnValue(
+      const dataflow::RecordStorageLocation &RecordLoc,
+      const CXXMemberCallExpr *MCE, dataflow::Environment &Env) {
+    auto &ObjMap = ConstMethodReturnValues[&RecordLoc];
+    auto it = ObjMap.find(MCE->getMethodDecl());
+    if (it != ObjMap.end()) return it->second;
+    auto *PV = cast<dataflow::PointerValue>(Env.createValue(MCE->getType()));
+    ObjMap.insert({MCE->getMethodDecl(), PV});
+    return PV;
+  }
+
+  void clearConstMethodReturnValues(
+      const dataflow::RecordStorageLocation &RecordLoc) {
+    ConstMethodReturnValues.erase(&RecordLoc);
+  }
+
   // If nullability for the decl D has been overridden, patch N to reflect it.
   // (N is the nullability of an access to D).
   void overrideNullabilityFromDecl(const Decl *D, TypeNullability &N) const;
@@ -69,13 +92,29 @@
   bool operator==(const PointerNullabilityLattice &Other) const { return true; }
 
   dataflow::LatticeJoinEffect join(const PointerNullabilityLattice &Other) {
-    return dataflow::LatticeJoinEffect::Unchanged;
+    if (ConstMethodReturnValues.empty())
+      return dataflow::LatticeJoinEffect::Unchanged;
+    // Conservatively, just clear the `ConstMethodReturnValues` map entirely.
+    // This means that we can't check the return value from a const method
+    // before a join, then call the method again to use the pointer after the
+    // join -- we'll get a false positive in this case.
+    // TODO(b/309667920): Add code to actually join the maps if it turns out
+    // these types of false positives are common.
+    ConstMethodReturnValues.clear();
+    return dataflow::LatticeJoinEffect::Changed;
   }
 
  private:
   // Owned by the PointerNullabilityAnalysis object, shared by all lattice
   // elements within one analysis run.
   NonFlowSensitiveState &NFS;
+
+  // Maps a record storage location and const method to the value to return
+  // from that const method.
+  llvm::SmallDenseMap<
+      const dataflow::RecordStorageLocation *,
+      llvm::SmallDenseMap<const CXXMethodDecl *, dataflow::PointerValue *>>
+      ConstMethodReturnValues;
 };
 
 inline std::ostream &operator<<(std::ostream &OS,