[llvm-branch-commits] [clang] [NFC][SSAF][EntityPointerLevel] Move EntityID-to-EPL map serialization to the EPL module (PR #193092)

Ziqing Luo via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Thu Apr 30 14:53:17 PDT 2026


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

>From f324fb19be45510da4fc68cc514bdf5e98ad6e1c 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 1/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       | 115 ++++++++++++++++
 .../PointerFlow/PointerFlowExtractor.cpp      |   2 +-
 .../Analyses/SSAFAnalysesCommon.h             |  23 +++-
 .../UnsafeBufferUsage/UnsafeBufferUsage.cpp   |  24 +---
 .../UnsafeBufferUsageAnalysis.cpp             | 120 ++++++++++++++++
 .../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 +++++
 30 files changed, 949 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..729bb21f91d9b
--- /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(const 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..4d629bb4b2c66 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(
+    const 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..8dab98d783346
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.cpp
@@ -0,0 +1,115 @@
+//===- 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 %zu", 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.h b/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.h
index e6759c1fb6e39..7eb78d32bfdfb 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.h
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.h
@@ -16,6 +16,22 @@
 #include "clang/AST/Decl.h"
 #include "llvm/Support/JSON.h"
 
+inline std::string describeJSONValue(const llvm::json::Value &V) {
+  return llvm::formatv("{0:2}", V).str();
+}
+
+/// Return a short description of a JSON array suitable for
+/// diagnostics.
+inline std::string describeJSONValue(const llvm::json::Array &A) {
+  return llvm::formatv("array of size {0}", A.size()).str();
+}
+
+/// Return a short description of a JSON object suitable for
+/// diagnostics.
+inline std::string describeJSONValue(const llvm::json::Object &O) {
+  return llvm::formatv("an object of {0} key(s)", O.size()).str();
+}
+
 template <typename NodeTy, typename... Ts>
 llvm::Error makeErrAtNode(clang::ASTContext &Ctx, const NodeTy *N,
                           llvm::StringRef Fmt, const Ts &...Args) {
@@ -24,12 +40,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..5aa798dd5f8b1
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
@@ -0,0 +1,120 @@
+//===- 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 %zu", 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 bbe2878e9a02cd4afbfc7b34498fbd4bbf18163b Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Mon, 20 Apr 2026 14:03:58 -0700
Subject: [PATCH 2/5] [NFC][SSAF][EntityPointerLevel] Move EntityID-to-EPL map
 serialization to the EPL module

Factor out the serialization of `std::map<EntityId, EntityPointerLevelSet>`
to `EntityPointerLevelFormat.h`.
---
 .../EntityPointerLevelFormat.h                | 14 +++++
 .../Analyses/PointerFlow/PointerFlowFormat.h  |  2 +-
 .../UnsafeBufferUsageAnalysis.h               |  7 ++-
 .../EntityPointerLevel/EntityPointerLevel.cpp | 51 +++++++++++++++++++
 .../Analyses/PointerFlow/PointerFlow.cpp      |  2 +-
 .../PointerFlow/PointerFlowAnalysis.cpp       |  3 +-
 .../Analyses/SSAFAnalysesCommon.cpp           | 12 +++++
 .../Analyses/SSAFAnalysesCommon.h             | 18 ++-----
 .../UnsafeBufferUsageAnalysis.cpp             | 48 +++--------------
 9 files changed, 97 insertions(+), 60 deletions(-)

diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h
index 13ecd880001e6..2b2caf8644bab 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h
@@ -10,8 +10,10 @@
 #define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_ENTITYPOINTERLEVEL_ENTITYPOINTERLEVELFORMAT_H
 
 #include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
 #include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
 #include "llvm/ADT/iterator_range.h"
+#include <map>
 
 namespace clang::ssaf {
 llvm::json::Value
@@ -29,6 +31,18 @@ llvm::json::Array entityPointerLevelSetToJSON(
 Expected<EntityPointerLevelSet>
 entityPointerLevelSetFromJSON(const llvm::json::Array &EPLsData,
                               JSONFormat::EntityIdFromJSONFn EntityIdFromJSON);
+
+/// Serialize a map<EntityId, EntityPointerLevelSet> as a flat array of
+/// alternating [EntityId, EntityPointerLevelSet, ...] pairs.
+llvm::json::Array entityPointerLevelMapToJSON(
+    const std::map<EntityId, EntityPointerLevelSet> &Map,
+    JSONFormat::EntityIdToJSONFn IdToJSON);
+
+/// Deserialize a flat array of alternating [EntityId, EntityPointerLevelSet,
+/// ...] pairs into a map.
+Expected<std::map<EntityId, EntityPointerLevelSet>>
+entityPointerLevelMapFromJSON(const llvm::json::Array &Content,
+                              JSONFormat::EntityIdFromJSONFn IdFromJSON);
 } // namespace clang::ssaf
 
 #endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_ENTITYPOINTERLEVEL_ENTITYPOINTERLEVELFORMAT_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowFormat.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowFormat.h
index 729bb21f91d9b..86bf1de4aaea2 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowFormat.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowFormat.h
@@ -22,7 +22,7 @@ namespace clang::ssaf {
 /// Serialize an EdgeSet as an array of arrays of EntityPointerLevels:
 ///   [ [lhs, rhs, rhs, ...], [lhs, rhs, rhs, ...], ... ]
 llvm::json::Array
-edgeSetToJSON(const llvm::iterator_range<EdgeSet::const_iterator> &Edges,
+edgeSetToJSON(llvm::iterator_range<EdgeSet::const_iterator> Edges,
               JSONFormat::EntityIdToJSONFn IdToJSON);
 
 /// Deserialize an EdgeSet from the array format produced by `edgeSetToJSON`.
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.h
index aba566498b44b..9cfd59e5c0333 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.h
@@ -1,4 +1,4 @@
-//===- UnsafeBufferUsageAnalysis.h --------------------------------*- C++ -*-===//
+//===- 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.
@@ -23,7 +23,7 @@
 
 namespace clang::ssaf {
 
-inline constexpr llvm::StringLiteral UnsafeBufferUsageAnalysisResultName =
+constexpr llvm::StringLiteral UnsafeBufferUsageAnalysisResultName =
     "UnsafeBufferUsageAnalysisResult";
 
 struct UnsafeBufferUsageAnalysisResult final : AnalysisResult {
@@ -33,6 +33,9 @@ struct UnsafeBufferUsageAnalysisResult final : AnalysisResult {
 
   /// Whole-program set of unsafe buffer pointers:
   std::map<EntityId, EntityPointerLevelSet> UnsafeBuffers;
+
+  auto begin() const { return UnsafeBuffers.begin(); }
+  auto end() const { return UnsafeBuffers.end(); }
 };
 
 } // namespace clang::ssaf
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
index fb47fd76241f0..97ff52cdb5055 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
@@ -14,6 +14,7 @@
 #include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h"
 #include "clang/ScalableStaticAnalysisFramework/Core/ASTEntityMapping.h"
 #include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityName.h"
+#include <map>
 #include <optional>
 
 using namespace clang;
@@ -328,3 +329,53 @@ Expected<EntityPointerLevelSet> clang::ssaf::entityPointerLevelSetFromJSON(
   }
   return EPLs;
 }
+
+llvm::json::Array clang::ssaf::entityPointerLevelMapToJSON(
+    const std::map<EntityId, EntityPointerLevelSet> &Map,
+    JSONFormat::EntityIdToJSONFn IdToJSON) {
+  llvm::json::Array Content;
+
+  for (const auto &[Id, EPLs] : Map) {
+    Content.push_back(IdToJSON(Id));
+    Content.push_back(entityPointerLevelSetToJSON(EPLs, IdToJSON));
+  }
+  return Content;
+}
+
+Expected<std::map<EntityId, EntityPointerLevelSet>>
+clang::ssaf::entityPointerLevelMapFromJSON(
+    const llvm::json::Array &Content,
+    JSONFormat::EntityIdFromJSONFn IdFromJSON) {
+  if (Content.size() % 2 != 0)
+    return makeSawButExpectedError(
+        Content, "an even number of elements, got %lu",
+        static_cast<unsigned long>(Content.size()));
+
+  std::map<EntityId, EntityPointerLevelSet> Result;
+
+  for (size_t I = 0; I < Content.size(); I += 2) {
+    const llvm::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 llvm::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();
+    Result[*Id] = std::move(*EPLs);
+  }
+  return Result;
+}
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.cpp
index 4d629bb4b2c66..ce45f4167016f 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.cpp
@@ -36,7 +36,7 @@ ssaf::getEdges(const PointerFlowEntitySummary &Sum) {
 }
 
 Array ssaf::edgeSetToJSON(
-    const llvm::iterator_range<EdgeSet::const_iterator> &Edges,
+    llvm::iterator_range<EdgeSet::const_iterator> Edges,
     JSONFormat::EntityIdToJSONFn EntityId2JSON) {
   Array EdgesData;
 
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.cpp
index 8dab98d783346..3db65bb3b4da8 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.cpp
@@ -54,7 +54,8 @@ Expected<std::unique_ptr<AnalysisResult>> deserializePointerFlowAnalysisResult(
 
   if (Content->size() % 2 != 0)
     return makeSawButExpectedError(
-        *Content, "an even number of elements, got %zu", Content->size());
+        *Content, "an even number of elements, got %lu",
+        static_cast<unsigned long>(Content->size()));
 
   std::map<EntityId, EdgeSet> Edges;
 
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 7eb78d32bfdfb..c798789fd12a8 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.h
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.h
@@ -16,21 +16,9 @@
 #include "clang/AST/Decl.h"
 #include "llvm/Support/JSON.h"
 
-inline std::string describeJSONValue(const llvm::json::Value &V) {
-  return llvm::formatv("{0:2}", V).str();
-}
-
-/// Return a short description of a JSON array suitable for
-/// diagnostics.
-inline std::string describeJSONValue(const llvm::json::Array &A) {
-  return llvm::formatv("array of size {0}", A.size()).str();
-}
-
-/// Return a short description of a JSON object suitable for
-/// diagnostics.
-inline std::string describeJSONValue(const llvm::json::Object &O) {
-  return llvm::formatv("an object of {0} key(s)", O.size()).str();
-}
+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,
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
index 5aa798dd5f8b1..d62c2b8bb47da 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
@@ -31,63 +31,31 @@ 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);
+  Result[UnsafeBufferUsageAnalysisResultName] =
+      entityPointerLevelMapToJSON(R.UnsafeBuffers, IdToJSON);
   return Result;
 }
 
 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 %zu", 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 UnsafeBuffers = entityPointerLevelMapFromJSON(*Content, IdFromJSON);
 
-    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);
-  }
+  if (!UnsafeBuffers)
+    return UnsafeBuffers.takeError();
 
   auto Ret = std::make_unique<UnsafeBufferUsageAnalysisResult>();
 
-  Ret->UnsafeBuffers = std::move(UnsafeBuffers);
+  Ret->UnsafeBuffers = std::move(*UnsafeBuffers);
   return Ret;
 }
 

>From 212f63d81cad5adb9f8e3d05cb8187174a615177 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Mon, 20 Apr 2026 15:16:55 -0700
Subject: [PATCH 3/5] fix format

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

diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
index 97ff52cdb5055..2fe48774fa0c1 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
@@ -347,9 +347,9 @@ clang::ssaf::entityPointerLevelMapFromJSON(
     const llvm::json::Array &Content,
     JSONFormat::EntityIdFromJSONFn IdFromJSON) {
   if (Content.size() % 2 != 0)
-    return makeSawButExpectedError(
-        Content, "an even number of elements, got %lu",
-        static_cast<unsigned long>(Content.size()));
+    return makeSawButExpectedError(Content,
+                                   "an even number of elements, got %lu",
+                                   static_cast<unsigned long>(Content.size()));
 
   std::map<EntityId, EntityPointerLevelSet> Result;
 
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.cpp
index 3db65bb3b4da8..5e9296995a474 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.cpp
@@ -53,9 +53,9 @@ Expected<std::unique_ptr<AnalysisResult>> deserializePointerFlowAnalysisResult(
                                    PointerFlowAnalysisResultName.data());
 
   if (Content->size() % 2 != 0)
-    return makeSawButExpectedError(
-        *Content, "an even number of elements, got %lu",
-        static_cast<unsigned long>(Content->size()));
+    return makeSawButExpectedError(*Content,
+                                   "an even number of elements, got %lu",
+                                   static_cast<unsigned long>(Content->size()));
 
   std::map<EntityId, EdgeSet> Edges;
 

>From 9343e8bb98439ac9ea7bdef357e2a1080367781d Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Wed, 22 Apr 2026 12:10:31 -0700
Subject: [PATCH 4/5] fix clang-format

---
 .../Analyses/PointerFlow/PointerFlowAnalysis.h                  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.h
index 85694c779adf4..3b4da10bdc65d 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowAnalysis.h
@@ -1,4 +1,4 @@
-//===- PointerFlowAnalysis.h -------------------------------------*- C++ -*-===//
+//===- 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.

>From 77d6f766fd186734841624da83dc25762c89db33 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Thu, 30 Apr 2026 14:39:38 -0700
Subject: [PATCH 5/5] fix bad conflict merge

---
 .../EntityPointerLevelFormat.cpp              | 50 +++++++++++++++++++
 1 file changed, 50 insertions(+)

diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.cpp
index 713c815a9ccca..7de4a63aff5f0 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.cpp
@@ -82,3 +82,53 @@ Expected<EntityPointerLevelSet> clang::ssaf::entityPointerLevelSetFromJSON(
   }
   return EPLs;
 }
+
+llvm::json::Array clang::ssaf::entityPointerLevelMapToJSON(
+    const std::map<EntityId, EntityPointerLevelSet> &Map,
+    JSONFormat::EntityIdToJSONFn IdToJSON) {
+  llvm::json::Array Content;
+
+  for (const auto &[Id, EPLs] : Map) {
+    Content.push_back(IdToJSON(Id));
+    Content.push_back(entityPointerLevelSetToJSON(EPLs, IdToJSON));
+  }
+  return Content;
+}
+
+Expected<std::map<EntityId, EntityPointerLevelSet>>
+clang::ssaf::entityPointerLevelMapFromJSON(
+    const llvm::json::Array &Content,
+    JSONFormat::EntityIdFromJSONFn IdFromJSON) {
+  if (Content.size() % 2 != 0)
+    return makeSawButExpectedError(Content,
+                                   "an even number of elements, got %lu",
+                                   static_cast<size_t>(Content.size()));
+
+  std::map<EntityId, EntityPointerLevelSet> Result;
+
+  for (size_t I = 0; I < Content.size(); I += 2) {
+    const llvm::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 llvm::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();
+    Result[*Id] = std::move(*EPLs);
+  }
+  return Result;
+}



More information about the llvm-branch-commits mailing list