[clang] b73ce3e - [SSAF][PointerFlow] Add PointerFlow summary and extractor (#188654)

via cfe-commits cfe-commits at lists.llvm.org
Tue May 5 13:02:03 PDT 2026


Author: Ziqing Luo
Date: 2026-05-05T13:01:58-07:00
New Revision: b73ce3e53fcb9f72c759139e93db7e16a813c3b2

URL: https://github.com/llvm/llvm-project/commit/b73ce3e53fcb9f72c759139e93db7e16a813c3b2
DIFF: https://github.com/llvm/llvm-project/commit/b73ce3e53fcb9f72c759139e93db7e16a813c3b2.diff

LOG: [SSAF][PointerFlow] Add PointerFlow summary and extractor (#188654)

Implement a PointerFlow summary and extractor, which uses
EntityPointerLevel. An assignment is extracted as a pair of
EntityPointerLevels, representing a directed edge.
Edges extracted from assignments form a directed graph
encoding abstract pointer flow information.

rdar://172429193

---------

Co-authored-by: Balázs Benics <benicsbalazs at gmail.com>
Co-authored-by: Jan Korous <jkorous at apple.com>

Added: 
    clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h
    clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.cpp
    clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.cpp
    clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowExtractor.cpp
    clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowFormat.cpp
    clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageFormat.cpp
    clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-array.json
    clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-summary.json
    clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-no-key.json
    clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary.json
    clang/test/Analysis/Scalable/PointerFlow/tu-summary-serialization.test
    clang/unittests/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowTest.cpp

Modified: 
    clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h
    clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h
    clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h
    clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
    clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
    clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
    clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.cpp
    clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.cpp
    clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
    clang/lib/ScalableStaticAnalysisFramework/Tool/Utils.cpp
    clang/test/Analysis/Scalable/ssaf-format/list.test
    clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h
index 429bb74fb417e..98812e0ea04e3 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h
@@ -43,7 +43,7 @@ class EntityPointerLevel {
   // For unittests:
   friend EntityPointerLevel buildEntityPointerLevel(EntityId, unsigned);
 
-  EntityPointerLevel(std::pair<EntityId, unsigned> Pair)
+  explicit EntityPointerLevel(std::pair<EntityId, unsigned> Pair)
       : Entity(Pair.first), PointerLevel(Pair.second) {}
 
 public:

diff  --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h
index 7b125edb1fb7f..568e03594005e 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h
@@ -1,4 +1,4 @@
-//===- EntityPointerLevelFormat.h -------------------------------*- C++-*-===//
+//===- EntityPointerLevelFormat.h -------------------------------*- C++ -*-===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.

diff  --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h
new file mode 100644
index 0000000000000..eda7f138d9e9b
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h
@@ -0,0 +1,51 @@
+//===- PointerFlow.h --------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file defines an analysis that builds directed graphs where nodes
+//  are pointers and edges are assignment operations, each of which bridges two
+//  nodes.
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_POINTERFLOW_POINTERFLOW_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_POINTERFLOW_POINTERFLOW_H
+
+#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/EntitySummary.h"
+
+namespace clang::ssaf {
+
+/// Maps each source node to its destination nodes:
+using EdgeSet = std::map<EntityPointerLevel, EntityPointerLevelSet>;
+
+class PointerFlowEntitySummary final : public EntitySummary {
+  EdgeSet Edges;
+
+  friend PointerFlowEntitySummary buildPointerFlowEntitySummary(EdgeSet Edges);
+  friend llvm::iterator_range<EdgeSet::const_iterator>
+  getEdges(const PointerFlowEntitySummary &);
+
+  explicit PointerFlowEntitySummary(EdgeSet Edges) : Edges(std::move(Edges)) {}
+
+public:
+  static constexpr llvm::StringLiteral Name = "PointerFlow";
+
+  SummaryName getSummaryName() const override { return summaryName(); }
+
+  bool operator==(const EdgeSet &Other) const { return Edges == Other; }
+
+  bool operator==(const PointerFlowEntitySummary &Other) const {
+    return Edges == Other.Edges;
+  }
+
+  bool empty() const { return Edges.empty(); }
+
+  static SummaryName summaryName() { return SummaryName{Name.str()}; }
+};
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_POINTERFLOW_POINTERFLOW_H

diff  --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h
index ec0c4334a802e..972ff6a21bb25 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h
@@ -26,7 +26,7 @@ class UnsafeBufferUsageEntitySummary final : public EntitySummary {
   getUnsafeBuffers(const UnsafeBufferUsageEntitySummary &);
 
   explicit UnsafeBufferUsageEntitySummary(EntityPointerLevelSet UnsafeBuffers)
-      : EntitySummary(), UnsafeBuffers(std::move(UnsafeBuffers)) {}
+      : UnsafeBuffers(std::move(UnsafeBuffers)) {}
 
 public:
   static constexpr llvm::StringLiteral Name = "UnsafeBufferUsage";

diff  --git a/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h b/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
index 5616976e10f77..8419ad23619f7 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
@@ -45,6 +45,18 @@ extern volatile int UnsafeBufferUsageTUSummaryExtractorAnchorSource;
     UnsafeBufferUsageTUSummaryExtractorAnchorDestination =
         UnsafeBufferUsageTUSummaryExtractorAnchorSource;
 
+// This anchor is used to force the linker to link the PointerFlow
+// JSONFormat registration:
+extern volatile int PointerFlowSSAFJSONFormatAnchorSource;
+[[maybe_unused]] static int PointerFlowSSAFJSONFormatAnchorDestination =
+    PointerFlowSSAFJSONFormatAnchorSource;
+
+// This anchor is used to force the linker to link the PointerFlow
+// TUSummaryExtractor registration.
+extern volatile int PointerFlowTUSummaryExtractorAnchorSource;
+[[maybe_unused]] static int PointerFlowTUSummaryExtractorAnchorDestination =
+    PointerFlowTUSummaryExtractorAnchorSource;
+
 // This anchor is used to force the linker to link the CallGraphExtractor.
 extern volatile int CallGraphExtractorAnchorSource;
 [[maybe_unused]] static int CallGraphExtractorAnchorDestination =

diff  --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
index 88dcef0c5a2fa..6353b0918fe73 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
@@ -6,9 +6,14 @@ add_clang_library(clangScalableStaticAnalysisFrameworkAnalyses
   CallGraph/CallGraphExtractor.cpp
   CallGraph/CallGraphJSONFormat.cpp
   EntityPointerLevel/EntityPointerLevel.cpp
+  EntityPointerLevel/EntityPointerLevelFormat.cpp
+  PointerFlow/PointerFlow.cpp
+  PointerFlow/PointerFlowExtractor.cpp
+  PointerFlow/PointerFlowFormat.cpp
   SSAFAnalysesCommon.cpp
   UnsafeBufferUsage/UnsafeBufferUsage.cpp
   UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
+  UnsafeBufferUsage/UnsafeBufferUsageFormat.cpp
 
   LINK_LIBS
   clangAST

diff  --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
index 52e6705ec43af..58c2195e1cd0e 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
@@ -1,4 +1,4 @@
-//===- EntityPointerLevel.cpp ----------------------------------*- C++ -*-===//
+//===- EntityPointerLevel.cpp -----------------------------------*- C++ -*-===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -11,7 +11,6 @@
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/StmtVisitor.h"
-#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h"
 #include "clang/ScalableStaticAnalysisFramework/Core/ASTEntityMapping.h"
 #include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityName.h"
 #include <optional>
@@ -222,6 +221,12 @@ class EntityPointerLevelTranslator
     return makeEntityNameErr(Ctx, E->getMemberDecl());
   }
 
+  // Translate(`DefaultArg`) -> Translate(`DefaultArg->getExpr()`)
+  Expected<EntityPointerLevelSet>
+  VisitCXXDefaultArgExpr(const CXXDefaultArgExpr *E) {
+    return Visit(E->getExpr());
+  }
+
   Expected<EntityPointerLevelSet>
   VisitOpaqueValueExpr(const OpaqueValueExpr *S) {
     return Visit(S->getSourceExpr());
@@ -255,44 +260,3 @@ EntityPointerLevel clang::ssaf::buildEntityPointerLevel(EntityId Id,
                                                         unsigned PtrLv) {
   return EntityPointerLevel({Id, PtrLv});
 }
-
-// Writes an EntityPointerLevel as
-// Array [
-//   Object { "@" : [entity-id]},
-//   [pointer-level-integer]
-// ]
-llvm::json::Value clang::ssaf::entityPointerLevelToJSON(
-    const EntityPointerLevel &EPL, JSONFormat::EntityIdToJSONFn EntityId2JSON) {
-  return llvm::json::Array{EntityId2JSON(EPL.getEntity()),
-                           llvm::json::Value(EPL.getPointerLevel())};
-}
-
-Expected<EntityPointerLevel> clang::ssaf::entityPointerLevelFromJSON(
-    const llvm::json::Value &EPLData,
-    JSONFormat::EntityIdFromJSONFn EntityIdFromJSON) {
-  auto *AsArr = EPLData.getAsArray();
-
-  if (!AsArr || AsArr->size() != 2)
-    return makeSawButExpectedError(
-        EPLData, "an array with exactly two elements representing "
-                 "EntityId and PointerLevel, respectively");
-
-  auto *EntityIdObj = (*AsArr)[0].getAsObject();
-
-  if (!EntityIdObj)
-    return makeSawButExpectedError((*AsArr)[0],
-                                   "an object representing EntityId");
-
-  Expected<EntityId> Id = EntityIdFromJSON(*EntityIdObj);
-
-  if (!Id)
-    return Id.takeError();
-
-  std::optional<uint64_t> PtrLv = (*AsArr)[1].getAsInteger();
-
-  if (!PtrLv)
-    return makeSawButExpectedError((*AsArr)[1],
-                                   "an integer representing PointerLevel");
-
-  return buildEntityPointerLevel(*Id, *PtrLv);
-}

diff  --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.cpp
new file mode 100644
index 0000000000000..978124279d9a2
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.cpp
@@ -0,0 +1,58 @@
+//===- EntityPointerLevelFormat.cpp ---------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h"
+#include "SSAFAnalysesCommon.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
+
+using namespace clang;
+using namespace ssaf;
+
+// Writes an EntityPointerLevel as
+// Array [
+//   Object { "@" : [entity-id]},
+//   [pointer-level-integer]
+// ]
+llvm::json::Value clang::ssaf::entityPointerLevelToJSON(
+    const EntityPointerLevel &EPL, JSONFormat::EntityIdToJSONFn EntityId2JSON) {
+  return llvm::json::Array{EntityId2JSON(EPL.getEntity()),
+                           llvm::json::Value(EPL.getPointerLevel())};
+}
+
+llvm::Expected<EntityPointerLevel> clang::ssaf::entityPointerLevelFromJSON(
+    const llvm::json::Value &EPLData,
+    JSONFormat::EntityIdFromJSONFn EntityIdFromJSON) {
+  auto *AsArr = EPLData.getAsArray();
+
+  if (!AsArr || AsArr->size() != 2)
+    return makeSawButExpectedError(
+        EPLData, "an array with exactly two elements representing "
+                 "EntityId and PointerLevel, respectively");
+
+  auto *EntityIdObj = (*AsArr)[0].getAsObject();
+
+  if (!EntityIdObj)
+    return makeSawButExpectedError((*AsArr)[0],
+                                   "an object representing EntityId");
+
+  llvm::Expected<EntityId> Id = EntityIdFromJSON(*EntityIdObj);
+
+  if (!Id)
+    return Id.takeError();
+
+  std::optional<uint64_t> PtrLv = (*AsArr)[1].getAsInteger();
+
+  if (!PtrLv)
+    return makeSawButExpectedError((*AsArr)[1],
+                                   "an integer representing PointerLevel");
+
+  return buildEntityPointerLevel(*Id, *PtrLv);
+}

diff  --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.cpp
new file mode 100644
index 0000000000000..fe28d9ae4efce
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.cpp
@@ -0,0 +1,22 @@
+//===- PointerFlow.cpp ----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h"
+
+using namespace clang;
+using namespace ssaf;
+
+ssaf::PointerFlowEntitySummary
+ssaf::buildPointerFlowEntitySummary(EdgeSet Edges) {
+  return PointerFlowEntitySummary(std::move(Edges));
+}
+
+llvm::iterator_range<EdgeSet::const_iterator>
+ssaf::getEdges(const PointerFlowEntitySummary &Sum) {
+  return Sum.Edges;
+}

diff  --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowExtractor.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowExtractor.cpp
new file mode 100644
index 0000000000000..f2c088af7bf1b
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowExtractor.cpp
@@ -0,0 +1,364 @@
+//===- PointerFlowExtractor.cpp -------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "SSAFAnalysesCommon.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTTypeTraits.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/AST/Stmt.h"
+#include "clang/AST/TypeBase.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/ASTEntityMapping.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/ExtractorRegistry.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummaryBuilder.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummaryExtractor.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
+#include "llvm/ADT/Sequence.h"
+#include "llvm/Support/Error.h"
+#include <memory>
+#include <optional>
+
+namespace clang::ssaf {
+extern PointerFlowEntitySummary buildPointerFlowEntitySummary(EdgeSet Edges);
+} // namespace  clang::ssaf
+
+namespace {
+using namespace clang;
+using namespace ssaf;
+
+class PointerFlowMatcher {
+public:
+  EdgeSet Results;
+  ASTContext &Ctx;
+
+  PointerFlowMatcher(ASTContext &Ctx,
+                     std::function<EntityId(const EntityName &)> AddEntity)
+      : Ctx(Ctx), AddEntity(std::move(AddEntity)) {}
+
+  llvm::Error matches(const DynTypedNode &DynNode, const NamedDecl *RootDecl);
+
+  llvm::Error matchesInitializerList(const ValueDecl *Base,
+                                     const Expr *InitExpr,
+                                     unsigned ArrayElementIndirectLevel = 0);
+
+  llvm::Error matchesStmt(const Stmt *S, const NamedDecl *RootDecl);
+
+  llvm::Error matchesDecl(const Decl *D, const NamedDecl *RootDecl);
+
+private:
+  std::function<EntityId(const EntityName &)> AddEntity;
+
+  Expected<EntityPointerLevelSet> toEPL(const NamedDecl *N,
+                                        bool IsRet = false) const;
+
+  Expected<EntityPointerLevelSet> toEPL(const Expr *N) const;
+
+  llvm::Error addEdges(Expected<EntityPointerLevelSet> &&LHS,
+                       Expected<EntityPointerLevelSet> &&RHS);
+
+  template <typename ParmsProvider, typename ArgsProvider>
+  llvm::Error matchesArgsWithParams(unsigned ArgIdxStart, ParmsProvider *PP,
+                                    ArgsProvider *AP) {
+    unsigned ArgIdx = ArgIdxStart;
+
+    for (unsigned ParmIdx = 0;
+         ParmIdx < PP->getNumParams() && ArgIdx < AP->getNumArgs();
+         ++ArgIdx, ++ParmIdx) {
+      if (const ParmVarDecl *PD = PP->getParamDecl(ParmIdx);
+          PD && hasPtrOrArrType(PD)) {
+        if (auto Err = addEdges(toEPL(PD), toEPL(AP->getArg(ArgIdx))))
+          return Err;
+      }
+    }
+    return llvm::Error::success();
+  }
+};
+
+Expected<EntityPointerLevelSet> PointerFlowMatcher::toEPL(const NamedDecl *N,
+                                                          bool IsRet) const {
+  auto Ret = createEntityPointerLevel(N, AddEntity, IsRet);
+
+  if (Ret)
+    return EntityPointerLevelSet{*Ret};
+  return Ret.takeError();
+}
+
+Expected<EntityPointerLevelSet> PointerFlowMatcher::toEPL(const Expr *N) const {
+  return translateEntityPointerLevel(N, Ctx, AddEntity);
+}
+
+llvm::Error
+PointerFlowMatcher::addEdges(Expected<EntityPointerLevelSet> &&LHS,
+                             Expected<EntityPointerLevelSet> &&RHS) {
+  if (!LHS && !RHS)
+    return llvm::joinErrors(LHS.takeError(), RHS.takeError());
+  if (!LHS)
+    return LHS.takeError();
+  if (!RHS)
+    return RHS.takeError();
+  for (auto L : *LHS)
+    Results[L].insert(RHS->begin(), RHS->end());
+  return llvm::Error::success();
+}
+
+/// Match and extract pointer flow.
+/// The extraction function 'XF' can be described by the following rules:
+///
+/// XF(l = r)               := add edge "toEPL(l) -> toEPL(r))"
+/// XF(foo(a, b, ...))      := XF(Param_1 = a), XF(Param_2 = b), ...
+/// XF(return e;)           := XF(FunRet = e), where 'FunRet' is the return
+///                                            entity of the enclosing
+///                                            function
+/// XF(ctor(a, ...) : x1(y1), ... {...})
+///                         := XF(Param_1 = a), ...,
+///                            XF(x1 = y1), ...,
+///                            ctor's body will be visited separately.
+/// XF(T var = e)           := XF(var = e)
+/// XF(T var = init-list)   := see \ref
+///                            PointerFlowMatcher::matchInitializerList
+llvm::Error PointerFlowMatcher::matches(const DynTypedNode &DynNode,
+                                        const NamedDecl *RootDecl) {
+  if (const Stmt *S = DynNode.get<Stmt>())
+    return matchesStmt(S, RootDecl);
+  if (const Decl *D = DynNode.get<Decl>())
+    return matchesDecl(D, RootDecl);
+  return llvm::Error::success();
+}
+
+llvm::Error PointerFlowMatcher::matchesStmt(const Stmt *S,
+                                            const NamedDecl *RootDecl) {
+  // Match 'p = q' whenever it has pointer or array type:
+  if (const auto *BO = dyn_cast<BinaryOperator>(S);
+      BO && BO->getOpcode() == BO_Assign && hasPtrOrArrType(BO)) {
+    return addEdges(toEPL(BO->getLHS()), toEPL(BO->getRHS()));
+  }
+
+  // Match arg-to-param passing (in CallExpr) for any pointer type argument:
+  if (const auto *CE = dyn_cast<CallExpr>(S)) {
+    const FunctionDecl *FD = CE->getDirectCallee();
+
+    if (!FD)
+      return llvm::Error::success();
+
+    unsigned ArgIdx = 0;
+
+    if (isa<CXXOperatorCallExpr>(CE))
+      if (auto *MD = dyn_cast<CXXMethodDecl>(FD);
+          MD && !MD->isExplicitObjectMemberFunction())
+        ArgIdx = 1;
+    return matchesArgsWithParams(ArgIdx, FD, CE);
+  }
+  // Match arg-to-param passing (in CXXConstructExpr) for any pointer type
+  // argument:
+  if (const auto *CCE = dyn_cast<CXXConstructExpr>(S)) {
+    return matchesArgsWithParams(/*ArgIdxStart=*/0, CCE->getConstructor(), CCE);
+  }
+  if (const auto *RS = dyn_cast<ReturnStmt>(S)) {
+    const Expr *RetExpr = RS->getRetValue();
+    if (!RetExpr || !hasPtrOrArrType(RetExpr))
+      return llvm::Error::success();
+    return addEdges(toEPL(RootDecl, true), toEPL(RetExpr));
+  }
+  return llvm::Error::success();
+}
+
+llvm::Error PointerFlowMatcher::matchesDecl(const Decl *D,
+                                            const NamedDecl *RootDecl) {
+  const Expr *InitExpr = nullptr;
+
+  if (const auto *VD = dyn_cast<ValueDecl>(D)) {
+    if (const auto *Var = dyn_cast<VarDecl>(VD))
+      InitExpr = Var->getInit();
+    if (const auto *Fd = dyn_cast<FieldDecl>(VD))
+      InitExpr = Fd->getInClassInitializer();
+
+    // Match initializer-list:
+    if (auto *InitLst = dyn_cast_or_null<InitListExpr>(InitExpr))
+      return matchesInitializerList(VD, InitLst);
+
+    // Match initializers to variables/fields of a pointer type:
+    if (InitExpr && hasPtrOrArrType(VD))
+      return addEdges(toEPL(VD), toEPL(InitExpr));
+  }
+
+  // Match C++ constructor member-initializers:
+  if (const auto *CtorD = dyn_cast<CXXConstructorDecl>(D)) {
+    for (auto *E : CtorD->inits()) {
+      if (E->isDelegatingInitializer())
+        return matches(DynTypedNode::create(*E->getInit()), RootDecl);
+      if (const FieldDecl *FD = E->getMember(); FD && hasPtrOrArrType(FD)) {
+        if (auto Err = addEdges(toEPL(E->getMember()), toEPL(E->getInit())))
+          return Err;
+      }
+    }
+  }
+  return llvm::Error::success();
+}
+
+// Helper function for matchInitializerList that handles record:
+llvm::Error matchInitializerListForRecordDecl(PointerFlowMatcher &Matcher,
+                                              const RecordDecl *RecordTy,
+                                              const InitListExpr *ILE) {
+  if (auto *CXXRD = dyn_cast<CXXRecordDecl>(RecordTy))
+    if (CXXRD->getNumBases() != 0) {
+      // FIXME: support this:
+      return makeErrAtNode(
+          Matcher.Ctx, ILE,
+          "attempt to create pointer assignment edges between "
+          "CXXRecordDecls with base classes and initializer-lists");
+    }
+  // Handle union:
+  if (RecordTy->isUnion()) {
+    auto *InitField = ILE->getInitializedFieldInUnion();
+
+    if (!InitField)
+      return llvm::Error::success();
+    assert(!ILE->inits().empty());
+    return Matcher.matchesInitializerList(InitField, ILE->getInit(0));
+  }
+  // Handle struct/class:
+  ILE = ILE->isSemanticForm() ? ILE : ILE->getSemanticForm();
+
+  auto FieldIter = RecordTy->field_begin();
+
+  assert(RecordTy->getNumFields() >= ILE->getNumInits());
+  for (auto *Init : ILE->inits())
+    if (auto Err = Matcher.matchesInitializerList(*(FieldIter++), Init))
+      return Err;
+  return llvm::Error::success();
+}
+
+// Helper function for matchInitializerList that handles array:
+llvm::Error matchInitializerListForArray(PointerFlowMatcher &Matcher,
+                                         const ValueDecl *Array,
+                                         const InitListExpr *ILE,
+                                         unsigned ArrayIndirectLevel = 0) {
+  for (auto *E : ILE->inits())
+    if (auto Err =
+            Matcher.matchesInitializerList(Array, E, ArrayIndirectLevel + 1))
+      return Err;
+  return llvm::Error::success();
+}
+
+/// Match initializer lists of the form 'Var = {a, b, c, ...}':
+///
+///   If 'Var' is a struct/union:
+///     XF(Var = {a, b, c, ...})  :=   XF(Var.field_1 = a)
+///                                    XF(Var.field_2 = b)
+///                                    ...
+///   If 'Var' is an array:
+///     XF(Var = {a, b, c, ...})  :=   XF(*Var = a)
+///                                    XF(*Var = b)
+///                                    ...
+///
+/// The process is recursive: 'a', 'b', 'c', ...  may themselves be
+/// initializer lists.  We therefore use \p ArrayElementIndirectLevel to keep
+/// track of the pointer level the left-hand side.
+llvm::Error
+PointerFlowMatcher::matchesInitializerList(const ValueDecl *Base,
+                                           const Expr *InitExpr,
+                                           unsigned ArrayElementIndirectLevel) {
+  const InitListExpr *ILE = dyn_cast<InitListExpr>(InitExpr);
+
+  if (!ILE) {
+    if (!hasPtrOrArrType(InitExpr))
+      return llvm::Error::success();
+
+    auto BaseEPL = toEPL(Base);
+
+    if (!BaseEPL)
+      return BaseEPL.takeError();
+
+    // Apply ArrayElementIndirectLevel to BaseEPL
+    auto R = llvm::map_range(*BaseEPL, [&ArrayElementIndirectLevel](
+                                           const EntityPointerLevel &EPL) {
+      EntityPointerLevel Result = EPL;
+      for ([[maybe_unused]] auto Ignored : llvm::seq(ArrayElementIndirectLevel))
+        Result = incrementPointerLevel(Result);
+      return Result;
+    });
+    return addEdges(EntityPointerLevelSet{R.begin(), R.end()}, toEPL(InitExpr));
+  }
+  // Note that `Base`'s type is NOT the real LHS type when
+  // ArrayElementIndirectLevel > 0:
+  QualType Type = InitExpr->getType();
+
+  if (auto *RD = Type->getAsRecordDecl())
+    return matchInitializerListForRecordDecl(*this, RD, ILE);
+  if (Type->isArrayType())
+    return matchInitializerListForArray(*this, Base, ILE,
+                                        ArrayElementIndirectLevel);
+  // Must be the case of using a initializer-list for a scalar:
+  return matchesInitializerList(Base, ILE->getInit(0));
+}
+
+class PointerFlowTUSummaryExtractor : public TUSummaryExtractor {
+public:
+  PointerFlowTUSummaryExtractor(TUSummaryBuilder &Builder)
+      : TUSummaryExtractor(Builder) {}
+
+  EntityId addEntity(const EntityName &EN) {
+    return SummaryBuilder.addEntity(EN);
+  }
+
+  Expected<std::unique_ptr<PointerFlowEntitySummary>>
+  extractEntitySummary(const NamedDecl *Contributor, ASTContext &Ctx) {
+    PointerFlowMatcher Matcher(
+        Ctx, [this](const EntityName &EN) { return addEntity(EN); });
+    auto MatchAction = [&Matcher, &Contributor](const DynTypedNode &Node) {
+      auto Err = Matcher.matches(Node, Contributor);
+
+      if (Err)
+        llvm::report_fatal_error(std::move(Err));
+    };
+
+    findMatchesIn(Contributor, MatchAction);
+    return std::make_unique<PointerFlowEntitySummary>(
+        buildPointerFlowEntitySummary(std::move(Matcher.Results)));
+  }
+
+  void HandleTranslationUnit(ASTContext &Ctx) override {
+    std::vector<const NamedDecl *> Contributors;
+
+    findContributors(Ctx, Contributors);
+    for (auto *CD : Contributors) {
+      auto EntitySummary = extractEntitySummary(CD, Ctx);
+
+      if (!EntitySummary)
+        llvm::reportFatalInternalError(EntitySummary.takeError());
+      assert(*EntitySummary);
+      if ((*EntitySummary)->empty())
+        continue;
+
+      auto ContributorName = getEntityName(CD);
+
+      if (!ContributorName)
+        llvm::reportFatalInternalError(makeEntityNameErr(Ctx, CD));
+
+      auto [Ignored, InsertionSucceeded] = SummaryBuilder.addSummary(
+          addEntity(*ContributorName), std::move(*EntitySummary));
+
+      assert(InsertionSucceeded && "duplicated contributor extraction");
+    }
+  }
+};
+} // namespace
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int PointerFlowTUSummaryExtractorAnchorSource = 0;
+
+static TUSummaryExtractorRegistry::Add<PointerFlowTUSummaryExtractor>
+    RegisterExtractor(PointerFlowEntitySummary::Name,
+                      "The TUSummaryExtractor for pointer flow");

diff  --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowFormat.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowFormat.cpp
new file mode 100644
index 0000000000000..d35cfa626debf
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowFormat.cpp
@@ -0,0 +1,110 @@
+//===- PointerFlowFormat.cpp ----------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "SSAFAnalysesCommon.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/iterator_range.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
+
+using namespace clang;
+using namespace ssaf;
+using Object = llvm::json::Object;
+using Array = llvm::json::Array;
+using Value = llvm::json::Value;
+
+ssaf::PointerFlowEntitySummary
+ssaf::buildPointerFlowEntitySummary(EdgeSet Edges);
+
+llvm::iterator_range<EdgeSet::const_iterator>
+ssaf::getEdges(const PointerFlowEntitySummary &Sum);
+
+namespace {
+constexpr const char *const PointerFlowKey = "PointerFlow";
+} // namespace
+
+// Writes the 'Edges' map as an array of array of EntityPointerLevels:
+// Array [
+//    Array [ [src-node], [dest-node], [dest-node], ...]
+//    Array [ [src-node], [dest-node], [dest-node], ...]
+//    ...
+// ]
+static llvm::json::Object
+summaryToJSON(const EntitySummary &ES,
+              JSONFormat::EntityIdToJSONFn EntityId2JSON) {
+  Array EdgesData;
+
+  for (const auto &Entry :
+       getEdges(static_cast<const PointerFlowEntitySummary &>(ES))) {
+    Array EdgesEntryData;
+    EntityPointerLevel LHS = Entry.first;
+
+    EdgesEntryData.push_back(entityPointerLevelToJSON(LHS, EntityId2JSON));
+    // Add to nodes:
+    for (const auto &RHS : Entry.second)
+      EdgesEntryData.push_back(entityPointerLevelToJSON(RHS, EntityId2JSON));
+    EdgesData.push_back(Value(std::move(EdgesEntryData)));
+  }
+  return Object{{PointerFlowKey, Value(std::move(EdgesData))}};
+}
+
+static llvm::Expected<std::unique_ptr<EntitySummary>>
+summaryFromJSON(const Object &Data, EntityIdTable &,
+                JSONFormat::EntityIdFromJSONFn EntityIdFromJSON) {
+  const Value *EdgesData = Data.get(PointerFlowKey);
+
+  if (!EdgesData)
+    return makeSawButExpectedError(
+        Object(Data), "a JSON object with the key: %s", PointerFlowKey);
+
+  EdgeSet Edges;
+  const auto *EdgesDataAsArr = EdgesData->getAsArray();
+
+  if (!EdgesDataAsArr)
+    return makeSawButExpectedError(
+        *EdgesData, "a JSON array of array of EntityPointerLevels");
+  for (const auto &EdgesEntryData : *EdgesDataAsArr) {
+    const auto *EPLArray = EdgesEntryData.getAsArray();
+
+    if (!EPLArray || EPLArray->size() <= 1)
+      return makeSawButExpectedError(
+          EdgesEntryData, "a JSON array of EntityPointerLevels with a size "
+                          "greater than 1: [lhs, rhs, rhs, ...]");
+
+    auto SrcEPL = entityPointerLevelFromJSON((*EPLArray)[0], EntityIdFromJSON);
+
+    if (!SrcEPL)
+      return SrcEPL.takeError();
+    for (const auto &EPLData : llvm::drop_begin(*EPLArray)) {
+      auto EPL = entityPointerLevelFromJSON(EPLData, EntityIdFromJSON);
+      if (!EPL)
+        return EPL.takeError();
+      Edges[*SrcEPL].insert(*EPL);
+    }
+  }
+  return std::make_unique<PointerFlowEntitySummary>(
+      buildPointerFlowEntitySummary(std::move(Edges)));
+}
+
+namespace {
+struct PointerFlowJSONFormatInfo final : JSONFormat::FormatInfo {
+  PointerFlowJSONFormatInfo()
+      : JSONFormat::FormatInfo(PointerFlowEntitySummary::summaryName(),
+                               summaryToJSON, summaryFromJSON) {}
+};
+} // namespace
+
+static llvm::Registry<JSONFormat::FormatInfo>::Add<PointerFlowJSONFormatInfo>
+    RegisterPointerFlowJSONFormatInfo(
+        "PointerFlow", "JSON Format info for PointerFlowEntitySummary");
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int PointerFlowSSAFJSONFormatAnchorSource = 0;

diff  --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.cpp
index 98f12dec6f273..1c2f2f2a9379e 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.cpp
@@ -11,6 +11,7 @@
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclObjC.h"
 #include "clang/AST/DynamicRecursiveASTVisitor.h"
+#include "clang/AST/ExprCXX.h"
 #include <set>
 
 using namespace clang;
@@ -34,10 +35,18 @@ class ContributorFinder : public DynamicRecursiveASTVisitor {
   bool VisitVarDecl(VarDecl *D) override {
     DeclContext *DC = D->getDeclContext();
 
-    if (DC->isFileContext() || DC->isNamespace())
+    // Collects Decl for global variables or static data members:
+    if (DC->isFileContext() || D->isStaticDataMember())
       Contributors.insert(D);
     return true;
   }
+
+  bool VisitLambdaExpr(LambdaExpr *L) override {
+    // TraverseLambdaExpr directly visits the body stmt, skipping the
+    // CXXMethodDecl, which is a contributor that needs to be collected.
+    VisitFunctionDecl(L->getCallOperator());
+    return true;
+  }
 };
 
 /// An AST visitor that skips the root node's strict-descendants that are

diff  --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.cpp
index ea5d2297b9836..86545762c0121 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.cpp
@@ -7,21 +7,9 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h"
-#include "SSAFAnalysesCommon.h"
-#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
-#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h"
-#include "clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
-#include "llvm/Support/Error.h"
-#include "llvm/Support/JSON.h"
-#include <cstdint>
 
 using namespace clang;
 using namespace ssaf;
-using Array = llvm::json::Array;
-using Object = llvm::json::Object;
-
-static constexpr llvm::StringLiteral SummarySerializationKey = "UnsafeBuffers";
 
 UnsafeBufferUsageEntitySummary
 ssaf::buildUnsafeBufferUsageEntitySummary(EntityPointerLevelSet UnsafeBuffers) {
@@ -32,79 +20,3 @@ llvm::iterator_range<EntityPointerLevelSet::const_iterator>
 ssaf::getUnsafeBuffers(const UnsafeBufferUsageEntitySummary &S) {
   return llvm::make_range(S.UnsafeBuffers.begin(), S.UnsafeBuffers.end());
 }
-
-static Object serialize(const EntitySummary &S,
-                        JSONFormat::EntityIdToJSONFn Fn) {
-  const auto &SS = static_cast<const UnsafeBufferUsageEntitySummary &>(S);
-  Array UnsafeBuffersData;
-
-  for (const auto &EPL : getUnsafeBuffers(SS))
-    UnsafeBuffersData.push_back(entityPointerLevelToJSON(EPL, Fn));
-  return Object{{SummarySerializationKey.data(), std::move(UnsafeBuffersData)}};
-}
-
-static llvm::Expected<std::unique_ptr<EntitySummary>>
-deserializeImpl(const Object &Data, JSONFormat::EntityIdFromJSONFn Fn) {
-  const Array *UnsafeBuffersData =
-      Data.getArray(SummarySerializationKey.data());
-
-  if (!UnsafeBuffersData)
-    return makeSawButExpectedError(Object(Data), "an Object with a key %s",
-                                   SummarySerializationKey.data());
-
-  EntityPointerLevelSet EPLs;
-
-  for (const auto &EltData : *UnsafeBuffersData) {
-    llvm::Expected<EntityPointerLevel> EPL =
-        entityPointerLevelFromJSON(EltData, Fn);
-
-    if (!EPL)
-      return EPL.takeError();
-    EPLs.insert(*EPL);
-  }
-  return std::make_unique<UnsafeBufferUsageEntitySummary>(
-      buildUnsafeBufferUsageEntitySummary(std::move(EPLs)));
-}
-
-static llvm::Expected<std::unique_ptr<EntitySummary>>
-deserialize(const Object &Data, EntityIdTable &,
-            JSONFormat::EntityIdFromJSONFn Fn) {
-  return deserializeImpl(Data, Fn);
-}
-
-struct UnsafeBufferUsageJSONFormatInfo : JSONFormat::FormatInfo {
-  UnsafeBufferUsageJSONFormatInfo()
-      : JSONFormat::FormatInfo(UnsafeBufferUsageEntitySummary::summaryName(),
-                               serialize, deserialize) {}
-};
-
-static llvm::Registry<JSONFormat::FormatInfo>::Add<
-    UnsafeBufferUsageJSONFormatInfo>
-    RegisterUnsafeBufferUsageJSONFormatInfo(
-        UnsafeBufferUsageEntitySummary::Name,
-        "JSON Format info for UnsafeBufferUsageEntitySummary");
-
-// NOLINTNEXTLINE(misc-use-internal-linkage)
-volatile int UnsafeBufferUsageSSAFJSONFormatAnchorSource = 0;
-
-// For unit test:
-llvm::Expected<std::unique_ptr<EntitySummary>>
-ssaf::serializeDeserializeRoundTrip(
-    const UnsafeBufferUsageEntitySummary &S,
-    std::function<uint64_t(EntityId)> IdToIntFn,
-    std::function<llvm::Expected<EntityId>(uint64_t)> IdFromIntFn) {
-
-  auto IdToJson = [&IdToIntFn](EntityId Id) -> Object {
-    return Object({{"@", IdToIntFn(Id)}});
-  };
-  auto IdFromJson =
-      [&IdFromIntFn](const Object &O) -> llvm::Expected<EntityId> {
-    const auto *Int = O.get("@");
-
-    if (Int && Int->getAsUINT64())
-      return IdFromIntFn(*Int->getAsUINT64());
-    return llvm::createStringError("failed to get EntityId from Object");
-  };
-
-  return deserializeImpl(serialize(S, IdToJson), IdFromJson);
-}

diff  --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
index 550416c353fb7..075e4896deed3 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
@@ -103,6 +103,6 @@ void clang::ssaf::UnsafeBufferUsageTUSummaryExtractor::HandleTranslationUnit(
 volatile int UnsafeBufferUsageTUSummaryExtractorAnchorSource = 0;
 
 static clang::ssaf::TUSummaryExtractorRegistry::Add<
-    ssaf::UnsafeBufferUsageTUSummaryExtractor>
+    UnsafeBufferUsageTUSummaryExtractor>
     RegisterExtractor(UnsafeBufferUsageEntitySummary::Name,
                       "The TUSummaryExtractor for unsafe buffer pointers");

diff  --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageFormat.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageFormat.cpp
new file mode 100644
index 0000000000000..ce1ef0b3ba691
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageFormat.cpp
@@ -0,0 +1,108 @@
+//===- UnsafeBufferUsageFormat.cpp ----------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "SSAFAnalysesCommon.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
+#include <cstdint>
+
+using namespace clang;
+using namespace ssaf;
+using Array = llvm::json::Array;
+using Object = llvm::json::Object;
+
+static constexpr llvm::StringLiteral SummarySerializationKey = "UnsafeBuffers";
+
+extern UnsafeBufferUsageEntitySummary
+ssaf::buildUnsafeBufferUsageEntitySummary(EntityPointerLevelSet UnsafeBuffers);
+
+extern llvm::iterator_range<EntityPointerLevelSet::const_iterator>
+ssaf::getUnsafeBuffers(const UnsafeBufferUsageEntitySummary &S);
+
+static Object serialize(const EntitySummary &S,
+                        JSONFormat::EntityIdToJSONFn Fn) {
+  const auto &SS = static_cast<const UnsafeBufferUsageEntitySummary &>(S);
+  Array UnsafeBuffersData;
+
+  for (const auto &EPL : getUnsafeBuffers(SS))
+    UnsafeBuffersData.push_back(entityPointerLevelToJSON(EPL, Fn));
+  return Object{{SummarySerializationKey.data(), std::move(UnsafeBuffersData)}};
+}
+
+static llvm::Expected<std::unique_ptr<EntitySummary>>
+deserializeImpl(const Object &Data, JSONFormat::EntityIdFromJSONFn Fn) {
+  const Array *UnsafeBuffersData =
+      Data.getArray(SummarySerializationKey.data());
+
+  if (!UnsafeBuffersData)
+    return makeSawButExpectedError(Object(Data), "an Object with a key %s",
+                                   SummarySerializationKey.data());
+
+  EntityPointerLevelSet EPLs;
+
+  for (const auto &EltData : *UnsafeBuffersData) {
+    llvm::Expected<EntityPointerLevel> EPL =
+        entityPointerLevelFromJSON(EltData, Fn);
+
+    if (!EPL)
+      return EPL.takeError();
+    EPLs.insert(*EPL);
+  }
+  return std::make_unique<UnsafeBufferUsageEntitySummary>(
+      buildUnsafeBufferUsageEntitySummary(std::move(EPLs)));
+}
+
+static llvm::Expected<std::unique_ptr<EntitySummary>>
+deserialize(const Object &Data, EntityIdTable &,
+            JSONFormat::EntityIdFromJSONFn Fn) {
+  return deserializeImpl(Data, Fn);
+}
+
+namespace {
+struct UnsafeBufferUsageJSONFormatInfo final : JSONFormat::FormatInfo {
+  UnsafeBufferUsageJSONFormatInfo()
+      : JSONFormat::FormatInfo(UnsafeBufferUsageEntitySummary::summaryName(),
+                               serialize, deserialize) {}
+};
+} // namespace
+
+static llvm::Registry<JSONFormat::FormatInfo>::Add<
+    UnsafeBufferUsageJSONFormatInfo>
+    RegisterUnsafeBufferUsageJSONFormatInfo(
+        UnsafeBufferUsageEntitySummary::Name,
+        "JSON Format info for UnsafeBufferUsageEntitySummary");
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int UnsafeBufferUsageSSAFJSONFormatAnchorSource = 0;
+
+// For unit test:
+llvm::Expected<std::unique_ptr<EntitySummary>>
+ssaf::serializeDeserializeRoundTrip(
+    const UnsafeBufferUsageEntitySummary &S,
+    std::function<uint64_t(EntityId)> IdToIntFn,
+    std::function<llvm::Expected<EntityId>(uint64_t)> IdFromIntFn) {
+
+  auto IdToJson = [&IdToIntFn](EntityId Id) -> Object {
+    return Object({{"@", IdToIntFn(Id)}});
+  };
+  auto IdFromJson =
+      [&IdFromIntFn](const Object &O) -> llvm::Expected<EntityId> {
+    const auto *Int = O.get("@");
+
+    if (Int && Int->getAsUINT64())
+      return IdFromIntFn(*Int->getAsUINT64());
+    return llvm::createStringError("failed to get EntityId from Object");
+  };
+
+  return deserializeImpl(serialize(S, IdToJson), IdFromJson);
+}

diff  --git a/clang/lib/ScalableStaticAnalysisFramework/Tool/Utils.cpp b/clang/lib/ScalableStaticAnalysisFramework/Tool/Utils.cpp
index e8dde39a846e9..6a78620b757e6 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Tool/Utils.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Tool/Utils.cpp
@@ -1,4 +1,4 @@
-//===- Utils.cpp - Shared utilities for SSAF tools -----------------------===//
+//===- Utils.cpp - Shared utilities for SSAF tools ------------------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.

diff  --git a/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-array.json b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-array.json
new file mode 100644
index 0000000000000..3848bd68ac0fb
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-array.json
@@ -0,0 +1,88 @@
+{
+  "data": [
+    {
+      "summary_data": [
+        {
+          "entity_id": 2,
+          "entity_summary": {
+            "PointerFlow": [
+              [
+                [
+                  {
+                    "@": 0
+                  },
+                  2
+                ]
+              ],
+              [
+                [
+                  {
+                    "@": 2
+                  },
+                  1
+                ],
+                [
+                  {
+                    "@": 0
+                  },
+                  1
+                ]
+              ]
+            ]
+          }
+        }
+      ],
+      "summary_name": "PointerFlow"
+    }
+  ],
+  "id_table": [
+    {
+      "id": 2,
+      "name": {
+        "namespace": [],
+        "suffix": "",
+        "usr": "c:@F at foo#***I#*S0_#I#"
+      }
+    },
+    {
+      "id": 0,
+      "name": {
+        "namespace": [],
+        "suffix": "1",
+        "usr": "c:@F at foo#***I#*S0_#I#"
+      }
+    },
+    {
+      "id": 1,
+      "name": {
+        "namespace": [],
+        "suffix": "2",
+        "usr": "c:@F at foo#***I#*S0_#I#"
+      }
+    }
+  ],
+  "linkage_table": [
+    {
+      "id": 0,
+      "linkage": {
+        "type": "External"
+      }
+    },
+    {
+      "id": 1,
+      "linkage": {
+        "type": "Internal"
+      }
+    },
+    {
+      "id": 2,
+      "linkage": {
+        "type": "Internal"
+      }
+    }
+  ],
+  "tu_namespace": {
+    "kind": "CompilationUnit",
+    "name": "Mock.cpp"
+  }
+}

diff  --git a/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-summary.json b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-summary.json
new file mode 100644
index 0000000000000..77c02313b6add
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-summary.json
@@ -0,0 +1,65 @@
+{
+  "data": [
+    {
+      "summary_data": [
+        {
+          "entity_id": 2,
+          "entity_summary": {
+            "PointerFlow": {}
+          }
+        }
+      ],
+      "summary_name": "PointerFlow"
+    }
+  ],
+  "id_table": [
+    {
+      "id": 2,
+      "name": {
+        "namespace": [],
+        "suffix": "",
+        "usr": "c:@F at foo#***I#*S0_#I#"
+      }
+    },
+    {
+      "id": 0,
+      "name": {
+        "namespace": [],
+        "suffix": "1",
+        "usr": "c:@F at foo#***I#*S0_#I#"
+      }
+    },
+    {
+      "id": 1,
+      "name": {
+        "namespace": [],
+        "suffix": "2",
+        "usr": "c:@F at foo#***I#*S0_#I#"
+      }
+    }
+  ],
+  "linkage_table": [
+    {
+      "id": 0,
+      "linkage": {
+        "type": "External"
+      }
+    },
+    {
+      "id": 1,
+      "linkage": {
+        "type": "Internal"
+      }
+    },
+    {
+      "id": 2,
+      "linkage": {
+        "type": "Internal"
+      }
+    }
+  ],
+  "tu_namespace": {
+    "kind": "CompilationUnit",
+    "name": "Mock.cpp"
+  }
+}

diff  --git a/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-no-key.json b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-no-key.json
new file mode 100644
index 0000000000000..e950edbfa1f3c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-no-key.json
@@ -0,0 +1,94 @@
+{
+  "data": [
+    {
+      "summary_data": [
+        {
+          "entity_id": 2,
+          "entity_summary": {
+            "NoPointerFlow": [
+              [
+                [
+                  {
+                    "@": 0
+                  },
+                  2
+                ],
+                [
+                  {
+                    "@": 1
+                  },
+                  1
+                ]
+              ],
+              [
+                [
+                  {
+                    "@": 2
+                  },
+                  1
+                ],
+                [
+                  {
+                    "@": 0
+                  },
+                  1
+                ]
+              ]
+            ]
+          }
+        }
+      ],
+      "summary_name": "PointerFlow"
+    }
+  ],
+  "id_table": [
+    {
+      "id": 2,
+      "name": {
+        "namespace": [],
+        "suffix": "",
+        "usr": "c:@F at foo#***I#*S0_#I#"
+      }
+    },
+    {
+      "id": 0,
+      "name": {
+        "namespace": [],
+        "suffix": "1",
+        "usr": "c:@F at foo#***I#*S0_#I#"
+      }
+    },
+    {
+      "id": 1,
+      "name": {
+        "namespace": [],
+        "suffix": "2",
+        "usr": "c:@F at foo#***I#*S0_#I#"
+      }
+    }
+  ],
+  "linkage_table": [
+    {
+      "id": 0,
+      "linkage": {
+        "type": "External"
+      }
+    },
+    {
+      "id": 1,
+      "linkage": {
+        "type": "Internal"
+      }
+    },
+    {
+      "id": 2,
+      "linkage": {
+        "type": "Internal"
+      }
+    }
+  ],
+  "tu_namespace": {
+    "kind": "CompilationUnit",
+    "name": "Mock.cpp"
+  }
+}

diff  --git a/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary.json b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary.json
new file mode 100644
index 0000000000000..4347b5851c8ff
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary.json
@@ -0,0 +1,91 @@
+{
+  "data": [
+    {
+      "summary_data": [
+        {
+          "entity_id": 2,
+          "entity_summary": {
+            "PointerFlow": [
+              [
+                [
+                  {
+                    "@": 0
+                  },
+                  2
+                ],
+                [
+                  {
+                    "@": 1
+                  },
+                  1
+                ]
+              ],
+              [
+                [
+                  {
+                    "@": 2
+                  },
+                  1
+                ],
+                [
+                  {
+                    "@": 0
+                  },
+                  1
+                ]
+              ]
+            ]
+          }
+        }
+      ],
+      "summary_name": "PointerFlow"
+    }
+  ],
+  "id_table": [
+    {
+      "id": 2,
+      "name": {
+        "suffix": "",
+        "usr": "c:@F at foo#***I#*S0_#I#"
+      }
+    },
+    {
+      "id": 0,
+      "name": {
+        "suffix": "1",
+        "usr": "c:@F at foo#***I#*S0_#I#"
+      }
+    },
+    {
+      "id": 1,
+      "name": {
+        "suffix": "2",
+        "usr": "c:@F at foo#***I#*S0_#I#"
+      }
+    }
+  ],
+  "linkage_table": [
+    {
+      "id": 0,
+      "linkage": {
+        "type": "External"
+      }
+    },
+    {
+      "id": 1,
+      "linkage": {
+        "type": "Internal"
+      }
+    },
+    {
+      "id": 2,
+      "linkage": {
+        "type": "Internal"
+      }
+    }
+  ],
+  "tu_namespace": {
+    "kind": "CompilationUnit",
+    "name": "Mock.cpp"
+  }
+}

diff  --git a/clang/test/Analysis/Scalable/PointerFlow/tu-summary-serialization.test b/clang/test/Analysis/Scalable/PointerFlow/tu-summary-serialization.test
new file mode 100644
index 0000000000000..e2cec8cb3f350
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/tu-summary-serialization.test
@@ -0,0 +1,27 @@
+// RUN: rm -rf %t.json
+// RUN: clang-ssaf-format -type=tu  %S/Inputs/tu-summary.json -o %t.json
+// RUN: 
diff  %S/Inputs/tu-summary.json %t.json
+
+// Negative tests:
+
+// RUN: not clang-ssaf-format -type=tu %S/Inputs/tu-summary-no-key.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=CHECK-MISSING-KEY
+// CHECK-MISSING-KEY: saw {
+// CHECK-MISSING-KEY:   "NoPointerFlow": [
+// CHECK-MISSING-KEY:     [
+// CHECK-MISSING-KEY: } but expected a JSON object with the key: PointerFlow
+
+// RUN: not clang-ssaf-format -type=tu %S/Inputs/tu-summary-bad-summary.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=CHECK-BAD-SUMMARY
+// CHECK-BAD-SUMMARY: saw {} but expected a JSON array of array of EntityPointerLevels
+
+// RUN: not clang-ssaf-format -type=tu %S/Inputs/tu-summary-bad-array.json 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=CHECK-BAD-ARRAY
+// CHECK-BAD-ARRAY: saw [
+// CHECK-BAD-ARRAY:  [
+// CHECK-BAD-ARRAY:    {
+// CHECK-BAD-ARRAY:      "@": 0
+// CHECK-BAD-ARRAY:    },
+// CHECK-BAD-ARRAY:    2
+// CHECK-BAD-ARRAY:  ]
+// CHECK-BAD-ARRAY:] but expected a JSON array of EntityPointerLevels with a size greater than 1: [lhs, rhs, rhs, ...]

diff  --git a/clang/test/Analysis/Scalable/ssaf-format/list.test b/clang/test/Analysis/Scalable/ssaf-format/list.test
index 82811a97c40c5..34181845ba3b5 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/list.test
+++ b/clang/test/Analysis/Scalable/ssaf-format/list.test
@@ -9,3 +9,4 @@
 // CHECK-DAG:     Analyses:
 // CHECK-DAG:         [[NthFormat]].{{[0-9]+}}. CallGraph - JSON Format info for CallGraph summary
 // CHECK-DAG:         [[NthFormat]].{{[0-9]+}}. UnsafeBufferUsage - JSON Format info for UnsafeBufferUsageEntitySummary
+// CHECK-DAG:         [[NthFormat]].{{[0-9]+}}. PointerFlow - JSON Format info for PointerFlowEntitySummary

diff  --git a/clang/unittests/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowTest.cpp
new file mode 100644
index 0000000000000..2d6a958ce4eac
--- /dev/null
+++ b/clang/unittests/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowTest.cpp
@@ -0,0 +1,1131 @@
+//===- PointerFlowTest.cpp ------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h"
+#include "TestFixture.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DynamicRecursiveASTVisitor.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/Frontend/ASTUnit.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/ASTEntityMapping.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/ExtractorRegistry.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummary.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummaryBuilder.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/Support/Casting.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <memory>
+#include <type_traits>
+#include <variant>
+
+using namespace clang;
+using namespace ssaf;
+
+namespace {
+// Use FindEntityByName to identify entities in unit tests.
+// Unit tests are simple enough to meet the following assumptions:
+// - Named declarations should have unique names, they can be found by comparing
+//   names with strings;
+// - Lambdas should initialize a variable named "X", they can be found using
+//   "LambdaOfVar("X")";
+// - CXX Ctors should have unique combination of names and number of parameters,
+//   they can be found using "CXXCtorOfNumParms(name, numParms)".
+struct LambdaOfVar {
+  StringRef VarName;
+};
+
+struct CXXCtorOfNumParms {
+  StringRef CXXCtorName;
+  unsigned NumParms;
+};
+
+using FindEntityByName =
+    std::variant<StringRef, CXXCtorOfNumParms, LambdaOfVar>;
+
+template <typename... Ts> struct Overloaded : Ts... {
+  using Ts::operator()...;
+};
+template <typename... Ts> Overloaded(Ts...) -> Overloaded<Ts...>;
+
+StringRef toStringRef(const FindEntityByName &N) {
+  return std::visit(
+      Overloaded{
+          [](StringRef S) -> StringRef { return S; },
+          [](const CXXCtorOfNumParms &L) -> StringRef { return L.CXXCtorName; },
+          [](const LambdaOfVar &L) -> StringRef { return L.VarName; },
+      },
+      N);
+}
+
+const NamedDecl *matchNamedDeclByFindEntityByName(const FindEntityByName &N,
+                                                  const NamedDecl *D) {
+  return std::visit(
+      Overloaded{
+          [&D](StringRef S) -> const NamedDecl * {
+            if (D->getNameAsString() == S)
+              return D;
+            return nullptr;
+          },
+          [&D](const CXXCtorOfNumParms &L) -> const NamedDecl * {
+            if (auto *CD = dyn_cast<CXXConstructorDecl>(D)) {
+              if (CD->getNameAsString() == L.CXXCtorName &&
+                  CD->getNumParams() == L.NumParms)
+                return D;
+            }
+            return nullptr;
+          },
+          [&D](const LambdaOfVar &L) -> const NamedDecl * {
+            if (const auto *VD = dyn_cast<VarDecl>(D); VD && VD->getInit()) {
+              const Expr *Init = VD->getInit()->IgnoreUnlessSpelledInSource();
+              if (isa<LambdaExpr>(Init) && VD->getNameAsString() == L.VarName)
+                return cast<LambdaExpr>(Init)->getCallOperator();
+            }
+            return nullptr;
+          },
+      },
+      N);
+}
+
+template <typename SomeDecl = NamedDecl,
+          typename = std::enable_if_t<std::is_base_of_v<NamedDecl, SomeDecl>>>
+const SomeDecl *findEntityByName(FindEntityByName Name, ASTContext &Ctx) {
+  class NamedDeclFinder : public DynamicRecursiveASTVisitor {
+  public:
+    FindEntityByName SearchingName;
+    const SomeDecl *FoundDecl = nullptr;
+
+    NamedDeclFinder(FindEntityByName SearchingName)
+        : SearchingName(SearchingName) {}
+
+    bool VisitDecl(Decl *D) override {
+      if (auto *ND = dyn_cast<NamedDecl>(D)) {
+        FoundDecl = llvm::dyn_cast_or_null<SomeDecl>(
+            matchNamedDeclByFindEntityByName(SearchingName, ND));
+        if (FoundDecl)
+          return false;
+      }
+      return true;
+    }
+  };
+
+  NamedDeclFinder Finder(Name);
+
+  Finder.TraverseDecl(Ctx.getTranslationUnitDecl());
+  return dyn_cast_or_null<SomeDecl>(Finder.FoundDecl);
+}
+
+const FunctionDecl *findFnByName(FindEntityByName Name, ASTContext &Ctx) {
+  return findEntityByName<FunctionDecl>(Name, Ctx);
+}
+
+// Same as `std::pair<StringName, unsigned>` for a pair of entity declaration
+// name and a pointer level with an extra optional flag for whether the entity
+// represents a function return value. This structure is used to explicitly
+// spell out components of an EPL such as "{"p", 1}" or "{"foo_fn", 2, true}".
+struct EPLPair {
+  EPLPair(FindEntityByName Name, unsigned Lv, bool isFunRet = false)
+      : Name(Name), Lv(Lv), isFunRet(isFunRet) {}
+
+  FindEntityByName Name;
+  unsigned Lv;
+  bool isFunRet;
+};
+
+class PointerFlowTest : public TestFixture {
+protected:
+  TUSummary TUSum;
+  TUSummaryBuilder Builder;
+  std::unique_ptr<TUSummaryExtractor> Extractor;
+  std::unique_ptr<ASTUnit> AST;
+
+  PointerFlowTest()
+      : TUSum(BuildNamespace(BuildNamespaceKind::CompilationUnit, "Mock.cpp")),
+        Builder(TUSum), Extractor(nullptr) {}
+
+  template <typename ContributorDecl = NamedDecl,
+            typename =
+                std::enable_if_t<std::is_base_of_v<NamedDecl, ContributorDecl>>>
+  bool setUpTest(StringRef Code) {
+    AST = tooling::buildASTFromCodeWithArgs(
+        Code, {"-Wno-unused-value", "-Wno-int-to-pointer-cast"});
+
+    for (auto &E : clang::ssaf::TUSummaryExtractorRegistry::entries()) {
+      if (E.getName() == PointerFlowEntitySummary::Name) {
+        Extractor = E.instantiate(Builder);
+        break;
+      }
+    }
+
+    if (!Extractor) {
+      ADD_FAILURE() << "failed to find PointerFlowTUSummaryExtractor";
+      return false;
+    }
+    Extractor->HandleTranslationUnit(AST->getASTContext());
+    return true;
+  }
+
+  template <typename ContributorDecl = NamedDecl>
+  const PointerFlowEntitySummary *getEntitySummary(FindEntityByName Name) {
+    const auto *ContributorDefn =
+        findEntityByName<ContributorDecl>(Name, AST->getASTContext());
+
+    if (!ContributorDefn) {
+      ADD_FAILURE() << "failed to find Decl of \"" << toStringRef(Name) << "\"";
+      return nullptr;
+    }
+
+    std::optional<EntityName> EN = getEntityName(ContributorDefn);
+
+    if (!EN) {
+      ADD_FAILURE() << "failed to get EntityName for contributor \""
+                    << toStringRef(Name) << "\"";
+      return nullptr;
+    }
+
+    EntityId ContributorEntityId = Builder.addEntity(*EN);
+    auto &TUSumData = getData(TUSum);
+    auto EntitiesSumIter =
+        TUSumData.find(PointerFlowEntitySummary::summaryName());
+
+    // If none entity summary was collected, it may not be an entry in
+    // `TUSumData`:
+    if (EntitiesSumIter == TUSumData.end())
+      return nullptr;
+
+    auto EntitySumIter = EntitiesSumIter->second.find(ContributorEntityId);
+
+    // If entity summary is empty, it may not exist:
+    if (EntitySumIter == EntitiesSumIter->second.end())
+      return nullptr;
+    return static_cast<const PointerFlowEntitySummary *>(
+        EntitySumIter->second.get());
+  }
+
+public:
+  std::optional<EntityId> getEntityId(FindEntityByName Name) {
+    if (const auto *D = findEntityByName(Name, AST->getASTContext())) {
+      if (auto EntityName = getEntityName(D))
+        return Builder.addEntity(*EntityName);
+    }
+    return std::nullopt;
+  }
+
+  std::optional<EntityId> getEntityIdForReturn(FindEntityByName FunName) {
+    if (const auto *D = findFnByName(FunName, AST->getASTContext())) {
+      if (auto EntityName = getEntityNameForReturn(D))
+        return Builder.addEntity(*EntityName);
+    }
+    return std::nullopt;
+  }
+
+  EdgeSet makeEdges(unsigned Line, ArrayRef<std::pair<EPLPair, EPLPair>> Edges);
+};
+
+// 'ToEPL(Test, Line)' is a lambda that converts a 'EPLPair' to a
+// 'EntityPointerLevel':
+static constexpr auto ToEPL =
+    [](PointerFlowTest *Test,
+       unsigned Line) -> std::function<EntityPointerLevel(const EPLPair &)> {
+  return [Test, Line](const EPLPair &Pair) -> EntityPointerLevel {
+    std::optional<EntityId> Entity = Pair.isFunRet
+                                         ? Test->getEntityIdForReturn(Pair.Name)
+                                         : Test->getEntityId(Pair.Name);
+    if (!Entity) {
+      ADD_FAILURE_AT(__FILE__, Line)
+          << "Entity not found: " << toStringRef(Pair.Name);
+    }
+    return buildEntityPointerLevel(*Entity, Pair.Lv);
+  };
+};
+
+EdgeSet
+PointerFlowTest::makeEdges(unsigned Line,
+                           ArrayRef<std::pair<EPLPair, EPLPair>> Edges) {
+  EdgeSet Result;
+  for (auto Edge : Edges)
+    Result[ToEPL(this, Line)(Edge.first)].insert(
+        ToEPL(this, Line)(Edge.second));
+  return Result;
+}
+
+TEST_F(PointerFlowTest, IsExtractorRegisteredTest) {
+  EXPECT_TRUE(isTUSummaryExtractorRegistered("PointerFlow"));
+}
+
+//////////////////////////////////////////////////////////////
+//          Simple Assign Tests                             //
+//////////////////////////////////////////////////////////////
+TEST_F(PointerFlowTest, SimpleAssign) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    void foo(int *p, int *q) {
+      q = p;
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, AssignWithSubscriptLHS) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    void foo(int **q, int *p, int x) {
+      q[x] = p;
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 2U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, AssignWithPtrArithRHS) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    void foo(int *p, int *q) {
+      q = p + 5;
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, AssignInSubscript) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    void foo(int *p, int *q) {
+      (q = p)[5];
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, MultipleAssign) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    void foo(int *p, int *q, int *r) {
+      q = p;
+      r = q;
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"q", 1U}, {"p", 1U}},
+                                          {{"r", 1U}, {"q", 1U}},
+                                      }));
+}
+
+TEST_F(PointerFlowTest, ChainedAssign) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    void foo(int *p, int *q, int *r) {
+      r = q = p;
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"q", 1U}, {"p", 1U}},
+                                          {{"r", 1U}, {"q", 1U}},
+                                      }));
+}
+
+TEST_F(PointerFlowTest, CastToRValue) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    void foo(int *p, int *q) {
+      q = static_cast<int *&&>(p);
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, AssignToMember) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    struct S { int *field; };
+    void foo(S s, int *p) {
+      s.field = p;
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"field", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, AssignToMember2) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    struct S { int *field; };
+    void foo(S *s, int *p) {
+      s->field = p;
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"field", 1U}, {"p", 1U}}}));
+}
+
+//////////////////////////////////////////////////////////////
+//          Call Expr Tests.                                //
+//////////////////////////////////////////////////////////////
+TEST_F(PointerFlowTest, CallArg) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    void bar(int *param);
+    void foo(int *p) {
+      bar(p);
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"param", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, CallMultiArgs) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    void bar(int *param1, int y, int *param2);
+    void foo(int *p, int x, int *q) {
+      bar(p, x, q);
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"param1", 1U}, {"p", 1U}},
+                                          {{"param2", 1U}, {"q", 1U}},
+                                      }));
+}
+
+TEST_F(PointerFlowTest, CallAsCallArg) {
+  ASSERT_EQ(setUpTest(R"cpp(
+
+    int *bar(int * w);
+    void foo(int * p) {
+      foo(bar(p));
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"w", 1U}, {"p", 1U}},
+                                       {{"p", 1U}, {"bar", 1U, true}}}));
+}
+
+TEST_F(PointerFlowTest, CXXOperatorCallMultiArgs) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    struct S {
+      int* operator()(int *a, int *b);
+    };
+    void foo(S obj, int *p, int *q) {
+      foo(obj, obj(p, q), obj(p, q));
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"a", 1U}, {"p", 1U}},
+                                          {{"b", 1U}, {"q", 1U}},
+                                          {{"p", 1U}, {"operator()", 1U, true}},
+                                          {{"q", 1U}, {"operator()", 1U, true}},
+                                      }));
+}
+
+TEST_F(PointerFlowTest, CXXMemberCall) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    struct S {
+      int* method(int *a, int *b);
+    };
+    void foo(S obj, int *p, int *q) {
+      foo(obj, obj.method(p, q), obj.method(p, q));
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"a", 1U}, {"p", 1U}},
+                                       {{"b", 1U}, {"q", 1U}},
+                                       {{"p", 1U}, {"method", 1U, true}},
+                                       {{"q", 1U}, {"method", 1U, true}}}));
+}
+
+TEST_F(PointerFlowTest, VirtualMethodCall) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    struct Base {
+      virtual void method(int *a);
+    };
+    void foo(Base &obj, int *p) {
+      obj.method(p);
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"a", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, StaticMethodCall) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    struct S {
+      static void method(int *a, int *b);
+    };
+    void foo(int *p, int *q) {
+      S::method(p, q);
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"a", 1U}, {"p", 1U}},
+                                          {{"b", 1U}, {"q", 1U}},
+                                      }));
+}
+
+TEST_F(PointerFlowTest, DefaultArg) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    int *g;
+    void bar(int *a, int *b = g);
+    void foo(int *p) {
+      bar(p);
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__,
+                            {{{"a", 1U}, {"p", 1U}}, {{"b", 1U}, {"g", 1U}}}));
+}
+
+// Counter-example for the concern that matchArgsWithParams could go OOB
+// when fewer explicit args are provided than params (due to default args).
+// In Clang's AST, CallExpr::getNumArgs() always includes CXXDefaultArgExpr
+// nodes for defaulted parameters, so getNumArgs() >= getNumParams() holds.
+TEST_F(PointerFlowTest, AllArgsDefaulted) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    int *g1, *g2;
+    void bar(int *a = g1, int *b = g2);
+    void foo() {
+      bar();
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"a", 1U}, {"g1", 1U}},
+                                          {{"b", 1U}, {"g2", 1U}},
+                                      }));
+}
+
+TEST_F(PointerFlowTest, DefaultArg2) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    int *g;
+    void bar(int *a, int *b = g);
+    void foo(int *p) {
+      bar(p, p);
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"a", 1U}, {"p", 1U}},
+                                          {{"b", 1U}, {"p", 1U}},
+                                      }));
+}
+
+//////////////////////////////////////////////////////////////
+//          CXX Ctor Tests.                                 //
+//////////////////////////////////////////////////////////////
+TEST_F(PointerFlowTest, CXXCtorCallMultiArgs) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    struct S {
+      S(int *a, int *b) {}
+    };
+    void foo(int *p, int *q) {
+      S s{p, q};
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"a", 1U}, {"p", 1U}},
+                                          {{"b", 1U}, {"q", 1U}},
+                                      }));
+}
+
+TEST_F(PointerFlowTest, CXXCtorCallMultiArgs2) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    struct S {
+      S(int *a, int x, int *b) {}
+    };
+    void foo(int *p, int x, int *q) {
+      S s{p, x, q};
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"a", 1U}, {"p", 1U}},
+                                          {{"b", 1U}, {"q", 1U}},
+                                      }));
+}
+
+TEST_F(PointerFlowTest, CXXCtorCallAsCallArg) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    struct Wrapper {
+      Wrapper(int *q) {}
+    };
+    void bar(Wrapper w);
+    void foo(int *p) {
+      bar(Wrapper{p});
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, DelegatingCXXCtorCall) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    struct S {
+      S(int *a, int *b) {}
+      S(int *p) : S(p, p) {}
+    };
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary<CXXConstructorDecl>(CXXCtorOfNumParms{"S", 1});
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"a", 1U}, {"p", 1U}},
+                                          {{"b", 1U}, {"p", 1U}},
+                                      }));
+}
+
+TEST_F(PointerFlowTest, CXXCtorBaseInit) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    struct Base {
+      Base(int *a) {}
+    };
+    struct Derived : Base {
+      Derived(int *p) : Base(p) {}
+    };
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary<CXXConstructorDecl>("Derived");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"a", 1U}, {"p", 1U}}}));
+}
+
+//////////////////////////////////////////////////////////////
+//          Initializers Tests.                             //
+//////////////////////////////////////////////////////////////
+TEST_F(PointerFlowTest, LocalVarDeclInit) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    void foo(int *p) {
+      int *q = p;
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, LocalVarDeclInit2) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    void foo(int (*arr)[10]) {
+      int (*p)[10] = arr;
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"p", 1U}, {"arr", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, FieldInit) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    void foo(int *p) {
+      struct Bar {
+        int *field = p;
+      };
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("Bar");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"field", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, CXXCtorMemberInit) {
+  StringRef Code = R"cpp(
+    void foo(int *p) {
+      struct Bar {
+        int *member;
+        Bar(int *q) : member(q) {}
+      };
+      Bar B{p};
+    }
+  )cpp";
+
+  ASSERT_EQ(setUpTest(Code), true);
+  auto *Sum = getEntitySummary<CXXConstructorDecl>("Bar");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"member", 1U}, {"q", 1U}}}));
+
+  Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, GlobalVarInit) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    int *q;
+    int *g = q;
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary<VarDecl>("g");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"g", 1U}, {"q", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, StaticLocalInit) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    void foo(int *p) {
+      static int *s = p;
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"s", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, StaticMemberInit) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    int *g;
+    struct S { static int *member; };
+    int *S::member = g;
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary<VarDecl>("member");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"member", 1U}, {"g", 1U}}}));
+}
+
+//////////////////////////////////////////////////////////////
+//              InitList Tests.                               //
+//////////////////////////////////////////////////////////////
+
+TEST_F(PointerFlowTest, ArrayInitList) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    void foo(int *p, int *q) {
+      int *arr[] = {p, q};
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"arr", 2U}, {"p", 1U}},
+                                          {{"arr", 2U}, {"q", 1U}},
+                                      }));
+}
+
+TEST_F(PointerFlowTest, StructInitList) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    struct S { int *a; int *b; };
+    void foo(int *p, int *q) {
+      S s = {p, q};
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"a", 1U}, {"p", 1U}},
+                                          {{"b", 1U}, {"q", 1U}},
+                                      }));
+}
+
+// A union initialized with a brace-enclosed initializer:
+TEST_F(PointerFlowTest, UnionInitList) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    union U { int *x; int y; };
+    void foo(int *p) {
+      U u = {p};
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"x", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, NestedInitList) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    struct Inner { int * a; int * b; };
+    struct S { Inner c; int * d; };
+    void foo(int *p, int *q, int *r) {
+      S s = {{p, q}, r};
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"a", 1U}, {"p", 1U}},
+                                          {{"b", 1U}, {"q", 1U}},
+                                          {{"d", 1U}, {"r", 1U}},
+                                      }));
+}
+
+TEST_F(PointerFlowTest, NestedInitList2) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    union Inner { int * a; int b; };
+    struct S { Inner c; int * d; };
+    void foo(int *p, int *q) {
+      S s = {{p}, q};
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"a", 1U}, {"p", 1U}},
+                                          {{"d", 1U}, {"q", 1U}},
+                                      }));
+}
+
+TEST_F(PointerFlowTest, NestedInitList3) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    struct Inner { int * a; int * b; };
+    union S { Inner c; int * d; };
+    void foo(int *p, int *q) {
+      S s = {{p, q}};
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"a", 1U}, {"p", 1U}},
+                                          {{"b", 1U}, {"q", 1U}},
+                                      }));
+}
+
+TEST_F(PointerFlowTest, NestedArrayInitList) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    void foo(int *p, int *q, int *r, int *s) {
+      int *arr[][2] = {{p, q}, {r, s}};
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"arr", 3U}, {"p", 1U}},
+                                          {{"arr", 3U}, {"q", 1U}},
+                                          {{"arr", 3U}, {"r", 1U}},
+                                          {{"arr", 3U}, {"s", 1U}},
+                                      }));
+}
+
+TEST_F(PointerFlowTest, MixedNestedArrayStructInitList) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    struct T { int *arr[2]; };
+    void foo(int *p, int *q, int *r, int *s) {
+      T t[2] = {{p, q}, {r, s}};
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"arr", 2U}, {"p", 1U}},
+                                          {{"arr", 2U}, {"q", 1U}},
+                                          {{"arr", 2U}, {"r", 1U}},
+                                          {{"arr", 2U}, {"s", 1U}},
+                                      }));
+}
+
+TEST_F(PointerFlowTest, ArrayOfStructInitList) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    struct S { int *a; int *b; };
+    void foo(int *p, int *q, int *r, int *s) {
+      S arr[] = {{p, q}, {r, s}};
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"a", 1U}, {"p", 1U}},
+                                          {{"b", 1U}, {"q", 1U}},
+                                          {{"a", 1U}, {"r", 1U}},
+                                          {{"b", 1U}, {"s", 1U}},
+                                      }));
+}
+
+//////////////////////////////////////////////////////////////
+//              Return Tests.                               //
+//////////////////////////////////////////////////////////////
+
+TEST_F(PointerFlowTest, ReturnEdge) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    int *foo(int *p) {
+      return p;
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"foo", 1U, true}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, MultipleReturnEdges) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    int *foo(int *p, int *q, bool cond) {
+      if (cond)
+        return p;
+      return q;
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+                                          {{"foo", 1U, true}, {"p", 1U}},
+                                          {{"foo", 1U, true}, {"q", 1U}},
+                                      }));
+}
+
+TEST_F(PointerFlowTest, NoReturnEdgeForNonPointerReturnType) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    int foo(int *p, int x) {
+      return x;
+    }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("foo");
+
+  EXPECT_THAT(Sum, testing::AnyOf(testing::IsNull(),
+                                  testing::Pointee(makeEdges(__LINE__, {}))));
+}
+
+TEST_F(PointerFlowTest, ReturnEdgeNotFromNestedFunction) {
+  StringRef Code = R"cpp(
+    int *foo(int *p) {
+      struct Inner {
+        int *bar(int *q) { return q; }
+      };
+      return p;
+    }
+  )cpp";
+
+  ASSERT_EQ(setUpTest(Code), true);
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"foo", 1U, true}, {"p", 1U}}}));
+
+  Sum = getEntitySummary("bar");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"bar", 1U, true}, {"q", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, ReturnEdgeInClassMethod) {
+  ASSERT_EQ(setUpTest(R"cpp(
+  void foo() {
+    struct S {
+      int *method(int *p, int *q) { return p; }
+    };
+  }
+  )cpp"),
+            true);
+
+  auto *Sum = getEntitySummary("method");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"method", 1U, true}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, NoEdgeFromIndirectCall) {
+  ASSERT_EQ(setUpTest(R"cpp(
+    void bar(int *param1);
+    void baz(int *param2);
+
+    void foo(int *p, void (*fp)(int *)) {
+      fp(p);
+    }
+
+    int main() {
+      int *q;
+      foo(q, bar);
+      foo(q, baz);
+      return 0;
+    }
+  )cpp"),
+            true);
+
+  /* FIXME or TBD: Currently indirect calls produce no edge: */
+  auto *Sum = getEntitySummary("foo");
+
+  EXPECT_THAT(Sum, testing::AnyOf(testing::IsNull(),
+                                  testing::Pointee(makeEdges(__LINE__, {}))));
+}
+
+//////////////////////////////////////////////////////////////
+//          Lambda Tests.                                   //
+//////////////////////////////////////////////////////////////
+
+TEST_F(PointerFlowTest, ReturnInLambda) {
+  StringRef Code = R"cpp(
+    int* foo(int *p) {
+      auto local = [](int *r) { return r; };
+      return local(p);
+    }
+  )cpp";
+
+  ASSERT_EQ(setUpTest(Code), true);
+  auto *Sum = getEntitySummary("foo");
+
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"r", 1U}, {"p", 1U}},
+                                       {{"foo", 1U, true},
+                                        {LambdaOfVar{"local"}, 1U, true}}}));
+
+  Sum = getEntitySummary(LambdaOfVar{"local"});
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__,
+                            {{{LambdaOfVar{"local"}, 1U, true}, {"r", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, NestedLambdaAssign) {
+  StringRef Code = R"cpp(
+    void foo() {
+      auto outer_lambda = [](int *r, int *s) {
+        s = r;
+        auto inner_lambda = [](int *x, int *y) { y = x; };
+      };
+    }
+  )cpp";
+
+  ASSERT_EQ(setUpTest(Code), true);
+  auto *Sum = getEntitySummary(LambdaOfVar{"outer_lambda"});
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"s", 1U}, {"r", 1U}}}));
+
+  Sum = getEntitySummary(LambdaOfVar{"inner_lambda"});
+  ASSERT_NE(Sum, nullptr);
+  EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"y", 1U}, {"x", 1U}}}));
+}
+} // namespace

diff  --git a/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt b/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
index 51466070ec8d9..e5f4688a44275 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
+++ b/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
@@ -1,4 +1,5 @@
 add_distinct_clang_unittest(ClangScalableAnalysisTests
+  Analyses/PointerFlow/PointerFlowTest.cpp
   Analyses/CallGraph/CallGraphExtractorTest.cpp
   Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp
   ASTEntityMappingTest.cpp


        


More information about the cfe-commits mailing list