[clang] SSAF JSON Format (PR #180021)

Aviral Goel via cfe-commits cfe-commits at lists.llvm.org
Tue Feb 10 16:51:32 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 01/19] 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 02/19] 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 03/19] 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 04/19] 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 05/19] 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 06/19] 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 07/19] 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 {

>From 0e8b42cb7eafb368557e44f1cbaa16eaf915e2ca Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 13:07:55 -0800
Subject: [PATCH 08/19] Cleanup changes to SerializationFormat.h and
 JSONFormat.h

---
 .../clang/Analysis/Scalable/Serialization/JSONFormat.h      | 2 +-
 .../Analysis/Scalable/Serialization/SerializationFormat.h   | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
index ac809cd00e0cd..8d2e4ed6ea70b 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -48,7 +48,7 @@ class JSONFormat : public SerializationFormat {
   llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) override;
 
   llvm::Error writeTUSummary(const TUSummary &Summary,
-                             llvm::StringRef OutputDir) override;
+                             llvm::StringRef Path) override;
 
   using SerializerFn = llvm::function_ref<llvm::json::Object(
       const EntitySummary &, const EntityIdConverter &)>;
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index 54098939cdfe4..adebf5a5d6b5b 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -46,10 +46,10 @@ class SerializationFormat {
   getEntities(const EntityIdTable &EIT);
   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 BuildNamespace &getTUNamespace(TUSummary &S);
+  static const EntityIdTable &getIdTable(const TUSummary &S);
+  static EntityIdTable &getIdTable(TUSummary &S);
   static const decltype(TUSummary::Data) &getData(const TUSummary &S);
   static decltype(TUSummary::Data) &getData(TUSummary &S);
 
@@ -71,7 +71,7 @@ class SerializationFormat {
   virtual llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) = 0;
 
   virtual llvm::Error writeTUSummary(const TUSummary &Summary,
-                                     llvm::StringRef OutputDir) = 0;
+                                     llvm::StringRef Path) = 0;
 
 protected:
   llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS;

>From 6fa82c995cd32a807038e703b7be9b3b7d87bdce Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 13:11:34 -0800
Subject: [PATCH 09/19] Fix tests

---
 .../Serialization/error-callgraph-invalid-caller.test       | 2 +-
 .../Serialization/error-callgraph-missing-field.test        | 2 +-
 .../Scalable/Serialization/error-defuse-invalid-use.test    | 2 +-
 .../Scalable/Serialization/error-defuse-missing-field.test  | 2 +-
 .../Scalable/Serialization/error-duplicate-entity.test      | 2 +-
 .../Serialization/error-entity-data-element-not-object.test | 2 +-
 .../Serialization/error-entity-data-missing-entity-id.test  | 2 +-
 .../error-entity-data-missing-entity-summary.test           | 2 +-
 .../Scalable/Serialization/error-entity-id-not-uint64.test  | 2 +-
 .../Serialization/error-entity-name-missing-namespace.test  | 4 ++--
 .../Serialization/error-entity-name-missing-suffix.test     | 4 ++--
 .../Serialization/error-entity-name-missing-usr.test        | 4 ++--
 .../Serialization/error-entity-summary-no-format-info.test  | 2 +-
 .../Serialization/error-id-table-element-not-object.test    | 2 +-
 .../Serialization/error-id-table-entry-id-not-uint64.test   | 2 +-
 .../Serialization/error-id-table-entry-missing-id.test      | 2 +-
 .../Serialization/error-id-table-entry-missing-name.test    | 2 +-
 .../Analysis/Scalable/Serialization/error-invalid-kind.test | 2 +-
 .../Scalable/Serialization/error-invalid-syntax.test        | 2 +-
 .../Analysis/Scalable/Serialization/error-missing-kind.test | 2 +-
 .../Analysis/Scalable/Serialization/error-missing-name.test | 2 +-
 .../Serialization/error-namespace-element-not-object.test   | 6 +++---
 .../Scalable/Serialization/error-not-json-extension.test    | 4 ++--
 .../Analysis/Scalable/Serialization/error-not-object.test   | 2 +-
 24 files changed, 30 insertions(+), 30 deletions(-)

diff --git a/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test b/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
index c051f41af4fc4..e10b242087ef3 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
@@ -3,6 +3,6 @@
 // Test that CallGraph analysis validates caller field type
 
 // CHECK: failed to read TUSummary
+// CHECK: failed to deserialize summary data for summary 'CallGraph'
 // 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
index 0e5e2877d06d2..a95855a99d257 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
@@ -3,6 +3,6 @@
 // Test that CallGraph analysis requires 'call_graph' field
 
 // CHECK: failed to read TUSummary
+// CHECK: failed to deserialize summary data for summary 'CallGraph'
 // 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-defuse-invalid-use.test b/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
index 64675a201dd59..27b20b2c9fa62 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
@@ -3,6 +3,6 @@
 // Test that DefUse analysis validates use value types
 
 // CHECK: failed to read TUSummary
+// CHECK: failed to deserialize summary data for summary 'DefUse'
 // 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
index 73409a2d46503..64574f78a0315 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
@@ -3,6 +3,6 @@
 // Test that DefUse analysis requires 'def_use_chains' field
 
 // CHECK: failed to read TUSummary
+// CHECK: failed to deserialize summary data for summary 'DefUse'
 // 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
index 8e4d594cc75e6..542ea9fa23552 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
@@ -3,6 +3,6 @@
 // Test that duplicate EntityName in id_table is detected
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable from file
+// CHECK: failed to deserialize EntityIdTable
 // CHECK: duplicate EntityName found at index 1
 // CHECK: EntityId=0 already exists in table
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
index 72bf04adc267c..9b247c7ee39e5 100644
--- 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
@@ -3,6 +3,6 @@
 // Test that entity data elements must be objects
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to deserialize entity data map from file
+// CHECK: failed to deserialize entity data map
 // 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
index e2dd9a34b34d3..3d4f30805af5a 100644
--- 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
@@ -3,7 +3,7 @@
 // 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: failed to deserialize entity data map entry
 // 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
index 078bc4b068258..3229e02650360 100644
--- 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
@@ -3,7 +3,7 @@
 // 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: failed to deserialize entity data map entry
 // 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
index f5903b02d7eeb..eede8033fc575 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
@@ -3,6 +3,6 @@
 // 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: failed to deserialize entity data map entry
 // 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
index f9230bed9b25f..da2f56d600b58 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
@@ -3,7 +3,7 @@
 // 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: failed to deserialize EntityIdTable entry
+// CHECK: failed to deserialize EntityName
 // 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
index 70a75f8b63433..ae0975fd850d9 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
@@ -3,6 +3,6 @@
 // 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: failed to deserialize EntityIdTable entry
+// CHECK: failed to deserialize EntityName
 // 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
index 8531cfa1bd307..db48593c232d7 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
@@ -3,7 +3,7 @@
 // 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: failed to deserialize EntityIdTable entry
+// CHECK: failed to deserialize EntityName
 // 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
index 9a317f1270c4a..075eddaf271c8 100644
--- 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
@@ -3,5 +3,5 @@
 // Test that EntitySummary deserialization requires registered FormatInfo
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntitySummary from file
+// CHECK: failed to deserialize EntitySummary
 // 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
index 5e1359a3ce94c..eb8a70b5d8933 100644
--- 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
@@ -3,6 +3,6 @@
 // Test that id_table elements must be objects
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable from file
+// CHECK: failed to deserialize EntityIdTable
 // 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
index 4e2706d89f52d..7d95da6189017 100644
--- 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
@@ -3,6 +3,6 @@
 // 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: failed to deserialize EntityIdTable entry
 // 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
index 423cd54b3c1f6..846d0088ec974 100644
--- 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
@@ -3,6 +3,6 @@
 // Test that id_table entries must have 'id' field
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: failed to deserialize EntityIdTable entry
 // 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
index de56b93de491b..2404199b14c44 100644
--- 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
@@ -3,6 +3,6 @@
 // Test that id_table entries must have 'name' field
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: failed to deserialize EntityIdTable entry
 // CHECK: missing or invalid field 'name'
 // CHECK: expected EntityName JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test b/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
index cecc51e633f40..41b994f625d87 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
@@ -3,5 +3,5 @@
 // Test that invalid kind value is detected
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to deserialize BuildNamespace from file
+// CHECK: failed to deserialize BuildNamespace
 // 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
index 1724b13031c13..529ebd0f9a6e2 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
@@ -3,4 +3,4 @@
 // Test that invalid JSON syntax is detected
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to read JSON object from file
+// CHECK: failed to read JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test b/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
index d26a39d46c4b2..24c276a825c7a 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
@@ -3,6 +3,6 @@
 // Test that missing kind field in tu_namespace is detected
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to deserialize BuildNamespace from file
+// CHECK: failed to deserialize BuildNamespace
 // 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
index d88a8c46bc85f..2fb17423943dc 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-missing-name.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-name.test
@@ -3,5 +3,5 @@
 // Test that missing name field in tu_namespace is detected
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to deserialize BuildNamespace from file
+// CHECK: failed to deserialize BuildNamespace
 // CHECK: missing required field 'name'
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
index 4723d9d4434d9..55006eb230c21 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
@@ -3,8 +3,8 @@
 // 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: failed to deserialize EntityIdTable entry
+// CHECK: failed to deserialize EntityName
+// CHECK: failed to deserialize NestedBuildNamespace
 // CHECK: element at index 0 is not a JSON object
 // CHECK: expected BuildNamespace object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test b/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
index 57bcab0bf8212..1a56fc6e13dc6 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
@@ -1,8 +1,8 @@
-// RUN: not ssaf-serialization-format-test %S/not_json-extension.txt 2>&1 | FileCheck %s
+// RUN: not ssaf-serialization-format-test %S/Inputs/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 read JSON object
 // 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
index ec34a20c9f641..20409e7642b32 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-not-object.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-not-object.test
@@ -3,4 +3,4 @@
 // Test that JSON array (not object) is rejected
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to read JSON object from file
+// CHECK: failed to read JSON object

>From 566a25292baa78c334e1fd0a6694a79694d67206 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 14:02:47 -0800
Subject: [PATCH 10/19] Add check for directory

---
 .../Scalable/Serialization/JSONFormat.cpp     |   5 +
 .../Inputs/callgraph-invalid-caller.json      |  25 ++++
 .../Inputs/callgraph-missing-field.json       |  18 +++
 .../Inputs/data-element-not-object.json       |   8 ++
 .../Inputs/data-entry-missing-data.json       |  12 ++
 .../Inputs/data-missing-summary-name.json     |  12 ++
 .../Serialization/Inputs/data-not-array.json  |   8 ++
 .../Inputs/defuse-invalid-use.json            |  30 +++++
 .../Inputs/defuse-missing-field.json          |  18 +++
 .../Inputs/directory.json/.gitkeep            |   0
 .../Inputs/duplicate-entity.json              |  35 ++++++
 .../Inputs/duplicate-summary-name.json        |  17 +++
 .../entity-data-element-not-object.json       |  13 +++
 .../Inputs/entity-data-missing-entity-id.json |  17 +++
 .../entity-data-missing-entity-summary.json   |  17 +++
 .../Inputs/entity-id-not-uint64.json          |  18 +++
 .../Inputs/entity-name-missing-namespace.json |  16 +++
 .../Inputs/entity-name-missing-suffix.json    |  21 ++++
 .../Inputs/entity-name-missing-usr.json       |  21 ++++
 .../Inputs/entity-summary-no-format-info.json |  18 +++
 .../Inputs/id-table-element-not-object.json   |   8 ++
 .../Inputs/id-table-entry-id-not-uint64.json  |  22 ++++
 .../Inputs/id-table-entry-missing-id.json     |  21 ++++
 .../Inputs/id-table-entry-missing-name.json   |  10 ++
 .../Inputs/id-table-not-array.json            |   8 ++
 .../Serialization/Inputs/invalid-kind.json    |   8 ++
 .../Serialization/Inputs/invalid-syntax.json  |   1 +
 .../Serialization/Inputs/missing-data.json    |   7 ++
 .../Inputs/missing-id-table.json              |   7 ++
 .../Serialization/Inputs/missing-kind.json    |   7 ++
 .../Serialization/Inputs/missing-name.json    |   7 ++
 .../Inputs/missing-tu-namespace.json          |   4 +
 .../Inputs/namespace-element-not-object.json  |  17 +++
 .../Inputs/not-json-extension.txt             |   8 ++
 .../Serialization/Inputs/not-object.json      |   1 +
 .../Inputs/valid-callgraph-expected.json      |  74 ++++++++++++
 .../Serialization/Inputs/valid-callgraph.json |  66 +++++++++++
 .../Inputs/valid-defuse-expected.json         | 108 ++++++++++++++++++
 .../Serialization/Inputs/valid-defuse.json    | 100 ++++++++++++++++
 .../Inputs/valid-empty-expected.json          |   8 ++
 .../Serialization/Inputs/valid-empty.json     |   8 ++
 .../Inputs/valid-link-unit-expected.json      |   8 ++
 .../Serialization/Inputs/valid-link-unit.json |   8 ++
 .../valid-with-empty-data-entry-expected.json |  13 +++
 .../Inputs/valid-with-empty-data-entry.json   |  13 +++
 .../Inputs/valid-with-idtable-expected.json   |  39 +++++++
 .../Inputs/valid-with-idtable.json            |  39 +++++++
 .../Serialization/error-is-directory.test     |   7 ++
 48 files changed, 956 insertions(+)
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/data-element-not-object.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/data-entry-missing-data.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/data-missing-summary-name.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/data-not-array.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/directory.json/.gitkeep
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-entity.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-namespace.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-suffix.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-usr.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-element-not-object.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-id-not-uint64.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-id.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-name.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-not-array.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/invalid-kind.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/invalid-syntax.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-data.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-id-table.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-kind.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-name.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-tu-namespace.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/namespace-element-not-object.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/not-json-extension.txt
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/not-object.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty-expected.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit-expected.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable-expected.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-is-directory.test

diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index 1d3376ef2c9f2..21f239d9d1894 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -40,6 +40,11 @@ llvm::Error isJSONFile(llvm::StringRef Path) {
                                    "file does not exist: '%s'",
                                    Path.str().c_str());
 
+  if (llvm::sys::fs::is_directory(Path))
+    return llvm::createStringError(std::errc::is_a_directory,
+                                   "path is a directory, not a file: '%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());
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json
new file mode 100644
index 0000000000000..9cb082d00fc4c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json
@@ -0,0 +1,25 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": [
+    {
+      "summary_name": "CallGraph",
+      "data": [
+        {
+          "entity_id": 0,
+          "entity_summary": {
+            "call_graph": [
+              {
+                "caller": "not_a_number",
+                "callees": []
+              }
+            ]
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json
new file mode 100644
index 0000000000000..243842fdc1f57
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json
@@ -0,0 +1,18 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": [
+    {
+      "summary_name": "CallGraph",
+      "data": [
+        {
+          "entity_id": 0,
+          "entity_summary": {}
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/data-element-not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/data-element-not-object.json
new file mode 100644
index 0000000000000..ca953efa67e77
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/data-element-not-object.json
@@ -0,0 +1,8 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": ["not an object"]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/data-entry-missing-data.json b/clang/test/Analysis/Scalable/Serialization/Inputs/data-entry-missing-data.json
new file mode 100644
index 0000000000000..a5631d1a60559
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/data-entry-missing-data.json
@@ -0,0 +1,12 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": [
+    {
+      "summary_name": "test"
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/data-missing-summary-name.json b/clang/test/Analysis/Scalable/Serialization/Inputs/data-missing-summary-name.json
new file mode 100644
index 0000000000000..d4f48d9d785f2
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/data-missing-summary-name.json
@@ -0,0 +1,12 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": [
+    {
+      "data": []
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/data-not-array.json b/clang/test/Analysis/Scalable/Serialization/Inputs/data-not-array.json
new file mode 100644
index 0000000000000..70daf052792bd
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/data-not-array.json
@@ -0,0 +1,8 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": "not an array"
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json
new file mode 100644
index 0000000000000..4fb91b8382648
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json
@@ -0,0 +1,30 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": [
+    {
+      "summary_name": "DefUse",
+      "data": [
+        {
+          "entity_id": 0,
+          "entity_summary": {
+            "def_use_chains": [
+              {
+                "variable": 0,
+                "definitions": [
+                  {
+                    "definition": 10,
+                    "uses": ["invalid"]
+                  }
+                ]
+              }
+            ]
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json
new file mode 100644
index 0000000000000..b7dea48c27899
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json
@@ -0,0 +1,18 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": [
+    {
+      "summary_name": "DefUse",
+      "data": [
+        {
+          "entity_id": 0,
+          "entity_summary": {}
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/directory.json/.gitkeep b/clang/test/Analysis/Scalable/Serialization/Inputs/directory.json/.gitkeep
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-entity.json b/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-entity.json
new file mode 100644
index 0000000000000..dd8962c409cc8
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-entity.json
@@ -0,0 +1,35 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [
+    {
+      "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"
+          }
+        ]
+      }
+    }
+  ],
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json b/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json
new file mode 100644
index 0000000000000..5c39b179364ca
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json
@@ -0,0 +1,17 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": [
+    {
+      "summary_name": "test",
+      "data": []
+    },
+    {
+      "summary_name": "test",
+      "data": []
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json
new file mode 100644
index 0000000000000..98018650c368f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json
@@ -0,0 +1,13 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": [
+    {
+      "summary_name": "test",
+      "data": ["not an object"]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json
new file mode 100644
index 0000000000000..c80f4e7a4ce64
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json
@@ -0,0 +1,17 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": [
+    {
+      "summary_name": "test",
+      "data": [
+        {
+          "entity_summary": {}
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json
new file mode 100644
index 0000000000000..692369d342904
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json
@@ -0,0 +1,17 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": [
+    {
+      "summary_name": "test",
+      "data": [
+        {
+          "entity_id": 0
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json
new file mode 100644
index 0000000000000..33d9855fb1da4
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json
@@ -0,0 +1,18 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": [
+    {
+      "summary_name": "test",
+      "data": [
+        {
+          "entity_id": "not a number",
+          "entity_summary": {}
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-namespace.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-namespace.json
new file mode 100644
index 0000000000000..d11c1064bbeb9
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-namespace.json
@@ -0,0 +1,16 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "usr": "c:@F at foo",
+        "suffix": ""
+      }
+    }
+  ],
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-suffix.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-suffix.json
new file mode 100644
index 0000000000000..3598e8d95b2ea
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-suffix.json
@@ -0,0 +1,21 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "usr": "c:@F at foo",
+        "namespace": [
+          {
+            "kind": "compilation_unit",
+            "name": "test.cpp"
+          }
+        ]
+      }
+    }
+  ],
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-usr.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-usr.json
new file mode 100644
index 0000000000000..3fab36bcb4916
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-usr.json
@@ -0,0 +1,21 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "suffix": "",
+        "namespace": [
+          {
+            "kind": "compilation_unit",
+            "name": "test.cpp"
+          }
+        ]
+      }
+    }
+  ],
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json
new file mode 100644
index 0000000000000..c56402615956c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json
@@ -0,0 +1,18 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": [
+    {
+      "summary_name": "test",
+      "data": [
+        {
+          "entity_id": 0,
+          "entity_summary": {}
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-element-not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-element-not-object.json
new file mode 100644
index 0000000000000..ea604010a81e9
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-element-not-object.json
@@ -0,0 +1,8 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": ["not an object"],
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-id-not-uint64.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-id-not-uint64.json
new file mode 100644
index 0000000000000..5a736543c5dbc
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-id-not-uint64.json
@@ -0,0 +1,22 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [
+    {
+      "id": -1,
+      "name": {
+        "usr": "c:@F at foo",
+        "suffix": "",
+        "namespace": [
+          {
+            "kind": "compilation_unit",
+            "name": "test.cpp"
+          }
+        ]
+      }
+    }
+  ],
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-id.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-id.json
new file mode 100644
index 0000000000000..f911bfb7ab94e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-id.json
@@ -0,0 +1,21 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [
+    {
+      "name": {
+        "usr": "c:@F at foo",
+        "suffix": "",
+        "namespace": [
+          {
+            "kind": "compilation_unit",
+            "name": "test.cpp"
+          }
+        ]
+      }
+    }
+  ],
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-name.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-name.json
new file mode 100644
index 0000000000000..e5cd1aea3e1ae
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-name.json
@@ -0,0 +1,10 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [
+    {"id": 0}
+  ],
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-not-array.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-not-array.json
new file mode 100644
index 0000000000000..fb5624207bc2c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-not-array.json
@@ -0,0 +1,8 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": {},
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-kind.json b/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-kind.json
new file mode 100644
index 0000000000000..a39c20d3016a0
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-kind.json
@@ -0,0 +1,8 @@
+{
+  "tu_namespace": {
+    "kind": "InvalidKind",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-syntax.json b/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-syntax.json
new file mode 100644
index 0000000000000..b0e13f61aa06e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-syntax.json
@@ -0,0 +1 @@
+{ invalid json }
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-data.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-data.json
new file mode 100644
index 0000000000000..071de7214fd23
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-data.json
@@ -0,0 +1,7 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-id-table.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-id-table.json
new file mode 100644
index 0000000000000..126e8fba55b5f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-id-table.json
@@ -0,0 +1,7 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-kind.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-kind.json
new file mode 100644
index 0000000000000..8aa5c5fb115e1
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-kind.json
@@ -0,0 +1,7 @@
+{
+  "tu_namespace": {
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-name.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-name.json
new file mode 100644
index 0000000000000..5a7c2f2f80c08
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-name.json
@@ -0,0 +1,7 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit"
+  },
+  "id_table": [],
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-tu-namespace.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-tu-namespace.json
new file mode 100644
index 0000000000000..536918c99e182
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-tu-namespace.json
@@ -0,0 +1,4 @@
+{
+  "id_table": [],
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/namespace-element-not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/namespace-element-not-object.json
new file mode 100644
index 0000000000000..c6715f2e0e5cc
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/namespace-element-not-object.json
@@ -0,0 +1,17 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "usr": "c:@F at foo",
+        "suffix": "",
+        "namespace": ["not an object"]
+      }
+    }
+  ],
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/not-json-extension.txt b/clang/test/Analysis/Scalable/Serialization/Inputs/not-json-extension.txt
new file mode 100644
index 0000000000000..11966839e2292
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/not-json-extension.txt
@@ -0,0 +1,8 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/not-object.json
new file mode 100644
index 0000000000000..fe51488c7066f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/not-object.json
@@ -0,0 +1 @@
+[]
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json
new file mode 100644
index 0000000000000..53af0a652cca7
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json
@@ -0,0 +1,74 @@
+{
+  "data": [
+    {
+      "data": [
+        {
+          "entity_id": 0,
+          "entity_summary": {
+            "call_graph": [
+              {
+                "callees": [
+                  1,
+                  2
+                ],
+                "caller": 0
+              },
+              {
+                "callees": [
+                  2,
+                  3
+                ],
+                "caller": 1
+              },
+              {
+                "callees": [
+                  3
+                ],
+                "caller": 2
+              }
+            ]
+          }
+        }
+      ],
+      "summary_name": "CallGraph"
+    }
+  ],
+  "id_table": [
+    {
+      "id": 2,
+      "name": {
+        "namespace": [],
+        "suffix": "",
+        "usr": "c:@F at bar"
+      }
+    },
+    {
+      "id": 3,
+      "name": {
+        "namespace": [],
+        "suffix": "",
+        "usr": "c:@F at baz"
+      }
+    },
+    {
+      "id": 1,
+      "name": {
+        "namespace": [],
+        "suffix": "",
+        "usr": "c:@F at foo"
+      }
+    },
+    {
+      "id": 0,
+      "name": {
+        "namespace": [],
+        "suffix": "",
+        "usr": "c:@F at main"
+      }
+    }
+  ],
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "call_graph_test.cpp"
+  }
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json
new file mode 100644
index 0000000000000..1545092b614ff
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json
@@ -0,0 +1,66 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "call_graph_test.cpp"
+  },
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "usr": "c:@F at main",
+        "suffix": "",
+        "namespace": []
+      }
+    },
+    {
+      "id": 1,
+      "name": {
+        "usr": "c:@F at foo",
+        "suffix": "",
+        "namespace": []
+      }
+    },
+    {
+      "id": 2,
+      "name": {
+        "usr": "c:@F at bar",
+        "suffix": "",
+        "namespace": []
+      }
+    },
+    {
+      "id": 3,
+      "name": {
+        "usr": "c:@F at baz",
+        "suffix": "",
+        "namespace": []
+      }
+    }
+  ],
+  "data": [
+    {
+      "summary_name": "CallGraph",
+      "data": [
+        {
+          "entity_id": 0,
+          "entity_summary": {
+            "call_graph": [
+              {
+                "caller": 0,
+                "callees": [1, 2]
+              },
+              {
+                "caller": 1,
+                "callees": [2, 3]
+              },
+              {
+                "caller": 2,
+                "callees": [3]
+              }
+            ]
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json
new file mode 100644
index 0000000000000..915d4261b2bc1
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json
@@ -0,0 +1,108 @@
+{
+  "data": [
+    {
+      "data": [
+        {
+          "entity_id": 0,
+          "entity_summary": {
+            "def_use_chains": [
+              {
+                "definitions": [
+                  {
+                    "definition": 10,
+                    "uses": [
+                      12,
+                      13
+                    ]
+                  },
+                  {
+                    "definition": 14,
+                    "uses": [
+                      13
+                    ]
+                  }
+                ],
+                "variable": 0
+              },
+              {
+                "definitions": [
+                  {
+                    "definition": 11,
+                    "uses": [
+                      12,
+                      14
+                    ]
+                  }
+                ],
+                "variable": 1
+              }
+            ]
+          }
+        }
+      ],
+      "summary_name": "DefUse"
+    }
+  ],
+  "id_table": [
+    {
+      "id": 12,
+      "name": {
+        "namespace": [],
+        "suffix": "",
+        "usr": "c:@Stmt at line10"
+      }
+    },
+    {
+      "id": 13,
+      "name": {
+        "namespace": [],
+        "suffix": "",
+        "usr": "c:@Stmt at line12"
+      }
+    },
+    {
+      "id": 14,
+      "name": {
+        "namespace": [],
+        "suffix": "",
+        "usr": "c:@Stmt at line15"
+      }
+    },
+    {
+      "id": 10,
+      "name": {
+        "namespace": [],
+        "suffix": "",
+        "usr": "c:@Stmt at line5"
+      }
+    },
+    {
+      "id": 11,
+      "name": {
+        "namespace": [],
+        "suffix": "",
+        "usr": "c:@Stmt at line8"
+      }
+    },
+    {
+      "id": 0,
+      "name": {
+        "namespace": [],
+        "suffix": "",
+        "usr": "c:@V at x"
+      }
+    },
+    {
+      "id": 1,
+      "name": {
+        "namespace": [],
+        "suffix": "",
+        "usr": "c:@V at y"
+      }
+    }
+  ],
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "defuse_test.cpp"
+  }
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json
new file mode 100644
index 0000000000000..88581b4813a58
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json
@@ -0,0 +1,100 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "defuse_test.cpp"
+  },
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "usr": "c:@V at x",
+        "suffix": "",
+        "namespace": []
+      }
+    },
+    {
+      "id": 1,
+      "name": {
+        "usr": "c:@V at y",
+        "suffix": "",
+        "namespace": []
+      }
+    },
+    {
+      "id": 10,
+      "name": {
+        "usr": "c:@Stmt at line5",
+        "suffix": "",
+        "namespace": []
+      }
+    },
+    {
+      "id": 11,
+      "name": {
+        "usr": "c:@Stmt at line8",
+        "suffix": "",
+        "namespace": []
+      }
+    },
+    {
+      "id": 12,
+      "name": {
+        "usr": "c:@Stmt at line10",
+        "suffix": "",
+        "namespace": []
+      }
+    },
+    {
+      "id": 13,
+      "name": {
+        "usr": "c:@Stmt at line12",
+        "suffix": "",
+        "namespace": []
+      }
+    },
+    {
+      "id": 14,
+      "name": {
+        "usr": "c:@Stmt at line15",
+        "suffix": "",
+        "namespace": []
+      }
+    }
+  ],
+  "data": [
+    {
+      "summary_name": "DefUse",
+      "data": [
+        {
+          "entity_id": 0,
+          "entity_summary": {
+            "def_use_chains": [
+              {
+                "variable": 0,
+                "definitions": [
+                  {
+                    "definition": 10,
+                    "uses": [12, 13]
+                  },
+                  {
+                    "definition": 14,
+                    "uses": [13]
+                  }
+                ]
+              },
+              {
+                "variable": 1,
+                "definitions": [
+                  {
+                    "definition": 11,
+                    "uses": [12, 14]
+                  }
+                ]
+              }
+            ]
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty-expected.json
new file mode 100644
index 0000000000000..e7bd20a47921c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty-expected.json
@@ -0,0 +1,8 @@
+{
+  "data": [],
+  "id_table": [],
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  }
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty.json
new file mode 100644
index 0000000000000..11966839e2292
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty.json
@@ -0,0 +1,8 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit-expected.json
new file mode 100644
index 0000000000000..840b5289f010a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit-expected.json
@@ -0,0 +1,8 @@
+{
+  "data": [],
+  "id_table": [],
+  "tu_namespace": {
+    "kind": "link_unit",
+    "name": "libtest.so"
+  }
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit.json
new file mode 100644
index 0000000000000..98e1cce334fd7
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit.json
@@ -0,0 +1,8 @@
+{
+  "tu_namespace": {
+    "kind": "link_unit",
+    "name": "libtest.so"
+  },
+  "id_table": [],
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json
new file mode 100644
index 0000000000000..ddda2507cdfec
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json
@@ -0,0 +1,13 @@
+{
+  "data": [
+    {
+      "data": [],
+      "summary_name": "test"
+    }
+  ],
+  "id_table": [],
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  }
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json
new file mode 100644
index 0000000000000..0d62ee2a6d232
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json
@@ -0,0 +1,13 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": [
+    {
+      "summary_name": "test",
+      "data": []
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable-expected.json
new file mode 100644
index 0000000000000..14142a8a22c88
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable-expected.json
@@ -0,0 +1,39 @@
+{
+  "data": [],
+  "id_table": [
+    {
+      "id": 1,
+      "name": {
+        "namespace": [
+          {
+            "kind": "compilation_unit",
+            "name": "test.cpp"
+          },
+          {
+            "kind": "link_unit",
+            "name": "libtest.so"
+          }
+        ],
+        "suffix": "1",
+        "usr": "c:@F at bar"
+      }
+    },
+    {
+      "id": 0,
+      "name": {
+        "namespace": [
+          {
+            "kind": "compilation_unit",
+            "name": "test.cpp"
+          }
+        ],
+        "suffix": "",
+        "usr": "c:@F at foo"
+      }
+    }
+  ],
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  }
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable.json
new file mode 100644
index 0000000000000..76515b8ba750d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable.json
@@ -0,0 +1,39 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [
+    {
+      "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"
+          }
+        ]
+      }
+    }
+  ],
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/error-is-directory.test b/clang/test/Analysis/Scalable/Serialization/error-is-directory.test
new file mode 100644
index 0000000000000..e30bdd137af89
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-is-directory.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/directory.json 2>&1 | FileCheck %s
+
+// Test that directories with .json extension are rejected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to validate JSON file
+// CHECK: path is a directory, not a file

>From 6cc40551b27012582f5a7508f62057c35712e290 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 14:13:40 -0800
Subject: [PATCH 11/19] More tests

---
 .../Serialization/Inputs/no-read-permission.json   |  8 ++++++++
 .../Serialization/error-broken-symlink.test        | 14 ++++++++++++++
 .../Serialization/error-permission-denied.test     | 14 ++++++++++++++
 3 files changed, 36 insertions(+)
 create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/no-read-permission.json
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-broken-symlink.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-permission-denied.test

diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/no-read-permission.json b/clang/test/Analysis/Scalable/Serialization/Inputs/no-read-permission.json
new file mode 100644
index 0000000000000..11966839e2292
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/no-read-permission.json
@@ -0,0 +1,8 @@
+{
+  "tu_namespace": {
+    "kind": "compilation_unit",
+    "name": "test.cpp"
+  },
+  "id_table": [],
+  "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/error-broken-symlink.test b/clang/test/Analysis/Scalable/Serialization/error-broken-symlink.test
new file mode 100644
index 0000000000000..d1e61603c06cc
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-broken-symlink.test
@@ -0,0 +1,14 @@
+## Test that broken symlinks with .json extension are rejected.
+## Unsupported on Windows as symlinks behave differently there.
+
+# UNSUPPORTED: system-windows
+
+# RUN: ln -sf non-existent-file.json %t-broken-symlink.json
+# RUN: not ssaf-serialization-format-test %t-broken-symlink.json 2>&1 | FileCheck %s
+# RUN: rm %t-broken-symlink.json
+
+## Test that broken symlinks are detected as non-existent files
+
+# CHECK: failed to read TUSummary
+# CHECK: failed to validate JSON file
+# CHECK: file does not exist
diff --git a/clang/test/Analysis/Scalable/Serialization/error-permission-denied.test b/clang/test/Analysis/Scalable/Serialization/error-permission-denied.test
new file mode 100644
index 0000000000000..b89f32d19620d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-permission-denied.test
@@ -0,0 +1,14 @@
+## Test that files without read permissions are handled correctly.
+## Unsupported on Windows as chmod doesn't work reliably there.
+
+# UNSUPPORTED: system-windows
+# REQUIRES: non-root-user
+
+# RUN: chmod 000 %S/Inputs/no-read-permission.json
+# RUN: not ssaf-serialization-format-test %S/Inputs/no-read-permission.json 2>&1 | FileCheck %s
+# RUN: chmod 644 %S/Inputs/no-read-permission.json
+
+## Test that permission denied error is reported when file cannot be read
+
+# CHECK: failed to read TUSummary
+# CHECK: failed to read file

>From b3cc14c04dcb85eba524b3a7bbf1f3506df8741f Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 17:18:08 -0800
Subject: [PATCH 12/19] Test more code paths

---
 .../Serialization/error-write-flush-failure.test | 13 +++++++++++++
 .../Serialization/error-write-readonly-dir.test  | 16 ++++++++++++++++
 2 files changed, 29 insertions(+)
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-write-flush-failure.test
 create mode 100644 clang/test/Analysis/Scalable/Serialization/error-write-readonly-dir.test

diff --git a/clang/test/Analysis/Scalable/Serialization/error-write-flush-failure.test b/clang/test/Analysis/Scalable/Serialization/error-write-flush-failure.test
new file mode 100644
index 0000000000000..9bbdc4e8a611e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-write-flush-failure.test
@@ -0,0 +1,13 @@
+## Test that write flush failures are handled correctly.
+## Uses /dev/full which always reports "disk full" errors on write.
+## This special device file successfully opens but fails on write/flush operations.
+
+# REQUIRES: system-linux
+
+# RUN: not ssaf-serialization-format-test %S/Inputs/valid-defuse.json -o /dev/full 2>&1 | FileCheck %s
+
+## Test that flush/write error is reported when device is full
+## This tests JSONFormat.cpp:97-99 (write/flush failure path)
+
+# CHECK: failed to write TUSummary
+# CHECK: write failed
diff --git a/clang/test/Analysis/Scalable/Serialization/error-write-readonly-dir.test b/clang/test/Analysis/Scalable/Serialization/error-write-readonly-dir.test
new file mode 100644
index 0000000000000..47087f0a1de12
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-write-readonly-dir.test
@@ -0,0 +1,16 @@
+## Test that write failures to read-only directories are handled correctly.
+## Unsupported on Windows as chmod doesn't work reliably there.
+
+# UNSUPPORTED: system-windows
+# REQUIRES: non-root-user
+
+# RUN: rm -rf %t-readonly && mkdir -p %t-readonly
+# RUN: chmod 444 %t-readonly
+# RUN: not ssaf-serialization-format-test %S/Inputs/valid-empty.json -o %t-readonly/output.json 2>&1 | FileCheck %s
+# RUN: chmod 755 %t-readonly && rm -rf %t-readonly
+
+## Test that write error is reported when output directory is read-only
+## This tests JSONFormat.cpp:89-92 (file open failure path)
+
+# CHECK: failed to write TUSummary
+# CHECK: failed to open

>From 6d468d6e4f93a72068c0d5fcc359d03e8e73bb26 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Sun, 8 Feb 2026 09:25:21 -0800
Subject: [PATCH 13/19] Fix JSONFormat and update tests

---
 .../Scalable/Serialization/JSONFormat.h       |  34 +-
 .../Scalable/Serialization/JSONFormat.cpp     | 310 ++++++++++--------
 .../error-callgraph-invalid-caller.test       |   5 +-
 .../error-callgraph-missing-field.test        |   5 +-
 .../error-data-element-not-object.test        |   4 +-
 .../error-data-entry-missing-data.test        |   5 +-
 .../error-data-missing-summary-name.test      |   2 +
 .../error-defuse-invalid-use.test             |   5 +-
 .../error-defuse-missing-field.test           |   5 +-
 .../error-duplicate-summary-name.test         |   3 +-
 .../error-entity-data-element-not-object.test |   4 +-
 .../error-entity-data-missing-entity-id.test  |   4 +-
 ...or-entity-data-missing-entity-summary.test |   4 +-
 .../error-entity-id-not-uint64.test           |   4 +-
 .../ExampleAnalyses.cpp                       |  45 ++-
 .../SSAFSerializationFormatTest.cpp           |   2 +-
 16 files changed, 269 insertions(+), 172 deletions(-)

diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
index 8d2e4ed6ea70b..5c8e95b35a87b 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -58,45 +58,50 @@ class JSONFormat : public SerializationFormat {
           const EntityIdConverter &)>;
 
   using FormatInfo = FormatInfoEntry<SerializerFn, DeserializerFn>;
-  std::map<SummaryName, FormatInfo> FormatInfos;
 
 private:
+  std::map<SummaryName, FormatInfo> FormatInfos;
+
   EntityId entityIdFromJSON(const uint64_t EntityIdIndex) const;
   uint64_t entityIdToJSON(EntityId EI) const;
 
   llvm::Expected<BuildNamespaceKind>
-  buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr);
+  buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr) const;
 
   llvm::Expected<BuildNamespace>
-  buildNamespaceFromJSON(const llvm::json::Object &BuildNamespaceObject);
+  buildNamespaceFromJSON(const llvm::json::Object &BuildNamespaceObject) const;
   llvm::json::Object buildNamespaceToJSON(const BuildNamespace &BN) const;
 
   llvm::Expected<NestedBuildNamespace> nestedBuildNamespaceFromJSON(
-      const llvm::json::Array &NestedBuildNamespaceArray);
+      const llvm::json::Array &NestedBuildNamespaceArray) const;
   llvm::json::Array
   nestedBuildNamespaceToJSON(const NestedBuildNamespace &NBN) const;
 
   llvm::Expected<EntityName>
-  entityNameFromJSON(const llvm::json::Object &EntityNameObject);
+  entityNameFromJSON(const llvm::json::Object &EntityNameObject) const;
   llvm::json::Object entityNameToJSON(const EntityName &EN) const;
 
   llvm::Expected<std::pair<EntityName, EntityId>> entityIdTableEntryFromJSON(
-      const llvm::json::Object &EntityIdTableEntryObject);
+      const llvm::json::Object &EntityIdTableEntryObject) const;
   llvm::Expected<EntityIdTable>
-  entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray);
+  entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray) const;
   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);
+                        EntityIdTable &IdTable) const;
   llvm::json::Object entitySummaryToJSON(const SummaryName &SN,
                                          const EntitySummary &ES) const;
 
+  llvm::Expected<std::pair<EntityId, std::unique_ptr<EntitySummary>>>
+  entityDataMapEntryFromJSON(const llvm::json::Object &EntityDataMapEntryObject,
+                             const SummaryName &SN,
+                             EntityIdTable &IdTable) const;
   llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
   entityDataMapFromJSON(const SummaryName &SN,
                         const llvm::json::Array &EntityDataArray,
-                        EntityIdTable &IdTable);
+                        EntityIdTable &IdTable) const;
   llvm::json::Array
   entityDataMapToJSON(const SummaryName &SN,
                       const std::map<EntityId, std::unique_ptr<EntitySummary>>
@@ -105,10 +110,19 @@ class JSONFormat : public SerializationFormat {
   llvm::Expected<std::pair<SummaryName,
                            std::map<EntityId, std::unique_ptr<EntitySummary>>>>
   summaryDataMapEntryFromJSON(const llvm::json::Object &SummaryDataObject,
-                              EntityIdTable &IdTable);
+                              EntityIdTable &IdTable) const;
   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>>>>
+  summaryDataMapFromJSON(const llvm::json::Array &SummaryDataArray,
+                         EntityIdTable &IdTable) const;
+  llvm::json::Array summaryDataMapToJSON(
+      const std::map<SummaryName,
+                     std::map<EntityId, std::unique_ptr<EntitySummary>>>
+          &SummaryDataMap) const;
 };
 
 } // namespace clang::ssaf
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index 21f239d9d1894..f374041f23827 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -28,8 +28,6 @@ llvm::Error wrapError(llvm::Error E, const char *Fmt, Args &&...Vals) {
                    std::forward<Args>(Vals)...);
 }
 
-} // namespace
-
 //----------------------------------------------------------------------------
 // JSON Reader and Writer
 //----------------------------------------------------------------------------
@@ -80,7 +78,7 @@ llvm::Expected<llvm::json::Object> readJSONObject(llvm::StringRef Path) {
                                    "failed to read JSON object from file '%s'",
                                    Path.str().c_str());
   }
-  return std::move(*Object);
+  return *Object;
 }
 
 llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
@@ -101,6 +99,18 @@ llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
   return llvm::Error::success();
 }
 
+llvm::StringRef buildNamespaceKindToJSON(BuildNamespaceKind BNK) {
+  return toString(BNK);
+}
+
+SummaryName summaryNameFromJSON(llvm::StringRef SummaryNameStr) {
+  return SummaryName(SummaryNameStr.str());
+}
+
+llvm::StringRef summaryNameToJSON(const SummaryName &SN) { return SN.str(); }
+
+} // namespace
+
 //----------------------------------------------------------------------------
 // JSONFormat Constructor
 //----------------------------------------------------------------------------
@@ -134,8 +144,8 @@ uint64_t JSONFormat::entityIdToJSON(EntityId EI) const {
 // BuildNamespaceKind
 //----------------------------------------------------------------------------
 
-llvm::Expected<BuildNamespaceKind>
-JSONFormat::buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr) {
+llvm::Expected<BuildNamespaceKind> JSONFormat::buildNamespaceKindFromJSON(
+    llvm::StringRef BuildNamespaceKindStr) const {
   auto OptBuildNamespaceKind = parseBuildNamespaceKind(BuildNamespaceKindStr);
   if (!OptBuildNamespaceKind) {
     return llvm::createStringError(
@@ -147,16 +157,12 @@ JSONFormat::buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr) {
   return *OptBuildNamespaceKind;
 }
 
-llvm::StringRef buildNamespaceKindToJSON(BuildNamespaceKind BNK) {
-  return toString(BNK);
-}
-
 //----------------------------------------------------------------------------
 // BuildNamespace
 //----------------------------------------------------------------------------
 
 llvm::Expected<BuildNamespace> JSONFormat::buildNamespaceFromJSON(
-    const llvm::json::Object &BuildNamespaceObject) {
+    const llvm::json::Object &BuildNamespaceObject) const {
   auto OptBuildNamespaceKindStr = BuildNamespaceObject.getString("kind");
   if (!OptBuildNamespaceKindStr) {
     return llvm::createStringError(
@@ -193,7 +199,7 @@ JSONFormat::buildNamespaceToJSON(const BuildNamespace &BN) const {
 //----------------------------------------------------------------------------
 
 llvm::Expected<NestedBuildNamespace> JSONFormat::nestedBuildNamespaceFromJSON(
-    const llvm::json::Array &NestedBuildNamespaceArray) {
+    const llvm::json::Array &NestedBuildNamespaceArray) const {
   std::vector<BuildNamespace> Namespaces;
 
   size_t NamespaceCount = NestedBuildNamespaceArray.size();
@@ -242,8 +248,8 @@ JSONFormat::nestedBuildNamespaceToJSON(const NestedBuildNamespace &NBN) const {
 // EntityName
 //----------------------------------------------------------------------------
 
-llvm::Expected<EntityName>
-JSONFormat::entityNameFromJSON(const llvm::json::Object &EntityNameObject) {
+llvm::Expected<EntityName> JSONFormat::entityNameFromJSON(
+    const llvm::json::Object &EntityNameObject) const {
   const auto OptUSR = EntityNameObject.getString("usr");
   if (!OptUSR) {
     return llvm::createStringError(
@@ -285,23 +291,13 @@ llvm::json::Object JSONFormat::entityNameToJSON(const EntityName &EN) const {
   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) {
+    const llvm::json::Object &EntityIdTableEntryObject) const {
 
   const llvm::json::Object *OptEntityNameObject =
       EntityIdTableEntryObject.getObject("name");
@@ -341,14 +337,12 @@ JSONFormat::entityIdTableEntryFromJSON(
   return std::make_pair(std::move(*ExpectedEntityName), std::move(EI));
 }
 
-llvm::Expected<EntityIdTable>
-JSONFormat::entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray) {
-  const size_t EntityCount = EntityIdTableArray.size();
-
+llvm::Expected<EntityIdTable> JSONFormat::entityIdTableFromJSON(
+    const llvm::json::Array &EntityIdTableArray) const {
   EntityIdTable IdTable;
   std::map<EntityName, EntityId> &Entities = getEntities(IdTable);
 
-  for (size_t Index = 0; Index < EntityCount; ++Index) {
+  for (size_t Index = 0; Index < EntityIdTableArray.size(); ++Index) {
     const llvm::json::Value &EntityIdTableEntryValue =
         EntityIdTableArray[Index];
 
@@ -408,7 +402,7 @@ JSONFormat::entityIdTableToJSON(const EntityIdTable &IdTable) const {
 llvm::Expected<std::unique_ptr<EntitySummary>>
 JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
                                   const llvm::json::Object &EntitySummaryObject,
-                                  EntityIdTable &IdTable) {
+                                  EntityIdTable &IdTable) const {
   auto InfoIt = FormatInfos.find(SN);
   if (InfoIt == FormatInfos.end()) {
     return llvm::createStringError(
@@ -442,83 +436,94 @@ JSONFormat::entitySummaryToJSON(const SummaryName &SN,
 }
 
 //----------------------------------------------------------------------------
-// SummaryData
+// EntityDataMapEntry
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::pair<EntityId, std::unique_ptr<EntitySummary>>>
+JSONFormat::entityDataMapEntryFromJSON(
+    const llvm::json::Object &EntityDataMapEntryObject, const SummaryName &SN,
+    EntityIdTable &IdTable) const {
+
+  const llvm::json::Value *EntityIdIntValue =
+      EntityDataMapEntryObject.get("entity_id");
+  if (!EntityIdIntValue) {
+    return llvm::createStringError(std::errc::invalid_argument,
+                                   "failed to deserialize EntityDataMap entry: "
+                                   "missing required field 'entity_id' "
+                                   "(expected unsigned integer EntityId)");
+  }
+
+  const std::optional<uint64_t> OptEntityIdInt =
+      EntityIdIntValue->getAsUINT64();
+  if (!OptEntityIdInt) {
+    return llvm::createStringError(
+        std::errc::invalid_argument,
+        "failed to deserialize EntityDataMap entry: "
+        "field 'entity_id' is not a valid unsigned 64-bit integer "
+        "(expected non-negative EntityId value)");
+  }
+
+  EntityId EI = entityIdFromJSON(*OptEntityIdInt);
+
+  const llvm::json::Object *OptEntitySummaryObject =
+      EntityDataMapEntryObject.getObject("entity_summary");
+  if (!OptEntitySummaryObject) {
+    return llvm::createStringError(std::errc::invalid_argument,
+                                   "failed to deserialize EntityDataMap entry: "
+                                   "missing or invalid field 'entity_summary' "
+                                   "(expected EntitySummary JSON object)");
+  }
+
+  auto ExpectedEntitySummary =
+      entitySummaryFromJSON(SN, *OptEntitySummaryObject, IdTable);
+  if (!ExpectedEntitySummary)
+    return wrapError(ExpectedEntitySummary.takeError(),
+                     "failed to deserialize EntityDataMap entry");
+
+  return std::make_pair(std::move(EI), std::move(*ExpectedEntitySummary));
+}
+
+//----------------------------------------------------------------------------
+// EntityDataMap
 //----------------------------------------------------------------------------
 
 llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
 JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
                                   const llvm::json::Array &EntityDataArray,
-                                  EntityIdTable &IdTable) {
+                                  EntityIdTable &IdTable) const {
   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: "
-          "element at index %zu is not a JSON object "
-          "(expected object with 'entity_id' and 'entity_summary' fields)",
-          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 "
-          "at index %zu: missing required field 'entity_id' "
-          "(expected unsigned integer EntityId)",
-          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 "
-          "at index %zu: field 'entity_id' is not a valid unsigned 64-bit "
-          "integer",
-          Index);
-    }
-
-    EntityId EI = entityIdFromJSON(*OptEntityIdInt);
+  for (size_t Index = 0; Index < EntityDataArray.size(); ++Index) {
+    const llvm::json::Value &EntityDataMapEntryValue = EntityDataArray[Index];
 
-    const llvm::json::Object *OptEntitySummaryObject =
-        OptEntityDataEntryObject->getObject("entity_summary");
-    if (!OptEntitySummaryObject) {
+    const llvm::json::Object *OptEntityDataMapEntryObject =
+        EntityDataMapEntryValue.getAsObject();
+    if (!OptEntityDataMapEntryObject) {
       return llvm::createStringError(
           std::errc::invalid_argument,
-          "failed to deserialize entity data map entry "
-          "at index %zu: missing or invalid field 'entity_summary' "
-          "(expected EntitySummary JSON object)",
+          "failed to deserialize EntityDataMap: "
+          "element at index %zu is not a JSON object "
+          "(expected EntityDataMap entry with 'entity_id' and 'entity_summary' "
+          "fields)",
           Index);
     }
 
-    auto ExpectedEntitySummary =
-        entitySummaryFromJSON(SN, *OptEntitySummaryObject, IdTable);
-
-    if (!ExpectedEntitySummary) {
-      return wrapError(
-          ExpectedEntitySummary.takeError(),
-          "failed to deserialize entity data map entry at index %zu", Index);
-    }
+    auto ExpectedEntityDataMapEntry =
+        entityDataMapEntryFromJSON(*OptEntityDataMapEntryObject, SN, IdTable);
+    if (!ExpectedEntityDataMapEntry)
+      return wrapError(ExpectedEntityDataMapEntry.takeError(),
+                       "failed to deserialize EntityDataMap at index %zu",
+                       Index);
 
-    auto [DataIt, DataInserted] = EntityDataMap.insert(
-        {std::move(EI), std::move(*ExpectedEntitySummary)});
+    auto [DataIt, DataInserted] =
+        EntityDataMap.insert(std::move(*ExpectedEntityDataMapEntry));
     if (!DataInserted) {
       return llvm::createStringError(
           std::errc::invalid_argument,
-          "failed to deserialize entity data map: "
+          "failed to deserialize EntityDataMap: "
           "duplicate EntityId (%zu) found at index %zu",
           getEntityIdIndex(DataIt->first), Index);
     }
-
-    ++Index;
   }
 
   return EntityDataMap;
@@ -539,18 +544,23 @@ llvm::json::Array JSONFormat::entityDataMapToJSON(
   return Result;
 }
 
+//----------------------------------------------------------------------------
+// SummaryDataMapEntry
+//----------------------------------------------------------------------------
+
 llvm::Expected<
     std::pair<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
 JSONFormat::summaryDataMapEntryFromJSON(
-    const llvm::json::Object &SummaryDataObject, EntityIdTable &IdTable) {
+    const llvm::json::Object &SummaryDataMapEntryObject,
+    EntityIdTable &IdTable) const {
 
   std::optional<llvm::StringRef> OptSummaryNameStr =
-      SummaryDataObject.getString("summary_name");
+      SummaryDataMapEntryObject.getString("summary_name");
 
   if (!OptSummaryNameStr) {
     return llvm::createStringError(
         std::errc::invalid_argument,
-        "failed to deserialize summary data: "
+        "failed to deserialize SummaryDataMap entry: "
         "missing required field 'summary_name' "
         "(expected string identifier for the analysis summary)");
   }
@@ -558,22 +568,22 @@ JSONFormat::summaryDataMapEntryFromJSON(
   SummaryName SN = summaryNameFromJSON(*OptSummaryNameStr);
 
   const llvm::json::Array *OptEntityDataArray =
-      SummaryDataObject.getArray("data");
+      SummaryDataMapEntryObject.getArray("data");
   if (!OptEntityDataArray) {
     return llvm::createStringError(
         std::errc::invalid_argument,
-        "failed to deserialize summary data for summary '%s': "
+        "failed to deserialize SummaryDataMap entry: "
         "missing or invalid field 'data' "
-        "(expected JSON array of entity summaries)",
-        SN.str().data());
+        "(expected JSON array of entity data entries)");
   }
 
   auto ExpectedEntityDataMap =
       entityDataMapFromJSON(SN, *OptEntityDataArray, IdTable);
   if (!ExpectedEntityDataMap)
-    return wrapError(ExpectedEntityDataMap.takeError(),
-                     "failed to deserialize summary data for summary '%s'",
-                     SN.str().data());
+    return wrapError(
+        ExpectedEntityDataMap.takeError(),
+        "failed to deserialize SummaryDataMap entry for summary '%s'",
+        SN.str().data());
 
   return std::make_pair(std::move(SN), std::move(*ExpectedEntityDataMap));
 }
@@ -587,6 +597,65 @@ llvm::json::Object JSONFormat::summaryDataMapEntryToJSON(
   return Result;
 }
 
+//----------------------------------------------------------------------------
+// SummaryDataMap
+//----------------------------------------------------------------------------
+
+llvm::Expected<
+    std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+JSONFormat::summaryDataMapFromJSON(const llvm::json::Array &SummaryDataArray,
+                                   EntityIdTable &IdTable) const {
+  std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>
+      SummaryDataMap;
+
+  for (size_t Index = 0; Index < SummaryDataArray.size(); ++Index) {
+    const llvm::json::Value &SummaryDataMapEntryValue = SummaryDataArray[Index];
+
+    const llvm::json::Object *OptSummaryDataMapEntryObject =
+        SummaryDataMapEntryValue.getAsObject();
+    if (!OptSummaryDataMapEntryObject) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "failed to deserialize SummaryDataMap: "
+          "element at index %zu is not a JSON object "
+          "(expected SummaryDataMap entry with 'summary_name' and 'data' "
+          "fields)",
+          Index);
+    }
+
+    auto ExpectedSummaryDataMapEntry =
+        summaryDataMapEntryFromJSON(*OptSummaryDataMapEntryObject, IdTable);
+    if (!ExpectedSummaryDataMapEntry)
+      return wrapError(ExpectedSummaryDataMapEntry.takeError(),
+                       "failed to deserialize SummaryDataMap at index %zu",
+                       Index);
+
+    auto [SummaryIt, SummaryInserted] =
+        SummaryDataMap.emplace(std::move(*ExpectedSummaryDataMapEntry));
+    if (!SummaryInserted) {
+      return llvm::createStringError(
+          std::errc::invalid_argument,
+          "failed to deserialize SummaryDataMap: "
+          "duplicate SummaryName '%s' found at index %zu",
+          SummaryIt->first.str().data(), Index);
+    }
+  }
+
+  return SummaryDataMap;
+}
+
+llvm::json::Array JSONFormat::summaryDataMapToJSON(
+    const std::map<SummaryName,
+                   std::map<EntityId, std::unique_ptr<EntitySummary>>>
+        &SummaryDataMap) const {
+  llvm::json::Array Result;
+  Result.reserve(SummaryDataMap.size());
+  for (const auto &[SummaryName, DataMap] : SummaryDataMap) {
+    Result.push_back(summaryDataMapEntryToJSON(SummaryName, DataMap));
+  }
+  return Result;
+}
+
 //----------------------------------------------------------------------------
 // TUSummary
 //----------------------------------------------------------------------------
@@ -649,38 +718,14 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
           Path.str().c_str());
     }
 
-    // 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());
-      }
-
-      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 ExpectedSummaryDataMap =
+        summaryDataMapFromJSON(*SummaryDataArray, getIdTable(Summary));
+    if (!ExpectedSummaryDataMap)
+      return wrapError(ExpectedSummaryDataMap.takeError(),
+                       "failed to read TUSummary from '%s'",
+                       Path.str().c_str());
 
-    getData(Summary) = std::move(Data);
+    getData(Summary) = std::move(*ExpectedSummaryDataMap);
   }
 
   return Summary;
@@ -694,14 +739,7 @@ llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
 
   RootObject["id_table"] = entityIdTableToJSON(getIdTable(S));
 
-  {
-    llvm::json::Array SummaryDataArray;
-    for (const auto &[SummaryName, DataMap] : getData(S)) {
-      SummaryDataArray.push_back(
-          summaryDataMapEntryToJSON(SummaryName, DataMap));
-    }
-    RootObject["data"] = std::move(SummaryDataArray);
-  }
+  RootObject["data"] = summaryDataMapToJSON(getData(S));
 
   // Write the JSON to file
   if (auto Error = writeJSON(std::move(RootObject), Path)) {
diff --git a/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test b/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
index e10b242087ef3..5ee78f60bcb2f 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
@@ -3,6 +3,7 @@
 // Test that CallGraph analysis validates caller field type
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to deserialize summary data for summary 'CallGraph'
-// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize SummaryDataMap entry for summary 'CallGraph'
+// CHECK: failed to deserialize EntityDataMap at index 0
+// CHECK: failed to deserialize EntityDataMap entry
 // 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
index a95855a99d257..2abbf1438654c 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
@@ -3,6 +3,7 @@
 // Test that CallGraph analysis requires 'call_graph' field
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to deserialize summary data for summary 'CallGraph'
-// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize SummaryDataMap entry for summary 'CallGraph'
+// CHECK: failed to deserialize EntityDataMap at index 0
+// CHECK: failed to deserialize EntityDataMap entry
 // 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
index 90dea14d683db..b44c7b85c3e49 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
@@ -3,4 +3,6 @@
 // Test that 'data' array elements must be objects
 
 // CHECK: failed to read TUSummary
-// CHECK: data array contains non-object element
+// CHECK: failed to deserialize SummaryDataMap
+// CHECK: element at index
+// CHECK: expected SummaryDataMap entry with 'summary_name' and 'data' fields
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
index 865db1e8709cd..25328c0eb603f 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
@@ -3,6 +3,7 @@
 // Test that data array entries must have 'data' field
 
 // CHECK: failed to read TUSummary
-// CHECK: for summary 'test'
+// CHECK: failed to deserialize SummaryDataMap at index
+// CHECK: failed to deserialize SummaryDataMap entry
 // CHECK: missing or invalid field 'data'
-// CHECK: expected JSON array of entity summaries
+// CHECK: expected JSON array of entity data entries
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
index a9bf765424b7e..03807ee6c44cb 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
@@ -3,5 +3,7 @@
 // Test that data array entries must have 'summary_name' field
 
 // CHECK: failed to read TUSummary
+// CHECK: failed to deserialize SummaryDataMap at index
+// CHECK: failed to deserialize SummaryDataMap entry
 // CHECK: missing required field 'summary_name'
 // CHECK: expected string identifier for the analysis summary
diff --git a/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test b/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
index 27b20b2c9fa62..9674ec9b4ada1 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
@@ -3,6 +3,7 @@
 // Test that DefUse analysis validates use value types
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to deserialize summary data for summary 'DefUse'
-// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize SummaryDataMap entry for summary 'DefUse'
+// CHECK: failed to deserialize EntityDataMap at index 0
+// CHECK: failed to deserialize EntityDataMap entry
 // 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
index 64574f78a0315..f03b4a1900307 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
@@ -3,6 +3,7 @@
 // Test that DefUse analysis requires 'def_use_chains' field
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to deserialize summary data for summary 'DefUse'
-// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize SummaryDataMap entry for summary 'DefUse'
+// CHECK: failed to deserialize EntityDataMap at index 0
+// CHECK: failed to deserialize EntityDataMap entry
 // CHECK: DefUse: missing or invalid 'def_use_chains' field
diff --git a/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test b/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
index 568db0a339620..b8f6e1335b02f 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
@@ -3,4 +3,5 @@
 // Test that duplicate summary names are detected
 
 // CHECK: failed to read TUSummary
-// CHECK: duplicate SummaryName 'test' found
+// CHECK: failed to deserialize SummaryDataMap
+// CHECK: duplicate SummaryName 'test' found at index
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
index 9b247c7ee39e5..43c0c9d164116 100644
--- 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
@@ -3,6 +3,6 @@
 // Test that entity data elements must be objects
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to deserialize entity data map
+// CHECK: failed to deserialize EntityDataMap
 // CHECK: element at index 0 is not a JSON object
-// CHECK: expected object with 'entity_id' and 'entity_summary' fields
+// CHECK: expected EntityDataMap entry 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
index 3d4f30805af5a..7c80a3e121279 100644
--- 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
@@ -3,7 +3,7 @@
 // Test that entity data entries must have 'entity_id' field
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to deserialize entity data map entry
-// CHECK: at index 0
+// CHECK: failed to deserialize EntityDataMap at index 0
+// CHECK: failed to deserialize EntityDataMap entry
 // 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
index 3229e02650360..57ed34e4eec26 100644
--- 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
@@ -3,7 +3,7 @@
 // Test that entity data entries must have 'entity_summary' field
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to deserialize entity data map entry
-// CHECK: at index 0
+// CHECK: failed to deserialize EntityDataMap at index 0
+// CHECK: failed to deserialize EntityDataMap entry
 // 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
index eede8033fc575..bacd042fa34e5 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
@@ -3,6 +3,6 @@
 // Test that entity_id field must be a valid uint64
 
 // CHECK: failed to read TUSummary
-// CHECK: failed to deserialize entity data map entry
-// CHECK: at index 0
+// CHECK: failed to deserialize EntityDataMap at index 0
+// CHECK: failed to deserialize EntityDataMap entry
 // CHECK: field 'entity_id' is not a valid unsigned 64-bit integer
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
index f8b4a535f00f3..fa93c8d64e9a3 100644
--- a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
+++ b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
@@ -25,10 +25,24 @@ using namespace clang::ssaf;
 //===----------------------------------------------------------------------===//
 
 namespace {
+/// Example analysis that tracks function call relationships.
+///
+/// This analysis builds a call graph where each function (represented by an
+/// EntityId) is mapped to the set of functions it directly calls. This is
+/// useful for understanding control flow and dependencies between functions.
+///
+/// Example structure:
+///   CallGraph[functionA] = {functionB, functionC}
+///   CallGraph[functionB] = {functionD}
+///
+/// This indicates that functionA calls functionB and functionC, and
+/// functionB calls functionD.
 struct CallGraphAnalysis : EntitySummary {
   CallGraphAnalysis() : EntitySummary(SummaryName("CallGraph")) {}
 
-  // Maps each function (EntityId) to the set of functions it calls
+  /// Maps each caller function (EntityId) to the set of functions it calls.
+  /// Key: Caller function EntityId
+  /// Value: Set of callee function EntityIds
   std::map<EntityId, std::set<EntityId>> CallGraph;
 };
 } // namespace
@@ -118,7 +132,8 @@ deserializeCallGraph(const llvm::json::Object &JSONObj, EntityIdTable &Table,
     Result->CallGraph[Caller] = std::move(Callees);
   }
 
-  return std::move(Result);
+  // Return by value to enable NRVO (Named Return Value Optimization)
+  return Result;
 }
 
 namespace {
@@ -142,11 +157,31 @@ static llvm::Registry<JSONFormat::FormatInfo>::Add<CallGraphFormatInfo>
 //===----------------------------------------------------------------------===//
 
 namespace {
+/// Example analysis that tracks definition-use chains for variables.
+///
+/// This analysis builds def-use chains showing how variable definitions flow
+/// to their use sites. The structure is a three-level nested map:
+///
+/// Level 1: Variable EntityId
+///   Level 2: Definition EntityId (where the variable is defined)
+///     Level 3: Set of Use EntityIds (where that definition is used)
+///
+/// Example structure:
+///   DefUseChains[varX][def1] = {use1, use2}
+///   DefUseChains[varX][def2] = {use3}
+///   DefUseChains[varY][def3] = {use4, use5, use6}
+///
+/// This indicates that:
+/// - Variable varX has two definitions (def1, def2)
+/// - def1 is used at use1 and use2
+/// - def2 is used at use3
+/// - Variable varY has one definition (def3) used at use4, use5, use6
 struct DefUseAnalysis : EntitySummary {
   DefUseAnalysis() : EntitySummary(SummaryName("DefUse")) {}
 
-  // For each variable (EntityId), maps definitions (EntityId) to their use
-  // sites (set of EntityId)
+  /// Maps variables to their definition-use chains.
+  /// Key: Variable EntityId
+  /// Value: Map from definition EntityId to set of use EntityIds
   std::map<EntityId, std::map<EntityId, std::set<EntityId>>> DefUseChains;
 };
 } // namespace
@@ -285,7 +320,7 @@ deserializeDefUse(const llvm::json::Object &JSONObj, EntityIdTable &Table,
     Result->DefUseChains[Variable] = std::move(DefUseMap);
   }
 
-  return std::move(Result);
+  return Result;
 }
 
 namespace {
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
index 2262b9117e7d5..2153b1dc4e52e 100644
--- a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp
+++ b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp
@@ -61,7 +61,7 @@ int main(int argc, char **argv) {
     return 1;
   }
 
-  TUSummary &Summary = *SummaryOrErr;
+  const TUSummary &Summary = *SummaryOrErr;
 
   // Print summary information if requested
   if (PrintSummary) {

>From 1dd02e0fdc53fe2f2c18e6fb24ef91d7286004a8 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Sun, 8 Feb 2026 12:29:34 -0800
Subject: [PATCH 14/19] Fix error messages and rename json field

---
 .../Scalable/Serialization/JSONFormat.h       |   2 +
 .../Scalable/Serialization/JSONFormat.cpp     | 623 ++++++++++++------
 .../Inputs/callgraph-invalid-caller.json      |   2 +-
 .../Inputs/callgraph-missing-field.json       |   2 +-
 .../Inputs/defuse-invalid-use.json            |   6 +-
 .../Inputs/defuse-missing-field.json          |   2 +-
 .../Inputs/duplicate-summary-name.json        |   4 +-
 .../entity-data-element-not-object.json       |   4 +-
 .../Inputs/entity-data-missing-entity-id.json |   2 +-
 .../entity-data-missing-entity-summary.json   |   2 +-
 .../Inputs/entity-id-not-uint64.json          |   2 +-
 .../Inputs/entity-summary-no-format-info.json |   2 +-
 .../Inputs/valid-callgraph-expected.json      |   2 +-
 .../Serialization/Inputs/valid-callgraph.json |  16 +-
 .../Inputs/valid-defuse-expected.json         |   2 +-
 .../Serialization/Inputs/valid-defuse.json    |  16 +-
 .../valid-with-empty-data-entry-expected.json |   2 +-
 .../Inputs/valid-with-empty-data-entry.json   |   2 +-
 .../error-data-element-not-object.test        |   2 +-
 .../error-data-entry-missing-data.test        |   4 +-
 20 files changed, 458 insertions(+), 241 deletions(-)

diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
index 5c8e95b35a87b..fb48e0122ade9 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -85,6 +85,8 @@ class JSONFormat : public SerializationFormat {
       const llvm::json::Object &EntityIdTableEntryObject) const;
   llvm::Expected<EntityIdTable>
   entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray) const;
+  llvm::json::Object entityIdTableEntryToJSON(const EntityName &EN,
+                                              EntityId EI) const;
   llvm::json::Array entityIdTableToJSON(const EntityIdTable &IdTable) const;
 
   llvm::Expected<std::unique_ptr<EntitySummary>>
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index f374041f23827..1a4e0047c709f 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -12,21 +12,207 @@
 using namespace clang::ssaf;
 
 namespace {
-// 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));
-}
+//----------------------------------------------------------------------------
+// ErrorBuilder - Fluent API for constructing contextual errors
+//----------------------------------------------------------------------------
 
-// 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)...);
-}
+class ErrorBuilder {
+private:
+  std::error_code Code;
+  std::vector<std::string> ContextStack;
+  llvm::Error WrappedError = llvm::Error::success();
+
+public:
+  explicit ErrorBuilder(std::errc EC) : Code(std::make_error_code(EC)) {}
+  explicit ErrorBuilder(std::error_code EC) : Code(EC) {}
+
+  // Add context message without formatting (for plain strings)
+  ErrorBuilder &context(const char *Msg) {
+    ContextStack.push_back(Msg);
+    return *this;
+  }
+
+  // Add context message with formatting
+  template <typename... Args>
+  ErrorBuilder &context(const char *Fmt, Args &&...ArgVals) {
+    char Buffer[2048];
+    snprintf(Buffer, sizeof(Buffer), Fmt, std::forward<Args>(ArgVals)...);
+    ContextStack.push_back(Buffer);
+    return *this;
+  }
+
+  // Wrap an existing error as the cause
+  ErrorBuilder &cause(llvm::Error E) {
+    // Consume the old WrappedError before assigning (LLVM Error requires
+    // checking)
+    llvm::consumeError(std::move(WrappedError));
+    WrappedError = std::move(E);
+    return *this;
+  }
+
+  // Build the final error
+  llvm::Error build() {
+    if (ContextStack.empty() && !WrappedError)
+      return llvm::Error::success();
+
+    if (ContextStack.empty())
+      return std::move(WrappedError);
+
+    std::string FinalMessage = llvm::join(ContextStack, ": ");
+    auto E = llvm::createStringError(Code, "%s", FinalMessage.c_str());
+
+    if (WrappedError)
+      return llvm::joinErrors(std::move(E), std::move(WrappedError));
+
+    return E;
+  }
+};
+
+//----------------------------------------------------------------------------
+// Error Message Constants
+//----------------------------------------------------------------------------
+
+namespace ErrorMessages {
+// File validation errors
+constexpr const char *FileNotFound = "file does not exist: '%s'";
+constexpr const char *IsDirectory = "path is a directory, not a file: '%s'";
+constexpr const char *NotJSONFile = "not a JSON file: '%s'";
+constexpr const char *FailedToValidateJSONFile =
+    "failed to validate JSON file '%s'";
+constexpr const char *FailedToReadFile = "failed to read file '%s'";
+constexpr const char *FailedToReadJSONObject =
+    "failed to read JSON object from file '%s'";
+constexpr const char *FailedToOpenFile = "failed to open '%s'";
+constexpr const char *WriteFailed = "write failed";
+
+// BuildNamespace errors
+constexpr const char *InvalidBuildNamespaceKind =
+    "invalid 'kind' BuildNamespaceKind value '%s'";
+constexpr const char *MissingBuildNamespaceKind =
+    "failed to deserialize BuildNamespace: "
+    "missing required field 'kind' (expected BuildNamespaceKind)";
+constexpr const char *MissingBuildNamespaceName =
+    "failed to deserialize BuildNamespace: "
+    "missing required field 'name'";
+constexpr const char *FailedToDeserializeBuildNamespace =
+    "failed to deserialize BuildNamespace";
+
+// NestedBuildNamespace errors
+constexpr const char *NestedBuildNamespaceElementNotObject =
+    "failed to deserialize NestedBuildNamespace: "
+    "element at index %zu is not a JSON object "
+    "(expected BuildNamespace object)";
+constexpr const char *FailedToDeserializeNestedBuildNamespace =
+    "failed to deserialize NestedBuildNamespace at index %zu";
+
+// EntityName errors
+constexpr const char *MissingEntityNameUSR =
+    "failed to deserialize EntityName: "
+    "missing required field 'usr' (Unified Symbol Resolution string)";
+constexpr const char *MissingEntityNameSuffix =
+    "failed to deserialize EntityName: "
+    "missing required field 'suffix'";
+constexpr const char *MissingEntityNameNamespace =
+    "failed to deserialize EntityName: "
+    "missing or invalid field 'namespace' "
+    "(expected JSON array of BuildNamespace objects)";
+constexpr const char *FailedToDeserializeEntityName =
+    "failed to deserialize EntityName";
+
+// EntityIdTable entry errors
+constexpr const char *MissingEntityIdTableEntryName =
+    "failed to deserialize EntityIdTable entry: "
+    "missing or invalid field 'name' (expected EntityName JSON object)";
+constexpr const char *MissingEntityIdTableEntryId =
+    "failed to deserialize EntityIdTable entry: "
+    "missing required field 'id' (expected unsigned integer EntityId)";
+constexpr const char *InvalidEntityIdTableEntryId =
+    "failed to deserialize EntityIdTable entry: "
+    "field 'id' is not a valid unsigned 64-bit integer "
+    "(expected non-negative EntityId value)";
+constexpr const char *FailedToDeserializeEntityIdTableEntry =
+    "failed to deserialize EntityIdTable entry";
+
+// EntityIdTable errors
+constexpr const char *EntityIdTableElementNotObject =
+    "failed to deserialize EntityIdTable: "
+    "element at index %zu is not a JSON object "
+    "(expected EntityIdTable entry with 'id' and 'name' fields)";
+constexpr const char *FailedToDeserializeEntityIdTable =
+    "failed to deserialize EntityIdTable at index %zu";
+constexpr const char *DuplicateEntityName =
+    "failed to deserialize EntityIdTable: "
+    "duplicate EntityName found at index %zu "
+    "(EntityId=%zu already exists in table)";
+
+// EntitySummary errors
+constexpr const char *NoFormatInfoForSummary =
+    "failed to deserialize EntitySummary: "
+    "no FormatInfo was registered for summary name: %s";
+
+// EntityDataMap entry errors
+constexpr const char *MissingEntityDataMapEntryEntityId =
+    "failed to deserialize EntityDataMap entry: "
+    "missing required field 'entity_id' (expected unsigned integer EntityId)";
+constexpr const char *InvalidEntityDataMapEntryEntityId =
+    "failed to deserialize EntityDataMap entry: "
+    "field 'entity_id' is not a valid unsigned 64-bit integer "
+    "(expected non-negative EntityId value)";
+constexpr const char *MissingEntityDataMapEntryEntitySummary =
+    "failed to deserialize EntityDataMap entry: "
+    "missing or invalid field 'entity_summary' "
+    "(expected EntitySummary JSON object)";
+constexpr const char *FailedToDeserializeEntityDataMapEntry =
+    "failed to deserialize EntityDataMap entry";
+
+// EntityDataMap errors
+constexpr const char *EntityDataMapElementNotObject =
+    "failed to deserialize EntityDataMap: "
+    "element at index %zu is not a JSON object "
+    "(expected EntityDataMap entry with 'entity_id' and 'entity_summary' "
+    "fields)";
+constexpr const char *FailedToDeserializeEntityDataMap =
+    "failed to deserialize EntityDataMap at index %zu";
+constexpr const char *DuplicateEntityId =
+    "failed to deserialize EntityDataMap: "
+    "duplicate EntityId (%zu) found at index %zu";
+
+// SummaryDataMap entry errors
+constexpr const char *MissingSummaryDataMapEntrySummaryName =
+    "failed to deserialize SummaryDataMap entry: "
+    "missing required field 'summary_name' "
+    "(expected string identifier for the analysis summary)";
+constexpr const char *MissingSummaryDataMapEntrySummaryData =
+    "failed to deserialize SummaryDataMap entry: "
+    "missing or invalid field 'summary_data' "
+    "(expected JSON array of entity data entries)";
+constexpr const char *FailedToDeserializeSummaryDataMapEntry =
+    "failed to deserialize SummaryDataMap entry for summary '%s'";
+
+// SummaryDataMap errors
+constexpr const char *SummaryDataMapElementNotObject =
+    "failed to deserialize SummaryDataMap: "
+    "element at index %zu is not a JSON object "
+    "(expected SummaryDataMap entry with 'summary_name' and 'summary_data' "
+    "fields)";
+constexpr const char *FailedToDeserializeSummaryDataMap =
+    "failed to deserialize SummaryDataMap at index %zu";
+constexpr const char *DuplicateSummaryName =
+    "failed to deserialize SummaryDataMap: "
+    "duplicate SummaryName '%s' found at index %zu";
+
+// TUSummary errors
+constexpr const char *ReadingTUSummaryFrom = "reading TUSummary from '%s'";
+constexpr const char *MissingTUNamespace =
+    "missing or invalid field 'tu_namespace' (expected JSON object)";
+constexpr const char *MissingIdTable =
+    "missing or invalid field 'id_table' (expected JSON array)";
+constexpr const char *MissingData =
+    "missing or invalid field 'data' (expected JSON array)";
+constexpr const char *WritingTUSummaryTo = "writing TUSummary to '%s'";
+} // namespace ErrorMessages
+
+} // namespace
 
 //----------------------------------------------------------------------------
 // JSON Reader and Writer
@@ -34,32 +220,35 @@ llvm::Error wrapError(llvm::Error E, const char *Fmt, Args &&...Vals) {
 
 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());
+    return ErrorBuilder(std::errc::no_such_file_or_directory)
+        .context(ErrorMessages::FileNotFound, Path.str().c_str())
+        .build();
 
   if (llvm::sys::fs::is_directory(Path))
-    return llvm::createStringError(std::errc::is_a_directory,
-                                   "path is a directory, not a file: '%s'",
-                                   Path.str().c_str());
+    return ErrorBuilder(std::errc::is_a_directory)
+        .context(ErrorMessages::IsDirectory, Path.str().c_str())
+        .build();
 
   if (!Path.ends_with_insensitive(".json"))
-    return llvm::createStringError(std::errc::invalid_argument,
-                                   "not a JSON file: '%s'", Path.str().c_str());
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::NotJSONFile, Path.str().c_str())
+        .build();
 
   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());
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::FailedToValidateJSONFile, Path.str().c_str())
+        .cause(std::move(Err))
+        .build();
 
   auto BufferOrError = llvm::MemoryBuffer::getFile(Path);
   if (!BufferOrError) {
-    return llvm::createStringError(BufferOrError.getError(),
-                                   "failed to read file '%s'",
-                                   Path.str().c_str());
+    return ErrorBuilder(BufferOrError.getError())
+        .context(ErrorMessages::FailedToReadFile, Path.str().c_str())
+        .build();
   }
 
   return llvm::json::parse(BufferOrError.get()->getBuffer());
@@ -68,15 +257,16 @@ llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
 llvm::Expected<llvm::json::Object> readJSONObject(llvm::StringRef Path) {
   auto ExpectedJSON = readJSON(Path);
   if (!ExpectedJSON)
-    return wrapError(ExpectedJSON.takeError(),
-                     "failed to read JSON object from file '%s'",
-                     Path.str().c_str());
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::FailedToReadJSONObject, Path.str().c_str())
+        .cause(ExpectedJSON.takeError())
+        .build();
 
   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 ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::FailedToReadJSONObject, Path.str().c_str())
+        .build();
   }
   return *Object;
 }
@@ -85,32 +275,23 @@ llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
   std::error_code EC;
   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());
+    return ErrorBuilder(EC)
+        .context(ErrorMessages::FailedToOpenFile, Path.str().c_str())
+        .build();
   }
 
   OutStream << llvm::formatv("{0:2}\n", Value);
   OutStream.flush();
 
   if (OutStream.has_error()) {
-    return llvm::createStringError(OutStream.error(), "write failed");
+    return ErrorBuilder(OutStream.error())
+        .context(ErrorMessages::WriteFailed)
+        .build();
   }
 
   return llvm::Error::success();
 }
 
-llvm::StringRef buildNamespaceKindToJSON(BuildNamespaceKind BNK) {
-  return toString(BNK);
-}
-
-SummaryName summaryNameFromJSON(llvm::StringRef SummaryNameStr) {
-  return SummaryName(SummaryNameStr.str());
-}
-
-llvm::StringRef summaryNameToJSON(const SummaryName &SN) { return SN.str(); }
-
-} // namespace
-
 //----------------------------------------------------------------------------
 // JSONFormat Constructor
 //----------------------------------------------------------------------------
@@ -128,6 +309,20 @@ JSONFormat::JSONFormat(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
   }
 }
 
+//----------------------------------------------------------------------------
+// SummaryName
+//----------------------------------------------------------------------------
+
+namespace {
+
+SummaryName summaryNameFromJSON(llvm::StringRef SummaryNameStr) {
+  return SummaryName(SummaryNameStr.str());
+}
+
+llvm::StringRef summaryNameToJSON(const SummaryName &SN) { return SN.str(); }
+
+} // namespace
+
 //----------------------------------------------------------------------------
 // EntityId
 //----------------------------------------------------------------------------
@@ -148,15 +343,23 @@ llvm::Expected<BuildNamespaceKind> JSONFormat::buildNamespaceKindFromJSON(
     llvm::StringRef BuildNamespaceKindStr) const {
   auto OptBuildNamespaceKind = parseBuildNamespaceKind(BuildNamespaceKindStr);
   if (!OptBuildNamespaceKind) {
-    return llvm::createStringError(
-        std::errc::invalid_argument,
-        "invalid 'kind' BuildNamespaceKind value '%s'",
-        BuildNamespaceKindStr.str().c_str());
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::InvalidBuildNamespaceKind,
+                 BuildNamespaceKindStr.str().c_str())
+        .build();
   }
 
   return *OptBuildNamespaceKind;
 }
 
+namespace {
+
+llvm::StringRef buildNamespaceKindToJSON(BuildNamespaceKind BNK) {
+  return toString(BNK);
+}
+
+} // namespace
+
 //----------------------------------------------------------------------------
 // BuildNamespace
 //----------------------------------------------------------------------------
@@ -165,22 +368,23 @@ llvm::Expected<BuildNamespace> JSONFormat::buildNamespaceFromJSON(
     const llvm::json::Object &BuildNamespaceObject) const {
   auto OptBuildNamespaceKindStr = BuildNamespaceObject.getString("kind");
   if (!OptBuildNamespaceKindStr) {
-    return llvm::createStringError(
-        std::errc::invalid_argument,
-        "failed to deserialize BuildNamespace: "
-        "missing required field 'kind' (expected BuildNamespaceKind)");
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::MissingBuildNamespaceKind)
+        .build();
   }
 
   auto ExpectedKind = buildNamespaceKindFromJSON(*OptBuildNamespaceKindStr);
   if (!ExpectedKind)
-    return wrapError(ExpectedKind.takeError(),
-                     "failed to deserialize BuildNamespace");
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::FailedToDeserializeBuildNamespace)
+        .cause(ExpectedKind.takeError())
+        .build();
 
   auto OptNameStr = BuildNamespaceObject.getString("name");
   if (!OptNameStr) {
-    return llvm::createStringError(std::errc::invalid_argument,
-                                   "failed to deserialize BuildNamespace: "
-                                   "missing required field 'name'");
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::MissingBuildNamespaceName)
+        .build();
   }
 
   return {BuildNamespace(*ExpectedKind, *OptNameStr)};
@@ -211,19 +415,18 @@ llvm::Expected<NestedBuildNamespace> JSONFormat::nestedBuildNamespaceFromJSON(
     const llvm::json::Object *BuildNamespaceObject =
         BuildNamespaceValue.getAsObject();
     if (!BuildNamespaceObject) {
-      return llvm::createStringError(
-          std::errc::invalid_argument,
-          "failed to deserialize NestedBuildNamespace: "
-          "element at index %zu is not a JSON object "
-          "(expected BuildNamespace object)",
-          Index);
+      return ErrorBuilder(std::errc::invalid_argument)
+          .context(ErrorMessages::NestedBuildNamespaceElementNotObject, Index)
+          .build();
     }
 
     auto ExpectedBuildNamespace = buildNamespaceFromJSON(*BuildNamespaceObject);
     if (!ExpectedBuildNamespace)
-      return wrapError(
-          ExpectedBuildNamespace.takeError(),
-          "failed to deserialize NestedBuildNamespace at index %zu", Index);
+      return ErrorBuilder(std::errc::invalid_argument)
+          .context(ErrorMessages::FailedToDeserializeNestedBuildNamespace,
+                   Index)
+          .cause(ExpectedBuildNamespace.takeError())
+          .build();
 
     Namespaces.push_back(std::move(*ExpectedBuildNamespace));
   }
@@ -252,33 +455,32 @@ llvm::Expected<EntityName> JSONFormat::entityNameFromJSON(
     const llvm::json::Object &EntityNameObject) const {
   const auto OptUSR = EntityNameObject.getString("usr");
   if (!OptUSR) {
-    return llvm::createStringError(
-        std::errc::invalid_argument,
-        "failed to deserialize EntityName: "
-        "missing required field 'usr' (Unified Symbol Resolution string)");
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::MissingEntityNameUSR)
+        .build();
   }
 
   const auto OptSuffix = EntityNameObject.getString("suffix");
   if (!OptSuffix) {
-    return llvm::createStringError(std::errc::invalid_argument,
-                                   "failed to deserialize EntityName: "
-                                   "missing required field 'suffix'");
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::MissingEntityNameSuffix)
+        .build();
   }
 
   const llvm::json::Array *OptNamespaceArray =
       EntityNameObject.getArray("namespace");
   if (!OptNamespaceArray) {
-    return llvm::createStringError(
-        std::errc::invalid_argument,
-        "failed to deserialize EntityName: "
-        "missing or invalid field 'namespace' "
-        "(expected JSON array of BuildNamespace objects)");
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::MissingEntityNameNamespace)
+        .build();
   }
 
   auto ExpectedNamespace = nestedBuildNamespaceFromJSON(*OptNamespaceArray);
   if (!ExpectedNamespace)
-    return wrapError(ExpectedNamespace.takeError(),
-                     "failed to deserialize EntityName");
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::FailedToDeserializeEntityName)
+        .cause(ExpectedNamespace.takeError())
+        .build();
 
   return EntityName{*OptUSR, *OptSuffix, std::move(*ExpectedNamespace)};
 }
@@ -292,7 +494,7 @@ llvm::json::Object JSONFormat::entityNameToJSON(const EntityName &EN) const {
 }
 
 //----------------------------------------------------------------------------
-// EntityIdTable
+// EntityIdTableEntry
 //----------------------------------------------------------------------------
 
 llvm::Expected<std::pair<EntityName, EntityId>>
@@ -302,34 +504,32 @@ JSONFormat::entityIdTableEntryFromJSON(
   const llvm::json::Object *OptEntityNameObject =
       EntityIdTableEntryObject.getObject("name");
   if (!OptEntityNameObject) {
-    return llvm::createStringError(
-        std::errc::invalid_argument,
-        "failed to deserialize EntityIdTable entry: "
-        "missing or invalid field 'name' (expected EntityName JSON object)");
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::MissingEntityIdTableEntryName)
+        .build();
   }
 
   auto ExpectedEntityName = entityNameFromJSON(*OptEntityNameObject);
   if (!ExpectedEntityName)
-    return wrapError(ExpectedEntityName.takeError(),
-                     "failed to deserialize EntityIdTable entry");
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::FailedToDeserializeEntityIdTableEntry)
+        .cause(ExpectedEntityName.takeError())
+        .build();
 
   const llvm::json::Value *EntityIdIntValue =
       EntityIdTableEntryObject.get("id");
   if (!EntityIdIntValue) {
-    return llvm::createStringError(
-        std::errc::invalid_argument,
-        "failed to deserialize EntityIdTable entry: "
-        "missing required field 'id' (expected unsigned integer EntityId)");
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::MissingEntityIdTableEntryId)
+        .build();
   }
 
   const std::optional<uint64_t> OptEntityIdInt =
       EntityIdIntValue->getAsUINT64();
   if (!OptEntityIdInt) {
-    return llvm::createStringError(
-        std::errc::invalid_argument,
-        "failed to deserialize EntityIdTable entry: "
-        "field 'id' is not a valid unsigned 64-bit integer "
-        "(expected non-negative EntityId value)");
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::InvalidEntityIdTableEntryId)
+        .build();
   }
 
   EntityId EI = entityIdFromJSON(*OptEntityIdInt);
@@ -337,6 +537,18 @@ JSONFormat::entityIdTableEntryFromJSON(
   return std::make_pair(std::move(*ExpectedEntityName), std::move(EI));
 }
 
+llvm::json::Object JSONFormat::entityIdTableEntryToJSON(const EntityName &EN,
+                                                        EntityId EI) const {
+  llvm::json::Object Entry;
+  Entry["id"] = entityIdToJSON(EI);
+  Entry["name"] = entityNameToJSON(EN);
+  return Entry;
+}
+
+//----------------------------------------------------------------------------
+// EntityIdTable
+//----------------------------------------------------------------------------
+
 llvm::Expected<EntityIdTable> JSONFormat::entityIdTableFromJSON(
     const llvm::json::Array &EntityIdTableArray) const {
   EntityIdTable IdTable;
@@ -350,29 +562,26 @@ llvm::Expected<EntityIdTable> JSONFormat::entityIdTableFromJSON(
         EntityIdTableEntryValue.getAsObject();
 
     if (!OptEntityIdTableEntryObject) {
-      return llvm::createStringError(
-          std::errc::invalid_argument,
-          "failed to deserialize EntityIdTable: "
-          "element at index %zu is not a JSON object "
-          "(expected EntityIdTable entry with 'id' and 'name' fields)",
-          Index);
+      return ErrorBuilder(std::errc::invalid_argument)
+          .context(ErrorMessages::EntityIdTableElementNotObject, Index)
+          .build();
     }
 
     auto ExpectedEntityIdTableEntry =
         entityIdTableEntryFromJSON(*OptEntityIdTableEntryObject);
     if (!ExpectedEntityIdTableEntry)
-      return wrapError(ExpectedEntityIdTableEntry.takeError(),
-                       "failed to deserialize EntityIdTable at index %zu",
-                       Index);
+      return ErrorBuilder(std::errc::invalid_argument)
+          .context(ErrorMessages::FailedToDeserializeEntityIdTable, Index)
+          .cause(ExpectedEntityIdTableEntry.takeError())
+          .build();
 
     auto [EntityIt, EntityInserted] =
         Entities.emplace(std::move(*ExpectedEntityIdTableEntry));
     if (!EntityInserted) {
-      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));
+      return ErrorBuilder(std::errc::invalid_argument)
+          .context(ErrorMessages::DuplicateEntityName, Index,
+                   getEntityIdIndex(EntityIt->second))
+          .build();
     }
   }
 
@@ -386,10 +595,8 @@ JSONFormat::entityIdTableToJSON(const EntityIdTable &IdTable) const {
   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));
+    EntityIdTableArray.push_back(
+        entityIdTableEntryToJSON(EntityName, EntityId));
   }
 
   return EntityIdTableArray;
@@ -405,11 +612,9 @@ JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
                                   EntityIdTable &IdTable) const {
   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());
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::NoFormatInfoForSummary, SN.str().data())
+        .build();
   }
   const auto &InfoEntry = InfoIt->second;
   assert(InfoEntry.ForSummary == SN);
@@ -447,20 +652,17 @@ JSONFormat::entityDataMapEntryFromJSON(
   const llvm::json::Value *EntityIdIntValue =
       EntityDataMapEntryObject.get("entity_id");
   if (!EntityIdIntValue) {
-    return llvm::createStringError(std::errc::invalid_argument,
-                                   "failed to deserialize EntityDataMap entry: "
-                                   "missing required field 'entity_id' "
-                                   "(expected unsigned integer EntityId)");
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::MissingEntityDataMapEntryEntityId)
+        .build();
   }
 
   const std::optional<uint64_t> OptEntityIdInt =
       EntityIdIntValue->getAsUINT64();
   if (!OptEntityIdInt) {
-    return llvm::createStringError(
-        std::errc::invalid_argument,
-        "failed to deserialize EntityDataMap entry: "
-        "field 'entity_id' is not a valid unsigned 64-bit integer "
-        "(expected non-negative EntityId value)");
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::InvalidEntityDataMapEntryEntityId)
+        .build();
   }
 
   EntityId EI = entityIdFromJSON(*OptEntityIdInt);
@@ -468,17 +670,18 @@ JSONFormat::entityDataMapEntryFromJSON(
   const llvm::json::Object *OptEntitySummaryObject =
       EntityDataMapEntryObject.getObject("entity_summary");
   if (!OptEntitySummaryObject) {
-    return llvm::createStringError(std::errc::invalid_argument,
-                                   "failed to deserialize EntityDataMap entry: "
-                                   "missing or invalid field 'entity_summary' "
-                                   "(expected EntitySummary JSON object)");
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::MissingEntityDataMapEntryEntitySummary)
+        .build();
   }
 
   auto ExpectedEntitySummary =
       entitySummaryFromJSON(SN, *OptEntitySummaryObject, IdTable);
   if (!ExpectedEntitySummary)
-    return wrapError(ExpectedEntitySummary.takeError(),
-                     "failed to deserialize EntityDataMap entry");
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::FailedToDeserializeEntityDataMapEntry)
+        .cause(ExpectedEntitySummary.takeError())
+        .build();
 
   return std::make_pair(std::move(EI), std::move(*ExpectedEntitySummary));
 }
@@ -499,30 +702,26 @@ JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
     const llvm::json::Object *OptEntityDataMapEntryObject =
         EntityDataMapEntryValue.getAsObject();
     if (!OptEntityDataMapEntryObject) {
-      return llvm::createStringError(
-          std::errc::invalid_argument,
-          "failed to deserialize EntityDataMap: "
-          "element at index %zu is not a JSON object "
-          "(expected EntityDataMap entry with 'entity_id' and 'entity_summary' "
-          "fields)",
-          Index);
+      return ErrorBuilder(std::errc::invalid_argument)
+          .context(ErrorMessages::EntityDataMapElementNotObject, Index)
+          .build();
     }
 
     auto ExpectedEntityDataMapEntry =
         entityDataMapEntryFromJSON(*OptEntityDataMapEntryObject, SN, IdTable);
     if (!ExpectedEntityDataMapEntry)
-      return wrapError(ExpectedEntityDataMapEntry.takeError(),
-                       "failed to deserialize EntityDataMap at index %zu",
-                       Index);
+      return ErrorBuilder(std::errc::invalid_argument)
+          .context(ErrorMessages::FailedToDeserializeEntityDataMap, Index)
+          .cause(ExpectedEntityDataMapEntry.takeError())
+          .build();
 
     auto [DataIt, DataInserted] =
         EntityDataMap.insert(std::move(*ExpectedEntityDataMapEntry));
     if (!DataInserted) {
-      return llvm::createStringError(
-          std::errc::invalid_argument,
-          "failed to deserialize EntityDataMap: "
-          "duplicate EntityId (%zu) found at index %zu",
-          getEntityIdIndex(DataIt->first), Index);
+      return ErrorBuilder(std::errc::invalid_argument)
+          .context(ErrorMessages::DuplicateEntityId,
+                   getEntityIdIndex(DataIt->first), Index)
+          .build();
     }
   }
 
@@ -558,32 +757,29 @@ JSONFormat::summaryDataMapEntryFromJSON(
       SummaryDataMapEntryObject.getString("summary_name");
 
   if (!OptSummaryNameStr) {
-    return llvm::createStringError(
-        std::errc::invalid_argument,
-        "failed to deserialize SummaryDataMap entry: "
-        "missing required field 'summary_name' "
-        "(expected string identifier for the analysis summary)");
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::MissingSummaryDataMapEntrySummaryName)
+        .build();
   }
 
   SummaryName SN = summaryNameFromJSON(*OptSummaryNameStr);
 
   const llvm::json::Array *OptEntityDataArray =
-      SummaryDataMapEntryObject.getArray("data");
+      SummaryDataMapEntryObject.getArray("summary_data");
   if (!OptEntityDataArray) {
-    return llvm::createStringError(
-        std::errc::invalid_argument,
-        "failed to deserialize SummaryDataMap entry: "
-        "missing or invalid field 'data' "
-        "(expected JSON array of entity data entries)");
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::MissingSummaryDataMapEntrySummaryData)
+        .build();
   }
 
   auto ExpectedEntityDataMap =
       entityDataMapFromJSON(SN, *OptEntityDataArray, IdTable);
   if (!ExpectedEntityDataMap)
-    return wrapError(
-        ExpectedEntityDataMap.takeError(),
-        "failed to deserialize SummaryDataMap entry for summary '%s'",
-        SN.str().data());
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::FailedToDeserializeSummaryDataMapEntry,
+                 SN.str().data())
+        .cause(ExpectedEntityDataMap.takeError())
+        .build();
 
   return std::make_pair(std::move(SN), std::move(*ExpectedEntityDataMap));
 }
@@ -593,7 +789,7 @@ 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["data"] = entityDataMapToJSON(SN, SD);
+  Result["summary_data"] = entityDataMapToJSON(SN, SD);
   return Result;
 }
 
@@ -614,30 +810,26 @@ JSONFormat::summaryDataMapFromJSON(const llvm::json::Array &SummaryDataArray,
     const llvm::json::Object *OptSummaryDataMapEntryObject =
         SummaryDataMapEntryValue.getAsObject();
     if (!OptSummaryDataMapEntryObject) {
-      return llvm::createStringError(
-          std::errc::invalid_argument,
-          "failed to deserialize SummaryDataMap: "
-          "element at index %zu is not a JSON object "
-          "(expected SummaryDataMap entry with 'summary_name' and 'data' "
-          "fields)",
-          Index);
+      return ErrorBuilder(std::errc::invalid_argument)
+          .context(ErrorMessages::SummaryDataMapElementNotObject, Index)
+          .build();
     }
 
     auto ExpectedSummaryDataMapEntry =
         summaryDataMapEntryFromJSON(*OptSummaryDataMapEntryObject, IdTable);
     if (!ExpectedSummaryDataMapEntry)
-      return wrapError(ExpectedSummaryDataMapEntry.takeError(),
-                       "failed to deserialize SummaryDataMap at index %zu",
-                       Index);
+      return ErrorBuilder(std::errc::invalid_argument)
+          .context(ErrorMessages::FailedToDeserializeSummaryDataMap, Index)
+          .cause(ExpectedSummaryDataMapEntry.takeError())
+          .build();
 
     auto [SummaryIt, SummaryInserted] =
         SummaryDataMap.emplace(std::move(*ExpectedSummaryDataMapEntry));
     if (!SummaryInserted) {
-      return llvm::createStringError(
-          std::errc::invalid_argument,
-          "failed to deserialize SummaryDataMap: "
-          "duplicate SummaryName '%s' found at index %zu",
-          SummaryIt->first.str().data(), Index);
+      return ErrorBuilder(std::errc::invalid_argument)
+          .context(ErrorMessages::DuplicateSummaryName,
+                   SummaryIt->first.str().data(), Index)
+          .build();
     }
   }
 
@@ -661,11 +853,12 @@ llvm::json::Array JSONFormat::summaryDataMapToJSON(
 //----------------------------------------------------------------------------
 
 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());
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
+        .cause(ExpectedRootObject.takeError())
+        .build();
 
   const llvm::json::Object &RootObject = *ExpectedRootObject;
 
@@ -673,17 +866,18 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
   const llvm::json::Object *TUNamespaceObject =
       RootObject.getObject("tu_namespace");
   if (!TUNamespaceObject) {
-    return llvm::createStringError(
-        std::errc::invalid_argument,
-        "failed to read TUSummary from '%s': "
-        "missing or invalid field 'tu_namespace' (expected JSON object)",
-        Path.str().c_str());
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
+        .context(ErrorMessages::MissingTUNamespace)
+        .build();
   }
 
   auto ExpectedTUNamespace = buildNamespaceFromJSON(*TUNamespaceObject);
   if (!ExpectedTUNamespace)
-    return wrapError(ExpectedTUNamespace.takeError(),
-                     "failed to read TUSummary from '%s'", Path.str().c_str());
+    return ErrorBuilder(std::errc::invalid_argument)
+        .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
+        .cause(ExpectedTUNamespace.takeError())
+        .build();
 
   TUSummary Summary(std::move(*ExpectedTUNamespace));
 
@@ -691,39 +885,39 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
   {
     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());
+      return ErrorBuilder(std::errc::invalid_argument)
+          .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
+          .context(ErrorMessages::MissingIdTable)
+          .build();
     }
 
     auto ExpectedIdTable = entityIdTableFromJSON(*IdTableArray);
     if (!ExpectedIdTable)
-      return wrapError(ExpectedIdTable.takeError(),
-                       "failed to read TUSummary from '%s'",
-                       Path.str().c_str());
+      return ErrorBuilder(std::errc::invalid_argument)
+          .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
+          .cause(ExpectedIdTable.takeError())
+          .build();
 
     getIdTable(Summary) = std::move(*ExpectedIdTable);
   }
 
-  // Parse data field
+  // Parse Data field
   {
     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());
+      return ErrorBuilder(std::errc::invalid_argument)
+          .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
+          .context(ErrorMessages::MissingData)
+          .build();
     }
 
     auto ExpectedSummaryDataMap =
         summaryDataMapFromJSON(*SummaryDataArray, getIdTable(Summary));
     if (!ExpectedSummaryDataMap)
-      return wrapError(ExpectedSummaryDataMap.takeError(),
-                       "failed to read TUSummary from '%s'",
-                       Path.str().c_str());
+      return ErrorBuilder(std::errc::invalid_argument)
+          .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
+          .cause(ExpectedSummaryDataMap.takeError())
+          .build();
 
     getData(Summary) = std::move(*ExpectedSummaryDataMap);
   }
@@ -741,10 +935,11 @@ llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
 
   RootObject["data"] = summaryDataMapToJSON(getData(S));
 
-  // 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 ErrorBuilder(std::errc::io_error)
+        .context(ErrorMessages::WritingTUSummaryTo, Path.str().c_str())
+        .cause(std::move(Error))
+        .build();
   }
 
   return llvm::Error::success();
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json
index 9cb082d00fc4c..ce1ab6423d847 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json
@@ -7,7 +7,7 @@
   "data": [
     {
       "summary_name": "CallGraph",
-      "data": [
+      "summary_data": [
         {
           "entity_id": 0,
           "entity_summary": {
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json
index 243842fdc1f57..bd23319d80cec 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json
@@ -7,7 +7,7 @@
   "data": [
     {
       "summary_name": "CallGraph",
-      "data": [
+      "summary_data": [
         {
           "entity_id": 0,
           "entity_summary": {}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json
index 4fb91b8382648..de3ee8d56682f 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json
@@ -7,7 +7,7 @@
   "data": [
     {
       "summary_name": "DefUse",
-      "data": [
+      "summary_data": [
         {
           "entity_id": 0,
           "entity_summary": {
@@ -17,7 +17,9 @@
                 "definitions": [
                   {
                     "definition": 10,
-                    "uses": ["invalid"]
+                    "uses": [
+                      "invalid"
+                    ]
                   }
                 ]
               }
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json
index b7dea48c27899..351825e77f74c 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json
@@ -7,7 +7,7 @@
   "data": [
     {
       "summary_name": "DefUse",
-      "data": [
+      "summary_data": [
         {
           "entity_id": 0,
           "entity_summary": {}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json b/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json
index 5c39b179364ca..d16d61501a361 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json
@@ -7,11 +7,11 @@
   "data": [
     {
       "summary_name": "test",
-      "data": []
+      "summary_data": []
     },
     {
       "summary_name": "test",
-      "data": []
+      "summary_data": []
     }
   ]
 }
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json
index 98018650c368f..d892ee698ee19 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json
@@ -7,7 +7,9 @@
   "data": [
     {
       "summary_name": "test",
-      "data": ["not an object"]
+      "summary_data": [
+        "not an object"
+      ]
     }
   ]
 }
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json
index c80f4e7a4ce64..3b53bcb3564e1 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json
@@ -7,7 +7,7 @@
   "data": [
     {
       "summary_name": "test",
-      "data": [
+      "summary_data": [
         {
           "entity_summary": {}
         }
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json
index 692369d342904..6d8a43a10c80c 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json
@@ -7,7 +7,7 @@
   "data": [
     {
       "summary_name": "test",
-      "data": [
+      "summary_data": [
         {
           "entity_id": 0
         }
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json
index 33d9855fb1da4..246a99c6537c0 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json
@@ -7,7 +7,7 @@
   "data": [
     {
       "summary_name": "test",
-      "data": [
+      "summary_data": [
         {
           "entity_id": "not a number",
           "entity_summary": {}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json
index c56402615956c..179f8e5d33c47 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json
@@ -7,7 +7,7 @@
   "data": [
     {
       "summary_name": "test",
-      "data": [
+      "summary_data": [
         {
           "entity_id": 0,
           "entity_summary": {}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json
index 53af0a652cca7..a9939b5467719 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json
@@ -1,7 +1,7 @@
 {
   "data": [
     {
-      "data": [
+      "summary_data": [
         {
           "entity_id": 0,
           "entity_summary": {
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json
index 1545092b614ff..831cd5000d1b2 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json
@@ -40,22 +40,30 @@
   "data": [
     {
       "summary_name": "CallGraph",
-      "data": [
+      "summary_data": [
         {
           "entity_id": 0,
           "entity_summary": {
             "call_graph": [
               {
                 "caller": 0,
-                "callees": [1, 2]
+                "callees": [
+                  1,
+                  2
+                ]
               },
               {
                 "caller": 1,
-                "callees": [2, 3]
+                "callees": [
+                  2,
+                  3
+                ]
               },
               {
                 "caller": 2,
-                "callees": [3]
+                "callees": [
+                  3
+                ]
               }
             ]
           }
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json
index 915d4261b2bc1..72ac6212c0266 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json
@@ -1,7 +1,7 @@
 {
   "data": [
     {
-      "data": [
+      "summary_data": [
         {
           "entity_id": 0,
           "entity_summary": {
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json
index 88581b4813a58..b541843bfd7c7 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json
@@ -64,7 +64,7 @@
   "data": [
     {
       "summary_name": "DefUse",
-      "data": [
+      "summary_data": [
         {
           "entity_id": 0,
           "entity_summary": {
@@ -74,11 +74,16 @@
                 "definitions": [
                   {
                     "definition": 10,
-                    "uses": [12, 13]
+                    "uses": [
+                      12,
+                      13
+                    ]
                   },
                   {
                     "definition": 14,
-                    "uses": [13]
+                    "uses": [
+                      13
+                    ]
                   }
                 ]
               },
@@ -87,7 +92,10 @@
                 "definitions": [
                   {
                     "definition": 11,
-                    "uses": [12, 14]
+                    "uses": [
+                      12,
+                      14
+                    ]
                   }
                 ]
               }
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json
index ddda2507cdfec..2e5e958748263 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json
@@ -1,7 +1,7 @@
 {
   "data": [
     {
-      "data": [],
+      "summary_data": [],
       "summary_name": "test"
     }
   ],
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json
index 0d62ee2a6d232..13a6cb34ae255 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json
@@ -7,7 +7,7 @@
   "data": [
     {
       "summary_name": "test",
-      "data": []
+      "summary_data": []
     }
   ]
 }
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
index b44c7b85c3e49..1c7adf5437f6e 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
@@ -5,4 +5,4 @@
 // CHECK: failed to read TUSummary
 // CHECK: failed to deserialize SummaryDataMap
 // CHECK: element at index
-// CHECK: expected SummaryDataMap entry with 'summary_name' and 'data' fields
+// CHECK: expected SummaryDataMap entry with 'summary_name' and 'summary_data' fields
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
index 25328c0eb603f..f7e25e81bc2b1 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
@@ -1,9 +1,9 @@
 // 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
+// Test that data array entries must have 'summary_data' field
 
 // CHECK: failed to read TUSummary
 // CHECK: failed to deserialize SummaryDataMap at index
 // CHECK: failed to deserialize SummaryDataMap entry
-// CHECK: missing or invalid field 'data'
+// CHECK: missing or invalid field 'summary_data'
 // CHECK: expected JSON array of entity data entries

>From c7118c1fb4a74a7db2a21be891a01cf67786ca79 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Sun, 8 Feb 2026 12:42:47 -0800
Subject: [PATCH 15/19] Use formatv

---
 .../Scalable/Serialization/JSONFormat.cpp     | 53 +++++++++----------
 1 file changed, 26 insertions(+), 27 deletions(-)

diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index 1a4e0047c709f..022f787f09182 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -35,9 +35,8 @@ class ErrorBuilder {
   // Add context message with formatting
   template <typename... Args>
   ErrorBuilder &context(const char *Fmt, Args &&...ArgVals) {
-    char Buffer[2048];
-    snprintf(Buffer, sizeof(Buffer), Fmt, std::forward<Args>(ArgVals)...);
-    ContextStack.push_back(Buffer);
+    ContextStack.push_back(
+        llvm::formatv(Fmt, std::forward<Args>(ArgVals)...).str());
     return *this;
   }
 
@@ -74,20 +73,20 @@ class ErrorBuilder {
 
 namespace ErrorMessages {
 // File validation errors
-constexpr const char *FileNotFound = "file does not exist: '%s'";
-constexpr const char *IsDirectory = "path is a directory, not a file: '%s'";
-constexpr const char *NotJSONFile = "not a JSON file: '%s'";
+constexpr const char *FileNotFound = "file does not exist: '{0}'";
+constexpr const char *IsDirectory = "path is a directory, not a file: '{0}'";
+constexpr const char *NotJSONFile = "not a JSON file: '{0}'";
 constexpr const char *FailedToValidateJSONFile =
-    "failed to validate JSON file '%s'";
-constexpr const char *FailedToReadFile = "failed to read file '%s'";
+    "failed to validate JSON file '{0}'";
+constexpr const char *FailedToReadFile = "failed to read file '{0}'";
 constexpr const char *FailedToReadJSONObject =
-    "failed to read JSON object from file '%s'";
-constexpr const char *FailedToOpenFile = "failed to open '%s'";
+    "failed to read JSON object from file '{0}'";
+constexpr const char *FailedToOpenFile = "failed to open '{0}'";
 constexpr const char *WriteFailed = "write failed";
 
 // BuildNamespace errors
 constexpr const char *InvalidBuildNamespaceKind =
-    "invalid 'kind' BuildNamespaceKind value '%s'";
+    "invalid 'kind' BuildNamespaceKind value '{0}'";
 constexpr const char *MissingBuildNamespaceKind =
     "failed to deserialize BuildNamespace: "
     "missing required field 'kind' (expected BuildNamespaceKind)";
@@ -100,10 +99,10 @@ constexpr const char *FailedToDeserializeBuildNamespace =
 // NestedBuildNamespace errors
 constexpr const char *NestedBuildNamespaceElementNotObject =
     "failed to deserialize NestedBuildNamespace: "
-    "element at index %zu is not a JSON object "
+    "element at index {0} is not a JSON object "
     "(expected BuildNamespace object)";
 constexpr const char *FailedToDeserializeNestedBuildNamespace =
-    "failed to deserialize NestedBuildNamespace at index %zu";
+    "failed to deserialize NestedBuildNamespace at index {0}";
 
 // EntityName errors
 constexpr const char *MissingEntityNameUSR =
@@ -136,19 +135,19 @@ constexpr const char *FailedToDeserializeEntityIdTableEntry =
 // EntityIdTable errors
 constexpr const char *EntityIdTableElementNotObject =
     "failed to deserialize EntityIdTable: "
-    "element at index %zu is not a JSON object "
+    "element at index {0} is not a JSON object "
     "(expected EntityIdTable entry with 'id' and 'name' fields)";
 constexpr const char *FailedToDeserializeEntityIdTable =
-    "failed to deserialize EntityIdTable at index %zu";
+    "failed to deserialize EntityIdTable at index {0}";
 constexpr const char *DuplicateEntityName =
     "failed to deserialize EntityIdTable: "
-    "duplicate EntityName found at index %zu "
-    "(EntityId=%zu already exists in table)";
+    "duplicate EntityName found at index {0} "
+    "(EntityId={1} already exists in table)";
 
 // EntitySummary errors
 constexpr const char *NoFormatInfoForSummary =
     "failed to deserialize EntitySummary: "
-    "no FormatInfo was registered for summary name: %s";
+    "no FormatInfo was registered for summary name: {0}";
 
 // EntityDataMap entry errors
 constexpr const char *MissingEntityDataMapEntryEntityId =
@@ -168,14 +167,14 @@ constexpr const char *FailedToDeserializeEntityDataMapEntry =
 // EntityDataMap errors
 constexpr const char *EntityDataMapElementNotObject =
     "failed to deserialize EntityDataMap: "
-    "element at index %zu is not a JSON object "
+    "element at index {0} is not a JSON object "
     "(expected EntityDataMap entry with 'entity_id' and 'entity_summary' "
     "fields)";
 constexpr const char *FailedToDeserializeEntityDataMap =
-    "failed to deserialize EntityDataMap at index %zu";
+    "failed to deserialize EntityDataMap at index {0}";
 constexpr const char *DuplicateEntityId =
     "failed to deserialize EntityDataMap: "
-    "duplicate EntityId (%zu) found at index %zu";
+    "duplicate EntityId ({0}) found at index {1}";
 
 // SummaryDataMap entry errors
 constexpr const char *MissingSummaryDataMapEntrySummaryName =
@@ -187,29 +186,29 @@ constexpr const char *MissingSummaryDataMapEntrySummaryData =
     "missing or invalid field 'summary_data' "
     "(expected JSON array of entity data entries)";
 constexpr const char *FailedToDeserializeSummaryDataMapEntry =
-    "failed to deserialize SummaryDataMap entry for summary '%s'";
+    "failed to deserialize SummaryDataMap entry for summary '{0}'";
 
 // SummaryDataMap errors
 constexpr const char *SummaryDataMapElementNotObject =
     "failed to deserialize SummaryDataMap: "
-    "element at index %zu is not a JSON object "
+    "element at index {0} is not a JSON object "
     "(expected SummaryDataMap entry with 'summary_name' and 'summary_data' "
     "fields)";
 constexpr const char *FailedToDeserializeSummaryDataMap =
-    "failed to deserialize SummaryDataMap at index %zu";
+    "failed to deserialize SummaryDataMap at index {0}";
 constexpr const char *DuplicateSummaryName =
     "failed to deserialize SummaryDataMap: "
-    "duplicate SummaryName '%s' found at index %zu";
+    "duplicate SummaryName '{0}' found at index {1}";
 
 // TUSummary errors
-constexpr const char *ReadingTUSummaryFrom = "reading TUSummary from '%s'";
+constexpr const char *ReadingTUSummaryFrom = "reading TUSummary from '{0}'";
 constexpr const char *MissingTUNamespace =
     "missing or invalid field 'tu_namespace' (expected JSON object)";
 constexpr const char *MissingIdTable =
     "missing or invalid field 'id_table' (expected JSON array)";
 constexpr const char *MissingData =
     "missing or invalid field 'data' (expected JSON array)";
-constexpr const char *WritingTUSummaryTo = "writing TUSummary to '%s'";
+constexpr const char *WritingTUSummaryTo = "writing TUSummary to '{0}'";
 } // namespace ErrorMessages
 
 } // namespace

>From d8fc6f85303e6b7af61fd37e461595a791f91e42 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Sun, 8 Feb 2026 15:45:17 -0800
Subject: [PATCH 16/19] Replace lit tests with unit tests

---
 .../Inputs/callgraph-invalid-caller.json      |   25 -
 .../Inputs/callgraph-missing-field.json       |   18 -
 .../Inputs/data-element-not-object.json       |    8 -
 .../Inputs/data-entry-missing-data.json       |   12 -
 .../Inputs/data-missing-summary-name.json     |   12 -
 .../Serialization/Inputs/data-not-array.json  |    8 -
 .../Inputs/defuse-invalid-use.json            |   32 -
 .../Inputs/defuse-missing-field.json          |   18 -
 .../Inputs/directory.json/.gitkeep            |    0
 .../Inputs/duplicate-entity.json              |   35 -
 .../Inputs/duplicate-summary-name.json        |   17 -
 .../entity-data-element-not-object.json       |   15 -
 .../Inputs/entity-data-missing-entity-id.json |   17 -
 .../entity-data-missing-entity-summary.json   |   17 -
 .../Inputs/entity-id-not-uint64.json          |   18 -
 .../Inputs/entity-name-missing-namespace.json |   16 -
 .../Inputs/entity-name-missing-suffix.json    |   21 -
 .../Inputs/entity-name-missing-usr.json       |   21 -
 .../Inputs/entity-summary-no-format-info.json |   18 -
 .../Inputs/id-table-element-not-object.json   |    8 -
 .../Inputs/id-table-entry-id-not-uint64.json  |   22 -
 .../Inputs/id-table-entry-missing-id.json     |   21 -
 .../Inputs/id-table-entry-missing-name.json   |   10 -
 .../Inputs/id-table-not-array.json            |    8 -
 .../Serialization/Inputs/invalid-kind.json    |    8 -
 .../Serialization/Inputs/invalid-syntax.json  |    1 -
 .../Serialization/Inputs/missing-data.json    |    7 -
 .../Inputs/missing-id-table.json              |    7 -
 .../Serialization/Inputs/missing-kind.json    |    7 -
 .../Serialization/Inputs/missing-name.json    |    7 -
 .../Inputs/missing-tu-namespace.json          |    4 -
 .../Inputs/namespace-element-not-object.json  |   17 -
 .../Inputs/no-read-permission.json            |    8 -
 .../Inputs/not-json-extension.txt             |    8 -
 .../Serialization/Inputs/not-object.json      |    1 -
 .../Inputs/valid-callgraph-expected.json      |   74 --
 .../Serialization/Inputs/valid-callgraph.json |   74 --
 .../Inputs/valid-defuse-expected.json         |  108 --
 .../Serialization/Inputs/valid-defuse.json    |  108 --
 .../Inputs/valid-empty-expected.json          |    8 -
 .../Serialization/Inputs/valid-empty.json     |    8 -
 .../Inputs/valid-link-unit-expected.json      |    8 -
 .../Serialization/Inputs/valid-link-unit.json |    8 -
 .../valid-with-empty-data-entry-expected.json |   13 -
 .../Inputs/valid-with-empty-data-entry.json   |   13 -
 .../Inputs/valid-with-idtable-expected.json   |   39 -
 .../Inputs/valid-with-idtable.json            |   39 -
 .../Serialization/error-broken-symlink.test   |   14 -
 .../error-callgraph-invalid-caller.test       |    9 -
 .../error-callgraph-missing-field.test        |    9 -
 .../error-data-element-not-object.test        |    8 -
 .../error-data-entry-missing-data.test        |    9 -
 .../error-data-missing-summary-name.test      |    9 -
 .../Serialization/error-data-not-array.test   |    7 -
 .../error-defuse-invalid-use.test             |    9 -
 .../error-defuse-missing-field.test           |    9 -
 .../Serialization/error-duplicate-entity.test |    8 -
 .../error-duplicate-summary-name.test         |    7 -
 .../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-is-directory.test     |    7 -
 .../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 -
 .../error-permission-denied.test              |   14 -
 .../error-write-flush-failure.test            |   13 -
 .../error-write-readonly-dir.test             |   16 -
 .../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 +-
 .../Scalable/Serialization/JSONFormatTest.cpp | 1091 +++++++++++++++++
 .../Analysis/Scalable/tools/CMakeLists.txt    |    1 -
 .../CMakeLists.txt                            |    9 -
 .../ExampleAnalyses.cpp                       |  338 -----
 .../SSAFSerializationFormatTest.cpp           |   84 --
 99 files changed, 1092 insertions(+), 1775 deletions(-)
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/data-element-not-object.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/data-entry-missing-data.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/data-missing-summary-name.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/data-not-array.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/directory.json/.gitkeep
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-entity.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-namespace.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-suffix.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-usr.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-element-not-object.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-id-not-uint64.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-id.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-name.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-not-array.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/invalid-kind.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/invalid-syntax.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-data.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-id-table.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-kind.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-name.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-tu-namespace.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/namespace-element-not-object.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/no-read-permission.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/not-json-extension.txt
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/not-object.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty-expected.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit-expected.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable-expected.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable.json
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-broken-symlink.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-not-array.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-not-array.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-is-directory.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-data.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-id-table.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-name.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-tu-namespace.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-nonexistent-file.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-not-object.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-permission-denied.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-write-flush-failure.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-write-readonly-dir.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/valid-callgraph.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/valid-defuse.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/valid-empty.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/valid-link-unit.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/valid-with-empty-data-entry.test
 delete mode 100644 clang/test/Analysis/Scalable/Serialization/valid-with-idtable.test
 create mode 100644 clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
 delete mode 100644 clang/unittests/Analysis/Scalable/tools/CMakeLists.txt
 delete mode 100644 clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/CMakeLists.txt
 delete mode 100644 clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
 delete mode 100644 clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp

diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json
deleted file mode 100644
index ce1ab6423d847..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": [
-    {
-      "summary_name": "CallGraph",
-      "summary_data": [
-        {
-          "entity_id": 0,
-          "entity_summary": {
-            "call_graph": [
-              {
-                "caller": "not_a_number",
-                "callees": []
-              }
-            ]
-          }
-        }
-      ]
-    }
-  ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json
deleted file mode 100644
index bd23319d80cec..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": [
-    {
-      "summary_name": "CallGraph",
-      "summary_data": [
-        {
-          "entity_id": 0,
-          "entity_summary": {}
-        }
-      ]
-    }
-  ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/data-element-not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/data-element-not-object.json
deleted file mode 100644
index ca953efa67e77..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/data-element-not-object.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": ["not an object"]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/data-entry-missing-data.json b/clang/test/Analysis/Scalable/Serialization/Inputs/data-entry-missing-data.json
deleted file mode 100644
index a5631d1a60559..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/data-entry-missing-data.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": [
-    {
-      "summary_name": "test"
-    }
-  ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/data-missing-summary-name.json b/clang/test/Analysis/Scalable/Serialization/Inputs/data-missing-summary-name.json
deleted file mode 100644
index d4f48d9d785f2..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/data-missing-summary-name.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": [
-    {
-      "data": []
-    }
-  ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/data-not-array.json b/clang/test/Analysis/Scalable/Serialization/Inputs/data-not-array.json
deleted file mode 100644
index 70daf052792bd..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/data-not-array.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": "not an array"
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json
deleted file mode 100644
index de3ee8d56682f..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json
+++ /dev/null
@@ -1,32 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": [
-    {
-      "summary_name": "DefUse",
-      "summary_data": [
-        {
-          "entity_id": 0,
-          "entity_summary": {
-            "def_use_chains": [
-              {
-                "variable": 0,
-                "definitions": [
-                  {
-                    "definition": 10,
-                    "uses": [
-                      "invalid"
-                    ]
-                  }
-                ]
-              }
-            ]
-          }
-        }
-      ]
-    }
-  ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json
deleted file mode 100644
index 351825e77f74c..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": [
-    {
-      "summary_name": "DefUse",
-      "summary_data": [
-        {
-          "entity_id": 0,
-          "entity_summary": {}
-        }
-      ]
-    }
-  ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/directory.json/.gitkeep b/clang/test/Analysis/Scalable/Serialization/Inputs/directory.json/.gitkeep
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-entity.json b/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-entity.json
deleted file mode 100644
index dd8962c409cc8..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-entity.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [
-    {
-      "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"
-          }
-        ]
-      }
-    }
-  ],
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json b/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json
deleted file mode 100644
index d16d61501a361..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": [
-    {
-      "summary_name": "test",
-      "summary_data": []
-    },
-    {
-      "summary_name": "test",
-      "summary_data": []
-    }
-  ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json
deleted file mode 100644
index d892ee698ee19..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": [
-    {
-      "summary_name": "test",
-      "summary_data": [
-        "not an object"
-      ]
-    }
-  ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json
deleted file mode 100644
index 3b53bcb3564e1..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": [
-    {
-      "summary_name": "test",
-      "summary_data": [
-        {
-          "entity_summary": {}
-        }
-      ]
-    }
-  ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json
deleted file mode 100644
index 6d8a43a10c80c..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": [
-    {
-      "summary_name": "test",
-      "summary_data": [
-        {
-          "entity_id": 0
-        }
-      ]
-    }
-  ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json
deleted file mode 100644
index 246a99c6537c0..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": [
-    {
-      "summary_name": "test",
-      "summary_data": [
-        {
-          "entity_id": "not a number",
-          "entity_summary": {}
-        }
-      ]
-    }
-  ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-namespace.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-namespace.json
deleted file mode 100644
index d11c1064bbeb9..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-namespace.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [
-    {
-      "id": 0,
-      "name": {
-        "usr": "c:@F at foo",
-        "suffix": ""
-      }
-    }
-  ],
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-suffix.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-suffix.json
deleted file mode 100644
index 3598e8d95b2ea..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-suffix.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [
-    {
-      "id": 0,
-      "name": {
-        "usr": "c:@F at foo",
-        "namespace": [
-          {
-            "kind": "compilation_unit",
-            "name": "test.cpp"
-          }
-        ]
-      }
-    }
-  ],
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-usr.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-usr.json
deleted file mode 100644
index 3fab36bcb4916..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-usr.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [
-    {
-      "id": 0,
-      "name": {
-        "suffix": "",
-        "namespace": [
-          {
-            "kind": "compilation_unit",
-            "name": "test.cpp"
-          }
-        ]
-      }
-    }
-  ],
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json
deleted file mode 100644
index 179f8e5d33c47..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": [
-    {
-      "summary_name": "test",
-      "summary_data": [
-        {
-          "entity_id": 0,
-          "entity_summary": {}
-        }
-      ]
-    }
-  ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-element-not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-element-not-object.json
deleted file mode 100644
index ea604010a81e9..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-element-not-object.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": ["not an object"],
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-id-not-uint64.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-id-not-uint64.json
deleted file mode 100644
index 5a736543c5dbc..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-id-not-uint64.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [
-    {
-      "id": -1,
-      "name": {
-        "usr": "c:@F at foo",
-        "suffix": "",
-        "namespace": [
-          {
-            "kind": "compilation_unit",
-            "name": "test.cpp"
-          }
-        ]
-      }
-    }
-  ],
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-id.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-id.json
deleted file mode 100644
index f911bfb7ab94e..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-id.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [
-    {
-      "name": {
-        "usr": "c:@F at foo",
-        "suffix": "",
-        "namespace": [
-          {
-            "kind": "compilation_unit",
-            "name": "test.cpp"
-          }
-        ]
-      }
-    }
-  ],
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-name.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-name.json
deleted file mode 100644
index e5cd1aea3e1ae..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-name.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [
-    {"id": 0}
-  ],
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-not-array.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-not-array.json
deleted file mode 100644
index fb5624207bc2c..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-not-array.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": {},
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-kind.json b/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-kind.json
deleted file mode 100644
index a39c20d3016a0..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-kind.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "InvalidKind",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-syntax.json b/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-syntax.json
deleted file mode 100644
index b0e13f61aa06e..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-syntax.json
+++ /dev/null
@@ -1 +0,0 @@
-{ invalid json }
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-data.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-data.json
deleted file mode 100644
index 071de7214fd23..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-data.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-id-table.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-id-table.json
deleted file mode 100644
index 126e8fba55b5f..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-id-table.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-kind.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-kind.json
deleted file mode 100644
index 8aa5c5fb115e1..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-kind.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "tu_namespace": {
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-name.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-name.json
deleted file mode 100644
index 5a7c2f2f80c08..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-name.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit"
-  },
-  "id_table": [],
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-tu-namespace.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-tu-namespace.json
deleted file mode 100644
index 536918c99e182..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-tu-namespace.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "id_table": [],
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/namespace-element-not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/namespace-element-not-object.json
deleted file mode 100644
index c6715f2e0e5cc..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/namespace-element-not-object.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [
-    {
-      "id": 0,
-      "name": {
-        "usr": "c:@F at foo",
-        "suffix": "",
-        "namespace": ["not an object"]
-      }
-    }
-  ],
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/no-read-permission.json b/clang/test/Analysis/Scalable/Serialization/Inputs/no-read-permission.json
deleted file mode 100644
index 11966839e2292..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/no-read-permission.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/not-json-extension.txt b/clang/test/Analysis/Scalable/Serialization/Inputs/not-json-extension.txt
deleted file mode 100644
index 11966839e2292..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/not-json-extension.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/not-object.json
deleted file mode 100644
index fe51488c7066f..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/not-object.json
+++ /dev/null
@@ -1 +0,0 @@
-[]
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json
deleted file mode 100644
index a9939b5467719..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json
+++ /dev/null
@@ -1,74 +0,0 @@
-{
-  "data": [
-    {
-      "summary_data": [
-        {
-          "entity_id": 0,
-          "entity_summary": {
-            "call_graph": [
-              {
-                "callees": [
-                  1,
-                  2
-                ],
-                "caller": 0
-              },
-              {
-                "callees": [
-                  2,
-                  3
-                ],
-                "caller": 1
-              },
-              {
-                "callees": [
-                  3
-                ],
-                "caller": 2
-              }
-            ]
-          }
-        }
-      ],
-      "summary_name": "CallGraph"
-    }
-  ],
-  "id_table": [
-    {
-      "id": 2,
-      "name": {
-        "namespace": [],
-        "suffix": "",
-        "usr": "c:@F at bar"
-      }
-    },
-    {
-      "id": 3,
-      "name": {
-        "namespace": [],
-        "suffix": "",
-        "usr": "c:@F at baz"
-      }
-    },
-    {
-      "id": 1,
-      "name": {
-        "namespace": [],
-        "suffix": "",
-        "usr": "c:@F at foo"
-      }
-    },
-    {
-      "id": 0,
-      "name": {
-        "namespace": [],
-        "suffix": "",
-        "usr": "c:@F at main"
-      }
-    }
-  ],
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "call_graph_test.cpp"
-  }
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json
deleted file mode 100644
index 831cd5000d1b2..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json
+++ /dev/null
@@ -1,74 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "call_graph_test.cpp"
-  },
-  "id_table": [
-    {
-      "id": 0,
-      "name": {
-        "usr": "c:@F at main",
-        "suffix": "",
-        "namespace": []
-      }
-    },
-    {
-      "id": 1,
-      "name": {
-        "usr": "c:@F at foo",
-        "suffix": "",
-        "namespace": []
-      }
-    },
-    {
-      "id": 2,
-      "name": {
-        "usr": "c:@F at bar",
-        "suffix": "",
-        "namespace": []
-      }
-    },
-    {
-      "id": 3,
-      "name": {
-        "usr": "c:@F at baz",
-        "suffix": "",
-        "namespace": []
-      }
-    }
-  ],
-  "data": [
-    {
-      "summary_name": "CallGraph",
-      "summary_data": [
-        {
-          "entity_id": 0,
-          "entity_summary": {
-            "call_graph": [
-              {
-                "caller": 0,
-                "callees": [
-                  1,
-                  2
-                ]
-              },
-              {
-                "caller": 1,
-                "callees": [
-                  2,
-                  3
-                ]
-              },
-              {
-                "caller": 2,
-                "callees": [
-                  3
-                ]
-              }
-            ]
-          }
-        }
-      ]
-    }
-  ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json
deleted file mode 100644
index 72ac6212c0266..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json
+++ /dev/null
@@ -1,108 +0,0 @@
-{
-  "data": [
-    {
-      "summary_data": [
-        {
-          "entity_id": 0,
-          "entity_summary": {
-            "def_use_chains": [
-              {
-                "definitions": [
-                  {
-                    "definition": 10,
-                    "uses": [
-                      12,
-                      13
-                    ]
-                  },
-                  {
-                    "definition": 14,
-                    "uses": [
-                      13
-                    ]
-                  }
-                ],
-                "variable": 0
-              },
-              {
-                "definitions": [
-                  {
-                    "definition": 11,
-                    "uses": [
-                      12,
-                      14
-                    ]
-                  }
-                ],
-                "variable": 1
-              }
-            ]
-          }
-        }
-      ],
-      "summary_name": "DefUse"
-    }
-  ],
-  "id_table": [
-    {
-      "id": 12,
-      "name": {
-        "namespace": [],
-        "suffix": "",
-        "usr": "c:@Stmt at line10"
-      }
-    },
-    {
-      "id": 13,
-      "name": {
-        "namespace": [],
-        "suffix": "",
-        "usr": "c:@Stmt at line12"
-      }
-    },
-    {
-      "id": 14,
-      "name": {
-        "namespace": [],
-        "suffix": "",
-        "usr": "c:@Stmt at line15"
-      }
-    },
-    {
-      "id": 10,
-      "name": {
-        "namespace": [],
-        "suffix": "",
-        "usr": "c:@Stmt at line5"
-      }
-    },
-    {
-      "id": 11,
-      "name": {
-        "namespace": [],
-        "suffix": "",
-        "usr": "c:@Stmt at line8"
-      }
-    },
-    {
-      "id": 0,
-      "name": {
-        "namespace": [],
-        "suffix": "",
-        "usr": "c:@V at x"
-      }
-    },
-    {
-      "id": 1,
-      "name": {
-        "namespace": [],
-        "suffix": "",
-        "usr": "c:@V at y"
-      }
-    }
-  ],
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "defuse_test.cpp"
-  }
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json
deleted file mode 100644
index b541843bfd7c7..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json
+++ /dev/null
@@ -1,108 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "defuse_test.cpp"
-  },
-  "id_table": [
-    {
-      "id": 0,
-      "name": {
-        "usr": "c:@V at x",
-        "suffix": "",
-        "namespace": []
-      }
-    },
-    {
-      "id": 1,
-      "name": {
-        "usr": "c:@V at y",
-        "suffix": "",
-        "namespace": []
-      }
-    },
-    {
-      "id": 10,
-      "name": {
-        "usr": "c:@Stmt at line5",
-        "suffix": "",
-        "namespace": []
-      }
-    },
-    {
-      "id": 11,
-      "name": {
-        "usr": "c:@Stmt at line8",
-        "suffix": "",
-        "namespace": []
-      }
-    },
-    {
-      "id": 12,
-      "name": {
-        "usr": "c:@Stmt at line10",
-        "suffix": "",
-        "namespace": []
-      }
-    },
-    {
-      "id": 13,
-      "name": {
-        "usr": "c:@Stmt at line12",
-        "suffix": "",
-        "namespace": []
-      }
-    },
-    {
-      "id": 14,
-      "name": {
-        "usr": "c:@Stmt at line15",
-        "suffix": "",
-        "namespace": []
-      }
-    }
-  ],
-  "data": [
-    {
-      "summary_name": "DefUse",
-      "summary_data": [
-        {
-          "entity_id": 0,
-          "entity_summary": {
-            "def_use_chains": [
-              {
-                "variable": 0,
-                "definitions": [
-                  {
-                    "definition": 10,
-                    "uses": [
-                      12,
-                      13
-                    ]
-                  },
-                  {
-                    "definition": 14,
-                    "uses": [
-                      13
-                    ]
-                  }
-                ]
-              },
-              {
-                "variable": 1,
-                "definitions": [
-                  {
-                    "definition": 11,
-                    "uses": [
-                      12,
-                      14
-                    ]
-                  }
-                ]
-              }
-            ]
-          }
-        }
-      ]
-    }
-  ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty-expected.json
deleted file mode 100644
index e7bd20a47921c..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty-expected.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "data": [],
-  "id_table": [],
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  }
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty.json
deleted file mode 100644
index 11966839e2292..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit-expected.json
deleted file mode 100644
index 840b5289f010a..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit-expected.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "data": [],
-  "id_table": [],
-  "tu_namespace": {
-    "kind": "link_unit",
-    "name": "libtest.so"
-  }
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit.json
deleted file mode 100644
index 98e1cce334fd7..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "link_unit",
-    "name": "libtest.so"
-  },
-  "id_table": [],
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json
deleted file mode 100644
index 2e5e958748263..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "data": [
-    {
-      "summary_data": [],
-      "summary_name": "test"
-    }
-  ],
-  "id_table": [],
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  }
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json
deleted file mode 100644
index 13a6cb34ae255..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [],
-  "data": [
-    {
-      "summary_name": "test",
-      "summary_data": []
-    }
-  ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable-expected.json
deleted file mode 100644
index 14142a8a22c88..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable-expected.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
-  "data": [],
-  "id_table": [
-    {
-      "id": 1,
-      "name": {
-        "namespace": [
-          {
-            "kind": "compilation_unit",
-            "name": "test.cpp"
-          },
-          {
-            "kind": "link_unit",
-            "name": "libtest.so"
-          }
-        ],
-        "suffix": "1",
-        "usr": "c:@F at bar"
-      }
-    },
-    {
-      "id": 0,
-      "name": {
-        "namespace": [
-          {
-            "kind": "compilation_unit",
-            "name": "test.cpp"
-          }
-        ],
-        "suffix": "",
-        "usr": "c:@F at foo"
-      }
-    }
-  ],
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  }
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable.json
deleted file mode 100644
index 76515b8ba750d..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
-  "tu_namespace": {
-    "kind": "compilation_unit",
-    "name": "test.cpp"
-  },
-  "id_table": [
-    {
-      "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"
-          }
-        ]
-      }
-    }
-  ],
-  "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/error-broken-symlink.test b/clang/test/Analysis/Scalable/Serialization/error-broken-symlink.test
deleted file mode 100644
index d1e61603c06cc..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-broken-symlink.test
+++ /dev/null
@@ -1,14 +0,0 @@
-## Test that broken symlinks with .json extension are rejected.
-## Unsupported on Windows as symlinks behave differently there.
-
-# UNSUPPORTED: system-windows
-
-# RUN: ln -sf non-existent-file.json %t-broken-symlink.json
-# RUN: not ssaf-serialization-format-test %t-broken-symlink.json 2>&1 | FileCheck %s
-# RUN: rm %t-broken-symlink.json
-
-## Test that broken symlinks are detected as non-existent files
-
-# CHECK: failed to read TUSummary
-# CHECK: failed to validate JSON file
-# CHECK: file does not exist
diff --git a/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test b/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
deleted file mode 100644
index 5ee78f60bcb2f..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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 SummaryDataMap entry for summary 'CallGraph'
-// CHECK: failed to deserialize EntityDataMap at index 0
-// CHECK: failed to deserialize EntityDataMap entry
-// 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
deleted file mode 100644
index 2abbf1438654c..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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 SummaryDataMap entry for summary 'CallGraph'
-// CHECK: failed to deserialize EntityDataMap at index 0
-// CHECK: failed to deserialize EntityDataMap entry
-// 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
deleted file mode 100644
index 1c7adf5437f6e..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// 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: failed to deserialize SummaryDataMap
-// CHECK: element at index
-// CHECK: expected SummaryDataMap entry with 'summary_name' and 'summary_data' fields
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
deleted file mode 100644
index f7e25e81bc2b1..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/data-entry-missing-data.json 2>&1 | FileCheck %s
-
-// Test that data array entries must have 'summary_data' field
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize SummaryDataMap at index
-// CHECK: failed to deserialize SummaryDataMap entry
-// CHECK: missing or invalid field 'summary_data'
-// CHECK: expected JSON array of entity data entries
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
deleted file mode 100644
index 03807ee6c44cb..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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: failed to deserialize SummaryDataMap at index
-// CHECK: failed to deserialize SummaryDataMap entry
-// 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
deleted file mode 100644
index 058be1a4bddbb..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-data-not-array.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// 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
deleted file mode 100644
index 9674ec9b4ada1..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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 SummaryDataMap entry for summary 'DefUse'
-// CHECK: failed to deserialize EntityDataMap at index 0
-// CHECK: failed to deserialize EntityDataMap entry
-// 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
deleted file mode 100644
index f03b4a1900307..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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 SummaryDataMap entry for summary 'DefUse'
-// CHECK: failed to deserialize EntityDataMap at index 0
-// CHECK: failed to deserialize EntityDataMap entry
-// 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
deleted file mode 100644
index 542ea9fa23552..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// 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
-// 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
deleted file mode 100644
index b8f6e1335b02f..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// 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: failed to deserialize SummaryDataMap
-// CHECK: duplicate SummaryName 'test' found at index
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
deleted file mode 100644
index 43c0c9d164116..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// 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 EntityDataMap
-// CHECK: element at index 0 is not a JSON object
-// CHECK: expected EntityDataMap entry 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
deleted file mode 100644
index 7c80a3e121279..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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 EntityDataMap at index 0
-// CHECK: failed to deserialize EntityDataMap entry
-// 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
deleted file mode 100644
index 57ed34e4eec26..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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 EntityDataMap at index 0
-// CHECK: failed to deserialize EntityDataMap entry
-// 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
deleted file mode 100644
index bacd042fa34e5..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// 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 EntityDataMap at index 0
-// CHECK: failed to deserialize EntityDataMap entry
-// 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
deleted file mode 100644
index da2f56d600b58..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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
-// CHECK: failed to deserialize EntityName
-// 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
deleted file mode 100644
index ae0975fd850d9..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// 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
-// CHECK: failed to deserialize EntityName
-// 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
deleted file mode 100644
index db48593c232d7..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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
-// CHECK: failed to deserialize EntityName
-// 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
deleted file mode 100644
index 075eddaf271c8..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// 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
-// 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
deleted file mode 100644
index eb8a70b5d8933..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// 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
-// 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
deleted file mode 100644
index 7d95da6189017..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// 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
-// 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
deleted file mode 100644
index 846d0088ec974..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// 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
-// 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
deleted file mode 100644
index 2404199b14c44..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// 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
-// 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
deleted file mode 100644
index 8d7a9bd640247..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-id-table-not-array.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// 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
deleted file mode 100644
index 41b994f625d87..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// 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
-// 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
deleted file mode 100644
index 529ebd0f9a6e2..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
+++ /dev/null
@@ -1,6 +0,0 @@
-// 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
diff --git a/clang/test/Analysis/Scalable/Serialization/error-is-directory.test b/clang/test/Analysis/Scalable/Serialization/error-is-directory.test
deleted file mode 100644
index e30bdd137af89..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-is-directory.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/directory.json 2>&1 | FileCheck %s
-
-// Test that directories with .json extension are rejected
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to validate JSON file
-// CHECK: path is a directory, not a file
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-data.test b/clang/test/Analysis/Scalable/Serialization/error-missing-data.test
deleted file mode 100644
index 8d4b654a8715d..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-missing-data.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// 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
deleted file mode 100644
index a71bb4e361fdd..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-missing-id-table.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// 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
deleted file mode 100644
index 24c276a825c7a..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// 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
-// 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
deleted file mode 100644
index 2fb17423943dc..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-missing-name.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// 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
-// 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
deleted file mode 100644
index fb6b58693edf6..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-missing-tu-namespace.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// 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
deleted file mode 100644
index 55006eb230c21..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
+++ /dev/null
@@ -1,10 +0,0 @@
-// 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
-// CHECK: failed to deserialize EntityName
-// CHECK: failed to deserialize NestedBuildNamespace
-// 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
deleted file mode 100644
index 3748d54622e75..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-nonexistent-file.test
+++ /dev/null
@@ -1,6 +0,0 @@
-// 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
deleted file mode 100644
index 1a56fc6e13dc6..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/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
-// 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
deleted file mode 100644
index 20409e7642b32..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-not-object.test
+++ /dev/null
@@ -1,6 +0,0 @@
-// 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
diff --git a/clang/test/Analysis/Scalable/Serialization/error-permission-denied.test b/clang/test/Analysis/Scalable/Serialization/error-permission-denied.test
deleted file mode 100644
index b89f32d19620d..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-permission-denied.test
+++ /dev/null
@@ -1,14 +0,0 @@
-## Test that files without read permissions are handled correctly.
-## Unsupported on Windows as chmod doesn't work reliably there.
-
-# UNSUPPORTED: system-windows
-# REQUIRES: non-root-user
-
-# RUN: chmod 000 %S/Inputs/no-read-permission.json
-# RUN: not ssaf-serialization-format-test %S/Inputs/no-read-permission.json 2>&1 | FileCheck %s
-# RUN: chmod 644 %S/Inputs/no-read-permission.json
-
-## Test that permission denied error is reported when file cannot be read
-
-# CHECK: failed to read TUSummary
-# CHECK: failed to read file
diff --git a/clang/test/Analysis/Scalable/Serialization/error-write-flush-failure.test b/clang/test/Analysis/Scalable/Serialization/error-write-flush-failure.test
deleted file mode 100644
index 9bbdc4e8a611e..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-write-flush-failure.test
+++ /dev/null
@@ -1,13 +0,0 @@
-## Test that write flush failures are handled correctly.
-## Uses /dev/full which always reports "disk full" errors on write.
-## This special device file successfully opens but fails on write/flush operations.
-
-# REQUIRES: system-linux
-
-# RUN: not ssaf-serialization-format-test %S/Inputs/valid-defuse.json -o /dev/full 2>&1 | FileCheck %s
-
-## Test that flush/write error is reported when device is full
-## This tests JSONFormat.cpp:97-99 (write/flush failure path)
-
-# CHECK: failed to write TUSummary
-# CHECK: write failed
diff --git a/clang/test/Analysis/Scalable/Serialization/error-write-readonly-dir.test b/clang/test/Analysis/Scalable/Serialization/error-write-readonly-dir.test
deleted file mode 100644
index 47087f0a1de12..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-write-readonly-dir.test
+++ /dev/null
@@ -1,16 +0,0 @@
-## Test that write failures to read-only directories are handled correctly.
-## Unsupported on Windows as chmod doesn't work reliably there.
-
-# UNSUPPORTED: system-windows
-# REQUIRES: non-root-user
-
-# RUN: rm -rf %t-readonly && mkdir -p %t-readonly
-# RUN: chmod 444 %t-readonly
-# RUN: not ssaf-serialization-format-test %S/Inputs/valid-empty.json -o %t-readonly/output.json 2>&1 | FileCheck %s
-# RUN: chmod 755 %t-readonly && rm -rf %t-readonly
-
-## Test that write error is reported when output directory is read-only
-## This tests JSONFormat.cpp:89-92 (file open failure path)
-
-# CHECK: failed to write TUSummary
-# CHECK: failed to open
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-callgraph.test b/clang/test/Analysis/Scalable/Serialization/valid-callgraph.test
deleted file mode 100644
index 09722d11a796e..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/valid-callgraph.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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
deleted file mode 100644
index 311bd02fdde3d..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/valid-defuse.test
+++ /dev/null
@@ -1,10 +0,0 @@
-// 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
deleted file mode 100644
index 30712a3e8d637..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/valid-empty.test
+++ /dev/null
@@ -1,4 +0,0 @@
-// 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
deleted file mode 100644
index b9533904c0bd1..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/valid-link-unit.test
+++ /dev/null
@@ -1,4 +0,0 @@
-// 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
deleted file mode 100644
index a9be32902a10a..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/valid-with-empty-data-entry.test
+++ /dev/null
@@ -1,4 +0,0 @@
-// 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
deleted file mode 100644
index a714aa7dcf2d1..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/valid-with-idtable.test
+++ /dev/null
@@ -1,4 +0,0 @@
-// 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 bb5c9b331a43f..a622f5335354a 100644
--- a/clang/test/lit.cfg.py
+++ b/clang/test/lit.cfg.py
@@ -103,7 +103,6 @@
     "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 3c642dd4b2e67..cdcd62f797ba8 100644
--- a/clang/unittests/Analysis/Scalable/CMakeLists.txt
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -10,6 +10,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
   Registries/MockSummaryExtractor2.cpp
   Registries/SerializationFormatRegistryTest.cpp
   Registries/SummaryExtractorRegistryTest.cpp
+  Serialization/JSONFormatTest.cpp
   SummaryNameTest.cpp
 
   CLANG_LIBS
@@ -28,5 +29,3 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
   Support
   )
 
-add_subdirectory(tools)
-
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
new file mode 100644
index 0000000000000..3aec2483766cd
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
@@ -0,0 +1,1091 @@
+//===- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Unit tests for SSAF JSON serialization format reading and writing.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Registry.h"
+#include "llvm/Support/VirtualFileSystem.h"
+#include "llvm/Support/raw_ostream.h"
+#include "gtest/gtest.h"
+#include <memory>
+#include <string>
+#include <vector>
+
+using namespace clang::ssaf;
+using namespace llvm;
+
+namespace {
+
+// ============================================================================
+// Test Analysis - Simple analysis for testing JSON serialization
+// ============================================================================
+
+struct TestAnalysis : EntitySummary {
+  TestAnalysis() : EntitySummary(SummaryName("test_summary")) {}
+  std::vector<std::pair<EntityId, EntityId>> Pairs;
+};
+
+static json::Object
+serializeTestAnalysis(const EntitySummary &Summary,
+                      const JSONFormat::EntityIdConverter &Converter) {
+  const auto &TA = static_cast<const TestAnalysis &>(Summary);
+  json::Array PairsArray;
+  for (const auto &[First, Second] : TA.Pairs) {
+    PairsArray.push_back(json::Object{
+        {"first", Converter.toJSON(First)},
+        {"second", Converter.toJSON(Second)},
+    });
+  }
+  return json::Object{{"pairs", std::move(PairsArray)}};
+}
+
+static Expected<std::unique_ptr<EntitySummary>>
+deserializeTestAnalysis(const json::Object &Obj, EntityIdTable &IdTable,
+                        const JSONFormat::EntityIdConverter &Converter) {
+  auto Result = std::make_unique<TestAnalysis>();
+  const json::Array *PairsArray = Obj.getArray("pairs");
+  if (!PairsArray)
+    return createStringError(inconvertibleErrorCode(),
+                             "missing required field 'pairs'");
+  for (size_t I = 0; I < PairsArray->size(); ++I) {
+    const json::Object *Pair = (*PairsArray)[I].getAsObject();
+    if (!Pair)
+      return createStringError(
+          inconvertibleErrorCode(),
+          "pairs element at index %zu is not a JSON object", I);
+    auto FirstOpt = Pair->getInteger("first");
+    if (!FirstOpt)
+      return createStringError(inconvertibleErrorCode(),
+                               "missing or invalid 'first' field at index %zu",
+                               I);
+    auto SecondOpt = Pair->getInteger("second");
+    if (!SecondOpt)
+      return createStringError(inconvertibleErrorCode(),
+                               "missing or invalid 'second' field at index %zu",
+                               I);
+    Result->Pairs.emplace_back(Converter.fromJSON(*FirstOpt),
+                               Converter.fromJSON(*SecondOpt));
+  }
+  return std::move(Result);
+}
+
+struct TestAnalysisFormatInfo : JSONFormat::FormatInfo {
+  TestAnalysisFormatInfo()
+      : JSONFormat::FormatInfo(SummaryName("test_summary"),
+                               serializeTestAnalysis, deserializeTestAnalysis) {
+  }
+};
+
+static llvm::Registry<JSONFormat::FormatInfo>::Add<TestAnalysisFormatInfo>
+    RegisterTestAnalysis("TestAnalysis", "Format info for test analysis data");
+
+// ============================================================================
+// Base Fixture with Common Utilities
+// ============================================================================
+
+class JSONFormatTestBase : public ::testing::Test {
+protected:
+  SmallString<128> TestDir;
+
+  void SetUp() override {
+    std::error_code EC =
+        sys::fs::createUniqueDirectory("json-format-test", TestDir);
+    ASSERT_FALSE(EC) << "Failed to create temp directory: " << EC.message();
+  }
+
+  void TearDown() override { sys::fs::remove_directories(TestDir); }
+
+  // Helper to create a temporary JSON file and read it using JSONFormat
+  std::pair<JSONFormat, SmallString<128>>
+  readJSON(StringRef JSON, StringRef Filename = "test.json") {
+    SmallString<128> FilePath = TestDir;
+    sys::path::append(FilePath, Filename);
+
+    std::error_code EC;
+    raw_fd_ostream OS(FilePath, EC);
+    EXPECT_FALSE(EC) << "Failed to create file: " << EC.message();
+    OS << JSON;
+    OS.close();
+
+    return {JSONFormat(vfs::getRealFileSystem()), FilePath};
+  }
+
+  // Helper to check if error message contains expected substrings
+  bool errorContains(Error &Err, ArrayRef<StringRef> Parts) {
+    std::string ErrorMsg = toString(std::move(Err));
+    for (StringRef Part : Parts) {
+      if (ErrorMsg.find(Part.str()) == std::string::npos)
+        return false;
+    }
+    return true;
+  }
+};
+
+// ============================================================================
+// Fixture for Error Tests
+// ============================================================================
+
+class JSONFormatErrorTest : public JSONFormatTestBase {
+protected:
+  void expectError(StringRef JSON, ArrayRef<StringRef> ErrorParts,
+                   StringRef Filename = "test.json") {
+    auto [Format, FilePath] = createFormat(JSON, Filename);
+    auto Result = Format.readTUSummary(FilePath);
+    ASSERT_FALSE(Result) << "Expected error but read succeeded";
+
+    Error Err = Result.takeError();
+    std::string ErrorMsg = toString(std::move(Err));
+
+    bool allFound = true;
+    for (StringRef Part : ErrorParts) {
+      if (ErrorMsg.find(Part.str()) == std::string::npos) {
+        allFound = false;
+        break;
+      }
+    }
+
+    EXPECT_TRUE(allFound) << "Error message didn't contain expected parts.\n"
+                          << "Actual error: " << ErrorMsg << "\n"
+                          << "Expected parts: [" <<
+        [&]() {
+          std::string result;
+          for (size_t i = 0; i < ErrorParts.size(); ++i) {
+            if (i > 0)
+              result += ", ";
+            result += "\"" + ErrorParts[i].str() + "\"";
+          }
+          return result;
+        }() << "]";
+  }
+};
+
+// ============================================================================
+// Fixture for Valid Configuration Tests
+// ============================================================================
+
+class JSONFormatValidTest : public JSONFormatTestBase {
+protected:
+  void expectSuccess(StringRef JSON, StringRef Filename = "test.json") {
+    auto [Format, FilePath] = createFormat(JSON, Filename);
+    auto Result = Format.readTUSummary(FilePath);
+    if (!Result) {
+      FAIL() << "Read failed: " << toString(Result.takeError());
+    }
+  }
+};
+
+// ============================================================================
+// Fixture for Round-Trip Tests
+// ============================================================================
+
+class JSONFormatRoundTripTest : public JSONFormatTestBase {
+protected:
+  void testRoundTrip(StringRef InputJSON) {
+    // Read the input
+    auto [InputFormat, InputPath] = createFormat(InputJSON, "input.json");
+    auto Summary = InputFormat.readTUSummary(InputPath);
+    if (!Summary) {
+      FAIL() << "Failed to read input: " << toString(Summary.takeError());
+    }
+
+    // Write to output file
+    SmallString<128> OutputPath = TestDir;
+    sys::path::append(OutputPath, "output.json");
+
+    JSONFormat OutputFormat(vfs::getRealFileSystem());
+    auto WriteErr = OutputFormat.writeTUSummary(*Summary, OutputPath);
+    if (WriteErr) {
+      FAIL() << "Failed to write output: " << toString(std::move(WriteErr));
+    }
+
+    // Read back the written file
+    auto RoundTrip = OutputFormat.readTUSummary(OutputPath);
+    if (!RoundTrip) {
+      FAIL() << "Failed to read round-trip output: "
+             << toString(RoundTrip.takeError());
+    }
+  }
+};
+
+// ============================================================================
+// File Access Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatErrorTest, NonexistentFile) {
+  SmallString<128> NonexistentPath = TestDir;
+  sys::path::append(NonexistentPath, "nonexistent.json");
+
+  JSONFormat Format(vfs::getRealFileSystem());
+  auto Result = Format.readTUSummary(NonexistentPath);
+  ASSERT_FALSE(Result);
+
+  Error Err = Result.takeError();
+  EXPECT_TRUE(
+      errorContains(Err, {"reading TUSummary from", "file does not exist"}));
+}
+
+TEST_F(JSONFormatErrorTest, NotJsonExtension) {
+  expectError("{}", {"reading TUSummary from", "not a JSON file"}, "test.txt");
+}
+
+// ============================================================================
+// JSON Syntax Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatErrorTest, InvalidSyntax) {
+  expectError("{ invalid json }", {"reading TUSummary from",
+                                   "failed to read JSON object from file"});
+}
+
+TEST_F(JSONFormatErrorTest, NotObject) {
+  expectError(
+      "[]", {"reading TUSummary from", "failed to read JSON object from file"});
+}
+
+// ============================================================================
+// Root Structure Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatErrorTest, MissingTUNamespace) {
+  expectError(
+      R"({
+    "id_table": [],
+    "data": []
+  })",
+      {"reading TUSummary from", "missing or invalid field 'tu_namespace'"});
+}
+
+TEST_F(JSONFormatErrorTest, MissingKind) {
+  expectError(R"({
+    "tu_namespace": {
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": []
+  })",
+              {"reading TUSummary from", "failed to deserialize BuildNamespace",
+               "missing required field 'kind' (expected BuildNamespaceKind)"});
+}
+
+TEST_F(JSONFormatErrorTest, MissingName) {
+  expectError(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit"
+    },
+    "id_table": [],
+    "data": []
+  })",
+              {"reading TUSummary from", "failed to deserialize BuildNamespace",
+               "missing required field 'name'"});
+}
+
+TEST_F(JSONFormatErrorTest, InvalidKind) {
+  expectError(R"({
+    "tu_namespace": {
+      "kind": "invalid_kind",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": []
+  })",
+              {"reading TUSummary from", "failed to deserialize BuildNamespace",
+               "invalid 'kind' BuildNamespaceKind value"});
+}
+
+TEST_F(JSONFormatErrorTest, MissingIDTable) {
+  expectError(
+      R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "data": []
+  })",
+      {"reading TUSummary from", "missing or invalid field 'id_table'"});
+}
+
+TEST_F(JSONFormatErrorTest, MissingData) {
+  expectError(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": []
+  })",
+              {"reading TUSummary from", "missing or invalid field 'data'"});
+}
+
+// ============================================================================
+// ID Table Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatErrorTest, IDTableNotArray) {
+  expectError(
+      R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": {},
+    "data": []
+  })",
+      {"reading TUSummary from", "missing or invalid field 'id_table'"});
+}
+
+TEST_F(JSONFormatErrorTest, IDTableElementNotObject) {
+  expectError(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [123],
+    "data": []
+  })",
+              {"reading TUSummary from", "failed to deserialize EntityIdTable",
+               "element at index 0 is not a JSON object",
+               "(expected EntityIdTable entry with 'id' and 'name' fields)"});
+}
+
+TEST_F(JSONFormatErrorTest, IDTableEntryMissingID) {
+  expectError(
+      R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": []
+        }
+      }
+    ],
+    "data": []
+  })",
+      {"reading TUSummary from",
+       "failed to deserialize EntityIdTable at index 0",
+       "failed to deserialize EntityIdTable entry",
+       "missing required field 'id' (expected unsigned integer EntityId)"});
+}
+
+TEST_F(JSONFormatErrorTest, IDTableEntryMissingName) {
+  expectError(
+      R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0
+      }
+    ],
+    "data": []
+  })",
+      {"reading TUSummary from",
+       "failed to deserialize EntityIdTable at index 0",
+       "failed to deserialize EntityIdTable entry",
+       "missing or invalid field 'name' (expected EntityName JSON object)"});
+}
+
+TEST_F(JSONFormatErrorTest, IDTableEntryIDNotUInt64) {
+  expectError(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": "not_a_number",
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": []
+        }
+      }
+    ],
+    "data": []
+  })",
+              {"reading TUSummary from",
+               "failed to deserialize EntityIdTable at index 0",
+               "failed to deserialize EntityIdTable entry",
+               "field 'id' is not a valid unsigned 64-bit integer",
+               "(expected non-negative EntityId value)"});
+}
+
+TEST_F(JSONFormatErrorTest, DuplicateEntity) {
+  expectError(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "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"
+            }
+          ]
+        }
+      }
+    ],
+    "data": []
+  })",
+              {"reading TUSummary from", "failed to deserialize EntityIdTable",
+               "duplicate EntityName found at index",
+               "(EntityId=0 already exists in table)"});
+}
+
+// ============================================================================
+// Entity Name Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatErrorTest, EntityNameMissingUSR) {
+  expectError(
+      R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "suffix": "",
+          "namespace": []
+        }
+      }
+    ],
+    "data": []
+  })",
+      {"reading TUSummary from",
+       "failed to deserialize EntityIdTable at index 0",
+       "failed to deserialize EntityIdTable entry",
+       "failed to deserialize EntityName",
+       "missing required field 'usr' (Unified Symbol Resolution string)"});
+}
+
+TEST_F(JSONFormatErrorTest, EntityNameMissingSuffix) {
+  expectError(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "namespace": []
+        }
+      }
+    ],
+    "data": []
+  })",
+              {"reading TUSummary from",
+               "failed to deserialize EntityIdTable at index 0",
+               "failed to deserialize EntityIdTable entry",
+               "failed to deserialize EntityName",
+               "missing required field 'suffix'"});
+}
+
+TEST_F(JSONFormatErrorTest, EntityNameMissingNamespace) {
+  expectError(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": ""
+        }
+      }
+    ],
+    "data": []
+  })",
+              {"reading TUSummary from",
+               "failed to deserialize EntityIdTable at index 0",
+               "failed to deserialize EntityIdTable entry",
+               "failed to deserialize EntityName",
+               "missing or invalid field 'namespace'",
+               "(expected JSON array of BuildNamespace objects)"});
+}
+
+TEST_F(JSONFormatErrorTest, NamespaceElementNotObject) {
+  expectError(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": ["invalid"]
+        }
+      }
+    ],
+    "data": []
+  })",
+              {"reading TUSummary from",
+               "failed to deserialize EntityIdTable at index 0",
+               "failed to deserialize EntityIdTable entry",
+               "failed to deserialize EntityName",
+               "failed to deserialize NestedBuildNamespace",
+               "element at index 0 is not a JSON object"});
+}
+
+// ============================================================================
+// Data Array Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatErrorTest, DataNotArray) {
+  expectError(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": {}
+  })",
+              {"reading TUSummary from", "missing or invalid field 'data'"});
+}
+
+TEST_F(JSONFormatErrorTest, DataElementNotObject) {
+  expectError(
+      R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": ["invalid"]
+  })",
+      {"reading TUSummary from", "failed to deserialize SummaryDataMap",
+       "element at index 0 is not a JSON object",
+       "(expected SummaryDataMap entry with 'summary_name' and 'summary_data'",
+       "fields)"});
+}
+
+TEST_F(JSONFormatErrorTest, DataEntryMissingSummaryName) {
+  expectError(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_data": []
+      }
+    ]
+  })",
+              {"reading TUSummary from",
+               "failed to deserialize SummaryDataMap at index 0",
+               "failed to deserialize SummaryDataMap entry",
+               "missing required field 'summary_name'"});
+}
+
+TEST_F(JSONFormatErrorTest, DataEntryMissingData) {
+  expectError(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "test_summary"
+      }
+    ]
+  })",
+              {"reading TUSummary from",
+               "failed to deserialize SummaryDataMap at index 0",
+               "failed to deserialize SummaryDataMap entry",
+               "missing or invalid field 'summary_data'"});
+}
+
+TEST_F(JSONFormatErrorTest, DuplicateSummaryName) {
+  expectError(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "test_summary",
+        "summary_data": []
+      },
+      {
+        "summary_name": "test_summary",
+        "summary_data": []
+      }
+    ]
+  })",
+              {"reading TUSummary from", "failed to deserialize SummaryDataMap",
+               "duplicate SummaryName 'test_summary' found at index"});
+}
+
+// ============================================================================
+// Entity Data Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatErrorTest, EntityDataElementNotObject) {
+  expectError(
+      R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "test_summary",
+        "summary_data": ["invalid"]
+      }
+    ]
+  })",
+      {"reading TUSummary from",
+       "failed to deserialize SummaryDataMap at index 0",
+       "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
+       "failed to deserialize EntityDataMap",
+       "element at index 0 is not a JSON object",
+       "(expected EntityDataMap entry with 'entity_id' and 'entity_summary'",
+       "fields)"});
+}
+
+TEST_F(JSONFormatErrorTest, EntityDataMissingEntityID) {
+  expectError(
+      R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "test_summary",
+        "summary_data": [
+          {
+            "entity_summary": {}
+          }
+        ]
+      }
+    ]
+  })",
+      {"reading TUSummary from",
+       "failed to deserialize SummaryDataMap at index 0",
+       "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
+       "failed to deserialize EntityDataMap at index 0",
+       "failed to deserialize EntityDataMap entry",
+       "missing required field 'entity_id' (expected unsigned integer "
+       "EntityId)"});
+}
+
+TEST_F(JSONFormatErrorTest, EntityDataMissingEntitySummary) {
+  expectError(
+      R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "test_summary",
+        "summary_data": [
+          {
+            "entity_id": 0
+          }
+        ]
+      }
+    ]
+  })",
+      {"reading TUSummary from",
+       "failed to deserialize SummaryDataMap at index 0",
+       "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
+       "failed to deserialize EntityDataMap at index 0",
+       "failed to deserialize EntityDataMap entry",
+       "missing or invalid field 'entity_summary'",
+       "(expected EntitySummary JSON object)"});
+}
+
+TEST_F(JSONFormatErrorTest, EntityIDNotUInt64) {
+  expectError(
+      R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "test_summary",
+        "summary_data": [
+          {
+            "entity_id": "not_a_number",
+            "entity_summary": {}
+          }
+        ]
+      }
+    ]
+  })",
+      {"reading TUSummary from",
+       "failed to deserialize SummaryDataMap at index 0",
+       "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
+       "failed to deserialize EntityDataMap at index 0",
+       "failed to deserialize EntityDataMap entry",
+       "field 'entity_id' is not a valid unsigned 64-bit integer",
+       "(expected non-negative EntityId value)"});
+}
+
+TEST_F(JSONFormatErrorTest, EntitySummaryNoFormatInfo) {
+  expectError(
+      R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "unknown_summary_type",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {}
+          }
+        ]
+      }
+    ]
+  })",
+      {"reading TUSummary from",
+       "failed to deserialize SummaryDataMap at index 0",
+       "failed to deserialize SummaryDataMap entry for summary "
+       "'unknown_summary_type'",
+       "failed to deserialize EntityDataMap at index 0",
+       "failed to deserialize EntityDataMap entry",
+       "failed to deserialize EntitySummary",
+       "no FormatInfo was registered for summary name: unknown_summary_type"});
+}
+
+// ============================================================================
+// Analysis-Specific Error Tests - TestAnalysis
+// ============================================================================
+
+TEST_F(JSONFormatErrorTest, TestAnalysisMissingField) {
+  expectError(
+      R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "test_summary",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {}
+          }
+        ]
+      }
+    ]
+  })",
+      {"reading TUSummary from",
+       "failed to deserialize SummaryDataMap at index 0",
+       "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
+       "failed to deserialize EntityDataMap at index 0",
+       "failed to deserialize EntityDataMap entry",
+       "missing required field 'pairs'"});
+}
+
+TEST_F(JSONFormatErrorTest, TestAnalysisInvalidPair) {
+  expectError(
+      R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "test_summary",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": [
+                {
+                  "first": 0,
+                  "second": "not_a_number"
+                }
+              ]
+            }
+          }
+        ]
+      }
+    ]
+  })",
+      {"reading TUSummary from",
+       "failed to deserialize SummaryDataMap at index 0",
+       "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
+       "failed to deserialize EntityDataMap at index 0",
+       "failed to deserialize EntityDataMap entry",
+       "missing or invalid 'second' field at index 0"});
+}
+
+// ============================================================================
+// Valid Configuration Tests
+// ============================================================================
+
+TEST_F(JSONFormatValidTest, Empty) {
+  expectSuccess(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": []
+  })");
+}
+
+TEST_F(JSONFormatValidTest, LinkUnit) {
+  expectSuccess(R"({
+    "tu_namespace": {
+      "kind": "link_unit",
+      "name": "libtest.so"
+    },
+    "id_table": [],
+    "data": []
+  })");
+}
+
+TEST_F(JSONFormatValidTest, WithIDTable) {
+  expectSuccess(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "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"
+            }
+          ]
+        }
+      }
+    ],
+    "data": []
+  })");
+}
+
+TEST_F(JSONFormatValidTest, WithEmptyDataEntry) {
+  expectSuccess(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "test_summary",
+        "summary_data": []
+      }
+    ]
+  })");
+}
+
+// ============================================================================
+// Round-Trip Tests
+// ============================================================================
+
+TEST_F(JSONFormatRoundTripTest, Empty) {
+  testRoundTrip(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": []
+  })");
+}
+
+TEST_F(JSONFormatRoundTripTest, WithIDTable) {
+  testRoundTrip(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "compilation_unit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      }
+    ],
+    "data": []
+  })");
+}
+
+TEST_F(JSONFormatRoundTripTest, LinkUnit) {
+  testRoundTrip(R"({
+    "tu_namespace": {
+      "kind": "link_unit",
+      "name": "libtest.so"
+    },
+    "id_table": [],
+    "data": []
+  })");
+}
+
+// ============================================================================
+// Analysis-Specific Round-Trip Tests
+// ============================================================================
+
+TEST_F(JSONFormatRoundTripTest, TestAnalysis) {
+  testRoundTrip(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at main",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "compilation_unit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      },
+      {
+        "id": 1,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "compilation_unit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      },
+      {
+        "id": 2,
+        "name": {
+          "usr": "c:@F at bar",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "compilation_unit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      }
+    ],
+    "data": [
+      {
+        "summary_name": "test_summary",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": [
+                {
+                  "first": 0,
+                  "second": 1
+                },
+                {
+                  "first": 1,
+                  "second": 2
+                }
+              ]
+            }
+          }
+        ]
+      }
+    ]
+  })");
+}
+
+} // anonymous namespace
diff --git a/clang/unittests/Analysis/Scalable/tools/CMakeLists.txt b/clang/unittests/Analysis/Scalable/tools/CMakeLists.txt
deleted file mode 100644
index 69e51c6a6aba5..0000000000000
--- a/clang/unittests/Analysis/Scalable/tools/CMakeLists.txt
+++ /dev/null
@@ -1 +0,0 @@
-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
deleted file mode 100644
index ea008bee8d2db..0000000000000
--- a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/CMakeLists.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-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
deleted file mode 100644
index fa93c8d64e9a3..0000000000000
--- a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
+++ /dev/null
@@ -1,338 +0,0 @@
-//===- 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 {
-/// Example analysis that tracks function call relationships.
-///
-/// This analysis builds a call graph where each function (represented by an
-/// EntityId) is mapped to the set of functions it directly calls. This is
-/// useful for understanding control flow and dependencies between functions.
-///
-/// Example structure:
-///   CallGraph[functionA] = {functionB, functionC}
-///   CallGraph[functionB] = {functionD}
-///
-/// This indicates that functionA calls functionB and functionC, and
-/// functionB calls functionD.
-struct CallGraphAnalysis : EntitySummary {
-  CallGraphAnalysis() : EntitySummary(SummaryName("CallGraph")) {}
-
-  /// Maps each caller function (EntityId) to the set of functions it calls.
-  /// Key: Caller function EntityId
-  /// Value: Set of callee function EntityIds
-  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 by value to enable NRVO (Named Return Value Optimization)
-  return 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 {
-/// Example analysis that tracks definition-use chains for variables.
-///
-/// This analysis builds def-use chains showing how variable definitions flow
-/// to their use sites. The structure is a three-level nested map:
-///
-/// Level 1: Variable EntityId
-///   Level 2: Definition EntityId (where the variable is defined)
-///     Level 3: Set of Use EntityIds (where that definition is used)
-///
-/// Example structure:
-///   DefUseChains[varX][def1] = {use1, use2}
-///   DefUseChains[varX][def2] = {use3}
-///   DefUseChains[varY][def3] = {use4, use5, use6}
-///
-/// This indicates that:
-/// - Variable varX has two definitions (def1, def2)
-/// - def1 is used at use1 and use2
-/// - def2 is used at use3
-/// - Variable varY has one definition (def3) used at use4, use5, use6
-struct DefUseAnalysis : EntitySummary {
-  DefUseAnalysis() : EntitySummary(SummaryName("DefUse")) {}
-
-  /// Maps variables to their definition-use chains.
-  /// Key: Variable EntityId
-  /// Value: Map from definition EntityId to set of use EntityIds
-  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 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
deleted file mode 100644
index 2153b1dc4e52e..0000000000000
--- a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-//===- 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;
-  }
-
-  const 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 cd78788efbfb7491991b2e5eb0058ab9ee8ed545 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Sun, 8 Feb 2026 16:19:11 -0800
Subject: [PATCH 17/19] Fix tests

---
 .../Scalable/Serialization/JSONFormat.cpp     |   4 +
 .../Scalable/Serialization/JSONFormatTest.cpp | 774 ++++++++++--------
 2 files changed, 449 insertions(+), 329 deletions(-)

diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index 022f787f09182..889783a8eb6f5 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -217,6 +217,8 @@ constexpr const char *WritingTUSummaryTo = "writing TUSummary to '{0}'";
 // JSON Reader and Writer
 //----------------------------------------------------------------------------
 
+namespace {
+
 llvm::Error isJSONFile(llvm::StringRef Path) {
   if (!llvm::sys::fs::exists(Path))
     return ErrorBuilder(std::errc::no_such_file_or_directory)
@@ -291,6 +293,8 @@ llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
   return llvm::Error::success();
 }
 
+} // namespace
+
 //----------------------------------------------------------------------------
 // JSONFormat Constructor
 //----------------------------------------------------------------------------
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
index 3aec2483766cd..70fb0701b3065 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
@@ -21,6 +21,7 @@
 #include "llvm/Support/Registry.h"
 #include "llvm/Support/VirtualFileSystem.h"
 #include "llvm/Support/raw_ostream.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include <memory>
 #include <string>
@@ -28,9 +29,73 @@
 
 using namespace clang::ssaf;
 using namespace llvm;
+using ::testing::AllOf;
+using ::testing::HasSubstr;
 
 namespace {
 
+// ============================================================================
+// Custom Matchers
+// ============================================================================
+
+// Helper to check if an Error or Expected succeeded
+template <typename T> struct SuccessChecker {
+  static bool isSuccess(T &val) {
+    // For Expected<U>
+    return static_cast<bool>(val);
+  }
+  static std::string getError(T &val) { return toString(val.takeError()); }
+};
+
+// Specialization for Error type
+template <> struct SuccessChecker<Error> {
+  static bool isSuccess(Error &val) {
+    // For Error, success means no error (false/empty)
+    return !static_cast<bool>(val);
+  }
+  static std::string getError(Error &val) { return toString(std::move(val)); }
+};
+
+// Matcher for Expected<T> or Error success
+MATCHER(Succeeded, "") {
+  // Cast away constness to get mutable access
+  auto &mutable_arg =
+      const_cast<std::remove_const_t<std::remove_reference_t<decltype(arg)>> &>(
+          arg);
+
+  using ArgType = std::remove_const_t<std::remove_reference_t<decltype(arg)>>;
+
+  if (!SuccessChecker<ArgType>::isSuccess(mutable_arg)) {
+    *result_listener << "Operation failed with error: "
+                     << SuccessChecker<ArgType>::getError(mutable_arg);
+    return false;
+  }
+  return true;
+}
+
+// Matcher for Expected<T> or Error failure with specific error message
+MATCHER_P(FailedWith, SubstrMatcher, "") {
+  // Cast away constness to get mutable access
+  auto &mutable_arg =
+      const_cast<std::remove_const_t<std::remove_reference_t<decltype(arg)>> &>(
+          arg);
+
+  using ArgType = std::remove_const_t<std::remove_reference_t<decltype(arg)>>;
+
+  if (SuccessChecker<ArgType>::isSuccess(mutable_arg)) {
+    *result_listener << "Expected operation to fail, but it succeeded";
+    return false;
+  }
+
+  std::string ErrorMsg = SuccessChecker<ArgType>::getError(mutable_arg);
+
+  if (!::testing::Matches(SubstrMatcher)(ErrorMsg)) {
+    *result_listener << "Error message was: " << ErrorMsg;
+    return false;
+  }
+  return true;
+}
+
 // ============================================================================
 // Test Analysis - Simple analysis for testing JSON serialization
 // ============================================================================
@@ -95,10 +160,10 @@ static llvm::Registry<JSONFormat::FormatInfo>::Add<TestAnalysisFormatInfo>
     RegisterTestAnalysis("TestAnalysis", "Format info for test analysis data");
 
 // ============================================================================
-// Base Fixture with Common Utilities
+// Test Fixture
 // ============================================================================
 
-class JSONFormatTestBase : public ::testing::Test {
+class JSONFormatTest : public ::testing::Test {
 protected:
   SmallString<128> TestDir;
 
@@ -110,9 +175,7 @@ class JSONFormatTestBase : public ::testing::Test {
 
   void TearDown() override { sys::fs::remove_directories(TestDir); }
 
-  // Helper to create a temporary JSON file and read it using JSONFormat
-  std::pair<JSONFormat, SmallString<128>>
-  readJSON(StringRef JSON, StringRef Filename = "test.json") {
+  auto readJSON(StringRef JSON, StringRef Filename = "test.json") {
     SmallString<128> FilePath = TestDir;
     sys::path::append(FilePath, Filename);
 
@@ -122,103 +185,22 @@ class JSONFormatTestBase : public ::testing::Test {
     OS << JSON;
     OS.close();
 
-    return {JSONFormat(vfs::getRealFileSystem()), FilePath};
-  }
-
-  // Helper to check if error message contains expected substrings
-  bool errorContains(Error &Err, ArrayRef<StringRef> Parts) {
-    std::string ErrorMsg = toString(std::move(Err));
-    for (StringRef Part : Parts) {
-      if (ErrorMsg.find(Part.str()) == std::string::npos)
-        return false;
-    }
-    return true;
-  }
-};
-
-// ============================================================================
-// Fixture for Error Tests
-// ============================================================================
-
-class JSONFormatErrorTest : public JSONFormatTestBase {
-protected:
-  void expectError(StringRef JSON, ArrayRef<StringRef> ErrorParts,
-                   StringRef Filename = "test.json") {
-    auto [Format, FilePath] = createFormat(JSON, Filename);
-    auto Result = Format.readTUSummary(FilePath);
-    ASSERT_FALSE(Result) << "Expected error but read succeeded";
-
-    Error Err = Result.takeError();
-    std::string ErrorMsg = toString(std::move(Err));
-
-    bool allFound = true;
-    for (StringRef Part : ErrorParts) {
-      if (ErrorMsg.find(Part.str()) == std::string::npos) {
-        allFound = false;
-        break;
-      }
-    }
-
-    EXPECT_TRUE(allFound) << "Error message didn't contain expected parts.\n"
-                          << "Actual error: " << ErrorMsg << "\n"
-                          << "Expected parts: [" <<
-        [&]() {
-          std::string result;
-          for (size_t i = 0; i < ErrorParts.size(); ++i) {
-            if (i > 0)
-              result += ", ";
-            result += "\"" + ErrorParts[i].str() + "\"";
-          }
-          return result;
-        }() << "]";
+    return JSONFormat(vfs::getRealFileSystem()).readTUSummary(FilePath);
   }
-};
-
-// ============================================================================
-// Fixture for Valid Configuration Tests
-// ============================================================================
-
-class JSONFormatValidTest : public JSONFormatTestBase {
-protected:
-  void expectSuccess(StringRef JSON, StringRef Filename = "test.json") {
-    auto [Format, FilePath] = createFormat(JSON, Filename);
-    auto Result = Format.readTUSummary(FilePath);
-    if (!Result) {
-      FAIL() << "Read failed: " << toString(Result.takeError());
-    }
-  }
-};
-
-// ============================================================================
-// Fixture for Round-Trip Tests
-// ============================================================================
 
-class JSONFormatRoundTripTest : public JSONFormatTestBase {
-protected:
   void testRoundTrip(StringRef InputJSON) {
-    // Read the input
-    auto [InputFormat, InputPath] = createFormat(InputJSON, "input.json");
-    auto Summary = InputFormat.readTUSummary(InputPath);
-    if (!Summary) {
-      FAIL() << "Failed to read input: " << toString(Summary.takeError());
-    }
-
-    // Write to output file
+    auto Summary = readJSON(InputJSON, "input.json");
+    ASSERT_THAT(Summary, Succeeded());
+
     SmallString<128> OutputPath = TestDir;
     sys::path::append(OutputPath, "output.json");
 
     JSONFormat OutputFormat(vfs::getRealFileSystem());
     auto WriteErr = OutputFormat.writeTUSummary(*Summary, OutputPath);
-    if (WriteErr) {
-      FAIL() << "Failed to write output: " << toString(std::move(WriteErr));
-    }
+    ASSERT_THAT(WriteErr, Succeeded());
 
-    // Read back the written file
     auto RoundTrip = OutputFormat.readTUSummary(OutputPath);
-    if (!RoundTrip) {
-      FAIL() << "Failed to read round-trip output: "
-             << toString(RoundTrip.takeError());
-    }
+    ASSERT_THAT(RoundTrip, Succeeded());
   }
 };
 
@@ -226,144 +208,192 @@ class JSONFormatRoundTripTest : public JSONFormatTestBase {
 // File Access Error Tests
 // ============================================================================
 
-TEST_F(JSONFormatErrorTest, NonexistentFile) {
+TEST_F(JSONFormatTest, NonexistentFile) {
   SmallString<128> NonexistentPath = TestDir;
   sys::path::append(NonexistentPath, "nonexistent.json");
 
   JSONFormat Format(vfs::getRealFileSystem());
   auto Result = Format.readTUSummary(NonexistentPath);
-  ASSERT_FALSE(Result);
 
-  Error Err = Result.takeError();
-  EXPECT_TRUE(
-      errorContains(Err, {"reading TUSummary from", "file does not exist"}));
+  EXPECT_THAT(Result, FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+                                       HasSubstr("file does not exist"))));
+}
+
+TEST_F(JSONFormatTest, PathIsDirectory) {
+  SmallString<128> DirPath = TestDir;
+  sys::path::append(DirPath, "test_directory.json");
+
+  std::error_code EC = sys::fs::create_directory(DirPath);
+  ASSERT_FALSE(EC) << "Failed to create directory: " << EC.message();
+
+  JSONFormat Format(vfs::getRealFileSystem());
+  auto Result = Format.readTUSummary(DirPath);
+
+  EXPECT_THAT(Result,
+              FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+                               HasSubstr("path is a directory, not a file"))));
 }
 
-TEST_F(JSONFormatErrorTest, NotJsonExtension) {
-  expectError("{}", {"reading TUSummary from", "not a JSON file"}, "test.txt");
+TEST_F(JSONFormatTest, NotJsonExtension) {
+  auto Result = readJSON("{}", "test.txt");
+
+  EXPECT_THAT(Result, FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+                                       HasSubstr("not a JSON file"))));
 }
 
 // ============================================================================
 // JSON Syntax Error Tests
 // ============================================================================
 
-TEST_F(JSONFormatErrorTest, InvalidSyntax) {
-  expectError("{ invalid json }", {"reading TUSummary from",
-                                   "failed to read JSON object from file"});
+TEST_F(JSONFormatTest, InvalidSyntax) {
+  auto Result = readJSON("{ invalid json }");
+
+  EXPECT_THAT(Result, FailedWith(AllOf(
+                          HasSubstr("reading TUSummary from"),
+                          HasSubstr("failed to read JSON object from file"))));
 }
 
-TEST_F(JSONFormatErrorTest, NotObject) {
-  expectError(
-      "[]", {"reading TUSummary from", "failed to read JSON object from file"});
+TEST_F(JSONFormatTest, NotObject) {
+  auto Result = readJSON("[]");
+
+  EXPECT_THAT(Result, FailedWith(AllOf(
+                          HasSubstr("reading TUSummary from"),
+                          HasSubstr("failed to read JSON object from file"))));
 }
 
 // ============================================================================
 // Root Structure Error Tests
 // ============================================================================
 
-TEST_F(JSONFormatErrorTest, MissingTUNamespace) {
-  expectError(
-      R"({
+TEST_F(JSONFormatTest, MissingTUNamespace) {
+  auto Result = readJSON(R"({
     "id_table": [],
     "data": []
-  })",
-      {"reading TUSummary from", "missing or invalid field 'tu_namespace'"});
+  })");
+
+  EXPECT_THAT(
+      Result,
+      FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+                       HasSubstr("missing or invalid field 'tu_namespace'"))));
 }
 
-TEST_F(JSONFormatErrorTest, MissingKind) {
-  expectError(R"({
+TEST_F(JSONFormatTest, MissingKind) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "name": "test.cpp"
     },
     "id_table": [],
     "data": []
-  })",
-              {"reading TUSummary from", "failed to deserialize BuildNamespace",
-               "missing required field 'kind' (expected BuildNamespaceKind)"});
+  })");
+
+  EXPECT_THAT(Result, FailedWith(AllOf(
+                          HasSubstr("reading TUSummary from"),
+                          HasSubstr("failed to deserialize BuildNamespace"),
+                          HasSubstr("missing required field 'kind' "
+                                    "(expected BuildNamespaceKind)"))));
 }
 
-TEST_F(JSONFormatErrorTest, MissingName) {
-  expectError(R"({
+TEST_F(JSONFormatTest, MissingName) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit"
     },
     "id_table": [],
     "data": []
-  })",
-              {"reading TUSummary from", "failed to deserialize BuildNamespace",
-               "missing required field 'name'"});
+  })");
+
+  EXPECT_THAT(Result, FailedWith(AllOf(
+                          HasSubstr("reading TUSummary from"),
+                          HasSubstr("failed to deserialize BuildNamespace"),
+                          HasSubstr("missing required field 'name'"))));
 }
 
-TEST_F(JSONFormatErrorTest, InvalidKind) {
-  expectError(R"({
+TEST_F(JSONFormatTest, InvalidKind) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "invalid_kind",
       "name": "test.cpp"
     },
     "id_table": [],
     "data": []
-  })",
-              {"reading TUSummary from", "failed to deserialize BuildNamespace",
-               "invalid 'kind' BuildNamespaceKind value"});
+  })");
+
+  EXPECT_THAT(Result, FailedWith(AllOf(
+                          HasSubstr("reading TUSummary from"),
+                          HasSubstr("failed to deserialize BuildNamespace"),
+                          HasSubstr("invalid 'kind' BuildNamespaceKind "
+                                    "value"))));
 }
 
-TEST_F(JSONFormatErrorTest, MissingIDTable) {
-  expectError(
-      R"({
+TEST_F(JSONFormatTest, MissingIDTable) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
     "data": []
-  })",
-      {"reading TUSummary from", "missing or invalid field 'id_table'"});
+  })");
+
+  EXPECT_THAT(Result, FailedWith(AllOf(
+                          HasSubstr("reading TUSummary from"),
+                          HasSubstr("missing or invalid field 'id_table'"))));
 }
 
-TEST_F(JSONFormatErrorTest, MissingData) {
-  expectError(R"({
+TEST_F(JSONFormatTest, MissingData) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
     "id_table": []
-  })",
-              {"reading TUSummary from", "missing or invalid field 'data'"});
+  })");
+
+  EXPECT_THAT(Result,
+              FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+                               HasSubstr("missing or invalid field 'data'"))));
 }
 
 // ============================================================================
 // ID Table Error Tests
 // ============================================================================
 
-TEST_F(JSONFormatErrorTest, IDTableNotArray) {
-  expectError(
-      R"({
+TEST_F(JSONFormatTest, IDTableNotArray) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
     "id_table": {},
     "data": []
-  })",
-      {"reading TUSummary from", "missing or invalid field 'id_table'"});
+  })");
+
+  EXPECT_THAT(Result, FailedWith(AllOf(
+                          HasSubstr("reading TUSummary from"),
+                          HasSubstr("missing or invalid field 'id_table'"))));
 }
 
-TEST_F(JSONFormatErrorTest, IDTableElementNotObject) {
-  expectError(R"({
+TEST_F(JSONFormatTest, IDTableElementNotObject) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
     "id_table": [123],
     "data": []
-  })",
-              {"reading TUSummary from", "failed to deserialize EntityIdTable",
-               "element at index 0 is not a JSON object",
-               "(expected EntityIdTable entry with 'id' and 'name' fields)"});
+  })");
+
+  EXPECT_THAT(
+      Result,
+      FailedWith(AllOf(
+          HasSubstr("reading TUSummary from"),
+          HasSubstr("failed to deserialize EntityIdTable"),
+          HasSubstr("element at index 0 is not a JSON object"),
+          HasSubstr(
+              "(expected EntityIdTable entry with 'id' and 'name' fields)"))));
 }
 
-TEST_F(JSONFormatErrorTest, IDTableEntryMissingID) {
-  expectError(
-      R"({
+TEST_F(JSONFormatTest, IDTableEntryMissingID) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -378,16 +408,19 @@ TEST_F(JSONFormatErrorTest, IDTableEntryMissingID) {
       }
     ],
     "data": []
-  })",
-      {"reading TUSummary from",
-       "failed to deserialize EntityIdTable at index 0",
-       "failed to deserialize EntityIdTable entry",
-       "missing required field 'id' (expected unsigned integer EntityId)"});
+  })");
+
+  EXPECT_THAT(Result,
+              FailedWith(AllOf(
+                  HasSubstr("reading TUSummary from"),
+                  HasSubstr("failed to deserialize EntityIdTable at index 0"),
+                  HasSubstr("failed to deserialize EntityIdTable entry"),
+                  HasSubstr("missing required field 'id' (expected unsigned "
+                            "integer EntityId)"))));
 }
 
-TEST_F(JSONFormatErrorTest, IDTableEntryMissingName) {
-  expectError(
-      R"({
+TEST_F(JSONFormatTest, IDTableEntryMissingName) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -398,15 +431,20 @@ TEST_F(JSONFormatErrorTest, IDTableEntryMissingName) {
       }
     ],
     "data": []
-  })",
-      {"reading TUSummary from",
-       "failed to deserialize EntityIdTable at index 0",
-       "failed to deserialize EntityIdTable entry",
-       "missing or invalid field 'name' (expected EntityName JSON object)"});
+  })");
+
+  EXPECT_THAT(
+      Result,
+      FailedWith(AllOf(
+          HasSubstr("reading TUSummary from"),
+          HasSubstr("failed to deserialize EntityIdTable at index 0"),
+          HasSubstr("failed to deserialize EntityIdTable entry"),
+          HasSubstr("missing or invalid field 'name' (expected EntityName JSON "
+                    "object)"))));
 }
 
-TEST_F(JSONFormatErrorTest, IDTableEntryIDNotUInt64) {
-  expectError(R"({
+TEST_F(JSONFormatTest, IDTableEntryIDNotUInt64) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -422,16 +460,20 @@ TEST_F(JSONFormatErrorTest, IDTableEntryIDNotUInt64) {
       }
     ],
     "data": []
-  })",
-              {"reading TUSummary from",
-               "failed to deserialize EntityIdTable at index 0",
-               "failed to deserialize EntityIdTable entry",
-               "field 'id' is not a valid unsigned 64-bit integer",
-               "(expected non-negative EntityId value)"});
+  })");
+
+  EXPECT_THAT(
+      Result,
+      FailedWith(
+          AllOf(HasSubstr("reading TUSummary from"),
+                HasSubstr("failed to deserialize EntityIdTable at index 0"),
+                HasSubstr("failed to deserialize EntityIdTable entry"),
+                HasSubstr("field 'id' is not a valid unsigned 64-bit integer"),
+                HasSubstr("(expected non-negative EntityId value)"))));
 }
 
-TEST_F(JSONFormatErrorTest, DuplicateEntity) {
-  expectError(R"({
+TEST_F(JSONFormatTest, DuplicateEntity) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -465,19 +507,21 @@ TEST_F(JSONFormatErrorTest, DuplicateEntity) {
       }
     ],
     "data": []
-  })",
-              {"reading TUSummary from", "failed to deserialize EntityIdTable",
-               "duplicate EntityName found at index",
-               "(EntityId=0 already exists in table)"});
+  })");
+
+  EXPECT_THAT(Result, FailedWith(AllOf(
+                          HasSubstr("reading TUSummary from"),
+                          HasSubstr("failed to deserialize EntityIdTable"),
+                          HasSubstr("duplicate EntityName found at index"),
+                          HasSubstr("(EntityId=0 already exists in table)"))));
 }
 
 // ============================================================================
 // Entity Name Error Tests
 // ============================================================================
 
-TEST_F(JSONFormatErrorTest, EntityNameMissingUSR) {
-  expectError(
-      R"({
+TEST_F(JSONFormatTest, EntityNameMissingUSR) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -492,16 +536,20 @@ TEST_F(JSONFormatErrorTest, EntityNameMissingUSR) {
       }
     ],
     "data": []
-  })",
-      {"reading TUSummary from",
-       "failed to deserialize EntityIdTable at index 0",
-       "failed to deserialize EntityIdTable entry",
-       "failed to deserialize EntityName",
-       "missing required field 'usr' (Unified Symbol Resolution string)"});
+  })");
+
+  EXPECT_THAT(Result,
+              FailedWith(AllOf(
+                  HasSubstr("reading TUSummary from"),
+                  HasSubstr("failed to deserialize EntityIdTable at index 0"),
+                  HasSubstr("failed to deserialize EntityIdTable entry"),
+                  HasSubstr("failed to deserialize EntityName"),
+                  HasSubstr("missing required field 'usr' (Unified Symbol "
+                            "Resolution string)"))));
 }
 
-TEST_F(JSONFormatErrorTest, EntityNameMissingSuffix) {
-  expectError(R"({
+TEST_F(JSONFormatTest, EntityNameMissingSuffix) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -516,16 +564,20 @@ TEST_F(JSONFormatErrorTest, EntityNameMissingSuffix) {
       }
     ],
     "data": []
-  })",
-              {"reading TUSummary from",
-               "failed to deserialize EntityIdTable at index 0",
-               "failed to deserialize EntityIdTable entry",
-               "failed to deserialize EntityName",
-               "missing required field 'suffix'"});
+  })");
+
+  EXPECT_THAT(
+      Result,
+      FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+                       HasSubstr("failed to deserialize EntityIdTable at "
+                                 "index 0"),
+                       HasSubstr("failed to deserialize EntityIdTable entry"),
+                       HasSubstr("failed to deserialize EntityName"),
+                       HasSubstr("missing required field 'suffix'"))));
 }
 
-TEST_F(JSONFormatErrorTest, EntityNameMissingNamespace) {
-  expectError(R"({
+TEST_F(JSONFormatTest, EntityNameMissingNamespace) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -540,17 +592,21 @@ TEST_F(JSONFormatErrorTest, EntityNameMissingNamespace) {
       }
     ],
     "data": []
-  })",
-              {"reading TUSummary from",
-               "failed to deserialize EntityIdTable at index 0",
-               "failed to deserialize EntityIdTable entry",
-               "failed to deserialize EntityName",
-               "missing or invalid field 'namespace'",
-               "(expected JSON array of BuildNamespace objects)"});
+  })");
+
+  EXPECT_THAT(
+      Result,
+      FailedWith(
+          AllOf(HasSubstr("reading TUSummary from"),
+                HasSubstr("failed to deserialize EntityIdTable at index 0"),
+                HasSubstr("failed to deserialize EntityIdTable entry"),
+                HasSubstr("failed to deserialize EntityName"),
+                HasSubstr("missing or invalid field 'namespace'"),
+                HasSubstr("(expected JSON array of BuildNamespace objects)"))));
 }
 
-TEST_F(JSONFormatErrorTest, NamespaceElementNotObject) {
-  expectError(R"({
+TEST_F(JSONFormatTest, NamespaceElementNotObject) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -566,49 +622,58 @@ TEST_F(JSONFormatErrorTest, NamespaceElementNotObject) {
       }
     ],
     "data": []
-  })",
-              {"reading TUSummary from",
-               "failed to deserialize EntityIdTable at index 0",
-               "failed to deserialize EntityIdTable entry",
-               "failed to deserialize EntityName",
-               "failed to deserialize NestedBuildNamespace",
-               "element at index 0 is not a JSON object"});
+  })");
+
+  EXPECT_THAT(Result,
+              FailedWith(AllOf(
+                  HasSubstr("reading TUSummary from"),
+                  HasSubstr("failed to deserialize EntityIdTable at index 0"),
+                  HasSubstr("failed to deserialize EntityIdTable entry"),
+                  HasSubstr("failed to deserialize EntityName"),
+                  HasSubstr("failed to deserialize NestedBuildNamespace"),
+                  HasSubstr("element at index 0 is not a JSON object"))));
 }
 
 // ============================================================================
 // Data Array Error Tests
 // ============================================================================
 
-TEST_F(JSONFormatErrorTest, DataNotArray) {
-  expectError(R"({
+TEST_F(JSONFormatTest, DataNotArray) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
     "id_table": [],
     "data": {}
-  })",
-              {"reading TUSummary from", "missing or invalid field 'data'"});
+  })");
+
+  EXPECT_THAT(Result,
+              FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+                               HasSubstr("missing or invalid field 'data'"))));
 }
 
-TEST_F(JSONFormatErrorTest, DataElementNotObject) {
-  expectError(
-      R"({
+TEST_F(JSONFormatTest, DataElementNotObject) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
     "id_table": [],
     "data": ["invalid"]
-  })",
-      {"reading TUSummary from", "failed to deserialize SummaryDataMap",
-       "element at index 0 is not a JSON object",
-       "(expected SummaryDataMap entry with 'summary_name' and 'summary_data'",
-       "fields)"});
+  })");
+
+  EXPECT_THAT(Result, FailedWith(AllOf(
+                          HasSubstr("reading TUSummary from"),
+                          HasSubstr("failed to deserialize SummaryDataMap"),
+                          HasSubstr("element at index 0 is not a JSON object"),
+                          HasSubstr("(expected SummaryDataMap entry with "
+                                    "'summary_name' and 'summary_data'"),
+                          HasSubstr("fields)"))));
 }
 
-TEST_F(JSONFormatErrorTest, DataEntryMissingSummaryName) {
-  expectError(R"({
+TEST_F(JSONFormatTest, DataEntryMissingSummaryName) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -619,15 +684,19 @@ TEST_F(JSONFormatErrorTest, DataEntryMissingSummaryName) {
         "summary_data": []
       }
     ]
-  })",
-              {"reading TUSummary from",
-               "failed to deserialize SummaryDataMap at index 0",
-               "failed to deserialize SummaryDataMap entry",
-               "missing required field 'summary_name'"});
+  })");
+
+  EXPECT_THAT(
+      Result,
+      FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+                       HasSubstr("failed to deserialize SummaryDataMap at "
+                                 "index 0"),
+                       HasSubstr("failed to deserialize SummaryDataMap entry"),
+                       HasSubstr("missing required field 'summary_name'"))));
 }
 
-TEST_F(JSONFormatErrorTest, DataEntryMissingData) {
-  expectError(R"({
+TEST_F(JSONFormatTest, DataEntryMissingData) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -638,15 +707,18 @@ TEST_F(JSONFormatErrorTest, DataEntryMissingData) {
         "summary_name": "test_summary"
       }
     ]
-  })",
-              {"reading TUSummary from",
-               "failed to deserialize SummaryDataMap at index 0",
-               "failed to deserialize SummaryDataMap entry",
-               "missing or invalid field 'summary_data'"});
+  })");
+
+  EXPECT_THAT(Result,
+              FailedWith(AllOf(
+                  HasSubstr("reading TUSummary from"),
+                  HasSubstr("failed to deserialize SummaryDataMap at index 0"),
+                  HasSubstr("failed to deserialize SummaryDataMap entry"),
+                  HasSubstr("missing or invalid field 'summary_data'"))));
 }
 
-TEST_F(JSONFormatErrorTest, DuplicateSummaryName) {
-  expectError(R"({
+TEST_F(JSONFormatTest, DuplicateSummaryName) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -662,18 +734,21 @@ TEST_F(JSONFormatErrorTest, DuplicateSummaryName) {
         "summary_data": []
       }
     ]
-  })",
-              {"reading TUSummary from", "failed to deserialize SummaryDataMap",
-               "duplicate SummaryName 'test_summary' found at index"});
+  })");
+
+  EXPECT_THAT(Result, FailedWith(AllOf(
+                          HasSubstr("reading TUSummary from"),
+                          HasSubstr("failed to deserialize SummaryDataMap"),
+                          HasSubstr("duplicate SummaryName 'test_summary' "
+                                    "found at index"))));
 }
 
 // ============================================================================
 // Entity Data Error Tests
 // ============================================================================
 
-TEST_F(JSONFormatErrorTest, EntityDataElementNotObject) {
-  expectError(
-      R"({
+TEST_F(JSONFormatTest, EntityDataElementNotObject) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -685,19 +760,24 @@ TEST_F(JSONFormatErrorTest, EntityDataElementNotObject) {
         "summary_data": ["invalid"]
       }
     ]
-  })",
-      {"reading TUSummary from",
-       "failed to deserialize SummaryDataMap at index 0",
-       "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
-       "failed to deserialize EntityDataMap",
-       "element at index 0 is not a JSON object",
-       "(expected EntityDataMap entry with 'entity_id' and 'entity_summary'",
-       "fields)"});
+  })");
+
+  EXPECT_THAT(
+      Result,
+      FailedWith(AllOf(
+          HasSubstr("reading TUSummary from"),
+          HasSubstr("failed to deserialize SummaryDataMap at index 0"),
+          HasSubstr("failed to deserialize SummaryDataMap entry for summary "
+                    "'test_summary'"),
+          HasSubstr("failed to deserialize EntityDataMap"),
+          HasSubstr("element at index 0 is not a JSON object"),
+          HasSubstr("(expected EntityDataMap entry with 'entity_id' and "
+                    "'entity_summary'"),
+          HasSubstr("fields)"))));
 }
 
-TEST_F(JSONFormatErrorTest, EntityDataMissingEntityID) {
-  expectError(
-      R"({
+TEST_F(JSONFormatTest, EntityDataMissingEntityID) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -713,19 +793,23 @@ TEST_F(JSONFormatErrorTest, EntityDataMissingEntityID) {
         ]
       }
     ]
-  })",
-      {"reading TUSummary from",
-       "failed to deserialize SummaryDataMap at index 0",
-       "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
-       "failed to deserialize EntityDataMap at index 0",
-       "failed to deserialize EntityDataMap entry",
-       "missing required field 'entity_id' (expected unsigned integer "
-       "EntityId)"});
+  })");
+
+  EXPECT_THAT(
+      Result,
+      FailedWith(AllOf(
+          HasSubstr("reading TUSummary from"),
+          HasSubstr("failed to deserialize SummaryDataMap at index 0"),
+          HasSubstr("failed to deserialize SummaryDataMap entry for summary "
+                    "'test_summary'"),
+          HasSubstr("failed to deserialize EntityDataMap at index 0"),
+          HasSubstr("failed to deserialize EntityDataMap entry"),
+          HasSubstr("missing required field 'entity_id' (expected unsigned "
+                    "integer EntityId)"))));
 }
 
-TEST_F(JSONFormatErrorTest, EntityDataMissingEntitySummary) {
-  expectError(
-      R"({
+TEST_F(JSONFormatTest, EntityDataMissingEntitySummary) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -741,19 +825,23 @@ TEST_F(JSONFormatErrorTest, EntityDataMissingEntitySummary) {
         ]
       }
     ]
-  })",
-      {"reading TUSummary from",
-       "failed to deserialize SummaryDataMap at index 0",
-       "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
-       "failed to deserialize EntityDataMap at index 0",
-       "failed to deserialize EntityDataMap entry",
-       "missing or invalid field 'entity_summary'",
-       "(expected EntitySummary JSON object)"});
+  })");
+
+  EXPECT_THAT(
+      Result,
+      FailedWith(AllOf(
+          HasSubstr("reading TUSummary from"),
+          HasSubstr("failed to deserialize SummaryDataMap at index 0"),
+          HasSubstr("failed to deserialize SummaryDataMap entry for summary "
+                    "'test_summary'"),
+          HasSubstr("failed to deserialize EntityDataMap at index 0"),
+          HasSubstr("failed to deserialize EntityDataMap entry"),
+          HasSubstr("missing or invalid field 'entity_summary'"),
+          HasSubstr("(expected EntitySummary JSON object)"))));
 }
 
-TEST_F(JSONFormatErrorTest, EntityIDNotUInt64) {
-  expectError(
-      R"({
+TEST_F(JSONFormatTest, EntityIDNotUInt64) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -770,19 +858,23 @@ TEST_F(JSONFormatErrorTest, EntityIDNotUInt64) {
         ]
       }
     ]
-  })",
-      {"reading TUSummary from",
-       "failed to deserialize SummaryDataMap at index 0",
-       "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
-       "failed to deserialize EntityDataMap at index 0",
-       "failed to deserialize EntityDataMap entry",
-       "field 'entity_id' is not a valid unsigned 64-bit integer",
-       "(expected non-negative EntityId value)"});
+  })");
+
+  EXPECT_THAT(
+      Result,
+      FailedWith(AllOf(
+          HasSubstr("reading TUSummary from"),
+          HasSubstr("failed to deserialize SummaryDataMap at index 0"),
+          HasSubstr("failed to deserialize SummaryDataMap entry for summary "
+                    "'test_summary'"),
+          HasSubstr("failed to deserialize EntityDataMap at index 0"),
+          HasSubstr("failed to deserialize EntityDataMap entry"),
+          HasSubstr("field 'entity_id' is not a valid unsigned 64-bit integer"),
+          HasSubstr("(expected non-negative EntityId value)"))));
 }
 
-TEST_F(JSONFormatErrorTest, EntitySummaryNoFormatInfo) {
-  expectError(
-      R"({
+TEST_F(JSONFormatTest, EntitySummaryNoFormatInfo) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -799,24 +891,28 @@ TEST_F(JSONFormatErrorTest, EntitySummaryNoFormatInfo) {
         ]
       }
     ]
-  })",
-      {"reading TUSummary from",
-       "failed to deserialize SummaryDataMap at index 0",
-       "failed to deserialize SummaryDataMap entry for summary "
-       "'unknown_summary_type'",
-       "failed to deserialize EntityDataMap at index 0",
-       "failed to deserialize EntityDataMap entry",
-       "failed to deserialize EntitySummary",
-       "no FormatInfo was registered for summary name: unknown_summary_type"});
+  })");
+
+  EXPECT_THAT(
+      Result,
+      FailedWith(AllOf(
+          HasSubstr("reading TUSummary from"),
+          HasSubstr("failed to deserialize SummaryDataMap at index 0"),
+          HasSubstr("failed to deserialize SummaryDataMap entry for summary "
+                    "'unknown_summary_type'"),
+          HasSubstr("failed to deserialize EntityDataMap at index 0"),
+          HasSubstr("failed to deserialize EntityDataMap entry"),
+          HasSubstr("failed to deserialize EntitySummary"),
+          HasSubstr("no FormatInfo was registered for summary name: "
+                    "unknown_summary_type"))));
 }
 
 // ============================================================================
 // Analysis-Specific Error Tests - TestAnalysis
 // ============================================================================
 
-TEST_F(JSONFormatErrorTest, TestAnalysisMissingField) {
-  expectError(
-      R"({
+TEST_F(JSONFormatTest, TestAnalysisMissingField) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -833,18 +929,23 @@ TEST_F(JSONFormatErrorTest, TestAnalysisMissingField) {
         ]
       }
     ]
-  })",
-      {"reading TUSummary from",
-       "failed to deserialize SummaryDataMap at index 0",
-       "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
-       "failed to deserialize EntityDataMap at index 0",
-       "failed to deserialize EntityDataMap entry",
-       "missing required field 'pairs'"});
+  })");
+
+  EXPECT_THAT(
+      Result,
+      FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+                       HasSubstr("failed to deserialize SummaryDataMap at "
+                                 "index 0"),
+                       HasSubstr("failed to deserialize SummaryDataMap entry "
+                                 "for summary 'test_summary'"),
+                       HasSubstr("failed to deserialize EntityDataMap at "
+                                 "index 0"),
+                       HasSubstr("failed to deserialize EntityDataMap entry"),
+                       HasSubstr("missing required field 'pairs'"))));
 }
 
-TEST_F(JSONFormatErrorTest, TestAnalysisInvalidPair) {
-  expectError(
-      R"({
+TEST_F(JSONFormatTest, TestAnalysisInvalidPair) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -868,21 +969,28 @@ TEST_F(JSONFormatErrorTest, TestAnalysisInvalidPair) {
         ]
       }
     ]
-  })",
-      {"reading TUSummary from",
-       "failed to deserialize SummaryDataMap at index 0",
-       "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
-       "failed to deserialize EntityDataMap at index 0",
-       "failed to deserialize EntityDataMap entry",
-       "missing or invalid 'second' field at index 0"});
+  })");
+
+  EXPECT_THAT(
+      Result,
+      FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+                       HasSubstr("failed to deserialize SummaryDataMap at "
+                                 "index 0"),
+                       HasSubstr("failed to deserialize SummaryDataMap entry "
+                                 "for summary 'test_summary'"),
+                       HasSubstr("failed to deserialize EntityDataMap at "
+                                 "index 0"),
+                       HasSubstr("failed to deserialize EntityDataMap entry"),
+                       HasSubstr("missing or invalid 'second' field at "
+                                 "index 0"))));
 }
 
 // ============================================================================
 // Valid Configuration Tests
 // ============================================================================
 
-TEST_F(JSONFormatValidTest, Empty) {
-  expectSuccess(R"({
+TEST_F(JSONFormatTest, Empty) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -890,10 +998,12 @@ TEST_F(JSONFormatValidTest, Empty) {
     "id_table": [],
     "data": []
   })");
+
+  EXPECT_THAT(Result, Succeeded());
 }
 
-TEST_F(JSONFormatValidTest, LinkUnit) {
-  expectSuccess(R"({
+TEST_F(JSONFormatTest, LinkUnit) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "link_unit",
       "name": "libtest.so"
@@ -901,10 +1011,12 @@ TEST_F(JSONFormatValidTest, LinkUnit) {
     "id_table": [],
     "data": []
   })");
+
+  EXPECT_THAT(Result, Succeeded());
 }
 
-TEST_F(JSONFormatValidTest, WithIDTable) {
-  expectSuccess(R"({
+TEST_F(JSONFormatTest, WithIDTable) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -943,10 +1055,12 @@ TEST_F(JSONFormatValidTest, WithIDTable) {
     ],
     "data": []
   })");
+
+  EXPECT_THAT(Result, Succeeded());
 }
 
-TEST_F(JSONFormatValidTest, WithEmptyDataEntry) {
-  expectSuccess(R"({
+TEST_F(JSONFormatTest, WithEmptyDataEntry) {
+  auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -959,13 +1073,15 @@ TEST_F(JSONFormatValidTest, WithEmptyDataEntry) {
       }
     ]
   })");
+
+  EXPECT_THAT(Result, Succeeded());
 }
 
 // ============================================================================
 // Round-Trip Tests
 // ============================================================================
 
-TEST_F(JSONFormatRoundTripTest, Empty) {
+TEST_F(JSONFormatTest, RoundTripEmpty) {
   testRoundTrip(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
@@ -976,7 +1092,7 @@ TEST_F(JSONFormatRoundTripTest, Empty) {
   })");
 }
 
-TEST_F(JSONFormatRoundTripTest, WithIDTable) {
+TEST_F(JSONFormatTest, RoundTripWithIDTable) {
   testRoundTrip(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
@@ -1001,7 +1117,7 @@ TEST_F(JSONFormatRoundTripTest, WithIDTable) {
   })");
 }
 
-TEST_F(JSONFormatRoundTripTest, LinkUnit) {
+TEST_F(JSONFormatTest, RoundTripLinkUnit) {
   testRoundTrip(R"({
     "tu_namespace": {
       "kind": "link_unit",
@@ -1016,7 +1132,7 @@ TEST_F(JSONFormatRoundTripTest, LinkUnit) {
 // Analysis-Specific Round-Trip Tests
 // ============================================================================
 
-TEST_F(JSONFormatRoundTripTest, TestAnalysis) {
+TEST_F(JSONFormatTest, RoundTripTestAnalysis) {
   testRoundTrip(R"({
     "tu_namespace": {
       "kind": "compilation_unit",

>From 9d7712501c55d7764f29228aaff7ec990e7dc9a9 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Mon, 9 Feb 2026 10:21:03 -0800
Subject: [PATCH 18/19] Fix error message templates and add new tests

---
 .../Scalable/Serialization/JSONFormat.cpp     | 264 ++++++++----------
 .../Scalable/Serialization/JSONFormatTest.cpp | 219 +++++++++++----
 2 files changed, 279 insertions(+), 204 deletions(-)

diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index 889783a8eb6f5..de4003498195a 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -81,133 +81,41 @@ constexpr const char *FailedToValidateJSONFile =
 constexpr const char *FailedToReadFile = "failed to read file '{0}'";
 constexpr const char *FailedToReadJSONObject =
     "failed to read JSON object from file '{0}'";
+constexpr const char *FailedToReadJSONArray =
+    "failed to read JSON array from field '{0}'";
+constexpr const char *FailedToReadJSONObjectField =
+    "failed to read JSON object from field '{0}'";
 constexpr const char *FailedToOpenFile = "failed to open '{0}'";
 constexpr const char *WriteFailed = "write failed";
 
-// BuildNamespace errors
+// Generic deserialization error templates
+constexpr const char *FailedToDeserialize = "failed to deserialize {0}";
+constexpr const char *AtIndex = "at index {0}";
+constexpr const char *ForSummary = "for summary '{0}'";
+
+// Specific error details (to be stacked with FailedToDeserialize)
+constexpr const char *MissingOrInvalidField =
+    "missing or invalid field '{0}' (expected {1})";
+constexpr const char *ElementNotObject =
+    "element at index {0} is not a JSON object (expected {1})";
+constexpr const char *InvalidUInt64Field =
+    "field '{0}' is not a valid unsigned 64-bit integer (expected "
+    "non-negative EntityId value)";
+constexpr const char *DuplicateWithExistingId =
+    "duplicate {0} found at index {1} (EntityId={2} already exists in table)";
+constexpr const char *DuplicateEntityIdAtIndex =
+    "duplicate EntityId ({0}) found at index {1}";
+constexpr const char *DuplicateAtIndex =
+    "duplicate {0} '{1}' found at index {2}";
+
+// Special cases
 constexpr const char *InvalidBuildNamespaceKind =
     "invalid 'kind' BuildNamespaceKind value '{0}'";
-constexpr const char *MissingBuildNamespaceKind =
-    "failed to deserialize BuildNamespace: "
-    "missing required field 'kind' (expected BuildNamespaceKind)";
-constexpr const char *MissingBuildNamespaceName =
-    "failed to deserialize BuildNamespace: "
-    "missing required field 'name'";
-constexpr const char *FailedToDeserializeBuildNamespace =
-    "failed to deserialize BuildNamespace";
-
-// NestedBuildNamespace errors
-constexpr const char *NestedBuildNamespaceElementNotObject =
-    "failed to deserialize NestedBuildNamespace: "
-    "element at index {0} is not a JSON object "
-    "(expected BuildNamespace object)";
-constexpr const char *FailedToDeserializeNestedBuildNamespace =
-    "failed to deserialize NestedBuildNamespace at index {0}";
-
-// EntityName errors
-constexpr const char *MissingEntityNameUSR =
-    "failed to deserialize EntityName: "
-    "missing required field 'usr' (Unified Symbol Resolution string)";
-constexpr const char *MissingEntityNameSuffix =
-    "failed to deserialize EntityName: "
-    "missing required field 'suffix'";
-constexpr const char *MissingEntityNameNamespace =
-    "failed to deserialize EntityName: "
-    "missing or invalid field 'namespace' "
-    "(expected JSON array of BuildNamespace objects)";
-constexpr const char *FailedToDeserializeEntityName =
-    "failed to deserialize EntityName";
-
-// EntityIdTable entry errors
-constexpr const char *MissingEntityIdTableEntryName =
-    "failed to deserialize EntityIdTable entry: "
-    "missing or invalid field 'name' (expected EntityName JSON object)";
-constexpr const char *MissingEntityIdTableEntryId =
-    "failed to deserialize EntityIdTable entry: "
-    "missing required field 'id' (expected unsigned integer EntityId)";
-constexpr const char *InvalidEntityIdTableEntryId =
-    "failed to deserialize EntityIdTable entry: "
-    "field 'id' is not a valid unsigned 64-bit integer "
-    "(expected non-negative EntityId value)";
-constexpr const char *FailedToDeserializeEntityIdTableEntry =
-    "failed to deserialize EntityIdTable entry";
-
-// EntityIdTable errors
-constexpr const char *EntityIdTableElementNotObject =
-    "failed to deserialize EntityIdTable: "
-    "element at index {0} is not a JSON object "
-    "(expected EntityIdTable entry with 'id' and 'name' fields)";
-constexpr const char *FailedToDeserializeEntityIdTable =
-    "failed to deserialize EntityIdTable at index {0}";
-constexpr const char *DuplicateEntityName =
-    "failed to deserialize EntityIdTable: "
-    "duplicate EntityName found at index {0} "
-    "(EntityId={1} already exists in table)";
-
-// EntitySummary errors
-constexpr const char *NoFormatInfoForSummary =
-    "failed to deserialize EntitySummary: "
+constexpr const char *NoFormatInfoForSummaryName =
     "no FormatInfo was registered for summary name: {0}";
 
-// EntityDataMap entry errors
-constexpr const char *MissingEntityDataMapEntryEntityId =
-    "failed to deserialize EntityDataMap entry: "
-    "missing required field 'entity_id' (expected unsigned integer EntityId)";
-constexpr const char *InvalidEntityDataMapEntryEntityId =
-    "failed to deserialize EntityDataMap entry: "
-    "field 'entity_id' is not a valid unsigned 64-bit integer "
-    "(expected non-negative EntityId value)";
-constexpr const char *MissingEntityDataMapEntryEntitySummary =
-    "failed to deserialize EntityDataMap entry: "
-    "missing or invalid field 'entity_summary' "
-    "(expected EntitySummary JSON object)";
-constexpr const char *FailedToDeserializeEntityDataMapEntry =
-    "failed to deserialize EntityDataMap entry";
-
-// EntityDataMap errors
-constexpr const char *EntityDataMapElementNotObject =
-    "failed to deserialize EntityDataMap: "
-    "element at index {0} is not a JSON object "
-    "(expected EntityDataMap entry with 'entity_id' and 'entity_summary' "
-    "fields)";
-constexpr const char *FailedToDeserializeEntityDataMap =
-    "failed to deserialize EntityDataMap at index {0}";
-constexpr const char *DuplicateEntityId =
-    "failed to deserialize EntityDataMap: "
-    "duplicate EntityId ({0}) found at index {1}";
-
-// SummaryDataMap entry errors
-constexpr const char *MissingSummaryDataMapEntrySummaryName =
-    "failed to deserialize SummaryDataMap entry: "
-    "missing required field 'summary_name' "
-    "(expected string identifier for the analysis summary)";
-constexpr const char *MissingSummaryDataMapEntrySummaryData =
-    "failed to deserialize SummaryDataMap entry: "
-    "missing or invalid field 'summary_data' "
-    "(expected JSON array of entity data entries)";
-constexpr const char *FailedToDeserializeSummaryDataMapEntry =
-    "failed to deserialize SummaryDataMap entry for summary '{0}'";
-
-// SummaryDataMap errors
-constexpr const char *SummaryDataMapElementNotObject =
-    "failed to deserialize SummaryDataMap: "
-    "element at index {0} is not a JSON object "
-    "(expected SummaryDataMap entry with 'summary_name' and 'summary_data' "
-    "fields)";
-constexpr const char *FailedToDeserializeSummaryDataMap =
-    "failed to deserialize SummaryDataMap at index {0}";
-constexpr const char *DuplicateSummaryName =
-    "failed to deserialize SummaryDataMap: "
-    "duplicate SummaryName '{0}' found at index {1}";
-
-// TUSummary errors
+// Context messages
 constexpr const char *ReadingTUSummaryFrom = "reading TUSummary from '{0}'";
-constexpr const char *MissingTUNamespace =
-    "missing or invalid field 'tu_namespace' (expected JSON object)";
-constexpr const char *MissingIdTable =
-    "missing or invalid field 'id_table' (expected JSON array)";
-constexpr const char *MissingData =
-    "missing or invalid field 'data' (expected JSON array)";
 constexpr const char *WritingTUSummaryTo = "writing TUSummary to '{0}'";
 } // namespace ErrorMessages
 
@@ -372,21 +280,25 @@ llvm::Expected<BuildNamespace> JSONFormat::buildNamespaceFromJSON(
   auto OptBuildNamespaceKindStr = BuildNamespaceObject.getString("kind");
   if (!OptBuildNamespaceKindStr) {
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::MissingBuildNamespaceKind)
+        .context(ErrorMessages::FailedToDeserialize, "BuildNamespace")
+        .context(ErrorMessages::MissingOrInvalidField, "kind",
+                 "BuildNamespaceKind")
         .build();
   }
 
   auto ExpectedKind = buildNamespaceKindFromJSON(*OptBuildNamespaceKindStr);
   if (!ExpectedKind)
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserializeBuildNamespace)
+        .context(ErrorMessages::FailedToDeserialize, "BuildNamespace")
+        .context("while parsing field 'kind'")
         .cause(ExpectedKind.takeError())
         .build();
 
   auto OptNameStr = BuildNamespaceObject.getString("name");
   if (!OptNameStr) {
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::MissingBuildNamespaceName)
+        .context(ErrorMessages::FailedToDeserialize, "BuildNamespace")
+        .context(ErrorMessages::MissingOrInvalidField, "name", "string")
         .build();
   }
 
@@ -419,15 +331,17 @@ llvm::Expected<NestedBuildNamespace> JSONFormat::nestedBuildNamespaceFromJSON(
         BuildNamespaceValue.getAsObject();
     if (!BuildNamespaceObject) {
       return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::NestedBuildNamespaceElementNotObject, Index)
+          .context(ErrorMessages::FailedToDeserialize, "NestedBuildNamespace")
+          .context(ErrorMessages::ElementNotObject, Index,
+                   "BuildNamespace object")
           .build();
     }
 
     auto ExpectedBuildNamespace = buildNamespaceFromJSON(*BuildNamespaceObject);
     if (!ExpectedBuildNamespace)
       return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::FailedToDeserializeNestedBuildNamespace,
-                   Index)
+          .context(ErrorMessages::FailedToDeserialize, "NestedBuildNamespace")
+          .context(ErrorMessages::AtIndex, Index)
           .cause(ExpectedBuildNamespace.takeError())
           .build();
 
@@ -459,14 +373,17 @@ llvm::Expected<EntityName> JSONFormat::entityNameFromJSON(
   const auto OptUSR = EntityNameObject.getString("usr");
   if (!OptUSR) {
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::MissingEntityNameUSR)
+        .context(ErrorMessages::FailedToDeserialize, "EntityName")
+        .context(ErrorMessages::MissingOrInvalidField, "usr",
+                 "string (Unified Symbol Resolution)")
         .build();
   }
 
   const auto OptSuffix = EntityNameObject.getString("suffix");
   if (!OptSuffix) {
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::MissingEntityNameSuffix)
+        .context(ErrorMessages::FailedToDeserialize, "EntityName")
+        .context(ErrorMessages::MissingOrInvalidField, "suffix", "string")
         .build();
   }
 
@@ -474,14 +391,17 @@ llvm::Expected<EntityName> JSONFormat::entityNameFromJSON(
       EntityNameObject.getArray("namespace");
   if (!OptNamespaceArray) {
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::MissingEntityNameNamespace)
+        .context(ErrorMessages::FailedToDeserialize, "EntityName")
+        .context(ErrorMessages::MissingOrInvalidField, "namespace",
+                 "JSON array of BuildNamespace objects")
         .build();
   }
 
   auto ExpectedNamespace = nestedBuildNamespaceFromJSON(*OptNamespaceArray);
   if (!ExpectedNamespace)
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserializeEntityName)
+        .context(ErrorMessages::FailedToDeserialize, "EntityName")
+        .context(ErrorMessages::FailedToReadJSONArray, "namespace")
         .cause(ExpectedNamespace.takeError())
         .build();
 
@@ -508,14 +428,17 @@ JSONFormat::entityIdTableEntryFromJSON(
       EntityIdTableEntryObject.getObject("name");
   if (!OptEntityNameObject) {
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::MissingEntityIdTableEntryName)
+        .context(ErrorMessages::FailedToDeserialize, "EntityIdTable entry")
+        .context(ErrorMessages::MissingOrInvalidField, "name",
+                 "EntityName JSON object")
         .build();
   }
 
   auto ExpectedEntityName = entityNameFromJSON(*OptEntityNameObject);
   if (!ExpectedEntityName)
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserializeEntityIdTableEntry)
+        .context(ErrorMessages::FailedToDeserialize, "EntityIdTable entry")
+        .context(ErrorMessages::FailedToReadJSONObjectField, "name")
         .cause(ExpectedEntityName.takeError())
         .build();
 
@@ -523,7 +446,9 @@ JSONFormat::entityIdTableEntryFromJSON(
       EntityIdTableEntryObject.get("id");
   if (!EntityIdIntValue) {
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::MissingEntityIdTableEntryId)
+        .context(ErrorMessages::FailedToDeserialize, "EntityIdTable entry")
+        .context(ErrorMessages::MissingOrInvalidField, "id",
+                 "unsigned integer EntityId")
         .build();
   }
 
@@ -531,7 +456,8 @@ JSONFormat::entityIdTableEntryFromJSON(
       EntityIdIntValue->getAsUINT64();
   if (!OptEntityIdInt) {
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::InvalidEntityIdTableEntryId)
+        .context(ErrorMessages::FailedToDeserialize, "EntityIdTable entry")
+        .context(ErrorMessages::InvalidUInt64Field, "id")
         .build();
   }
 
@@ -566,7 +492,9 @@ llvm::Expected<EntityIdTable> JSONFormat::entityIdTableFromJSON(
 
     if (!OptEntityIdTableEntryObject) {
       return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::EntityIdTableElementNotObject, Index)
+          .context(ErrorMessages::FailedToDeserialize, "EntityIdTable")
+          .context(ErrorMessages::ElementNotObject, Index,
+                   "EntityIdTable entry with 'id' and 'name' fields")
           .build();
     }
 
@@ -574,7 +502,8 @@ llvm::Expected<EntityIdTable> JSONFormat::entityIdTableFromJSON(
         entityIdTableEntryFromJSON(*OptEntityIdTableEntryObject);
     if (!ExpectedEntityIdTableEntry)
       return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::FailedToDeserializeEntityIdTable, Index)
+          .context(ErrorMessages::FailedToDeserialize, "EntityIdTable")
+          .context(ErrorMessages::AtIndex, Index)
           .cause(ExpectedEntityIdTableEntry.takeError())
           .build();
 
@@ -582,7 +511,8 @@ llvm::Expected<EntityIdTable> JSONFormat::entityIdTableFromJSON(
         Entities.emplace(std::move(*ExpectedEntityIdTableEntry));
     if (!EntityInserted) {
       return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::DuplicateEntityName, Index,
+          .context(ErrorMessages::FailedToDeserialize, "EntityIdTable")
+          .context(ErrorMessages::DuplicateWithExistingId, "EntityName", Index,
                    getEntityIdIndex(EntityIt->second))
           .build();
     }
@@ -616,7 +546,8 @@ JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
   auto InfoIt = FormatInfos.find(SN);
   if (InfoIt == FormatInfos.end()) {
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::NoFormatInfoForSummary, SN.str().data())
+        .context(ErrorMessages::FailedToDeserialize, "EntitySummary")
+        .context(ErrorMessages::NoFormatInfoForSummaryName, SN.str().data())
         .build();
   }
   const auto &InfoEntry = InfoIt->second;
@@ -656,7 +587,9 @@ JSONFormat::entityDataMapEntryFromJSON(
       EntityDataMapEntryObject.get("entity_id");
   if (!EntityIdIntValue) {
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::MissingEntityDataMapEntryEntityId)
+        .context(ErrorMessages::FailedToDeserialize, "EntityDataMap entry")
+        .context(ErrorMessages::MissingOrInvalidField, "entity_id",
+                 "unsigned integer EntityId")
         .build();
   }
 
@@ -664,7 +597,8 @@ JSONFormat::entityDataMapEntryFromJSON(
       EntityIdIntValue->getAsUINT64();
   if (!OptEntityIdInt) {
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::InvalidEntityDataMapEntryEntityId)
+        .context(ErrorMessages::FailedToDeserialize, "EntityDataMap entry")
+        .context(ErrorMessages::InvalidUInt64Field, "entity_id")
         .build();
   }
 
@@ -674,7 +608,9 @@ JSONFormat::entityDataMapEntryFromJSON(
       EntityDataMapEntryObject.getObject("entity_summary");
   if (!OptEntitySummaryObject) {
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::MissingEntityDataMapEntryEntitySummary)
+        .context(ErrorMessages::FailedToDeserialize, "EntityDataMap entry")
+        .context(ErrorMessages::MissingOrInvalidField, "entity_summary",
+                 "EntitySummary JSON object")
         .build();
   }
 
@@ -682,7 +618,8 @@ JSONFormat::entityDataMapEntryFromJSON(
       entitySummaryFromJSON(SN, *OptEntitySummaryObject, IdTable);
   if (!ExpectedEntitySummary)
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserializeEntityDataMapEntry)
+        .context(ErrorMessages::FailedToDeserialize, "EntityDataMap entry")
+        .context(ErrorMessages::FailedToReadJSONObjectField, "entity_summary")
         .cause(ExpectedEntitySummary.takeError())
         .build();
 
@@ -706,7 +643,10 @@ JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
         EntityDataMapEntryValue.getAsObject();
     if (!OptEntityDataMapEntryObject) {
       return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::EntityDataMapElementNotObject, Index)
+          .context(ErrorMessages::FailedToDeserialize, "EntityDataMap")
+          .context(ErrorMessages::ElementNotObject, Index,
+                   "EntityDataMap entry with 'entity_id' and 'entity_summary' "
+                   "fields")
           .build();
     }
 
@@ -714,7 +654,8 @@ JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
         entityDataMapEntryFromJSON(*OptEntityDataMapEntryObject, SN, IdTable);
     if (!ExpectedEntityDataMapEntry)
       return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::FailedToDeserializeEntityDataMap, Index)
+          .context(ErrorMessages::FailedToDeserialize, "EntityDataMap")
+          .context(ErrorMessages::AtIndex, Index)
           .cause(ExpectedEntityDataMapEntry.takeError())
           .build();
 
@@ -722,7 +663,8 @@ JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
         EntityDataMap.insert(std::move(*ExpectedEntityDataMapEntry));
     if (!DataInserted) {
       return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::DuplicateEntityId,
+          .context(ErrorMessages::FailedToDeserialize, "EntityDataMap")
+          .context(ErrorMessages::DuplicateEntityIdAtIndex,
                    getEntityIdIndex(DataIt->first), Index)
           .build();
     }
@@ -761,7 +703,9 @@ JSONFormat::summaryDataMapEntryFromJSON(
 
   if (!OptSummaryNameStr) {
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::MissingSummaryDataMapEntrySummaryName)
+        .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap entry")
+        .context(ErrorMessages::MissingOrInvalidField, "summary_name",
+                 "string (analysis summary identifier)")
         .build();
   }
 
@@ -771,7 +715,9 @@ JSONFormat::summaryDataMapEntryFromJSON(
       SummaryDataMapEntryObject.getArray("summary_data");
   if (!OptEntityDataArray) {
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::MissingSummaryDataMapEntrySummaryData)
+        .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap entry")
+        .context(ErrorMessages::MissingOrInvalidField, "summary_data",
+                 "JSON array of entity data entries")
         .build();
   }
 
@@ -779,8 +725,9 @@ JSONFormat::summaryDataMapEntryFromJSON(
       entityDataMapFromJSON(SN, *OptEntityDataArray, IdTable);
   if (!ExpectedEntityDataMap)
     return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserializeSummaryDataMapEntry,
-                 SN.str().data())
+        .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap entry")
+        .context(ErrorMessages::ForSummary, SN.str().data())
+        .context(ErrorMessages::FailedToReadJSONArray, "summary_data")
         .cause(ExpectedEntityDataMap.takeError())
         .build();
 
@@ -814,7 +761,10 @@ JSONFormat::summaryDataMapFromJSON(const llvm::json::Array &SummaryDataArray,
         SummaryDataMapEntryValue.getAsObject();
     if (!OptSummaryDataMapEntryObject) {
       return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::SummaryDataMapElementNotObject, Index)
+          .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap")
+          .context(ErrorMessages::ElementNotObject, Index,
+                   "SummaryDataMap entry with 'summary_name' and "
+                   "'summary_data' fields")
           .build();
     }
 
@@ -822,7 +772,8 @@ JSONFormat::summaryDataMapFromJSON(const llvm::json::Array &SummaryDataArray,
         summaryDataMapEntryFromJSON(*OptSummaryDataMapEntryObject, IdTable);
     if (!ExpectedSummaryDataMapEntry)
       return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::FailedToDeserializeSummaryDataMap, Index)
+          .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap")
+          .context(ErrorMessages::AtIndex, Index)
           .cause(ExpectedSummaryDataMapEntry.takeError())
           .build();
 
@@ -830,7 +781,8 @@ JSONFormat::summaryDataMapFromJSON(const llvm::json::Array &SummaryDataArray,
         SummaryDataMap.emplace(std::move(*ExpectedSummaryDataMapEntry));
     if (!SummaryInserted) {
       return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::DuplicateSummaryName,
+          .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap")
+          .context(ErrorMessages::DuplicateAtIndex, "SummaryName",
                    SummaryIt->first.str().data(), Index)
           .build();
     }
@@ -871,7 +823,8 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
   if (!TUNamespaceObject) {
     return ErrorBuilder(std::errc::invalid_argument)
         .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
-        .context(ErrorMessages::MissingTUNamespace)
+        .context(ErrorMessages::MissingOrInvalidField, "tu_namespace",
+                 "JSON object")
         .build();
   }
 
@@ -890,7 +843,8 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
     if (!IdTableArray) {
       return ErrorBuilder(std::errc::invalid_argument)
           .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
-          .context(ErrorMessages::MissingIdTable)
+          .context(ErrorMessages::MissingOrInvalidField, "id_table",
+                   "JSON array")
           .build();
     }
 
@@ -898,6 +852,7 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
     if (!ExpectedIdTable)
       return ErrorBuilder(std::errc::invalid_argument)
           .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
+          .context(ErrorMessages::FailedToReadJSONArray, "id_table")
           .cause(ExpectedIdTable.takeError())
           .build();
 
@@ -910,7 +865,7 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
     if (!SummaryDataArray) {
       return ErrorBuilder(std::errc::invalid_argument)
           .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
-          .context(ErrorMessages::MissingData)
+          .context(ErrorMessages::MissingOrInvalidField, "data", "JSON array")
           .build();
     }
 
@@ -919,6 +874,7 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
     if (!ExpectedSummaryDataMap)
       return ErrorBuilder(std::errc::invalid_argument)
           .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
+          .context(ErrorMessages::FailedToReadJSONArray, "data")
           .cause(ExpectedSummaryDataMap.takeError())
           .build();
 
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
index 70fb0701b3065..567f294700d9e 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
@@ -234,6 +234,84 @@ TEST_F(JSONFormatTest, PathIsDirectory) {
                                HasSubstr("path is a directory, not a file"))));
 }
 
+TEST_F(JSONFormatTest, BrokenSymlink) {
+#ifdef _WIN32
+  GTEST_SKIP() << "Symlink test skipped on Windows";
+#else
+  SmallString<128> SymlinkPath = TestDir;
+  sys::path::append(SymlinkPath, "symlink.json");
+
+  // Create a symlink to a non-existent file
+  SmallString<128> NonexistentTarget = TestDir;
+  sys::path::append(NonexistentTarget, "does_not_exist.json");
+
+  std::error_code EC = sys::fs::create_link(NonexistentTarget, SymlinkPath);
+  if (EC) {
+    GTEST_SKIP() << "Failed to create symlink (may need elevated privileges): "
+                 << EC.message();
+  }
+
+  // Verify the symlink points to a non-existent file by checking file status
+  sys::fs::file_status Status;
+  EC = sys::fs::status(SymlinkPath, Status);
+  if (!EC) {
+    // If status succeeds, the target exists - skip test
+    GTEST_SKIP() << "Symlink unexpectedly points to existing file";
+  }
+
+  JSONFormat Format(vfs::getRealFileSystem());
+  auto Result = Format.readTUSummary(SymlinkPath);
+
+  EXPECT_THAT(Result, FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+                                       HasSubstr("file does not exist"))));
+#endif
+}
+
+TEST_F(JSONFormatTest, FileWithoutReadPermission) {
+#ifdef _WIN32
+  GTEST_SKIP() << "Permission test skipped on Windows (uses different ACL "
+                  "model)";
+#else
+  SmallString<128> FilePath = TestDir;
+  sys::path::append(FilePath, "no_read.json");
+
+  // Create a file with valid JSON content
+  std::error_code EC;
+  raw_fd_ostream OS(FilePath, EC);
+  ASSERT_FALSE(EC) << "Failed to create file: " << EC.message();
+  OS << R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": []
+  })";
+  OS.close();
+
+  // Remove read permissions (chmod 000)
+  auto Perms = sys::fs::perms::all_all;
+  EC = sys::fs::setPermissions(FilePath, Perms);
+  ASSERT_FALSE(EC) << "Failed to set permissions: " << EC.message();
+
+  // Now remove all permissions
+  EC = sys::fs::setPermissions(FilePath, static_cast<sys::fs::perms>(0));
+  if (EC) {
+    GTEST_SKIP() << "Failed to remove permissions (may be running as root): "
+                 << EC.message();
+  }
+
+  JSONFormat Format(vfs::getRealFileSystem());
+  auto Result = Format.readTUSummary(FilePath);
+
+  // Restore permissions for cleanup
+  sys::fs::setPermissions(FilePath, sys::fs::perms::all_all);
+
+  EXPECT_THAT(Result, FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+                                       HasSubstr("failed to read file"))));
+#endif
+}
+
 TEST_F(JSONFormatTest, NotJsonExtension) {
   auto Result = readJSON("{}", "test.txt");
 
@@ -289,7 +367,7 @@ TEST_F(JSONFormatTest, MissingKind) {
   EXPECT_THAT(Result, FailedWith(AllOf(
                           HasSubstr("reading TUSummary from"),
                           HasSubstr("failed to deserialize BuildNamespace"),
-                          HasSubstr("missing required field 'kind' "
+                          HasSubstr("missing or invalid field 'kind' "
                                     "(expected BuildNamespaceKind)"))));
 }
 
@@ -305,7 +383,8 @@ TEST_F(JSONFormatTest, MissingName) {
   EXPECT_THAT(Result, FailedWith(AllOf(
                           HasSubstr("reading TUSummary from"),
                           HasSubstr("failed to deserialize BuildNamespace"),
-                          HasSubstr("missing required field 'name'"))));
+                          HasSubstr("missing or invalid field 'name' "
+                                    "(expected string)"))));
 }
 
 TEST_F(JSONFormatTest, InvalidKind) {
@@ -321,6 +400,7 @@ TEST_F(JSONFormatTest, InvalidKind) {
   EXPECT_THAT(Result, FailedWith(AllOf(
                           HasSubstr("reading TUSummary from"),
                           HasSubstr("failed to deserialize BuildNamespace"),
+                          HasSubstr("while parsing field 'kind'"),
                           HasSubstr("invalid 'kind' BuildNamespaceKind "
                                     "value"))));
 }
@@ -413,10 +493,11 @@ TEST_F(JSONFormatTest, IDTableEntryMissingID) {
   EXPECT_THAT(Result,
               FailedWith(AllOf(
                   HasSubstr("reading TUSummary from"),
+                  HasSubstr("failed to read JSON array from field 'id_table'"),
                   HasSubstr("failed to deserialize EntityIdTable at index 0"),
                   HasSubstr("failed to deserialize EntityIdTable entry"),
-                  HasSubstr("missing required field 'id' (expected unsigned "
-                            "integer EntityId)"))));
+                  HasSubstr("missing or invalid field 'id' "
+                            "(expected unsigned integer EntityId)"))));
 }
 
 TEST_F(JSONFormatTest, IDTableEntryMissingName) {
@@ -437,8 +518,10 @@ TEST_F(JSONFormatTest, IDTableEntryMissingName) {
       Result,
       FailedWith(AllOf(
           HasSubstr("reading TUSummary from"),
+          HasSubstr("failed to read JSON array from field 'id_table'"),
           HasSubstr("failed to deserialize EntityIdTable at index 0"),
           HasSubstr("failed to deserialize EntityIdTable entry"),
+          HasSubstr("failed to read JSON object from field 'name'"),
           HasSubstr("missing or invalid field 'name' (expected EntityName JSON "
                     "object)"))));
 }
@@ -466,6 +549,7 @@ TEST_F(JSONFormatTest, IDTableEntryIDNotUInt64) {
       Result,
       FailedWith(
           AllOf(HasSubstr("reading TUSummary from"),
+                HasSubstr("failed to read JSON array from field 'id_table'"),
                 HasSubstr("failed to deserialize EntityIdTable at index 0"),
                 HasSubstr("failed to deserialize EntityIdTable entry"),
                 HasSubstr("field 'id' is not a valid unsigned 64-bit integer"),
@@ -541,11 +625,13 @@ TEST_F(JSONFormatTest, EntityNameMissingUSR) {
   EXPECT_THAT(Result,
               FailedWith(AllOf(
                   HasSubstr("reading TUSummary from"),
+                  HasSubstr("failed to read JSON array from field 'id_table'"),
                   HasSubstr("failed to deserialize EntityIdTable at index 0"),
                   HasSubstr("failed to deserialize EntityIdTable entry"),
+                  HasSubstr("failed to read JSON object from field 'name'"),
                   HasSubstr("failed to deserialize EntityName"),
-                  HasSubstr("missing required field 'usr' (Unified Symbol "
-                            "Resolution string)"))));
+                  HasSubstr("missing or invalid field 'usr' "
+                            "(expected string (Unified Symbol Resolution))"))));
 }
 
 TEST_F(JSONFormatTest, EntityNameMissingSuffix) {
@@ -566,14 +652,17 @@ TEST_F(JSONFormatTest, EntityNameMissingSuffix) {
     "data": []
   })");
 
-  EXPECT_THAT(
-      Result,
-      FailedWith(AllOf(HasSubstr("reading TUSummary from"),
-                       HasSubstr("failed to deserialize EntityIdTable at "
-                                 "index 0"),
-                       HasSubstr("failed to deserialize EntityIdTable entry"),
-                       HasSubstr("failed to deserialize EntityName"),
-                       HasSubstr("missing required field 'suffix'"))));
+  EXPECT_THAT(Result,
+              FailedWith(AllOf(
+                  HasSubstr("reading TUSummary from"),
+                  HasSubstr("failed to read JSON array from field 'id_table'"),
+                  HasSubstr("failed to deserialize EntityIdTable at "
+                            "index 0"),
+                  HasSubstr("failed to deserialize EntityIdTable entry"),
+                  HasSubstr("failed to read JSON object from field 'name'"),
+                  HasSubstr("failed to deserialize EntityName"),
+                  HasSubstr("missing or invalid field 'suffix' "
+                            "(expected string)"))));
 }
 
 TEST_F(JSONFormatTest, EntityNameMissingNamespace) {
@@ -598,9 +687,12 @@ TEST_F(JSONFormatTest, EntityNameMissingNamespace) {
       Result,
       FailedWith(
           AllOf(HasSubstr("reading TUSummary from"),
+                HasSubstr("failed to read JSON array from field 'id_table'"),
                 HasSubstr("failed to deserialize EntityIdTable at index 0"),
                 HasSubstr("failed to deserialize EntityIdTable entry"),
+                HasSubstr("failed to read JSON object from field 'name'"),
                 HasSubstr("failed to deserialize EntityName"),
+                HasSubstr("failed to read JSON array from field 'namespace'"),
                 HasSubstr("missing or invalid field 'namespace'"),
                 HasSubstr("(expected JSON array of BuildNamespace objects)"))));
 }
@@ -627,9 +719,12 @@ TEST_F(JSONFormatTest, NamespaceElementNotObject) {
   EXPECT_THAT(Result,
               FailedWith(AllOf(
                   HasSubstr("reading TUSummary from"),
+                  HasSubstr("failed to read JSON array from field 'id_table'"),
                   HasSubstr("failed to deserialize EntityIdTable at index 0"),
                   HasSubstr("failed to deserialize EntityIdTable entry"),
+                  HasSubstr("failed to read JSON object from field 'name'"),
                   HasSubstr("failed to deserialize EntityName"),
+                  HasSubstr("failed to read JSON array from field 'namespace'"),
                   HasSubstr("failed to deserialize NestedBuildNamespace"),
                   HasSubstr("element at index 0 is not a JSON object"))));
 }
@@ -688,11 +783,14 @@ TEST_F(JSONFormatTest, DataEntryMissingSummaryName) {
 
   EXPECT_THAT(
       Result,
-      FailedWith(AllOf(HasSubstr("reading TUSummary from"),
-                       HasSubstr("failed to deserialize SummaryDataMap at "
-                                 "index 0"),
-                       HasSubstr("failed to deserialize SummaryDataMap entry"),
-                       HasSubstr("missing required field 'summary_name'"))));
+      FailedWith(
+          AllOf(HasSubstr("reading TUSummary from"),
+                HasSubstr("failed to read JSON array from field 'data'"),
+                HasSubstr("failed to deserialize SummaryDataMap at "
+                          "index 0"),
+                HasSubstr("failed to deserialize SummaryDataMap entry"),
+                HasSubstr("missing or invalid field 'summary_name' "
+                          "(expected string (analysis summary identifier))"))));
 }
 
 TEST_F(JSONFormatTest, DataEntryMissingData) {
@@ -712,6 +810,7 @@ TEST_F(JSONFormatTest, DataEntryMissingData) {
   EXPECT_THAT(Result,
               FailedWith(AllOf(
                   HasSubstr("reading TUSummary from"),
+                  HasSubstr("failed to read JSON array from field 'data'"),
                   HasSubstr("failed to deserialize SummaryDataMap at index 0"),
                   HasSubstr("failed to deserialize SummaryDataMap entry"),
                   HasSubstr("missing or invalid field 'summary_data'"))));
@@ -766,9 +865,11 @@ TEST_F(JSONFormatTest, EntityDataElementNotObject) {
       Result,
       FailedWith(AllOf(
           HasSubstr("reading TUSummary from"),
+          HasSubstr("failed to read JSON array from field 'data'"),
           HasSubstr("failed to deserialize SummaryDataMap at index 0"),
-          HasSubstr("failed to deserialize SummaryDataMap entry for summary "
-                    "'test_summary'"),
+          HasSubstr("failed to deserialize SummaryDataMap entry"),
+          HasSubstr("for summary 'test_summary'"),
+          HasSubstr("failed to read JSON array from field 'summary_data'"),
           HasSubstr("failed to deserialize EntityDataMap"),
           HasSubstr("element at index 0 is not a JSON object"),
           HasSubstr("(expected EntityDataMap entry with 'entity_id' and "
@@ -799,13 +900,15 @@ TEST_F(JSONFormatTest, EntityDataMissingEntityID) {
       Result,
       FailedWith(AllOf(
           HasSubstr("reading TUSummary from"),
+          HasSubstr("failed to read JSON array from field 'data'"),
           HasSubstr("failed to deserialize SummaryDataMap at index 0"),
-          HasSubstr("failed to deserialize SummaryDataMap entry for summary "
-                    "'test_summary'"),
+          HasSubstr("failed to deserialize SummaryDataMap entry"),
+          HasSubstr("for summary 'test_summary'"),
+          HasSubstr("failed to read JSON array from field 'summary_data'"),
           HasSubstr("failed to deserialize EntityDataMap at index 0"),
           HasSubstr("failed to deserialize EntityDataMap entry"),
-          HasSubstr("missing required field 'entity_id' (expected unsigned "
-                    "integer EntityId)"))));
+          HasSubstr("missing or invalid field 'entity_id' "
+                    "(expected unsigned integer EntityId)"))));
 }
 
 TEST_F(JSONFormatTest, EntityDataMissingEntitySummary) {
@@ -831,11 +934,14 @@ TEST_F(JSONFormatTest, EntityDataMissingEntitySummary) {
       Result,
       FailedWith(AllOf(
           HasSubstr("reading TUSummary from"),
+          HasSubstr("failed to read JSON array from field 'data'"),
           HasSubstr("failed to deserialize SummaryDataMap at index 0"),
-          HasSubstr("failed to deserialize SummaryDataMap entry for summary "
-                    "'test_summary'"),
+          HasSubstr("failed to deserialize SummaryDataMap entry"),
+          HasSubstr("for summary 'test_summary'"),
+          HasSubstr("failed to read JSON array from field 'summary_data'"),
           HasSubstr("failed to deserialize EntityDataMap at index 0"),
           HasSubstr("failed to deserialize EntityDataMap entry"),
+          HasSubstr("failed to read JSON object from field 'entity_summary'"),
           HasSubstr("missing or invalid field 'entity_summary'"),
           HasSubstr("(expected EntitySummary JSON object)"))));
 }
@@ -864,9 +970,11 @@ TEST_F(JSONFormatTest, EntityIDNotUInt64) {
       Result,
       FailedWith(AllOf(
           HasSubstr("reading TUSummary from"),
+          HasSubstr("failed to read JSON array from field 'data'"),
           HasSubstr("failed to deserialize SummaryDataMap at index 0"),
-          HasSubstr("failed to deserialize SummaryDataMap entry for summary "
-                    "'test_summary'"),
+          HasSubstr("failed to deserialize SummaryDataMap entry"),
+          HasSubstr("for summary 'test_summary'"),
+          HasSubstr("failed to read JSON array from field 'summary_data'"),
           HasSubstr("failed to deserialize EntityDataMap at index 0"),
           HasSubstr("failed to deserialize EntityDataMap entry"),
           HasSubstr("field 'entity_id' is not a valid unsigned 64-bit integer"),
@@ -897,11 +1005,14 @@ TEST_F(JSONFormatTest, EntitySummaryNoFormatInfo) {
       Result,
       FailedWith(AllOf(
           HasSubstr("reading TUSummary from"),
+          HasSubstr("failed to read JSON array from field 'data'"),
           HasSubstr("failed to deserialize SummaryDataMap at index 0"),
-          HasSubstr("failed to deserialize SummaryDataMap entry for summary "
-                    "'unknown_summary_type'"),
+          HasSubstr("failed to deserialize SummaryDataMap entry"),
+          HasSubstr("for summary 'unknown_summary_type'"),
+          HasSubstr("failed to read JSON array from field 'summary_data'"),
           HasSubstr("failed to deserialize EntityDataMap at index 0"),
           HasSubstr("failed to deserialize EntityDataMap entry"),
+          HasSubstr("failed to read JSON object from field 'entity_summary'"),
           HasSubstr("failed to deserialize EntitySummary"),
           HasSubstr("no FormatInfo was registered for summary name: "
                     "unknown_summary_type"))));
@@ -933,15 +1044,19 @@ TEST_F(JSONFormatTest, TestAnalysisMissingField) {
 
   EXPECT_THAT(
       Result,
-      FailedWith(AllOf(HasSubstr("reading TUSummary from"),
-                       HasSubstr("failed to deserialize SummaryDataMap at "
-                                 "index 0"),
-                       HasSubstr("failed to deserialize SummaryDataMap entry "
-                                 "for summary 'test_summary'"),
-                       HasSubstr("failed to deserialize EntityDataMap at "
-                                 "index 0"),
-                       HasSubstr("failed to deserialize EntityDataMap entry"),
-                       HasSubstr("missing required field 'pairs'"))));
+      FailedWith(AllOf(
+          HasSubstr("reading TUSummary from"),
+          HasSubstr("failed to read JSON array from field 'data'"),
+          HasSubstr("failed to deserialize SummaryDataMap at "
+                    "index 0"),
+          HasSubstr("failed to deserialize SummaryDataMap entry"),
+          HasSubstr("for summary 'test_summary'"),
+          HasSubstr("failed to read JSON array from field 'summary_data'"),
+          HasSubstr("failed to deserialize EntityDataMap at "
+                    "index 0"),
+          HasSubstr("failed to deserialize EntityDataMap entry"),
+          HasSubstr("failed to read JSON object from field 'entity_summary'"),
+          HasSubstr("missing required field 'pairs'"))));
 }
 
 TEST_F(JSONFormatTest, TestAnalysisInvalidPair) {
@@ -973,16 +1088,20 @@ TEST_F(JSONFormatTest, TestAnalysisInvalidPair) {
 
   EXPECT_THAT(
       Result,
-      FailedWith(AllOf(HasSubstr("reading TUSummary from"),
-                       HasSubstr("failed to deserialize SummaryDataMap at "
-                                 "index 0"),
-                       HasSubstr("failed to deserialize SummaryDataMap entry "
-                                 "for summary 'test_summary'"),
-                       HasSubstr("failed to deserialize EntityDataMap at "
-                                 "index 0"),
-                       HasSubstr("failed to deserialize EntityDataMap entry"),
-                       HasSubstr("missing or invalid 'second' field at "
-                                 "index 0"))));
+      FailedWith(AllOf(
+          HasSubstr("reading TUSummary from"),
+          HasSubstr("failed to read JSON array from field 'data'"),
+          HasSubstr("failed to deserialize SummaryDataMap at "
+                    "index 0"),
+          HasSubstr("failed to deserialize SummaryDataMap entry"),
+          HasSubstr("for summary 'test_summary'"),
+          HasSubstr("failed to read JSON array from field 'summary_data'"),
+          HasSubstr("failed to deserialize EntityDataMap at "
+                    "index 0"),
+          HasSubstr("failed to deserialize EntityDataMap entry"),
+          HasSubstr("failed to read JSON object from field 'entity_summary'"),
+          HasSubstr("missing or invalid 'second' field at "
+                    "index 0"))));
 }
 
 // ============================================================================

>From edc0eaf2082e5c28c0b67eba291b4ac27d4af5f0 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Tue, 10 Feb 2026 16:50:44 -0800
Subject: [PATCH 19/19] Fix ErrorBuilder, and tests

---
 .../Scalable/Serialization/JSONFormat.h       |   10 +-
 .../Scalable/Serialization/JSONFormat.cpp     |  642 +++++----
 .../Scalable/Serialization/JSONFormatTest.cpp | 1228 +++++++++--------
 3 files changed, 999 insertions(+), 881 deletions(-)

diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
index fb48e0122ade9..8078937a94b6f 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -93,8 +93,8 @@ class JSONFormat : public SerializationFormat {
   entitySummaryFromJSON(const SummaryName &SN,
                         const llvm::json::Object &EntitySummaryObject,
                         EntityIdTable &IdTable) const;
-  llvm::json::Object entitySummaryToJSON(const SummaryName &SN,
-                                         const EntitySummary &ES) const;
+  llvm::Expected<llvm::json::Object>
+  entitySummaryToJSON(const SummaryName &SN, const EntitySummary &ES) const;
 
   llvm::Expected<std::pair<EntityId, std::unique_ptr<EntitySummary>>>
   entityDataMapEntryFromJSON(const llvm::json::Object &EntityDataMapEntryObject,
@@ -104,7 +104,7 @@ class JSONFormat : public SerializationFormat {
   entityDataMapFromJSON(const SummaryName &SN,
                         const llvm::json::Array &EntityDataArray,
                         EntityIdTable &IdTable) const;
-  llvm::json::Array
+  llvm::Expected<llvm::json::Array>
   entityDataMapToJSON(const SummaryName &SN,
                       const std::map<EntityId, std::unique_ptr<EntitySummary>>
                           &EntityDataMap) const;
@@ -113,7 +113,7 @@ class JSONFormat : public SerializationFormat {
                            std::map<EntityId, std::unique_ptr<EntitySummary>>>>
   summaryDataMapEntryFromJSON(const llvm::json::Object &SummaryDataObject,
                               EntityIdTable &IdTable) const;
-  llvm::json::Object summaryDataMapEntryToJSON(
+  llvm::Expected<llvm::json::Object> summaryDataMapEntryToJSON(
       const SummaryName &SN,
       const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const;
 
@@ -121,7 +121,7 @@ class JSONFormat : public SerializationFormat {
       std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
   summaryDataMapFromJSON(const llvm::json::Array &SummaryDataArray,
                          EntityIdTable &IdTable) const;
-  llvm::json::Array summaryDataMapToJSON(
+  llvm::Expected<llvm::json::Array> summaryDataMapToJSON(
       const std::map<SummaryName,
                      std::map<EntityId, std::unique_ptr<EntitySummary>>>
           &SummaryDataMap) const;
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index de4003498195a..31b2d48fa6a4e 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -11,112 +11,142 @@
 
 using namespace clang::ssaf;
 
-namespace {
 //----------------------------------------------------------------------------
 // ErrorBuilder - Fluent API for constructing contextual errors
 //----------------------------------------------------------------------------
 
+namespace {
+
 class ErrorBuilder {
 private:
   std::error_code Code;
   std::vector<std::string> ContextStack;
-  llvm::Error WrappedError = llvm::Error::success();
 
-public:
-  explicit ErrorBuilder(std::errc EC) : Code(std::make_error_code(EC)) {}
+  // Private constructor - only accessible via static factories
   explicit ErrorBuilder(std::error_code EC) : Code(EC) {}
 
-  // Add context message without formatting (for plain strings)
+  // Helper: Format message and add to context stack
+  template <typename... Args>
+  void addFormattedContext(const char *Fmt, Args &&...ArgVals) {
+    std::string Message =
+        llvm::formatv(Fmt, std::forward<Args>(ArgVals)...).str();
+    ContextStack.push_back(std::move(Message));
+  }
+
+public:
+  // Static factory: Create new error from error code and formatted message
+  template <typename... Args>
+  static ErrorBuilder create(std::error_code EC, const char *Fmt,
+                             Args &&...ArgVals) {
+    ErrorBuilder Builder(EC);
+    Builder.addFormattedContext(Fmt, std::forward<Args>(ArgVals)...);
+    return Builder;
+  }
+
+  // Convenience overload for std::errc
+  template <typename... Args>
+  static ErrorBuilder create(std::errc EC, const char *Fmt, Args &&...ArgVals) {
+    return create(std::make_error_code(EC), Fmt,
+                  std::forward<Args>(ArgVals)...);
+  }
+
+  // Static factory: Wrap existing error and optionally add context
+  static ErrorBuilder wrap(llvm::Error E) {
+    if (!E) {
+      llvm::consumeError(std::move(E));
+      // Return builder with generic error code for success case
+      return ErrorBuilder(std::make_error_code(std::errc::invalid_argument));
+    }
+
+    std::error_code EC;
+    std::string ErrorMsg;
+
+    llvm::handleAllErrors(std::move(E), [&](const llvm::ErrorInfoBase &EI) {
+      EC = EI.convertToErrorCode();
+      ErrorMsg = EI.message();
+    });
+
+    ErrorBuilder Builder(EC);
+    if (!ErrorMsg.empty()) {
+      Builder.ContextStack.push_back(std::move(ErrorMsg));
+    }
+    return Builder;
+  }
+
+  // Add context (plain string)
   ErrorBuilder &context(const char *Msg) {
     ContextStack.push_back(Msg);
     return *this;
   }
 
-  // Add context message with formatting
+  // Add context (formatted string)
   template <typename... Args>
   ErrorBuilder &context(const char *Fmt, Args &&...ArgVals) {
-    ContextStack.push_back(
-        llvm::formatv(Fmt, std::forward<Args>(ArgVals)...).str());
-    return *this;
-  }
-
-  // Wrap an existing error as the cause
-  ErrorBuilder &cause(llvm::Error E) {
-    // Consume the old WrappedError before assigning (LLVM Error requires
-    // checking)
-    llvm::consumeError(std::move(WrappedError));
-    WrappedError = std::move(E);
+    addFormattedContext(Fmt, std::forward<Args>(ArgVals)...);
     return *this;
   }
 
   // Build the final error
   llvm::Error build() {
-    if (ContextStack.empty() && !WrappedError)
-      return llvm::Error::success();
-
     if (ContextStack.empty())
-      return std::move(WrappedError);
-
-    std::string FinalMessage = llvm::join(ContextStack, ": ");
-    auto E = llvm::createStringError(Code, "%s", FinalMessage.c_str());
-
-    if (WrappedError)
-      return llvm::joinErrors(std::move(E), std::move(WrappedError));
+      return llvm::Error::success();
 
-    return E;
+    // Reverse the context stack so that the most recent context appears first
+    // and the wrapped error (if any) appears last
+    std::vector<std::string> ReversedContext(ContextStack.rbegin(),
+                                             ContextStack.rend());
+    std::string FinalMessage = llvm::join(ReversedContext, "\n");
+    return llvm::createStringError(Code, "%s", FinalMessage.c_str());
   }
 };
 
+} // namespace
+
 //----------------------------------------------------------------------------
 // Error Message Constants
 //----------------------------------------------------------------------------
 
+namespace {
+
 namespace ErrorMessages {
-// File validation errors
-constexpr const char *FileNotFound = "file does not exist: '{0}'";
-constexpr const char *IsDirectory = "path is a directory, not a file: '{0}'";
-constexpr const char *NotJSONFile = "not a JSON file: '{0}'";
-constexpr const char *FailedToValidateJSONFile =
-    "failed to validate JSON file '{0}'";
-constexpr const char *FailedToReadFile = "failed to read file '{0}'";
-constexpr const char *FailedToReadJSONObject =
-    "failed to read JSON object from file '{0}'";
-constexpr const char *FailedToReadJSONArray =
-    "failed to read JSON array from field '{0}'";
-constexpr const char *FailedToReadJSONObjectField =
-    "failed to read JSON object from field '{0}'";
-constexpr const char *FailedToOpenFile = "failed to open '{0}'";
-constexpr const char *WriteFailed = "write failed";
-
-// Generic deserialization error templates
-constexpr const char *FailedToDeserialize = "failed to deserialize {0}";
-constexpr const char *AtIndex = "at index {0}";
-constexpr const char *ForSummary = "for summary '{0}'";
-
-// Specific error details (to be stacked with FailedToDeserialize)
-constexpr const char *MissingOrInvalidField =
-    "missing or invalid field '{0}' (expected {1})";
-constexpr const char *ElementNotObject =
-    "element at index {0} is not a JSON object (expected {1})";
-constexpr const char *InvalidUInt64Field =
-    "field '{0}' is not a valid unsigned 64-bit integer (expected "
-    "non-negative EntityId value)";
-constexpr const char *DuplicateWithExistingId =
-    "duplicate {0} found at index {1} (EntityId={2} already exists in table)";
-constexpr const char *DuplicateEntityIdAtIndex =
-    "duplicate EntityId ({0}) found at index {1}";
-constexpr const char *DuplicateAtIndex =
-    "duplicate {0} '{1}' found at index {2}";
-
-// Special cases
+
+constexpr const char *FailedToReadFile = "failed to read file '{0}': {1}";
+constexpr const char *FailedToWriteFile = "failed to write file '{0}': {1}";
+constexpr const char *FileNotFound = "file does not exist";
+constexpr const char *FileIsDirectory = "path is a directory, not a file";
+constexpr const char *FileIsNotJSON =
+    "file does not end with '.json' extension";
+constexpr const char *FileExists = "file already exists";
+constexpr const char *ParentDirectoryNotFound =
+    "parent directory does not exist";
+
+constexpr const char *ReadingFromField = "reading {0} from field '{1}'";
+constexpr const char *WritingToField = "writing {0} to field '{1}'";
+constexpr const char *ReadingFromIndex = "reading {0} from index '{1}'";
+constexpr const char *WritingToIndex = "writing {0} to index '{1}'";
+constexpr const char *ReadingFromFile = "reading {0} from file '{1}'";
+constexpr const char *WritingToFile = "writing {0} to file '{1}'";
+
+constexpr const char *FailedInsertionOnDuplication =
+    "failed to insert {0} at index '{1}': encountered duplicate {2} '{3}'";
+
+constexpr const char *FailedToReadObject =
+    "failed to read {0}: expected JSON {1}";
+constexpr const char *FailedToReadObjectAtField =
+    "failed to read {0} from field '{1}': expected JSON {2}";
+constexpr const char *FailedToReadObjectAtIndex =
+    "failed to read {0} from index '{1}': expected JSON {2}";
+
+constexpr const char *FailedToDeserializeEntitySummary =
+    "failed to deserialize EntitySummary: no FormatInfo registered for summary "
+    "'{0}'";
+constexpr const char *FailedToSerializeEntitySummary =
+    "failed to serialize EntitySummary: no FormatInfo registered for summary "
+    "'{0}'";
+
 constexpr const char *InvalidBuildNamespaceKind =
     "invalid 'kind' BuildNamespaceKind value '{0}'";
-constexpr const char *NoFormatInfoForSummaryName =
-    "no FormatInfo was registered for summary name: {0}";
 
-// Context messages
-constexpr const char *ReadingTUSummaryFrom = "reading TUSummary from '{0}'";
-constexpr const char *WritingTUSummaryTo = "writing TUSummary to '{0}'";
 } // namespace ErrorMessages
 
 } // namespace
@@ -127,65 +157,68 @@ constexpr const char *WritingTUSummaryTo = "writing TUSummary to '{0}'";
 
 namespace {
 
-llvm::Error isJSONFile(llvm::StringRef Path) {
-  if (!llvm::sys::fs::exists(Path))
-    return ErrorBuilder(std::errc::no_such_file_or_directory)
-        .context(ErrorMessages::FileNotFound, Path.str().c_str())
-        .build();
-
-  if (llvm::sys::fs::is_directory(Path))
-    return ErrorBuilder(std::errc::is_a_directory)
-        .context(ErrorMessages::IsDirectory, Path.str().c_str())
+llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
+  if (!llvm::sys::fs::exists(Path)) {
+    return ErrorBuilder::create(std::errc::no_such_file_or_directory,
+                                ErrorMessages::FailedToReadFile, Path,
+                                ErrorMessages::FileNotFound)
         .build();
+  }
 
-  if (!Path.ends_with_insensitive(".json"))
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::NotJSONFile, Path.str().c_str())
+  if (llvm::sys::fs::is_directory(Path)) {
+    return ErrorBuilder::create(std::errc::is_a_directory,
+                                ErrorMessages::FailedToReadFile, Path,
+                                ErrorMessages::FileIsDirectory)
         .build();
+  }
 
-  return llvm::Error::success();
-}
-
-llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
-  if (llvm::Error Err = isJSONFile(Path))
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToValidateJSONFile, Path.str().c_str())
-        .cause(std::move(Err))
+  if (!Path.ends_with_insensitive(".json")) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadFile, Path,
+                                ErrorMessages::FileIsNotJSON)
         .build();
+  }
 
   auto BufferOrError = llvm::MemoryBuffer::getFile(Path);
   if (!BufferOrError) {
-    return ErrorBuilder(BufferOrError.getError())
-        .context(ErrorMessages::FailedToReadFile, Path.str().c_str())
+    const std::error_code EC = BufferOrError.getError();
+    return ErrorBuilder::create(EC, ErrorMessages::FailedToReadFile, Path,
+                                EC.message())
         .build();
   }
 
   return llvm::json::parse(BufferOrError.get()->getBuffer());
 }
 
-llvm::Expected<llvm::json::Object> readJSONObject(llvm::StringRef Path) {
-  auto ExpectedJSON = readJSON(Path);
-  if (!ExpectedJSON)
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToReadJSONObject, Path.str().c_str())
-        .cause(ExpectedJSON.takeError())
+llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
+  if (llvm::sys::fs::exists(Path)) {
+    return ErrorBuilder::create(std::errc::file_exists,
+                                ErrorMessages::FailedToWriteFile, Path,
+                                ErrorMessages::FileExists)
         .build();
+  }
 
-  llvm::json::Object *Object = ExpectedJSON->getAsObject();
-  if (!Object) {
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToReadJSONObject, Path.str().c_str())
+  llvm::StringRef Dir = llvm::sys::path::parent_path(Path);
+  if (!Dir.empty() && !llvm::sys::fs::is_directory(Dir)) {
+    return ErrorBuilder::create(std::errc::no_such_file_or_directory,
+                                ErrorMessages::FailedToWriteFile, Path,
+                                ErrorMessages::ParentDirectoryNotFound)
+        .build();
+  }
+
+  if (!Path.ends_with_insensitive(".json")) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToWriteFile, Path,
+                                ErrorMessages::FileIsNotJSON)
         .build();
   }
-  return *Object;
-}
 
-llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
   std::error_code EC;
   llvm::raw_fd_ostream OutStream(Path, EC, llvm::sys::fs::OF_Text);
+
   if (EC) {
-    return ErrorBuilder(EC)
-        .context(ErrorMessages::FailedToOpenFile, Path.str().c_str())
+    return ErrorBuilder::create(EC, ErrorMessages::FailedToWriteFile, Path,
+                                EC.message())
         .build();
   }
 
@@ -193,8 +226,9 @@ llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
   OutStream.flush();
 
   if (OutStream.has_error()) {
-    return ErrorBuilder(OutStream.error())
-        .context(ErrorMessages::WriteFailed)
+    return ErrorBuilder::create(OutStream.error(),
+                                ErrorMessages::FailedToWriteFile, Path,
+                                OutStream.error().message())
         .build();
   }
 
@@ -214,7 +248,7 @@ JSONFormat::JSONFormat(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
     bool Inserted = FormatInfos.try_emplace(Info->ForSummary, *Info).second;
     if (!Inserted) {
       llvm::report_fatal_error(
-          "Format info was already registered for summary name: " +
+          "FormatInfo is already registered for summary: " +
           Info->ForSummary.str());
     }
   }
@@ -254,9 +288,9 @@ llvm::Expected<BuildNamespaceKind> JSONFormat::buildNamespaceKindFromJSON(
     llvm::StringRef BuildNamespaceKindStr) const {
   auto OptBuildNamespaceKind = parseBuildNamespaceKind(BuildNamespaceKindStr);
   if (!OptBuildNamespaceKind) {
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::InvalidBuildNamespaceKind,
-                 BuildNamespaceKindStr.str().c_str())
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::InvalidBuildNamespaceKind,
+                                BuildNamespaceKindStr)
         .build();
   }
 
@@ -279,26 +313,23 @@ llvm::Expected<BuildNamespace> JSONFormat::buildNamespaceFromJSON(
     const llvm::json::Object &BuildNamespaceObject) const {
   auto OptBuildNamespaceKindStr = BuildNamespaceObject.getString("kind");
   if (!OptBuildNamespaceKindStr) {
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "BuildNamespace")
-        .context(ErrorMessages::MissingOrInvalidField, "kind",
-                 "BuildNamespaceKind")
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "BuildNamespaceKind", "kind", "string")
         .build();
   }
 
   auto ExpectedKind = buildNamespaceKindFromJSON(*OptBuildNamespaceKindStr);
   if (!ExpectedKind)
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "BuildNamespace")
-        .context("while parsing field 'kind'")
-        .cause(ExpectedKind.takeError())
+    return ErrorBuilder::wrap(ExpectedKind.takeError())
+        .context(ErrorMessages::ReadingFromField, "BuildNamespaceKind", "kind")
         .build();
 
   auto OptNameStr = BuildNamespaceObject.getString("name");
   if (!OptNameStr) {
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "BuildNamespace")
-        .context(ErrorMessages::MissingOrInvalidField, "name", "string")
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "BuildNamespaceName", "name", "string")
         .build();
   }
 
@@ -323,6 +354,7 @@ llvm::Expected<NestedBuildNamespace> JSONFormat::nestedBuildNamespaceFromJSON(
 
   size_t NamespaceCount = NestedBuildNamespaceArray.size();
   Namespaces.reserve(NamespaceCount);
+
   for (size_t Index = 0; Index < NamespaceCount; ++Index) {
     const llvm::json::Value &BuildNamespaceValue =
         NestedBuildNamespaceArray[Index];
@@ -330,20 +362,18 @@ llvm::Expected<NestedBuildNamespace> JSONFormat::nestedBuildNamespaceFromJSON(
     const llvm::json::Object *BuildNamespaceObject =
         BuildNamespaceValue.getAsObject();
     if (!BuildNamespaceObject) {
-      return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::FailedToDeserialize, "NestedBuildNamespace")
-          .context(ErrorMessages::ElementNotObject, Index,
-                   "BuildNamespace object")
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtIndex,
+                                  "BuildNamespace", Index, "object")
           .build();
     }
 
     auto ExpectedBuildNamespace = buildNamespaceFromJSON(*BuildNamespaceObject);
-    if (!ExpectedBuildNamespace)
-      return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::FailedToDeserialize, "NestedBuildNamespace")
-          .context(ErrorMessages::AtIndex, Index)
-          .cause(ExpectedBuildNamespace.takeError())
+    if (!ExpectedBuildNamespace) {
+      return ErrorBuilder::wrap(ExpectedBuildNamespace.takeError())
+          .context(ErrorMessages::ReadingFromIndex, "BuildNamespace", Index)
           .build();
+    }
 
     Namespaces.push_back(std::move(*ExpectedBuildNamespace));
   }
@@ -372,38 +402,36 @@ llvm::Expected<EntityName> JSONFormat::entityNameFromJSON(
     const llvm::json::Object &EntityNameObject) const {
   const auto OptUSR = EntityNameObject.getString("usr");
   if (!OptUSR) {
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "EntityName")
-        .context(ErrorMessages::MissingOrInvalidField, "usr",
-                 "string (Unified Symbol Resolution)")
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField, "USR",
+                                "usr", "string")
         .build();
   }
 
   const auto OptSuffix = EntityNameObject.getString("suffix");
   if (!OptSuffix) {
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "EntityName")
-        .context(ErrorMessages::MissingOrInvalidField, "suffix", "string")
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "Suffix", "suffix", "string")
         .build();
   }
 
   const llvm::json::Array *OptNamespaceArray =
       EntityNameObject.getArray("namespace");
   if (!OptNamespaceArray) {
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "EntityName")
-        .context(ErrorMessages::MissingOrInvalidField, "namespace",
-                 "JSON array of BuildNamespace objects")
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "NestedBuildNamespace", "namespace", "array")
         .build();
   }
 
   auto ExpectedNamespace = nestedBuildNamespaceFromJSON(*OptNamespaceArray);
-  if (!ExpectedNamespace)
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "EntityName")
-        .context(ErrorMessages::FailedToReadJSONArray, "namespace")
-        .cause(ExpectedNamespace.takeError())
+  if (!ExpectedNamespace) {
+    return ErrorBuilder::wrap(ExpectedNamespace.takeError())
+        .context(ErrorMessages::ReadingFromField, "NesteBuildNamespace",
+                 "namespace")
         .build();
+  }
 
   return EntityName{*OptUSR, *OptSuffix, std::move(*ExpectedNamespace)};
 }
@@ -427,37 +455,34 @@ JSONFormat::entityIdTableEntryFromJSON(
   const llvm::json::Object *OptEntityNameObject =
       EntityIdTableEntryObject.getObject("name");
   if (!OptEntityNameObject) {
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "EntityIdTable entry")
-        .context(ErrorMessages::MissingOrInvalidField, "name",
-                 "EntityName JSON object")
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntityName", "name", "object")
         .build();
   }
 
   auto ExpectedEntityName = entityNameFromJSON(*OptEntityNameObject);
-  if (!ExpectedEntityName)
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "EntityIdTable entry")
-        .context(ErrorMessages::FailedToReadJSONObjectField, "name")
-        .cause(ExpectedEntityName.takeError())
+  if (!ExpectedEntityName) {
+    return ErrorBuilder::wrap(ExpectedEntityName.takeError())
+        .context(ErrorMessages::ReadingFromField, "EntityName", "name")
         .build();
+  }
 
   const llvm::json::Value *EntityIdIntValue =
       EntityIdTableEntryObject.get("id");
   if (!EntityIdIntValue) {
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "EntityIdTable entry")
-        .context(ErrorMessages::MissingOrInvalidField, "id",
-                 "unsigned integer EntityId")
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntityId", "id", "(unsigned 64-bit)")
         .build();
   }
 
   const std::optional<uint64_t> OptEntityIdInt =
       EntityIdIntValue->getAsUINT64();
   if (!OptEntityIdInt) {
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "EntityIdTable entry")
-        .context(ErrorMessages::InvalidUInt64Field, "id")
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntityId", "id", "integer (unsigned 64-bit)")
         .build();
   }
 
@@ -489,31 +514,28 @@ llvm::Expected<EntityIdTable> JSONFormat::entityIdTableFromJSON(
 
     const llvm::json::Object *OptEntityIdTableEntryObject =
         EntityIdTableEntryValue.getAsObject();
-
     if (!OptEntityIdTableEntryObject) {
-      return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::FailedToDeserialize, "EntityIdTable")
-          .context(ErrorMessages::ElementNotObject, Index,
-                   "EntityIdTable entry with 'id' and 'name' fields")
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtIndex,
+                                  "EntityIdTable entry", Index, "object")
           .build();
     }
 
     auto ExpectedEntityIdTableEntry =
         entityIdTableEntryFromJSON(*OptEntityIdTableEntryObject);
     if (!ExpectedEntityIdTableEntry)
-      return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::FailedToDeserialize, "EntityIdTable")
-          .context(ErrorMessages::AtIndex, Index)
-          .cause(ExpectedEntityIdTableEntry.takeError())
+      return ErrorBuilder::wrap(ExpectedEntityIdTableEntry.takeError())
+          .context(ErrorMessages::ReadingFromIndex, "EntityIdTable entry",
+                   Index)
           .build();
 
     auto [EntityIt, EntityInserted] =
         Entities.emplace(std::move(*ExpectedEntityIdTableEntry));
     if (!EntityInserted) {
-      return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::FailedToDeserialize, "EntityIdTable")
-          .context(ErrorMessages::DuplicateWithExistingId, "EntityName", Index,
-                   getEntityIdIndex(EntityIt->second))
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedInsertionOnDuplication,
+                                  "EntityIdTable entry", Index, "EntityId",
+                                  getEntityIdIndex(EntityIt->second))
           .build();
     }
   }
@@ -545,11 +567,12 @@ JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
                                   EntityIdTable &IdTable) const {
   auto InfoIt = FormatInfos.find(SN);
   if (InfoIt == FormatInfos.end()) {
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "EntitySummary")
-        .context(ErrorMessages::NoFormatInfoForSummaryName, SN.str().data())
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToDeserializeEntitySummary,
+                                SN.str())
         .build();
   }
+
   const auto &InfoEntry = InfoIt->second;
   assert(InfoEntry.ForSummary == SN);
 
@@ -557,16 +580,17 @@ JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
   return InfoEntry.Deserialize(EntitySummaryObject, IdTable, Converter);
 }
 
-llvm::json::Object
+llvm::Expected<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());
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToSerializeEntitySummary,
+                                SN.str())
+        .build();
   }
+
   const auto &InfoEntry = InfoIt->second;
   assert(InfoEntry.ForSummary == SN);
 
@@ -586,19 +610,18 @@ JSONFormat::entityDataMapEntryFromJSON(
   const llvm::json::Value *EntityIdIntValue =
       EntityDataMapEntryObject.get("entity_id");
   if (!EntityIdIntValue) {
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "EntityDataMap entry")
-        .context(ErrorMessages::MissingOrInvalidField, "entity_id",
-                 "unsigned integer EntityId")
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntityId", "entity_id", "integer")
         .build();
   }
 
   const std::optional<uint64_t> OptEntityIdInt =
       EntityIdIntValue->getAsUINT64();
   if (!OptEntityIdInt) {
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "EntityDataMap entry")
-        .context(ErrorMessages::InvalidUInt64Field, "entity_id")
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntityId", "entity_id", "integer")
         .build();
   }
 
@@ -607,21 +630,20 @@ JSONFormat::entityDataMapEntryFromJSON(
   const llvm::json::Object *OptEntitySummaryObject =
       EntityDataMapEntryObject.getObject("entity_summary");
   if (!OptEntitySummaryObject) {
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "EntityDataMap entry")
-        .context(ErrorMessages::MissingOrInvalidField, "entity_summary",
-                 "EntitySummary JSON object")
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntitySummary", "entity_summary", "object")
         .build();
   }
 
   auto ExpectedEntitySummary =
       entitySummaryFromJSON(SN, *OptEntitySummaryObject, IdTable);
-  if (!ExpectedEntitySummary)
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "EntityDataMap entry")
-        .context(ErrorMessages::FailedToReadJSONObjectField, "entity_summary")
-        .cause(ExpectedEntitySummary.takeError())
+  if (!ExpectedEntitySummary) {
+    return ErrorBuilder::wrap(ExpectedEntitySummary.takeError())
+        .context(ErrorMessages::ReadingFromField, "EntitySummary",
+                 "entity_summary")
         .build();
+  }
 
   return std::make_pair(std::move(EI), std::move(*ExpectedEntitySummary));
 }
@@ -642,30 +664,27 @@ JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
     const llvm::json::Object *OptEntityDataMapEntryObject =
         EntityDataMapEntryValue.getAsObject();
     if (!OptEntityDataMapEntryObject) {
-      return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::FailedToDeserialize, "EntityDataMap")
-          .context(ErrorMessages::ElementNotObject, Index,
-                   "EntityDataMap entry with 'entity_id' and 'entity_summary' "
-                   "fields")
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtIndex,
+                                  "EntitySummary entry", Index, "object")
           .build();
     }
 
     auto ExpectedEntityDataMapEntry =
         entityDataMapEntryFromJSON(*OptEntityDataMapEntryObject, SN, IdTable);
     if (!ExpectedEntityDataMapEntry)
-      return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::FailedToDeserialize, "EntityDataMap")
-          .context(ErrorMessages::AtIndex, Index)
-          .cause(ExpectedEntityDataMapEntry.takeError())
+      return ErrorBuilder::wrap(ExpectedEntityDataMapEntry.takeError())
+          .context(ErrorMessages::ReadingFromIndex, "EntitySummary entry",
+                   Index)
           .build();
 
     auto [DataIt, DataInserted] =
         EntityDataMap.insert(std::move(*ExpectedEntityDataMapEntry));
     if (!DataInserted) {
-      return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::FailedToDeserialize, "EntityDataMap")
-          .context(ErrorMessages::DuplicateEntityIdAtIndex,
-                   getEntityIdIndex(DataIt->first), Index)
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedInsertionOnDuplication,
+                                  "EntitySummary entry", Index, "EntityId",
+                                  getEntityIdIndex(DataIt->first))
           .build();
     }
   }
@@ -673,17 +692,31 @@ JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
   return EntityDataMap;
 }
 
-llvm::json::Array JSONFormat::entityDataMapToJSON(
+llvm::Expected<llvm::json::Array> JSONFormat::entityDataMapToJSON(
     const SummaryName &SN,
     const std::map<EntityId, std::unique_ptr<EntitySummary>> &EntityDataMap)
     const {
   llvm::json::Array Result;
   Result.reserve(EntityDataMap.size());
+
+  size_t Index = 0;
   for (const auto &[EntityId, EntitySummary] : EntityDataMap) {
     llvm::json::Object Entry;
+
     Entry["entity_id"] = entityIdToJSON(EntityId);
-    Entry["entity_summary"] = entitySummaryToJSON(SN, *EntitySummary);
+
+    auto ExpectedEntitySummaryObject = entitySummaryToJSON(SN, *EntitySummary);
+    if (!ExpectedEntitySummaryObject) {
+      return ErrorBuilder::wrap(ExpectedEntitySummaryObject.takeError())
+          .context(ErrorMessages::WritingToIndex, "EntitySummary entry", Index)
+          .build();
+    }
+
+    Entry["entity_summary"] = std::move(*ExpectedEntitySummaryObject);
+
     Result.push_back(std::move(Entry));
+
+    ++Index;
   }
   return Result;
 }
@@ -700,12 +733,10 @@ JSONFormat::summaryDataMapEntryFromJSON(
 
   std::optional<llvm::StringRef> OptSummaryNameStr =
       SummaryDataMapEntryObject.getString("summary_name");
-
   if (!OptSummaryNameStr) {
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap entry")
-        .context(ErrorMessages::MissingOrInvalidField, "summary_name",
-                 "string (analysis summary identifier)")
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "SummaryName", "summary_name", "string")
         .build();
   }
 
@@ -714,32 +745,41 @@ JSONFormat::summaryDataMapEntryFromJSON(
   const llvm::json::Array *OptEntityDataArray =
       SummaryDataMapEntryObject.getArray("summary_data");
   if (!OptEntityDataArray) {
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap entry")
-        .context(ErrorMessages::MissingOrInvalidField, "summary_data",
-                 "JSON array of entity data entries")
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntitySummary entries", "summary_data",
+                                "array")
         .build();
   }
 
   auto ExpectedEntityDataMap =
       entityDataMapFromJSON(SN, *OptEntityDataArray, IdTable);
   if (!ExpectedEntityDataMap)
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap entry")
-        .context(ErrorMessages::ForSummary, SN.str().data())
-        .context(ErrorMessages::FailedToReadJSONArray, "summary_data")
-        .cause(ExpectedEntityDataMap.takeError())
+    return ErrorBuilder::wrap(ExpectedEntityDataMap.takeError())
+        .context(ErrorMessages::ReadingFromField, "EntitySummary entries",
+                 "summary_data")
         .build();
 
   return std::make_pair(std::move(SN), std::move(*ExpectedEntityDataMap));
 }
 
-llvm::json::Object JSONFormat::summaryDataMapEntryToJSON(
+llvm::Expected<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(SN, SD);
+
+  auto ExpectedSummaryDataArray = entityDataMapToJSON(SN, SD);
+  if (!ExpectedSummaryDataArray) {
+    return ErrorBuilder::wrap(ExpectedSummaryDataArray.takeError())
+        .context(ErrorMessages::WritingToField, "EntitySummary entries",
+                 "summary_data")
+        .build();
+  }
+
+  Result["summary_data"] = std::move(*ExpectedSummaryDataArray);
+
   return Result;
 }
 
@@ -760,30 +800,27 @@ JSONFormat::summaryDataMapFromJSON(const llvm::json::Array &SummaryDataArray,
     const llvm::json::Object *OptSummaryDataMapEntryObject =
         SummaryDataMapEntryValue.getAsObject();
     if (!OptSummaryDataMapEntryObject) {
-      return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap")
-          .context(ErrorMessages::ElementNotObject, Index,
-                   "SummaryDataMap entry with 'summary_name' and "
-                   "'summary_data' fields")
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtIndex,
+                                  "SummaryData entry", Index, "object")
           .build();
     }
 
     auto ExpectedSummaryDataMapEntry =
         summaryDataMapEntryFromJSON(*OptSummaryDataMapEntryObject, IdTable);
-    if (!ExpectedSummaryDataMapEntry)
-      return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap")
-          .context(ErrorMessages::AtIndex, Index)
-          .cause(ExpectedSummaryDataMapEntry.takeError())
+    if (!ExpectedSummaryDataMapEntry) {
+      return ErrorBuilder::wrap(ExpectedSummaryDataMapEntry.takeError())
+          .context(ErrorMessages::ReadingFromIndex, "SummaryData entry", Index)
           .build();
+    }
 
     auto [SummaryIt, SummaryInserted] =
         SummaryDataMap.emplace(std::move(*ExpectedSummaryDataMapEntry));
     if (!SummaryInserted) {
-      return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap")
-          .context(ErrorMessages::DuplicateAtIndex, "SummaryName",
-                   SummaryIt->first.str().data(), Index)
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedInsertionOnDuplication,
+                                  "SummaryData entry", Index, "SummaryName",
+                                  SummaryIt->first.str())
           .build();
     }
   }
@@ -791,15 +828,29 @@ JSONFormat::summaryDataMapFromJSON(const llvm::json::Array &SummaryDataArray,
   return SummaryDataMap;
 }
 
-llvm::json::Array JSONFormat::summaryDataMapToJSON(
+llvm::Expected<llvm::json::Array> JSONFormat::summaryDataMapToJSON(
     const std::map<SummaryName,
                    std::map<EntityId, std::unique_ptr<EntitySummary>>>
         &SummaryDataMap) const {
   llvm::json::Array Result;
   Result.reserve(SummaryDataMap.size());
+
+  size_t Index = 0;
   for (const auto &[SummaryName, DataMap] : SummaryDataMap) {
-    Result.push_back(summaryDataMapEntryToJSON(SummaryName, DataMap));
+
+    auto ExpectedSummaryDataMapObject =
+        summaryDataMapEntryToJSON(SummaryName, DataMap);
+    if (!ExpectedSummaryDataMapObject) {
+      return ErrorBuilder::wrap(ExpectedSummaryDataMapObject.takeError())
+          .context(ErrorMessages::ReadingFromIndex, "SummaryData entry", Index)
+          .build();
+    }
+
+    Result.push_back(std::move(*ExpectedSummaryDataMapObject));
+
+    ++Index;
   }
+
   return Result;
 }
 
@@ -808,75 +859,86 @@ llvm::json::Array JSONFormat::summaryDataMapToJSON(
 //----------------------------------------------------------------------------
 
 llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
-  auto ExpectedRootObject = readJSONObject(Path);
-  if (!ExpectedRootObject)
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
-        .cause(ExpectedRootObject.takeError())
+
+  auto ExpectedJSON = readJSON(Path);
+  if (!ExpectedJSON) {
+    return ErrorBuilder::wrap(ExpectedJSON.takeError())
+        .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
+        .build();
+  }
+
+  llvm::json::Object *RootObjectPtr = ExpectedJSON->getAsObject();
+  if (!RootObjectPtr) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObject, "TUSummary",
+                                "object")
+        .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
         .build();
+  }
 
-  const llvm::json::Object &RootObject = *ExpectedRootObject;
+  const llvm::json::Object &RootObject = *RootObjectPtr;
 
-  // Parse TUNamespace field
   const llvm::json::Object *TUNamespaceObject =
       RootObject.getObject("tu_namespace");
   if (!TUNamespaceObject) {
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
-        .context(ErrorMessages::MissingOrInvalidField, "tu_namespace",
-                 "JSON object")
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "BuildNamespace", "tu_namespace", "object")
+        .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
         .build();
   }
 
   auto ExpectedTUNamespace = buildNamespaceFromJSON(*TUNamespaceObject);
-  if (!ExpectedTUNamespace)
-    return ErrorBuilder(std::errc::invalid_argument)
-        .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
-        .cause(ExpectedTUNamespace.takeError())
+  if (!ExpectedTUNamespace) {
+    return ErrorBuilder::wrap(ExpectedTUNamespace.takeError())
+        .context(ErrorMessages::ReadingFromField, "BuildNamespace",
+                 "tu_namespace")
+        .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
         .build();
+  }
 
   TUSummary Summary(std::move(*ExpectedTUNamespace));
 
-  // Parse IdTable field
   {
     const llvm::json::Array *IdTableArray = RootObject.getArray("id_table");
     if (!IdTableArray) {
-      return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
-          .context(ErrorMessages::MissingOrInvalidField, "id_table",
-                   "JSON array")
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtField,
+                                  "IdTable", "id_table", "array")
+          .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
           .build();
     }
 
     auto ExpectedIdTable = entityIdTableFromJSON(*IdTableArray);
-    if (!ExpectedIdTable)
-      return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
-          .context(ErrorMessages::FailedToReadJSONArray, "id_table")
-          .cause(ExpectedIdTable.takeError())
+    if (!ExpectedIdTable) {
+      return ErrorBuilder::wrap(ExpectedIdTable.takeError())
+          .context(ErrorMessages::ReadingFromField, "IdTable", "id_table")
+          .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
           .build();
+    }
 
     getIdTable(Summary) = std::move(*ExpectedIdTable);
   }
 
-  // Parse Data field
   {
     const llvm::json::Array *SummaryDataArray = RootObject.getArray("data");
     if (!SummaryDataArray) {
-      return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
-          .context(ErrorMessages::MissingOrInvalidField, "data", "JSON array")
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtField,
+                                  "SummaryData entries", "data", "array")
+          .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
           .build();
     }
 
     auto ExpectedSummaryDataMap =
         summaryDataMapFromJSON(*SummaryDataArray, getIdTable(Summary));
-    if (!ExpectedSummaryDataMap)
-      return ErrorBuilder(std::errc::invalid_argument)
-          .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
-          .context(ErrorMessages::FailedToReadJSONArray, "data")
-          .cause(ExpectedSummaryDataMap.takeError())
+    if (!ExpectedSummaryDataMap) {
+      return ErrorBuilder::wrap(ExpectedSummaryDataMap.takeError())
+          .context(ErrorMessages::ReadingFromField, "SummaryData entries",
+                   "data")
+          .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
           .build();
+    }
 
     getData(Summary) = std::move(*ExpectedSummaryDataMap);
   }
@@ -892,12 +954,18 @@ llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
 
   RootObject["id_table"] = entityIdTableToJSON(getIdTable(S));
 
-  RootObject["data"] = summaryDataMapToJSON(getData(S));
+  auto ExpectedDataObject = summaryDataMapToJSON(getData(S));
+  if (!ExpectedDataObject) {
+    return ErrorBuilder::wrap(ExpectedDataObject.takeError())
+        .context(ErrorMessages::WritingToFile, "TUSummary", Path)
+        .build();
+  }
+
+  RootObject["data"] = std::move(*ExpectedDataObject);
 
   if (auto Error = writeJSON(std::move(RootObject), Path)) {
-    return ErrorBuilder(std::errc::io_error)
-        .context(ErrorMessages::WritingTUSummaryTo, Path.str().c_str())
-        .cause(std::move(Error))
+    return ErrorBuilder::wrap(std::move(Error))
+        .context(ErrorMessages::WritingToFile, "TUSummary", Path)
         .build();
   }
 
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
index 567f294700d9e..420ee06c37b7d 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
@@ -10,6 +10,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+
 #include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
 #include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
 #include "llvm/ADT/IntrusiveRefCntPtr.h"
@@ -21,6 +22,7 @@
 #include "llvm/Support/Registry.h"
 #include "llvm/Support/VirtualFileSystem.h"
 #include "llvm/Support/raw_ostream.h"
+#include "llvm/Testing/Support/Error.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include <memory>
@@ -34,68 +36,6 @@ using ::testing::HasSubstr;
 
 namespace {
 
-// ============================================================================
-// Custom Matchers
-// ============================================================================
-
-// Helper to check if an Error or Expected succeeded
-template <typename T> struct SuccessChecker {
-  static bool isSuccess(T &val) {
-    // For Expected<U>
-    return static_cast<bool>(val);
-  }
-  static std::string getError(T &val) { return toString(val.takeError()); }
-};
-
-// Specialization for Error type
-template <> struct SuccessChecker<Error> {
-  static bool isSuccess(Error &val) {
-    // For Error, success means no error (false/empty)
-    return !static_cast<bool>(val);
-  }
-  static std::string getError(Error &val) { return toString(std::move(val)); }
-};
-
-// Matcher for Expected<T> or Error success
-MATCHER(Succeeded, "") {
-  // Cast away constness to get mutable access
-  auto &mutable_arg =
-      const_cast<std::remove_const_t<std::remove_reference_t<decltype(arg)>> &>(
-          arg);
-
-  using ArgType = std::remove_const_t<std::remove_reference_t<decltype(arg)>>;
-
-  if (!SuccessChecker<ArgType>::isSuccess(mutable_arg)) {
-    *result_listener << "Operation failed with error: "
-                     << SuccessChecker<ArgType>::getError(mutable_arg);
-    return false;
-  }
-  return true;
-}
-
-// Matcher for Expected<T> or Error failure with specific error message
-MATCHER_P(FailedWith, SubstrMatcher, "") {
-  // Cast away constness to get mutable access
-  auto &mutable_arg =
-      const_cast<std::remove_const_t<std::remove_reference_t<decltype(arg)>> &>(
-          arg);
-
-  using ArgType = std::remove_const_t<std::remove_reference_t<decltype(arg)>>;
-
-  if (SuccessChecker<ArgType>::isSuccess(mutable_arg)) {
-    *result_listener << "Expected operation to fail, but it succeeded";
-    return false;
-  }
-
-  std::string ErrorMsg = SuccessChecker<ArgType>::getError(mutable_arg);
-
-  if (!::testing::Matches(SubstrMatcher)(ErrorMsg)) {
-    *result_listener << "Error message was: " << ErrorMsg;
-    return false;
-  }
-  return true;
-}
-
 // ============================================================================
 // Test Analysis - Simple analysis for testing JSON serialization
 // ============================================================================
@@ -126,7 +66,7 @@ deserializeTestAnalysis(const json::Object &Obj, EntityIdTable &IdTable,
   const json::Array *PairsArray = Obj.getArray("pairs");
   if (!PairsArray)
     return createStringError(inconvertibleErrorCode(),
-                             "missing required field 'pairs'");
+                             "missing or invalid field 'pairs'");
   for (size_t I = 0; I < PairsArray->size(); ++I) {
     const json::Object *Pair = (*PairsArray)[I].getAsObject();
     if (!Pair)
@@ -136,12 +76,12 @@ deserializeTestAnalysis(const json::Object &Obj, EntityIdTable &IdTable,
     auto FirstOpt = Pair->getInteger("first");
     if (!FirstOpt)
       return createStringError(inconvertibleErrorCode(),
-                               "missing or invalid 'first' field at index %zu",
+                               "missing or invalid 'first' field at index '%zu'",
                                I);
     auto SecondOpt = Pair->getInteger("second");
     if (!SecondOpt)
       return createStringError(inconvertibleErrorCode(),
-                               "missing or invalid 'second' field at index %zu",
+                               "missing or invalid 'second' field at index '%zu'",
                                I);
     Result->Pairs.emplace_back(Converter.fromJSON(*FirstOpt),
                                Converter.fromJSON(*SecondOpt));
@@ -175,7 +115,8 @@ class JSONFormatTest : public ::testing::Test {
 
   void TearDown() override { sys::fs::remove_directories(TestDir); }
 
-  auto readJSON(StringRef JSON, StringRef Filename = "test.json") {
+  llvm::Expected<TUSummary> readJSON(StringRef JSON,
+                                     StringRef Filename = "test.json") {
     SmallString<128> FilePath = TestDir;
     sys::path::append(FilePath, Filename);
 
@@ -185,27 +126,53 @@ class JSONFormatTest : public ::testing::Test {
     OS << JSON;
     OS.close();
 
-    return JSONFormat(vfs::getRealFileSystem()).readTUSummary(FilePath);
+    auto Result = JSONFormat(vfs::getRealFileSystem()).readTUSummary(FilePath);
+    if (!Result) {
+      std::string Message = llvm::toString(Result.takeError());
+      llvm::outs() << Message << "\n\n";
+      return {llvm::createStringError(std::move(Message))};
+    }
+    return Result;
   }
 
-  void testRoundTrip(StringRef InputJSON) {
-    auto Summary = readJSON(InputJSON, "input.json");
-    ASSERT_THAT(Summary, Succeeded());
+  void readWriteJSON(StringRef InputJSON) {
+    // Read the input JSON
+    auto Summary1 = readJSON(InputJSON, "input.json");
+    ASSERT_THAT_EXPECTED(Summary1, Succeeded());
+
+    // Write to first output file
+    SmallString<128> Output1Path = TestDir;
+    sys::path::append(Output1Path, "output1.json");
 
-    SmallString<128> OutputPath = TestDir;
-    sys::path::append(OutputPath, "output.json");
+    JSONFormat Format(vfs::getRealFileSystem());
+    auto WriteErr1 = Format.writeTUSummary(*Summary1, Output1Path);
+    ASSERT_THAT_ERROR(std::move(WriteErr1), Succeeded());
 
-    JSONFormat OutputFormat(vfs::getRealFileSystem());
-    auto WriteErr = OutputFormat.writeTUSummary(*Summary, OutputPath);
-    ASSERT_THAT(WriteErr, Succeeded());
+    // Read back from first output
+    auto Summary2 = Format.readTUSummary(Output1Path);
+    ASSERT_THAT_EXPECTED(Summary2, Succeeded());
 
-    auto RoundTrip = OutputFormat.readTUSummary(OutputPath);
-    ASSERT_THAT(RoundTrip, Succeeded());
+    // Write to second output file
+    SmallString<128> Output2Path = TestDir;
+    sys::path::append(Output2Path, "output2.json");
+
+    auto WriteErr2 = Format.writeTUSummary(*Summary2, Output2Path);
+    ASSERT_THAT_ERROR(std::move(WriteErr2), Succeeded());
+
+    // Compare the two output files byte-by-byte
+    auto Buffer1 = MemoryBuffer::getFile(Output1Path);
+    ASSERT_TRUE(Buffer1) << "Failed to read output1.json";
+
+    auto Buffer2 = MemoryBuffer::getFile(Output2Path);
+    ASSERT_TRUE(Buffer2) << "Failed to read output2.json";
+
+    EXPECT_EQ(Buffer1.get()->getBuffer(), Buffer2.get()->getBuffer())
+        << "Serialization is not stable: first write differs from second write";
   }
 };
 
 // ============================================================================
-// File Access Error Tests
+// readJSON() Error Tests
 // ============================================================================
 
 TEST_F(JSONFormatTest, NonexistentFile) {
@@ -215,8 +182,9 @@ TEST_F(JSONFormatTest, NonexistentFile) {
   JSONFormat Format(vfs::getRealFileSystem());
   auto Result = Format.readTUSummary(NonexistentPath);
 
-  EXPECT_THAT(Result, FailedWith(AllOf(HasSubstr("reading TUSummary from"),
-                                       HasSubstr("file does not exist"))));
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from"),
+                                      HasSubstr("file does not exist"))));
 }
 
 TEST_F(JSONFormatTest, PathIsDirectory) {
@@ -229,53 +197,47 @@ TEST_F(JSONFormatTest, PathIsDirectory) {
   JSONFormat Format(vfs::getRealFileSystem());
   auto Result = Format.readTUSummary(DirPath);
 
-  EXPECT_THAT(Result,
-              FailedWith(AllOf(HasSubstr("reading TUSummary from"),
-                               HasSubstr("path is a directory, not a file"))));
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(HasSubstr("reading TUSummary from"),
+                              HasSubstr("path is a directory, not a file"))));
 }
 
-TEST_F(JSONFormatTest, BrokenSymlink) {
-#ifdef _WIN32
-  GTEST_SKIP() << "Symlink test skipped on Windows";
-#else
-  SmallString<128> SymlinkPath = TestDir;
-  sys::path::append(SymlinkPath, "symlink.json");
+TEST_F(JSONFormatTest, NotJsonExtension) {
+  auto Result = readJSON("{}", "test.txt");
 
-  // Create a symlink to a non-existent file
-  SmallString<128> NonexistentTarget = TestDir;
-  sys::path::append(NonexistentTarget, "does_not_exist.json");
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(
+                  HasSubstr("reading TUSummary from file"),
+                  HasSubstr("failed to read file"),
+                  HasSubstr("file does not end with '.json' extension"))));
+}
 
-  std::error_code EC = sys::fs::create_link(NonexistentTarget, SymlinkPath);
-  if (EC) {
-    GTEST_SKIP() << "Failed to create symlink (may need elevated privileges): "
-                 << EC.message();
-  }
+TEST_F(JSONFormatTest, BrokenSymlink) {
+  SmallString<128> TargetPath = TestDir;
+  sys::path::append(TargetPath, "nonexistent_target.json");
 
-  // Verify the symlink points to a non-existent file by checking file status
-  sys::fs::file_status Status;
-  EC = sys::fs::status(SymlinkPath, Status);
-  if (!EC) {
-    // If status succeeds, the target exists - skip test
-    GTEST_SKIP() << "Symlink unexpectedly points to existing file";
-  }
+  SmallString<128> SymlinkPath = TestDir;
+  sys::path::append(SymlinkPath, "broken_symlink.json");
+
+  // Create a symlink pointing to a non-existent file
+  std::error_code EC = sys::fs::create_link(TargetPath, SymlinkPath);
+  ASSERT_FALSE(EC) << "Failed to create symlink: " << EC.message();
 
   JSONFormat Format(vfs::getRealFileSystem());
   auto Result = Format.readTUSummary(SymlinkPath);
 
-  EXPECT_THAT(Result, FailedWith(AllOf(HasSubstr("reading TUSummary from"),
-                                       HasSubstr("file does not exist"))));
-#endif
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from file"),
+                                      HasSubstr("failed to read file"))));
 }
 
-TEST_F(JSONFormatTest, FileWithoutReadPermission) {
-#ifdef _WIN32
-  GTEST_SKIP() << "Permission test skipped on Windows (uses different ACL "
-                  "model)";
-#else
+TEST_F(JSONFormatTest, NoReadPermission) {
+#ifndef _WIN32 // Skip on Windows as permission model is different
   SmallString<128> FilePath = TestDir;
-  sys::path::append(FilePath, "no_read.json");
+  sys::path::append(FilePath, "no_read_permission.json");
 
-  // Create a file with valid JSON content
+  // Create file with valid JSON
   std::error_code EC;
   raw_fd_ostream OS(FilePath, EC);
   ASSERT_FALSE(EC) << "Failed to create file: " << EC.message();
@@ -289,72 +251,69 @@ TEST_F(JSONFormatTest, FileWithoutReadPermission) {
   })";
   OS.close();
 
-  // Remove read permissions (chmod 000)
-  auto Perms = sys::fs::perms::all_all;
+  // Remove read permissions
+  sys::fs::perms Perms =
+      sys::fs::perms::owner_write | sys::fs::perms::owner_exe;
   EC = sys::fs::setPermissions(FilePath, Perms);
   ASSERT_FALSE(EC) << "Failed to set permissions: " << EC.message();
 
-  // Now remove all permissions
-  EC = sys::fs::setPermissions(FilePath, static_cast<sys::fs::perms>(0));
-  if (EC) {
-    GTEST_SKIP() << "Failed to remove permissions (may be running as root): "
-                 << EC.message();
-  }
-
   JSONFormat Format(vfs::getRealFileSystem());
   auto Result = Format.readTUSummary(FilePath);
 
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from file"),
+                                      HasSubstr("failed to read file"))));
+
   // Restore permissions for cleanup
   sys::fs::setPermissions(FilePath, sys::fs::perms::all_all);
-
-  EXPECT_THAT(Result, FailedWith(AllOf(HasSubstr("reading TUSummary from"),
-                                       HasSubstr("failed to read file"))));
 #endif
 }
 
-TEST_F(JSONFormatTest, NotJsonExtension) {
-  auto Result = readJSON("{}", "test.txt");
-
-  EXPECT_THAT(Result, FailedWith(AllOf(HasSubstr("reading TUSummary from"),
-                                       HasSubstr("not a JSON file"))));
-}
-
-// ============================================================================
-// JSON Syntax Error Tests
-// ============================================================================
-
 TEST_F(JSONFormatTest, InvalidSyntax) {
   auto Result = readJSON("{ invalid json }");
 
-  EXPECT_THAT(Result, FailedWith(AllOf(
-                          HasSubstr("reading TUSummary from"),
-                          HasSubstr("failed to read JSON object from file"))));
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from file"),
+                                      HasSubstr("Expected object key"))));
 }
 
 TEST_F(JSONFormatTest, NotObject) {
   auto Result = readJSON("[]");
 
-  EXPECT_THAT(Result, FailedWith(AllOf(
-                          HasSubstr("reading TUSummary from"),
-                          HasSubstr("failed to read JSON object from file"))));
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from file"),
+                                      HasSubstr("failed to read TUSummary"),
+                                      HasSubstr("expected JSON object"))));
 }
 
 // ============================================================================
-// Root Structure Error Tests
+// JSONFormat::buildNamespaceKindFromJSON() Error Tests
 // ============================================================================
 
-TEST_F(JSONFormatTest, MissingTUNamespace) {
+TEST_F(JSONFormatTest, InvalidKind) {
   auto Result = readJSON(R"({
+    "tu_namespace": {
+      "kind": "invalid_kind",
+      "name": "test.cpp"
+    },
     "id_table": [],
     "data": []
   })");
 
-  EXPECT_THAT(
+  EXPECT_THAT_EXPECTED(
       Result,
-      FailedWith(AllOf(HasSubstr("reading TUSummary from"),
-                       HasSubstr("missing or invalid field 'tu_namespace'"))));
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading BuildNamespace from field 'tu_namespace'"),
+          HasSubstr("reading BuildNamespaceKind from field 'kind'"),
+          HasSubstr(
+              "invalid 'kind' BuildNamespaceKind value 'invalid_kind'"))));
 }
 
+// ============================================================================
+// JSONFormat::buildNamespaceFromJSON() Error Tests
+// ============================================================================
+
 TEST_F(JSONFormatTest, MissingKind) {
   auto Result = readJSON(R"({
     "tu_namespace": {
@@ -364,11 +323,13 @@ TEST_F(JSONFormatTest, MissingKind) {
     "data": []
   })");
 
-  EXPECT_THAT(Result, FailedWith(AllOf(
-                          HasSubstr("reading TUSummary from"),
-                          HasSubstr("failed to deserialize BuildNamespace"),
-                          HasSubstr("missing or invalid field 'kind' "
-                                    "(expected BuildNamespaceKind)"))));
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading BuildNamespace from field 'tu_namespace'"),
+          HasSubstr("failed to read BuildNamespaceKind from field 'kind'"),
+          HasSubstr("expected JSON string"))));
 }
 
 TEST_F(JSONFormatTest, MissingName) {
@@ -380,98 +341,144 @@ TEST_F(JSONFormatTest, MissingName) {
     "data": []
   })");
 
-  EXPECT_THAT(Result, FailedWith(AllOf(
-                          HasSubstr("reading TUSummary from"),
-                          HasSubstr("failed to deserialize BuildNamespace"),
-                          HasSubstr("missing or invalid field 'name' "
-                                    "(expected string)"))));
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading BuildNamespace from field 'tu_namespace'"),
+          HasSubstr("failed to read BuildNamespaceName from field 'name'"),
+          HasSubstr("expected JSON string"))));
 }
 
-TEST_F(JSONFormatTest, InvalidKind) {
-  auto Result = readJSON(R"({
-    "tu_namespace": {
-      "kind": "invalid_kind",
-      "name": "test.cpp"
-    },
-    "id_table": [],
-    "data": []
-  })");
-
-  EXPECT_THAT(Result, FailedWith(AllOf(
-                          HasSubstr("reading TUSummary from"),
-                          HasSubstr("failed to deserialize BuildNamespace"),
-                          HasSubstr("while parsing field 'kind'"),
-                          HasSubstr("invalid 'kind' BuildNamespaceKind "
-                                    "value"))));
-}
+// ============================================================================
+// JSONFormat::nestedBuildNamespaceFromJSON() Error Tests
+// ============================================================================
 
-TEST_F(JSONFormatTest, MissingIDTable) {
+TEST_F(JSONFormatTest, NamespaceElementNotObject) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": ["invalid"]
+        }
+      }
+    ],
     "data": []
   })");
 
-  EXPECT_THAT(Result, FailedWith(AllOf(
-                          HasSubstr("reading TUSummary from"),
-                          HasSubstr("missing or invalid field 'id_table'"))));
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(
+          AllOf(HasSubstr("reading TUSummary from file"),
+                HasSubstr("reading IdTable from field 'id_table'"),
+                HasSubstr("reading EntityIdTable entry from index '0'"),
+                HasSubstr("reading EntityName from field 'name'"),
+                HasSubstr("reading NesteBuildNamespace from field 'namespace'"),
+                HasSubstr("failed to read BuildNamespace from index '0'"),
+                HasSubstr("expected JSON object"))));
 }
 
-TEST_F(JSONFormatTest, MissingData) {
+// ============================================================================
+// JSONFormat::entityNameFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, EntityNameMissingUSR) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
-    "id_table": []
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "suffix": "",
+          "namespace": []
+        }
+      }
+    ],
+    "data": []
   })");
 
-  EXPECT_THAT(Result,
-              FailedWith(AllOf(HasSubstr("reading TUSummary from"),
-                               HasSubstr("missing or invalid field 'data'"))));
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(
+                  AllOf(HasSubstr("reading TUSummary from file"),
+                        HasSubstr("reading IdTable from field 'id_table'"),
+                        HasSubstr("reading EntityIdTable entry from index '0'"),
+                        HasSubstr("reading EntityName from field 'name'"),
+                        HasSubstr("failed to read USR from field 'usr'"),
+                        HasSubstr("expected JSON string"))));
 }
 
-// ============================================================================
-// ID Table Error Tests
-// ============================================================================
-
-TEST_F(JSONFormatTest, IDTableNotArray) {
+TEST_F(JSONFormatTest, EntityNameMissingSuffix) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
-    "id_table": {},
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "namespace": []
+        }
+      }
+    ],
     "data": []
   })");
 
-  EXPECT_THAT(Result, FailedWith(AllOf(
-                          HasSubstr("reading TUSummary from"),
-                          HasSubstr("missing or invalid field 'id_table'"))));
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(
+                  AllOf(HasSubstr("reading TUSummary from file"),
+                        HasSubstr("reading IdTable from field 'id_table'"),
+                        HasSubstr("reading EntityIdTable entry from index '0'"),
+                        HasSubstr("reading EntityName from field 'name'"),
+                        HasSubstr("failed to read Suffix from field 'suffix'"),
+                        HasSubstr("expected JSON string"))));
 }
 
-TEST_F(JSONFormatTest, IDTableElementNotObject) {
+TEST_F(JSONFormatTest, EntityNameMissingNamespace) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
-    "id_table": [123],
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": ""
+        }
+      }
+    ],
     "data": []
   })");
 
-  EXPECT_THAT(
+  EXPECT_THAT_EXPECTED(
       Result,
-      FailedWith(AllOf(
-          HasSubstr("reading TUSummary from"),
-          HasSubstr("failed to deserialize EntityIdTable"),
-          HasSubstr("element at index 0 is not a JSON object"),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading IdTable from field 'id_table'"),
+          HasSubstr("reading EntityIdTable entry from index '0'"),
+          HasSubstr("reading EntityName from field 'name'"),
           HasSubstr(
-              "(expected EntityIdTable entry with 'id' and 'name' fields)"))));
+              "failed to read NestedBuildNamespace from field 'namespace'"),
+          HasSubstr("expected JSON array"))));
 }
 
+// ============================================================================
+// JSONFormat::entityIdTableEntryFromJSON() Error Tests
+// ============================================================================
+
 TEST_F(JSONFormatTest, IDTableEntryMissingID) {
   auto Result = readJSON(R"({
     "tu_namespace": {
@@ -490,14 +497,13 @@ TEST_F(JSONFormatTest, IDTableEntryMissingID) {
     "data": []
   })");
 
-  EXPECT_THAT(Result,
-              FailedWith(AllOf(
-                  HasSubstr("reading TUSummary from"),
-                  HasSubstr("failed to read JSON array from field 'id_table'"),
-                  HasSubstr("failed to deserialize EntityIdTable at index 0"),
-                  HasSubstr("failed to deserialize EntityIdTable entry"),
-                  HasSubstr("missing or invalid field 'id' "
-                            "(expected unsigned integer EntityId)"))));
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(
+                  AllOf(HasSubstr("reading TUSummary from file"),
+                        HasSubstr("reading IdTable from field 'id_table'"),
+                        HasSubstr("reading EntityIdTable entry from index '0'"),
+                        HasSubstr("failed to read EntityId from field 'id'"),
+                        HasSubstr("expected JSON (unsigned 64-bit)"))));
 }
 
 TEST_F(JSONFormatTest, IDTableEntryMissingName) {
@@ -514,16 +520,13 @@ TEST_F(JSONFormatTest, IDTableEntryMissingName) {
     "data": []
   })");
 
-  EXPECT_THAT(
-      Result,
-      FailedWith(AllOf(
-          HasSubstr("reading TUSummary from"),
-          HasSubstr("failed to read JSON array from field 'id_table'"),
-          HasSubstr("failed to deserialize EntityIdTable at index 0"),
-          HasSubstr("failed to deserialize EntityIdTable entry"),
-          HasSubstr("failed to read JSON object from field 'name'"),
-          HasSubstr("missing or invalid field 'name' (expected EntityName JSON "
-                    "object)"))));
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(
+                  HasSubstr("reading TUSummary from file"),
+                  HasSubstr("reading IdTable from field 'id_table'"),
+                  HasSubstr("reading EntityIdTable entry from index '0'"),
+                  HasSubstr("failed to read EntityName from field 'name'"),
+                  HasSubstr("expected JSON object"))));
 }
 
 TEST_F(JSONFormatTest, IDTableEntryIDNotUInt64) {
@@ -545,15 +548,53 @@ TEST_F(JSONFormatTest, IDTableEntryIDNotUInt64) {
     "data": []
   })");
 
-  EXPECT_THAT(
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(
+                  AllOf(HasSubstr("reading TUSummary from file"),
+                        HasSubstr("reading IdTable from field 'id_table'"),
+                        HasSubstr("reading EntityIdTable entry from index '0'"),
+                        HasSubstr("failed to read EntityId from field 'id'"),
+                        HasSubstr("expected JSON integer (unsigned 64-bit)"))));
+}
+
+// ============================================================================
+// JSONFormat::entityIdTableFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, IDTableNotArray) {
+  auto Result = readJSON(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": {},
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(
+                  HasSubstr("reading TUSummary from file"),
+                  HasSubstr("failed to read IdTable from field 'id_table'"),
+                  HasSubstr("expected JSON array"))));
+}
+
+TEST_F(JSONFormatTest, IDTableElementNotObject) {
+  auto Result = readJSON(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [123],
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
       Result,
-      FailedWith(
-          AllOf(HasSubstr("reading TUSummary from"),
-                HasSubstr("failed to read JSON array from field 'id_table'"),
-                HasSubstr("failed to deserialize EntityIdTable at index 0"),
-                HasSubstr("failed to deserialize EntityIdTable entry"),
-                HasSubstr("field 'id' is not a valid unsigned 64-bit integer"),
-                HasSubstr("(expected non-negative EntityId value)"))));
+      FailedWithMessage(
+          AllOf(HasSubstr("reading TUSummary from file"),
+                HasSubstr("reading IdTable from field 'id_table'"),
+                HasSubstr("failed to read EntityIdTable entry from index '0'"),
+                HasSubstr("expected JSON object"))));
 }
 
 TEST_F(JSONFormatTest, DuplicateEntity) {
@@ -593,181 +634,191 @@ TEST_F(JSONFormatTest, DuplicateEntity) {
     "data": []
   })");
 
-  EXPECT_THAT(Result, FailedWith(AllOf(
-                          HasSubstr("reading TUSummary from"),
-                          HasSubstr("failed to deserialize EntityIdTable"),
-                          HasSubstr("duplicate EntityName found at index"),
-                          HasSubstr("(EntityId=0 already exists in table)"))));
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(
+          AllOf(HasSubstr("reading TUSummary from file"),
+                HasSubstr("reading IdTable from field 'id_table'"),
+                HasSubstr("failed to insert EntityIdTable entry at index '1'"),
+                HasSubstr("encountered duplicate EntityId '0'"))));
 }
 
 // ============================================================================
-// Entity Name Error Tests
+// JSONFormat::entitySummaryFromJSON() Error Tests
 // ============================================================================
 
-TEST_F(JSONFormatTest, EntityNameMissingUSR) {
+TEST_F(JSONFormatTest, EntitySummaryNoFormatInfo) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
-    "id_table": [
+    "id_table": [],
+    "data": [
       {
-        "id": 0,
-        "name": {
-          "suffix": "",
-          "namespace": []
-        }
+        "summary_name": "unknown_summary_type",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {}
+          }
+        ]
       }
-    ],
-    "data": []
+    ]
   })");
 
-  EXPECT_THAT(Result,
-              FailedWith(AllOf(
-                  HasSubstr("reading TUSummary from"),
-                  HasSubstr("failed to read JSON array from field 'id_table'"),
-                  HasSubstr("failed to deserialize EntityIdTable at index 0"),
-                  HasSubstr("failed to deserialize EntityIdTable entry"),
-                  HasSubstr("failed to read JSON object from field 'name'"),
-                  HasSubstr("failed to deserialize EntityName"),
-                  HasSubstr("missing or invalid field 'usr' "
-                            "(expected string (Unified Symbol Resolution))"))));
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("reading EntitySummary entry from index '0'"),
+          HasSubstr("reading EntitySummary from field 'entity_summary'"),
+          HasSubstr("failed to deserialize EntitySummary"),
+          HasSubstr(
+              "no FormatInfo registered for summary 'unknown_summary_type'"))));
 }
 
-TEST_F(JSONFormatTest, EntityNameMissingSuffix) {
+TEST_F(JSONFormatTest, TestAnalysisMissingField) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
-    "id_table": [
+    "id_table": [],
+    "data": [
       {
-        "id": 0,
-        "name": {
-          "usr": "c:@F at foo",
-          "namespace": []
-        }
+        "summary_name": "test_summary",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {}
+          }
+        ]
       }
-    ],
-    "data": []
+    ]
   })");
 
-  EXPECT_THAT(Result,
-              FailedWith(AllOf(
-                  HasSubstr("reading TUSummary from"),
-                  HasSubstr("failed to read JSON array from field 'id_table'"),
-                  HasSubstr("failed to deserialize EntityIdTable at "
-                            "index 0"),
-                  HasSubstr("failed to deserialize EntityIdTable entry"),
-                  HasSubstr("failed to read JSON object from field 'name'"),
-                  HasSubstr("failed to deserialize EntityName"),
-                  HasSubstr("missing or invalid field 'suffix' "
-                            "(expected string)"))));
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("reading EntitySummary entry from index '0'"),
+          HasSubstr("reading EntitySummary from field 'entity_summary'"),
+          HasSubstr("missing or invalid field 'pairs'"))));
 }
 
-TEST_F(JSONFormatTest, EntityNameMissingNamespace) {
+TEST_F(JSONFormatTest, TestAnalysisInvalidPair) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
-    "id_table": [
+    "id_table": [],
+    "data": [
       {
-        "id": 0,
-        "name": {
-          "usr": "c:@F at foo",
-          "suffix": ""
-        }
+        "summary_name": "test_summary",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": [
+                {
+                  "first": 0,
+                  "second": "not_a_number"
+                }
+              ]
+            }
+          }
+        ]
       }
-    ],
-    "data": []
+    ]
   })");
 
-  EXPECT_THAT(
+  EXPECT_THAT_EXPECTED(
       Result,
-      FailedWith(
-          AllOf(HasSubstr("reading TUSummary from"),
-                HasSubstr("failed to read JSON array from field 'id_table'"),
-                HasSubstr("failed to deserialize EntityIdTable at index 0"),
-                HasSubstr("failed to deserialize EntityIdTable entry"),
-                HasSubstr("failed to read JSON object from field 'name'"),
-                HasSubstr("failed to deserialize EntityName"),
-                HasSubstr("failed to read JSON array from field 'namespace'"),
-                HasSubstr("missing or invalid field 'namespace'"),
-                HasSubstr("(expected JSON array of BuildNamespace objects)"))));
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("reading EntitySummary entry from index '0'"),
+          HasSubstr("reading EntitySummary from field 'entity_summary'"),
+          HasSubstr("missing or invalid 'second' field at index '0'"))));
 }
 
-TEST_F(JSONFormatTest, NamespaceElementNotObject) {
+// ============================================================================
+// JSONFormat::entityDataMapEntryFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, EntityDataMissingEntityID) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
-    "id_table": [
+    "id_table": [],
+    "data": [
       {
-        "id": 0,
-        "name": {
-          "usr": "c:@F at foo",
-          "suffix": "",
-          "namespace": ["invalid"]
-        }
+        "summary_name": "test_summary",
+        "summary_data": [
+          {
+            "entity_summary": {}
+          }
+        ]
       }
-    ],
-    "data": []
-  })");
-
-  EXPECT_THAT(Result,
-              FailedWith(AllOf(
-                  HasSubstr("reading TUSummary from"),
-                  HasSubstr("failed to read JSON array from field 'id_table'"),
-                  HasSubstr("failed to deserialize EntityIdTable at index 0"),
-                  HasSubstr("failed to deserialize EntityIdTable entry"),
-                  HasSubstr("failed to read JSON object from field 'name'"),
-                  HasSubstr("failed to deserialize EntityName"),
-                  HasSubstr("failed to read JSON array from field 'namespace'"),
-                  HasSubstr("failed to deserialize NestedBuildNamespace"),
-                  HasSubstr("element at index 0 is not a JSON object"))));
-}
-
-// ============================================================================
-// Data Array Error Tests
-// ============================================================================
-
-TEST_F(JSONFormatTest, DataNotArray) {
-  auto Result = readJSON(R"({
-    "tu_namespace": {
-      "kind": "compilation_unit",
-      "name": "test.cpp"
-    },
-    "id_table": [],
-    "data": {}
+    ]
   })");
 
-  EXPECT_THAT(Result,
-              FailedWith(AllOf(HasSubstr("reading TUSummary from"),
-                               HasSubstr("missing or invalid field 'data'"))));
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("reading EntitySummary entry from index '0'"),
+          HasSubstr("failed to read EntityId from field 'entity_id'"),
+          HasSubstr("expected JSON integer"))));
 }
 
-TEST_F(JSONFormatTest, DataElementNotObject) {
+TEST_F(JSONFormatTest, EntityDataMissingEntitySummary) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
     "id_table": [],
-    "data": ["invalid"]
+    "data": [
+      {
+        "summary_name": "test_summary",
+        "summary_data": [
+          {
+            "entity_id": 0
+          }
+        ]
+      }
+    ]
   })");
 
-  EXPECT_THAT(Result, FailedWith(AllOf(
-                          HasSubstr("reading TUSummary from"),
-                          HasSubstr("failed to deserialize SummaryDataMap"),
-                          HasSubstr("element at index 0 is not a JSON object"),
-                          HasSubstr("(expected SummaryDataMap entry with "
-                                    "'summary_name' and 'summary_data'"),
-                          HasSubstr("fields)"))));
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("reading EntitySummary entry from index '0'"),
+          HasSubstr("failed to read EntitySummary from field 'entity_summary'"),
+          HasSubstr("expected JSON object"))));
 }
 
-TEST_F(JSONFormatTest, DataEntryMissingSummaryName) {
+TEST_F(JSONFormatTest, EntityIDNotUInt64) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
@@ -776,24 +827,34 @@ TEST_F(JSONFormatTest, DataEntryMissingSummaryName) {
     "id_table": [],
     "data": [
       {
-        "summary_data": []
+        "summary_name": "test_summary",
+        "summary_data": [
+          {
+            "entity_id": "not_a_number",
+            "entity_summary": {}
+          }
+        ]
       }
     ]
   })");
 
-  EXPECT_THAT(
+  EXPECT_THAT_EXPECTED(
       Result,
-      FailedWith(
-          AllOf(HasSubstr("reading TUSummary from"),
-                HasSubstr("failed to read JSON array from field 'data'"),
-                HasSubstr("failed to deserialize SummaryDataMap at "
-                          "index 0"),
-                HasSubstr("failed to deserialize SummaryDataMap entry"),
-                HasSubstr("missing or invalid field 'summary_name' "
-                          "(expected string (analysis summary identifier))"))));
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("reading EntitySummary entry from index '0'"),
+          HasSubstr("failed to read EntityId from field 'entity_id'"),
+          HasSubstr("expected JSON integer"))));
 }
 
-TEST_F(JSONFormatTest, DataEntryMissingData) {
+// ============================================================================
+// JSONFormat::entityDataMapFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, EntityDataElementNotObject) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
@@ -802,51 +863,76 @@ TEST_F(JSONFormatTest, DataEntryMissingData) {
     "id_table": [],
     "data": [
       {
-        "summary_name": "test_summary"
+        "summary_name": "test_summary",
+        "summary_data": ["invalid"]
       }
     ]
   })");
 
-  EXPECT_THAT(Result,
-              FailedWith(AllOf(
-                  HasSubstr("reading TUSummary from"),
-                  HasSubstr("failed to read JSON array from field 'data'"),
-                  HasSubstr("failed to deserialize SummaryDataMap at index 0"),
-                  HasSubstr("failed to deserialize SummaryDataMap entry"),
-                  HasSubstr("missing or invalid field 'summary_data'"))));
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("failed to read EntitySummary entry from index '0'"),
+          HasSubstr("expected JSON object"))));
 }
 
-TEST_F(JSONFormatTest, DuplicateSummaryName) {
+TEST_F(JSONFormatTest, DuplicateEntityIdInDataMap) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
-    "id_table": [],
-    "data": [
+    "id_table": [
       {
-        "summary_name": "test_summary",
-        "summary_data": []
-      },
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": []
+        }
+      }
+    ],
+    "data": [
       {
         "summary_name": "test_summary",
-        "summary_data": []
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": []
+            }
+          },
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": []
+            }
+          }
+        ]
       }
     ]
   })");
 
-  EXPECT_THAT(Result, FailedWith(AllOf(
-                          HasSubstr("reading TUSummary from"),
-                          HasSubstr("failed to deserialize SummaryDataMap"),
-                          HasSubstr("duplicate SummaryName 'test_summary' "
-                                    "found at index"))));
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("failed to insert EntitySummary entry at index '1'"),
+          HasSubstr("encountered duplicate EntityId '0'"))));
 }
 
 // ============================================================================
-// Entity Data Error Tests
+// JSONFormat::summaryDataMapEntryFromJSON() Error Tests
 // ============================================================================
 
-TEST_F(JSONFormatTest, EntityDataElementNotObject) {
+TEST_F(JSONFormatTest, DataEntryMissingSummaryName) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
@@ -855,29 +941,22 @@ TEST_F(JSONFormatTest, EntityDataElementNotObject) {
     "id_table": [],
     "data": [
       {
-        "summary_name": "test_summary",
-        "summary_data": ["invalid"]
+        "summary_data": []
       }
     ]
   })");
 
-  EXPECT_THAT(
+  EXPECT_THAT_EXPECTED(
       Result,
-      FailedWith(AllOf(
-          HasSubstr("reading TUSummary from"),
-          HasSubstr("failed to read JSON array from field 'data'"),
-          HasSubstr("failed to deserialize SummaryDataMap at index 0"),
-          HasSubstr("failed to deserialize SummaryDataMap entry"),
-          HasSubstr("for summary 'test_summary'"),
-          HasSubstr("failed to read JSON array from field 'summary_data'"),
-          HasSubstr("failed to deserialize EntityDataMap"),
-          HasSubstr("element at index 0 is not a JSON object"),
-          HasSubstr("(expected EntityDataMap entry with 'entity_id' and "
-                    "'entity_summary'"),
-          HasSubstr("fields)"))));
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("failed to read SummaryName from field 'summary_name'"),
+          HasSubstr("expected JSON string"))));
 }
 
-TEST_F(JSONFormatTest, EntityDataMissingEntityID) {
+TEST_F(JSONFormatTest, DataEntryMissingData) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
@@ -886,102 +965,63 @@ TEST_F(JSONFormatTest, EntityDataMissingEntityID) {
     "id_table": [],
     "data": [
       {
-        "summary_name": "test_summary",
-        "summary_data": [
-          {
-            "entity_summary": {}
-          }
-        ]
+        "summary_name": "test_summary"
       }
     ]
   })");
 
-  EXPECT_THAT(
+  EXPECT_THAT_EXPECTED(
       Result,
-      FailedWith(AllOf(
-          HasSubstr("reading TUSummary from"),
-          HasSubstr("failed to read JSON array from field 'data'"),
-          HasSubstr("failed to deserialize SummaryDataMap at index 0"),
-          HasSubstr("failed to deserialize SummaryDataMap entry"),
-          HasSubstr("for summary 'test_summary'"),
-          HasSubstr("failed to read JSON array from field 'summary_data'"),
-          HasSubstr("failed to deserialize EntityDataMap at index 0"),
-          HasSubstr("failed to deserialize EntityDataMap entry"),
-          HasSubstr("missing or invalid field 'entity_id' "
-                    "(expected unsigned integer EntityId)"))));
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr(
+              "failed to read EntitySummary entries from field 'summary_data'"),
+          HasSubstr("expected JSON array"))));
 }
 
-TEST_F(JSONFormatTest, EntityDataMissingEntitySummary) {
+// ============================================================================
+// JSONFormat::summaryDataMapFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, DataNotArray) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
     "id_table": [],
-    "data": [
-      {
-        "summary_name": "test_summary",
-        "summary_data": [
-          {
-            "entity_id": 0
-          }
-        ]
-      }
-    ]
+    "data": {}
   })");
 
-  EXPECT_THAT(
+  EXPECT_THAT_EXPECTED(
       Result,
-      FailedWith(AllOf(
-          HasSubstr("reading TUSummary from"),
-          HasSubstr("failed to read JSON array from field 'data'"),
-          HasSubstr("failed to deserialize SummaryDataMap at index 0"),
-          HasSubstr("failed to deserialize SummaryDataMap entry"),
-          HasSubstr("for summary 'test_summary'"),
-          HasSubstr("failed to read JSON array from field 'summary_data'"),
-          HasSubstr("failed to deserialize EntityDataMap at index 0"),
-          HasSubstr("failed to deserialize EntityDataMap entry"),
-          HasSubstr("failed to read JSON object from field 'entity_summary'"),
-          HasSubstr("missing or invalid field 'entity_summary'"),
-          HasSubstr("(expected EntitySummary JSON object)"))));
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("failed to read SummaryData entries from field 'data'"),
+          HasSubstr("expected JSON array"))));
 }
 
-TEST_F(JSONFormatTest, EntityIDNotUInt64) {
+TEST_F(JSONFormatTest, DataElementNotObject) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
     "id_table": [],
-    "data": [
-      {
-        "summary_name": "test_summary",
-        "summary_data": [
-          {
-            "entity_id": "not_a_number",
-            "entity_summary": {}
-          }
-        ]
-      }
-    ]
+    "data": ["invalid"]
   })");
 
-  EXPECT_THAT(
-      Result,
-      FailedWith(AllOf(
-          HasSubstr("reading TUSummary from"),
-          HasSubstr("failed to read JSON array from field 'data'"),
-          HasSubstr("failed to deserialize SummaryDataMap at index 0"),
-          HasSubstr("failed to deserialize SummaryDataMap entry"),
-          HasSubstr("for summary 'test_summary'"),
-          HasSubstr("failed to read JSON array from field 'summary_data'"),
-          HasSubstr("failed to deserialize EntityDataMap at index 0"),
-          HasSubstr("failed to deserialize EntityDataMap entry"),
-          HasSubstr("field 'entity_id' is not a valid unsigned 64-bit integer"),
-          HasSubstr("(expected non-negative EntityId value)"))));
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(
+                  HasSubstr("reading TUSummary from file"),
+                  HasSubstr("reading SummaryData entries from field 'data'"),
+                  HasSubstr("failed to read SummaryData entry from index '0'"),
+                  HasSubstr("expected JSON object"))));
 }
 
-TEST_F(JSONFormatTest, EntitySummaryNoFormatInfo) {
+TEST_F(JSONFormatTest, DuplicateSummaryName) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
@@ -990,126 +1030,174 @@ TEST_F(JSONFormatTest, EntitySummaryNoFormatInfo) {
     "id_table": [],
     "data": [
       {
-        "summary_name": "unknown_summary_type",
-        "summary_data": [
-          {
-            "entity_id": 0,
-            "entity_summary": {}
-          }
-        ]
+        "summary_name": "test_summary",
+        "summary_data": []
+      },
+      {
+        "summary_name": "test_summary",
+        "summary_data": []
       }
     ]
   })");
 
-  EXPECT_THAT(
+  EXPECT_THAT_EXPECTED(
       Result,
-      FailedWith(AllOf(
-          HasSubstr("reading TUSummary from"),
-          HasSubstr("failed to read JSON array from field 'data'"),
-          HasSubstr("failed to deserialize SummaryDataMap at index 0"),
-          HasSubstr("failed to deserialize SummaryDataMap entry"),
-          HasSubstr("for summary 'unknown_summary_type'"),
-          HasSubstr("failed to read JSON array from field 'summary_data'"),
-          HasSubstr("failed to deserialize EntityDataMap at index 0"),
-          HasSubstr("failed to deserialize EntityDataMap entry"),
-          HasSubstr("failed to read JSON object from field 'entity_summary'"),
-          HasSubstr("failed to deserialize EntitySummary"),
-          HasSubstr("no FormatInfo was registered for summary name: "
-                    "unknown_summary_type"))));
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("failed to insert SummaryData entry at index '1'"),
+          HasSubstr("encountered duplicate SummaryName 'test_summary'"))));
 }
 
 // ============================================================================
-// Analysis-Specific Error Tests - TestAnalysis
+// JSONFormat::readTUSummary() Error Tests
 // ============================================================================
 
-TEST_F(JSONFormatTest, TestAnalysisMissingField) {
+TEST_F(JSONFormatTest, MissingTUNamespace) {
+  auto Result = readJSON(R"({
+    "id_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("failed to read BuildNamespace from field 'tu_namespace'"),
+          HasSubstr("expected JSON object"))));
+}
+
+TEST_F(JSONFormatTest, MissingIDTable) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
-    "id_table": [],
-    "data": [
-      {
-        "summary_name": "test_summary",
-        "summary_data": [
-          {
-            "entity_id": 0,
-            "entity_summary": {}
-          }
-        ]
-      }
-    ]
+    "data": []
   })");
 
-  EXPECT_THAT(
-      Result,
-      FailedWith(AllOf(
-          HasSubstr("reading TUSummary from"),
-          HasSubstr("failed to read JSON array from field 'data'"),
-          HasSubstr("failed to deserialize SummaryDataMap at "
-                    "index 0"),
-          HasSubstr("failed to deserialize SummaryDataMap entry"),
-          HasSubstr("for summary 'test_summary'"),
-          HasSubstr("failed to read JSON array from field 'summary_data'"),
-          HasSubstr("failed to deserialize EntityDataMap at "
-                    "index 0"),
-          HasSubstr("failed to deserialize EntityDataMap entry"),
-          HasSubstr("failed to read JSON object from field 'entity_summary'"),
-          HasSubstr("missing required field 'pairs'"))));
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(
+                  HasSubstr("reading TUSummary from file"),
+                  HasSubstr("failed to read IdTable from field 'id_table'"),
+                  HasSubstr("expected JSON array"))));
 }
 
-TEST_F(JSONFormatTest, TestAnalysisInvalidPair) {
+TEST_F(JSONFormatTest, MissingData) {
   auto Result = readJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
     },
-    "id_table": [],
-    "data": [
-      {
-        "summary_name": "test_summary",
-        "summary_data": [
-          {
-            "entity_id": 0,
-            "entity_summary": {
-              "pairs": [
-                {
-                  "first": 0,
-                  "second": "not_a_number"
-                }
-              ]
-            }
-          }
-        ]
-      }
-    ]
+    "id_table": []
   })");
 
-  EXPECT_THAT(
+  EXPECT_THAT_EXPECTED(
       Result,
-      FailedWith(AllOf(
-          HasSubstr("reading TUSummary from"),
-          HasSubstr("failed to read JSON array from field 'data'"),
-          HasSubstr("failed to deserialize SummaryDataMap at "
-                    "index 0"),
-          HasSubstr("failed to deserialize SummaryDataMap entry"),
-          HasSubstr("for summary 'test_summary'"),
-          HasSubstr("failed to read JSON array from field 'summary_data'"),
-          HasSubstr("failed to deserialize EntityDataMap at "
-                    "index 0"),
-          HasSubstr("failed to deserialize EntityDataMap entry"),
-          HasSubstr("failed to read JSON object from field 'entity_summary'"),
-          HasSubstr("missing or invalid 'second' field at "
-                    "index 0"))));
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("failed to read SummaryData entries from field 'data'"),
+          HasSubstr("expected JSON array"))));
 }
 
 // ============================================================================
-// Valid Configuration Tests
+// JSONFormat::writeJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, WriteFileAlreadyExists) {
+  SmallString<128> FilePath = TestDir;
+  sys::path::append(FilePath, "existing.json");
+
+  // Create an existing file
+  std::error_code EC;
+  raw_fd_ostream OS(FilePath, EC);
+  ASSERT_FALSE(EC) << "Failed to create file: " << EC.message();
+  OS << "{}";
+  OS.close();
+
+  // Try to write to the same path
+  TUSummary Summary(
+      BuildNamespace(BuildNamespaceKind::CompilationUnit, "test.cpp"));
+  JSONFormat Format(vfs::getRealFileSystem());
+  auto Result = Format.writeTUSummary(Summary, FilePath);
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(HasSubstr("writing TUSummary to file"),
+                              HasSubstr("failed to write file"),
+                              HasSubstr("file already exists"))));
+}
+
+TEST_F(JSONFormatTest, WriteParentDirectoryNotFound) {
+  SmallString<128> FilePath = TestDir;
+  sys::path::append(FilePath, "nonexistent_dir", "test.json");
+
+  TUSummary Summary(
+      BuildNamespace(BuildNamespaceKind::CompilationUnit, "test.cpp"));
+  JSONFormat Format(vfs::getRealFileSystem());
+  auto Result = Format.writeTUSummary(Summary, FilePath);
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(HasSubstr("writing TUSummary to file"),
+                              HasSubstr("failed to write file"),
+                              HasSubstr("parent directory does not exist"))));
+}
+
+TEST_F(JSONFormatTest, WriteNotJsonExtension) {
+  SmallString<128> FilePath = TestDir;
+  sys::path::append(FilePath, "test.txt");
+
+  TUSummary Summary(
+      BuildNamespace(BuildNamespaceKind::CompilationUnit, "test.cpp"));
+  JSONFormat Format(vfs::getRealFileSystem());
+  auto Result = Format.writeTUSummary(Summary, FilePath);
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("writing TUSummary to file"),
+                HasSubstr("failed to write file"),
+                HasSubstr("file does not end with '.json' extension"))));
+}
+
+TEST_F(JSONFormatTest, WriteStreamOpenFailure) {
+#ifndef _WIN32 // Skip on Windows as permission model is different
+  SmallString<128> DirPath = TestDir;
+  sys::path::append(DirPath, "write_protected_dir");
+
+  // Create a directory without write permissions
+  std::error_code EC = sys::fs::create_directory(DirPath);
+  ASSERT_FALSE(EC) << "Failed to create directory: " << EC.message();
+
+  sys::fs::perms Perms = sys::fs::perms::owner_read | sys::fs::perms::owner_exe;
+  EC = sys::fs::setPermissions(DirPath, Perms);
+  ASSERT_FALSE(EC) << "Failed to set permissions: " << EC.message();
+
+  SmallString<128> FilePath = DirPath;
+  sys::path::append(FilePath, "test.json");
+
+  TUSummary Summary(
+      BuildNamespace(BuildNamespaceKind::CompilationUnit, "test.cpp"));
+  JSONFormat Format(vfs::getRealFileSystem());
+  auto Result = Format.writeTUSummary(Summary, FilePath);
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(HasSubstr("writing TUSummary to file"),
+                              HasSubstr("failed to write file"))));
+
+  // Restore permissions for cleanup
+  sys::fs::setPermissions(DirPath, sys::fs::perms::all_all);
+#endif
+}
+
+// ============================================================================
+// Round-Trip Tests - Serialization Verification
 // ============================================================================
 
 TEST_F(JSONFormatTest, Empty) {
-  auto Result = readJSON(R"({
+  readWriteJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -1117,12 +1205,10 @@ TEST_F(JSONFormatTest, Empty) {
     "id_table": [],
     "data": []
   })");
-
-  EXPECT_THAT(Result, Succeeded());
 }
 
 TEST_F(JSONFormatTest, LinkUnit) {
-  auto Result = readJSON(R"({
+  readWriteJSON(R"({
     "tu_namespace": {
       "kind": "link_unit",
       "name": "libtest.so"
@@ -1130,12 +1216,10 @@ TEST_F(JSONFormatTest, LinkUnit) {
     "id_table": [],
     "data": []
   })");
-
-  EXPECT_THAT(Result, Succeeded());
 }
 
 TEST_F(JSONFormatTest, WithIDTable) {
-  auto Result = readJSON(R"({
+  readWriteJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -1174,12 +1258,10 @@ TEST_F(JSONFormatTest, WithIDTable) {
     ],
     "data": []
   })");
-
-  EXPECT_THAT(Result, Succeeded());
 }
 
 TEST_F(JSONFormatTest, WithEmptyDataEntry) {
-  auto Result = readJSON(R"({
+  readWriteJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -1192,27 +1274,10 @@ TEST_F(JSONFormatTest, WithEmptyDataEntry) {
       }
     ]
   })");
-
-  EXPECT_THAT(Result, Succeeded());
-}
-
-// ============================================================================
-// Round-Trip Tests
-// ============================================================================
-
-TEST_F(JSONFormatTest, RoundTripEmpty) {
-  testRoundTrip(R"({
-    "tu_namespace": {
-      "kind": "compilation_unit",
-      "name": "test.cpp"
-    },
-    "id_table": [],
-    "data": []
-  })");
 }
 
 TEST_F(JSONFormatTest, RoundTripWithIDTable) {
-  testRoundTrip(R"({
+  readWriteJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"
@@ -1236,23 +1301,8 @@ TEST_F(JSONFormatTest, RoundTripWithIDTable) {
   })");
 }
 
-TEST_F(JSONFormatTest, RoundTripLinkUnit) {
-  testRoundTrip(R"({
-    "tu_namespace": {
-      "kind": "link_unit",
-      "name": "libtest.so"
-    },
-    "id_table": [],
-    "data": []
-  })");
-}
-
-// ============================================================================
-// Analysis-Specific Round-Trip Tests
-// ============================================================================
-
 TEST_F(JSONFormatTest, RoundTripTestAnalysis) {
-  testRoundTrip(R"({
+  readWriteJSON(R"({
     "tu_namespace": {
       "kind": "compilation_unit",
       "name": "test.cpp"



More information about the cfe-commits mailing list