[llvm-branch-commits] [clang] [SSAF][WPA] Add no-op PointerFlow and UnsafeBufferUsage analysis (PR #193089)

Ziqing Luo via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Wed Apr 22 11:52:25 PDT 2026


https://github.com/ziqingluo-90 updated https://github.com/llvm/llvm-project/pull/193089

>From 4e2adbf269f557d7e6fad0e8bf37b9af80f82c9c Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Fri, 10 Apr 2026 13:43:11 -0700
Subject: [PATCH 1/5] Revert "Revert "[NFC][SSAF] Move EntityPointerLevel to a
 separate folder" (#191481)"

This reverts commit 2ae4ddd720a9702e8aac6094c3930bfeac8fc325.
---
 .../EntityPointerLevel/EntityPointerLevel.h   |  99 +++++++
 .../UnsafeBufferUsage/UnsafeBufferUsage.h     |  68 +----
 .../UnsafeBufferUsageExtractor.h              |   5 -
 .../Analyses/CMakeLists.txt                   |   1 +
 .../EntityPointerLevel/EntityPointerLevel.cpp | 244 ++++++++++++++++++
 .../UnsafeBufferUsage/UnsafeBufferUsage.cpp   |   4 -
 .../UnsafeBufferUsageExtractor.cpp            | 220 +---------------
 .../UnsafeBufferUsageTest.cpp                 |   4 +-
 8 files changed, 353 insertions(+), 292 deletions(-)
 create mode 100644 clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h
 create mode 100644 clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp

diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h
new file mode 100644
index 0000000000000..52caa52e1120d
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h
@@ -0,0 +1,99 @@
+//===- EntityPointerLevel.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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_ENTITYPOINTERLEVEL_ENTITYPOINTERLEVEL_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_ENTITYPOINTERLEVEL_ENTITYPOINTERLEVEL_H
+
+#include "clang/AST/Expr.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityName.h"
+#include <set>
+
+namespace clang::ssaf {
+
+/// An EntityPointerLevel represents a level of the declared pointer/array
+/// type of an entity.  In the fully-expanded spelling of the declared type, a
+/// EntityPointerLevel is associated with a '*' (or a '[]`) in that declaration.
+///
+/// For example, for 'int *p[10];', there are two EntityPointerLevels. One
+/// is associated with 'int *[10]' of 'p' and the other is associated with 'int
+/// *' of 'p'.
+///
+/// An EntityPointerLevel can be identified by an EntityId and an unsigned
+/// integer indicating the pointer level: '(EntityId, PointerLevel)'.
+/// An EntityPointerLevel 'P' is valid iff 'P.EntityId' has a pointer type with
+/// at least 'P.PointerLevel' levels (This implies 'P.PointerLevel > 0').
+///
+/// For the same example 'int *p[10];', the EntityPointerLevels below are valid:
+/// - '(p, 2)' is associated with the 'int *' part of the declared type of 'p';
+/// - '(p, 1)' is associated with the 'int *[10]' part of the declared type of
+/// 'p'.
+class EntityPointerLevel {
+  EntityId Entity;
+  unsigned PointerLevel;
+
+  friend class EntityPointerLevelTranslator;
+  friend EntityPointerLevel buildEntityPointerLevel(EntityId, unsigned);
+
+  EntityPointerLevel(EntityId Entity, unsigned PointerLevel)
+      : Entity(Entity), PointerLevel(PointerLevel) {}
+
+public:
+  EntityId getEntity() const { return Entity; }
+  unsigned getPointerLevel() const { return PointerLevel; }
+
+  bool operator==(const EntityPointerLevel &Other) const {
+    return std::tie(Entity, PointerLevel) ==
+           std::tie(Other.Entity, Other.PointerLevel);
+  }
+
+  bool operator!=(const EntityPointerLevel &Other) const {
+    return !(*this == Other);
+  }
+
+  bool operator<(const EntityPointerLevel &Other) const {
+    return std::tie(Entity, PointerLevel) <
+           std::tie(Other.Entity, Other.PointerLevel);
+  }
+
+  /// Compares `EntityPointerLevel`s; additionally, partially compares
+  /// `EntityPointerLevel` with `EntityId`.
+  struct Comparator {
+    using is_transparent = void;
+    bool operator()(const EntityPointerLevel &L,
+                    const EntityPointerLevel &R) const {
+      return L < R;
+    }
+    bool operator()(const EntityId &L, const EntityPointerLevel &R) const {
+      return L < R.getEntity();
+    }
+    bool operator()(const EntityPointerLevel &L, const EntityId &R) const {
+      return L.getEntity() < R;
+    }
+  };
+};
+
+using EntityPointerLevelSet =
+    std::set<EntityPointerLevel, EntityPointerLevel::Comparator>;
+
+/// Translate a pointer/array type expression 'E' to a (set of)
+/// EntityPointerLevel(s) associated with the declared type of the base address
+/// of `E`. If the base address of `E` is not associated with an entity, the
+/// translation result is an empty set.
+///
+/// \param E the pointer expression to be translated
+/// \param Ctx the AST context of `E`
+/// \param AddEntity the callback provided by the caller to convert EntityNames
+/// to EntityIds.
+llvm::Expected<EntityPointerLevelSet>
+translateEntityPointerLevel(const Expr *E, ASTContext &Ctx,
+                            std::function<EntityId(EntityName EN)> AddEntity);
+
+EntityPointerLevel buildEntityPointerLevel(EntityId, unsigned);
+} // namespace clang::ssaf
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_ENTITYPOINTERLEVEL_ENTITYPOINTERLEVEL_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h
index 4e217c2eb87ef..250bad5b72f75 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h
@@ -9,7 +9,7 @@
 #ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_UNSAFEBUFFERUSAGE_UNSAFEBUFFERUSAGE_H
 #define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_UNSAFEBUFFERUSAGE_UNSAFEBUFFERUSAGE_H
 
-#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
 #include "clang/ScalableStaticAnalysisFramework/Core/Model/SummaryName.h"
 #include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/EntitySummary.h"
 #include "llvm/ADT/StringRef.h"
@@ -18,72 +18,6 @@
 
 namespace clang::ssaf {
 
-/// An EntityPointerLevel represents a level of the declared pointer/array
-/// type of an entity.  In the fully-expanded spelling of the declared type, a
-/// EntityPointerLevel is associated with a '*' (or a '[]`) in that declaration.
-///
-/// For example, for 'int *p[10];', there are two EntityPointerLevels. One
-/// is associated with 'int *[10]' of 'p' and the other is associated with 'int
-/// *' of 'p'.
-///
-/// An EntityPointerLevel can be identified by an EntityId and an unsigned
-/// integer indicating the pointer level: '(EntityId, PointerLevel)'.
-/// An EntityPointerLevel 'P' is valid iff 'P.EntityId' has a pointer type with
-/// at least 'P.PointerLevel' levels (This implies 'P.PointerLevel > 0').
-///
-/// For the same example 'int *p[10];', the EntityPointerLevels below are valid:
-/// - '(p, 2)' is associated with the 'int *' part of the declared type of 'p';
-/// - '(p, 1)' is associated with the 'int *[10]' part of the declared type of
-/// 'p'.
-class EntityPointerLevel {
-  EntityId Entity;
-  unsigned PointerLevel;
-
-  friend class UnsafeBufferUsageEntitySummary;
-  friend class UnsafeBufferUsageTUSummaryExtractor;
-  friend EntityPointerLevel buildEntityPointerLevel(EntityId, unsigned);
-
-  EntityPointerLevel(EntityId Entity, unsigned PointerLevel)
-      : Entity(Entity), PointerLevel(PointerLevel) {}
-
-public:
-  EntityId getEntity() const { return Entity; }
-  unsigned getPointerLevel() const { return PointerLevel; }
-
-  bool operator==(const EntityPointerLevel &Other) const {
-    return std::tie(Entity, PointerLevel) ==
-           std::tie(Other.Entity, Other.PointerLevel);
-  }
-
-  bool operator!=(const EntityPointerLevel &Other) const {
-    return !(*this == Other);
-  }
-
-  bool operator<(const EntityPointerLevel &Other) const {
-    return std::tie(Entity, PointerLevel) <
-           std::tie(Other.Entity, Other.PointerLevel);
-  }
-
-  /// Compares `EntityPointerLevel`s; additionally, partially compares
-  /// `EntityPointerLevel` with `EntityId`.
-  struct Comparator {
-    using is_transparent = void;
-    bool operator()(const EntityPointerLevel &L,
-                    const EntityPointerLevel &R) const {
-      return L < R;
-    }
-    bool operator()(const EntityId &L, const EntityPointerLevel &R) const {
-      return L < R.getEntity();
-    }
-    bool operator()(const EntityPointerLevel &L, const EntityId &R) const {
-      return L.getEntity() < R;
-    }
-  };
-};
-
-using EntityPointerLevelSet =
-    std::set<EntityPointerLevel, EntityPointerLevel::Comparator>;
-
 /// An UnsafeBufferUsageEntitySummary is an immutable set of unsafe buffers, in
 /// the form of EntityPointerLevel.
 class UnsafeBufferUsageEntitySummary final : public EntitySummary {
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.h
index 765b2c37562ce..13d4e18b4e81f 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.h
@@ -22,11 +22,6 @@ class UnsafeBufferUsageTUSummaryExtractor : public TUSummaryExtractor {
   UnsafeBufferUsageTUSummaryExtractor(TUSummaryBuilder &Builder)
       : TUSummaryExtractor(Builder) {}
 
-  static EntityPointerLevel buildEntityPointerLevel(EntityId Entity,
-                                                    unsigned PointerLevel) {
-    return {Entity, PointerLevel};
-  }
-
   EntityId addEntity(EntityName EN) { return SummaryBuilder.addEntity(EN); }
 
   std::unique_ptr<UnsafeBufferUsageEntitySummary>
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
index 926b610aa8dee..c15ff3b3c42e7 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
@@ -5,6 +5,7 @@ set(LLVM_LINK_COMPONENTS
 add_clang_library(clangScalableStaticAnalysisFrameworkAnalyses 
   CallGraph/CallGraphExtractor.cpp
   CallGraph/CallGraphJSONFormat.cpp
+  EntityPointerLevel/EntityPointerLevel.cpp
   UnsafeBufferUsage/UnsafeBufferUsage.cpp
   UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
 
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
new file mode 100644
index 0000000000000..49a135b13877a
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
@@ -0,0 +1,244 @@
+//===- 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.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/StmtVisitor.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/ASTEntityMapping.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityName.h"
+
+using namespace clang;
+using namespace ssaf;
+
+static bool hasPointerType(const Expr *E) {
+  auto Ty = E->getType();
+  return !Ty.isNull() && !Ty->isFunctionPointerType() &&
+         (Ty->isPointerType() || Ty->isArrayType());
+}
+
+static llvm::Error makeUnsupportedStmtKindError(const Stmt *Unsupported) {
+  return llvm::createStringError(
+      "unsupported expression kind for translation to "
+      "EntityPointerLevel: %s",
+      Unsupported->getStmtClassName());
+}
+
+static llvm::Error makeCreateEntityNameError(const NamedDecl *FailedDecl,
+                                             ASTContext &Ctx) {
+  std::string LocStr = FailedDecl->getSourceRange().getBegin().printToString(
+      Ctx.getSourceManager());
+  return llvm::createStringError(
+      "failed to create entity name for %s declared at %s",
+      FailedDecl->getNameAsString().c_str(), LocStr.c_str());
+}
+
+// Translate a pointer type expression 'E' to a (set of) EntityPointerLevel(s)
+// associated with the declared type of the base address of `E`. If the base
+// address of `E` is not associated with an entity, the translation result is an
+// empty set.
+//
+// The translation is a process of traversing into the pointer 'E' until its
+// base address can be represented by an entity, with the number of dereferences
+// tracked by incrementing the pointer level.  Naturally, taking address of, as
+// the inverse operation of dereference, is tracked by decrementing the pointer
+// level.
+//
+// For example, suppose there are pointers and arrays declared as
+//   int *ptr, **p1, **p2;
+//   int arr[10][10];
+// , the translation of expressions involving these base addresses will be:
+//   Translate(ptr + 5)            -> {(ptr, 1)}
+//   Translate(arr[5])             -> {(arr, 2)}
+//   Translate(cond ? p1[5] : p2)  -> {(p1, 2), (p2, 1)}
+//   Translate(&arr[5])            -> {(arr, 1)}
+class ssaf::EntityPointerLevelTranslator
+    : ConstStmtVisitor<EntityPointerLevelTranslator,
+                       Expected<EntityPointerLevelSet>> {
+  friend class StmtVisitorBase;
+
+  // Fallback method for all unsupported expression kind:
+  llvm::Error fallback(const Stmt *E) {
+    return makeUnsupportedStmtKindError(E);
+  }
+
+  static EntityPointerLevel incrementPointerLevel(const EntityPointerLevel &E) {
+    return EntityPointerLevel(E.getEntity(), E.getPointerLevel() + 1);
+  }
+
+  static EntityPointerLevel decrementPointerLevel(const EntityPointerLevel &E) {
+    assert(E.getPointerLevel() > 0);
+    return EntityPointerLevel(E.getEntity(), E.getPointerLevel() - 1);
+  }
+
+  EntityPointerLevel createEntityPointerLevelFor(const EntityName &Name) {
+    return EntityPointerLevel(AddEntity(Name), 1);
+  }
+
+  // The common helper function for Translate(*base):
+  // Translate(*base) -> Translate(base) with .pointerLevel + 1
+  Expected<EntityPointerLevelSet> translateDereferencePointer(const Expr *Ptr) {
+    assert(hasPointerType(Ptr));
+
+    Expected<EntityPointerLevelSet> SubResult = Visit(Ptr);
+    if (!SubResult)
+      return SubResult.takeError();
+
+    auto Incremented = llvm::map_range(*SubResult, incrementPointerLevel);
+    return EntityPointerLevelSet{Incremented.begin(), Incremented.end()};
+  }
+
+  std::function<EntityId(EntityName EN)> AddEntity;
+  ASTContext &Ctx;
+
+public:
+  EntityPointerLevelTranslator(std::function<EntityId(EntityName EN)> AddEntity,
+                               ASTContext &Ctx)
+      : AddEntity(AddEntity), Ctx(Ctx) {}
+
+  Expected<EntityPointerLevelSet> translate(const Expr *E) { return Visit(E); }
+
+private:
+  Expected<EntityPointerLevelSet> VisitStmt(const Stmt *E) {
+    return fallback(E);
+  }
+
+  // Translate(base + x)           -> Translate(base)
+  // Translate(x + base)           -> Translate(base)
+  // Translate(base - x)           -> Translate(base)
+  // Translate(base {+=, -=, =} x) -> Translate(base)
+  // Translate(x, base)            -> Translate(base)
+  Expected<EntityPointerLevelSet> VisitBinaryOperator(const BinaryOperator *E) {
+    switch (E->getOpcode()) {
+    case clang::BO_Add:
+      if (hasPointerType(E->getLHS()))
+        return Visit(E->getLHS());
+      return Visit(E->getRHS());
+    case clang::BO_Sub:
+    case clang::BO_AddAssign:
+    case clang::BO_SubAssign:
+    case clang::BO_Assign:
+      return Visit(E->getLHS());
+    case clang::BO_Comma:
+      return Visit(E->getRHS());
+    default:
+      return fallback(E);
+    }
+  }
+
+  // Translate({++, --}base)   -> Translate(base)
+  // Translate(base{++, --})   -> Translate(base)
+  // Translate(*base)          -> Translate(base) with .pointerLevel += 1
+  // Translate(&base)          -> {}, if Translate(base) is {}
+  //                           -> Translate(base) with .pointerLevel -= 1
+  Expected<EntityPointerLevelSet> VisitUnaryOperator(const UnaryOperator *E) {
+    switch (E->getOpcode()) {
+    case clang::UO_PostInc:
+    case clang::UO_PostDec:
+    case clang::UO_PreInc:
+    case clang::UO_PreDec:
+      return Visit(E->getSubExpr());
+    case clang::UO_AddrOf: {
+      Expected<EntityPointerLevelSet> SubResult = Visit(E->getSubExpr());
+      if (!SubResult)
+        return SubResult.takeError();
+
+      auto Decremented = llvm::map_range(*SubResult, decrementPointerLevel);
+      return EntityPointerLevelSet{Decremented.begin(), Decremented.end()};
+    }
+    case clang::UO_Deref:
+      return translateDereferencePointer(E->getSubExpr());
+    default:
+      return fallback(E);
+    }
+  }
+
+  // Translate((T*)base) -> Translate(p) if p has pointer type
+  //                     -> {} otherwise
+  Expected<EntityPointerLevelSet> VisitCastExpr(const CastExpr *E) {
+    if (hasPointerType(E->getSubExpr()))
+      return Visit(E->getSubExpr());
+    return EntityPointerLevelSet{};
+  }
+
+  // Translate(f(...)) -> {} if it is an indirect call
+  //                   -> {(f_return, 1)}, otherwise
+  Expected<EntityPointerLevelSet> VisitCallExpr(const CallExpr *E) {
+    if (auto *FD = E->getDirectCallee())
+      if (auto FDEntityName = getEntityNameForReturn(FD))
+        return EntityPointerLevelSet{
+            createEntityPointerLevelFor(*FDEntityName)};
+    return EntityPointerLevelSet{};
+  }
+
+  // Translate(base[x]) -> Translate(*base)
+  Expected<EntityPointerLevelSet>
+  VisitArraySubscriptExpr(const ArraySubscriptExpr *E) {
+    return translateDereferencePointer(E->getBase());
+  }
+
+  // Translate(cond ? base1 : base2) := Translate(base1) U Translate(base2)
+  Expected<EntityPointerLevelSet>
+  VisitAbstractConditionalOperator(const AbstractConditionalOperator *E) {
+    Expected<EntityPointerLevelSet> ReT = Visit(E->getTrueExpr());
+    Expected<EntityPointerLevelSet> ReF = Visit(E->getFalseExpr());
+
+    if (ReT && ReF) {
+      ReT->insert(ReF->begin(), ReF->end());
+      return ReT;
+    }
+    if (!ReF && !ReT)
+      return llvm::joinErrors(ReT.takeError(), ReF.takeError());
+    if (!ReF)
+      return ReF.takeError();
+    return ReT.takeError();
+  }
+
+  Expected<EntityPointerLevelSet> VisitParenExpr(const ParenExpr *E) {
+    return Visit(E->getSubExpr());
+  }
+
+  // Translate("string-literal") -> {}
+  // Buffer accesses on string literals are unsafe, but string literals are not
+  // entities so there is no EntityPointerLevel associated with it.
+  Expected<EntityPointerLevelSet> VisitStringLiteral(const StringLiteral *E) {
+    return EntityPointerLevelSet{};
+  }
+
+  // Translate(DRE) -> {(Decl, 1)}
+  Expected<EntityPointerLevelSet> VisitDeclRefExpr(const DeclRefExpr *E) {
+    if (auto EntityName = getEntityName(E->getDecl()))
+      return EntityPointerLevelSet{createEntityPointerLevelFor(*EntityName)};
+    return makeCreateEntityNameError(E->getDecl(), Ctx);
+  }
+
+  // Translate({., ->}f) -> {(MemberDecl, 1)}
+  Expected<EntityPointerLevelSet> VisitMemberExpr(const MemberExpr *E) {
+    if (auto EntityName = getEntityName(E->getMemberDecl()))
+      return EntityPointerLevelSet{createEntityPointerLevelFor(*EntityName)};
+    return makeCreateEntityNameError(E->getMemberDecl(), Ctx);
+  }
+
+  Expected<EntityPointerLevelSet>
+  VisitOpaqueValueExpr(const OpaqueValueExpr *S) {
+    return Visit(S->getSourceExpr());
+  }
+};
+
+Expected<EntityPointerLevelSet> clang::ssaf::translateEntityPointerLevel(
+    const Expr *E, ASTContext &Ctx,
+    std::function<EntityId(EntityName EN)> AddEntity) {
+  EntityPointerLevelTranslator Translator(AddEntity, Ctx);
+
+  return Translator.translate(E);
+}
+
+EntityPointerLevel clang::ssaf::buildEntityPointerLevel(EntityId Id,
+                                                        unsigned PtrLv) {
+  return EntityPointerLevel({Id, PtrLv});
+}
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.cpp
index d325e8df79c20..84f3f9cbb3852 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.cpp
@@ -21,10 +21,6 @@ using Object = llvm::json::Object;
 
 static constexpr llvm::StringLiteral SummarySerializationKey = "UnsafeBuffers";
 
-EntityPointerLevel ssaf::buildEntityPointerLevel(EntityId Id, unsigned PtrLv) {
-  return EntityPointerLevel(Id, PtrLv);
-}
-
 UnsafeBufferUsageEntitySummary
 ssaf::buildUnsafeBufferUsageEntitySummary(EntityPointerLevelSet UnsafeBuffers) {
   return UnsafeBufferUsageEntitySummary(std::move(UnsafeBuffers));
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
index c609168e4dc7d..b29eaa6b903d0 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
@@ -11,8 +11,8 @@
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/DynamicRecursiveASTVisitor.h"
-#include "clang/AST/StmtVisitor.h"
 #include "clang/Analysis/Analyses/UnsafeBufferUsage.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
 #include "clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h"
 #include "clang/ScalableStaticAnalysisFramework/Core/ASTEntityMapping.h"
 #include "llvm/ADT/STLExtras.h"
@@ -24,22 +24,6 @@ namespace {
 using namespace clang;
 using namespace ssaf;
 
-static bool hasPointerType(const Expr *E) {
-  auto Ty = E->getType();
-  return !Ty.isNull() && !Ty->isFunctionPointerType() &&
-         (Ty->isPointerType() || Ty->isArrayType());
-}
-
-constexpr inline auto buildEntityPointerLevel =
-    UnsafeBufferUsageTUSummaryExtractor::buildEntityPointerLevel;
-
-static llvm::Error makeUnsupportedStmtKindError(const Stmt *Unsupported) {
-  return llvm::createStringError(
-      "unsupported expression kind for translation to "
-      "EntityPointerLevel: %s",
-      Unsupported->getStmtClassName());
-}
-
 static llvm::Error makeCreateEntityNameError(const NamedDecl *FailedDecl,
                                              ASTContext &Ctx) {
   std::string LocStr = FailedDecl->getSourceRange().getBegin().printToString(
@@ -59,208 +43,17 @@ static llvm::Error makeAddEntitySummaryError(const NamedDecl *FailedContributor,
       FailedContributor->getNameAsString().c_str(), LocStr.c_str());
 }
 
-// Translate a pointer type expression 'E' to a (set of) EntityPointerLevel(s)
-// associated with the declared type of the base address of `E`. If the base
-// address of `E` is not associated with an entity, the translation result is an
-// empty set.
-//
-// The translation is a process of traversing into the pointer 'E' until its
-// base address can be represented by an entity, with the number of dereferences
-// tracked by incrementing the pointer level.  Naturally, taking address of, as
-// the inverse operation of dereference, is tracked by decrementing the pointer
-// level.
-//
-// For example, suppose there are pointers and arrays declared as
-//   int *ptr, **p1, **p2;
-//   int arr[10][10];
-// , the translation of expressions involving these base addresses will be:
-//   Translate(ptr + 5)            -> {(ptr, 1)}
-//   Translate(arr[5])             -> {(arr, 2)}
-//   Translate(cond ? p1[5] : p2)  -> {(p1, 2), (p2, 1)}
-//   Translate(&arr[5])            -> {(arr, 1)}
-class EntityPointerLevelTranslator
-    : ConstStmtVisitor<EntityPointerLevelTranslator,
-                       Expected<EntityPointerLevelSet>> {
-  friend class StmtVisitorBase;
-
-  // Fallback method for all unsupported expression kind:
-  llvm::Error fallback(const Stmt *E) {
-    return makeUnsupportedStmtKindError(E);
-  }
-
-  static EntityPointerLevel incrementPointerLevel(const EntityPointerLevel &E) {
-    return buildEntityPointerLevel(E.getEntity(), E.getPointerLevel() + 1);
-  }
-
-  static EntityPointerLevel decrementPointerLevel(const EntityPointerLevel &E) {
-    assert(E.getPointerLevel() > 0);
-    return buildEntityPointerLevel(E.getEntity(), E.getPointerLevel() - 1);
-  }
-
-  EntityPointerLevel createEntityPointerLevelFor(const EntityName &Name) {
-    return buildEntityPointerLevel(Extractor.addEntity(Name), 1);
-  }
-
-  // The common helper function for Translate(*base):
-  // Translate(*base) -> Translate(base) with .pointerLevel + 1
-  Expected<EntityPointerLevelSet> translateDereferencePointer(const Expr *Ptr) {
-    assert(hasPointerType(Ptr));
-
-    Expected<EntityPointerLevelSet> SubResult = Visit(Ptr);
-    if (!SubResult)
-      return SubResult.takeError();
-
-    auto Incremented = llvm::map_range(*SubResult, incrementPointerLevel);
-    return EntityPointerLevelSet{Incremented.begin(), Incremented.end()};
-  }
-
-  UnsafeBufferUsageTUSummaryExtractor &Extractor;
-  ASTContext &Ctx;
-
-public:
-  EntityPointerLevelTranslator(UnsafeBufferUsageTUSummaryExtractor &Extractor,
-                               ASTContext &Ctx)
-      : Extractor(Extractor), Ctx(Ctx) {}
-
-  Expected<EntityPointerLevelSet> translate(const Expr *E) { return Visit(E); }
-
-private:
-  Expected<EntityPointerLevelSet> VisitStmt(const Stmt *E) {
-    return fallback(E);
-  }
-
-  // Translate(base + x)           -> Translate(base)
-  // Translate(x + base)           -> Translate(base)
-  // Translate(base - x)           -> Translate(base)
-  // Translate(base {+=, -=, =} x) -> Translate(base)
-  // Translate(x, base)            -> Translate(base)
-  Expected<EntityPointerLevelSet> VisitBinaryOperator(const BinaryOperator *E) {
-    switch (E->getOpcode()) {
-    case clang::BO_Add:
-      if (hasPointerType(E->getLHS()))
-        return Visit(E->getLHS());
-      return Visit(E->getRHS());
-    case clang::BO_Sub:
-    case clang::BO_AddAssign:
-    case clang::BO_SubAssign:
-    case clang::BO_Assign:
-      return Visit(E->getLHS());
-    case clang::BO_Comma:
-      return Visit(E->getRHS());
-    default:
-      return fallback(E);
-    }
-  }
-
-  // Translate({++, --}base)   -> Translate(base)
-  // Translate(base{++, --})   -> Translate(base)
-  // Translate(*base)          -> Translate(base) with .pointerLevel += 1
-  // Translate(&base)          -> {}, if Translate(base) is {}
-  //                           -> Translate(base) with .pointerLevel -= 1
-  Expected<EntityPointerLevelSet> VisitUnaryOperator(const UnaryOperator *E) {
-    switch (E->getOpcode()) {
-    case clang::UO_PostInc:
-    case clang::UO_PostDec:
-    case clang::UO_PreInc:
-    case clang::UO_PreDec:
-      return Visit(E->getSubExpr());
-    case clang::UO_AddrOf: {
-      Expected<EntityPointerLevelSet> SubResult = Visit(E->getSubExpr());
-      if (!SubResult)
-        return SubResult.takeError();
-
-      auto Decremented = llvm::map_range(*SubResult, decrementPointerLevel);
-      return EntityPointerLevelSet{Decremented.begin(), Decremented.end()};
-    }
-    case clang::UO_Deref:
-      return translateDereferencePointer(E->getSubExpr());
-    default:
-      return fallback(E);
-    }
-  }
-
-  // Translate((T*)base) -> Translate(p) if p has pointer type
-  //                     -> {} otherwise
-  Expected<EntityPointerLevelSet> VisitCastExpr(const CastExpr *E) {
-    if (hasPointerType(E->getSubExpr()))
-      return Visit(E->getSubExpr());
-    return EntityPointerLevelSet{};
-  }
-
-  // Translate(f(...)) -> {} if it is an indirect call
-  //                   -> {(f_return, 1)}, otherwise
-  Expected<EntityPointerLevelSet> VisitCallExpr(const CallExpr *E) {
-    if (auto *FD = E->getDirectCallee())
-      if (auto FDEntityName = getEntityNameForReturn(FD))
-        return EntityPointerLevelSet{
-            createEntityPointerLevelFor(*FDEntityName)};
-    return EntityPointerLevelSet{};
-  }
-
-  // Translate(base[x]) -> Translate(*base)
-  Expected<EntityPointerLevelSet>
-  VisitArraySubscriptExpr(const ArraySubscriptExpr *E) {
-    return translateDereferencePointer(E->getBase());
-  }
-
-  // Translate(cond ? base1 : base2) := Translate(base1) U Translate(base2)
-  Expected<EntityPointerLevelSet>
-  VisitAbstractConditionalOperator(const AbstractConditionalOperator *E) {
-    Expected<EntityPointerLevelSet> ReT = Visit(E->getTrueExpr());
-    Expected<EntityPointerLevelSet> ReF = Visit(E->getFalseExpr());
-
-    if (ReT && ReF) {
-      ReT->insert(ReF->begin(), ReF->end());
-      return ReT;
-    }
-    if (!ReF && !ReT)
-      return llvm::joinErrors(ReT.takeError(), ReF.takeError());
-    if (!ReF)
-      return ReF.takeError();
-    return ReT.takeError();
-  }
-
-  Expected<EntityPointerLevelSet> VisitParenExpr(const ParenExpr *E) {
-    return Visit(E->getSubExpr());
-  }
-
-  // Translate("string-literal") -> {}
-  // Buffer accesses on string literals are unsafe, but string literals are not
-  // entities so there is no EntityPointerLevel associated with it.
-  Expected<EntityPointerLevelSet> VisitStringLiteral(const StringLiteral *E) {
-    return EntityPointerLevelSet{};
-  }
-
-  // Translate(DRE) -> {(Decl, 1)}
-  Expected<EntityPointerLevelSet> VisitDeclRefExpr(const DeclRefExpr *E) {
-    if (auto EntityName = getEntityName(E->getDecl()))
-      return EntityPointerLevelSet{createEntityPointerLevelFor(*EntityName)};
-    return makeCreateEntityNameError(E->getDecl(), Ctx);
-  }
-
-  // Translate({., ->}f) -> {(MemberDecl, 1)}
-  Expected<EntityPointerLevelSet> VisitMemberExpr(const MemberExpr *E) {
-    if (auto EntityName = getEntityName(E->getMemberDecl()))
-      return EntityPointerLevelSet{createEntityPointerLevelFor(*EntityName)};
-    return makeCreateEntityNameError(E->getMemberDecl(), Ctx);
-  }
-
-  Expected<EntityPointerLevelSet>
-  VisitOpaqueValueExpr(const OpaqueValueExpr *S) {
-    return Visit(S->getSourceExpr());
-  }
-};
-
 Expected<EntityPointerLevelSet>
 buildEntityPointerLevels(std::set<const Expr *> &&UnsafePointers,
                          UnsafeBufferUsageTUSummaryExtractor &Extractor,
-                         ASTContext &Ctx) {
+                         ASTContext &Ctx,
+                         std::function<EntityId(EntityName)> AddEntity) {
   EntityPointerLevelSet Result{};
-  EntityPointerLevelTranslator Translator{Extractor, Ctx};
   llvm::Error AllErrors = llvm::ErrorSuccess();
 
   for (const Expr *Ptr : UnsafePointers) {
-    Expected<EntityPointerLevelSet> Translation = Translator.translate(Ptr);
+    Expected<EntityPointerLevelSet> Translation =
+        translateEntityPointerLevel(Ptr, Ctx, AddEntity);
 
     if (Translation) {
       // Filter out those temporary invalid EntityPointerLevels associated with
@@ -297,8 +90,9 @@ static std::set<const Expr *> findUnsafePointersInContributor(const Decl *D) {
 std::unique_ptr<UnsafeBufferUsageEntitySummary>
 UnsafeBufferUsageTUSummaryExtractor::extractEntitySummary(
     const Decl *Contributor, ASTContext &Ctx, llvm::Error &Error) {
+  auto AddEntity = [this](EntityName EN) { return addEntity(EN); };
   Expected<EntityPointerLevelSet> EPLs = buildEntityPointerLevels(
-      findUnsafePointersInContributor(Contributor), *this, Ctx);
+      findUnsafePointersInContributor(Contributor), *this, Ctx, AddEntity);
 
   if (EPLs)
     return std::make_unique<UnsafeBufferUsageEntitySummary>(
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp
index 51d422c1921af..d2c513b7c70a2 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp
@@ -10,6 +10,7 @@
 #include "TestFixture.h"
 #include "clang/AST/DynamicRecursiveASTVisitor.h"
 #include "clang/Frontend/ASTUnit.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
 #include "clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.h"
 #include "clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.h"
 #include "clang/ScalableStaticAnalysisFramework/Core/ASTEntityMapping.h"
@@ -63,9 +64,6 @@ const FunctionDecl *findFnByName(StringRef Name, ASTContext &Ctx) {
   return findDeclByName<FunctionDecl>(Name, Ctx);
 }
 
-constexpr inline auto buildEntityPointerLevel =
-    UnsafeBufferUsageTUSummaryExtractor::buildEntityPointerLevel;
-
 class UnsafeBufferUsageTest : public TestFixture {
 protected:
   TUSummary TUSum;

>From b6d0b59cf51daea221705010587bff871819db58 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Fri, 10 Apr 2026 13:45:17 -0700
Subject: [PATCH 2/5] Remove name qualifiers on a class, using namespace
 instead. This should fix the bot failure:
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

    FAILED: clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp:61:5: error: qualified name does not name a class before ‘:’ token
       61 |     : ConstStmtVisitor<EntityPointerLevelTranslator,
          |     ^
---
 .../Analyses/EntityPointerLevel/EntityPointerLevel.cpp        | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
index 49a135b13877a..d7b8aebef55aa 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
@@ -38,6 +38,7 @@ static llvm::Error makeCreateEntityNameError(const NamedDecl *FailedDecl,
       FailedDecl->getNameAsString().c_str(), LocStr.c_str());
 }
 
+namespace clang::ssaf {
 // Translate a pointer type expression 'E' to a (set of) EntityPointerLevel(s)
 // associated with the declared type of the base address of `E`. If the base
 // address of `E` is not associated with an entity, the translation result is an
@@ -57,7 +58,7 @@ static llvm::Error makeCreateEntityNameError(const NamedDecl *FailedDecl,
 //   Translate(arr[5])             -> {(arr, 2)}
 //   Translate(cond ? p1[5] : p2)  -> {(p1, 2), (p2, 1)}
 //   Translate(&arr[5])            -> {(arr, 1)}
-class ssaf::EntityPointerLevelTranslator
+class EntityPointerLevelTranslator
     : ConstStmtVisitor<EntityPointerLevelTranslator,
                        Expected<EntityPointerLevelSet>> {
   friend class StmtVisitorBase;
@@ -229,6 +230,7 @@ class ssaf::EntityPointerLevelTranslator
     return Visit(S->getSourceExpr());
   }
 };
+} // namespace clang::ssaf
 
 Expected<EntityPointerLevelSet> clang::ssaf::translateEntityPointerLevel(
     const Expr *E, ASTContext &Ctx,

>From 915984c69b277228cecac6cb345624939ce7daef Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Fri, 17 Apr 2026 12:22:03 -0700
Subject: [PATCH 3/5] [SSAF][WPA] Add no-op PointerFlow and UnsafeBufferUsage
 analysis

We need no-op PointerFlow and UnsafeBufferUsage analyses for the
analysis that depends on their summary data.

Refactored PointerFlow and UnsafeBufferUsage serialization for code
sharing.

rdar://174874942
---
 .../EntityPointerLevelFormat.h                |   9 ++
 .../PointerFlow/PointerFlowAnalysis.h         |  40 ++++++
 .../Analyses/PointerFlow/PointerFlowFormat.h  |  35 +++++
 .../UnsafeBufferUsageAnalysis.h               |  40 ++++++
 .../SSAFBuiltinForceLinker.h                  |   8 ++
 .../Analyses/CMakeLists.txt                   |   4 +-
 .../EntityPointerLevel/EntityPointerLevel.cpp |  26 ++++
 .../Analyses/PointerFlow/PointerFlow.cpp      |  95 +++++++------
 .../PointerFlow/PointerFlowAnalysis.cpp       | 116 ++++++++++++++++
 .../PointerFlow/PointerFlowExtractor.cpp      |   2 +-
 .../Analyses/SSAFAnalysesCommon.cpp           |  12 ++
 .../Analyses/SSAFAnalysesCommon.h             |  11 +-
 .../UnsafeBufferUsage/UnsafeBufferUsage.cpp   |  24 +---
 .../UnsafeBufferUsageAnalysis.cpp             | 121 +++++++++++++++++
 .../UnsafeBufferUsageExtractor.cpp            |   2 +-
 .../Inputs/wpa-result-bad-edges.json          |  30 ++++
 .../PointerFlow/Inputs/wpa-result-bad-id.json |  14 ++
 .../PointerFlow/Inputs/wpa-result-empty.json  |  11 ++
 .../PointerFlow/Inputs/wpa-result-no-key.json |  11 ++
 .../Inputs/wpa-result-odd-count.json          |  29 ++++
 .../PointerFlow/Inputs/wpa-result.json        | 128 ++++++++++++++++++
 .../PointerFlow/tu-summary-serialization.test |   7 +-
 .../PointerFlow/wpa-result-serialization.test |  32 +++++
 .../Inputs/wpa-result-bad-epls.json           |  30 ++++
 .../Inputs/wpa-result-bad-id.json             |  14 ++
 .../Inputs/wpa-result-empty.json              |  11 ++
 .../Inputs/wpa-result-no-key.json             |  11 ++
 .../Inputs/wpa-result-odd-count.json          |  29 ++++
 .../UnsafeBufferUsage/Inputs/wpa-result.json  |  86 ++++++++++++
 .../tu-summary-serialization.test             |  11 +-
 .../wpa-result-serialization.test             |  32 +++++
 31 files changed, 951 insertions(+), 80 deletions(-)
 create mode 100644 clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.h
 create mode 100644 clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowFormat.h
 create mode 100644 clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.h
 create mode 100644 clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.cpp
 create mode 100644 clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
 create mode 100644 clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-bad-edges.json
 create mode 100644 clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-bad-id.json
 create mode 100644 clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-empty.json
 create mode 100644 clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-no-key.json
 create mode 100644 clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-odd-count.json
 create mode 100644 clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result.json
 create mode 100644 clang/test/Analysis/Scalable/PointerFlow/wpa-result-serialization.test
 create mode 100644 clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-bad-epls.json
 create mode 100644 clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-bad-id.json
 create mode 100644 clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-empty.json
 create mode 100644 clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-no-key.json
 create mode 100644 clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-odd-count.json
 create mode 100644 clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result.json
 create mode 100644 clang/test/Analysis/Scalable/UnsafeBufferUsage/wpa-result-serialization.test

diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h
index 7b125edb1fb7f..13ecd880001e6 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h
@@ -11,6 +11,7 @@
 
 #include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
 #include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
+#include "llvm/ADT/iterator_range.h"
 
 namespace clang::ssaf {
 llvm::json::Value
@@ -20,6 +21,14 @@ entityPointerLevelToJSON(const EntityPointerLevel &EPL,
 Expected<EntityPointerLevel>
 entityPointerLevelFromJSON(const llvm::json::Value &EPLData,
                            JSONFormat::EntityIdFromJSONFn EntityIdFromJSON);
+
+llvm::json::Array entityPointerLevelSetToJSON(
+    llvm::iterator_range<EntityPointerLevelSet::const_iterator> EPLs,
+    JSONFormat::EntityIdToJSONFn EntityId2JSON);
+
+Expected<EntityPointerLevelSet>
+entityPointerLevelSetFromJSON(const llvm::json::Array &EPLsData,
+                              JSONFormat::EntityIdFromJSONFn EntityIdFromJSON);
 } // namespace clang::ssaf
 
 #endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_ENTITYPOINTERLEVEL_ENTITYPOINTERLEVELFORMAT_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.h
new file mode 100644
index 0000000000000..85694c779adf4
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.h
@@ -0,0 +1,40 @@
+//===- PointerFlowAnalysis.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Defines PointerFlowAnalysisResult, the whole-program analysis result type
+// for PointerFlowAnalysis.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_POINTERFLOW_POINTERFLOWANALYSIS_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_POINTERFLOW_POINTERFLOWANALYSIS_H
+
+#include "clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisResult.h"
+#include "llvm/ADT/StringRef.h"
+#include <map>
+
+namespace clang::ssaf {
+
+inline constexpr llvm::StringLiteral PointerFlowAnalysisResultName =
+    "PointerFlowAnalysisResult";
+
+struct PointerFlowAnalysisResult final : AnalysisResult {
+  static AnalysisName analysisName() {
+    return AnalysisName(PointerFlowAnalysisResultName.str());
+  }
+
+  /// Whole-program map from EntityIds to their EdgeSets.
+  std::map<EntityId, EdgeSet> Edges;
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_POINTERFLOW_POINTERFLOWANALYSIS_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowFormat.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowFormat.h
new file mode 100644
index 0000000000000..86bf1de4aaea2
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowFormat.h
@@ -0,0 +1,35 @@
+//===- PointerFlowFormat.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
+//
+//===----------------------------------------------------------------------===//
+//
+// JSON serialization helpers for EdgeSet (PointerFlow edge maps).
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_POINTERFLOW_POINTERFLOWFORMAT_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_POINTERFLOW_POINTERFLOWFORMAT_H
+
+#include "clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
+#include "llvm/ADT/iterator_range.h"
+
+namespace clang::ssaf {
+
+/// Serialize an EdgeSet as an array of arrays of EntityPointerLevels:
+///   [ [lhs, rhs, rhs, ...], [lhs, rhs, rhs, ...], ... ]
+llvm::json::Array
+edgeSetToJSON(llvm::iterator_range<EdgeSet::const_iterator> Edges,
+              JSONFormat::EntityIdToJSONFn IdToJSON);
+
+/// Deserialize an EdgeSet from the array format produced by `edgeSetToJSON`.
+llvm::Expected<EdgeSet>
+edgeSetFromJSON(const llvm::json::Array &EdgesData,
+                JSONFormat::EntityIdFromJSONFn IdFromJSON);
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_POINTERFLOW_POINTERFLOWFORMAT_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.h
new file mode 100644
index 0000000000000..aba566498b44b
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.h
@@ -0,0 +1,40 @@
+//===- UnsafeBufferUsageAnalysis.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Defines UnsafeBufferUsageAnalysisResult, the whole-program analysis result
+// type for UnsafeBufferUsageAnalysis.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_UNSAFEBUFFERUSAGE_UNSAFEBUFFERUSAGEANALYSIS_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_UNSAFEBUFFERUSAGE_UNSAFEBUFFERUSAGEANALYSIS_H
+
+#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisResult.h"
+#include "llvm/ADT/StringRef.h"
+#include <map>
+
+namespace clang::ssaf {
+
+inline constexpr llvm::StringLiteral UnsafeBufferUsageAnalysisResultName =
+    "UnsafeBufferUsageAnalysisResult";
+
+struct UnsafeBufferUsageAnalysisResult final : AnalysisResult {
+  static AnalysisName analysisName() {
+    return AnalysisName(UnsafeBufferUsageAnalysisResultName.str());
+  }
+
+  /// Whole-program set of unsafe buffer pointers:
+  std::map<EntityId, EntityPointerLevelSet> UnsafeBuffers;
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_UNSAFEBUFFERUSAGE_UNSAFEBUFFERUSAGEANALYSIS_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h b/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
index 8419ad23619f7..26b1fe4a47840 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
@@ -45,6 +45,10 @@ extern volatile int UnsafeBufferUsageTUSummaryExtractorAnchorSource;
     UnsafeBufferUsageTUSummaryExtractorAnchorDestination =
         UnsafeBufferUsageTUSummaryExtractorAnchorSource;
 
+extern volatile int UnsafeBufferUsageAnalysisAnchorSource;
+[[maybe_unused]] static int UnsafeBufferUsageAnalysisAnchorDestination =
+    UnsafeBufferUsageAnalysisAnchorSource;
+
 // This anchor is used to force the linker to link the PointerFlow
 // JSONFormat registration:
 extern volatile int PointerFlowSSAFJSONFormatAnchorSource;
@@ -57,6 +61,10 @@ extern volatile int PointerFlowTUSummaryExtractorAnchorSource;
 [[maybe_unused]] static int PointerFlowTUSummaryExtractorAnchorDestination =
     PointerFlowTUSummaryExtractorAnchorSource;
 
+extern volatile int PointerFlowAnalysisAnchorSource;
+[[maybe_unused]] static int PointerFlowAnalysisAnchorDestination =
+    PointerFlowAnalysisAnchorSource;
+
 // 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 798d06a4c9107..5f898ae10dc26 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
@@ -7,9 +7,11 @@ add_clang_library(clangScalableStaticAnalysisFrameworkAnalyses
   CallGraph/CallGraphJSONFormat.cpp
   EntityPointerLevel/EntityPointerLevel.cpp
   PointerFlow/PointerFlow.cpp
-  PointerFlow/PointerFlowExtractor.cpp  
+  PointerFlow/PointerFlowAnalysis.cpp
+  PointerFlow/PointerFlowExtractor.cpp
   SSAFAnalysesCommon.cpp
   UnsafeBufferUsage/UnsafeBufferUsage.cpp
+  UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
   UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
 
   LINK_LIBS
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
index 044f02ee25a24..fb47fd76241f0 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
@@ -302,3 +302,29 @@ Expected<EntityPointerLevel> clang::ssaf::entityPointerLevelFromJSON(
 
   return buildEntityPointerLevel(*Id, *PtrLv);
 }
+
+llvm::json::Array clang::ssaf::entityPointerLevelSetToJSON(
+    llvm::iterator_range<EntityPointerLevelSet::const_iterator> EPLs,
+    JSONFormat::EntityIdToJSONFn EntityId2JSON) {
+  llvm::json::Array Result;
+
+  for (const auto &EPL : EPLs)
+    Result.push_back(entityPointerLevelToJSON(EPL, EntityId2JSON));
+  return Result;
+}
+
+Expected<EntityPointerLevelSet> clang::ssaf::entityPointerLevelSetFromJSON(
+    const llvm::json::Array &EPLsData,
+    JSONFormat::EntityIdFromJSONFn EntityIdFromJSON) {
+  EntityPointerLevelSet EPLs;
+
+  for (const auto &EltData : EPLsData) {
+    llvm::Expected<EntityPointerLevel> EPL =
+        entityPointerLevelFromJSON(EltData, EntityIdFromJSON);
+
+    if (!EPL)
+      return EPL.takeError();
+    EPLs.insert(*EPL);
+  }
+  return EPLs;
+}
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.cpp
index dc347f4dc4086..ce45f4167016f 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.cpp
@@ -8,10 +8,9 @@
 
 #include "clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h"
 #include "SSAFAnalysesCommon.h"
-#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
 #include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowFormat.h"
 #include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
-#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/iterator_range.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/JSON.h"
@@ -36,51 +35,27 @@ ssaf::getEdges(const PointerFlowEntitySummary &Sum) {
   return Sum.Edges;
 }
 
-// Writes the 'Edges' map as an array of array of EntityPointerLevels:
-// Array [
-//    Array [ [lhs-node], [rhs-node], [rhs-node], ...]
-//    Array [ [lhs-node], [rhs-node], [rhs-node], ...]
-//    ...
-// ]
-static llvm::json::Object
-summaryToJSON(const EntitySummary &ES,
-              JSONFormat::EntityIdToJSONFn EntityId2JSON) {
+Array ssaf::edgeSetToJSON(
+    llvm::iterator_range<EdgeSet::const_iterator> Edges,
+    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)));
+  for (const auto &[LHS, RHSSet] : Edges) {
+    Array EdgeEntry;
+    EdgeEntry.push_back(entityPointerLevelToJSON(LHS, EntityId2JSON));
+    for (const auto &RHS : RHSSet)
+      EdgeEntry.push_back(entityPointerLevelToJSON(RHS, EntityId2JSON));
+    EdgesData.push_back(Value(std::move(EdgeEntry)));
   }
-
-  Object Data;
-
-  Data[PointerFlowKey] = Value(std::move(EdgesData));
-  return Data;
+  return 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);
-
+llvm::Expected<EdgeSet>
+ssaf::edgeSetFromJSON(const Array &EdgesData,
+                      JSONFormat::EntityIdFromJSONFn EntityIdFromJSON) {
   EdgeSet Edges;
-  const auto *EdgesDataAsArr = EdgesData->getAsArray();
 
-  if (!EdgesDataAsArr)
-    return makeSawButExpectedError(
-        *EdgesData, "a JSON array of array of EntityPointerLevels");
-  for (const auto &EdgesEntryData : *EdgesDataAsArr) {
+  for (const auto &EdgesEntryData : EdgesData) {
     const auto *EPLArray = EdgesEntryData.getAsArray();
 
     if (!EPLArray || EPLArray->size() <= 1)
@@ -101,8 +76,46 @@ summaryFromJSON(const Object &Data, EntityIdTable &,
       Edges[*SrcEPL].insert(*EPL);
     }
   }
+  return Edges;
+}
+
+// Writes the 'Edges' map as an array of array of EntityPointerLevels:
+// Array [
+//    Array [ [lhs-node], [rhs-node], [rhs-node], ...]
+//    Array [ [lhs-node], [rhs-node], [rhs-node], ...]
+//    ...
+// ]
+static llvm::json::Object
+summaryToJSON(const EntitySummary &ES,
+              JSONFormat::EntityIdToJSONFn EntityId2JSON) {
+  Object Data;
+  Data[PointerFlowKey] = Value(
+      edgeSetToJSON(getEdges(static_cast<const PointerFlowEntitySummary &>(ES)),
+                    EntityId2JSON));
+  return Data;
+}
+
+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(Data, "a JSON object with the key: %s",
+                                   PointerFlowKey);
+
+  const auto *EdgesDataAsArr = EdgesData->getAsArray();
+
+  if (!EdgesDataAsArr)
+    return makeSawButExpectedError(
+        *EdgesData, "a JSON array of array of EntityPointerLevels");
+
+  auto Edges = edgeSetFromJSON(*EdgesDataAsArr, EntityIdFromJSON);
+
+  if (!Edges)
+    return Edges.takeError();
   return std::make_unique<PointerFlowEntitySummary>(
-      buildPointerFlowEntitySummary(std::move(Edges)));
+      buildPointerFlowEntitySummary(std::move(*Edges)));
 }
 
 struct PointerFlowJSONFormatInfo : JSONFormat::FormatInfo {
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.cpp
new file mode 100644
index 0000000000000..c6f2f6ddc7288
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.cpp
@@ -0,0 +1,116 @@
+//===- PointerFlowAnalysis.cpp - WPA for PointerFlow ----------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+// PointerFlowAnalysis is a noop analysis.
+//
+// PointerFlowAnalysisResult is a map from EntityIds to
+// EdgeSets.
+//===----------------------------------------------------------------------===//
+
+#include "SSAFAnalysesCommon.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowFormat.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/SummaryAnalysis.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
+#include <memory>
+
+using namespace clang::ssaf;
+using namespace llvm;
+
+namespace {
+
+// Serialized as a flat array of alternating [EntityId, EdgesArray, ...] pairs.
+json::Object
+serializePointerFlowAnalysisResult(const PointerFlowAnalysisResult &R,
+                                   JSONFormat::EntityIdToJSONFn IdToJSON) {
+  json::Array Content;
+
+  for (const auto &[Id, EntityEdges] : R.Edges) {
+    Content.push_back(IdToJSON(Id));
+    Content.push_back(json::Value(edgeSetToJSON(EntityEdges, IdToJSON)));
+  }
+
+  json::Object Result;
+
+  Result[PointerFlowAnalysisResultName] = std::move(Content);
+  return Result;
+}
+
+Expected<std::unique_ptr<AnalysisResult>> deserializePointerFlowAnalysisResult(
+    const json::Object &Obj, JSONFormat::EntityIdFromJSONFn IdFromJSON) {
+  const json::Array *Content = Obj.getArray(PointerFlowAnalysisResultName);
+
+  if (!Content)
+    return makeSawButExpectedError(Obj, "an object with a key %s",
+                                   PointerFlowAnalysisResultName.data());
+
+  if (Content->size() % 2 != 0)
+    return makeSawButExpectedError(
+        *Content, "an even number of elements, got %lu",
+        (unsigned long)Content->size());
+
+  std::map<EntityId, EdgeSet> Edges;
+
+  for (size_t I = 0; I < Content->size(); I += 2) {
+    const json::Object *IdData = (*Content)[I].getAsObject();
+
+    if (!IdData)
+      return makeSawButExpectedError((*Content)[I],
+                                     "an object representing EntityId");
+
+    auto Id = IdFromJSON(*IdData);
+
+    if (!Id)
+      return Id.takeError();
+
+    const json::Array *EdgesData = (*Content)[I + 1].getAsArray();
+
+    if (!EdgesData)
+      return makeSawButExpectedError((*Content)[I + 1],
+                                     "an array of arrays representing EdgeSet");
+
+    auto EntityEdges = edgeSetFromJSON(*EdgesData, IdFromJSON);
+
+    if (!EntityEdges)
+      return EntityEdges.takeError();
+    Edges[*Id] = std::move(*EntityEdges);
+  }
+
+  auto Ret = std::make_unique<PointerFlowAnalysisResult>();
+
+  Ret->Edges = std::move(Edges);
+  return Ret;
+}
+
+JSONFormat::AnalysisResultRegistry::Add<PointerFlowAnalysisResult>
+    RegisterPointerFlowResultForJSON(serializePointerFlowAnalysisResult,
+                                     deserializePointerFlowAnalysisResult);
+
+class PointerFlowAnalysis final
+    : public SummaryAnalysis<PointerFlowAnalysisResult,
+                             PointerFlowEntitySummary> {
+public:
+  llvm::Error add(EntityId Id,
+                  const PointerFlowEntitySummary &Summary) override {
+    auto EdgesOfEntity = getEdges(Summary);
+
+    result().Edges[Id] = EdgeSet(EdgesOfEntity.begin(), EdgesOfEntity.end());
+    return llvm::Error::success();
+  }
+};
+
+AnalysisRegistry::Add<PointerFlowAnalysis>
+    RegisterPointerFlowAnalysis("Whole-program pointer flow analysis");
+
+} // namespace
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int PointerFlowAnalysisAnchorSource = 0;
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowExtractor.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowExtractor.cpp
index 8b892c266281c..40c4bc9ed3677 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowExtractor.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowExtractor.cpp
@@ -326,4 +326,4 @@ volatile int PointerFlowTUSummaryExtractorAnchorSource = 0;
 
 static TUSummaryExtractorRegistry::Add<PointerFlowTUSummaryExtractor>
     RegisterExtractor(PointerFlowEntitySummary::Name,
-                      "The TUSummaryExtractor for pointer assignments");
+                      "Extract pointer flow information");
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.cpp
index 774e7e94ac67a..cbd8ac92c5eaf 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.cpp
@@ -10,6 +10,18 @@
 
 using namespace clang;
 
+std::string describeJSONValue(const llvm::json::Value &V) {
+  return llvm::formatv("{0:2}", V).str();
+}
+
+std::string describeJSONValue(const llvm::json::Array &A) {
+  return llvm::formatv("array of size {0}", A.size()).str();
+}
+
+std::string describeJSONValue(const llvm::json::Object &O) {
+  return llvm::formatv("an object of {0} key(s)", O.size()).str();
+}
+
 namespace {
 // Traverses the AST and finds contributors.
 class ContributorFinder : public DynamicRecursiveASTVisitor {
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.h b/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.h
index e6759c1fb6e39..c798789fd12a8 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.h
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.h
@@ -16,6 +16,10 @@
 #include "clang/AST/Decl.h"
 #include "llvm/Support/JSON.h"
 
+std::string describeJSONValue(const llvm::json::Value &V);
+std::string describeJSONValue(const llvm::json::Array &A);
+std::string describeJSONValue(const llvm::json::Object &O);
+
 template <typename NodeTy, typename... Ts>
 llvm::Error makeErrAtNode(clang::ASTContext &Ctx, const NodeTy *N,
                           llvm::StringRef Fmt, const Ts &...Args) {
@@ -24,12 +28,11 @@ llvm::Error makeErrAtNode(clang::ASTContext &Ctx, const NodeTy *N,
                                  LocStr.c_str());
 }
 
-template <typename... Ts>
-llvm::Error makeSawButExpectedError(const llvm::json::Value &Saw,
-                                    llvm::StringRef Expected,
+template <typename JSONTy, typename... Ts>
+llvm::Error makeSawButExpectedError(const JSONTy &Saw, llvm::StringRef Expected,
                                     const Ts &...ExpectedArgs) {
   std::string Fmt = ("saw %s but expected " + Expected).str();
-  std::string SawStr = llvm::formatv("{0:2}", Saw).str();
+  std::string SawStr = describeJSONValue(Saw);
 
   return llvm::createStringError(Fmt.c_str(), SawStr.c_str(), ExpectedArgs...);
 }
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.cpp
index ea5d2297b9836..e884484f9c729 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.cpp
@@ -36,11 +36,8 @@ 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)}};
+  return Object{{SummarySerializationKey.data(),
+                 entityPointerLevelSetToJSON(getUnsafeBuffers(SS), Fn)}};
 }
 
 static llvm::Expected<std::unique_ptr<EntitySummary>>
@@ -49,21 +46,14 @@ deserializeImpl(const Object &Data, JSONFormat::EntityIdFromJSONFn Fn) {
       Data.getArray(SummarySerializationKey.data());
 
   if (!UnsafeBuffersData)
-    return makeSawButExpectedError(Object(Data), "an Object with a key %s",
+    return makeSawButExpectedError(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);
-  }
+  auto EPLs = entityPointerLevelSetFromJSON(*UnsafeBuffersData, Fn);
+  if (!EPLs)
+    return EPLs.takeError();
   return std::make_unique<UnsafeBufferUsageEntitySummary>(
-      buildUnsafeBufferUsageEntitySummary(std::move(EPLs)));
+      buildUnsafeBufferUsageEntitySummary(std::move(*EPLs)));
 }
 
 static llvm::Expected<std::unique_ptr<EntitySummary>>
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
new file mode 100644
index 0000000000000..b856074ce988c
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
@@ -0,0 +1,121 @@
+//===- UnsafeBufferUsageAnalysis.cpp - WPA for UnsafeBufferUsage ----------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+// UnsafeBufferUsageAnalysis is a noop analysis.
+//
+// UnsafeBufferUsageAnalysisResult is a map from EntityIds to
+// EntityPointerLevelSets
+//===----------------------------------------------------------------------===//
+
+#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/UnsafeBufferUsageAnalysis.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/SummaryAnalysis.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
+#include <memory>
+
+using namespace clang::ssaf;
+using namespace llvm;
+
+namespace {
+
+json::Object serializeUnsafeBufferUsageAnalysisResult(
+    const UnsafeBufferUsageAnalysisResult &R,
+    JSONFormat::EntityIdToJSONFn IdToJSON) {
+  json::Array Content;
+
+  // Flat key-value pairs into an array of values:
+  for (auto &[Id, EPLs] : R.UnsafeBuffers) {
+    Content.push_back(IdToJSON(Id));
+    Content.push_back(entityPointerLevelSetToJSON(EPLs, IdToJSON));
+  }
+
+  json::Object Result;
+
+  Result[UnsafeBufferUsageAnalysisResultName] = std::move(Content);
+  return Result;
+}
+
+Expected<std::unique_ptr<AnalysisResult>>
+deserializeUnsafeBufferUsageAnalysisResult(
+    const json::Object &Obj, JSONFormat::EntityIdFromJSONFn IdFromJSON) {
+  const json::Array *Content = Obj.getArray(UnsafeBufferUsageAnalysisResultName);
+
+  if (!Content)
+    return makeSawButExpectedError(Obj, "an object with a key %s",
+                                   UnsafeBufferUsageAnalysisResultName.data());
+
+  if (Content->size() % 2 != 0)
+    return makeSawButExpectedError(
+        *Content, "an even number of elements, got %lu",
+        (unsigned long)Content->size());
+
+  std::map<EntityId, EntityPointerLevelSet> UnsafeBuffers;
+
+  for (size_t I = 0; I < Content->size(); I += 2) {
+    const json::Object *IdData = (*Content)[I].getAsObject();
+
+    if (!IdData)
+      return makeSawButExpectedError((*Content)[I],
+                                     "an object representing EntityId");
+
+    auto Id = IdFromJSON(*IdData);
+
+    if (!Id)
+      return Id.takeError();
+
+    const json::Array *EPLsData = (*Content)[I + 1].getAsArray();
+
+    if (!EPLsData)
+      return makeSawButExpectedError(
+          (*Content)[I + 1], "an array representing EntityPointerLevelSet");
+
+    auto EPLs = entityPointerLevelSetFromJSON(*EPLsData, IdFromJSON);
+
+    if (!EPLs)
+      return EPLs.takeError();
+    UnsafeBuffers[*Id] = std::move(*EPLs);
+  }
+
+  auto Ret = std::make_unique<UnsafeBufferUsageAnalysisResult>();
+
+  Ret->UnsafeBuffers = std::move(UnsafeBuffers);
+  return Ret;
+}
+
+JSONFormat::AnalysisResultRegistry::Add<UnsafeBufferUsageAnalysisResult>
+    RegisterUnsafeBufferUsageResultForJSON(
+        serializeUnsafeBufferUsageAnalysisResult,
+        deserializeUnsafeBufferUsageAnalysisResult);
+
+class UnsafeBufferUsageAnalysis final
+    : public SummaryAnalysis<UnsafeBufferUsageAnalysisResult,
+                             UnsafeBufferUsageEntitySummary> {
+public:
+  llvm::Error add(EntityId Id,
+                  const UnsafeBufferUsageEntitySummary &Summary) override {
+    auto UnsafeBuffersOfEntity = getUnsafeBuffers(Summary);
+
+    result().UnsafeBuffers[Id] = EntityPointerLevelSet(
+        UnsafeBuffersOfEntity.begin(), UnsafeBuffersOfEntity.end());
+    return llvm::Error::success();
+  }
+};
+
+AnalysisRegistry::Add<UnsafeBufferUsageAnalysis>
+    RegisterUnsafeBufferUsageAnalysis(
+        "Whole-program unsafe buffer usage analysis");
+
+} // namespace
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int UnsafeBufferUsageAnalysisAnchorSource = 0;
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
index 78996bea040d8..9d1ce7589384e 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
@@ -104,4 +104,4 @@ volatile int UnsafeBufferUsageTUSummaryExtractorAnchorSource = 0;
 static clang::ssaf::TUSummaryExtractorRegistry::Add<
     UnsafeBufferUsageTUSummaryExtractor>
     RegisterExtractor(UnsafeBufferUsageEntitySummary::Name,
-                      "The TUSummaryExtractor for unsafe buffer pointers");
+                      "Extract unsafe buffer pointers");
diff --git a/clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-bad-edges.json b/clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-bad-edges.json
new file mode 100644
index 0000000000000..cd9084d5a69bd
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-bad-edges.json
@@ -0,0 +1,30 @@
+{
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "namespace": [
+          {
+            "kind": "LinkUnit",
+            "name": "test.exe"
+          }
+        ],
+        "suffix": "",
+        "usr": "c:@F at foo#"
+      }
+    }
+  ],
+  "results": [
+    {
+      "analysis_name": "PointerFlowAnalysisResult",
+      "result": {
+        "PointerFlowAnalysisResult": [
+          {
+            "@": 0
+          },
+          "not-an-array"
+        ]
+      }
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-bad-id.json b/clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-bad-id.json
new file mode 100644
index 0000000000000..08102111e224d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-bad-id.json
@@ -0,0 +1,14 @@
+{
+  "id_table": [],
+  "results": [
+    {
+      "analysis_name": "PointerFlowAnalysisResult",
+      "result": {
+        "PointerFlowAnalysisResult": [
+          "not-an-object",
+          []
+        ]
+      }
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-empty.json b/clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-empty.json
new file mode 100644
index 0000000000000..2eed81bc8e990
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-empty.json
@@ -0,0 +1,11 @@
+{
+  "id_table": [],
+  "results": [
+    {
+      "analysis_name": "PointerFlowAnalysisResult",
+      "result": {
+        "PointerFlowAnalysisResult": []
+      }
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-no-key.json b/clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-no-key.json
new file mode 100644
index 0000000000000..32bda55463253
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-no-key.json
@@ -0,0 +1,11 @@
+{
+  "id_table": [],
+  "results": [
+    {
+      "analysis_name": "PointerFlowAnalysisResult",
+      "result": {
+        "WrongKey": []
+      }
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-odd-count.json b/clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-odd-count.json
new file mode 100644
index 0000000000000..32c36f549509a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result-odd-count.json
@@ -0,0 +1,29 @@
+{
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "namespace": [
+          {
+            "kind": "LinkUnit",
+            "name": "test.exe"
+          }
+        ],
+        "suffix": "",
+        "usr": "c:@F at foo#"
+      }
+    }
+  ],
+  "results": [
+    {
+      "analysis_name": "PointerFlowAnalysisResult",
+      "result": {
+        "PointerFlowAnalysisResult": [
+          {
+            "@": 0
+          }
+        ]
+      }
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result.json b/clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result.json
new file mode 100644
index 0000000000000..bd5b215d9ad9a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/Inputs/wpa-result.json
@@ -0,0 +1,128 @@
+{
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "namespace": [
+          {
+            "kind": "LinkUnit",
+            "name": "test.exe"
+          }
+        ],
+        "suffix": "",
+        "usr": "c:@F at foo#"
+      }
+    },
+    {
+      "id": 1,
+      "name": {
+        "namespace": [
+          {
+            "kind": "LinkUnit",
+            "name": "test.exe"
+          }
+        ],
+        "suffix": "1",
+        "usr": "c:@F at foo#"
+      }
+    },
+    {
+      "id": 2,
+      "name": {
+        "namespace": [
+          {
+            "kind": "LinkUnit",
+            "name": "test.exe"
+          }
+        ],
+        "suffix": "2",
+        "usr": "c:@F at foo#"
+      }
+    }
+  ],
+  "results": [
+    {
+      "analysis_name": "PointerFlowAnalysisResult",
+      "result": {
+        "PointerFlowAnalysisResult": [
+          {
+            "@": 0
+          },
+          [
+            [
+              [
+                {
+                  "@": 1
+                },
+                1
+              ],
+              [
+                {
+                  "@": 0
+                },
+                2
+              ],
+              [
+                {
+                  "@": 2
+                },
+                1
+              ],
+              [
+                {
+                  "@": 2
+                },
+                2
+              ]
+            ],
+            [
+              [
+                {
+                  "@": 2
+                },
+                1
+              ],
+              [
+                {
+                  "@": 1
+                },
+                2
+              ]
+            ]
+          ],
+          {
+            "@": 1
+          },
+          [
+            [
+              [
+                {
+                  "@": 0
+                },
+                1
+              ],
+              [
+                {
+                  "@": 1
+                },
+                1
+              ],
+              [
+                {
+                  "@": 2
+                },
+                1
+              ],
+              [
+                {
+                  "@": 2
+                },
+                3
+              ]
+            ]
+          ]
+        ]
+      }
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/PointerFlow/tu-summary-serialization.test b/clang/test/Analysis/Scalable/PointerFlow/tu-summary-serialization.test
index c36e740350d45..34d38bb7a6200 100644
--- a/clang/test/Analysis/Scalable/PointerFlow/tu-summary-serialization.test
+++ b/clang/test/Analysis/Scalable/PointerFlow/tu-summary-serialization.test
@@ -5,11 +5,8 @@
 // Negative tests:
 
 // RUN: not clang-ssaf-format -type=tu %S/Inputs/tu-summary-no-key.json 2>&1 \
-// RUN: | FileCheck %s --check-prefix=CHECK-NO-KEY
-// CHECK-NO-KEY: saw {
-// CHECK-NO-KEY:   "NoPointerFlow": [
-// CHECK-NO-KEY:     [
-// CHECK-NO-KEY: } but expected a JSON object with the key: PointerFlow
+// RUN: | FileCheck %s --check-prefix=CHECK-MISSING-KEY
+// CHECK-MISSING-KEY: saw an object of 1 key(s) 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
diff --git a/clang/test/Analysis/Scalable/PointerFlow/wpa-result-serialization.test b/clang/test/Analysis/Scalable/PointerFlow/wpa-result-serialization.test
new file mode 100644
index 0000000000000..fec997b3a173e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/wpa-result-serialization.test
@@ -0,0 +1,32 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// Round-trip tests:
+
+// RUN: clang-ssaf-format --type wpa %S/Inputs/wpa-result.json -o %t/wpa-result.json
+// RUN: diff %S/Inputs/wpa-result.json %t/wpa-result.json
+
+// RUN: clang-ssaf-format --type wpa %S/Inputs/wpa-result-empty.json -o %t/wpa-result-empty.json
+// RUN: diff %S/Inputs/wpa-result-empty.json %t/wpa-result-empty.json
+
+// Negative tests:
+
+// Missing the PointerFlowAnalysisResult key in the result object.
+// RUN: not clang-ssaf-format --type wpa %S/Inputs/wpa-result-no-key.json 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=CHECK-MISSING-KEY
+// CHECK-MISSING-KEY: saw an object of 1 key(s) but expected an object with a key PointerFlowAnalysisResult
+
+// Odd number of elements in the flat array.
+// RUN: not clang-ssaf-format --type wpa %S/Inputs/wpa-result-odd-count.json 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=CHECK-ODD-COUNT
+// CHECK-ODD-COUNT: saw array of size 1 but expected an even number of elements, got 1
+
+// EntityId slot is not an object.
+// RUN: not clang-ssaf-format --type wpa %S/Inputs/wpa-result-bad-id.json 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=CHECK-BAD-ID
+// CHECK-BAD-ID: saw "not-an-object" but expected an object representing EntityId
+
+// EdgeSet slot is not an array.
+// RUN: not clang-ssaf-format --type wpa %S/Inputs/wpa-result-bad-edges.json 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=CHECK-BAD-EDGES
+// CHECK-BAD-EDGES: saw "not-an-array" but expected an array of arrays representing EdgeSet
diff --git a/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-bad-epls.json b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-bad-epls.json
new file mode 100644
index 0000000000000..9f2be6121ff05
--- /dev/null
+++ b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-bad-epls.json
@@ -0,0 +1,30 @@
+{
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "namespace": [
+          {
+            "kind": "LinkUnit",
+            "name": "test.exe"
+          }
+        ],
+        "suffix": "",
+        "usr": "c:@F at foo#"
+      }
+    }
+  ],
+  "results": [
+    {
+      "analysis_name": "UnsafeBufferUsageAnalysisResult",
+      "result": {
+        "UnsafeBufferUsageAnalysisResult": [
+          {
+            "@": 0
+          },
+          "not-an-array"
+        ]
+      }
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-bad-id.json b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-bad-id.json
new file mode 100644
index 0000000000000..58f1fabc2d16a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-bad-id.json
@@ -0,0 +1,14 @@
+{
+  "id_table": [],
+  "results": [
+    {
+      "analysis_name": "UnsafeBufferUsageAnalysisResult",
+      "result": {
+        "UnsafeBufferUsageAnalysisResult": [
+          "not-an-object",
+          []
+        ]
+      }
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-empty.json b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-empty.json
new file mode 100644
index 0000000000000..89a58332cfd17
--- /dev/null
+++ b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-empty.json
@@ -0,0 +1,11 @@
+{
+  "id_table": [],
+  "results": [
+    {
+      "analysis_name": "UnsafeBufferUsageAnalysisResult",
+      "result": {
+        "UnsafeBufferUsageAnalysisResult": []
+      }
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-no-key.json b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-no-key.json
new file mode 100644
index 0000000000000..7d68ef8998871
--- /dev/null
+++ b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-no-key.json
@@ -0,0 +1,11 @@
+{
+  "id_table": [],
+  "results": [
+    {
+      "analysis_name": "UnsafeBufferUsageAnalysisResult",
+      "result": {
+        "WrongKey": []
+      }
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-odd-count.json b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-odd-count.json
new file mode 100644
index 0000000000000..12725357b5a30
--- /dev/null
+++ b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result-odd-count.json
@@ -0,0 +1,29 @@
+{
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "namespace": [
+          {
+            "kind": "LinkUnit",
+            "name": "test.exe"
+          }
+        ],
+        "suffix": "",
+        "usr": "c:@F at foo#"
+      }
+    }
+  ],
+  "results": [
+    {
+      "analysis_name": "UnsafeBufferUsageAnalysisResult",
+      "result": {
+        "UnsafeBufferUsageAnalysisResult": [
+          {
+            "@": 0
+          }
+        ]
+      }
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result.json b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result.json
new file mode 100644
index 0000000000000..a5b8c1cc9df7b
--- /dev/null
+++ b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/wpa-result.json
@@ -0,0 +1,86 @@
+{
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "namespace": [
+          {
+            "kind": "LinkUnit",
+            "name": "test.exe"
+          }
+        ],
+        "suffix": "",
+        "usr": "c:@F at foo#"
+      }
+    },
+    {
+      "id": 1,
+      "name": {
+        "namespace": [
+          {
+            "kind": "LinkUnit",
+            "name": "test.exe"
+          }
+        ],
+        "suffix": "1",
+        "usr": "c:@F at foo#"
+      }
+    },
+    {
+      "id": 2,
+      "name": {
+        "namespace": [
+          {
+            "kind": "LinkUnit",
+            "name": "test.exe"
+          }
+        ],
+        "suffix": "2",
+        "usr": "c:@F at foo#"
+      }
+    }
+  ],
+  "results": [
+    {
+      "analysis_name": "UnsafeBufferUsageAnalysisResult",
+      "result": {
+        "UnsafeBufferUsageAnalysisResult": [
+          {
+            "@": 0
+          },
+          [
+            [
+              {
+                "@": 1
+              },
+              1
+            ],
+            [
+              {
+                "@": 2
+              },
+              2
+            ]
+          ],
+          {
+            "@": 1
+          },
+          [
+            [
+              {
+                "@": 0
+              },
+              1
+            ],
+            [
+              {
+                "@": 0
+              },
+              3
+            ]
+          ]
+        ]
+      }
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/UnsafeBufferUsage/tu-summary-serialization.test b/clang/test/Analysis/Scalable/UnsafeBufferUsage/tu-summary-serialization.test
index ff13aa7f8ca27..7eced926e3e56 100644
--- a/clang/test/Analysis/Scalable/UnsafeBufferUsage/tu-summary-serialization.test
+++ b/clang/test/Analysis/Scalable/UnsafeBufferUsage/tu-summary-serialization.test
@@ -6,16 +6,7 @@
 
 // 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-NEXT:   "NotUnsafeBuffers": [
-// CHECK-MISSING-KEY-NEXT:     [
-// CHECK-MISSING-KEY-NEXT:       {
-// CHECK-MISSING-KEY-NEXT:         "@": 0
-// CHECK-MISSING-KEY-NEXT:       },
-// CHECK-MISSING-KEY-NEXT:       1
-// CHECK-MISSING-KEY-NEXT:     ]
-// CHECK-MISSING-KEY-NEXT:   ]
-// CHECK-MISSING-KEY-NEXT: } but expected an Object with a key UnsafeBuffers
+// CHECK-MISSING-KEY: saw an object of 1 key(s) but expected an Object with a key UnsafeBuffers
 
 // RUN: not clang-ssaf-format -type=tu %S/Inputs/tu-summary-bad-element.json 2>&1 \
 // RUN: | FileCheck %s --check-prefix=CHECK-BAD-ELEMENT
diff --git a/clang/test/Analysis/Scalable/UnsafeBufferUsage/wpa-result-serialization.test b/clang/test/Analysis/Scalable/UnsafeBufferUsage/wpa-result-serialization.test
new file mode 100644
index 0000000000000..ce60aa8b50ee2
--- /dev/null
+++ b/clang/test/Analysis/Scalable/UnsafeBufferUsage/wpa-result-serialization.test
@@ -0,0 +1,32 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// Round-trip tests:
+
+// RUN: clang-ssaf-format --type wpa %S/Inputs/wpa-result.json -o %t/wpa-result.json
+// RUN: diff %S/Inputs/wpa-result.json %t/wpa-result.json
+
+// RUN: clang-ssaf-format --type wpa %S/Inputs/wpa-result-empty.json -o %t/wpa-result-empty.json
+// RUN: diff %S/Inputs/wpa-result-empty.json %t/wpa-result-empty.json
+
+// Negative tests:
+
+// Missing the UnsafeBufferUsageAnalysisResult key in the result object.
+// RUN: not clang-ssaf-format --type wpa %S/Inputs/wpa-result-no-key.json 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=CHECK-MISSING-KEY
+// CHECK-MISSING-KEY: saw an object of 1 key(s) but expected an object with a key UnsafeBufferUsageAnalysisResult
+
+// Odd number of elements in the flat array.
+// RUN: not clang-ssaf-format --type wpa %S/Inputs/wpa-result-odd-count.json 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=CHECK-ODD-COUNT
+// CHECK-ODD-COUNT: saw array of size 1 but expected an even number of elements, got 1
+
+// EntityId slot is not an object.
+// RUN: not clang-ssaf-format --type wpa %S/Inputs/wpa-result-bad-id.json 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=CHECK-BAD-ID
+// CHECK-BAD-ID: saw "not-an-object" but expected an object representing EntityId
+
+// EntityPointerLevelSet slot is not an array.
+// RUN: not clang-ssaf-format --type wpa %S/Inputs/wpa-result-bad-epls.json 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=CHECK-BAD-EPLS
+// CHECK-BAD-EPLS: saw "not-an-array" but expected an array representing EntityPointerLevelSet

>From a07fb84d47f79a0e015ea66ce103519143fdd6ff Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Wed, 22 Apr 2026 11:45:13 -0700
Subject: [PATCH 4/5] fix clang-format

---
 .../UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp   | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
index b856074ce988c..1f367720de30f 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
@@ -11,11 +11,11 @@
 // EntityPointerLevelSets
 //===----------------------------------------------------------------------===//
 
+#include "clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.h"
 #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/UnsafeBufferUsageAnalysis.h"
 #include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
 #include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.h"
 #include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/SummaryAnalysis.h"
@@ -48,16 +48,17 @@ json::Object serializeUnsafeBufferUsageAnalysisResult(
 Expected<std::unique_ptr<AnalysisResult>>
 deserializeUnsafeBufferUsageAnalysisResult(
     const json::Object &Obj, JSONFormat::EntityIdFromJSONFn IdFromJSON) {
-  const json::Array *Content = Obj.getArray(UnsafeBufferUsageAnalysisResultName);
+  const json::Array *Content =
+      Obj.getArray(UnsafeBufferUsageAnalysisResultName);
 
   if (!Content)
     return makeSawButExpectedError(Obj, "an object with a key %s",
                                    UnsafeBufferUsageAnalysisResultName.data());
 
   if (Content->size() % 2 != 0)
-    return makeSawButExpectedError(
-        *Content, "an even number of elements, got %lu",
-        (unsigned long)Content->size());
+    return makeSawButExpectedError(*Content,
+                                   "an even number of elements, got %lu",
+                                   (unsigned long)Content->size());
 
   std::map<EntityId, EntityPointerLevelSet> UnsafeBuffers;
 

>From 54759df3236809bfc97e3fb727414439ed2eb885 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Wed, 22 Apr 2026 11:52:03 -0700
Subject: [PATCH 5/5] Change 'result()' to 'getResult()'

---
 .../Analyses/PointerFlow/PointerFlowAnalysis.cpp                | 2 +-
 .../Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp    | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.cpp
index c6f2f6ddc7288..82061b49b1929 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.cpp
@@ -102,7 +102,7 @@ class PointerFlowAnalysis final
                   const PointerFlowEntitySummary &Summary) override {
     auto EdgesOfEntity = getEdges(Summary);
 
-    result().Edges[Id] = EdgeSet(EdgesOfEntity.begin(), EdgesOfEntity.end());
+    getResult().Edges[Id] = EdgeSet(EdgesOfEntity.begin(), EdgesOfEntity.end());
     return llvm::Error::success();
   }
 };
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
index 1f367720de30f..8860cf781c856 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
@@ -106,7 +106,7 @@ class UnsafeBufferUsageAnalysis final
                   const UnsafeBufferUsageEntitySummary &Summary) override {
     auto UnsafeBuffersOfEntity = getUnsafeBuffers(Summary);
 
-    result().UnsafeBuffers[Id] = EntityPointerLevelSet(
+    getResult().UnsafeBuffers[Id] = EntityPointerLevelSet(
         UnsafeBuffersOfEntity.begin(), UnsafeBuffersOfEntity.end());
     return llvm::Error::success();
   }



More information about the llvm-branch-commits mailing list