[clang] SSAF JSON Format (PR #180021)

Aviral Goel via cfe-commits cfe-commits at lists.llvm.org
Fri Feb 6 12:53:35 PST 2026


https://github.com/aviralg updated https://github.com/llvm/llvm-project/pull/180021

>From 5ae260fc6191ef087118a619214684fe0d471739 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Wed, 28 Jan 2026 09:21:20 -0800
Subject: [PATCH 1/7] Replace const and non-const static methods with templated
 methods

---
 .../Serialization/SerializationFormat.h       | 30 ++++-----
 clang/lib/Analysis/Scalable/CMakeLists.txt    |  1 -
 .../Serialization/SerializationFormat.cpp     | 61 -------------------
 3 files changed, 16 insertions(+), 76 deletions(-)
 delete mode 100644 clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp

diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index a53a315f461df..30d728239e5c3 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -32,20 +32,22 @@ class SerializationFormat {
 protected:
   // Helpers providing access to implementation details of basic data structures
   // for efficient serialization/deserialization.
-  static EntityIdTable &getIdTableForDeserialization(TUSummary &S);
-  static BuildNamespace &getTUNamespaceForDeserialization(TUSummary &S);
-  static const EntityIdTable &getIdTable(const TUSummary &S);
-  static const BuildNamespace &getTUNamespace(const TUSummary &S);
-
-  static BuildNamespaceKind getBuildNamespaceKind(const BuildNamespace &BN);
-  static llvm::StringRef getBuildNamespaceName(const BuildNamespace &BN);
-  static const std::vector<BuildNamespace> &
-  getNestedBuildNamespaces(const NestedBuildNamespace &NBN);
-
-  static llvm::StringRef getEntityNameUSR(const EntityName &EN);
-  static const llvm::SmallString<16> &getEntityNameSuffix(const EntityName &EN);
-  static const NestedBuildNamespace &
-  getEntityNameNamespace(const EntityName &EN);
+
+  // Accessors for TUSummary:
+  template <class T> static auto &IdTableOf(T &X) { return X.IdTable; }
+  template <class T> static auto &TUNamespaceOf(T &X) { return X.TUNamespace; }
+
+  // Accessors for BuildNamespace:
+  template <class T> static auto &KindOf(T &X) { return X.Kind; }
+  template <class T> static auto &NameOf(T &X) { return X.Name; }
+
+  // Accessors for NestedBuildNamespace:
+  template <class T> static auto &NamespacesOf(T &X) { return X.Namespaces; }
+
+  // Accessors for EntityName:
+  template <class T> static auto &USROf(T &X) { return X.USR; }
+  template <class T> static auto &SuffixOf(T &X) { return X.Suffix; }
+  template <class T> static auto &NamespaceOf(T &X) { return X.Namespace; }
 
 public:
   virtual ~SerializationFormat() = default;
diff --git a/clang/lib/Analysis/Scalable/CMakeLists.txt b/clang/lib/Analysis/Scalable/CMakeLists.txt
index 36365b1fb87e1..566edca552388 100644
--- a/clang/lib/Analysis/Scalable/CMakeLists.txt
+++ b/clang/lib/Analysis/Scalable/CMakeLists.txt
@@ -7,7 +7,6 @@ add_clang_library(clangAnalysisScalable
   Model/BuildNamespace.cpp
   Model/EntityIdTable.cpp
   Model/EntityName.cpp
-  Serialization/SerializationFormat.cpp
   TUSummary/ExtractorRegistry.cpp
 
   LINK_LIBS
diff --git a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
deleted file mode 100644
index ee155d22afa9b..0000000000000
--- a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-//===- SerializationFormat.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/Analysis/Scalable/Serialization/SerializationFormat.h"
-#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
-#include "clang/Analysis/Scalable/Model/EntityId.h"
-#include "clang/Analysis/Scalable/Model/EntityName.h"
-#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
-
-using namespace clang::ssaf;
-
-EntityIdTable &SerializationFormat::getIdTableForDeserialization(TUSummary &S) {
-  return S.IdTable;
-}
-
-BuildNamespace &
-SerializationFormat::getTUNamespaceForDeserialization(TUSummary &S) {
-  return S.TUNamespace;
-}
-
-const EntityIdTable &SerializationFormat::getIdTable(const TUSummary &S) {
-  return S.IdTable;
-}
-
-const BuildNamespace &SerializationFormat::getTUNamespace(const TUSummary &S) {
-  return S.TUNamespace;
-}
-
-BuildNamespaceKind
-SerializationFormat::getBuildNamespaceKind(const BuildNamespace &BN) {
-  return BN.Kind;
-}
-
-llvm::StringRef
-SerializationFormat::getBuildNamespaceName(const BuildNamespace &BN) {
-  return BN.Name;
-}
-
-const std::vector<BuildNamespace> &
-SerializationFormat::getNestedBuildNamespaces(const NestedBuildNamespace &NBN) {
-  return NBN.Namespaces;
-}
-
-llvm::StringRef SerializationFormat::getEntityNameUSR(const EntityName &EN) {
-  return EN.USR;
-}
-
-const llvm::SmallString<16> &
-SerializationFormat::getEntityNameSuffix(const EntityName &EN) {
-  return EN.Suffix;
-}
-
-const NestedBuildNamespace &
-SerializationFormat::getEntityNameNamespace(const EntityName &EN) {
-  return EN.Namespace;
-}

>From b5ae6874e8cbcc6d8eeb3737b7bffea7a0506eaa Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Wed, 28 Jan 2026 13:52:12 -0800
Subject: [PATCH 2/7] Revert "Replace const and non-const static methods with
 templated methods"

This reverts commit c93430657298a6ac87ac51be6bd310cc652aa52d.
---
 .../Serialization/SerializationFormat.h       | 30 +++++----
 clang/lib/Analysis/Scalable/CMakeLists.txt    |  1 +
 .../Serialization/SerializationFormat.cpp     | 61 +++++++++++++++++++
 3 files changed, 76 insertions(+), 16 deletions(-)
 create mode 100644 clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp

diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index 30d728239e5c3..a53a315f461df 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -32,22 +32,20 @@ class SerializationFormat {
 protected:
   // Helpers providing access to implementation details of basic data structures
   // for efficient serialization/deserialization.
-
-  // Accessors for TUSummary:
-  template <class T> static auto &IdTableOf(T &X) { return X.IdTable; }
-  template <class T> static auto &TUNamespaceOf(T &X) { return X.TUNamespace; }
-
-  // Accessors for BuildNamespace:
-  template <class T> static auto &KindOf(T &X) { return X.Kind; }
-  template <class T> static auto &NameOf(T &X) { return X.Name; }
-
-  // Accessors for NestedBuildNamespace:
-  template <class T> static auto &NamespacesOf(T &X) { return X.Namespaces; }
-
-  // Accessors for EntityName:
-  template <class T> static auto &USROf(T &X) { return X.USR; }
-  template <class T> static auto &SuffixOf(T &X) { return X.Suffix; }
-  template <class T> static auto &NamespaceOf(T &X) { return X.Namespace; }
+  static EntityIdTable &getIdTableForDeserialization(TUSummary &S);
+  static BuildNamespace &getTUNamespaceForDeserialization(TUSummary &S);
+  static const EntityIdTable &getIdTable(const TUSummary &S);
+  static const BuildNamespace &getTUNamespace(const TUSummary &S);
+
+  static BuildNamespaceKind getBuildNamespaceKind(const BuildNamespace &BN);
+  static llvm::StringRef getBuildNamespaceName(const BuildNamespace &BN);
+  static const std::vector<BuildNamespace> &
+  getNestedBuildNamespaces(const NestedBuildNamespace &NBN);
+
+  static llvm::StringRef getEntityNameUSR(const EntityName &EN);
+  static const llvm::SmallString<16> &getEntityNameSuffix(const EntityName &EN);
+  static const NestedBuildNamespace &
+  getEntityNameNamespace(const EntityName &EN);
 
 public:
   virtual ~SerializationFormat() = default;
diff --git a/clang/lib/Analysis/Scalable/CMakeLists.txt b/clang/lib/Analysis/Scalable/CMakeLists.txt
index 566edca552388..36365b1fb87e1 100644
--- a/clang/lib/Analysis/Scalable/CMakeLists.txt
+++ b/clang/lib/Analysis/Scalable/CMakeLists.txt
@@ -7,6 +7,7 @@ add_clang_library(clangAnalysisScalable
   Model/BuildNamespace.cpp
   Model/EntityIdTable.cpp
   Model/EntityName.cpp
+  Serialization/SerializationFormat.cpp
   TUSummary/ExtractorRegistry.cpp
 
   LINK_LIBS
diff --git a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
new file mode 100644
index 0000000000000..ee155d22afa9b
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
@@ -0,0 +1,61 @@
+//===- SerializationFormat.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/Analysis/Scalable/Serialization/SerializationFormat.h"
+#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
+#include "clang/Analysis/Scalable/Model/EntityId.h"
+#include "clang/Analysis/Scalable/Model/EntityName.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+
+using namespace clang::ssaf;
+
+EntityIdTable &SerializationFormat::getIdTableForDeserialization(TUSummary &S) {
+  return S.IdTable;
+}
+
+BuildNamespace &
+SerializationFormat::getTUNamespaceForDeserialization(TUSummary &S) {
+  return S.TUNamespace;
+}
+
+const EntityIdTable &SerializationFormat::getIdTable(const TUSummary &S) {
+  return S.IdTable;
+}
+
+const BuildNamespace &SerializationFormat::getTUNamespace(const TUSummary &S) {
+  return S.TUNamespace;
+}
+
+BuildNamespaceKind
+SerializationFormat::getBuildNamespaceKind(const BuildNamespace &BN) {
+  return BN.Kind;
+}
+
+llvm::StringRef
+SerializationFormat::getBuildNamespaceName(const BuildNamespace &BN) {
+  return BN.Name;
+}
+
+const std::vector<BuildNamespace> &
+SerializationFormat::getNestedBuildNamespaces(const NestedBuildNamespace &NBN) {
+  return NBN.Namespaces;
+}
+
+llvm::StringRef SerializationFormat::getEntityNameUSR(const EntityName &EN) {
+  return EN.USR;
+}
+
+const llvm::SmallString<16> &
+SerializationFormat::getEntityNameSuffix(const EntityName &EN) {
+  return EN.Suffix;
+}
+
+const NestedBuildNamespace &
+SerializationFormat::getEntityNameNamespace(const EntityName &EN) {
+  return EN.Namespace;
+}

>From 1d12d1eebf9a5db3208d8ccf3911d7035e4d3d45 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Wed, 4 Feb 2026 16:12:00 -0800
Subject: [PATCH 3/7] Initial implementation of JSONFormat with tests.

---
 .../clang/Analysis/Scalable/Model/EntityId.h  |    1 +
 .../Analysis/Scalable/Model/EntityIdTable.h   |    2 +
 .../Analysis/Scalable/Model/SummaryName.h     |    2 +
 .../Scalable/Serialization/JSONFormat.h       |   77 ++
 .../Serialization/SerializationFormat.h       |   25 +-
 clang/lib/Analysis/Scalable/CMakeLists.txt    |    1 +
 .../Scalable/Serialization/JSONFormat.cpp     |  860 +++++++++++++
 .../Serialization/SerializationFormat.cpp     |   29 +
 .../Analysis/Scalable/CMakeLists.txt          |    1 +
 .../Analysis/Scalable/JSONFormatTest.cpp      | 1127 +++++++++++++++++
 10 files changed, 2122 insertions(+), 3 deletions(-)
 create mode 100644 clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
 create mode 100644 clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
 create mode 100644 clang/unittests/Analysis/Scalable/JSONFormatTest.cpp

diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityId.h b/clang/include/clang/Analysis/Scalable/Model/EntityId.h
index 6fa059445d853..e348486386cb6 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityId.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityId.h
@@ -29,6 +29,7 @@ class EntityIdTable;
 /// \see EntityIdTable
 class EntityId {
   friend class EntityIdTable;
+  friend class SerializationFormat;
 
   size_t Index;
 
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h b/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
index cf4bb83efddd0..a1099c4e4d0f8 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
@@ -21,6 +21,8 @@ namespace clang::ssaf {
 /// The table maps each unique EntityName to exactly one EntityId.
 /// Entities are never removed.
 class EntityIdTable {
+  friend class SerializationFormat;
+
   std::map<EntityName, EntityId> Entities;
 
 public:
diff --git a/clang/include/clang/Analysis/Scalable/Model/SummaryName.h b/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
index 785fe0eb10372..08ed67fe17bed 100644
--- a/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
+++ b/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
@@ -10,6 +10,8 @@
 #define LLVM_CLANG_ANALYSIS_SCALABLE_MODEL_SUMMARYNAME_H
 
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Path.h"
+#include <cassert>
 #include <string>
 
 namespace clang::ssaf {
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
new file mode 100644
index 0000000000000..476853af0dce4
--- /dev/null
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -0,0 +1,77 @@
+//===- JSONFormat.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 format implementation
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef CLANG_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_H
+#define CLANG_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_H
+
+#include "clang/Analysis/Scalable/Serialization/SerializationFormat.h"
+#include "llvm/Support/JSON.h"
+
+namespace clang::ssaf {
+
+class EntitySummary;
+class SummaryName;
+
+class JSONFormat : public SerializationFormat {
+public:
+  JSONFormat() = default;
+
+  ~JSONFormat() = default;
+
+  llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) override;
+
+  llvm::Error writeTUSummary(const TUSummary &Summary,
+                             llvm::StringRef OutputDir) override;
+
+private:
+  EntityId entityIdFromJSON(const uint64_t EntityIdIndex);
+  uint64_t entityIdToJSON(EntityId EI) const;
+
+  llvm::json::Object buildNamespaceToJSON(const BuildNamespace &BN) const;
+  llvm::json::Array
+  nestedBuildNamespaceToJSON(const NestedBuildNamespace &NBN) const;
+  llvm::json::Object entityNameToJSON(const EntityName &EN) const;
+  llvm::Expected<std::pair<EntityName, EntityId>>
+  entityIdTableEntryFromJSON(const llvm::json::Object &EntityIdTableEntryObject,
+                             llvm::StringRef Path);
+  llvm::Expected<EntityIdTable>
+  entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray,
+                        llvm::StringRef Path);
+  llvm::json::Array entityIdTableToJSON(const EntityIdTable &IdTable) const;
+
+  llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
+  entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
+                        llvm::StringRef Path);
+  llvm::json::Array entityDataMapToJSON(
+      const std::map<EntityId, std::unique_ptr<EntitySummary>> &EntityDataMap)
+      const;
+
+  llvm::Expected<std::pair<SummaryName,
+                           std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+  summaryDataMapEntryFromJSON(const llvm::json::Object &SummaryDataObject,
+                              llvm::StringRef Path);
+  llvm::json::Object summaryDataMapEntryToJSON(
+      const SummaryName &SN,
+      const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const;
+
+  llvm::Expected<
+      std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+  readTUSummaryData(llvm::StringRef Path);
+  llvm::Error writeTUSummaryData(
+      const std::map<SummaryName,
+                     std::map<EntityId, std::unique_ptr<EntitySummary>>> &Data,
+      llvm::StringRef Path);
+};
+
+} // namespace clang::ssaf
+
+#endif // CLANG_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_H
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index a53a315f461df..3d79f49c12dcb 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -17,6 +17,8 @@
 #include "clang/Analysis/Scalable/Model/BuildNamespace.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include <map>
 #include <vector>
 
 namespace clang::ssaf {
@@ -26,16 +28,33 @@ class EntityIdTable;
 class EntityName;
 class TUSummary;
 class TUSummaryData;
+class SummaryName;
+class EntitySummary;
 
 /// Abstract base class for serialization formats.
 class SerializationFormat {
 protected:
   // Helpers providing access to implementation details of basic data structures
   // for efficient serialization/deserialization.
+
+  static size_t getEntityIdIndex(const EntityId &EI);
+  static EntityId makeEntityId(const size_t Index);
+
+  static const std::map<EntityName, EntityId> &
+  getEntities(const EntityIdTable &EIT);
+  static std::map<EntityName, EntityId> &
+  getEntitiesForDeserialization(EntityIdTable &EIT);
+
   static EntityIdTable &getIdTableForDeserialization(TUSummary &S);
   static BuildNamespace &getTUNamespaceForDeserialization(TUSummary &S);
+  static std::map<SummaryName,
+                  std::map<EntityId, std::unique_ptr<EntitySummary>>> &
+  getDataForDeserialization(TUSummary &S);
   static const EntityIdTable &getIdTable(const TUSummary &S);
   static const BuildNamespace &getTUNamespace(const TUSummary &S);
+  static const std::map<SummaryName,
+                        std::map<EntityId, std::unique_ptr<EntitySummary>>> &
+  getData(const TUSummary &S);
 
   static BuildNamespaceKind getBuildNamespaceKind(const BuildNamespace &BN);
   static llvm::StringRef getBuildNamespaceName(const BuildNamespace &BN);
@@ -50,10 +69,10 @@ class SerializationFormat {
 public:
   virtual ~SerializationFormat() = default;
 
-  virtual TUSummary readTUSummary(llvm::StringRef Path) = 0;
+  virtual llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) = 0;
 
-  virtual void writeTUSummary(const TUSummary &Summary,
-                              llvm::StringRef OutputDir) = 0;
+  virtual llvm::Error writeTUSummary(const TUSummary &Summary,
+                                     llvm::StringRef OutputDir) = 0;
 };
 
 } // namespace clang::ssaf
diff --git a/clang/lib/Analysis/Scalable/CMakeLists.txt b/clang/lib/Analysis/Scalable/CMakeLists.txt
index 36365b1fb87e1..6592d01bd8f38 100644
--- a/clang/lib/Analysis/Scalable/CMakeLists.txt
+++ b/clang/lib/Analysis/Scalable/CMakeLists.txt
@@ -7,6 +7,7 @@ add_clang_library(clangAnalysisScalable
   Model/BuildNamespace.cpp
   Model/EntityIdTable.cpp
   Model/EntityName.cpp
+  Serialization/JSONFormat.cpp
   Serialization/SerializationFormat.cpp
   TUSummary/ExtractorRegistry.cpp
 
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
new file mode 100644
index 0000000000000..cca3ca36eb9ae
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -0,0 +1,860 @@
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+
+using namespace clang::ssaf;
+
+namespace {
+constexpr size_t kPathBufferSize = 128;
+constexpr const char *TUSummaryTUNamespaceFilename = "tu_namespace.json";
+constexpr const char *TUSummaryIdTableFilename = "id_table.json";
+constexpr const char *TUSummaryDataDirname = "data";
+
+// Helper to wrap an error with additional context
+template <typename... Args>
+llvm::Error wrapError(llvm::Error E, std::errc ErrorCode, const char *Fmt,
+                      Args &&...Vals) {
+  return llvm::joinErrors(
+      llvm::createStringError(ErrorCode, Fmt, std::forward<Args>(Vals)...),
+      std::move(E));
+}
+
+// Convenience version that defaults to invalid_argument
+template <typename... Args>
+llvm::Error wrapError(llvm::Error E, const char *Fmt, Args &&...Vals) {
+  return wrapError(std::move(E), std::errc::invalid_argument, Fmt,
+                   std::forward<Args>(Vals)...);
+}
+
+} // namespace
+
+//----------------------------------------------------------------------------
+// JSON Reader and Writer
+//----------------------------------------------------------------------------
+
+llvm::Error isJSONFile(llvm::StringRef Path) {
+  if (!llvm::sys::fs::exists(Path))
+    return llvm::createStringError(std::errc::no_such_file_or_directory,
+                                   "file does not exist: '%s'",
+                                   Path.str().c_str());
+
+  if (!Path.ends_with_insensitive(".json"))
+    return llvm::createStringError(std::errc::invalid_argument,
+                                   "not a JSON file: '%s'", Path.str().c_str());
+
+  return llvm::Error::success();
+}
+
+llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
+  if (llvm::Error Err = isJSONFile(Path))
+    return wrapError(std::move(Err), "failed to validate JSON file '%s'",
+                     Path.str().c_str());
+
+  auto BufferOrError = llvm::MemoryBuffer::getFile(Path);
+  if (!BufferOrError) {
+    return llvm::createStringError(BufferOrError.getError(),
+                                   "failed to read file '%s'",
+                                   Path.str().c_str());
+  }
+
+  return llvm::json::parse(BufferOrError.get()->getBuffer());
+}
+
+llvm::Expected<llvm::json::Object> readJSONObject(llvm::StringRef Path) {
+  auto ExpectedJSON = readJSON(Path);
+  if (!ExpectedJSON)
+    return wrapError(ExpectedJSON.takeError(),
+                     "failed to read JSON from file '%s'", Path.str().c_str());
+
+  llvm::json::Object *Object = ExpectedJSON->getAsObject();
+  if (!Object) {
+    return llvm::createStringError(std::errc::invalid_argument,
+                                   "failed to read JSON object from file '%s'",
+                                   Path.str().c_str());
+  }
+  return std::move(*Object);
+}
+
+llvm::Expected<llvm::json::Array> readJSONArray(llvm::StringRef Path) {
+  auto ExpectedJSON = readJSON(Path);
+  if (!ExpectedJSON)
+    return wrapError(ExpectedJSON.takeError(),
+                     "failed to read JSON from file '%s'", Path.str().c_str());
+
+  llvm::json::Array *Array = ExpectedJSON->getAsArray();
+  if (!Array) {
+    return llvm::createStringError(std::errc::invalid_argument,
+                                   "failed to read JSON array from file '%s'",
+                                   Path.str().c_str());
+  }
+  return std::move(*Array);
+}
+
+llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
+  std::error_code EC;
+  llvm::raw_fd_ostream OS(Path, EC);
+  if (EC) {
+    return llvm::createStringError(EC, "failed to open '%s'",
+                                   Path.str().c_str());
+  }
+
+  OS << llvm::formatv("{0:2}\n", Value);
+
+  OS.flush();
+  if (OS.has_error()) {
+    return llvm::createStringError(OS.error(), "write failed");
+  }
+
+  return llvm::Error::success();
+}
+
+//----------------------------------------------------------------------------
+// EntityId
+//----------------------------------------------------------------------------
+
+EntityId JSONFormat::entityIdFromJSON(const uint64_t EntityIdIndex) {
+  return makeEntityId(static_cast<size_t>(EntityIdIndex));
+}
+
+uint64_t JSONFormat::entityIdToJSON(EntityId EI) const {
+  return static_cast<uint64_t>(getEntityIdIndex(EI));
+}
+
+//----------------------------------------------------------------------------
+// BuildNamespaceKind
+//----------------------------------------------------------------------------
+
+llvm::Expected<BuildNamespaceKind>
+buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr,
+                           llvm::StringRef Path) {
+  auto OptBuildNamespaceKind = parseBuildNamespaceKind(BuildNamespaceKindStr);
+  if (!OptBuildNamespaceKind) {
+    return llvm::createStringError(
+        std::errc::invalid_argument,
+        "invalid 'kind' BuildNamespaceKind value '%s' in file '%s'",
+        BuildNamespaceKindStr.str().c_str(), Path.str().c_str());
+  }
+
+  return *OptBuildNamespaceKind;
+}
+
+llvm::StringRef buildNamespaceKindToJSON(BuildNamespaceKind BNK) {
+  return toString(BNK);
+}
+
+//----------------------------------------------------------------------------
+// BuildNamespace
+//----------------------------------------------------------------------------
+
+llvm::Expected<BuildNamespace>
+buildNamespaceFromJSON(const llvm::json::Object &BuildNamespaceObject,
+                       llvm::StringRef Path) {
+  auto OptBuildNamespaceKindStr = BuildNamespaceObject.getString("kind");
+  if (!OptBuildNamespaceKindStr) {
+    return llvm::createStringError(
+        std::errc::invalid_argument,
+        "failed to deserialize BuildNamespace from file '%s': "
+        "missing required field 'kind' (expected BuildNamespaceKind)",
+        Path.str().c_str());
+  }
+
+  auto ExpectedKind =
+      buildNamespaceKindFromJSON(*OptBuildNamespaceKindStr, Path);
+  if (!ExpectedKind)
+    return wrapError(ExpectedKind.takeError(),
+                     "failed to deserialize BuildNamespace from file '%s'",
+                     Path.str().c_str());
+
+  auto OptNameStr = BuildNamespaceObject.getString("name");
+  if (!OptNameStr) {
+    return llvm::createStringError(
+        std::errc::invalid_argument,
+        "failed to deserialize BuildNamespace from file '%s': "
+        "missing required field 'name'",
+        Path.str().c_str());
+  }
+
+  return {BuildNamespace(*ExpectedKind, *OptNameStr)};
+}
+
+llvm::json::Object
+JSONFormat::buildNamespaceToJSON(const BuildNamespace &BN) const {
+  llvm::json::Object Result;
+  Result["kind"] = buildNamespaceKindToJSON(getBuildNamespaceKind(BN));
+  Result["name"] = getBuildNamespaceName(BN);
+  return Result;
+}
+
+//----------------------------------------------------------------------------
+// NestedBuildNamespace
+//----------------------------------------------------------------------------
+
+llvm::Expected<NestedBuildNamespace>
+nestedBuildNamespaceFromJSON(const llvm::json::Array &NestedBuildNamespaceArray,
+                             llvm::StringRef Path) {
+  std::vector<BuildNamespace> Namespaces;
+
+  size_t NamespaceCount = NestedBuildNamespaceArray.size();
+  Namespaces.reserve(NamespaceCount);
+  for (size_t Index = 0; Index < NamespaceCount; ++Index) {
+    const llvm::json::Value &BuildNamespaceValue =
+        NestedBuildNamespaceArray[Index];
+
+    const llvm::json::Object *BuildNamespaceObject =
+        BuildNamespaceValue.getAsObject();
+    if (!BuildNamespaceObject) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "failed to deserialize NestedBuildNamespace from file '%s': "
+          "element at index %zu is not a JSON object "
+          "(expected BuildNamespace object)",
+          Path.str().c_str(), Index);
+    }
+
+    auto ExpectedBuildNamespace =
+        buildNamespaceFromJSON(*BuildNamespaceObject, Path);
+    if (!ExpectedBuildNamespace)
+      return wrapError(
+          ExpectedBuildNamespace.takeError(),
+          "failed to deserialize NestedBuildNamespace from file '%s' "
+          "at index %zu",
+          Path.str().c_str(), Index);
+
+    Namespaces.push_back(std::move(*ExpectedBuildNamespace));
+  }
+
+  return NestedBuildNamespace(std::move(Namespaces));
+}
+
+llvm::json::Array
+JSONFormat::nestedBuildNamespaceToJSON(const NestedBuildNamespace &NBN) const {
+  llvm::json::Array Result;
+  const auto &Namespaces = getNestedBuildNamespaces(NBN);
+  Result.reserve(Namespaces.size());
+
+  for (const auto &BN : Namespaces) {
+    Result.push_back(buildNamespaceToJSON(BN));
+  }
+
+  return Result;
+}
+
+//----------------------------------------------------------------------------
+// EntityName
+//----------------------------------------------------------------------------
+
+llvm::Expected<EntityName>
+entityNameFromJSON(const llvm::json::Object &EntityNameObject,
+                   llvm::StringRef Path) {
+  const auto OptUSR = EntityNameObject.getString("usr");
+  if (!OptUSR) {
+    return llvm::createStringError(
+        std::errc::invalid_argument,
+        "failed to deserialize EntityName from file '%s': "
+        "missing required field 'usr' (Unified Symbol Resolution string)",
+        Path.str().c_str());
+  }
+
+  const auto OptSuffix = EntityNameObject.getString("suffix");
+  if (!OptSuffix) {
+    return llvm::createStringError(
+        std::errc::invalid_argument,
+        "failed to deserialize EntityName from file '%s': "
+        "missing required field 'suffix'",
+        Path.str().c_str());
+  }
+
+  const llvm::json::Array *OptNamespaceArray =
+      EntityNameObject.getArray("namespace");
+  if (!OptNamespaceArray) {
+    return llvm::createStringError(
+        std::errc::invalid_argument,
+        "failed to deserialize EntityName from file '%s': "
+        "missing or invalid field 'namespace' "
+        "(expected JSON array of BuildNamespace objects)",
+        Path.str().c_str());
+  }
+
+  auto ExpectedNamespace =
+      nestedBuildNamespaceFromJSON(*OptNamespaceArray, Path);
+  if (!ExpectedNamespace)
+    return wrapError(ExpectedNamespace.takeError(),
+                     "failed to deserialize EntityName from file '%s'",
+                     Path.str().c_str());
+
+  return EntityName{*OptUSR, *OptSuffix, std::move(*ExpectedNamespace)};
+}
+
+llvm::json::Object JSONFormat::entityNameToJSON(const EntityName &EN) const {
+  llvm::json::Object Result;
+  Result["usr"] = getEntityNameUSR(EN);
+  Result["suffix"] = getEntityNameSuffix(EN);
+  Result["namespace"] = nestedBuildNamespaceToJSON(getEntityNameNamespace(EN));
+  return Result;
+}
+
+//----------------------------------------------------------------------------
+// SummaryName
+//----------------------------------------------------------------------------
+
+SummaryName summaryNameFromJSON(llvm::StringRef SummaryNameStr) {
+  return SummaryName(SummaryNameStr.str());
+}
+
+llvm::StringRef summaryNameToJSON(const SummaryName &SN) { return SN.str(); }
+
+//----------------------------------------------------------------------------
+// EntityIdTable
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::pair<EntityName, EntityId>>
+JSONFormat::entityIdTableEntryFromJSON(
+    const llvm::json::Object &EntityIdTableEntryObject, llvm::StringRef Path) {
+
+  const llvm::json::Object *OptEntityNameObject =
+      EntityIdTableEntryObject.getObject("name");
+  if (!OptEntityNameObject) {
+    return llvm::createStringError(
+        std::errc::invalid_argument,
+        "failed to deserialize EntityIdTable entry from file '%s': "
+        "missing or invalid field 'name' (expected EntityName JSON object)",
+        Path.str().c_str());
+  }
+
+  auto ExpectedEntityName = entityNameFromJSON(*OptEntityNameObject, Path);
+  if (!ExpectedEntityName)
+    return wrapError(ExpectedEntityName.takeError(),
+                     "failed to deserialize EntityIdTable entry from file '%s'",
+                     Path.str().c_str());
+
+  const llvm::json::Value *EntityIdIntValue =
+      EntityIdTableEntryObject.get("id");
+  if (!EntityIdIntValue) {
+    return llvm::createStringError(
+        std::errc::invalid_argument,
+        "failed to deserialize EntityIdTable entry from file '%s': "
+        "missing required field 'id' (expected unsigned integer EntityId)",
+        Path.str().c_str());
+  }
+
+  const std::optional<uint64_t> OptEntityIdInt =
+      EntityIdIntValue->getAsUINT64();
+  if (!OptEntityIdInt) {
+    return llvm::createStringError(
+        std::errc::invalid_argument,
+        "failed to deserialize EntityIdTable entry from file '%s': "
+        "field 'id' is not a valid unsigned 64-bit integer "
+        "(expected non-negative EntityId value)",
+        Path.str().c_str());
+  }
+
+  EntityId EI = entityIdFromJSON(*OptEntityIdInt);
+
+  return std::make_pair(std::move(*ExpectedEntityName), std::move(EI));
+}
+
+llvm::Expected<EntityIdTable>
+JSONFormat::entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray,
+                                  llvm::StringRef Path) {
+  const size_t EntityCount = EntityIdTableArray.size();
+
+  EntityIdTable IdTable;
+  std::map<EntityName, EntityId> &Entities =
+      getEntitiesForDeserialization(IdTable);
+
+  for (size_t Index = 0; Index < EntityCount; ++Index) {
+    const llvm::json::Value &EntityIdTableEntryValue =
+        EntityIdTableArray[Index];
+
+    const llvm::json::Object *OptEntityIdTableEntryObject =
+        EntityIdTableEntryValue.getAsObject();
+
+    if (!OptEntityIdTableEntryObject) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "failed to deserialize EntityIdTable from file '%s': "
+          "element at index %zu is not a JSON object "
+          "(expected EntityIdTable entry with 'id' and 'name' fields)",
+          Path.str().c_str(), Index);
+    }
+
+    auto ExpectedEntityIdTableEntry =
+        entityIdTableEntryFromJSON(*OptEntityIdTableEntryObject, Path);
+    if (!ExpectedEntityIdTableEntry)
+      return wrapError(
+          ExpectedEntityIdTableEntry.takeError(),
+          "failed to deserialize EntityIdTable from file '%s' at index %zu",
+          Path.str().c_str(), Index);
+
+    auto [EntityIt, EntityInserted] =
+        Entities.emplace(std::move(*ExpectedEntityIdTableEntry));
+    if (!EntityInserted) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "failed to deserialize EntityIdTable from file '%s': "
+          "duplicate EntityName found at index %zu "
+          "(EntityId=%zu already exists in table)",
+          Path.str().c_str(), Index, getEntityIdIndex(EntityIt->second));
+    }
+  }
+
+  return IdTable;
+}
+
+llvm::json::Array
+JSONFormat::entityIdTableToJSON(const EntityIdTable &IdTable) const {
+  llvm::json::Array EntityIdTableArray;
+  const auto &Entities = getEntities(IdTable);
+  EntityIdTableArray.reserve(Entities.size());
+
+  for (const auto &[EntityName, EntityId] : Entities) {
+    llvm::json::Object Entry;
+    Entry["id"] = entityIdToJSON(EntityId);
+    Entry["name"] = entityNameToJSON(EntityName);
+    EntityIdTableArray.push_back(std::move(Entry));
+  }
+
+  return EntityIdTableArray;
+}
+
+//----------------------------------------------------------------------------
+// EntitySummary
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::unique_ptr<EntitySummary>>
+entitySummaryFromJSON(const llvm::json::Object &EntitySummaryObject,
+                      llvm::StringRef Path) {
+  return llvm::createStringError(
+      std::errc::function_not_supported,
+      "EntitySummary deserialization from file '%s' is not yet implemented",
+      Path.str().c_str());
+}
+
+llvm::json::Object entitySummaryToJSON(const EntitySummary &ES) {
+  // TODO
+  llvm::json::Object Result;
+  return Result;
+}
+
+//----------------------------------------------------------------------------
+// SummaryData
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
+JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
+                                  llvm::StringRef Path) {
+  std::map<EntityId, std::unique_ptr<EntitySummary>> EntityDataMap;
+
+  size_t Index = 0;
+  for (const llvm::json::Value &EntityDataEntryValue : EntityDataArray) {
+    const llvm::json::Object *OptEntityDataEntryObject =
+        EntityDataEntryValue.getAsObject();
+    if (!OptEntityDataEntryObject) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "failed to deserialize entity data map from file '%s': "
+          "element at index %zu is not a JSON object "
+          "(expected object with 'entity_id' and 'entity_summary' fields)",
+          Path.str().c_str(), Index);
+    }
+
+    const llvm::json::Value *EntityIdIntValue =
+        OptEntityDataEntryObject->get("entity_id");
+    if (!EntityIdIntValue) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "failed to deserialize entity data map entry from file '%s' "
+          "at index %zu: missing required field 'entity_id' "
+          "(expected unsigned integer EntityId)",
+          Path.str().c_str(), Index);
+    }
+
+    const std::optional<uint64_t> OptEntityIdInt =
+        EntityIdIntValue->getAsUINT64();
+    if (!OptEntityIdInt) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "failed to deserialize entity data map entry from file '%s' "
+          "at index %zu: field 'entity_id' is not a valid unsigned 64-bit "
+          "integer",
+          Path.str().c_str(), Index);
+    }
+
+    EntityId EI = entityIdFromJSON(*OptEntityIdInt);
+
+    const llvm::json::Object *OptEntitySummaryObject =
+        OptEntityDataEntryObject->getObject("entity_summary");
+    if (!OptEntitySummaryObject) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "failed to deserialize entity data map entry from file '%s' "
+          "at index %zu: missing or invalid field 'entity_summary' "
+          "(expected EntitySummary JSON object)",
+          Path.str().c_str(), Index);
+    }
+
+    auto ExpectedEntitySummary =
+        entitySummaryFromJSON(*OptEntitySummaryObject, Path);
+
+    if (!ExpectedEntitySummary) {
+      return wrapError(
+          ExpectedEntitySummary.takeError(),
+          "failed to deserialize entity data map entry from file '%s' "
+          "at index %zu",
+          Path.str().c_str(), Index);
+    }
+
+    auto [DataIt, DataInserted] = EntityDataMap.insert(
+        {std::move(EI), std::move(*ExpectedEntitySummary)});
+    if (!DataInserted) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "failed to deserialize entity data map from file '%s': "
+          "duplicate EntityId (%zu) found at index %zu",
+          Path.str().c_str(), getEntityIdIndex(DataIt->first), Index);
+    }
+
+    ++Index;
+  }
+
+  return EntityDataMap;
+}
+
+llvm::json::Array JSONFormat::entityDataMapToJSON(
+    const std::map<EntityId, std::unique_ptr<EntitySummary>> &EntityDataMap)
+    const {
+  llvm::json::Array Result;
+  Result.reserve(EntityDataMap.size());
+  for (const auto &[EntityId, EntitySummary] : EntityDataMap) {
+    llvm::json::Object Entry;
+    Entry["entity_id"] = entityIdToJSON(EntityId);
+    Entry["entity_summary"] = entitySummaryToJSON(*EntitySummary);
+    Result.push_back(std::move(Entry));
+  }
+  return Result;
+}
+
+llvm::Expected<
+    std::pair<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+JSONFormat::summaryDataMapEntryFromJSON(
+    const llvm::json::Object &SummaryDataObject, llvm::StringRef Path) {
+
+  std::optional<llvm::StringRef> OptSummaryNameStr =
+      SummaryDataObject.getString("summary_name");
+
+  if (!OptSummaryNameStr) {
+    return llvm::createStringError(
+        std::errc::invalid_argument,
+        "failed to deserialize summary data from file '%s': "
+        "missing required field 'summary_name' "
+        "(expected string identifier for the analysis summary)",
+        Path.str().c_str());
+  }
+
+  SummaryName SN = summaryNameFromJSON(*OptSummaryNameStr);
+
+  const llvm::json::Array *OptEntityDataArray =
+      SummaryDataObject.getArray("summary_data");
+  if (!OptEntityDataArray) {
+    return llvm::createStringError(
+        std::errc::invalid_argument,
+        "failed to deserialize summary data from file '%s' for summary '%s': "
+        "missing or invalid field 'summary_data' "
+        "(expected JSON array of entity summaries)",
+        Path.str().c_str(), SN.str().data());
+  }
+
+  auto ExpectedEntityDataMap = entityDataMapFromJSON(*OptEntityDataArray, Path);
+  if (!ExpectedEntityDataMap)
+    return wrapError(
+        ExpectedEntityDataMap.takeError(),
+        "failed to deserialize summary data from file '%s' for summary '%s'",
+        Path.str().c_str(), SN.str().data());
+
+  return std::make_pair(std::move(SN), std::move(*ExpectedEntityDataMap));
+}
+
+llvm::json::Object JSONFormat::summaryDataMapEntryToJSON(
+    const SummaryName &SN,
+    const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const {
+  llvm::json::Object Result;
+  Result["summary_name"] = summaryNameToJSON(SN);
+  Result["summary_data"] = entityDataMapToJSON(SD);
+  return Result;
+}
+
+//----------------------------------------------------------------------------
+// SummaryDataMap
+//----------------------------------------------------------------------------
+
+llvm::Expected<
+    std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+JSONFormat::readTUSummaryData(llvm::StringRef Path) {
+  if (!llvm::sys::fs::exists(Path)) {
+    return llvm::createStringError(
+        std::errc::no_such_file_or_directory,
+        "failed to read TUSummary data: directory does not exist: '%s'",
+        Path.str().c_str());
+  }
+
+  if (!llvm::sys::fs::is_directory(Path)) {
+    return llvm::createStringError(
+        std::errc::not_a_directory,
+        "failed to read TUSummary data: path is not a directory: '%s'",
+        Path.str().c_str());
+  }
+
+  std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>
+      Data;
+  std::error_code EC;
+
+  llvm::sys::fs::directory_iterator Dir(Path, EC);
+  if (EC) {
+    return llvm::createStringError(
+        EC, "failed to read TUSummary data: cannot iterate directory '%s'",
+        Path.str().c_str());
+  }
+
+  for (llvm::sys::fs::directory_iterator End; Dir != End && !EC;
+       Dir.increment(EC)) {
+    std::string SummaryPath = Dir->path();
+
+    auto ExpectedObject = readJSONObject(SummaryPath);
+    if (!ExpectedObject)
+      return wrapError(ExpectedObject.takeError(),
+                       "failed to read TUSummary data from file '%s'",
+                       SummaryPath.c_str());
+
+    auto ExpectedSummaryDataMap =
+        summaryDataMapEntryFromJSON(*ExpectedObject, SummaryPath);
+    if (!ExpectedSummaryDataMap)
+      return wrapError(ExpectedSummaryDataMap.takeError(),
+                       "failed to read TUSummary data from file '%s'",
+                       SummaryPath.c_str());
+
+    auto [SummaryIt, SummaryInserted] =
+        Data.emplace(std::move(*ExpectedSummaryDataMap));
+    if (!SummaryInserted) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "failed to read TUSummary data from directory '%s': "
+          "duplicate SummaryName '%s' encountered in file '%s'",
+          Path.str().c_str(), SummaryIt->first.str().data(),
+          SummaryPath.c_str());
+    }
+  }
+
+  if (EC) {
+    return llvm::createStringError(EC,
+                                   "failed to read TUSummary data: "
+                                   "error during directory iteration of '%s'",
+                                   Path.str().c_str());
+  }
+
+  return Data;
+}
+
+std::string makeValidFilename(llvm::StringRef Name, size_t Prefix,
+                              char Replacement) {
+  std::string Result;
+  llvm::raw_string_ostream OS(Result);
+  OS << llvm::format("%02d-%s", Prefix, Name.str().c_str());
+
+  for (size_t Index = 0; Index < Result.size(); ++Index) {
+    char &Actual = Result[Index];
+    if (llvm::isAlnum(Actual) || Actual == '-' || Actual == '_')
+      continue;
+    Actual = Replacement;
+  }
+
+  return Result;
+}
+
+llvm::Error JSONFormat::writeTUSummaryData(
+    const std::map<SummaryName,
+                   std::map<EntityId, std::unique_ptr<EntitySummary>>> &Data,
+    llvm::StringRef Path) {
+  if (!llvm::sys::fs::exists(Path)) {
+    return llvm::createStringError(
+        std::errc::no_such_file_or_directory,
+        "failed to write TUSummary data: directory does not exist: '%s'",
+        Path.str().c_str());
+  }
+
+  if (!llvm::sys::fs::is_directory(Path)) {
+    return llvm::createStringError(
+        std::errc::not_a_directory,
+        "failed to write TUSummary data: path is not a directory: '%s'",
+        Path.str().c_str());
+  }
+
+  size_t Index = 0;
+  for (const auto &[SummaryName, DataMap] : Data) {
+    llvm::SmallString<kPathBufferSize> SummaryPath(Path);
+    llvm::sys::path::append(SummaryPath,
+                            makeValidFilename(SummaryName.str(), Index, '_'));
+    llvm::sys::path::replace_extension(SummaryPath, ".json");
+
+    llvm::json::Object Result = summaryDataMapEntryToJSON(SummaryName, DataMap);
+    if (auto Error = writeJSON(std::move(Result), SummaryPath)) {
+      return wrapError(
+          std::move(Error), std::errc::io_error,
+          "failed to write TUSummary data to directory '%s': cannot write "
+          "summary '%s' to file '%s'",
+          Path.str().c_str(), SummaryName.str().data(),
+          SummaryPath.str().data());
+    }
+
+    ++Index;
+  }
+
+  return llvm::Error::success();
+}
+
+//----------------------------------------------------------------------------
+// TUSummary
+//----------------------------------------------------------------------------
+
+llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
+
+  // Populate TUNamespace field.
+  llvm::SmallString<kPathBufferSize> TUNamespacePath(Path);
+  llvm::sys::path::append(TUNamespacePath, TUSummaryTUNamespaceFilename);
+
+  auto ExpectedObject = readJSONObject(TUNamespacePath);
+  if (!ExpectedObject)
+    return wrapError(ExpectedObject.takeError(),
+                     "failed to read TUSummary from '%s'", Path.str().c_str());
+
+  auto ExpectedTUNamespace =
+      buildNamespaceFromJSON(*ExpectedObject, TUNamespacePath);
+  if (!ExpectedTUNamespace)
+    return wrapError(ExpectedTUNamespace.takeError(),
+                     "failed to read TUSummary from '%s'", Path.str().c_str());
+
+  TUSummary Summary(std::move(*ExpectedTUNamespace));
+
+  // Populate IdTable field.
+  {
+    llvm::SmallString<kPathBufferSize> IdTablePath(Path);
+    llvm::sys::path::append(IdTablePath, TUSummaryIdTableFilename);
+
+    auto ExpectedArray = readJSONArray(IdTablePath);
+    if (!ExpectedArray)
+      return wrapError(ExpectedArray.takeError(),
+                       "failed to read TUSummary from '%s'",
+                       Path.str().c_str());
+
+    auto ExpectedIdTable = entityIdTableFromJSON(*ExpectedArray, IdTablePath);
+    if (!ExpectedIdTable)
+      return wrapError(ExpectedIdTable.takeError(),
+                       "failed to read TUSummary from '%s'",
+                       Path.str().c_str());
+
+    getIdTableForDeserialization(Summary) = std::move(*ExpectedIdTable);
+  }
+
+  // Populate Data field.
+  {
+    llvm::SmallString<kPathBufferSize> DataPath(Path);
+    llvm::sys::path::append(DataPath, TUSummaryDataDirname);
+
+    if (!llvm::sys::fs::exists(DataPath)) {
+      return llvm::createStringError(std::errc::no_such_file_or_directory,
+                                     "failed to read TUSummary from '%s': "
+                                     "data directory does not exist: '%s'",
+                                     Path.str().c_str(), DataPath.str().data());
+    }
+
+    if (!llvm::sys::fs::is_directory(DataPath)) {
+      return llvm::createStringError(std::errc::not_a_directory,
+                                     "failed to read TUSummary from '%s': "
+                                     "data path is not a directory: '%s'",
+                                     Path.str().c_str(), DataPath.str().data());
+    }
+
+    auto ExpectedData = readTUSummaryData(DataPath);
+    if (!ExpectedData)
+      return wrapError(ExpectedData.takeError(),
+                       "failed to read TUSummary from '%s'",
+                       Path.str().c_str());
+
+    getDataForDeserialization(Summary) = std::move(*ExpectedData);
+  }
+
+  return Summary;
+}
+
+llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
+                                       llvm::StringRef Dir) {
+  // Serialize TUNamespace field.
+  {
+    llvm::SmallString<kPathBufferSize> TUNamespacePath(Dir);
+    llvm::sys::path::append(TUNamespacePath, TUSummaryTUNamespaceFilename);
+
+    llvm::json::Object BuildNamespaceObj =
+        buildNamespaceToJSON(getTUNamespace(S));
+    if (auto Error = writeJSON(std::move(BuildNamespaceObj), TUNamespacePath)) {
+      return wrapError(std::move(Error), std::errc::io_error,
+                       "failed to write TUSummary to '%s': "
+                       "cannot write TUNamespace file '%s'",
+                       Dir.str().c_str(), TUNamespacePath.str().data());
+    }
+  }
+
+  // Serialize IdTable field.
+  {
+    llvm::SmallString<kPathBufferSize> IdTablePath(Dir);
+    llvm::sys::path::append(IdTablePath, TUSummaryIdTableFilename);
+
+    llvm::json::Array IdTableObj = entityIdTableToJSON(getIdTable(S));
+    if (auto Error = writeJSON(std::move(IdTableObj), IdTablePath)) {
+      return wrapError(std::move(Error), std::errc::io_error,
+                       "failed to write TUSummary to '%s': "
+                       "cannot write IdTable file '%s'",
+                       Dir.str().c_str(), IdTablePath.str().data());
+    }
+  }
+
+  // Serialize Data field.
+  {
+    llvm::SmallString<kPathBufferSize> DataPath(Dir);
+    llvm::sys::path::append(DataPath, TUSummaryDataDirname);
+
+    // Create the data directory if it doesn't exist
+    if (std::error_code EC = llvm::sys::fs::create_directory(DataPath)) {
+      // If error is not "already exists", return error
+      if (EC != std::errc::file_exists) {
+        return llvm::createStringError(EC,
+                                       "failed to write TUSummary to '%s': "
+                                       "cannot create data directory '%s'",
+                                       Dir.str().c_str(),
+                                       DataPath.str().data());
+      }
+    }
+
+    // Verify it's a directory (could be a file with the same name)
+    if (llvm::sys::fs::exists(DataPath) &&
+        !llvm::sys::fs::is_directory(DataPath)) {
+      return llvm::createStringError(
+          std::errc::not_a_directory,
+          "failed to write TUSummary to '%s': data path exists but is not a "
+          "directory: '%s'",
+          Dir.str().c_str(), DataPath.str().data());
+    }
+
+    if (auto Error = writeTUSummaryData(getData(S), DataPath)) {
+      return wrapError(std::move(Error), std::errc::io_error,
+                       "failed to write TUSummary to '%s': cannot write data",
+                       Dir.str().c_str());
+    }
+  }
+
+  return llvm::Error::success();
+}
diff --git a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
index ee155d22afa9b..4e5dd47b7b17e 100644
--- a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
@@ -14,6 +14,24 @@
 
 using namespace clang::ssaf;
 
+size_t SerializationFormat::getEntityIdIndex(const EntityId &EI) {
+  return EI.Index;
+}
+
+EntityId SerializationFormat::makeEntityId(const size_t Index) {
+  return EntityId(Index);
+}
+
+const std::map<EntityName, EntityId> &
+SerializationFormat::getEntities(const EntityIdTable &EIT) {
+  return EIT.Entities;
+}
+
+std::map<EntityName, EntityId> &
+SerializationFormat::getEntitiesForDeserialization(EntityIdTable &EIT) {
+  return EIT.Entities;
+}
+
 EntityIdTable &SerializationFormat::getIdTableForDeserialization(TUSummary &S) {
   return S.IdTable;
 }
@@ -23,6 +41,11 @@ SerializationFormat::getTUNamespaceForDeserialization(TUSummary &S) {
   return S.TUNamespace;
 }
 
+std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>> &
+SerializationFormat::getDataForDeserialization(TUSummary &S) {
+  return S.Data;
+}
+
 const EntityIdTable &SerializationFormat::getIdTable(const TUSummary &S) {
   return S.IdTable;
 }
@@ -31,6 +54,12 @@ const BuildNamespace &SerializationFormat::getTUNamespace(const TUSummary &S) {
   return S.TUNamespace;
 }
 
+const std::map<SummaryName,
+               std::map<EntityId, std::unique_ptr<EntitySummary>>> &
+SerializationFormat::getData(const TUSummary &S) {
+  return S.Data;
+}
+
 BuildNamespaceKind
 SerializationFormat::getBuildNamespaceKind(const BuildNamespace &BN) {
   return BN.Kind;
diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt b/clang/unittests/Analysis/Scalable/CMakeLists.txt
index a21002e313ead..4732f5f600bcf 100644
--- a/clang/unittests/Analysis/Scalable/CMakeLists.txt
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -4,6 +4,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
   EntityIdTest.cpp
   EntityIdTableTest.cpp
   EntityNameTest.cpp
+  JSONFormatTest.cpp
   Registries/MockSummaryExtractor1.cpp
   Registries/MockSummaryExtractor2.cpp
   Registries/SummaryExtractorRegistryTest.cpp
diff --git a/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
new file mode 100644
index 0000000000000..db38ce23565da
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
@@ -0,0 +1,1127 @@
+//===- unittests/Analysis/Scalable/JSONFormatTest.cpp -------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
+#include "clang/Analysis/Scalable/Model/EntityId.h"
+#include "clang/Analysis/Scalable/Model/EntityIdTable.h"
+#include "clang/Analysis/Scalable/Model/EntityName.h"
+#include "clang/Analysis/Scalable/Model/SummaryName.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <fstream>
+
+using namespace clang::ssaf;
+using llvm::Failed;
+using llvm::Succeeded;
+using ::testing::AllOf;
+using ::testing::HasSubstr;
+
+namespace {
+
+// Helper function to check that an error message contains all specified
+// substrings
+::testing::Matcher<std::string>
+ContainsAllSubstrings(std::initializer_list<const char *> substrings) {
+  std::vector<::testing::Matcher<std::string>> matchers;
+  for (const char *substr : substrings) {
+    matchers.push_back(HasSubstr(substr));
+  }
+  return ::testing::AllOfArray(matchers);
+}
+
+//===----------------------------------------------------------------------===//
+// Test Fixtures and Helpers
+//===----------------------------------------------------------------------===//
+
+// Helper class to manage temporary directories for testing
+class TempDir {
+  llvm::SmallString<128> Path;
+  std::error_code EC;
+
+public:
+  TempDir() {
+    EC = llvm::sys::fs::createUniqueDirectory("JSONFormatTest", Path);
+  }
+
+  ~TempDir() {
+    if (!EC && llvm::sys::fs::exists(Path)) {
+      llvm::sys::fs::remove_directories(Path);
+    }
+  }
+
+  llvm::StringRef path() const { return Path; }
+  bool isValid() const { return !EC; }
+};
+
+// Helper function to write a file with content
+void writeFile(llvm::StringRef Path, llvm::StringRef Content) {
+  std::ofstream File(Path.str());
+  File << Content.str();
+  File.close();
+}
+
+// Helper function to create a directory
+void createDir(llvm::StringRef Path) {
+  llvm::sys::fs::create_directories(Path);
+}
+
+// Base test fixture for JSONFormat tests
+class JSONFormatTestBase : public ::testing::Test {
+protected:
+  JSONFormat Format;
+};
+
+//===----------------------------------------------------------------------===//
+// TUSummary Read Tests - TUNamespace
+//===----------------------------------------------------------------------===//
+
+class JSONFormatReadTUNamespaceTest : public JSONFormatTestBase {};
+
+TEST_F(JSONFormatReadTUNamespaceTest, NonexistentDirectory) {
+  auto Result = Format.readTUSummary("/nonexistent/path");
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings(
+                  {"failed to read TUSummary from '/nonexistent/path'",
+                   "failed to read JSON from file", "tu_namespace.json",
+                   "failed to validate JSON file", "file does not exist"}));
+}
+
+TEST_F(JSONFormatReadTUNamespaceTest, MissingTUNamespaceFile) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg, ContainsAllSubstrings({"failed to read TUSummary from",
+                                               "failed to read JSON from file",
+                                               "tu_namespace.json",
+                                               "file does not exist"}));
+}
+
+TEST_F(JSONFormatReadTUNamespaceTest, InvalidJSONSyntax) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+
+  llvm::SmallString<128> TUNamespacePath(Dir.path());
+  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+  writeFile(TUNamespacePath, "{ invalid json }");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg, ContainsAllSubstrings({"failed to read TUSummary from",
+                                               "failed to read JSON from file",
+                                               "tu_namespace.json"}));
+}
+
+TEST_F(JSONFormatReadTUNamespaceTest, NotJSONObject) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+
+  llvm::SmallString<128> TUNamespacePath(Dir.path());
+  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+  writeFile(TUNamespacePath, "[]");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings({"failed to read TUSummary from",
+                                     "failed to read JSON object from file",
+                                     "tu_namespace.json"}));
+}
+
+TEST_F(JSONFormatReadTUNamespaceTest, MissingKindField) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+
+  llvm::SmallString<128> TUNamespacePath(Dir.path());
+  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+  writeFile(TUNamespacePath, R"({"name": "test.cpp"})");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(
+      ErrorMsg,
+      ContainsAllSubstrings(
+          {"failed to read TUSummary from",
+           "failed to deserialize BuildNamespace from file",
+           "tu_namespace.json",
+           "missing required field 'kind' (expected BuildNamespaceKind)"}));
+}
+
+TEST_F(JSONFormatReadTUNamespaceTest, InvalidKindValue) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+
+  llvm::SmallString<128> TUNamespacePath(Dir.path());
+  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+  writeFile(TUNamespacePath, R"({"kind": "InvalidKind", "name": "test.cpp"})");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings(
+                  {"failed to read TUSummary from",
+                   "failed to deserialize BuildNamespace from file",
+                   "tu_namespace.json",
+                   "invalid 'kind' BuildNamespaceKind value 'InvalidKind'"}));
+}
+
+TEST_F(JSONFormatReadTUNamespaceTest, MissingNameField) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+
+  llvm::SmallString<128> TUNamespacePath(Dir.path());
+  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+  writeFile(TUNamespacePath, R"({"kind": "compilation_unit"})");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings(
+                  {"failed to read TUSummary from",
+                   "failed to deserialize BuildNamespace from file",
+                   "tu_namespace.json", "missing required field 'name'"}));
+}
+
+//===----------------------------------------------------------------------===//
+// TUSummary Read Tests - IdTable
+//===----------------------------------------------------------------------===//
+
+class JSONFormatReadIdTableTest : public JSONFormatTestBase {
+protected:
+  void SetUpValidTUNamespace(TempDir &Dir) {
+    llvm::SmallString<128> TUNamespacePath(Dir.path());
+    llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+    writeFile(TUNamespacePath,
+              R"({"kind": "compilation_unit", "name": "test.cpp"})");
+  }
+};
+
+TEST_F(JSONFormatReadIdTableTest, MissingIdTableFile) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespace(Dir);
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings({"failed to read TUSummary from",
+                                     "failed to read JSON from file",
+                                     "id_table.json", "file does not exist"}));
+}
+
+TEST_F(JSONFormatReadIdTableTest, NotJSONArray) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespace(Dir);
+
+  llvm::SmallString<128> IdTablePath(Dir.path());
+  llvm::sys::path::append(IdTablePath, "id_table.json");
+  writeFile(IdTablePath, "{}");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings({"failed to read TUSummary from",
+                                     "failed to read JSON array from file",
+                                     "id_table.json"}));
+}
+
+TEST_F(JSONFormatReadIdTableTest, ElementNotObject) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespace(Dir);
+
+  llvm::SmallString<128> IdTablePath(Dir.path());
+  llvm::sys::path::append(IdTablePath, "id_table.json");
+  writeFile(IdTablePath, "[\"not an object\"]");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(
+      ErrorMsg,
+      ContainsAllSubstrings(
+          {"failed to read TUSummary from",
+           "failed to deserialize EntityIdTable from file", "id_table.json",
+           "element at index 0 is not a JSON object",
+           "expected EntityIdTable entry with 'id' and 'name' fields"}));
+}
+
+TEST_F(JSONFormatReadIdTableTest, EntryMissingName) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespace(Dir);
+
+  llvm::SmallString<128> IdTablePath(Dir.path());
+  llvm::sys::path::append(IdTablePath, "id_table.json");
+  writeFile(IdTablePath, R"([{"id": 0}])");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings(
+                  {"failed to read TUSummary from",
+                   "failed to deserialize EntityIdTable entry from file",
+                   "id_table.json",
+                   "missing or invalid field 'name' (expected EntityName JSON "
+                   "object)"}));
+}
+
+TEST_F(JSONFormatReadIdTableTest, EntryMissingId) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespace(Dir);
+
+  llvm::SmallString<128> IdTablePath(Dir.path());
+  llvm::sys::path::append(IdTablePath, "id_table.json");
+  writeFile(IdTablePath, R"([{
+    "name": {
+      "usr": "c:@F at foo",
+      "suffix": "",
+      "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+    }
+  }])");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings(
+                  {"failed to read TUSummary from",
+                   "failed to deserialize EntityIdTable entry from file",
+                   "id_table.json",
+                   "missing required field 'id' (expected unsigned integer "
+                   "EntityId)"}));
+}
+
+TEST_F(JSONFormatReadIdTableTest, EntryIdNotInteger) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespace(Dir);
+
+  llvm::SmallString<128> IdTablePath(Dir.path());
+  llvm::sys::path::append(IdTablePath, "id_table.json");
+  writeFile(IdTablePath, R"([{
+    "id": "not a number",
+    "name": {
+      "usr": "c:@F at foo",
+      "suffix": "",
+      "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+    }
+  }])");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(
+      ErrorMsg,
+      ContainsAllSubstrings(
+          {"failed to read TUSummary from",
+           "failed to deserialize EntityIdTable entry from file",
+           "id_table.json", "field 'id' is not a valid unsigned 64-bit integer",
+           "expected non-negative EntityId value"}));
+}
+
+TEST_F(JSONFormatReadIdTableTest, DuplicateEntityName) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespace(Dir);
+
+  llvm::SmallString<128> IdTablePath(Dir.path());
+  llvm::sys::path::append(IdTablePath, "id_table.json");
+  writeFile(IdTablePath, R"([
+    {
+      "id": 0,
+      "name": {
+        "usr": "c:@F at foo",
+        "suffix": "",
+        "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+      }
+    },
+    {
+      "id": 1,
+      "name": {
+        "usr": "c:@F at foo",
+        "suffix": "",
+        "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+      }
+    }
+  ])");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings(
+                  {"failed to read TUSummary from",
+                   "failed to deserialize EntityIdTable from file",
+                   "id_table.json", "duplicate EntityName found at index 1",
+                   "EntityId=0 already exists in table"}));
+}
+
+//===----------------------------------------------------------------------===//
+// TUSummary Read Tests - EntityName
+//===----------------------------------------------------------------------===//
+
+class JSONFormatReadEntityNameTest : public JSONFormatTestBase {
+protected:
+  void SetUpValidTUNamespaceAndPartialIdTable(TempDir &Dir,
+                                              llvm::StringRef EntityNameJson) {
+    llvm::SmallString<128> TUNamespacePath(Dir.path());
+    llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+    writeFile(TUNamespacePath,
+              R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+    llvm::SmallString<128> IdTablePath(Dir.path());
+    llvm::sys::path::append(IdTablePath, "id_table.json");
+    std::string JsonContent = "[{\"id\": 0, \"name\": ";
+    JsonContent += EntityNameJson.str();
+    JsonContent += "}]";
+    writeFile(IdTablePath, JsonContent);
+  }
+};
+
+TEST_F(JSONFormatReadEntityNameTest, MissingUSR) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+    "suffix": "",
+    "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+  })");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(
+      ErrorMsg,
+      ContainsAllSubstrings(
+          {"failed to read TUSummary from",
+           "failed to deserialize EntityName from file", "id_table.json",
+           "missing required field 'usr' (Unified Symbol Resolution string)"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, MissingSuffix) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+    "usr": "c:@F at foo",
+    "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+  })");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings(
+                  {"failed to read TUSummary from",
+                   "failed to deserialize EntityName from file",
+                   "id_table.json", "missing required field 'suffix'"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, MissingNamespace) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+    "usr": "c:@F at foo",
+    "suffix": ""
+  })");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings(
+                  {"failed to read TUSummary from",
+                   "failed to deserialize EntityName from file",
+                   "id_table.json", "missing or invalid field 'namespace'",
+                   "expected JSON array of BuildNamespace objects"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, NamespaceNotArray) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+    "usr": "c:@F at foo",
+    "suffix": "",
+    "namespace": "not an array"
+  })");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings(
+                  {"failed to read TUSummary from",
+                   "failed to deserialize EntityName from file",
+                   "id_table.json", "missing or invalid field 'namespace'",
+                   "expected JSON array of BuildNamespace objects"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, NamespaceElementNotObject) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+    "usr": "c:@F at foo",
+    "suffix": "",
+    "namespace": ["not an object"]
+  })");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings(
+                  {"failed to read TUSummary from",
+                   "failed to deserialize NestedBuildNamespace from file",
+                   "id_table.json", "element at index 0 is not a JSON object",
+                   "expected BuildNamespace object"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, NamespaceMissingKind) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+    "usr": "c:@F at foo",
+    "suffix": "",
+    "namespace": [{"name": "test.cpp"}]
+  })");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(
+      ErrorMsg,
+      ContainsAllSubstrings(
+          {"failed to read TUSummary from",
+           "failed to deserialize NestedBuildNamespace from file",
+           "id_table.json", "at index 0",
+           "failed to deserialize BuildNamespace from file",
+           "missing required field 'kind' (expected BuildNamespaceKind)"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, NamespaceInvalidKind) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+    "usr": "c:@F at foo",
+    "suffix": "",
+    "namespace": [{"kind": "InvalidKind", "name": "test.cpp"}]
+  })");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings(
+                  {"failed to read TUSummary from",
+                   "failed to deserialize NestedBuildNamespace from file",
+                   "id_table.json", "at index 0",
+                   "failed to deserialize BuildNamespace from file",
+                   "invalid 'kind' BuildNamespaceKind value 'InvalidKind'"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, NamespaceMissingName) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+    "usr": "c:@F at foo",
+    "suffix": "",
+    "namespace": [{"kind": "compilation_unit"}]
+  })");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings(
+                  {"failed to read TUSummary from",
+                   "failed to deserialize NestedBuildNamespace from file",
+                   "id_table.json", "at index 0",
+                   "failed to deserialize BuildNamespace from file",
+                   "missing required field 'name'"}));
+}
+
+//===----------------------------------------------------------------------===//
+// TUSummary Read Tests - Data Directory and Files
+//===----------------------------------------------------------------------===//
+
+class JSONFormatReadDataTest : public JSONFormatTestBase {
+protected:
+  void SetUpValidTUNamespaceAndIdTable(TempDir &Dir) {
+    llvm::SmallString<128> TUNamespacePath(Dir.path());
+    llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+    writeFile(TUNamespacePath,
+              R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+    llvm::SmallString<128> IdTablePath(Dir.path());
+    llvm::sys::path::append(IdTablePath, "id_table.json");
+    writeFile(IdTablePath, "[]");
+  }
+};
+
+TEST_F(JSONFormatReadDataTest, MissingDataDirectory) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceAndIdTable(Dir);
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings({"failed to read TUSummary from",
+                                     "data directory does not exist"}));
+}
+
+TEST_F(JSONFormatReadDataTest, DataPathIsFile) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceAndIdTable(Dir);
+
+  llvm::SmallString<128> DataPath(Dir.path());
+  llvm::sys::path::append(DataPath, "data");
+  writeFile(DataPath, "content");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings({"failed to read TUSummary from",
+                                     "data path is not a directory"}));
+}
+
+TEST_F(JSONFormatReadDataTest, NonJSONFileInDataDirectory) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceAndIdTable(Dir);
+
+  llvm::SmallString<128> DataPath(Dir.path());
+  llvm::sys::path::append(DataPath, "data");
+  createDir(DataPath);
+
+  llvm::SmallString<128> SummaryPath(DataPath);
+  llvm::sys::path::append(SummaryPath, "summary.txt");
+  writeFile(SummaryPath, "{}");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings(
+                  {"failed to read TUSummary from",
+                   "failed to read TUSummary data from file", "summary.txt",
+                   "failed to validate JSON file", "not a JSON file"}));
+}
+
+TEST_F(JSONFormatReadDataTest, FileNotJSONObject) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceAndIdTable(Dir);
+
+  llvm::SmallString<128> DataPath(Dir.path());
+  llvm::sys::path::append(DataPath, "data");
+  createDir(DataPath);
+
+  llvm::SmallString<128> SummaryPath(DataPath);
+  llvm::sys::path::append(SummaryPath, "summary.json");
+  writeFile(SummaryPath, "[]");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings({"failed to read TUSummary from",
+                                     "failed to read TUSummary data from file",
+                                     "summary.json",
+                                     "failed to read JSON object from file"}));
+}
+
+TEST_F(JSONFormatReadDataTest, MissingSummaryName) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceAndIdTable(Dir);
+
+  llvm::SmallString<128> DataPath(Dir.path());
+  llvm::sys::path::append(DataPath, "data");
+  createDir(DataPath);
+
+  llvm::SmallString<128> SummaryPath(DataPath);
+  llvm::sys::path::append(SummaryPath, "summary.json");
+  writeFile(SummaryPath, R"({"summary_data": []})");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings(
+                  {"failed to read TUSummary from",
+                   "failed to deserialize summary data from file",
+                   "summary.json", "missing required field 'summary_name'",
+                   "expected string identifier for the analysis summary"}));
+}
+
+TEST_F(JSONFormatReadDataTest, MissingSummaryData) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceAndIdTable(Dir);
+
+  llvm::SmallString<128> DataPath(Dir.path());
+  llvm::sys::path::append(DataPath, "data");
+  createDir(DataPath);
+
+  llvm::SmallString<128> SummaryPath(DataPath);
+  llvm::sys::path::append(SummaryPath, "summary.json");
+  writeFile(SummaryPath, R"({"summary_name": "test"})");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg, ContainsAllSubstrings(
+                            {"failed to read TUSummary from",
+                             "failed to deserialize summary data from file",
+                             "summary.json", "for summary 'test'",
+                             "missing or invalid field 'summary_data'",
+                             "expected JSON array of entity summaries"}));
+}
+
+TEST_F(JSONFormatReadDataTest, SummaryDataNotArray) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceAndIdTable(Dir);
+
+  llvm::SmallString<128> DataPath(Dir.path());
+  llvm::sys::path::append(DataPath, "data");
+  createDir(DataPath);
+
+  llvm::SmallString<128> SummaryPath(DataPath);
+  llvm::sys::path::append(SummaryPath, "summary.json");
+  writeFile(SummaryPath,
+            R"({"summary_name": "test", "summary_data": "not an array"})");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg, ContainsAllSubstrings(
+                            {"failed to read TUSummary from",
+                             "failed to deserialize summary data from file",
+                             "summary.json", "for summary 'test'",
+                             "missing or invalid field 'summary_data'",
+                             "expected JSON array of entity summaries"}));
+}
+
+TEST_F(JSONFormatReadDataTest, DuplicateSummaryName) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceAndIdTable(Dir);
+
+  llvm::SmallString<128> DataPath(Dir.path());
+  llvm::sys::path::append(DataPath, "data");
+  createDir(DataPath);
+
+  llvm::SmallString<128> SummaryPath1(DataPath);
+  llvm::sys::path::append(SummaryPath1, "summary1.json");
+  writeFile(SummaryPath1, R"({"summary_name": "test", "summary_data": []})");
+
+  llvm::SmallString<128> SummaryPath2(DataPath);
+  llvm::sys::path::append(SummaryPath2, "summary2.json");
+  writeFile(SummaryPath2, R"({"summary_name": "test", "summary_data": []})");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(
+      ErrorMsg,
+      ContainsAllSubstrings(
+          {"failed to read TUSummary from", "failed to read TUSummary data",
+           "duplicate SummaryName 'test' encountered in file"}));
+}
+
+//===----------------------------------------------------------------------===//
+// TUSummary Read Tests - Entity Data
+//===----------------------------------------------------------------------===//
+
+class JSONFormatReadEntityDataTest : public JSONFormatTestBase {
+protected:
+  void SetUpValidTUNamespaceIdTableAndDataDir(TempDir &Dir,
+                                              llvm::StringRef SummaryDataJson) {
+    llvm::SmallString<128> TUNamespacePath(Dir.path());
+    llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+    writeFile(TUNamespacePath,
+              R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+    llvm::SmallString<128> IdTablePath(Dir.path());
+    llvm::sys::path::append(IdTablePath, "id_table.json");
+    writeFile(IdTablePath, "[]");
+
+    llvm::SmallString<128> DataPath(Dir.path());
+    llvm::sys::path::append(DataPath, "data");
+    createDir(DataPath);
+
+    llvm::SmallString<128> SummaryPath(DataPath);
+    llvm::sys::path::append(SummaryPath, "summary.json");
+    std::string JsonContent = R"({"summary_name": "test", "summary_data": )";
+    JsonContent += SummaryDataJson.str();
+    JsonContent += "}";
+    writeFile(SummaryPath, JsonContent);
+  }
+};
+
+TEST_F(JSONFormatReadEntityDataTest, ElementNotObject) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceIdTableAndDataDir(Dir, "[\"not an object\"]");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(
+      ErrorMsg,
+      ContainsAllSubstrings(
+          {"failed to read TUSummary from",
+           "failed to deserialize entity data map from file", "summary.json",
+           "element at index 0 is not a JSON object",
+           "expected object with 'entity_id' and 'entity_summary' fields"}));
+}
+
+TEST_F(JSONFormatReadEntityDataTest, MissingEntityId) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceIdTableAndDataDir(Dir, R"([{"entity_summary": {}}])");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(
+      ErrorMsg,
+      ContainsAllSubstrings(
+          {"failed to read TUSummary from",
+           "failed to deserialize entity data map entry from file",
+           "summary.json", "at index 0", "missing required field 'entity_id'",
+           "expected unsigned integer EntityId"}));
+}
+
+TEST_F(JSONFormatReadEntityDataTest, EntityIdNotInteger) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceIdTableAndDataDir(
+      Dir, R"([{"entity_id": "not a number", "entity_summary": {}}])");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(
+      ErrorMsg,
+      ContainsAllSubstrings(
+          {"failed to read TUSummary from",
+           "failed to deserialize entity data map entry from file",
+           "summary.json", "at index 0",
+           "field 'entity_id' is not a valid unsigned 64-bit integer"}));
+}
+
+TEST_F(JSONFormatReadEntityDataTest, MissingEntitySummary) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceIdTableAndDataDir(Dir, R"([{"entity_id": 0}])");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings(
+                  {"failed to read TUSummary from",
+                   "failed to deserialize entity data map entry from file",
+                   "summary.json", "at index 0",
+                   "missing or invalid field 'entity_summary'",
+                   "expected EntitySummary JSON object"}));
+}
+
+TEST_F(JSONFormatReadEntityDataTest, EntitySummaryNotObject) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceIdTableAndDataDir(
+      Dir, R"([{"entity_id": 0, "entity_summary": "not an object"}])");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings(
+                  {"failed to read TUSummary from",
+                   "failed to deserialize entity data map entry from file",
+                   "summary.json", "at index 0",
+                   "missing or invalid field 'entity_summary'",
+                   "expected EntitySummary JSON object"}));
+}
+
+TEST_F(JSONFormatReadEntityDataTest,
+       EntitySummaryDeserializationNotImplemented) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+  SetUpValidTUNamespaceIdTableAndDataDir(
+      Dir, R"([{"entity_id": 0, "entity_summary": {}}])");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  ASSERT_FALSE(Result);
+  std::string ErrorMsg = llvm::toString(Result.takeError());
+  EXPECT_THAT(ErrorMsg,
+              ContainsAllSubstrings(
+                  {"failed to read TUSummary from",
+                   "failed to deserialize entity data map entry from file",
+                   "summary.json", "at index 0",
+                   "EntitySummary deserialization from file",
+                   "is not yet implemented"}));
+}
+
+// Note: DuplicateEntityId test cannot be implemented without EntitySummary
+// deserialization support, as the error occurs during EntitySummary parsing
+// before the duplicate check is reached.
+
+//===----------------------------------------------------------------------===//
+// TUSummary Write Tests
+//===----------------------------------------------------------------------===//
+
+class JSONFormatWriteTest : public JSONFormatTestBase {};
+
+TEST_F(JSONFormatWriteTest, Success) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+
+  BuildNamespace TUNamespace = BuildNamespace::makeCompilationUnit("test.cpp");
+  TUSummary Summary(TUNamespace);
+
+  auto Error = Format.writeTUSummary(Summary, Dir.path());
+  EXPECT_THAT_ERROR(std::move(Error), Succeeded());
+
+  // Verify files were created
+  llvm::SmallString<128> TUNamespacePath(Dir.path());
+  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+  EXPECT_TRUE(llvm::sys::fs::exists(TUNamespacePath));
+
+  llvm::SmallString<128> IdTablePath(Dir.path());
+  llvm::sys::path::append(IdTablePath, "id_table.json");
+  EXPECT_TRUE(llvm::sys::fs::exists(IdTablePath));
+
+  llvm::SmallString<128> DataPath(Dir.path());
+  llvm::sys::path::append(DataPath, "data");
+  EXPECT_TRUE(llvm::sys::fs::exists(DataPath));
+  EXPECT_TRUE(llvm::sys::fs::is_directory(DataPath));
+}
+
+TEST_F(JSONFormatWriteTest, DataDirectoryExistsAsFile) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+
+  // Create 'data' as a file instead of directory
+  llvm::SmallString<128> DataPath(Dir.path());
+  llvm::sys::path::append(DataPath, "data");
+  writeFile(DataPath, "content");
+
+  BuildNamespace TUNamespace = BuildNamespace::makeCompilationUnit("test.cpp");
+  TUSummary Summary(TUNamespace);
+
+  auto Error = Format.writeTUSummary(Summary, Dir.path());
+  ASSERT_TRUE(!!Error);
+  std::string ErrorMsg = llvm::toString(std::move(Error));
+  EXPECT_THAT(ErrorMsg, ContainsAllSubstrings(
+                            {"failed to write TUSummary to",
+                             "data path exists but is not a directory"}));
+}
+
+//===----------------------------------------------------------------------===//
+// TUSummary Success Cases
+//===----------------------------------------------------------------------===//
+
+class JSONFormatSuccessTest : public JSONFormatTestBase {};
+
+TEST_F(JSONFormatSuccessTest, ReadWithEmptyIdTable) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+
+  llvm::SmallString<128> TUNamespacePath(Dir.path());
+  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+  writeFile(TUNamespacePath,
+            R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+  llvm::SmallString<128> IdTablePath(Dir.path());
+  llvm::sys::path::append(IdTablePath, "id_table.json");
+  writeFile(IdTablePath, "[]");
+
+  llvm::SmallString<128> DataPath(Dir.path());
+  llvm::sys::path::append(DataPath, "data");
+  createDir(DataPath);
+
+  auto Result = Format.readTUSummary(Dir.path());
+  EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
+}
+
+TEST_F(JSONFormatSuccessTest, ReadWithNonEmptyIdTable) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+
+  llvm::SmallString<128> TUNamespacePath(Dir.path());
+  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+  writeFile(TUNamespacePath,
+            R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+  llvm::SmallString<128> IdTablePath(Dir.path());
+  llvm::sys::path::append(IdTablePath, "id_table.json");
+  writeFile(IdTablePath, R"([
+    {
+      "id": 0,
+      "name": {
+        "usr": "c:@F at foo",
+        "suffix": "",
+        "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+      }
+    },
+    {
+      "id": 1,
+      "name": {
+        "usr": "c:@F at bar",
+        "suffix": "1",
+        "namespace": [
+          {"kind": "compilation_unit", "name": "test.cpp"},
+          {"kind": "link_unit", "name": "libtest.so"}
+        ]
+      }
+    }
+  ])");
+
+  llvm::SmallString<128> DataPath(Dir.path());
+  llvm::sys::path::append(DataPath, "data");
+  createDir(DataPath);
+
+  auto Result = Format.readTUSummary(Dir.path());
+  EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
+}
+
+TEST_F(JSONFormatSuccessTest, ReadWithEmptyData) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+
+  llvm::SmallString<128> TUNamespacePath(Dir.path());
+  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+  writeFile(TUNamespacePath,
+            R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+  llvm::SmallString<128> IdTablePath(Dir.path());
+  llvm::sys::path::append(IdTablePath, "id_table.json");
+  writeFile(IdTablePath, "[]");
+
+  llvm::SmallString<128> DataPath(Dir.path());
+  llvm::sys::path::append(DataPath, "data");
+  createDir(DataPath);
+
+  // Add an empty summary data file
+  llvm::SmallString<128> SummaryPath(DataPath);
+  llvm::sys::path::append(SummaryPath, "summary.json");
+  writeFile(SummaryPath, R"({"summary_name": "test", "summary_data": []})");
+
+  auto Result = Format.readTUSummary(Dir.path());
+  EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
+}
+
+TEST_F(JSONFormatSuccessTest, ReadWithLinkUnitNamespace) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+
+  llvm::SmallString<128> TUNamespacePath(Dir.path());
+  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+  writeFile(TUNamespacePath, R"({"kind": "link_unit", "name": "libtest.so"})");
+
+  llvm::SmallString<128> IdTablePath(Dir.path());
+  llvm::sys::path::append(IdTablePath, "id_table.json");
+  writeFile(IdTablePath, "[]");
+
+  llvm::SmallString<128> DataPath(Dir.path());
+  llvm::sys::path::append(DataPath, "data");
+  createDir(DataPath);
+
+  auto Result = Format.readTUSummary(Dir.path());
+  EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
+}
+
+//===----------------------------------------------------------------------===//
+// Round-Trip Tests
+//===----------------------------------------------------------------------===//
+
+class JSONFormatRoundTripTest : public JSONFormatTestBase {};
+
+TEST_F(JSONFormatRoundTripTest, EmptyIdTable) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+
+  BuildNamespace TUNamespace = BuildNamespace::makeCompilationUnit("test.cpp");
+  TUSummary Summary(TUNamespace);
+
+  auto WriteError = Format.writeTUSummary(Summary, Dir.path());
+  EXPECT_THAT_ERROR(std::move(WriteError), Succeeded());
+
+  auto ReadResult = Format.readTUSummary(Dir.path());
+  EXPECT_THAT_ERROR(ReadResult.takeError(), Succeeded());
+}
+
+TEST_F(JSONFormatRoundTripTest, NonEmptyIdTable) {
+  TempDir Dir;
+  ASSERT_TRUE(Dir.isValid());
+
+  // Manually create the files to test roundtrip
+  llvm::SmallString<128> TUNamespacePath(Dir.path());
+  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+  writeFile(TUNamespacePath,
+            R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+  llvm::SmallString<128> IdTablePath(Dir.path());
+  llvm::sys::path::append(IdTablePath, "id_table.json");
+  writeFile(IdTablePath, R"([
+    {
+      "id": 0,
+      "name": {
+        "usr": "c:@F at foo",
+        "suffix": "",
+        "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+      }
+    }
+  ])");
+
+  llvm::SmallString<128> DataPath(Dir.path());
+  llvm::sys::path::append(DataPath, "data");
+  createDir(DataPath);
+
+  auto ReadResult = Format.readTUSummary(Dir.path());
+  ASSERT_THAT_EXPECTED(ReadResult, Succeeded());
+
+  TempDir Dir2;
+  ASSERT_TRUE(Dir2.isValid());
+
+  auto WriteError = Format.writeTUSummary(*ReadResult, Dir2.path());
+  EXPECT_THAT_ERROR(std::move(WriteError), Succeeded());
+
+  // Verify the written files
+  llvm::SmallString<128> TUNamespacePath2(Dir2.path());
+  llvm::sys::path::append(TUNamespacePath2, "tu_namespace.json");
+  EXPECT_TRUE(llvm::sys::fs::exists(TUNamespacePath2));
+}
+
+} // namespace

>From 47ed1daa2b4db92b08656ed509e95410e92d378d Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 5 Feb 2026 13:51:50 -0800
Subject: [PATCH 4/7] Fix return types, incorporate VFS APIs, and fix tests.

---
 .../Scalable/Serialization/JSONFormat.h       |   3 +-
 .../Serialization/SerializationFormat.h       |  21 +--
 .../Scalable/Serialization/JSONFormat.cpp     | 132 ++++++++++++------
 .../Serialization/SerializationFormat.cpp     |  40 ++----
 .../Analysis/Scalable/JSONFormatTest.cpp      |   2 +-
 .../Registries/MockSerializationFormat.cpp    |  46 +++---
 .../Registries/MockSerializationFormat.h      |   6 +-
 .../SerializationFormatRegistryTest.cpp       |   7 +-
 8 files changed, 151 insertions(+), 106 deletions(-)

diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
index 476853af0dce4..23e37138d6926 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -23,7 +23,8 @@ class SummaryName;
 
 class JSONFormat : public SerializationFormat {
 public:
-  JSONFormat() = default;
+  explicit JSONFormat(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
+      : SerializationFormat(FS) {}
 
   ~JSONFormat() = default;
 
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index 1d3df0dc55e08..54098939cdfe4 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -42,21 +42,16 @@ class SerializationFormat {
   static size_t getEntityIdIndex(const EntityId &EI);
   static EntityId makeEntityId(const size_t Index);
 
-  static const std::map<EntityName, EntityId> &
+  static const decltype(EntityIdTable::Entities) &
   getEntities(const EntityIdTable &EIT);
-  static std::map<EntityName, EntityId> &
-  getEntitiesForDeserialization(EntityIdTable &EIT);
-
-  static EntityIdTable &getIdTableForDeserialization(TUSummary &S);
-  static BuildNamespace &getTUNamespaceForDeserialization(TUSummary &S);
-  static std::map<SummaryName,
-                  std::map<EntityId, std::unique_ptr<EntitySummary>>> &
-  getDataForDeserialization(TUSummary &S);
+  static decltype(EntityIdTable::Entities) &getEntities(EntityIdTable &EIT);
+
   static const EntityIdTable &getIdTable(const TUSummary &S);
+  static EntityIdTable &getIdTable(TUSummary &S);
   static const BuildNamespace &getTUNamespace(const TUSummary &S);
-  static const std::map<SummaryName,
-                        std::map<EntityId, std::unique_ptr<EntitySummary>>> &
-  getData(const TUSummary &S);
+  static BuildNamespace &getTUNamespace(TUSummary &S);
+  static const decltype(TUSummary::Data) &getData(const TUSummary &S);
+  static decltype(TUSummary::Data) &getData(TUSummary &S);
 
   static BuildNamespaceKind getBuildNamespaceKind(const BuildNamespace &BN);
   static llvm::StringRef getBuildNamespaceName(const BuildNamespace &BN);
@@ -67,8 +62,6 @@ class SerializationFormat {
   static const llvm::SmallString<16> &getEntityNameSuffix(const EntityName &EN);
   static const NestedBuildNamespace &
   getEntityNameNamespace(const EntityName &EN);
-  static decltype(TUSummary::Data) &getData(TUSummary &S);
-  static const decltype(TUSummary::Data) &getData(const TUSummary &S);
 
 public:
   explicit SerializationFormat(
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index cca3ca36eb9ae..7a9d301c2791b 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -38,8 +38,8 @@ llvm::Error wrapError(llvm::Error E, const char *Fmt, Args &&...Vals) {
 // JSON Reader and Writer
 //----------------------------------------------------------------------------
 
-llvm::Error isJSONFile(llvm::StringRef Path) {
-  if (!llvm::sys::fs::exists(Path))
+llvm::Error isJSONFile(llvm::vfs::FileSystem &FS, llvm::StringRef Path) {
+  if (!FS.exists(Path))
     return llvm::createStringError(std::errc::no_such_file_or_directory,
                                    "file does not exist: '%s'",
                                    Path.str().c_str());
@@ -51,12 +51,13 @@ llvm::Error isJSONFile(llvm::StringRef Path) {
   return llvm::Error::success();
 }
 
-llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
-  if (llvm::Error Err = isJSONFile(Path))
+llvm::Expected<llvm::json::Value> readJSON(llvm::vfs::FileSystem &FS,
+                                           llvm::StringRef Path) {
+  if (llvm::Error Err = isJSONFile(FS, Path))
     return wrapError(std::move(Err), "failed to validate JSON file '%s'",
                      Path.str().c_str());
 
-  auto BufferOrError = llvm::MemoryBuffer::getFile(Path);
+  auto BufferOrError = FS.getBufferForFile(Path);
   if (!BufferOrError) {
     return llvm::createStringError(BufferOrError.getError(),
                                    "failed to read file '%s'",
@@ -66,8 +67,9 @@ llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
   return llvm::json::parse(BufferOrError.get()->getBuffer());
 }
 
-llvm::Expected<llvm::json::Object> readJSONObject(llvm::StringRef Path) {
-  auto ExpectedJSON = readJSON(Path);
+llvm::Expected<llvm::json::Object> readJSONObject(llvm::vfs::FileSystem &FS,
+                                                  llvm::StringRef Path) {
+  auto ExpectedJSON = readJSON(FS, Path);
   if (!ExpectedJSON)
     return wrapError(ExpectedJSON.takeError(),
                      "failed to read JSON from file '%s'", Path.str().c_str());
@@ -81,8 +83,9 @@ llvm::Expected<llvm::json::Object> readJSONObject(llvm::StringRef Path) {
   return std::move(*Object);
 }
 
-llvm::Expected<llvm::json::Array> readJSONArray(llvm::StringRef Path) {
-  auto ExpectedJSON = readJSON(Path);
+llvm::Expected<llvm::json::Array> readJSONArray(llvm::vfs::FileSystem &FS,
+                                                llvm::StringRef Path) {
+  auto ExpectedJSON = readJSON(FS, Path);
   if (!ExpectedJSON)
     return wrapError(ExpectedJSON.takeError(),
                      "failed to read JSON from file '%s'", Path.str().c_str());
@@ -96,19 +99,28 @@ llvm::Expected<llvm::json::Array> readJSONArray(llvm::StringRef Path) {
   return std::move(*Array);
 }
 
-llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
+llvm::Error writeJSON(llvm::vfs::FileSystem &FS, llvm::json::Value &&Value,
+                      llvm::StringRef Path) {
+  std::string Content;
+  llvm::raw_string_ostream OS(Content);
+  OS << llvm::formatv("{0:2}\n", Value);
+  OS.flush();
+
   std::error_code EC;
-  llvm::raw_fd_ostream OS(Path, EC);
+
+  // For real file system, we can use the atomic file approach
+  // For VFS, we need to write the content directly
+  llvm::raw_fd_ostream OutStream(Path, EC, llvm::sys::fs::OF_Text);
   if (EC) {
     return llvm::createStringError(EC, "failed to open '%s'",
                                    Path.str().c_str());
   }
 
-  OS << llvm::formatv("{0:2}\n", Value);
+  OutStream << Content;
+  OutStream.flush();
 
-  OS.flush();
-  if (OS.has_error()) {
-    return llvm::createStringError(OS.error(), "write failed");
+  if (OutStream.has_error()) {
+    return llvm::createStringError(OutStream.error(), "write failed");
   }
 
   return llvm::Error::success();
@@ -365,8 +377,7 @@ JSONFormat::entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray,
   const size_t EntityCount = EntityIdTableArray.size();
 
   EntityIdTable IdTable;
-  std::map<EntityName, EntityId> &Entities =
-      getEntitiesForDeserialization(IdTable);
+  std::map<EntityName, EntityId> &Entities = getEntities(IdTable);
 
   for (size_t Index = 0; Index < EntityCount; ++Index) {
     const llvm::json::Value &EntityIdTableEntryValue =
@@ -596,14 +607,22 @@ llvm::json::Object JSONFormat::summaryDataMapEntryToJSON(
 llvm::Expected<
     std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
 JSONFormat::readTUSummaryData(llvm::StringRef Path) {
-  if (!llvm::sys::fs::exists(Path)) {
+  if (!FS->exists(Path)) {
     return llvm::createStringError(
         std::errc::no_such_file_or_directory,
         "failed to read TUSummary data: directory does not exist: '%s'",
         Path.str().c_str());
   }
 
-  if (!llvm::sys::fs::is_directory(Path)) {
+  auto StatusOrErr = FS->status(Path);
+  if (!StatusOrErr) {
+    return llvm::createStringError(
+        StatusOrErr.getError(),
+        "failed to read TUSummary data: cannot get status of '%s'",
+        Path.str().c_str());
+  }
+
+  if (!StatusOrErr->isDirectory()) {
     return llvm::createStringError(
         std::errc::not_a_directory,
         "failed to read TUSummary data: path is not a directory: '%s'",
@@ -614,18 +633,19 @@ JSONFormat::readTUSummaryData(llvm::StringRef Path) {
       Data;
   std::error_code EC;
 
-  llvm::sys::fs::directory_iterator Dir(Path, EC);
+  auto DirIter = FS->dir_begin(Path, EC);
   if (EC) {
     return llvm::createStringError(
         EC, "failed to read TUSummary data: cannot iterate directory '%s'",
         Path.str().c_str());
   }
 
-  for (llvm::sys::fs::directory_iterator End; Dir != End && !EC;
-       Dir.increment(EC)) {
-    std::string SummaryPath = Dir->path();
+  for (llvm::vfs::directory_iterator End; DirIter != End && !EC;
+       DirIter.increment(EC)) {
+    llvm::StringRef SummaryPathRef = DirIter->path();
+    std::string SummaryPath = SummaryPathRef.str();
 
-    auto ExpectedObject = readJSONObject(SummaryPath);
+    auto ExpectedObject = readJSONObject(*FS, SummaryPath);
     if (!ExpectedObject)
       return wrapError(ExpectedObject.takeError(),
                        "failed to read TUSummary data from file '%s'",
@@ -680,14 +700,22 @@ llvm::Error JSONFormat::writeTUSummaryData(
     const std::map<SummaryName,
                    std::map<EntityId, std::unique_ptr<EntitySummary>>> &Data,
     llvm::StringRef Path) {
-  if (!llvm::sys::fs::exists(Path)) {
+  if (!FS->exists(Path)) {
     return llvm::createStringError(
         std::errc::no_such_file_or_directory,
         "failed to write TUSummary data: directory does not exist: '%s'",
         Path.str().c_str());
   }
 
-  if (!llvm::sys::fs::is_directory(Path)) {
+  auto StatusOrErr = FS->status(Path);
+  if (!StatusOrErr) {
+    return llvm::createStringError(
+        StatusOrErr.getError(),
+        "failed to write TUSummary data: cannot get status of '%s'",
+        Path.str().c_str());
+  }
+
+  if (!StatusOrErr->isDirectory()) {
     return llvm::createStringError(
         std::errc::not_a_directory,
         "failed to write TUSummary data: path is not a directory: '%s'",
@@ -702,7 +730,7 @@ llvm::Error JSONFormat::writeTUSummaryData(
     llvm::sys::path::replace_extension(SummaryPath, ".json");
 
     llvm::json::Object Result = summaryDataMapEntryToJSON(SummaryName, DataMap);
-    if (auto Error = writeJSON(std::move(Result), SummaryPath)) {
+    if (auto Error = writeJSON(*FS, std::move(Result), SummaryPath)) {
       return wrapError(
           std::move(Error), std::errc::io_error,
           "failed to write TUSummary data to directory '%s': cannot write "
@@ -727,7 +755,7 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
   llvm::SmallString<kPathBufferSize> TUNamespacePath(Path);
   llvm::sys::path::append(TUNamespacePath, TUSummaryTUNamespaceFilename);
 
-  auto ExpectedObject = readJSONObject(TUNamespacePath);
+  auto ExpectedObject = readJSONObject(*FS, TUNamespacePath);
   if (!ExpectedObject)
     return wrapError(ExpectedObject.takeError(),
                      "failed to read TUSummary from '%s'", Path.str().c_str());
@@ -745,7 +773,7 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
     llvm::SmallString<kPathBufferSize> IdTablePath(Path);
     llvm::sys::path::append(IdTablePath, TUSummaryIdTableFilename);
 
-    auto ExpectedArray = readJSONArray(IdTablePath);
+    auto ExpectedArray = readJSONArray(*FS, IdTablePath);
     if (!ExpectedArray)
       return wrapError(ExpectedArray.takeError(),
                        "failed to read TUSummary from '%s'",
@@ -757,7 +785,7 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
                        "failed to read TUSummary from '%s'",
                        Path.str().c_str());
 
-    getIdTableForDeserialization(Summary) = std::move(*ExpectedIdTable);
+    getIdTable(Summary) = std::move(*ExpectedIdTable);
   }
 
   // Populate Data field.
@@ -765,14 +793,22 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
     llvm::SmallString<kPathBufferSize> DataPath(Path);
     llvm::sys::path::append(DataPath, TUSummaryDataDirname);
 
-    if (!llvm::sys::fs::exists(DataPath)) {
+    if (!FS->exists(DataPath)) {
       return llvm::createStringError(std::errc::no_such_file_or_directory,
                                      "failed to read TUSummary from '%s': "
                                      "data directory does not exist: '%s'",
                                      Path.str().c_str(), DataPath.str().data());
     }
 
-    if (!llvm::sys::fs::is_directory(DataPath)) {
+    auto StatusOrErr = FS->status(DataPath);
+    if (!StatusOrErr) {
+      return llvm::createStringError(StatusOrErr.getError(),
+                                     "failed to read TUSummary from '%s': "
+                                     "cannot get status of data path: '%s'",
+                                     Path.str().c_str(), DataPath.str().data());
+    }
+
+    if (!StatusOrErr->isDirectory()) {
       return llvm::createStringError(std::errc::not_a_directory,
                                      "failed to read TUSummary from '%s': "
                                      "data path is not a directory: '%s'",
@@ -785,7 +821,7 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
                        "failed to read TUSummary from '%s'",
                        Path.str().c_str());
 
-    getDataForDeserialization(Summary) = std::move(*ExpectedData);
+    getData(Summary) = std::move(*ExpectedData);
   }
 
   return Summary;
@@ -800,7 +836,8 @@ llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
 
     llvm::json::Object BuildNamespaceObj =
         buildNamespaceToJSON(getTUNamespace(S));
-    if (auto Error = writeJSON(std::move(BuildNamespaceObj), TUNamespacePath)) {
+    if (auto Error =
+            writeJSON(*FS, std::move(BuildNamespaceObj), TUNamespacePath)) {
       return wrapError(std::move(Error), std::errc::io_error,
                        "failed to write TUSummary to '%s': "
                        "cannot write TUNamespace file '%s'",
@@ -814,7 +851,7 @@ llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
     llvm::sys::path::append(IdTablePath, TUSummaryIdTableFilename);
 
     llvm::json::Array IdTableObj = entityIdTableToJSON(getIdTable(S));
-    if (auto Error = writeJSON(std::move(IdTableObj), IdTablePath)) {
+    if (auto Error = writeJSON(*FS, std::move(IdTableObj), IdTablePath)) {
       return wrapError(std::move(Error), std::errc::io_error,
                        "failed to write TUSummary to '%s': "
                        "cannot write IdTable file '%s'",
@@ -828,6 +865,8 @@ llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
     llvm::sys::path::append(DataPath, TUSummaryDataDirname);
 
     // Create the data directory if it doesn't exist
+    // Use the real filesystem for directory creation as VFS doesn't always
+    // support this
     if (std::error_code EC = llvm::sys::fs::create_directory(DataPath)) {
       // If error is not "already exists", return error
       if (EC != std::errc::file_exists) {
@@ -840,13 +879,22 @@ llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
     }
 
     // Verify it's a directory (could be a file with the same name)
-    if (llvm::sys::fs::exists(DataPath) &&
-        !llvm::sys::fs::is_directory(DataPath)) {
-      return llvm::createStringError(
-          std::errc::not_a_directory,
-          "failed to write TUSummary to '%s': data path exists but is not a "
-          "directory: '%s'",
-          Dir.str().c_str(), DataPath.str().data());
+    if (FS->exists(DataPath)) {
+      auto StatusOrErr = FS->status(DataPath);
+      if (!StatusOrErr) {
+        return llvm::createStringError(StatusOrErr.getError(),
+                                       "failed to write TUSummary to '%s': "
+                                       "cannot get status of data path '%s'",
+                                       Dir.str().c_str(),
+                                       DataPath.str().data());
+      }
+      if (!StatusOrErr->isDirectory()) {
+        return llvm::createStringError(
+            std::errc::not_a_directory,
+            "failed to write TUSummary to '%s': data path exists but is not a "
+            "directory: '%s'",
+            Dir.str().c_str(), DataPath.str().data());
+      }
     }
 
     if (auto Error = writeTUSummaryData(getData(S), DataPath)) {
diff --git a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
index 1208780510b18..02f64f112761a 100644
--- a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
@@ -26,31 +26,21 @@ EntityId SerializationFormat::makeEntityId(const size_t Index) {
   return EntityId(Index);
 }
 
-const std::map<EntityName, EntityId> &
+const decltype(EntityIdTable::Entities) &
 SerializationFormat::getEntities(const EntityIdTable &EIT) {
   return EIT.Entities;
 }
 
-std::map<EntityName, EntityId> &
-SerializationFormat::getEntitiesForDeserialization(EntityIdTable &EIT) {
+decltype(EntityIdTable::Entities) &
+SerializationFormat::getEntities(EntityIdTable &EIT) {
   return EIT.Entities;
 }
 
-EntityIdTable &SerializationFormat::getIdTableForDeserialization(TUSummary &S) {
+const EntityIdTable &SerializationFormat::getIdTable(const TUSummary &S) {
   return S.IdTable;
 }
 
-BuildNamespace &
-SerializationFormat::getTUNamespaceForDeserialization(TUSummary &S) {
-  return S.TUNamespace;
-}
-
-std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>> &
-SerializationFormat::getDataForDeserialization(TUSummary &S) {
-  return S.Data;
-}
-
-const EntityIdTable &SerializationFormat::getIdTable(const TUSummary &S) {
+EntityIdTable &SerializationFormat::getIdTable(TUSummary &S) {
   return S.IdTable;
 }
 
@@ -58,12 +48,19 @@ const BuildNamespace &SerializationFormat::getTUNamespace(const TUSummary &S) {
   return S.TUNamespace;
 }
 
-const std::map<SummaryName,
-               std::map<EntityId, std::unique_ptr<EntitySummary>>> &
+BuildNamespace &SerializationFormat::getTUNamespace(TUSummary &S) {
+  return S.TUNamespace;
+}
+
+const decltype(TUSummary::Data) &
 SerializationFormat::getData(const TUSummary &S) {
   return S.Data;
 }
 
+decltype(TUSummary::Data) &SerializationFormat::getData(TUSummary &S) {
+  return S.Data;
+}
+
 BuildNamespaceKind
 SerializationFormat::getBuildNamespaceKind(const BuildNamespace &BN) {
   return BN.Kind;
@@ -92,12 +89,3 @@ const NestedBuildNamespace &
 SerializationFormat::getEntityNameNamespace(const EntityName &EN) {
   return EN.Namespace;
 }
-
-const decltype(TUSummary::Data) &
-SerializationFormat::getData(const TUSummary &S) {
-  return S.Data;
-}
-
-decltype(TUSummary::Data) &SerializationFormat::getData(TUSummary &S) {
-  return S.Data;
-}
diff --git a/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
index db38ce23565da..eb2ed77976bda 100644
--- a/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
+++ b/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
@@ -78,7 +78,7 @@ void createDir(llvm::StringRef Path) {
 // Base test fixture for JSONFormat tests
 class JSONFormatTestBase : public ::testing::Test {
 protected:
-  JSONFormat Format;
+  JSONFormat Format{llvm::vfs::getRealFileSystem()};
 };
 
 //===----------------------------------------------------------------------===//
diff --git a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
index 44904d53d2412..2ff75c9c5b1c3 100644
--- a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
@@ -40,12 +40,16 @@ MockSerializationFormat::MockSerializationFormat(
   }
 }
 
-TUSummary MockSerializationFormat::readTUSummary(llvm::StringRef Path) {
+llvm::Expected<TUSummary>
+MockSerializationFormat::readTUSummary(llvm::StringRef Path) {
   BuildNamespace NS(BuildNamespaceKind::CompilationUnit, "Mock.cpp");
   TUSummary Summary(NS);
 
   auto ManifestFile = FS->getBufferForFile(Path + "/analyses.txt");
-  assert(ManifestFile); // TODO Handle error.
+  if (!ManifestFile) {
+    return llvm::createStringError(ManifestFile.getError(),
+                                   "Failed to read manifest file");
+  }
   llvm::StringRef ManifestFileContent = (*ManifestFile)->getBuffer();
 
   llvm::SmallVector<llvm::StringRef, 5> Analyses;
@@ -55,20 +59,24 @@ TUSummary MockSerializationFormat::readTUSummary(llvm::StringRef Path) {
   for (llvm::StringRef Analysis : Analyses) {
     SummaryName Name(Analysis.str());
     auto InputFile = FS->getBufferForFile(Path + "/" + Name.str() + ".special");
-    assert(InputFile);
+    if (!InputFile) {
+      return llvm::createStringError(InputFile.getError(),
+                                     "Failed to read analysis file");
+    }
     auto InfoIt = FormatInfos.find(Name);
     if (InfoIt == FormatInfos.end()) {
-      llvm::report_fatal_error(
+      return llvm::createStringError(
+          std::make_error_code(std::errc::invalid_argument),
           "No FormatInfo was registered for summary name: " + Name.str());
     }
     const auto &InfoEntry = InfoIt->second;
     assert(InfoEntry.ForSummary == Name);
 
     SpecialFileRepresentation Repr{(*InputFile)->getBuffer().str()};
-    auto &Table = getIdTableForDeserialization(Summary);
+    auto &Table = getIdTable(Summary);
 
     std::unique_ptr<EntitySummary> Result = InfoEntry.Deserialize(Repr, Table);
-    if (!Result) // TODO: Handle error.
+    if (!Result)
       continue;
 
     EntityId FooId = Table.getId(EntityName{"c:@F at foo", "", /*Namespace=*/{}});
@@ -81,16 +89,16 @@ TUSummary MockSerializationFormat::readTUSummary(llvm::StringRef Path) {
   return Summary;
 }
 
-void MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
-                                             llvm::StringRef OutputDir) {
+llvm::Error MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
+                                                    llvm::StringRef OutputDir) {
   std::error_code EC;
 
   // Check if output directory exists, create if needed
   if (!llvm::sys::fs::exists(OutputDir)) {
     EC = llvm::sys::fs::create_directories(OutputDir);
     if (EC) {
-      llvm::report_fatal_error("Failed to create output directory '" +
-                               OutputDir + "': " + EC.message());
+      return llvm::createStringError(EC, "Failed to create output directory '" +
+                                             OutputDir + "': " + EC.message());
     }
   }
 
@@ -101,9 +109,10 @@ void MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
     for (const auto &Data : llvm::make_second_range(EntityMappings)) {
       auto InfoIt = FormatInfos.find(SummaryName);
       if (InfoIt == FormatInfos.end()) {
-        llvm::report_fatal_error(
+        return llvm::createStringError(
+            std::make_error_code(std::errc::invalid_argument),
             "There was no FormatInfo registered for summary name '" +
-            SummaryName.str() + "'");
+                SummaryName.str() + "'");
       }
       const auto &InfoEntry = InfoIt->second;
       assert(InfoEntry.ForSummary == SummaryName);
@@ -114,8 +123,9 @@ void MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
           (OutputDir + "/" + SummaryName.str() + ".special").str();
       llvm::raw_fd_ostream AnalysisOutputFile(AnalysisFilePath, EC);
       if (EC) {
-        llvm::report_fatal_error("Failed to create file '" + AnalysisFilePath +
-                                 "': " + llvm::StringRef(EC.message()));
+        return llvm::createStringError(
+            EC, "Failed to create file '" + AnalysisFilePath +
+                    "': " + llvm::StringRef(EC.message()));
       }
       AnalysisOutputFile << Output.MockRepresentation;
     }
@@ -124,14 +134,16 @@ void MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
   std::string ManifestFilePath = (OutputDir + "/analyses.txt").str();
   llvm::raw_fd_ostream ManifestFile(ManifestFilePath, EC);
   if (EC) {
-    llvm::report_fatal_error("Failed to create manifest file '" +
-                             ManifestFilePath +
-                             "': " + llvm::StringRef(EC.message()));
+    return llvm::createStringError(
+        EC, "Failed to create manifest file '" + ManifestFilePath +
+                "': " + llvm::StringRef(EC.message()));
   }
 
   interleave(map_range(Analyses, std::mem_fn(&SummaryName::str)), ManifestFile,
              "\n");
   ManifestFile << "\n";
+
+  return llvm::Error::success();
 }
 
 static SerializationFormatRegistry::Add<MockSerializationFormat>
diff --git a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h
index a106e53fc20ac..c8b146821a91f 100644
--- a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h
+++ b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h
@@ -21,10 +21,10 @@ class MockSerializationFormat final : public SerializationFormat {
   explicit MockSerializationFormat(
       llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS);
 
-  TUSummary readTUSummary(llvm::StringRef Path) override;
+  llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) override;
 
-  void writeTUSummary(const TUSummary &Summary,
-                      llvm::StringRef OutputDir) override;
+  llvm::Error writeTUSummary(const TUSummary &Summary,
+                             llvm::StringRef OutputDir) override;
 
   struct SpecialFileRepresentation {
     std::string MockRepresentation;
diff --git a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
index 484c38309e3d8..79a4adf218aac 100644
--- a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
@@ -68,7 +68,9 @@ TEST(SerializationFormatRegistryTest, Roundtrip) {
       makeFormat(Inputs, "MockSerializationFormat");
   ASSERT_TRUE(Format);
 
-  TUSummary LoadedSummary = Format->readTUSummary("input");
+  auto LoadedSummaryOrErr = Format->readTUSummary("input");
+  ASSERT_TRUE(!!LoadedSummaryOrErr);
+  TUSummary LoadedSummary = std::move(*LoadedSummaryOrErr);
 
   // Create a temporary output directory
   SmallString<128> OutputDir;
@@ -77,7 +79,8 @@ TEST(SerializationFormatRegistryTest, Roundtrip) {
   llvm::scope_exit CleanupOnExit(
       [&] { sys::fs::remove_directories(OutputDir); });
 
-  Format->writeTUSummary(LoadedSummary, OutputDir);
+  auto WriteErr = Format->writeTUSummary(LoadedSummary, OutputDir);
+  ASSERT_FALSE(!!WriteErr);
 
   EXPECT_EQ(readFilesFromDir(OutputDir),
             (std::map<std::string, std::string>{

>From b45cf5fc2bcb870c68d582ec731fc481b3b956f1 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 12:22:53 -0800
Subject: [PATCH 5/7] Fix JSONFormat to use a single file and add tests

---
 .../Scalable/Serialization/JSONFormat.h       |  82 ++-
 .../Scalable/Serialization/JSONFormat.cpp     | 640 ++++++------------
 .../error-callgraph-invalid-caller.test       |   8 +
 .../error-callgraph-missing-field.test        |   8 +
 .../error-data-element-not-object.test        |   6 +
 .../error-data-entry-missing-data.test        |   8 +
 .../error-data-missing-summary-name.test      |   7 +
 .../Serialization/error-data-not-array.test   |   7 +
 .../error-defuse-invalid-use.test             |   8 +
 .../error-defuse-missing-field.test           |   8 +
 .../Serialization/error-duplicate-entity.test |   8 +
 .../error-duplicate-summary-name.test         |   6 +
 .../error-entity-data-element-not-object.test |   8 +
 .../error-entity-data-missing-entity-id.test  |   9 +
 ...or-entity-data-missing-entity-summary.test |   9 +
 .../error-entity-id-not-uint64.test           |   8 +
 .../error-entity-name-missing-namespace.test  |   9 +
 .../error-entity-name-missing-suffix.test     |   8 +
 .../error-entity-name-missing-usr.test        |   9 +
 .../error-entity-summary-no-format-info.test  |   7 +
 .../error-id-table-element-not-object.test    |   8 +
 .../error-id-table-entry-id-not-uint64.test   |   8 +
 .../error-id-table-entry-missing-id.test      |   8 +
 .../error-id-table-entry-missing-name.test    |   8 +
 .../error-id-table-not-array.test             |   7 +
 .../Serialization/error-invalid-kind.test     |   7 +
 .../Serialization/error-invalid-syntax.test   |   6 +
 .../Serialization/error-missing-data.test     |   7 +
 .../Serialization/error-missing-id-table.test |   7 +
 .../Serialization/error-missing-kind.test     |   8 +
 .../Serialization/error-missing-name.test     |   7 +
 .../error-missing-tu-namespace.test           |   7 +
 .../error-namespace-element-not-object.test   |  10 +
 .../Serialization/error-nonexistent-file.test |   6 +
 .../error-not-json-extension.test             |   8 +
 .../Serialization/error-not-object.test       |   6 +
 .../Serialization/valid-callgraph.test        |   9 +
 .../Scalable/Serialization/valid-defuse.test  |  10 +
 .../Scalable/Serialization/valid-empty.test   |   4 +
 .../Serialization/valid-link-unit.test        |   4 +
 .../valid-with-empty-data-entry.test          |   4 +
 .../Serialization/valid-with-idtable.test     |   4 +
 clang/test/lit.cfg.py                         |   1 +
 .../Analysis/Scalable/CMakeLists.txt          |   3 +
 .../Analysis/Scalable/tools/CMakeLists.txt    |   1 +
 .../CMakeLists.txt                            |   9 +
 .../ExampleAnalyses.cpp                       | 303 +++++++++
 .../SSAFSerializationFormatTest.cpp           |  84 +++
 48 files changed, 975 insertions(+), 442 deletions(-)
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-not-array.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-not-array.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-data.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-id-table.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-name.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-tu-namespace.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-nonexistent-file.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-not-object.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/valid-callgraph.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/valid-defuse.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/valid-empty.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/valid-link-unit.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/valid-with-empty-data-entry.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/valid-with-idtable.test
 create mode 100644 clang/unittests/Analysis/Scalable/tools/CMakeLists.txt
 create mode 100644 clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/CMakeLists.txt
 create mode 100644 clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
 create mode 100644 clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp

diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
index 23e37138d6926..ac809cd00e0cd 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -14,17 +14,34 @@
 #define CLANG_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_H
 
 #include "clang/Analysis/Scalable/Serialization/SerializationFormat.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/Support/JSON.h"
 
 namespace clang::ssaf {
 
 class EntitySummary;
+class EntityIdTable;
 class SummaryName;
 
 class JSONFormat : public SerializationFormat {
 public:
-  explicit JSONFormat(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
-      : SerializationFormat(FS) {}
+  // Helper class to provide limited access to EntityId conversion methods
+  // Only exposes EntityId serialization/deserialization to format handlers
+  class EntityIdConverter {
+  public:
+    EntityId fromJSON(uint64_t EntityIdIndex) const {
+      return Format.entityIdFromJSON(EntityIdIndex);
+    }
+
+    uint64_t toJSON(EntityId EI) const { return Format.entityIdToJSON(EI); }
+
+  private:
+    friend class JSONFormat;
+    EntityIdConverter(const JSONFormat &Format) : Format(Format) {}
+    const JSONFormat &Format;
+  };
+
+  explicit JSONFormat(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS);
 
   ~JSONFormat() = default;
 
@@ -33,44 +50,65 @@ class JSONFormat : public SerializationFormat {
   llvm::Error writeTUSummary(const TUSummary &Summary,
                              llvm::StringRef OutputDir) override;
 
+  using SerializerFn = llvm::function_ref<llvm::json::Object(
+      const EntitySummary &, const EntityIdConverter &)>;
+  using DeserializerFn =
+      llvm::function_ref<llvm::Expected<std::unique_ptr<EntitySummary>>(
+          const llvm::json::Object &, EntityIdTable &,
+          const EntityIdConverter &)>;
+
+  using FormatInfo = FormatInfoEntry<SerializerFn, DeserializerFn>;
+  std::map<SummaryName, FormatInfo> FormatInfos;
+
 private:
-  EntityId entityIdFromJSON(const uint64_t EntityIdIndex);
+  EntityId entityIdFromJSON(const uint64_t EntityIdIndex) const;
   uint64_t entityIdToJSON(EntityId EI) const;
 
+  llvm::Expected<BuildNamespaceKind>
+  buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr);
+
+  llvm::Expected<BuildNamespace>
+  buildNamespaceFromJSON(const llvm::json::Object &BuildNamespaceObject);
   llvm::json::Object buildNamespaceToJSON(const BuildNamespace &BN) const;
+
+  llvm::Expected<NestedBuildNamespace> nestedBuildNamespaceFromJSON(
+      const llvm::json::Array &NestedBuildNamespaceArray);
   llvm::json::Array
   nestedBuildNamespaceToJSON(const NestedBuildNamespace &NBN) const;
+
+  llvm::Expected<EntityName>
+  entityNameFromJSON(const llvm::json::Object &EntityNameObject);
   llvm::json::Object entityNameToJSON(const EntityName &EN) const;
-  llvm::Expected<std::pair<EntityName, EntityId>>
-  entityIdTableEntryFromJSON(const llvm::json::Object &EntityIdTableEntryObject,
-                             llvm::StringRef Path);
+
+  llvm::Expected<std::pair<EntityName, EntityId>> entityIdTableEntryFromJSON(
+      const llvm::json::Object &EntityIdTableEntryObject);
   llvm::Expected<EntityIdTable>
-  entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray,
-                        llvm::StringRef Path);
+  entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray);
   llvm::json::Array entityIdTableToJSON(const EntityIdTable &IdTable) const;
 
+  llvm::Expected<std::unique_ptr<EntitySummary>>
+  entitySummaryFromJSON(const SummaryName &SN,
+                        const llvm::json::Object &EntitySummaryObject,
+                        EntityIdTable &IdTable);
+  llvm::json::Object entitySummaryToJSON(const SummaryName &SN,
+                                         const EntitySummary &ES) const;
+
   llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
-  entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
-                        llvm::StringRef Path);
-  llvm::json::Array entityDataMapToJSON(
-      const std::map<EntityId, std::unique_ptr<EntitySummary>> &EntityDataMap)
-      const;
+  entityDataMapFromJSON(const SummaryName &SN,
+                        const llvm::json::Array &EntityDataArray,
+                        EntityIdTable &IdTable);
+  llvm::json::Array
+  entityDataMapToJSON(const SummaryName &SN,
+                      const std::map<EntityId, std::unique_ptr<EntitySummary>>
+                          &EntityDataMap) const;
 
   llvm::Expected<std::pair<SummaryName,
                            std::map<EntityId, std::unique_ptr<EntitySummary>>>>
   summaryDataMapEntryFromJSON(const llvm::json::Object &SummaryDataObject,
-                              llvm::StringRef Path);
+                              EntityIdTable &IdTable);
   llvm::json::Object summaryDataMapEntryToJSON(
       const SummaryName &SN,
       const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const;
-
-  llvm::Expected<
-      std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
-  readTUSummaryData(llvm::StringRef Path);
-  llvm::Error writeTUSummaryData(
-      const std::map<SummaryName,
-                     std::map<EntityId, std::unique_ptr<EntitySummary>>> &Data,
-      llvm::StringRef Path);
 };
 
 } // namespace clang::ssaf
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index 7a9d301c2791b..1d3376ef2c9f2 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -7,15 +7,11 @@
 #include "llvm/Support/JSON.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Path.h"
+#include "llvm/Support/Registry.h"
 
 using namespace clang::ssaf;
 
 namespace {
-constexpr size_t kPathBufferSize = 128;
-constexpr const char *TUSummaryTUNamespaceFilename = "tu_namespace.json";
-constexpr const char *TUSummaryIdTableFilename = "id_table.json";
-constexpr const char *TUSummaryDataDirname = "data";
-
 // Helper to wrap an error with additional context
 template <typename... Args>
 llvm::Error wrapError(llvm::Error E, std::errc ErrorCode, const char *Fmt,
@@ -38,8 +34,8 @@ llvm::Error wrapError(llvm::Error E, const char *Fmt, Args &&...Vals) {
 // JSON Reader and Writer
 //----------------------------------------------------------------------------
 
-llvm::Error isJSONFile(llvm::vfs::FileSystem &FS, llvm::StringRef Path) {
-  if (!FS.exists(Path))
+llvm::Error isJSONFile(llvm::StringRef Path) {
+  if (!llvm::sys::fs::exists(Path))
     return llvm::createStringError(std::errc::no_such_file_or_directory,
                                    "file does not exist: '%s'",
                                    Path.str().c_str());
@@ -51,13 +47,12 @@ llvm::Error isJSONFile(llvm::vfs::FileSystem &FS, llvm::StringRef Path) {
   return llvm::Error::success();
 }
 
-llvm::Expected<llvm::json::Value> readJSON(llvm::vfs::FileSystem &FS,
-                                           llvm::StringRef Path) {
-  if (llvm::Error Err = isJSONFile(FS, Path))
+llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
+  if (llvm::Error Err = isJSONFile(Path))
     return wrapError(std::move(Err), "failed to validate JSON file '%s'",
                      Path.str().c_str());
 
-  auto BufferOrError = FS.getBufferForFile(Path);
+  auto BufferOrError = llvm::MemoryBuffer::getFile(Path);
   if (!BufferOrError) {
     return llvm::createStringError(BufferOrError.getError(),
                                    "failed to read file '%s'",
@@ -67,12 +62,12 @@ llvm::Expected<llvm::json::Value> readJSON(llvm::vfs::FileSystem &FS,
   return llvm::json::parse(BufferOrError.get()->getBuffer());
 }
 
-llvm::Expected<llvm::json::Object> readJSONObject(llvm::vfs::FileSystem &FS,
-                                                  llvm::StringRef Path) {
-  auto ExpectedJSON = readJSON(FS, Path);
+llvm::Expected<llvm::json::Object> readJSONObject(llvm::StringRef Path) {
+  auto ExpectedJSON = readJSON(Path);
   if (!ExpectedJSON)
     return wrapError(ExpectedJSON.takeError(),
-                     "failed to read JSON from file '%s'", Path.str().c_str());
+                     "failed to read JSON object from file '%s'",
+                     Path.str().c_str());
 
   llvm::json::Object *Object = ExpectedJSON->getAsObject();
   if (!Object) {
@@ -83,40 +78,15 @@ llvm::Expected<llvm::json::Object> readJSONObject(llvm::vfs::FileSystem &FS,
   return std::move(*Object);
 }
 
-llvm::Expected<llvm::json::Array> readJSONArray(llvm::vfs::FileSystem &FS,
-                                                llvm::StringRef Path) {
-  auto ExpectedJSON = readJSON(FS, Path);
-  if (!ExpectedJSON)
-    return wrapError(ExpectedJSON.takeError(),
-                     "failed to read JSON from file '%s'", Path.str().c_str());
-
-  llvm::json::Array *Array = ExpectedJSON->getAsArray();
-  if (!Array) {
-    return llvm::createStringError(std::errc::invalid_argument,
-                                   "failed to read JSON array from file '%s'",
-                                   Path.str().c_str());
-  }
-  return std::move(*Array);
-}
-
-llvm::Error writeJSON(llvm::vfs::FileSystem &FS, llvm::json::Value &&Value,
-                      llvm::StringRef Path) {
-  std::string Content;
-  llvm::raw_string_ostream OS(Content);
-  OS << llvm::formatv("{0:2}\n", Value);
-  OS.flush();
-
+llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
   std::error_code EC;
-
-  // For real file system, we can use the atomic file approach
-  // For VFS, we need to write the content directly
   llvm::raw_fd_ostream OutStream(Path, EC, llvm::sys::fs::OF_Text);
   if (EC) {
     return llvm::createStringError(EC, "failed to open '%s'",
                                    Path.str().c_str());
   }
 
-  OutStream << Content;
+  OutStream << llvm::formatv("{0:2}\n", Value);
   OutStream.flush();
 
   if (OutStream.has_error()) {
@@ -126,16 +96,33 @@ llvm::Error writeJSON(llvm::vfs::FileSystem &FS, llvm::json::Value &&Value,
   return llvm::Error::success();
 }
 
+//----------------------------------------------------------------------------
+// JSONFormat Constructor
+//----------------------------------------------------------------------------
+
+JSONFormat::JSONFormat(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
+    : SerializationFormat(FS) {
+  for (const auto &FormatInfoEntry : llvm::Registry<FormatInfo>::entries()) {
+    std::unique_ptr<FormatInfo> Info = FormatInfoEntry.instantiate();
+    bool Inserted = FormatInfos.try_emplace(Info->ForSummary, *Info).second;
+    if (!Inserted) {
+      llvm::report_fatal_error(
+          "Format info was already registered for summary name: " +
+          Info->ForSummary.str());
+    }
+  }
+}
+
 //----------------------------------------------------------------------------
 // EntityId
 //----------------------------------------------------------------------------
 
-EntityId JSONFormat::entityIdFromJSON(const uint64_t EntityIdIndex) {
-  return makeEntityId(static_cast<size_t>(EntityIdIndex));
+EntityId JSONFormat::entityIdFromJSON(const uint64_t EntityIdIndex) const {
+  return SerializationFormat::makeEntityId(static_cast<size_t>(EntityIdIndex));
 }
 
 uint64_t JSONFormat::entityIdToJSON(EntityId EI) const {
-  return static_cast<uint64_t>(getEntityIdIndex(EI));
+  return static_cast<uint64_t>(SerializationFormat::getEntityIdIndex(EI));
 }
 
 //----------------------------------------------------------------------------
@@ -143,14 +130,13 @@ uint64_t JSONFormat::entityIdToJSON(EntityId EI) const {
 //----------------------------------------------------------------------------
 
 llvm::Expected<BuildNamespaceKind>
-buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr,
-                           llvm::StringRef Path) {
+JSONFormat::buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr) {
   auto OptBuildNamespaceKind = parseBuildNamespaceKind(BuildNamespaceKindStr);
   if (!OptBuildNamespaceKind) {
     return llvm::createStringError(
         std::errc::invalid_argument,
-        "invalid 'kind' BuildNamespaceKind value '%s' in file '%s'",
-        BuildNamespaceKindStr.str().c_str(), Path.str().c_str());
+        "invalid 'kind' BuildNamespaceKind value '%s'",
+        BuildNamespaceKindStr.str().c_str());
   }
 
   return *OptBuildNamespaceKind;
@@ -164,32 +150,26 @@ llvm::StringRef buildNamespaceKindToJSON(BuildNamespaceKind BNK) {
 // BuildNamespace
 //----------------------------------------------------------------------------
 
-llvm::Expected<BuildNamespace>
-buildNamespaceFromJSON(const llvm::json::Object &BuildNamespaceObject,
-                       llvm::StringRef Path) {
+llvm::Expected<BuildNamespace> JSONFormat::buildNamespaceFromJSON(
+    const llvm::json::Object &BuildNamespaceObject) {
   auto OptBuildNamespaceKindStr = BuildNamespaceObject.getString("kind");
   if (!OptBuildNamespaceKindStr) {
     return llvm::createStringError(
         std::errc::invalid_argument,
-        "failed to deserialize BuildNamespace from file '%s': "
-        "missing required field 'kind' (expected BuildNamespaceKind)",
-        Path.str().c_str());
+        "failed to deserialize BuildNamespace: "
+        "missing required field 'kind' (expected BuildNamespaceKind)");
   }
 
-  auto ExpectedKind =
-      buildNamespaceKindFromJSON(*OptBuildNamespaceKindStr, Path);
+  auto ExpectedKind = buildNamespaceKindFromJSON(*OptBuildNamespaceKindStr);
   if (!ExpectedKind)
     return wrapError(ExpectedKind.takeError(),
-                     "failed to deserialize BuildNamespace from file '%s'",
-                     Path.str().c_str());
+                     "failed to deserialize BuildNamespace");
 
   auto OptNameStr = BuildNamespaceObject.getString("name");
   if (!OptNameStr) {
-    return llvm::createStringError(
-        std::errc::invalid_argument,
-        "failed to deserialize BuildNamespace from file '%s': "
-        "missing required field 'name'",
-        Path.str().c_str());
+    return llvm::createStringError(std::errc::invalid_argument,
+                                   "failed to deserialize BuildNamespace: "
+                                   "missing required field 'name'");
   }
 
   return {BuildNamespace(*ExpectedKind, *OptNameStr)};
@@ -207,9 +187,8 @@ JSONFormat::buildNamespaceToJSON(const BuildNamespace &BN) const {
 // NestedBuildNamespace
 //----------------------------------------------------------------------------
 
-llvm::Expected<NestedBuildNamespace>
-nestedBuildNamespaceFromJSON(const llvm::json::Array &NestedBuildNamespaceArray,
-                             llvm::StringRef Path) {
+llvm::Expected<NestedBuildNamespace> JSONFormat::nestedBuildNamespaceFromJSON(
+    const llvm::json::Array &NestedBuildNamespaceArray) {
   std::vector<BuildNamespace> Namespaces;
 
   size_t NamespaceCount = NestedBuildNamespaceArray.size();
@@ -223,20 +202,17 @@ nestedBuildNamespaceFromJSON(const llvm::json::Array &NestedBuildNamespaceArray,
     if (!BuildNamespaceObject) {
       return llvm::createStringError(
           std::errc::invalid_argument,
-          "failed to deserialize NestedBuildNamespace from file '%s': "
+          "failed to deserialize NestedBuildNamespace: "
           "element at index %zu is not a JSON object "
           "(expected BuildNamespace object)",
-          Path.str().c_str(), Index);
+          Index);
     }
 
-    auto ExpectedBuildNamespace =
-        buildNamespaceFromJSON(*BuildNamespaceObject, Path);
+    auto ExpectedBuildNamespace = buildNamespaceFromJSON(*BuildNamespaceObject);
     if (!ExpectedBuildNamespace)
       return wrapError(
           ExpectedBuildNamespace.takeError(),
-          "failed to deserialize NestedBuildNamespace from file '%s' "
-          "at index %zu",
-          Path.str().c_str(), Index);
+          "failed to deserialize NestedBuildNamespace at index %zu", Index);
 
     Namespaces.push_back(std::move(*ExpectedBuildNamespace));
   }
@@ -262,24 +238,20 @@ JSONFormat::nestedBuildNamespaceToJSON(const NestedBuildNamespace &NBN) const {
 //----------------------------------------------------------------------------
 
 llvm::Expected<EntityName>
-entityNameFromJSON(const llvm::json::Object &EntityNameObject,
-                   llvm::StringRef Path) {
+JSONFormat::entityNameFromJSON(const llvm::json::Object &EntityNameObject) {
   const auto OptUSR = EntityNameObject.getString("usr");
   if (!OptUSR) {
     return llvm::createStringError(
         std::errc::invalid_argument,
-        "failed to deserialize EntityName from file '%s': "
-        "missing required field 'usr' (Unified Symbol Resolution string)",
-        Path.str().c_str());
+        "failed to deserialize EntityName: "
+        "missing required field 'usr' (Unified Symbol Resolution string)");
   }
 
   const auto OptSuffix = EntityNameObject.getString("suffix");
   if (!OptSuffix) {
-    return llvm::createStringError(
-        std::errc::invalid_argument,
-        "failed to deserialize EntityName from file '%s': "
-        "missing required field 'suffix'",
-        Path.str().c_str());
+    return llvm::createStringError(std::errc::invalid_argument,
+                                   "failed to deserialize EntityName: "
+                                   "missing required field 'suffix'");
   }
 
   const llvm::json::Array *OptNamespaceArray =
@@ -287,18 +259,15 @@ entityNameFromJSON(const llvm::json::Object &EntityNameObject,
   if (!OptNamespaceArray) {
     return llvm::createStringError(
         std::errc::invalid_argument,
-        "failed to deserialize EntityName from file '%s': "
+        "failed to deserialize EntityName: "
         "missing or invalid field 'namespace' "
-        "(expected JSON array of BuildNamespace objects)",
-        Path.str().c_str());
+        "(expected JSON array of BuildNamespace objects)");
   }
 
-  auto ExpectedNamespace =
-      nestedBuildNamespaceFromJSON(*OptNamespaceArray, Path);
+  auto ExpectedNamespace = nestedBuildNamespaceFromJSON(*OptNamespaceArray);
   if (!ExpectedNamespace)
     return wrapError(ExpectedNamespace.takeError(),
-                     "failed to deserialize EntityName from file '%s'",
-                     Path.str().c_str());
+                     "failed to deserialize EntityName");
 
   return EntityName{*OptUSR, *OptSuffix, std::move(*ExpectedNamespace)};
 }
@@ -327,32 +296,29 @@ llvm::StringRef summaryNameToJSON(const SummaryName &SN) { return SN.str(); }
 
 llvm::Expected<std::pair<EntityName, EntityId>>
 JSONFormat::entityIdTableEntryFromJSON(
-    const llvm::json::Object &EntityIdTableEntryObject, llvm::StringRef Path) {
+    const llvm::json::Object &EntityIdTableEntryObject) {
 
   const llvm::json::Object *OptEntityNameObject =
       EntityIdTableEntryObject.getObject("name");
   if (!OptEntityNameObject) {
     return llvm::createStringError(
         std::errc::invalid_argument,
-        "failed to deserialize EntityIdTable entry from file '%s': "
-        "missing or invalid field 'name' (expected EntityName JSON object)",
-        Path.str().c_str());
+        "failed to deserialize EntityIdTable entry: "
+        "missing or invalid field 'name' (expected EntityName JSON object)");
   }
 
-  auto ExpectedEntityName = entityNameFromJSON(*OptEntityNameObject, Path);
+  auto ExpectedEntityName = entityNameFromJSON(*OptEntityNameObject);
   if (!ExpectedEntityName)
     return wrapError(ExpectedEntityName.takeError(),
-                     "failed to deserialize EntityIdTable entry from file '%s'",
-                     Path.str().c_str());
+                     "failed to deserialize EntityIdTable entry");
 
   const llvm::json::Value *EntityIdIntValue =
       EntityIdTableEntryObject.get("id");
   if (!EntityIdIntValue) {
     return llvm::createStringError(
         std::errc::invalid_argument,
-        "failed to deserialize EntityIdTable entry from file '%s': "
-        "missing required field 'id' (expected unsigned integer EntityId)",
-        Path.str().c_str());
+        "failed to deserialize EntityIdTable entry: "
+        "missing required field 'id' (expected unsigned integer EntityId)");
   }
 
   const std::optional<uint64_t> OptEntityIdInt =
@@ -360,10 +326,9 @@ JSONFormat::entityIdTableEntryFromJSON(
   if (!OptEntityIdInt) {
     return llvm::createStringError(
         std::errc::invalid_argument,
-        "failed to deserialize EntityIdTable entry from file '%s': "
+        "failed to deserialize EntityIdTable entry: "
         "field 'id' is not a valid unsigned 64-bit integer "
-        "(expected non-negative EntityId value)",
-        Path.str().c_str());
+        "(expected non-negative EntityId value)");
   }
 
   EntityId EI = entityIdFromJSON(*OptEntityIdInt);
@@ -372,8 +337,7 @@ JSONFormat::entityIdTableEntryFromJSON(
 }
 
 llvm::Expected<EntityIdTable>
-JSONFormat::entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray,
-                                  llvm::StringRef Path) {
+JSONFormat::entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray) {
   const size_t EntityCount = EntityIdTableArray.size();
 
   EntityIdTable IdTable;
@@ -389,29 +353,27 @@ JSONFormat::entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray,
     if (!OptEntityIdTableEntryObject) {
       return llvm::createStringError(
           std::errc::invalid_argument,
-          "failed to deserialize EntityIdTable from file '%s': "
+          "failed to deserialize EntityIdTable: "
           "element at index %zu is not a JSON object "
           "(expected EntityIdTable entry with 'id' and 'name' fields)",
-          Path.str().c_str(), Index);
+          Index);
     }
 
     auto ExpectedEntityIdTableEntry =
-        entityIdTableEntryFromJSON(*OptEntityIdTableEntryObject, Path);
+        entityIdTableEntryFromJSON(*OptEntityIdTableEntryObject);
     if (!ExpectedEntityIdTableEntry)
-      return wrapError(
-          ExpectedEntityIdTableEntry.takeError(),
-          "failed to deserialize EntityIdTable from file '%s' at index %zu",
-          Path.str().c_str(), Index);
+      return wrapError(ExpectedEntityIdTableEntry.takeError(),
+                       "failed to deserialize EntityIdTable at index %zu",
+                       Index);
 
     auto [EntityIt, EntityInserted] =
         Entities.emplace(std::move(*ExpectedEntityIdTableEntry));
     if (!EntityInserted) {
-      return llvm::createStringError(
-          std::errc::invalid_argument,
-          "failed to deserialize EntityIdTable from file '%s': "
-          "duplicate EntityName found at index %zu "
-          "(EntityId=%zu already exists in table)",
-          Path.str().c_str(), Index, getEntityIdIndex(EntityIt->second));
+      return llvm::createStringError(std::errc::invalid_argument,
+                                     "failed to deserialize EntityIdTable: "
+                                     "duplicate EntityName found at index %zu "
+                                     "(EntityId=%zu already exists in table)",
+                                     Index, getEntityIdIndex(EntityIt->second));
     }
   }
 
@@ -439,18 +401,39 @@ JSONFormat::entityIdTableToJSON(const EntityIdTable &IdTable) const {
 //----------------------------------------------------------------------------
 
 llvm::Expected<std::unique_ptr<EntitySummary>>
-entitySummaryFromJSON(const llvm::json::Object &EntitySummaryObject,
-                      llvm::StringRef Path) {
-  return llvm::createStringError(
-      std::errc::function_not_supported,
-      "EntitySummary deserialization from file '%s' is not yet implemented",
-      Path.str().c_str());
+JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
+                                  const llvm::json::Object &EntitySummaryObject,
+                                  EntityIdTable &IdTable) {
+  auto InfoIt = FormatInfos.find(SN);
+  if (InfoIt == FormatInfos.end()) {
+    return llvm::createStringError(
+        std::errc::invalid_argument,
+        "failed to deserialize EntitySummary: "
+        "no FormatInfo was registered for summary name: %s",
+        SN.str().data());
+  }
+  const auto &InfoEntry = InfoIt->second;
+  assert(InfoEntry.ForSummary == SN);
+
+  EntityIdConverter Converter(*this);
+  return InfoEntry.Deserialize(EntitySummaryObject, IdTable, Converter);
 }
 
-llvm::json::Object entitySummaryToJSON(const EntitySummary &ES) {
-  // TODO
-  llvm::json::Object Result;
-  return Result;
+llvm::json::Object
+JSONFormat::entitySummaryToJSON(const SummaryName &SN,
+                                const EntitySummary &ES) const {
+  auto InfoIt = FormatInfos.find(SN);
+  if (InfoIt == FormatInfos.end()) {
+    llvm::report_fatal_error(
+        "Failed to serialize EntitySummary: no FormatInfo was registered for "
+        "summary name: " +
+        SN.str());
+  }
+  const auto &InfoEntry = InfoIt->second;
+  assert(InfoEntry.ForSummary == SN);
+
+  EntityIdConverter Converter(*this);
+  return InfoEntry.Serialize(ES, Converter);
 }
 
 //----------------------------------------------------------------------------
@@ -458,8 +441,9 @@ llvm::json::Object entitySummaryToJSON(const EntitySummary &ES) {
 //----------------------------------------------------------------------------
 
 llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
-JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
-                                  llvm::StringRef Path) {
+JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
+                                  const llvm::json::Array &EntityDataArray,
+                                  EntityIdTable &IdTable) {
   std::map<EntityId, std::unique_ptr<EntitySummary>> EntityDataMap;
 
   size_t Index = 0;
@@ -469,10 +453,10 @@ JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
     if (!OptEntityDataEntryObject) {
       return llvm::createStringError(
           std::errc::invalid_argument,
-          "failed to deserialize entity data map from file '%s': "
+          "failed to deserialize entity data map: "
           "element at index %zu is not a JSON object "
           "(expected object with 'entity_id' and 'entity_summary' fields)",
-          Path.str().c_str(), Index);
+          Index);
     }
 
     const llvm::json::Value *EntityIdIntValue =
@@ -480,10 +464,10 @@ JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
     if (!EntityIdIntValue) {
       return llvm::createStringError(
           std::errc::invalid_argument,
-          "failed to deserialize entity data map entry from file '%s' "
+          "failed to deserialize entity data map entry "
           "at index %zu: missing required field 'entity_id' "
           "(expected unsigned integer EntityId)",
-          Path.str().c_str(), Index);
+          Index);
     }
 
     const std::optional<uint64_t> OptEntityIdInt =
@@ -491,10 +475,10 @@ JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
     if (!OptEntityIdInt) {
       return llvm::createStringError(
           std::errc::invalid_argument,
-          "failed to deserialize entity data map entry from file '%s' "
+          "failed to deserialize entity data map entry "
           "at index %zu: field 'entity_id' is not a valid unsigned 64-bit "
           "integer",
-          Path.str().c_str(), Index);
+          Index);
     }
 
     EntityId EI = entityIdFromJSON(*OptEntityIdInt);
@@ -504,21 +488,19 @@ JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
     if (!OptEntitySummaryObject) {
       return llvm::createStringError(
           std::errc::invalid_argument,
-          "failed to deserialize entity data map entry from file '%s' "
+          "failed to deserialize entity data map entry "
           "at index %zu: missing or invalid field 'entity_summary' "
           "(expected EntitySummary JSON object)",
-          Path.str().c_str(), Index);
+          Index);
     }
 
     auto ExpectedEntitySummary =
-        entitySummaryFromJSON(*OptEntitySummaryObject, Path);
+        entitySummaryFromJSON(SN, *OptEntitySummaryObject, IdTable);
 
     if (!ExpectedEntitySummary) {
       return wrapError(
           ExpectedEntitySummary.takeError(),
-          "failed to deserialize entity data map entry from file '%s' "
-          "at index %zu",
-          Path.str().c_str(), Index);
+          "failed to deserialize entity data map entry at index %zu", Index);
     }
 
     auto [DataIt, DataInserted] = EntityDataMap.insert(
@@ -526,9 +508,9 @@ JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
     if (!DataInserted) {
       return llvm::createStringError(
           std::errc::invalid_argument,
-          "failed to deserialize entity data map from file '%s': "
+          "failed to deserialize entity data map: "
           "duplicate EntityId (%zu) found at index %zu",
-          Path.str().c_str(), getEntityIdIndex(DataIt->first), Index);
+          getEntityIdIndex(DataIt->first), Index);
     }
 
     ++Index;
@@ -538,6 +520,7 @@ JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
 }
 
 llvm::json::Array JSONFormat::entityDataMapToJSON(
+    const SummaryName &SN,
     const std::map<EntityId, std::unique_ptr<EntitySummary>> &EntityDataMap)
     const {
   llvm::json::Array Result;
@@ -545,7 +528,7 @@ llvm::json::Array JSONFormat::entityDataMapToJSON(
   for (const auto &[EntityId, EntitySummary] : EntityDataMap) {
     llvm::json::Object Entry;
     Entry["entity_id"] = entityIdToJSON(EntityId);
-    Entry["entity_summary"] = entitySummaryToJSON(*EntitySummary);
+    Entry["entity_summary"] = entitySummaryToJSON(SN, *EntitySummary);
     Result.push_back(std::move(Entry));
   }
   return Result;
@@ -554,7 +537,7 @@ llvm::json::Array JSONFormat::entityDataMapToJSON(
 llvm::Expected<
     std::pair<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
 JSONFormat::summaryDataMapEntryFromJSON(
-    const llvm::json::Object &SummaryDataObject, llvm::StringRef Path) {
+    const llvm::json::Object &SummaryDataObject, EntityIdTable &IdTable) {
 
   std::optional<llvm::StringRef> OptSummaryNameStr =
       SummaryDataObject.getString("summary_name");
@@ -562,31 +545,30 @@ JSONFormat::summaryDataMapEntryFromJSON(
   if (!OptSummaryNameStr) {
     return llvm::createStringError(
         std::errc::invalid_argument,
-        "failed to deserialize summary data from file '%s': "
+        "failed to deserialize summary data: "
         "missing required field 'summary_name' "
-        "(expected string identifier for the analysis summary)",
-        Path.str().c_str());
+        "(expected string identifier for the analysis summary)");
   }
 
   SummaryName SN = summaryNameFromJSON(*OptSummaryNameStr);
 
   const llvm::json::Array *OptEntityDataArray =
-      SummaryDataObject.getArray("summary_data");
+      SummaryDataObject.getArray("data");
   if (!OptEntityDataArray) {
     return llvm::createStringError(
         std::errc::invalid_argument,
-        "failed to deserialize summary data from file '%s' for summary '%s': "
-        "missing or invalid field 'summary_data' "
+        "failed to deserialize summary data for summary '%s': "
+        "missing or invalid field 'data' "
         "(expected JSON array of entity summaries)",
-        Path.str().c_str(), SN.str().data());
+        SN.str().data());
   }
 
-  auto ExpectedEntityDataMap = entityDataMapFromJSON(*OptEntityDataArray, Path);
+  auto ExpectedEntityDataMap =
+      entityDataMapFromJSON(SN, *OptEntityDataArray, IdTable);
   if (!ExpectedEntityDataMap)
-    return wrapError(
-        ExpectedEntityDataMap.takeError(),
-        "failed to deserialize summary data from file '%s' for summary '%s'",
-        Path.str().c_str(), SN.str().data());
+    return wrapError(ExpectedEntityDataMap.takeError(),
+                     "failed to deserialize summary data for summary '%s'",
+                     SN.str().data());
 
   return std::make_pair(std::move(SN), std::move(*ExpectedEntityDataMap));
 }
@@ -596,190 +578,53 @@ llvm::json::Object JSONFormat::summaryDataMapEntryToJSON(
     const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const {
   llvm::json::Object Result;
   Result["summary_name"] = summaryNameToJSON(SN);
-  Result["summary_data"] = entityDataMapToJSON(SD);
+  Result["data"] = entityDataMapToJSON(SN, SD);
   return Result;
 }
 
 //----------------------------------------------------------------------------
-// SummaryDataMap
+// TUSummary
 //----------------------------------------------------------------------------
 
-llvm::Expected<
-    std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
-JSONFormat::readTUSummaryData(llvm::StringRef Path) {
-  if (!FS->exists(Path)) {
-    return llvm::createStringError(
-        std::errc::no_such_file_or_directory,
-        "failed to read TUSummary data: directory does not exist: '%s'",
-        Path.str().c_str());
-  }
-
-  auto StatusOrErr = FS->status(Path);
-  if (!StatusOrErr) {
-    return llvm::createStringError(
-        StatusOrErr.getError(),
-        "failed to read TUSummary data: cannot get status of '%s'",
-        Path.str().c_str());
-  }
-
-  if (!StatusOrErr->isDirectory()) {
-    return llvm::createStringError(
-        std::errc::not_a_directory,
-        "failed to read TUSummary data: path is not a directory: '%s'",
-        Path.str().c_str());
-  }
-
-  std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>
-      Data;
-  std::error_code EC;
-
-  auto DirIter = FS->dir_begin(Path, EC);
-  if (EC) {
-    return llvm::createStringError(
-        EC, "failed to read TUSummary data: cannot iterate directory '%s'",
-        Path.str().c_str());
-  }
-
-  for (llvm::vfs::directory_iterator End; DirIter != End && !EC;
-       DirIter.increment(EC)) {
-    llvm::StringRef SummaryPathRef = DirIter->path();
-    std::string SummaryPath = SummaryPathRef.str();
-
-    auto ExpectedObject = readJSONObject(*FS, SummaryPath);
-    if (!ExpectedObject)
-      return wrapError(ExpectedObject.takeError(),
-                       "failed to read TUSummary data from file '%s'",
-                       SummaryPath.c_str());
-
-    auto ExpectedSummaryDataMap =
-        summaryDataMapEntryFromJSON(*ExpectedObject, SummaryPath);
-    if (!ExpectedSummaryDataMap)
-      return wrapError(ExpectedSummaryDataMap.takeError(),
-                       "failed to read TUSummary data from file '%s'",
-                       SummaryPath.c_str());
-
-    auto [SummaryIt, SummaryInserted] =
-        Data.emplace(std::move(*ExpectedSummaryDataMap));
-    if (!SummaryInserted) {
-      return llvm::createStringError(
-          std::errc::invalid_argument,
-          "failed to read TUSummary data from directory '%s': "
-          "duplicate SummaryName '%s' encountered in file '%s'",
-          Path.str().c_str(), SummaryIt->first.str().data(),
-          SummaryPath.c_str());
-    }
-  }
-
-  if (EC) {
-    return llvm::createStringError(EC,
-                                   "failed to read TUSummary data: "
-                                   "error during directory iteration of '%s'",
-                                   Path.str().c_str());
-  }
-
-  return Data;
-}
-
-std::string makeValidFilename(llvm::StringRef Name, size_t Prefix,
-                              char Replacement) {
-  std::string Result;
-  llvm::raw_string_ostream OS(Result);
-  OS << llvm::format("%02d-%s", Prefix, Name.str().c_str());
-
-  for (size_t Index = 0; Index < Result.size(); ++Index) {
-    char &Actual = Result[Index];
-    if (llvm::isAlnum(Actual) || Actual == '-' || Actual == '_')
-      continue;
-    Actual = Replacement;
-  }
-
-  return Result;
-}
-
-llvm::Error JSONFormat::writeTUSummaryData(
-    const std::map<SummaryName,
-                   std::map<EntityId, std::unique_ptr<EntitySummary>>> &Data,
-    llvm::StringRef Path) {
-  if (!FS->exists(Path)) {
-    return llvm::createStringError(
-        std::errc::no_such_file_or_directory,
-        "failed to write TUSummary data: directory does not exist: '%s'",
-        Path.str().c_str());
-  }
+llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
+  // Read the JSON object from the file
+  auto ExpectedRootObject = readJSONObject(Path);
+  if (!ExpectedRootObject)
+    return wrapError(ExpectedRootObject.takeError(),
+                     "failed to read TUSummary from '%s'", Path.str().c_str());
 
-  auto StatusOrErr = FS->status(Path);
-  if (!StatusOrErr) {
-    return llvm::createStringError(
-        StatusOrErr.getError(),
-        "failed to write TUSummary data: cannot get status of '%s'",
-        Path.str().c_str());
-  }
+  const llvm::json::Object &RootObject = *ExpectedRootObject;
 
-  if (!StatusOrErr->isDirectory()) {
+  // Parse TUNamespace field
+  const llvm::json::Object *TUNamespaceObject =
+      RootObject.getObject("tu_namespace");
+  if (!TUNamespaceObject) {
     return llvm::createStringError(
-        std::errc::not_a_directory,
-        "failed to write TUSummary data: path is not a directory: '%s'",
+        std::errc::invalid_argument,
+        "failed to read TUSummary from '%s': "
+        "missing or invalid field 'tu_namespace' (expected JSON object)",
         Path.str().c_str());
   }
 
-  size_t Index = 0;
-  for (const auto &[SummaryName, DataMap] : Data) {
-    llvm::SmallString<kPathBufferSize> SummaryPath(Path);
-    llvm::sys::path::append(SummaryPath,
-                            makeValidFilename(SummaryName.str(), Index, '_'));
-    llvm::sys::path::replace_extension(SummaryPath, ".json");
-
-    llvm::json::Object Result = summaryDataMapEntryToJSON(SummaryName, DataMap);
-    if (auto Error = writeJSON(*FS, std::move(Result), SummaryPath)) {
-      return wrapError(
-          std::move(Error), std::errc::io_error,
-          "failed to write TUSummary data to directory '%s': cannot write "
-          "summary '%s' to file '%s'",
-          Path.str().c_str(), SummaryName.str().data(),
-          SummaryPath.str().data());
-    }
-
-    ++Index;
-  }
-
-  return llvm::Error::success();
-}
-
-//----------------------------------------------------------------------------
-// TUSummary
-//----------------------------------------------------------------------------
-
-llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
-
-  // Populate TUNamespace field.
-  llvm::SmallString<kPathBufferSize> TUNamespacePath(Path);
-  llvm::sys::path::append(TUNamespacePath, TUSummaryTUNamespaceFilename);
-
-  auto ExpectedObject = readJSONObject(*FS, TUNamespacePath);
-  if (!ExpectedObject)
-    return wrapError(ExpectedObject.takeError(),
-                     "failed to read TUSummary from '%s'", Path.str().c_str());
-
-  auto ExpectedTUNamespace =
-      buildNamespaceFromJSON(*ExpectedObject, TUNamespacePath);
+  auto ExpectedTUNamespace = buildNamespaceFromJSON(*TUNamespaceObject);
   if (!ExpectedTUNamespace)
     return wrapError(ExpectedTUNamespace.takeError(),
                      "failed to read TUSummary from '%s'", Path.str().c_str());
 
   TUSummary Summary(std::move(*ExpectedTUNamespace));
 
-  // Populate IdTable field.
+  // Parse IdTable field
   {
-    llvm::SmallString<kPathBufferSize> IdTablePath(Path);
-    llvm::sys::path::append(IdTablePath, TUSummaryIdTableFilename);
-
-    auto ExpectedArray = readJSONArray(*FS, IdTablePath);
-    if (!ExpectedArray)
-      return wrapError(ExpectedArray.takeError(),
-                       "failed to read TUSummary from '%s'",
-                       Path.str().c_str());
+    const llvm::json::Array *IdTableArray = RootObject.getArray("id_table");
+    if (!IdTableArray) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "failed to read TUSummary from '%s': "
+          "missing or invalid field 'id_table' (expected JSON array)",
+          Path.str().c_str());
+    }
 
-    auto ExpectedIdTable = entityIdTableFromJSON(*ExpectedArray, IdTablePath);
+    auto ExpectedIdTable = entityIdTableFromJSON(*IdTableArray);
     if (!ExpectedIdTable)
       return wrapError(ExpectedIdTable.takeError(),
                        "failed to read TUSummary from '%s'",
@@ -788,120 +633,75 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
     getIdTable(Summary) = std::move(*ExpectedIdTable);
   }
 
-  // Populate Data field.
+  // Parse data field
   {
-    llvm::SmallString<kPathBufferSize> DataPath(Path);
-    llvm::sys::path::append(DataPath, TUSummaryDataDirname);
-
-    if (!FS->exists(DataPath)) {
-      return llvm::createStringError(std::errc::no_such_file_or_directory,
-                                     "failed to read TUSummary from '%s': "
-                                     "data directory does not exist: '%s'",
-                                     Path.str().c_str(), DataPath.str().data());
+    const llvm::json::Array *SummaryDataArray = RootObject.getArray("data");
+    if (!SummaryDataArray) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "failed to read TUSummary from '%s': "
+          "missing or invalid field 'data' (expected JSON array)",
+          Path.str().c_str());
     }
 
-    auto StatusOrErr = FS->status(DataPath);
-    if (!StatusOrErr) {
-      return llvm::createStringError(StatusOrErr.getError(),
-                                     "failed to read TUSummary from '%s': "
-                                     "cannot get status of data path: '%s'",
-                                     Path.str().c_str(), DataPath.str().data());
-    }
+    // Parse each summary data entry
+    std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>
+        Data;
+    for (const llvm::json::Value &SummaryDataValue : *SummaryDataArray) {
+      const llvm::json::Object *SummaryDataObject =
+          SummaryDataValue.getAsObject();
+      if (!SummaryDataObject) {
+        return llvm::createStringError(std::errc::invalid_argument,
+                                       "failed to read TUSummary from '%s': "
+                                       "data array contains non-object element",
+                                       Path.str().c_str());
+      }
 
-    if (!StatusOrErr->isDirectory()) {
-      return llvm::createStringError(std::errc::not_a_directory,
-                                     "failed to read TUSummary from '%s': "
-                                     "data path is not a directory: '%s'",
-                                     Path.str().c_str(), DataPath.str().data());
+      auto ExpectedSummaryDataEntry =
+          summaryDataMapEntryFromJSON(*SummaryDataObject, getIdTable(Summary));
+      if (!ExpectedSummaryDataEntry)
+        return wrapError(ExpectedSummaryDataEntry.takeError(),
+                         "failed to read TUSummary from '%s'",
+                         Path.str().c_str());
+
+      auto [SummaryIt, SummaryInserted] =
+          Data.emplace(std::move(*ExpectedSummaryDataEntry));
+      if (!SummaryInserted) {
+        return llvm::createStringError(std::errc::invalid_argument,
+                                       "failed to read TUSummary from '%s': "
+                                       "duplicate SummaryName '%s' found",
+                                       Path.str().c_str(),
+                                       SummaryIt->first.str().data());
+      }
     }
 
-    auto ExpectedData = readTUSummaryData(DataPath);
-    if (!ExpectedData)
-      return wrapError(ExpectedData.takeError(),
-                       "failed to read TUSummary from '%s'",
-                       Path.str().c_str());
-
-    getData(Summary) = std::move(*ExpectedData);
+    getData(Summary) = std::move(Data);
   }
 
   return Summary;
 }
 
 llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
-                                       llvm::StringRef Dir) {
-  // Serialize TUNamespace field.
-  {
-    llvm::SmallString<kPathBufferSize> TUNamespacePath(Dir);
-    llvm::sys::path::append(TUNamespacePath, TUSummaryTUNamespaceFilename);
-
-    llvm::json::Object BuildNamespaceObj =
-        buildNamespaceToJSON(getTUNamespace(S));
-    if (auto Error =
-            writeJSON(*FS, std::move(BuildNamespaceObj), TUNamespacePath)) {
-      return wrapError(std::move(Error), std::errc::io_error,
-                       "failed to write TUSummary to '%s': "
-                       "cannot write TUNamespace file '%s'",
-                       Dir.str().c_str(), TUNamespacePath.str().data());
-    }
-  }
+                                       llvm::StringRef Path) {
+  llvm::json::Object RootObject;
 
-  // Serialize IdTable field.
-  {
-    llvm::SmallString<kPathBufferSize> IdTablePath(Dir);
-    llvm::sys::path::append(IdTablePath, TUSummaryIdTableFilename);
-
-    llvm::json::Array IdTableObj = entityIdTableToJSON(getIdTable(S));
-    if (auto Error = writeJSON(*FS, std::move(IdTableObj), IdTablePath)) {
-      return wrapError(std::move(Error), std::errc::io_error,
-                       "failed to write TUSummary to '%s': "
-                       "cannot write IdTable file '%s'",
-                       Dir.str().c_str(), IdTablePath.str().data());
-    }
-  }
+  RootObject["tu_namespace"] = buildNamespaceToJSON(getTUNamespace(S));
 
-  // Serialize Data field.
-  {
-    llvm::SmallString<kPathBufferSize> DataPath(Dir);
-    llvm::sys::path::append(DataPath, TUSummaryDataDirname);
-
-    // Create the data directory if it doesn't exist
-    // Use the real filesystem for directory creation as VFS doesn't always
-    // support this
-    if (std::error_code EC = llvm::sys::fs::create_directory(DataPath)) {
-      // If error is not "already exists", return error
-      if (EC != std::errc::file_exists) {
-        return llvm::createStringError(EC,
-                                       "failed to write TUSummary to '%s': "
-                                       "cannot create data directory '%s'",
-                                       Dir.str().c_str(),
-                                       DataPath.str().data());
-      }
-    }
+  RootObject["id_table"] = entityIdTableToJSON(getIdTable(S));
 
-    // Verify it's a directory (could be a file with the same name)
-    if (FS->exists(DataPath)) {
-      auto StatusOrErr = FS->status(DataPath);
-      if (!StatusOrErr) {
-        return llvm::createStringError(StatusOrErr.getError(),
-                                       "failed to write TUSummary to '%s': "
-                                       "cannot get status of data path '%s'",
-                                       Dir.str().c_str(),
-                                       DataPath.str().data());
-      }
-      if (!StatusOrErr->isDirectory()) {
-        return llvm::createStringError(
-            std::errc::not_a_directory,
-            "failed to write TUSummary to '%s': data path exists but is not a "
-            "directory: '%s'",
-            Dir.str().c_str(), DataPath.str().data());
-      }
+  {
+    llvm::json::Array SummaryDataArray;
+    for (const auto &[SummaryName, DataMap] : getData(S)) {
+      SummaryDataArray.push_back(
+          summaryDataMapEntryToJSON(SummaryName, DataMap));
     }
+    RootObject["data"] = std::move(SummaryDataArray);
+  }
 
-    if (auto Error = writeTUSummaryData(getData(S), DataPath)) {
-      return wrapError(std::move(Error), std::errc::io_error,
-                       "failed to write TUSummary to '%s': cannot write data",
-                       Dir.str().c_str());
-    }
+  // Write the JSON to file
+  if (auto Error = writeJSON(std::move(RootObject), Path)) {
+    return wrapError(std::move(Error), std::errc::io_error,
+                     "failed to write TUSummary to '%s'", Path.str().c_str());
   }
 
   return llvm::Error::success();
diff --git a/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test b/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
new file mode 100644
index 0000000000000..c051f41af4fc4
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/callgraph-invalid-caller.json 2>&1 | FileCheck %s
+
+// Test that CallGraph analysis validates caller field type
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize EntitySummary
+// CHECK: CallGraph: 'caller' at index 0 is not a valid uint64
diff --git a/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test b/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
new file mode 100644
index 0000000000000..0e5e2877d06d2
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/callgraph-missing-field.json 2>&1 | FileCheck %s
+
+// Test that CallGraph analysis requires 'call_graph' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize EntitySummary
+// CHECK: CallGraph: missing or invalid 'call_graph' field
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
new file mode 100644
index 0000000000000..90dea14d683db
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
@@ -0,0 +1,6 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/data-element-not-object.json 2>&1 | FileCheck %s
+
+// Test that 'data' array elements must be objects
+
+// CHECK: failed to read TUSummary
+// CHECK: data array contains non-object element
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test b/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
new file mode 100644
index 0000000000000..865db1e8709cd
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/data-entry-missing-data.json 2>&1 | FileCheck %s
+
+// Test that data array entries must have 'data' field
+
+// CHECK: failed to read TUSummary
+// CHECK: for summary 'test'
+// CHECK: missing or invalid field 'data'
+// CHECK: expected JSON array of entity summaries
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test b/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
new file mode 100644
index 0000000000000..a9bf765424b7e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/data-missing-summary-name.json 2>&1 | FileCheck %s
+
+// Test that data array entries must have 'summary_name' field
+
+// CHECK: failed to read TUSummary
+// CHECK: missing required field 'summary_name'
+// CHECK: expected string identifier for the analysis summary
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-not-array.test b/clang/test/Analysis/Scalable/Serialization/error-data-not-array.test
new file mode 100644
index 0000000000000..058be1a4bddbb
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-not-array.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/data-not-array.json 2>&1 | FileCheck %s
+
+// Test that root 'data' field must be an array
+
+// CHECK: failed to read TUSummary
+// CHECK: missing or invalid field 'data'
+// CHECK: expected JSON array
diff --git a/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test b/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
new file mode 100644
index 0000000000000..64675a201dd59
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/defuse-invalid-use.json 2>&1 | FileCheck %s
+
+// Test that DefUse analysis validates use value types
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize EntitySummary
+// CHECK: DefUse: use at index 0,0,0 is not a valid uint64
diff --git a/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test b/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
new file mode 100644
index 0000000000000..73409a2d46503
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/defuse-missing-field.json 2>&1 | FileCheck %s
+
+// Test that DefUse analysis requires 'def_use_chains' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize EntitySummary
+// CHECK: DefUse: missing or invalid 'def_use_chains' field
diff --git a/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test b/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
new file mode 100644
index 0000000000000..8e4d594cc75e6
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/duplicate-entity.json 2>&1 | FileCheck %s
+
+// Test that duplicate EntityName in id_table is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable from file
+// CHECK: duplicate EntityName found at index 1
+// CHECK: EntityId=0 already exists in table
diff --git a/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test b/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
new file mode 100644
index 0000000000000..568db0a339620
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
@@ -0,0 +1,6 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/duplicate-summary-name.json 2>&1 | FileCheck %s
+
+// Test that duplicate summary names are detected
+
+// CHECK: failed to read TUSummary
+// CHECK: duplicate SummaryName 'test' found
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
new file mode 100644
index 0000000000000..72bf04adc267c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-data-element-not-object.json 2>&1 | FileCheck %s
+
+// Test that entity data elements must be objects
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map from file
+// CHECK: element at index 0 is not a JSON object
+// CHECK: expected object with 'entity_id' and 'entity_summary' fields
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
new file mode 100644
index 0000000000000..e2dd9a34b34d3
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
@@ -0,0 +1,9 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-data-missing-entity-id.json 2>&1 | FileCheck %s
+
+// Test that entity data entries must have 'entity_id' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry from file
+// CHECK: at index 0
+// CHECK: missing required field 'entity_id'
+// CHECK: expected unsigned integer EntityId
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
new file mode 100644
index 0000000000000..078bc4b068258
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
@@ -0,0 +1,9 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-data-missing-entity-summary.json 2>&1 | FileCheck %s
+
+// Test that entity data entries must have 'entity_summary' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry from file
+// CHECK: at index 0
+// CHECK: missing or invalid field 'entity_summary'
+// CHECK: expected EntitySummary JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test b/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
new file mode 100644
index 0000000000000..f5903b02d7eeb
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-id-not-uint64.json 2>&1 | FileCheck %s
+
+// Test that entity_id field must be a valid uint64
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry from file
+// CHECK: at index 0
+// CHECK: field 'entity_id' is not a valid unsigned 64-bit integer
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
new file mode 100644
index 0000000000000..f9230bed9b25f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
@@ -0,0 +1,9 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-name-missing-namespace.json 2>&1 | FileCheck %s
+
+// Test that EntityName must have 'namespace' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: failed to deserialize EntityName from file
+// CHECK: missing or invalid field 'namespace'
+// CHECK: expected JSON array of BuildNamespace objects
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
new file mode 100644
index 0000000000000..70a75f8b63433
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-name-missing-suffix.json 2>&1 | FileCheck %s
+
+// Test that EntityName must have 'suffix' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: failed to deserialize EntityName from file
+// CHECK: missing required field 'suffix'
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
new file mode 100644
index 0000000000000..8531cfa1bd307
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
@@ -0,0 +1,9 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-name-missing-usr.json 2>&1 | FileCheck %s
+
+// Test that EntityName must have 'usr' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: failed to deserialize EntityName from file
+// CHECK: missing required field 'usr'
+// CHECK: Unified Symbol Resolution string
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test b/clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test
new file mode 100644
index 0000000000000..9a317f1270c4a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-summary-no-format-info.json 2>&1 | FileCheck %s
+
+// Test that EntitySummary deserialization requires registered FormatInfo
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntitySummary from file
+// CHECK: no FormatInfo was registered for summary name
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test
new file mode 100644
index 0000000000000..5e1359a3ce94c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-element-not-object.json 2>&1 | FileCheck %s
+
+// Test that id_table elements must be objects
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable from file
+// CHECK: element at index 0 is not a JSON object
+// CHECK: expected EntityIdTable entry with 'id' and 'name' fields
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test
new file mode 100644
index 0000000000000..4e2706d89f52d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-entry-id-not-uint64.json 2>&1 | FileCheck %s
+
+// Test that id_table entry 'id' field must be a valid uint64
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: field 'id' is not a valid unsigned 64-bit integer
+// CHECK: expected non-negative EntityId value
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test
new file mode 100644
index 0000000000000..423cd54b3c1f6
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-entry-missing-id.json 2>&1 | FileCheck %s
+
+// Test that id_table entries must have 'id' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: missing required field 'id'
+// CHECK: expected unsigned integer EntityId
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test
new file mode 100644
index 0000000000000..de56b93de491b
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-entry-missing-name.json 2>&1 | FileCheck %s
+
+// Test that id_table entries must have 'name' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: missing or invalid field 'name'
+// CHECK: expected EntityName JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-not-array.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-not-array.test
new file mode 100644
index 0000000000000..8d7a9bd640247
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-not-array.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-not-array.json 2>&1 | FileCheck %s
+
+// Test that id_table must be an array
+
+// CHECK: failed to read TUSummary
+// CHECK: missing or invalid field 'id_table'
+// CHECK: expected JSON array
diff --git a/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test b/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
new file mode 100644
index 0000000000000..cecc51e633f40
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/invalid-kind.json 2>&1 | FileCheck %s
+
+// Test that invalid kind value is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize BuildNamespace from file
+// CHECK: invalid 'kind' BuildNamespaceKind value 'InvalidKind'
diff --git a/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test b/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
new file mode 100644
index 0000000000000..1724b13031c13
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
@@ -0,0 +1,6 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/invalid-syntax.json 2>&1 | FileCheck %s
+
+// Test that invalid JSON syntax is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to read JSON object from file
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-data.test b/clang/test/Analysis/Scalable/Serialization/error-missing-data.test
new file mode 100644
index 0000000000000..8d4b654a8715d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-data.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/missing-data.json 2>&1 | FileCheck %s
+
+// Test that missing root 'data' field is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: missing or invalid field 'data'
+// CHECK: expected JSON array
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-id-table.test b/clang/test/Analysis/Scalable/Serialization/error-missing-id-table.test
new file mode 100644
index 0000000000000..a71bb4e361fdd
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-id-table.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/missing-id-table.json 2>&1 | FileCheck %s
+
+// Test that missing id_table field is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: missing or invalid field 'id_table'
+// CHECK: expected JSON array
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test b/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
new file mode 100644
index 0000000000000..d26a39d46c4b2
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/missing-kind.json 2>&1 | FileCheck %s
+
+// Test that missing kind field in tu_namespace is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize BuildNamespace from file
+// CHECK: missing required field 'kind'
+// CHECK: expected BuildNamespaceKind
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-name.test b/clang/test/Analysis/Scalable/Serialization/error-missing-name.test
new file mode 100644
index 0000000000000..d88a8c46bc85f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-name.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/missing-name.json 2>&1 | FileCheck %s
+
+// Test that missing name field in tu_namespace is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize BuildNamespace from file
+// CHECK: missing required field 'name'
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-tu-namespace.test b/clang/test/Analysis/Scalable/Serialization/error-missing-tu-namespace.test
new file mode 100644
index 0000000000000..fb6b58693edf6
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-tu-namespace.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/missing-tu-namespace.json 2>&1 | FileCheck %s
+
+// Test that missing tu_namespace field is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: missing or invalid field 'tu_namespace'
+// CHECK: expected JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
new file mode 100644
index 0000000000000..4723d9d4434d9
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
@@ -0,0 +1,10 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/namespace-element-not-object.json 2>&1 | FileCheck %s
+
+// Test that namespace array elements must be objects
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: failed to deserialize EntityName from file
+// CHECK: failed to deserialize NestedBuildNamespace from file
+// CHECK: element at index 0 is not a JSON object
+// CHECK: expected BuildNamespace object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-nonexistent-file.test b/clang/test/Analysis/Scalable/Serialization/error-nonexistent-file.test
new file mode 100644
index 0000000000000..3748d54622e75
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-nonexistent-file.test
@@ -0,0 +1,6 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/nonexistent.json 2>&1 | FileCheck %s
+
+// Test that nonexistent file is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: file does not exist
diff --git a/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test b/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
new file mode 100644
index 0000000000000..57bcab0bf8212
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/not_json-extension.txt 2>&1 | FileCheck %s
+
+// Test that files with non-.json extensions are rejected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to read JSON object from file
+// CHECK: failed to validate JSON file
+// CHECK: not a JSON file
diff --git a/clang/test/Analysis/Scalable/Serialization/error-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-not-object.test
new file mode 100644
index 0000000000000..ec34a20c9f641
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-not-object.test
@@ -0,0 +1,6 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/not-object.json 2>&1 | FileCheck %s
+
+// Test that JSON array (not object) is rejected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to read JSON object from file
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-callgraph.test b/clang/test/Analysis/Scalable/Serialization/valid-callgraph.test
new file mode 100644
index 0000000000000..09722d11a796e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/valid-callgraph.test
@@ -0,0 +1,9 @@
+// RUN: ssaf-serialization-format-test %S/Inputs/valid-callgraph.json -q -o %t.json
+// RUN: diff %t.json %S/Inputs/valid-callgraph-expected.json
+
+// Test CallGraph analysis round-trip serialization
+// This test verifies that:
+// - CallGraph analysis data can be read from JSON
+// - The call graph structure (caller -> callees) is preserved
+// - EntityId references are correctly serialized/deserialized
+// - Output matches expected format with alphabetically sorted fields
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-defuse.test b/clang/test/Analysis/Scalable/Serialization/valid-defuse.test
new file mode 100644
index 0000000000000..311bd02fdde3d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/valid-defuse.test
@@ -0,0 +1,10 @@
+// RUN: ssaf-serialization-format-test %S/Inputs/valid-defuse.json -q -o %t.json
+// RUN: diff %t.json %S/Inputs/valid-defuse-expected.json
+
+// Test DefUse analysis round-trip serialization
+// This test verifies that:
+// - DefUse analysis data can be read from JSON
+// - The def-use chain structure (variable -> definitions -> uses) is preserved
+// - Nested maps and sets are correctly serialized/deserialized
+// - EntityId references are correctly converted using EntityIdConverter
+// - Output matches expected format with alphabetically sorted fields
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-empty.test b/clang/test/Analysis/Scalable/Serialization/valid-empty.test
new file mode 100644
index 0000000000000..30712a3e8d637
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/valid-empty.test
@@ -0,0 +1,4 @@
+// RUN: ssaf-serialization-format-test %S/Inputs/valid-empty.json -q -o %t.json
+// RUN: diff %t.json %S/Inputs/valid-empty-expected.json
+
+// Test that an empty valid TUSummary can be read and written back
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-link-unit.test b/clang/test/Analysis/Scalable/Serialization/valid-link-unit.test
new file mode 100644
index 0000000000000..b9533904c0bd1
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/valid-link-unit.test
@@ -0,0 +1,4 @@
+// RUN: ssaf-serialization-format-test %S/Inputs/valid-link-unit.json -q -o %t.json
+// RUN: diff %t.json %S/Inputs/valid-link-unit-expected.json
+
+// Test that a TUSummary with link_unit namespace can be read and written back
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-with-empty-data-entry.test b/clang/test/Analysis/Scalable/Serialization/valid-with-empty-data-entry.test
new file mode 100644
index 0000000000000..a9be32902a10a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/valid-with-empty-data-entry.test
@@ -0,0 +1,4 @@
+// RUN: ssaf-serialization-format-test %S/Inputs/valid-with-empty-data-entry.json -q -o %t.json
+// RUN: diff %t.json %S/Inputs/valid-with-empty-data-entry-expected.json
+
+// Test that a TUSummary with an empty data entry can be read and written back
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-with-idtable.test b/clang/test/Analysis/Scalable/Serialization/valid-with-idtable.test
new file mode 100644
index 0000000000000..a714aa7dcf2d1
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/valid-with-idtable.test
@@ -0,0 +1,4 @@
+// RUN: ssaf-serialization-format-test %S/Inputs/valid-with-idtable.json -q -o %t.json
+// RUN: diff %t.json %S/Inputs/valid-with-idtable-expected.json
+
+// Test that a TUSummary with id_table entries can be read and written back
diff --git a/clang/test/lit.cfg.py b/clang/test/lit.cfg.py
index a622f5335354a..bb5c9b331a43f 100644
--- a/clang/test/lit.cfg.py
+++ b/clang/test/lit.cfg.py
@@ -103,6 +103,7 @@
     "clang-diff",
     "clang-format",
     "clang-repl",
+    "ssaf-serialization-format-test",
     "llvm-offload-binary",
     "clang-tblgen",
     "clang-scan-deps",
diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt b/clang/unittests/Analysis/Scalable/CMakeLists.txt
index e9bfca2c8fe7f..993ca3669228c 100644
--- a/clang/unittests/Analysis/Scalable/CMakeLists.txt
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -28,3 +28,6 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
   FrontendOpenMP
   Support
   )
+
+add_subdirectory(tools)
+
diff --git a/clang/unittests/Analysis/Scalable/tools/CMakeLists.txt b/clang/unittests/Analysis/Scalable/tools/CMakeLists.txt
new file mode 100644
index 0000000000000..69e51c6a6aba5
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/tools/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(ssaf-serialization-format-test)
diff --git a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/CMakeLists.txt b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/CMakeLists.txt
new file mode 100644
index 0000000000000..ea008bee8d2db
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/CMakeLists.txt
@@ -0,0 +1,9 @@
+add_clang_tool(ssaf-serialization-format-test
+  SSAFSerializationFormatTest.cpp
+  ExampleAnalyses.cpp
+  )
+
+target_link_libraries(ssaf-serialization-format-test
+  PRIVATE
+  clangAnalysisScalable
+  )
diff --git a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
new file mode 100644
index 0000000000000..f8b4a535f00f3
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
@@ -0,0 +1,303 @@
+//===- ExampleAnalyses.cpp - Example analysis data for clang-ssaf-dump ---===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines example analysis data structures and their serialization/
+// deserialization functions for the JSONFormat.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/TUSummary/EntitySummary.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/Registry.h"
+#include <map>
+#include <set>
+
+using namespace clang::ssaf;
+
+//===----------------------------------------------------------------------===//
+// CallGraphAnalysis - Tracks caller-to-callees relationships
+//===----------------------------------------------------------------------===//
+
+namespace {
+struct CallGraphAnalysis : EntitySummary {
+  CallGraphAnalysis() : EntitySummary(SummaryName("CallGraph")) {}
+
+  // Maps each function (EntityId) to the set of functions it calls
+  std::map<EntityId, std::set<EntityId>> CallGraph;
+};
+} // namespace
+
+static llvm::json::Object
+serializeCallGraph(const EntitySummary &Data,
+                   const JSONFormat::EntityIdConverter &Converter) {
+  const auto &CG = static_cast<const CallGraphAnalysis &>(Data);
+  llvm::json::Object Result;
+
+  // Serialize the call graph as an array of objects
+  llvm::json::Array CallGraphArray;
+  for (const auto &[Caller, Callees] : CG.CallGraph) {
+    llvm::json::Object Entry;
+    Entry["caller"] = Converter.toJSON(Caller);
+
+    llvm::json::Array CalleesArray;
+    for (const auto &Callee : Callees) {
+      CalleesArray.push_back(Converter.toJSON(Callee));
+    }
+    Entry["callees"] = std::move(CalleesArray);
+
+    CallGraphArray.push_back(std::move(Entry));
+  }
+
+  Result["call_graph"] = std::move(CallGraphArray);
+  return Result;
+}
+
+static llvm::Expected<std::unique_ptr<EntitySummary>>
+deserializeCallGraph(const llvm::json::Object &JSONObj, EntityIdTable &Table,
+                     const JSONFormat::EntityIdConverter &Converter) {
+  auto Result = std::make_unique<CallGraphAnalysis>();
+
+  const llvm::json::Array *CallGraphArray = JSONObj.getArray("call_graph");
+  if (!CallGraphArray) {
+    return llvm::createStringError(
+        std::errc::invalid_argument,
+        "CallGraph: missing or invalid 'call_graph' field");
+  }
+
+  for (size_t Index = 0; Index < CallGraphArray->size(); ++Index) {
+    const llvm::json::Object *EntryObj = (*CallGraphArray)[Index].getAsObject();
+    if (!EntryObj) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "CallGraph: call_graph entry at index %zu is not an object", Index);
+    }
+
+    // Parse caller
+    const llvm::json::Value *CallerValue = EntryObj->get("caller");
+    if (!CallerValue) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "CallGraph: entry at index %zu missing 'caller' field", Index);
+    }
+    auto CallerInt = CallerValue->getAsUINT64();
+    if (!CallerInt) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "CallGraph: 'caller' at index %zu is not a valid uint64", Index);
+    }
+    EntityId Caller = Converter.fromJSON(*CallerInt);
+
+    // Parse callees array
+    const llvm::json::Array *CalleesArray = EntryObj->getArray("callees");
+    if (!CalleesArray) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "CallGraph: entry at index %zu missing or invalid 'callees' field",
+          Index);
+    }
+
+    std::set<EntityId> Callees;
+    for (size_t CalleeIndex = 0; CalleeIndex < CalleesArray->size();
+         ++CalleeIndex) {
+      auto CalleeInt = (*CalleesArray)[CalleeIndex].getAsUINT64();
+      if (!CalleeInt) {
+        return llvm::createStringError(
+            std::errc::invalid_argument,
+            "CallGraph: callee at index %zu,%zu is not a valid uint64", Index,
+            CalleeIndex);
+      }
+      Callees.insert(Converter.fromJSON(*CalleeInt));
+    }
+
+    Result->CallGraph[Caller] = std::move(Callees);
+  }
+
+  return std::move(Result);
+}
+
+namespace {
+using FormatInfo = JSONFormat::FormatInfo;
+struct CallGraphFormatInfo : FormatInfo {
+  CallGraphFormatInfo()
+      : FormatInfo{
+            SummaryName("CallGraph"),
+            serializeCallGraph,
+            deserializeCallGraph,
+        } {}
+};
+} // namespace
+
+static llvm::Registry<JSONFormat::FormatInfo>::Add<CallGraphFormatInfo>
+    RegisterCallGraph("CallGraphAnalysis",
+                      "Format info for CallGraph analysis data");
+
+//===----------------------------------------------------------------------===//
+// DefUseAnalysis - Tracks definition-use chains
+//===----------------------------------------------------------------------===//
+
+namespace {
+struct DefUseAnalysis : EntitySummary {
+  DefUseAnalysis() : EntitySummary(SummaryName("DefUse")) {}
+
+  // For each variable (EntityId), maps definitions (EntityId) to their use
+  // sites (set of EntityId)
+  std::map<EntityId, std::map<EntityId, std::set<EntityId>>> DefUseChains;
+};
+} // namespace
+
+static llvm::json::Object
+serializeDefUse(const EntitySummary &Data,
+                const JSONFormat::EntityIdConverter &Converter) {
+  const auto &DU = static_cast<const DefUseAnalysis &>(Data);
+  llvm::json::Object Result;
+
+  // Serialize def-use chains as an array of objects
+  llvm::json::Array ChainsArray;
+  for (const auto &[Variable, DefUseMap] : DU.DefUseChains) {
+    llvm::json::Object VarEntry;
+    VarEntry["variable"] = Converter.toJSON(Variable);
+
+    llvm::json::Array DefsArray;
+    for (const auto &[Definition, Uses] : DefUseMap) {
+      llvm::json::Object DefEntry;
+      DefEntry["definition"] = Converter.toJSON(Definition);
+
+      llvm::json::Array UsesArray;
+      for (const auto &Use : Uses) {
+        UsesArray.push_back(Converter.toJSON(Use));
+      }
+      DefEntry["uses"] = std::move(UsesArray);
+
+      DefsArray.push_back(std::move(DefEntry));
+    }
+    VarEntry["definitions"] = std::move(DefsArray);
+
+    ChainsArray.push_back(std::move(VarEntry));
+  }
+
+  Result["def_use_chains"] = std::move(ChainsArray);
+  return Result;
+}
+
+static llvm::Expected<std::unique_ptr<EntitySummary>>
+deserializeDefUse(const llvm::json::Object &JSONObj, EntityIdTable &Table,
+                  const JSONFormat::EntityIdConverter &Converter) {
+  auto Result = std::make_unique<DefUseAnalysis>();
+
+  const llvm::json::Array *ChainsArray = JSONObj.getArray("def_use_chains");
+  if (!ChainsArray) {
+    return llvm::createStringError(
+        std::errc::invalid_argument,
+        "DefUse: missing or invalid 'def_use_chains' field");
+  }
+
+  for (size_t VarIndex = 0; VarIndex < ChainsArray->size(); ++VarIndex) {
+    const llvm::json::Object *VarObj = (*ChainsArray)[VarIndex].getAsObject();
+    if (!VarObj) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "DefUse: def_use_chains entry at index %zu is not an object",
+          VarIndex);
+    }
+
+    // Parse variable
+    const llvm::json::Value *VarValue = VarObj->get("variable");
+    if (!VarValue) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "DefUse: entry at index %zu missing 'variable' field", VarIndex);
+    }
+    auto VarInt = VarValue->getAsUINT64();
+    if (!VarInt) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "DefUse: 'variable' at index %zu is not a valid uint64", VarIndex);
+    }
+    EntityId Variable = Converter.fromJSON(*VarInt);
+
+    // Parse definitions array
+    const llvm::json::Array *DefsArray = VarObj->getArray("definitions");
+    if (!DefsArray) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "DefUse: entry at index %zu missing or invalid 'definitions' field",
+          VarIndex);
+    }
+
+    std::map<EntityId, std::set<EntityId>> DefUseMap;
+    for (size_t DefIndex = 0; DefIndex < DefsArray->size(); ++DefIndex) {
+      const llvm::json::Object *DefObj = (*DefsArray)[DefIndex].getAsObject();
+      if (!DefObj) {
+        return llvm::createStringError(
+            std::errc::invalid_argument,
+            "DefUse: definition at index %zu,%zu is not an object", VarIndex,
+            DefIndex);
+      }
+
+      // Parse definition
+      const llvm::json::Value *DefValue = DefObj->get("definition");
+      if (!DefValue) {
+        return llvm::createStringError(
+            std::errc::invalid_argument,
+            "DefUse: definition at index %zu,%zu missing 'definition' field",
+            VarIndex, DefIndex);
+      }
+      auto DefInt = DefValue->getAsUINT64();
+      if (!DefInt) {
+        return llvm::createStringError(
+            std::errc::invalid_argument,
+            "DefUse: 'definition' at index %zu,%zu is not a valid uint64",
+            VarIndex, DefIndex);
+      }
+      EntityId Definition = Converter.fromJSON(*DefInt);
+
+      // Parse uses array
+      const llvm::json::Array *UsesArray = DefObj->getArray("uses");
+      if (!UsesArray) {
+        return llvm::createStringError(
+            std::errc::invalid_argument,
+            "DefUse: definition at index %zu,%zu missing or invalid 'uses' "
+            "field",
+            VarIndex, DefIndex);
+      }
+
+      std::set<EntityId> Uses;
+      for (size_t UseIndex = 0; UseIndex < UsesArray->size(); ++UseIndex) {
+        auto UseInt = (*UsesArray)[UseIndex].getAsUINT64();
+        if (!UseInt) {
+          return llvm::createStringError(
+              std::errc::invalid_argument,
+              "DefUse: use at index %zu,%zu,%zu is not a valid uint64",
+              VarIndex, DefIndex, UseIndex);
+        }
+        Uses.insert(Converter.fromJSON(*UseInt));
+      }
+
+      DefUseMap[Definition] = std::move(Uses);
+    }
+
+    Result->DefUseChains[Variable] = std::move(DefUseMap);
+  }
+
+  return std::move(Result);
+}
+
+namespace {
+struct DefUseFormatInfo : FormatInfo {
+  DefUseFormatInfo()
+      : FormatInfo{
+            SummaryName("DefUse"),
+            serializeDefUse,
+            deserializeDefUse,
+        } {}
+};
+} // namespace
+
+static llvm::Registry<JSONFormat::FormatInfo>::Add<DefUseFormatInfo>
+    RegisterDefUse("DefUseAnalysis", "Format info for DefUse analysis data");
diff --git a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp
new file mode 100644
index 0000000000000..2262b9117e7d5
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp
@@ -0,0 +1,84 @@
+//===- SSAFSerializationFormatTest.cpp - Test SSAF JSON serialization ---===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This tool reads SSAF TUSummary JSON files and can:
+// - Validate the JSON format
+// - Print summary information
+// - Write back to JSON (for round-trip testing)
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/VirtualFileSystem.h"
+#include "llvm/Support/WithColor.h"
+
+using namespace clang::ssaf;
+using namespace llvm;
+
+static cl::opt<std::string>
+    InputFilename(cl::Positional, cl::desc("<input file>"), cl::Required);
+
+static cl::opt<std::string> OutputFilename("o", cl::desc("Output filename"),
+                                           cl::value_desc("filename"),
+                                           cl::init("-"));
+
+static cl::opt<bool> PrintSummary("print-summary",
+                                  cl::desc("Print summary information"),
+                                  cl::init(false));
+
+static cl::opt<bool> Quiet("q", cl::desc("Suppress diagnostic messages"),
+                           cl::init(false));
+
+static void reportError(StringRef Prefix, Error E) {
+  if (Quiet)
+    return;
+  WithColor::error(errs(), "ssaf-serialization-format-test")
+      << Prefix << ": " << toString(std::move(E)) << "\n";
+}
+
+int main(int argc, char **argv) {
+  InitLLVM X(argc, argv);
+  cl::ParseCommandLineOptions(argc, argv,
+                              "SSAF JSON format validator and dumper\n");
+
+  // Create JSONFormat instance
+  auto FS = vfs::getRealFileSystem();
+  JSONFormat Format(FS);
+
+  // Read the input file
+  auto SummaryOrErr = Format.readTUSummary(InputFilename);
+  if (!SummaryOrErr) {
+    reportError("failed to read TUSummary", SummaryOrErr.takeError());
+    return 1;
+  }
+
+  TUSummary &Summary = *SummaryOrErr;
+
+  // Print summary information if requested
+  if (PrintSummary) {
+    outs() << "TUSummary successfully read from: " << InputFilename << "\n";
+    // Could add more detailed summary info here
+  }
+
+  // Write output if specified
+  if (OutputFilename != "-") {
+    if (auto Err = Format.writeTUSummary(Summary, OutputFilename)) {
+      reportError("failed to write TUSummary", std::move(Err));
+      return 1;
+    }
+    if (!Quiet) {
+      outs() << "TUSummary written to: " << OutputFilename << "\n";
+    }
+  }
+
+  return 0;
+}

>From cf6dd82d2c785089894daf09d88f1fe85538f9a6 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 12:35:00 -0800
Subject: [PATCH 6/7] Remove JSONFormatTest since we are using lit tests

---
 .../Analysis/Scalable/CMakeLists.txt          |    1 -
 .../Analysis/Scalable/JSONFormatTest.cpp      | 1127 -----------------
 2 files changed, 1128 deletions(-)
 delete mode 100644 clang/unittests/Analysis/Scalable/JSONFormatTest.cpp

diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt b/clang/unittests/Analysis/Scalable/CMakeLists.txt
index 993ca3669228c..3c642dd4b2e67 100644
--- a/clang/unittests/Analysis/Scalable/CMakeLists.txt
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -4,7 +4,6 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
   EntityIdTest.cpp
   EntityIdTableTest.cpp
   EntityNameTest.cpp
-  JSONFormatTest.cpp
   Registries/FancyAnalysisData.cpp
   Registries/MockSerializationFormat.cpp
   Registries/MockSummaryExtractor1.cpp
diff --git a/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
deleted file mode 100644
index eb2ed77976bda..0000000000000
--- a/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
+++ /dev/null
@@ -1,1127 +0,0 @@
-//===- unittests/Analysis/Scalable/JSONFormatTest.cpp -------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
-#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
-#include "clang/Analysis/Scalable/Model/EntityId.h"
-#include "clang/Analysis/Scalable/Model/EntityIdTable.h"
-#include "clang/Analysis/Scalable/Model/EntityName.h"
-#include "clang/Analysis/Scalable/Model/SummaryName.h"
-#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
-#include "llvm/Support/FileSystem.h"
-#include "llvm/Support/Path.h"
-#include "llvm/Testing/Support/Error.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include <fstream>
-
-using namespace clang::ssaf;
-using llvm::Failed;
-using llvm::Succeeded;
-using ::testing::AllOf;
-using ::testing::HasSubstr;
-
-namespace {
-
-// Helper function to check that an error message contains all specified
-// substrings
-::testing::Matcher<std::string>
-ContainsAllSubstrings(std::initializer_list<const char *> substrings) {
-  std::vector<::testing::Matcher<std::string>> matchers;
-  for (const char *substr : substrings) {
-    matchers.push_back(HasSubstr(substr));
-  }
-  return ::testing::AllOfArray(matchers);
-}
-
-//===----------------------------------------------------------------------===//
-// Test Fixtures and Helpers
-//===----------------------------------------------------------------------===//
-
-// Helper class to manage temporary directories for testing
-class TempDir {
-  llvm::SmallString<128> Path;
-  std::error_code EC;
-
-public:
-  TempDir() {
-    EC = llvm::sys::fs::createUniqueDirectory("JSONFormatTest", Path);
-  }
-
-  ~TempDir() {
-    if (!EC && llvm::sys::fs::exists(Path)) {
-      llvm::sys::fs::remove_directories(Path);
-    }
-  }
-
-  llvm::StringRef path() const { return Path; }
-  bool isValid() const { return !EC; }
-};
-
-// Helper function to write a file with content
-void writeFile(llvm::StringRef Path, llvm::StringRef Content) {
-  std::ofstream File(Path.str());
-  File << Content.str();
-  File.close();
-}
-
-// Helper function to create a directory
-void createDir(llvm::StringRef Path) {
-  llvm::sys::fs::create_directories(Path);
-}
-
-// Base test fixture for JSONFormat tests
-class JSONFormatTestBase : public ::testing::Test {
-protected:
-  JSONFormat Format{llvm::vfs::getRealFileSystem()};
-};
-
-//===----------------------------------------------------------------------===//
-// TUSummary Read Tests - TUNamespace
-//===----------------------------------------------------------------------===//
-
-class JSONFormatReadTUNamespaceTest : public JSONFormatTestBase {};
-
-TEST_F(JSONFormatReadTUNamespaceTest, NonexistentDirectory) {
-  auto Result = Format.readTUSummary("/nonexistent/path");
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings(
-                  {"failed to read TUSummary from '/nonexistent/path'",
-                   "failed to read JSON from file", "tu_namespace.json",
-                   "failed to validate JSON file", "file does not exist"}));
-}
-
-TEST_F(JSONFormatReadTUNamespaceTest, MissingTUNamespaceFile) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg, ContainsAllSubstrings({"failed to read TUSummary from",
-                                               "failed to read JSON from file",
-                                               "tu_namespace.json",
-                                               "file does not exist"}));
-}
-
-TEST_F(JSONFormatReadTUNamespaceTest, InvalidJSONSyntax) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-
-  llvm::SmallString<128> TUNamespacePath(Dir.path());
-  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
-  writeFile(TUNamespacePath, "{ invalid json }");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg, ContainsAllSubstrings({"failed to read TUSummary from",
-                                               "failed to read JSON from file",
-                                               "tu_namespace.json"}));
-}
-
-TEST_F(JSONFormatReadTUNamespaceTest, NotJSONObject) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-
-  llvm::SmallString<128> TUNamespacePath(Dir.path());
-  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
-  writeFile(TUNamespacePath, "[]");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings({"failed to read TUSummary from",
-                                     "failed to read JSON object from file",
-                                     "tu_namespace.json"}));
-}
-
-TEST_F(JSONFormatReadTUNamespaceTest, MissingKindField) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-
-  llvm::SmallString<128> TUNamespacePath(Dir.path());
-  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
-  writeFile(TUNamespacePath, R"({"name": "test.cpp"})");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(
-      ErrorMsg,
-      ContainsAllSubstrings(
-          {"failed to read TUSummary from",
-           "failed to deserialize BuildNamespace from file",
-           "tu_namespace.json",
-           "missing required field 'kind' (expected BuildNamespaceKind)"}));
-}
-
-TEST_F(JSONFormatReadTUNamespaceTest, InvalidKindValue) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-
-  llvm::SmallString<128> TUNamespacePath(Dir.path());
-  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
-  writeFile(TUNamespacePath, R"({"kind": "InvalidKind", "name": "test.cpp"})");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings(
-                  {"failed to read TUSummary from",
-                   "failed to deserialize BuildNamespace from file",
-                   "tu_namespace.json",
-                   "invalid 'kind' BuildNamespaceKind value 'InvalidKind'"}));
-}
-
-TEST_F(JSONFormatReadTUNamespaceTest, MissingNameField) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-
-  llvm::SmallString<128> TUNamespacePath(Dir.path());
-  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
-  writeFile(TUNamespacePath, R"({"kind": "compilation_unit"})");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings(
-                  {"failed to read TUSummary from",
-                   "failed to deserialize BuildNamespace from file",
-                   "tu_namespace.json", "missing required field 'name'"}));
-}
-
-//===----------------------------------------------------------------------===//
-// TUSummary Read Tests - IdTable
-//===----------------------------------------------------------------------===//
-
-class JSONFormatReadIdTableTest : public JSONFormatTestBase {
-protected:
-  void SetUpValidTUNamespace(TempDir &Dir) {
-    llvm::SmallString<128> TUNamespacePath(Dir.path());
-    llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
-    writeFile(TUNamespacePath,
-              R"({"kind": "compilation_unit", "name": "test.cpp"})");
-  }
-};
-
-TEST_F(JSONFormatReadIdTableTest, MissingIdTableFile) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespace(Dir);
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings({"failed to read TUSummary from",
-                                     "failed to read JSON from file",
-                                     "id_table.json", "file does not exist"}));
-}
-
-TEST_F(JSONFormatReadIdTableTest, NotJSONArray) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespace(Dir);
-
-  llvm::SmallString<128> IdTablePath(Dir.path());
-  llvm::sys::path::append(IdTablePath, "id_table.json");
-  writeFile(IdTablePath, "{}");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings({"failed to read TUSummary from",
-                                     "failed to read JSON array from file",
-                                     "id_table.json"}));
-}
-
-TEST_F(JSONFormatReadIdTableTest, ElementNotObject) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespace(Dir);
-
-  llvm::SmallString<128> IdTablePath(Dir.path());
-  llvm::sys::path::append(IdTablePath, "id_table.json");
-  writeFile(IdTablePath, "[\"not an object\"]");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(
-      ErrorMsg,
-      ContainsAllSubstrings(
-          {"failed to read TUSummary from",
-           "failed to deserialize EntityIdTable from file", "id_table.json",
-           "element at index 0 is not a JSON object",
-           "expected EntityIdTable entry with 'id' and 'name' fields"}));
-}
-
-TEST_F(JSONFormatReadIdTableTest, EntryMissingName) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespace(Dir);
-
-  llvm::SmallString<128> IdTablePath(Dir.path());
-  llvm::sys::path::append(IdTablePath, "id_table.json");
-  writeFile(IdTablePath, R"([{"id": 0}])");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings(
-                  {"failed to read TUSummary from",
-                   "failed to deserialize EntityIdTable entry from file",
-                   "id_table.json",
-                   "missing or invalid field 'name' (expected EntityName JSON "
-                   "object)"}));
-}
-
-TEST_F(JSONFormatReadIdTableTest, EntryMissingId) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespace(Dir);
-
-  llvm::SmallString<128> IdTablePath(Dir.path());
-  llvm::sys::path::append(IdTablePath, "id_table.json");
-  writeFile(IdTablePath, R"([{
-    "name": {
-      "usr": "c:@F at foo",
-      "suffix": "",
-      "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
-    }
-  }])");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings(
-                  {"failed to read TUSummary from",
-                   "failed to deserialize EntityIdTable entry from file",
-                   "id_table.json",
-                   "missing required field 'id' (expected unsigned integer "
-                   "EntityId)"}));
-}
-
-TEST_F(JSONFormatReadIdTableTest, EntryIdNotInteger) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespace(Dir);
-
-  llvm::SmallString<128> IdTablePath(Dir.path());
-  llvm::sys::path::append(IdTablePath, "id_table.json");
-  writeFile(IdTablePath, R"([{
-    "id": "not a number",
-    "name": {
-      "usr": "c:@F at foo",
-      "suffix": "",
-      "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
-    }
-  }])");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(
-      ErrorMsg,
-      ContainsAllSubstrings(
-          {"failed to read TUSummary from",
-           "failed to deserialize EntityIdTable entry from file",
-           "id_table.json", "field 'id' is not a valid unsigned 64-bit integer",
-           "expected non-negative EntityId value"}));
-}
-
-TEST_F(JSONFormatReadIdTableTest, DuplicateEntityName) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespace(Dir);
-
-  llvm::SmallString<128> IdTablePath(Dir.path());
-  llvm::sys::path::append(IdTablePath, "id_table.json");
-  writeFile(IdTablePath, R"([
-    {
-      "id": 0,
-      "name": {
-        "usr": "c:@F at foo",
-        "suffix": "",
-        "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
-      }
-    },
-    {
-      "id": 1,
-      "name": {
-        "usr": "c:@F at foo",
-        "suffix": "",
-        "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
-      }
-    }
-  ])");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings(
-                  {"failed to read TUSummary from",
-                   "failed to deserialize EntityIdTable from file",
-                   "id_table.json", "duplicate EntityName found at index 1",
-                   "EntityId=0 already exists in table"}));
-}
-
-//===----------------------------------------------------------------------===//
-// TUSummary Read Tests - EntityName
-//===----------------------------------------------------------------------===//
-
-class JSONFormatReadEntityNameTest : public JSONFormatTestBase {
-protected:
-  void SetUpValidTUNamespaceAndPartialIdTable(TempDir &Dir,
-                                              llvm::StringRef EntityNameJson) {
-    llvm::SmallString<128> TUNamespacePath(Dir.path());
-    llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
-    writeFile(TUNamespacePath,
-              R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
-    llvm::SmallString<128> IdTablePath(Dir.path());
-    llvm::sys::path::append(IdTablePath, "id_table.json");
-    std::string JsonContent = "[{\"id\": 0, \"name\": ";
-    JsonContent += EntityNameJson.str();
-    JsonContent += "}]";
-    writeFile(IdTablePath, JsonContent);
-  }
-};
-
-TEST_F(JSONFormatReadEntityNameTest, MissingUSR) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
-    "suffix": "",
-    "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
-  })");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(
-      ErrorMsg,
-      ContainsAllSubstrings(
-          {"failed to read TUSummary from",
-           "failed to deserialize EntityName from file", "id_table.json",
-           "missing required field 'usr' (Unified Symbol Resolution string)"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, MissingSuffix) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
-    "usr": "c:@F at foo",
-    "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
-  })");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings(
-                  {"failed to read TUSummary from",
-                   "failed to deserialize EntityName from file",
-                   "id_table.json", "missing required field 'suffix'"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, MissingNamespace) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
-    "usr": "c:@F at foo",
-    "suffix": ""
-  })");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings(
-                  {"failed to read TUSummary from",
-                   "failed to deserialize EntityName from file",
-                   "id_table.json", "missing or invalid field 'namespace'",
-                   "expected JSON array of BuildNamespace objects"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, NamespaceNotArray) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
-    "usr": "c:@F at foo",
-    "suffix": "",
-    "namespace": "not an array"
-  })");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings(
-                  {"failed to read TUSummary from",
-                   "failed to deserialize EntityName from file",
-                   "id_table.json", "missing or invalid field 'namespace'",
-                   "expected JSON array of BuildNamespace objects"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, NamespaceElementNotObject) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
-    "usr": "c:@F at foo",
-    "suffix": "",
-    "namespace": ["not an object"]
-  })");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings(
-                  {"failed to read TUSummary from",
-                   "failed to deserialize NestedBuildNamespace from file",
-                   "id_table.json", "element at index 0 is not a JSON object",
-                   "expected BuildNamespace object"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, NamespaceMissingKind) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
-    "usr": "c:@F at foo",
-    "suffix": "",
-    "namespace": [{"name": "test.cpp"}]
-  })");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(
-      ErrorMsg,
-      ContainsAllSubstrings(
-          {"failed to read TUSummary from",
-           "failed to deserialize NestedBuildNamespace from file",
-           "id_table.json", "at index 0",
-           "failed to deserialize BuildNamespace from file",
-           "missing required field 'kind' (expected BuildNamespaceKind)"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, NamespaceInvalidKind) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
-    "usr": "c:@F at foo",
-    "suffix": "",
-    "namespace": [{"kind": "InvalidKind", "name": "test.cpp"}]
-  })");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings(
-                  {"failed to read TUSummary from",
-                   "failed to deserialize NestedBuildNamespace from file",
-                   "id_table.json", "at index 0",
-                   "failed to deserialize BuildNamespace from file",
-                   "invalid 'kind' BuildNamespaceKind value 'InvalidKind'"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, NamespaceMissingName) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
-    "usr": "c:@F at foo",
-    "suffix": "",
-    "namespace": [{"kind": "compilation_unit"}]
-  })");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings(
-                  {"failed to read TUSummary from",
-                   "failed to deserialize NestedBuildNamespace from file",
-                   "id_table.json", "at index 0",
-                   "failed to deserialize BuildNamespace from file",
-                   "missing required field 'name'"}));
-}
-
-//===----------------------------------------------------------------------===//
-// TUSummary Read Tests - Data Directory and Files
-//===----------------------------------------------------------------------===//
-
-class JSONFormatReadDataTest : public JSONFormatTestBase {
-protected:
-  void SetUpValidTUNamespaceAndIdTable(TempDir &Dir) {
-    llvm::SmallString<128> TUNamespacePath(Dir.path());
-    llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
-    writeFile(TUNamespacePath,
-              R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
-    llvm::SmallString<128> IdTablePath(Dir.path());
-    llvm::sys::path::append(IdTablePath, "id_table.json");
-    writeFile(IdTablePath, "[]");
-  }
-};
-
-TEST_F(JSONFormatReadDataTest, MissingDataDirectory) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceAndIdTable(Dir);
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings({"failed to read TUSummary from",
-                                     "data directory does not exist"}));
-}
-
-TEST_F(JSONFormatReadDataTest, DataPathIsFile) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceAndIdTable(Dir);
-
-  llvm::SmallString<128> DataPath(Dir.path());
-  llvm::sys::path::append(DataPath, "data");
-  writeFile(DataPath, "content");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings({"failed to read TUSummary from",
-                                     "data path is not a directory"}));
-}
-
-TEST_F(JSONFormatReadDataTest, NonJSONFileInDataDirectory) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceAndIdTable(Dir);
-
-  llvm::SmallString<128> DataPath(Dir.path());
-  llvm::sys::path::append(DataPath, "data");
-  createDir(DataPath);
-
-  llvm::SmallString<128> SummaryPath(DataPath);
-  llvm::sys::path::append(SummaryPath, "summary.txt");
-  writeFile(SummaryPath, "{}");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings(
-                  {"failed to read TUSummary from",
-                   "failed to read TUSummary data from file", "summary.txt",
-                   "failed to validate JSON file", "not a JSON file"}));
-}
-
-TEST_F(JSONFormatReadDataTest, FileNotJSONObject) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceAndIdTable(Dir);
-
-  llvm::SmallString<128> DataPath(Dir.path());
-  llvm::sys::path::append(DataPath, "data");
-  createDir(DataPath);
-
-  llvm::SmallString<128> SummaryPath(DataPath);
-  llvm::sys::path::append(SummaryPath, "summary.json");
-  writeFile(SummaryPath, "[]");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings({"failed to read TUSummary from",
-                                     "failed to read TUSummary data from file",
-                                     "summary.json",
-                                     "failed to read JSON object from file"}));
-}
-
-TEST_F(JSONFormatReadDataTest, MissingSummaryName) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceAndIdTable(Dir);
-
-  llvm::SmallString<128> DataPath(Dir.path());
-  llvm::sys::path::append(DataPath, "data");
-  createDir(DataPath);
-
-  llvm::SmallString<128> SummaryPath(DataPath);
-  llvm::sys::path::append(SummaryPath, "summary.json");
-  writeFile(SummaryPath, R"({"summary_data": []})");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings(
-                  {"failed to read TUSummary from",
-                   "failed to deserialize summary data from file",
-                   "summary.json", "missing required field 'summary_name'",
-                   "expected string identifier for the analysis summary"}));
-}
-
-TEST_F(JSONFormatReadDataTest, MissingSummaryData) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceAndIdTable(Dir);
-
-  llvm::SmallString<128> DataPath(Dir.path());
-  llvm::sys::path::append(DataPath, "data");
-  createDir(DataPath);
-
-  llvm::SmallString<128> SummaryPath(DataPath);
-  llvm::sys::path::append(SummaryPath, "summary.json");
-  writeFile(SummaryPath, R"({"summary_name": "test"})");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg, ContainsAllSubstrings(
-                            {"failed to read TUSummary from",
-                             "failed to deserialize summary data from file",
-                             "summary.json", "for summary 'test'",
-                             "missing or invalid field 'summary_data'",
-                             "expected JSON array of entity summaries"}));
-}
-
-TEST_F(JSONFormatReadDataTest, SummaryDataNotArray) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceAndIdTable(Dir);
-
-  llvm::SmallString<128> DataPath(Dir.path());
-  llvm::sys::path::append(DataPath, "data");
-  createDir(DataPath);
-
-  llvm::SmallString<128> SummaryPath(DataPath);
-  llvm::sys::path::append(SummaryPath, "summary.json");
-  writeFile(SummaryPath,
-            R"({"summary_name": "test", "summary_data": "not an array"})");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg, ContainsAllSubstrings(
-                            {"failed to read TUSummary from",
-                             "failed to deserialize summary data from file",
-                             "summary.json", "for summary 'test'",
-                             "missing or invalid field 'summary_data'",
-                             "expected JSON array of entity summaries"}));
-}
-
-TEST_F(JSONFormatReadDataTest, DuplicateSummaryName) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceAndIdTable(Dir);
-
-  llvm::SmallString<128> DataPath(Dir.path());
-  llvm::sys::path::append(DataPath, "data");
-  createDir(DataPath);
-
-  llvm::SmallString<128> SummaryPath1(DataPath);
-  llvm::sys::path::append(SummaryPath1, "summary1.json");
-  writeFile(SummaryPath1, R"({"summary_name": "test", "summary_data": []})");
-
-  llvm::SmallString<128> SummaryPath2(DataPath);
-  llvm::sys::path::append(SummaryPath2, "summary2.json");
-  writeFile(SummaryPath2, R"({"summary_name": "test", "summary_data": []})");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(
-      ErrorMsg,
-      ContainsAllSubstrings(
-          {"failed to read TUSummary from", "failed to read TUSummary data",
-           "duplicate SummaryName 'test' encountered in file"}));
-}
-
-//===----------------------------------------------------------------------===//
-// TUSummary Read Tests - Entity Data
-//===----------------------------------------------------------------------===//
-
-class JSONFormatReadEntityDataTest : public JSONFormatTestBase {
-protected:
-  void SetUpValidTUNamespaceIdTableAndDataDir(TempDir &Dir,
-                                              llvm::StringRef SummaryDataJson) {
-    llvm::SmallString<128> TUNamespacePath(Dir.path());
-    llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
-    writeFile(TUNamespacePath,
-              R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
-    llvm::SmallString<128> IdTablePath(Dir.path());
-    llvm::sys::path::append(IdTablePath, "id_table.json");
-    writeFile(IdTablePath, "[]");
-
-    llvm::SmallString<128> DataPath(Dir.path());
-    llvm::sys::path::append(DataPath, "data");
-    createDir(DataPath);
-
-    llvm::SmallString<128> SummaryPath(DataPath);
-    llvm::sys::path::append(SummaryPath, "summary.json");
-    std::string JsonContent = R"({"summary_name": "test", "summary_data": )";
-    JsonContent += SummaryDataJson.str();
-    JsonContent += "}";
-    writeFile(SummaryPath, JsonContent);
-  }
-};
-
-TEST_F(JSONFormatReadEntityDataTest, ElementNotObject) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceIdTableAndDataDir(Dir, "[\"not an object\"]");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(
-      ErrorMsg,
-      ContainsAllSubstrings(
-          {"failed to read TUSummary from",
-           "failed to deserialize entity data map from file", "summary.json",
-           "element at index 0 is not a JSON object",
-           "expected object with 'entity_id' and 'entity_summary' fields"}));
-}
-
-TEST_F(JSONFormatReadEntityDataTest, MissingEntityId) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceIdTableAndDataDir(Dir, R"([{"entity_summary": {}}])");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(
-      ErrorMsg,
-      ContainsAllSubstrings(
-          {"failed to read TUSummary from",
-           "failed to deserialize entity data map entry from file",
-           "summary.json", "at index 0", "missing required field 'entity_id'",
-           "expected unsigned integer EntityId"}));
-}
-
-TEST_F(JSONFormatReadEntityDataTest, EntityIdNotInteger) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceIdTableAndDataDir(
-      Dir, R"([{"entity_id": "not a number", "entity_summary": {}}])");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(
-      ErrorMsg,
-      ContainsAllSubstrings(
-          {"failed to read TUSummary from",
-           "failed to deserialize entity data map entry from file",
-           "summary.json", "at index 0",
-           "field 'entity_id' is not a valid unsigned 64-bit integer"}));
-}
-
-TEST_F(JSONFormatReadEntityDataTest, MissingEntitySummary) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceIdTableAndDataDir(Dir, R"([{"entity_id": 0}])");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings(
-                  {"failed to read TUSummary from",
-                   "failed to deserialize entity data map entry from file",
-                   "summary.json", "at index 0",
-                   "missing or invalid field 'entity_summary'",
-                   "expected EntitySummary JSON object"}));
-}
-
-TEST_F(JSONFormatReadEntityDataTest, EntitySummaryNotObject) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceIdTableAndDataDir(
-      Dir, R"([{"entity_id": 0, "entity_summary": "not an object"}])");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings(
-                  {"failed to read TUSummary from",
-                   "failed to deserialize entity data map entry from file",
-                   "summary.json", "at index 0",
-                   "missing or invalid field 'entity_summary'",
-                   "expected EntitySummary JSON object"}));
-}
-
-TEST_F(JSONFormatReadEntityDataTest,
-       EntitySummaryDeserializationNotImplemented) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-  SetUpValidTUNamespaceIdTableAndDataDir(
-      Dir, R"([{"entity_id": 0, "entity_summary": {}}])");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  ASSERT_FALSE(Result);
-  std::string ErrorMsg = llvm::toString(Result.takeError());
-  EXPECT_THAT(ErrorMsg,
-              ContainsAllSubstrings(
-                  {"failed to read TUSummary from",
-                   "failed to deserialize entity data map entry from file",
-                   "summary.json", "at index 0",
-                   "EntitySummary deserialization from file",
-                   "is not yet implemented"}));
-}
-
-// Note: DuplicateEntityId test cannot be implemented without EntitySummary
-// deserialization support, as the error occurs during EntitySummary parsing
-// before the duplicate check is reached.
-
-//===----------------------------------------------------------------------===//
-// TUSummary Write Tests
-//===----------------------------------------------------------------------===//
-
-class JSONFormatWriteTest : public JSONFormatTestBase {};
-
-TEST_F(JSONFormatWriteTest, Success) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-
-  BuildNamespace TUNamespace = BuildNamespace::makeCompilationUnit("test.cpp");
-  TUSummary Summary(TUNamespace);
-
-  auto Error = Format.writeTUSummary(Summary, Dir.path());
-  EXPECT_THAT_ERROR(std::move(Error), Succeeded());
-
-  // Verify files were created
-  llvm::SmallString<128> TUNamespacePath(Dir.path());
-  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
-  EXPECT_TRUE(llvm::sys::fs::exists(TUNamespacePath));
-
-  llvm::SmallString<128> IdTablePath(Dir.path());
-  llvm::sys::path::append(IdTablePath, "id_table.json");
-  EXPECT_TRUE(llvm::sys::fs::exists(IdTablePath));
-
-  llvm::SmallString<128> DataPath(Dir.path());
-  llvm::sys::path::append(DataPath, "data");
-  EXPECT_TRUE(llvm::sys::fs::exists(DataPath));
-  EXPECT_TRUE(llvm::sys::fs::is_directory(DataPath));
-}
-
-TEST_F(JSONFormatWriteTest, DataDirectoryExistsAsFile) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-
-  // Create 'data' as a file instead of directory
-  llvm::SmallString<128> DataPath(Dir.path());
-  llvm::sys::path::append(DataPath, "data");
-  writeFile(DataPath, "content");
-
-  BuildNamespace TUNamespace = BuildNamespace::makeCompilationUnit("test.cpp");
-  TUSummary Summary(TUNamespace);
-
-  auto Error = Format.writeTUSummary(Summary, Dir.path());
-  ASSERT_TRUE(!!Error);
-  std::string ErrorMsg = llvm::toString(std::move(Error));
-  EXPECT_THAT(ErrorMsg, ContainsAllSubstrings(
-                            {"failed to write TUSummary to",
-                             "data path exists but is not a directory"}));
-}
-
-//===----------------------------------------------------------------------===//
-// TUSummary Success Cases
-//===----------------------------------------------------------------------===//
-
-class JSONFormatSuccessTest : public JSONFormatTestBase {};
-
-TEST_F(JSONFormatSuccessTest, ReadWithEmptyIdTable) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-
-  llvm::SmallString<128> TUNamespacePath(Dir.path());
-  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
-  writeFile(TUNamespacePath,
-            R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
-  llvm::SmallString<128> IdTablePath(Dir.path());
-  llvm::sys::path::append(IdTablePath, "id_table.json");
-  writeFile(IdTablePath, "[]");
-
-  llvm::SmallString<128> DataPath(Dir.path());
-  llvm::sys::path::append(DataPath, "data");
-  createDir(DataPath);
-
-  auto Result = Format.readTUSummary(Dir.path());
-  EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
-}
-
-TEST_F(JSONFormatSuccessTest, ReadWithNonEmptyIdTable) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-
-  llvm::SmallString<128> TUNamespacePath(Dir.path());
-  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
-  writeFile(TUNamespacePath,
-            R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
-  llvm::SmallString<128> IdTablePath(Dir.path());
-  llvm::sys::path::append(IdTablePath, "id_table.json");
-  writeFile(IdTablePath, R"([
-    {
-      "id": 0,
-      "name": {
-        "usr": "c:@F at foo",
-        "suffix": "",
-        "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
-      }
-    },
-    {
-      "id": 1,
-      "name": {
-        "usr": "c:@F at bar",
-        "suffix": "1",
-        "namespace": [
-          {"kind": "compilation_unit", "name": "test.cpp"},
-          {"kind": "link_unit", "name": "libtest.so"}
-        ]
-      }
-    }
-  ])");
-
-  llvm::SmallString<128> DataPath(Dir.path());
-  llvm::sys::path::append(DataPath, "data");
-  createDir(DataPath);
-
-  auto Result = Format.readTUSummary(Dir.path());
-  EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
-}
-
-TEST_F(JSONFormatSuccessTest, ReadWithEmptyData) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-
-  llvm::SmallString<128> TUNamespacePath(Dir.path());
-  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
-  writeFile(TUNamespacePath,
-            R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
-  llvm::SmallString<128> IdTablePath(Dir.path());
-  llvm::sys::path::append(IdTablePath, "id_table.json");
-  writeFile(IdTablePath, "[]");
-
-  llvm::SmallString<128> DataPath(Dir.path());
-  llvm::sys::path::append(DataPath, "data");
-  createDir(DataPath);
-
-  // Add an empty summary data file
-  llvm::SmallString<128> SummaryPath(DataPath);
-  llvm::sys::path::append(SummaryPath, "summary.json");
-  writeFile(SummaryPath, R"({"summary_name": "test", "summary_data": []})");
-
-  auto Result = Format.readTUSummary(Dir.path());
-  EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
-}
-
-TEST_F(JSONFormatSuccessTest, ReadWithLinkUnitNamespace) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-
-  llvm::SmallString<128> TUNamespacePath(Dir.path());
-  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
-  writeFile(TUNamespacePath, R"({"kind": "link_unit", "name": "libtest.so"})");
-
-  llvm::SmallString<128> IdTablePath(Dir.path());
-  llvm::sys::path::append(IdTablePath, "id_table.json");
-  writeFile(IdTablePath, "[]");
-
-  llvm::SmallString<128> DataPath(Dir.path());
-  llvm::sys::path::append(DataPath, "data");
-  createDir(DataPath);
-
-  auto Result = Format.readTUSummary(Dir.path());
-  EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
-}
-
-//===----------------------------------------------------------------------===//
-// Round-Trip Tests
-//===----------------------------------------------------------------------===//
-
-class JSONFormatRoundTripTest : public JSONFormatTestBase {};
-
-TEST_F(JSONFormatRoundTripTest, EmptyIdTable) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-
-  BuildNamespace TUNamespace = BuildNamespace::makeCompilationUnit("test.cpp");
-  TUSummary Summary(TUNamespace);
-
-  auto WriteError = Format.writeTUSummary(Summary, Dir.path());
-  EXPECT_THAT_ERROR(std::move(WriteError), Succeeded());
-
-  auto ReadResult = Format.readTUSummary(Dir.path());
-  EXPECT_THAT_ERROR(ReadResult.takeError(), Succeeded());
-}
-
-TEST_F(JSONFormatRoundTripTest, NonEmptyIdTable) {
-  TempDir Dir;
-  ASSERT_TRUE(Dir.isValid());
-
-  // Manually create the files to test roundtrip
-  llvm::SmallString<128> TUNamespacePath(Dir.path());
-  llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
-  writeFile(TUNamespacePath,
-            R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
-  llvm::SmallString<128> IdTablePath(Dir.path());
-  llvm::sys::path::append(IdTablePath, "id_table.json");
-  writeFile(IdTablePath, R"([
-    {
-      "id": 0,
-      "name": {
-        "usr": "c:@F at foo",
-        "suffix": "",
-        "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
-      }
-    }
-  ])");
-
-  llvm::SmallString<128> DataPath(Dir.path());
-  llvm::sys::path::append(DataPath, "data");
-  createDir(DataPath);
-
-  auto ReadResult = Format.readTUSummary(Dir.path());
-  ASSERT_THAT_EXPECTED(ReadResult, Succeeded());
-
-  TempDir Dir2;
-  ASSERT_TRUE(Dir2.isValid());
-
-  auto WriteError = Format.writeTUSummary(*ReadResult, Dir2.path());
-  EXPECT_THAT_ERROR(std::move(WriteError), Succeeded());
-
-  // Verify the written files
-  llvm::SmallString<128> TUNamespacePath2(Dir2.path());
-  llvm::sys::path::append(TUNamespacePath2, "tu_namespace.json");
-  EXPECT_TRUE(llvm::sys::fs::exists(TUNamespacePath2));
-}
-
-} // namespace

>From 7296235a0a53762ba7a25fe88eb1a94ff5e7ab6d Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 12:53:02 -0800
Subject: [PATCH 7/7] Cleanup changes to SummaryName.h

---
 clang/include/clang/Analysis/Scalable/Model/SummaryName.h | 2 --
 1 file changed, 2 deletions(-)

diff --git a/clang/include/clang/Analysis/Scalable/Model/SummaryName.h b/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
index 08ed67fe17bed..785fe0eb10372 100644
--- a/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
+++ b/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
@@ -10,8 +10,6 @@
 #define LLVM_CLANG_ANALYSIS_SCALABLE_MODEL_SUMMARYNAME_H
 
 #include "llvm/ADT/StringRef.h"
-#include "llvm/Support/Path.h"
-#include <cassert>
 #include <string>
 
 namespace clang::ssaf {



More information about the cfe-commits mailing list