[clang] [clang][ssaf] Add `JSONFormat` serialization support for `LUSummary` and `LUSummaryEncoding` (PR #184037)

Aviral Goel via cfe-commits cfe-commits at lists.llvm.org
Mon Mar 2 07:57:09 PST 2026


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

>From 11f7eb4d9959f65a558b2cba955f38bcf0ad671b Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 27 Feb 2026 13:05:06 -0800
Subject: [PATCH 1/9] Introduce APIs

---
 .../Scalable/Serialization/JSONFormat.h       | 11 ++++++++
 .../Serialization/SerializationFormat.h       | 12 +++++++++
 clang/lib/Analysis/Scalable/CMakeLists.txt    |  2 ++
 .../Analysis/Scalable/CMakeLists.txt          |  1 +
 .../Registries/MockSerializationFormat.cpp    | 25 +++++++++++++++++++
 .../Registries/MockSerializationFormat.h      | 11 ++++++++
 6 files changed, 62 insertions(+)

diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
index f69dfd6444685..d93fe41f67972 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -60,6 +60,17 @@ class JSONFormat final : public SerializationFormat {
   llvm::Error writeTUSummaryEncoding(const TUSummaryEncoding &SummaryEncoding,
                                      llvm::StringRef Path) override;
 
+  llvm::Expected<LUSummary> readLUSummary(llvm::StringRef Path) override;
+
+  llvm::Error writeLUSummary(const LUSummary &Summary,
+                             llvm::StringRef Path) override;
+
+  llvm::Expected<LUSummaryEncoding>
+  readLUSummaryEncoding(llvm::StringRef Path) override;
+
+  llvm::Error writeLUSummaryEncoding(const LUSummaryEncoding &SummaryEncoding,
+                                     llvm::StringRef Path) override;
+
   using SerializerFn = llvm::function_ref<Object(const EntitySummary &,
                                                  const EntityIdConverter &)>;
   using DeserializerFn =
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index 784e239f4d7a8..c86ca7ef960e9 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -42,6 +42,18 @@ class SerializationFormat {
   writeTUSummaryEncoding(const TUSummaryEncoding &SummaryEncoding,
                          llvm::StringRef Path) = 0;
 
+  virtual llvm::Expected<LUSummary> readLUSummary(llvm::StringRef Path) = 0;
+
+  virtual llvm::Error writeLUSummary(const LUSummary &Summary,
+                                     llvm::StringRef Path) = 0;
+
+  virtual llvm::Expected<LUSummaryEncoding>
+  readLUSummaryEncoding(llvm::StringRef Path) = 0;
+
+  virtual llvm::Error
+  writeLUSummaryEncoding(const LUSummaryEncoding &SummaryEncoding,
+                         llvm::StringRef Path) = 0;
+
 protected:
   // Helpers providing access to implementation details of basic data structures
   // for efficient serialization/deserialization.
diff --git a/clang/lib/Analysis/Scalable/CMakeLists.txt b/clang/lib/Analysis/Scalable/CMakeLists.txt
index 5d195174d9f9d..81550df4565cb 100644
--- a/clang/lib/Analysis/Scalable/CMakeLists.txt
+++ b/clang/lib/Analysis/Scalable/CMakeLists.txt
@@ -12,6 +12,8 @@ add_clang_library(clangAnalysisScalable
   Model/EntityName.cpp
   Model/SummaryName.cpp
   Serialization/JSONFormat/JSONFormatImpl.cpp
+  Serialization/JSONFormat/LUSummary.cpp
+  Serialization/JSONFormat/LUSummaryEncoding.cpp
   Serialization/JSONFormat/TUSummary.cpp
   Serialization/JSONFormat/TUSummaryEncoding.cpp
   Serialization/SerializationFormatRegistry.cpp
diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt b/clang/unittests/Analysis/Scalable/CMakeLists.txt
index 5c92b49087f65..379e97cd67251 100644
--- a/clang/unittests/Analysis/Scalable/CMakeLists.txt
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -15,6 +15,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
   Registries/MockSummaryExtractor2.cpp
   Registries/SerializationFormatRegistryTest.cpp
   Registries/SummaryExtractorRegistryTest.cpp
+  Serialization/JSONFormatTest/LUSummaryTest.cpp
   Serialization/JSONFormatTest/TUSummaryTest.cpp
   SummaryNameTest.cpp
   TestFixture.cpp
diff --git a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
index d734009b9d42c..c792de5d380f8 100644
--- a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
@@ -7,6 +7,9 @@
 //===----------------------------------------------------------------------===//
 
 #include "Registries/MockSerializationFormat.h"
+#include "clang/Analysis/Scalable/EntityLinker/LUSummary.h"
+#include "clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h"
+#include "clang/Analysis/Scalable/EntityLinker/TUSummary.h"
 #include "clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h"
 #include "clang/Analysis/Scalable/Model/BuildNamespace.h"
 #include "clang/Analysis/Scalable/Model/EntityName.h"
@@ -167,3 +170,25 @@ llvm::Error MockSerializationFormat::writeTUSummaryEncoding(
   llvm_unreachable(
       "MockSerializationFormat does not support TUSummaryEncoding");
 }
+
+llvm::Expected<LUSummary>
+MockSerializationFormat::readLUSummary(llvm::StringRef Path) {
+  llvm_unreachable("MockSerializationFormat does not support LUSummary");
+}
+
+llvm::Error MockSerializationFormat::writeLUSummary(const LUSummary &Summary,
+                                                    llvm::StringRef Path) {
+  llvm_unreachable("MockSerializationFormat does not support LUSummary");
+}
+
+llvm::Expected<LUSummaryEncoding>
+MockSerializationFormat::readLUSummaryEncoding(llvm::StringRef Path) {
+  llvm_unreachable(
+      "MockSerializationFormat does not support LUSummaryEncoding");
+}
+
+llvm::Error MockSerializationFormat::writeLUSummaryEncoding(
+    const LUSummaryEncoding &SummaryEncoding, llvm::StringRef Path) {
+  llvm_unreachable(
+      "MockSerializationFormat does not support LUSummaryEncoding");
+}
diff --git a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h
index 7ec0de9216be0..06495f4a83929 100644
--- a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h
+++ b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h
@@ -33,6 +33,17 @@ class MockSerializationFormat final : public SerializationFormat {
   llvm::Error writeTUSummaryEncoding(const TUSummaryEncoding &SummaryEncoding,
                                      llvm::StringRef Path) override;
 
+  llvm::Expected<LUSummary> readLUSummary(llvm::StringRef Path) override;
+
+  llvm::Error writeLUSummary(const LUSummary &Summary,
+                             llvm::StringRef Path) override;
+
+  llvm::Expected<LUSummaryEncoding>
+  readLUSummaryEncoding(llvm::StringRef Path) override;
+
+  llvm::Error writeLUSummaryEncoding(const LUSummaryEncoding &SummaryEncoding,
+                                     llvm::StringRef Path) override;
+
   struct SpecialFileRepresentation {
     std::string MockRepresentation;
   };

>From 73ed82793ab091ea67cb7119a0453dc55e2fc45d Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 27 Feb 2026 13:10:14 -0800
Subject: [PATCH 2/9] Add core data structures

---
 .../Serialization/JSONFormat/LUSummary.cpp    | 163 +++++++++
 .../JSONFormat/LUSummaryEncoding.cpp          | 160 ++++++++
 .../Serialization/JSONFormat/TUSummary.cpp    | 341 ------------------
 .../JSONFormat/TUSummaryEncoding.cpp          | 247 -------------
 4 files changed, 323 insertions(+), 588 deletions(-)
 create mode 100644 clang/lib/Analysis/Scalable/Serialization/JSONFormat/LUSummary.cpp
 create mode 100644 clang/lib/Analysis/Scalable/Serialization/JSONFormat/LUSummaryEncoding.cpp

diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/LUSummary.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/LUSummary.cpp
new file mode 100644
index 0000000000000..dc05ef5e4e07d
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/LUSummary.cpp
@@ -0,0 +1,163 @@
+//===- LUSummary.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 "JSONFormatImpl.h"
+
+#include "clang/Analysis/Scalable/EntityLinker/LUSummary.h"
+
+namespace clang::ssaf {
+
+//----------------------------------------------------------------------------
+// LUSummary
+//----------------------------------------------------------------------------
+
+llvm::Expected<LUSummary> JSONFormat::readLUSummary(llvm::StringRef Path) {
+  auto ExpectedJSON = readJSON(Path);
+  if (!ExpectedJSON) {
+    return ErrorBuilder::wrap(ExpectedJSON.takeError())
+        .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+        .build();
+  }
+
+  Object *RootObjectPtr = ExpectedJSON->getAsObject();
+  if (!RootObjectPtr) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObject, "LUSummary",
+                                "object")
+        .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+        .build();
+  }
+
+  const Object &RootObject = *RootObjectPtr;
+
+  const Array *LUNamespaceArray = RootObject.getArray("lu_namespace");
+  if (!LUNamespaceArray) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "NestedBuildNamespace", "lu_namespace", "array")
+        .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+        .build();
+  }
+
+  auto ExpectedLUNamespace = nestedBuildNamespaceFromJSON(*LUNamespaceArray);
+  if (!ExpectedLUNamespace) {
+    return ErrorBuilder::wrap(ExpectedLUNamespace.takeError())
+        .context(ErrorMessages::ReadingFromField, "NestedBuildNamespace",
+                 "lu_namespace")
+        .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+        .build();
+  }
+
+  LUSummary Summary(std::move(*ExpectedLUNamespace));
+
+  {
+    const Array *IdTableArray = RootObject.getArray("id_table");
+    if (!IdTableArray) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtField,
+                                  "IdTable", "id_table", "array")
+          .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+          .build();
+    }
+
+    auto ExpectedIdTable = entityIdTableFromJSON(*IdTableArray);
+    if (!ExpectedIdTable) {
+      return ErrorBuilder::wrap(ExpectedIdTable.takeError())
+          .context(ErrorMessages::ReadingFromField, "IdTable", "id_table")
+          .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+          .build();
+    }
+
+    getIdTable(Summary) = std::move(*ExpectedIdTable);
+  }
+
+  {
+    const Array *LinkageTableArray = RootObject.getArray("linkage_table");
+    if (!LinkageTableArray) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtField,
+                                  "LinkageTable", "linkage_table", "array")
+          .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+          .build();
+    }
+
+    auto ExpectedIdRange =
+        llvm::make_second_range(getEntities(getIdTable(Summary)));
+    std::set<EntityId> ExpectedIds(ExpectedIdRange.begin(),
+                                   ExpectedIdRange.end());
+
+    // Move ExpectedIds in since linkageTableFromJSON consumes it to verify
+    // that the linkage table contains exactly the ids present in the IdTable.
+    auto ExpectedLinkageTable =
+        linkageTableFromJSON(*LinkageTableArray, std::move(ExpectedIds));
+    if (!ExpectedLinkageTable) {
+      return ErrorBuilder::wrap(ExpectedLinkageTable.takeError())
+          .context(ErrorMessages::ReadingFromField, "LinkageTable",
+                   "linkage_table")
+          .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+          .build();
+    }
+
+    getLinkageTable(Summary) = std::move(*ExpectedLinkageTable);
+  }
+
+  {
+    const Array *SummaryDataArray = RootObject.getArray("data");
+    if (!SummaryDataArray) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtField,
+                                  "SummaryData entries", "data", "array")
+          .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+          .build();
+    }
+
+    auto ExpectedSummaryDataMap =
+        summaryDataMapFromJSON(*SummaryDataArray, getIdTable(Summary));
+    if (!ExpectedSummaryDataMap) {
+      return ErrorBuilder::wrap(ExpectedSummaryDataMap.takeError())
+          .context(ErrorMessages::ReadingFromField, "SummaryData entries",
+                   "data")
+          .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+          .build();
+    }
+
+    getData(Summary) = std::move(*ExpectedSummaryDataMap);
+  }
+
+  return std::move(Summary);
+}
+
+llvm::Error JSONFormat::writeLUSummary(const LUSummary &S,
+                                       llvm::StringRef Path) {
+  Object RootObject;
+
+  RootObject["lu_namespace"] = nestedBuildNamespaceToJSON(getLUNamespace(S));
+
+  RootObject["id_table"] = entityIdTableToJSON(getIdTable(S));
+
+  RootObject["linkage_table"] = linkageTableToJSON(getLinkageTable(S));
+
+  auto ExpectedDataObject = summaryDataMapToJSON(getData(S));
+  if (!ExpectedDataObject) {
+    return ErrorBuilder::wrap(ExpectedDataObject.takeError())
+        .context(ErrorMessages::WritingToFile, "LUSummary", Path)
+        .build();
+  }
+
+  RootObject["data"] = std::move(*ExpectedDataObject);
+
+  if (auto Error = writeJSON(std::move(RootObject), Path)) {
+    return ErrorBuilder::wrap(std::move(Error))
+        .context(ErrorMessages::WritingToFile, "LUSummary", Path)
+        .build();
+  }
+
+  return llvm::Error::success();
+}
+
+} // namespace clang::ssaf
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/LUSummaryEncoding.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/LUSummaryEncoding.cpp
new file mode 100644
index 0000000000000..c4912f286d81a
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/LUSummaryEncoding.cpp
@@ -0,0 +1,160 @@
+//===- LUSummaryEncoding.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 "JSONFormatImpl.h"
+
+#include "clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h"
+
+namespace clang::ssaf {
+
+//----------------------------------------------------------------------------
+// LUSummaryEncoding
+//----------------------------------------------------------------------------
+
+llvm::Expected<LUSummaryEncoding>
+JSONFormat::readLUSummaryEncoding(llvm::StringRef Path) {
+  auto ExpectedJSON = readJSON(Path);
+  if (!ExpectedJSON) {
+    return ErrorBuilder::wrap(ExpectedJSON.takeError())
+        .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+        .build();
+  }
+
+  Object *RootObjectPtr = ExpectedJSON->getAsObject();
+  if (!RootObjectPtr) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObject, "LUSummary",
+                                "object")
+        .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+        .build();
+  }
+
+  const Object &RootObject = *RootObjectPtr;
+
+  const Array *LUNamespaceArray = RootObject.getArray("lu_namespace");
+  if (!LUNamespaceArray) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "NestedBuildNamespace", "lu_namespace", "array")
+        .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+        .build();
+  }
+
+  auto ExpectedLUNamespace = nestedBuildNamespaceFromJSON(*LUNamespaceArray);
+  if (!ExpectedLUNamespace) {
+    return ErrorBuilder::wrap(ExpectedLUNamespace.takeError())
+        .context(ErrorMessages::ReadingFromField, "NestedBuildNamespace",
+                 "lu_namespace")
+        .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+        .build();
+  }
+
+  LUSummaryEncoding Encoding(std::move(*ExpectedLUNamespace));
+
+  {
+    const Array *IdTableArray = RootObject.getArray("id_table");
+    if (!IdTableArray) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtField,
+                                  "IdTable", "id_table", "array")
+          .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+          .build();
+    }
+
+    auto ExpectedIdTable = entityIdTableFromJSON(*IdTableArray);
+    if (!ExpectedIdTable) {
+      return ErrorBuilder::wrap(ExpectedIdTable.takeError())
+          .context(ErrorMessages::ReadingFromField, "IdTable", "id_table")
+          .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+          .build();
+    }
+
+    getIdTable(Encoding) = std::move(*ExpectedIdTable);
+  }
+
+  {
+    const Array *LinkageTableArray = RootObject.getArray("linkage_table");
+    if (!LinkageTableArray) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtField,
+                                  "LinkageTable", "linkage_table", "array")
+          .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+          .build();
+    }
+
+    auto ExpectedIdRange =
+        llvm::make_second_range(getEntities(getIdTable(Encoding)));
+    std::set<EntityId> ExpectedIds(ExpectedIdRange.begin(),
+                                   ExpectedIdRange.end());
+
+    // Move ExpectedIds in since linkageTableFromJSON consumes it to verify
+    // that the linkage table contains exactly the ids present in the IdTable.
+    auto ExpectedLinkageTable =
+        linkageTableFromJSON(*LinkageTableArray, std::move(ExpectedIds));
+    if (!ExpectedLinkageTable) {
+      return ErrorBuilder::wrap(ExpectedLinkageTable.takeError())
+          .context(ErrorMessages::ReadingFromField, "LinkageTable",
+                   "linkage_table")
+          .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+          .build();
+    }
+
+    getLinkageTable(Encoding) = std::move(*ExpectedLinkageTable);
+  }
+
+  {
+    const Array *SummaryDataArray = RootObject.getArray("data");
+    if (!SummaryDataArray) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtField,
+                                  "SummaryData entries", "data", "array")
+          .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+          .build();
+    }
+
+    auto ExpectedEncodingSummaryDataMap =
+        encodingSummaryDataMapFromJSON(*SummaryDataArray);
+    if (!ExpectedEncodingSummaryDataMap) {
+      return ErrorBuilder::wrap(ExpectedEncodingSummaryDataMap.takeError())
+          .context(ErrorMessages::ReadingFromField, "SummaryData entries",
+                   "data")
+          .context(ErrorMessages::ReadingFromFile, "LUSummary", Path)
+          .build();
+    }
+
+    getData(Encoding) = std::move(*ExpectedEncodingSummaryDataMap);
+  }
+
+  return std::move(Encoding);
+}
+
+llvm::Error
+JSONFormat::writeLUSummaryEncoding(const LUSummaryEncoding &SummaryEncoding,
+                                   llvm::StringRef Path) {
+  Object RootObject;
+
+  RootObject["lu_namespace"] =
+      nestedBuildNamespaceToJSON(getLUNamespace(SummaryEncoding));
+
+  RootObject["id_table"] = entityIdTableToJSON(getIdTable(SummaryEncoding));
+
+  RootObject["linkage_table"] =
+      linkageTableToJSON(getLinkageTable(SummaryEncoding));
+
+  RootObject["data"] = encodingSummaryDataMapToJSON(getData(SummaryEncoding));
+
+  if (auto Error = writeJSON(std::move(RootObject), Path)) {
+    return ErrorBuilder::wrap(std::move(Error))
+        .context(ErrorMessages::WritingToFile, "LUSummary", Path)
+        .build();
+  }
+
+  return llvm::Error::success();
+}
+
+} // namespace clang::ssaf
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummary.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummary.cpp
index 19612bb380aef..bf22a744a3c15 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummary.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummary.cpp
@@ -12,347 +12,6 @@
 
 namespace clang::ssaf {
 
-//----------------------------------------------------------------------------
-// EntitySummary
-//----------------------------------------------------------------------------
-
-llvm::Expected<std::unique_ptr<EntitySummary>>
-JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
-                                  const Object &EntitySummaryObject,
-                                  EntityIdTable &IdTable) const {
-  auto InfoIt = FormatInfos.find(SN);
-  if (InfoIt == FormatInfos.end()) {
-    return ErrorBuilder::create(
-               std::errc::invalid_argument,
-               ErrorMessages::FailedToDeserializeEntitySummaryNoFormatInfo, SN)
-        .build();
-  }
-
-  const auto &InfoEntry = InfoIt->second;
-  assert(InfoEntry.ForSummary == SN);
-
-  EntityIdConverter Converter(*this);
-  return InfoEntry.Deserialize(EntitySummaryObject, IdTable, Converter);
-}
-
-llvm::Expected<Object>
-JSONFormat::entitySummaryToJSON(const SummaryName &SN,
-                                const EntitySummary &ES) const {
-  auto InfoIt = FormatInfos.find(SN);
-  if (InfoIt == FormatInfos.end()) {
-    return ErrorBuilder::create(
-               std::errc::invalid_argument,
-               ErrorMessages::FailedToSerializeEntitySummaryNoFormatInfo, SN)
-        .build();
-  }
-
-  const auto &InfoEntry = InfoIt->second;
-  assert(InfoEntry.ForSummary == SN);
-
-  EntityIdConverter Converter(*this);
-  return InfoEntry.Serialize(ES, Converter);
-}
-
-//----------------------------------------------------------------------------
-// EntityDataMapEntry
-//----------------------------------------------------------------------------
-
-llvm::Expected<std::pair<EntityId, std::unique_ptr<EntitySummary>>>
-JSONFormat::entityDataMapEntryFromJSON(const Object &EntityDataMapEntryObject,
-                                       const SummaryName &SN,
-                                       EntityIdTable &IdTable) const {
-
-  const Value *EntityIdIntValue = EntityDataMapEntryObject.get("entity_id");
-  if (!EntityIdIntValue) {
-    return ErrorBuilder::create(std::errc::invalid_argument,
-                                ErrorMessages::FailedToReadObjectAtField,
-                                "EntityId", "entity_id",
-                                "number (unsigned 64-bit integer)")
-        .build();
-  }
-
-  const std::optional<uint64_t> OptEntityIdInt =
-      EntityIdIntValue->getAsUINT64();
-  if (!OptEntityIdInt) {
-    return ErrorBuilder::create(std::errc::invalid_argument,
-                                ErrorMessages::FailedToReadObjectAtField,
-                                "EntityId", "entity_id",
-                                "number (unsigned 64-bit integer)")
-        .build();
-  }
-
-  EntityId EI = entityIdFromJSON(*OptEntityIdInt);
-
-  const Object *OptEntitySummaryObject =
-      EntityDataMapEntryObject.getObject("entity_summary");
-  if (!OptEntitySummaryObject) {
-    return ErrorBuilder::create(std::errc::invalid_argument,
-                                ErrorMessages::FailedToReadObjectAtField,
-                                "EntitySummary", "entity_summary", "object")
-        .build();
-  }
-
-  auto ExpectedEntitySummary =
-      entitySummaryFromJSON(SN, *OptEntitySummaryObject, IdTable);
-  if (!ExpectedEntitySummary) {
-    return ErrorBuilder::wrap(ExpectedEntitySummary.takeError())
-        .context(ErrorMessages::ReadingFromField, "EntitySummary",
-                 "entity_summary")
-        .build();
-  }
-
-  if (*ExpectedEntitySummary == nullptr) {
-    return ErrorBuilder::create(
-               std::errc::invalid_argument,
-               ErrorMessages::FailedToDeserializeEntitySummaryMissingData, SN)
-        .build();
-  }
-
-  auto ActualSN = (*ExpectedEntitySummary)->getSummaryName();
-  if (SN != ActualSN) {
-    return ErrorBuilder::create(
-               std::errc::invalid_argument,
-               ErrorMessages::
-                   FailedToDeserializeEntitySummaryMismatchedSummaryName,
-               SN, ActualSN)
-        .build();
-  }
-
-  return std::make_pair(std::move(EI), std::move(*ExpectedEntitySummary));
-}
-
-llvm::Expected<Object> JSONFormat::entityDataMapEntryToJSON(
-    const EntityId EI, const std::unique_ptr<EntitySummary> &EntitySummary,
-    const SummaryName &SN) const {
-  Object Entry;
-
-  Entry["entity_id"] = entityIdToJSON(EI);
-
-  if (!EntitySummary) {
-    ErrorBuilder::fatal(
-        ErrorMessages::FailedToSerializeEntitySummaryMissingData, SN);
-  }
-
-  const auto ActualSN = EntitySummary->getSummaryName();
-  if (SN != ActualSN) {
-    ErrorBuilder::fatal(
-        ErrorMessages::FailedToSerializeEntitySummaryMismatchedSummaryName, SN,
-        ActualSN);
-  }
-
-  auto ExpectedEntitySummaryObject = entitySummaryToJSON(SN, *EntitySummary);
-  if (!ExpectedEntitySummaryObject) {
-    return ErrorBuilder::wrap(ExpectedEntitySummaryObject.takeError())
-        .context(ErrorMessages::WritingToField, "EntitySummary",
-                 "entity_summary")
-        .build();
-  }
-
-  Entry["entity_summary"] = std::move(*ExpectedEntitySummaryObject);
-
-  return Entry;
-}
-
-//----------------------------------------------------------------------------
-// EntityDataMap
-//----------------------------------------------------------------------------
-
-llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
-JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
-                                  const Array &EntityDataArray,
-                                  EntityIdTable &IdTable) const {
-  std::map<EntityId, std::unique_ptr<EntitySummary>> EntityDataMap;
-
-  for (const auto &[Index, EntityDataMapEntryValue] :
-       llvm::enumerate(EntityDataArray)) {
-
-    const Object *OptEntityDataMapEntryObject =
-        EntityDataMapEntryValue.getAsObject();
-    if (!OptEntityDataMapEntryObject) {
-      return ErrorBuilder::create(std::errc::invalid_argument,
-                                  ErrorMessages::FailedToReadObjectAtIndex,
-                                  "EntitySummary entry", Index, "object")
-          .build();
-    }
-
-    auto ExpectedEntityDataMapEntry =
-        entityDataMapEntryFromJSON(*OptEntityDataMapEntryObject, SN, IdTable);
-    if (!ExpectedEntityDataMapEntry) {
-      return ErrorBuilder::wrap(ExpectedEntityDataMapEntry.takeError())
-          .context(ErrorMessages::ReadingFromIndex, "EntitySummary entry",
-                   Index)
-          .build();
-    }
-
-    auto [DataIt, DataInserted] =
-        EntityDataMap.insert(std::move(*ExpectedEntityDataMapEntry));
-    if (!DataInserted) {
-      return ErrorBuilder::create(std::errc::invalid_argument,
-                                  ErrorMessages::FailedInsertionOnDuplication,
-                                  "EntitySummary entry", Index, DataIt->first)
-          .build();
-    }
-  }
-
-  return std::move(EntityDataMap);
-}
-
-llvm::Expected<Array> JSONFormat::entityDataMapToJSON(
-    const SummaryName &SN,
-    const std::map<EntityId, std::unique_ptr<EntitySummary>> &EntityDataMap)
-    const {
-  Array Result;
-  Result.reserve(EntityDataMap.size());
-
-  for (const auto &[Index, EntityDataMapEntry] :
-       llvm::enumerate(EntityDataMap)) {
-    const auto &[EntityId, EntitySummary] = EntityDataMapEntry;
-
-    auto ExpectedEntityDataMapEntryObject =
-        entityDataMapEntryToJSON(EntityId, EntitySummary, SN);
-
-    if (!ExpectedEntityDataMapEntryObject) {
-      return ErrorBuilder::wrap(ExpectedEntityDataMapEntryObject.takeError())
-          .context(ErrorMessages::WritingToIndex, "EntitySummary entry", Index)
-          .build();
-    }
-
-    Result.push_back(std::move(*ExpectedEntityDataMapEntryObject));
-  }
-
-  return Result;
-}
-
-//----------------------------------------------------------------------------
-// SummaryDataMapEntry
-//----------------------------------------------------------------------------
-
-llvm::Expected<
-    std::pair<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
-JSONFormat::summaryDataMapEntryFromJSON(const Object &SummaryDataMapEntryObject,
-                                        EntityIdTable &IdTable) const {
-
-  std::optional<llvm::StringRef> OptSummaryNameStr =
-      SummaryDataMapEntryObject.getString("summary_name");
-  if (!OptSummaryNameStr) {
-    return ErrorBuilder::create(std::errc::invalid_argument,
-                                ErrorMessages::FailedToReadObjectAtField,
-                                "SummaryName", "summary_name", "string")
-        .build();
-  }
-
-  SummaryName SN = summaryNameFromJSON(*OptSummaryNameStr);
-
-  const Array *OptEntityDataArray =
-      SummaryDataMapEntryObject.getArray("summary_data");
-  if (!OptEntityDataArray) {
-    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::wrap(ExpectedEntityDataMap.takeError())
-        .context(ErrorMessages::ReadingFromField, "EntitySummary entries",
-                 "summary_data")
-        .build();
-
-  return std::make_pair(std::move(SN), std::move(*ExpectedEntityDataMap));
-}
-
-llvm::Expected<Object> JSONFormat::summaryDataMapEntryToJSON(
-    const SummaryName &SN,
-    const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const {
-  Object Result;
-
-  Result["summary_name"] = summaryNameToJSON(SN);
-
-  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;
-}
-
-//----------------------------------------------------------------------------
-// SummaryDataMap
-//----------------------------------------------------------------------------
-
-llvm::Expected<
-    std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
-JSONFormat::summaryDataMapFromJSON(const Array &SummaryDataArray,
-                                   EntityIdTable &IdTable) const {
-  std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>
-      SummaryDataMap;
-
-  for (const auto &[Index, SummaryDataMapEntryValue] :
-       llvm::enumerate(SummaryDataArray)) {
-
-    const Object *OptSummaryDataMapEntryObject =
-        SummaryDataMapEntryValue.getAsObject();
-    if (!OptSummaryDataMapEntryObject) {
-      return ErrorBuilder::create(std::errc::invalid_argument,
-                                  ErrorMessages::FailedToReadObjectAtIndex,
-                                  "SummaryData entry", Index, "object")
-          .build();
-    }
-
-    auto ExpectedSummaryDataMapEntry =
-        summaryDataMapEntryFromJSON(*OptSummaryDataMapEntryObject, IdTable);
-    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::create(std::errc::invalid_argument,
-                                  ErrorMessages::FailedInsertionOnDuplication,
-                                  "SummaryData entry", Index, SummaryIt->first)
-          .build();
-    }
-  }
-
-  return std::move(SummaryDataMap);
-}
-
-llvm::Expected<Array> JSONFormat::summaryDataMapToJSON(
-    const std::map<SummaryName,
-                   std::map<EntityId, std::unique_ptr<EntitySummary>>>
-        &SummaryDataMap) const {
-  Array Result;
-  Result.reserve(SummaryDataMap.size());
-
-  for (const auto &[Index, SummaryDataMapEntry] :
-       llvm::enumerate(SummaryDataMap)) {
-    const auto &[SummaryName, DataMap] = SummaryDataMapEntry;
-
-    auto ExpectedSummaryDataMapObject =
-        summaryDataMapEntryToJSON(SummaryName, DataMap);
-    if (!ExpectedSummaryDataMapObject) {
-      return ErrorBuilder::wrap(ExpectedSummaryDataMapObject.takeError())
-          .context(ErrorMessages::WritingToIndex, "SummaryData entry", Index)
-          .build();
-    }
-
-    Result.push_back(std::move(*ExpectedSummaryDataMapObject));
-  }
-
-  return std::move(Result);
-}
-
 //----------------------------------------------------------------------------
 // TUSummary
 //----------------------------------------------------------------------------
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummaryEncoding.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummaryEncoding.cpp
index bcf534e6e7e7e..56713f375c6f2 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummaryEncoding.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummaryEncoding.cpp
@@ -8,257 +8,10 @@
 
 #include "JSONFormatImpl.h"
 
-#include "clang/Analysis/Scalable/EntityLinker/EntitySummaryEncoding.h"
 #include "clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h"
 
 namespace clang::ssaf {
 
-//----------------------------------------------------------------------------
-// JSONEntitySummaryEncoding
-//----------------------------------------------------------------------------
-
-namespace {
-
-class JSONEntitySummaryEncoding final : public EntitySummaryEncoding {
-  friend JSONFormat;
-
-public:
-  void
-  patch(const std::map<EntityId, EntityId> &EntityResolutionTable) override {
-    ErrorBuilder::fatal("will be implemented in the future");
-  }
-
-private:
-  explicit JSONEntitySummaryEncoding(llvm::json::Value Data)
-      : Data(std::move(Data)) {}
-
-  llvm::json::Value Data;
-};
-
-} // namespace
-
-//----------------------------------------------------------------------------
-// EncodingDataMapEntry
-//----------------------------------------------------------------------------
-
-llvm::Expected<std::pair<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
-JSONFormat::encodingDataMapEntryFromJSON(
-    const Object &EntityDataMapEntryObject) const {
-  const Value *EntityIdIntValue = EntityDataMapEntryObject.get("entity_id");
-  if (!EntityIdIntValue) {
-    return ErrorBuilder::create(std::errc::invalid_argument,
-                                ErrorMessages::FailedToReadObjectAtField,
-                                "EntityId", "entity_id",
-                                "number (unsigned 64-bit integer)")
-        .build();
-  }
-
-  const std::optional<uint64_t> OptEntityIdInt =
-      EntityIdIntValue->getAsUINT64();
-  if (!OptEntityIdInt) {
-    return ErrorBuilder::create(std::errc::invalid_argument,
-                                ErrorMessages::FailedToReadObjectAtField,
-                                "EntityId", "entity_id",
-                                "number (unsigned 64-bit integer)")
-        .build();
-  }
-
-  EntityId EI = entityIdFromJSON(*OptEntityIdInt);
-
-  const Object *OptEntitySummaryObject =
-      EntityDataMapEntryObject.getObject("entity_summary");
-  if (!OptEntitySummaryObject) {
-    return ErrorBuilder::create(std::errc::invalid_argument,
-                                ErrorMessages::FailedToReadObjectAtField,
-                                "EntitySummary", "entity_summary", "object")
-        .build();
-  }
-
-  std::unique_ptr<EntitySummaryEncoding> Encoding(
-      new JSONEntitySummaryEncoding(Value(Object(*OptEntitySummaryObject))));
-
-  return std::make_pair(std::move(EI), std::move(Encoding));
-}
-
-Object JSONFormat::encodingDataMapEntryToJSON(
-    EntityId EI, const std::unique_ptr<EntitySummaryEncoding> &Encoding) const {
-  Object Entry;
-  Entry["entity_id"] = entityIdToJSON(EI);
-
-  // All EntitySummaryEncoding objects stored in a TUSummaryEncoding read by
-  // JSONFormat are JSONEntitySummaryEncoding instances, since
-  // encodingDataMapEntryFromJSON is the only place that creates them.
-  auto *JSONEncoding = static_cast<JSONEntitySummaryEncoding *>(Encoding.get());
-  Entry["entity_summary"] = JSONEncoding->Data;
-
-  return Entry;
-}
-
-//----------------------------------------------------------------------------
-// EncodingDataMap
-//----------------------------------------------------------------------------
-
-llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
-JSONFormat::encodingDataMapFromJSON(const Array &EntityDataArray) const {
-  std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>> EncodingDataMap;
-
-  for (const auto &[Index, EntityDataMapEntryValue] :
-       llvm::enumerate(EntityDataArray)) {
-
-    const Object *OptEntityDataMapEntryObject =
-        EntityDataMapEntryValue.getAsObject();
-    if (!OptEntityDataMapEntryObject) {
-      return ErrorBuilder::create(std::errc::invalid_argument,
-                                  ErrorMessages::FailedToReadObjectAtIndex,
-                                  "EntitySummary entry", Index, "object")
-          .build();
-    }
-
-    auto ExpectedEntry =
-        encodingDataMapEntryFromJSON(*OptEntityDataMapEntryObject);
-    if (!ExpectedEntry) {
-      return ErrorBuilder::wrap(ExpectedEntry.takeError())
-          .context(ErrorMessages::ReadingFromIndex, "EntitySummary entry",
-                   Index)
-          .build();
-    }
-
-    auto [DataIt, DataInserted] =
-        EncodingDataMap.insert(std::move(*ExpectedEntry));
-    if (!DataInserted) {
-      return ErrorBuilder::create(std::errc::invalid_argument,
-                                  ErrorMessages::FailedInsertionOnDuplication,
-                                  "EntitySummary entry", Index, DataIt->first)
-          .build();
-    }
-  }
-
-  return std::move(EncodingDataMap);
-}
-
-Array JSONFormat::encodingDataMapToJSON(
-    const std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>
-        &EncodingDataMap) const {
-  Array Result;
-  Result.reserve(EncodingDataMap.size());
-
-  for (const auto &[EI, Encoding] : EncodingDataMap) {
-    Result.push_back(encodingDataMapEntryToJSON(EI, Encoding));
-  }
-
-  return Result;
-}
-
-//----------------------------------------------------------------------------
-// EncodingSummaryDataMapEntry
-//----------------------------------------------------------------------------
-
-llvm::Expected<std::pair<
-    SummaryName, std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>>
-JSONFormat::encodingSummaryDataMapEntryFromJSON(
-    const Object &SummaryDataMapEntryObject) const {
-  std::optional<llvm::StringRef> OptSummaryNameStr =
-      SummaryDataMapEntryObject.getString("summary_name");
-  if (!OptSummaryNameStr) {
-    return ErrorBuilder::create(std::errc::invalid_argument,
-                                ErrorMessages::FailedToReadObjectAtField,
-                                "SummaryName", "summary_name", "string")
-        .build();
-  }
-
-  SummaryName SN = summaryNameFromJSON(*OptSummaryNameStr);
-
-  const Array *OptEntityDataArray =
-      SummaryDataMapEntryObject.getArray("summary_data");
-  if (!OptEntityDataArray) {
-    return ErrorBuilder::create(std::errc::invalid_argument,
-                                ErrorMessages::FailedToReadObjectAtField,
-                                "EntitySummary entries", "summary_data",
-                                "array")
-        .build();
-  }
-
-  auto ExpectedEncodingDataMap = encodingDataMapFromJSON(*OptEntityDataArray);
-  if (!ExpectedEncodingDataMap)
-    return ErrorBuilder::wrap(ExpectedEncodingDataMap.takeError())
-        .context(ErrorMessages::ReadingFromField, "EntitySummary entries",
-                 "summary_data")
-        .build();
-
-  return std::make_pair(std::move(SN), std::move(*ExpectedEncodingDataMap));
-}
-
-Object JSONFormat::encodingSummaryDataMapEntryToJSON(
-    const SummaryName &SN,
-    const std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>
-        &EncodingMap) const {
-  Object Result;
-
-  Result["summary_name"] = summaryNameToJSON(SN);
-  Result["summary_data"] = encodingDataMapToJSON(EncodingMap);
-
-  return Result;
-}
-
-//----------------------------------------------------------------------------
-// EncodingSummaryDataMap
-//----------------------------------------------------------------------------
-
-llvm::Expected<std::map<
-    SummaryName, std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>>
-JSONFormat::encodingSummaryDataMapFromJSON(
-    const Array &SummaryDataArray) const {
-  std::map<SummaryName,
-           std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
-      EncodingSummaryDataMap;
-
-  for (const auto &[Index, SummaryDataMapEntryValue] :
-       llvm::enumerate(SummaryDataArray)) {
-
-    const Object *OptSummaryDataMapEntryObject =
-        SummaryDataMapEntryValue.getAsObject();
-    if (!OptSummaryDataMapEntryObject) {
-      return ErrorBuilder::create(std::errc::invalid_argument,
-                                  ErrorMessages::FailedToReadObjectAtIndex,
-                                  "SummaryData entry", Index, "object")
-          .build();
-    }
-
-    auto ExpectedEntry =
-        encodingSummaryDataMapEntryFromJSON(*OptSummaryDataMapEntryObject);
-    if (!ExpectedEntry) {
-      return ErrorBuilder::wrap(ExpectedEntry.takeError())
-          .context(ErrorMessages::ReadingFromIndex, "SummaryData entry", Index)
-          .build();
-    }
-
-    auto [SummaryIt, SummaryInserted] =
-        EncodingSummaryDataMap.emplace(std::move(*ExpectedEntry));
-    if (!SummaryInserted) {
-      return ErrorBuilder::create(std::errc::invalid_argument,
-                                  ErrorMessages::FailedInsertionOnDuplication,
-                                  "SummaryData entry", Index, SummaryIt->first)
-          .build();
-    }
-  }
-
-  return std::move(EncodingSummaryDataMap);
-}
-
-Array JSONFormat::encodingSummaryDataMapToJSON(
-    const std::map<SummaryName,
-                   std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
-        &EncodingSummaryDataMap) const {
-  Array Result;
-  Result.reserve(EncodingSummaryDataMap.size());
-
-  for (const auto &[SN, EncodingMap] : EncodingSummaryDataMap) {
-    Result.push_back(encodingSummaryDataMapEntryToJSON(SN, EncodingMap));
-  }
-
-  return Result;
-}
-
 //----------------------------------------------------------------------------
 // TUSummaryEncoding
 //----------------------------------------------------------------------------

>From 7112278ad39ab92e8935a0335fa7497d9372ac09 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 27 Feb 2026 13:12:03 -0800
Subject: [PATCH 3/9] Move common functionality to a file.

---
 .../JSONFormat/JSONFormatImpl.cpp             | 564 ++++++++++++++++++
 .../Serialization/JSONFormat/JSONFormatImpl.h |  25 +
 2 files changed, 589 insertions(+)

diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
index 3b9b49439dfa2..8717a7d73b936 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
@@ -593,4 +593,568 @@ Array JSONFormat::linkageTableToJSON(
   return Result;
 }
 
+//----------------------------------------------------------------------------
+// EntitySummary
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::unique_ptr<EntitySummary>>
+JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
+                                  const Object &EntitySummaryObject,
+                                  EntityIdTable &IdTable) const {
+  auto InfoIt = FormatInfos.find(SN);
+  if (InfoIt == FormatInfos.end()) {
+    return ErrorBuilder::create(
+               std::errc::invalid_argument,
+               ErrorMessages::FailedToDeserializeEntitySummaryNoFormatInfo, SN)
+        .build();
+  }
+
+  const auto &InfoEntry = InfoIt->second;
+  assert(InfoEntry.ForSummary == SN);
+
+  EntityIdConverter Converter(*this);
+  return InfoEntry.Deserialize(EntitySummaryObject, IdTable, Converter);
+}
+
+llvm::Expected<Object>
+JSONFormat::entitySummaryToJSON(const SummaryName &SN,
+                                const EntitySummary &ES) const {
+  auto InfoIt = FormatInfos.find(SN);
+  if (InfoIt == FormatInfos.end()) {
+    return ErrorBuilder::create(
+               std::errc::invalid_argument,
+               ErrorMessages::FailedToSerializeEntitySummaryNoFormatInfo, SN)
+        .build();
+  }
+
+  const auto &InfoEntry = InfoIt->second;
+  assert(InfoEntry.ForSummary == SN);
+
+  EntityIdConverter Converter(*this);
+  return InfoEntry.Serialize(ES, Converter);
+}
+
+//----------------------------------------------------------------------------
+// EntityDataMapEntry
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::pair<EntityId, std::unique_ptr<EntitySummary>>>
+JSONFormat::entityDataMapEntryFromJSON(const Object &EntityDataMapEntryObject,
+                                       const SummaryName &SN,
+                                       EntityIdTable &IdTable) const {
+
+  const Value *EntityIdIntValue = EntityDataMapEntryObject.get("entity_id");
+  if (!EntityIdIntValue) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntityId", "entity_id",
+                                "number (unsigned 64-bit integer)")
+        .build();
+  }
+
+  const std::optional<uint64_t> OptEntityIdInt =
+      EntityIdIntValue->getAsUINT64();
+  if (!OptEntityIdInt) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntityId", "entity_id",
+                                "number (unsigned 64-bit integer)")
+        .build();
+  }
+
+  EntityId EI = entityIdFromJSON(*OptEntityIdInt);
+
+  const Object *OptEntitySummaryObject =
+      EntityDataMapEntryObject.getObject("entity_summary");
+  if (!OptEntitySummaryObject) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntitySummary", "entity_summary", "object")
+        .build();
+  }
+
+  auto ExpectedEntitySummary =
+      entitySummaryFromJSON(SN, *OptEntitySummaryObject, IdTable);
+  if (!ExpectedEntitySummary) {
+    return ErrorBuilder::wrap(ExpectedEntitySummary.takeError())
+        .context(ErrorMessages::ReadingFromField, "EntitySummary",
+                 "entity_summary")
+        .build();
+  }
+
+  if (*ExpectedEntitySummary == nullptr) {
+    return ErrorBuilder::create(
+               std::errc::invalid_argument,
+               ErrorMessages::FailedToDeserializeEntitySummaryMissingData, SN)
+        .build();
+  }
+
+  auto ActualSN = (*ExpectedEntitySummary)->getSummaryName();
+  if (SN != ActualSN) {
+    return ErrorBuilder::create(
+               std::errc::invalid_argument,
+               ErrorMessages::
+                   FailedToDeserializeEntitySummaryMismatchedSummaryName,
+               SN, ActualSN)
+        .build();
+  }
+
+  return std::make_pair(std::move(EI), std::move(*ExpectedEntitySummary));
+}
+
+llvm::Expected<Object> JSONFormat::entityDataMapEntryToJSON(
+    const EntityId EI, const std::unique_ptr<EntitySummary> &EntitySummary,
+    const SummaryName &SN) const {
+  Object Entry;
+
+  Entry["entity_id"] = entityIdToJSON(EI);
+
+  if (!EntitySummary) {
+    ErrorBuilder::fatal(
+        ErrorMessages::FailedToSerializeEntitySummaryMissingData, SN);
+  }
+
+  const auto ActualSN = EntitySummary->getSummaryName();
+  if (SN != ActualSN) {
+    ErrorBuilder::fatal(
+        ErrorMessages::FailedToSerializeEntitySummaryMismatchedSummaryName, SN,
+        ActualSN);
+  }
+
+  auto ExpectedEntitySummaryObject = entitySummaryToJSON(SN, *EntitySummary);
+  if (!ExpectedEntitySummaryObject) {
+    return ErrorBuilder::wrap(ExpectedEntitySummaryObject.takeError())
+        .context(ErrorMessages::WritingToField, "EntitySummary",
+                 "entity_summary")
+        .build();
+  }
+
+  Entry["entity_summary"] = std::move(*ExpectedEntitySummaryObject);
+
+  return Entry;
+}
+
+//----------------------------------------------------------------------------
+// EntityDataMap
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
+JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
+                                  const Array &EntityDataArray,
+                                  EntityIdTable &IdTable) const {
+  std::map<EntityId, std::unique_ptr<EntitySummary>> EntityDataMap;
+
+  for (const auto &[Index, EntityDataMapEntryValue] :
+       llvm::enumerate(EntityDataArray)) {
+
+    const Object *OptEntityDataMapEntryObject =
+        EntityDataMapEntryValue.getAsObject();
+    if (!OptEntityDataMapEntryObject) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtIndex,
+                                  "EntitySummary entry", Index, "object")
+          .build();
+    }
+
+    auto ExpectedEntityDataMapEntry =
+        entityDataMapEntryFromJSON(*OptEntityDataMapEntryObject, SN, IdTable);
+    if (!ExpectedEntityDataMapEntry) {
+      return ErrorBuilder::wrap(ExpectedEntityDataMapEntry.takeError())
+          .context(ErrorMessages::ReadingFromIndex, "EntitySummary entry",
+                   Index)
+          .build();
+    }
+
+    auto [DataIt, DataInserted] =
+        EntityDataMap.insert(std::move(*ExpectedEntityDataMapEntry));
+    if (!DataInserted) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedInsertionOnDuplication,
+                                  "EntitySummary entry", Index, DataIt->first)
+          .build();
+    }
+  }
+
+  return std::move(EntityDataMap);
+}
+
+llvm::Expected<Array> JSONFormat::entityDataMapToJSON(
+    const SummaryName &SN,
+    const std::map<EntityId, std::unique_ptr<EntitySummary>> &EntityDataMap)
+    const {
+  Array Result;
+  Result.reserve(EntityDataMap.size());
+
+  for (const auto &[Index, EntityDataMapEntry] :
+       llvm::enumerate(EntityDataMap)) {
+    const auto &[EntityId, EntitySummary] = EntityDataMapEntry;
+
+    auto ExpectedEntityDataMapEntryObject =
+        entityDataMapEntryToJSON(EntityId, EntitySummary, SN);
+
+    if (!ExpectedEntityDataMapEntryObject) {
+      return ErrorBuilder::wrap(ExpectedEntityDataMapEntryObject.takeError())
+          .context(ErrorMessages::WritingToIndex, "EntitySummary entry", Index)
+          .build();
+    }
+
+    Result.push_back(std::move(*ExpectedEntityDataMapEntryObject));
+  }
+
+  return Result;
+}
+
+//----------------------------------------------------------------------------
+// SummaryDataMapEntry
+//----------------------------------------------------------------------------
+
+llvm::Expected<
+    std::pair<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+JSONFormat::summaryDataMapEntryFromJSON(const Object &SummaryDataMapEntryObject,
+                                        EntityIdTable &IdTable) const {
+
+  std::optional<llvm::StringRef> OptSummaryNameStr =
+      SummaryDataMapEntryObject.getString("summary_name");
+  if (!OptSummaryNameStr) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "SummaryName", "summary_name", "string")
+        .build();
+  }
+
+  SummaryName SN = summaryNameFromJSON(*OptSummaryNameStr);
+
+  const Array *OptEntityDataArray =
+      SummaryDataMapEntryObject.getArray("summary_data");
+  if (!OptEntityDataArray) {
+    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::wrap(ExpectedEntityDataMap.takeError())
+        .context(ErrorMessages::ReadingFromField, "EntitySummary entries",
+                 "summary_data")
+        .build();
+
+  return std::make_pair(std::move(SN), std::move(*ExpectedEntityDataMap));
+}
+
+llvm::Expected<Object> JSONFormat::summaryDataMapEntryToJSON(
+    const SummaryName &SN,
+    const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const {
+  Object Result;
+
+  Result["summary_name"] = summaryNameToJSON(SN);
+
+  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;
+}
+
+//----------------------------------------------------------------------------
+// SummaryDataMap
+//----------------------------------------------------------------------------
+
+llvm::Expected<
+    std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+JSONFormat::summaryDataMapFromJSON(const Array &SummaryDataArray,
+                                   EntityIdTable &IdTable) const {
+  std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>
+      SummaryDataMap;
+
+  for (const auto &[Index, SummaryDataMapEntryValue] :
+       llvm::enumerate(SummaryDataArray)) {
+
+    const Object *OptSummaryDataMapEntryObject =
+        SummaryDataMapEntryValue.getAsObject();
+    if (!OptSummaryDataMapEntryObject) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtIndex,
+                                  "SummaryData entry", Index, "object")
+          .build();
+    }
+
+    auto ExpectedSummaryDataMapEntry =
+        summaryDataMapEntryFromJSON(*OptSummaryDataMapEntryObject, IdTable);
+    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::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedInsertionOnDuplication,
+                                  "SummaryData entry", Index, SummaryIt->first)
+          .build();
+    }
+  }
+
+  return std::move(SummaryDataMap);
+}
+
+llvm::Expected<Array> JSONFormat::summaryDataMapToJSON(
+    const std::map<SummaryName,
+                   std::map<EntityId, std::unique_ptr<EntitySummary>>>
+        &SummaryDataMap) const {
+  Array Result;
+  Result.reserve(SummaryDataMap.size());
+
+  for (const auto &[Index, SummaryDataMapEntry] :
+       llvm::enumerate(SummaryDataMap)) {
+    const auto &[SummaryName, DataMap] = SummaryDataMapEntry;
+
+    auto ExpectedSummaryDataMapObject =
+        summaryDataMapEntryToJSON(SummaryName, DataMap);
+    if (!ExpectedSummaryDataMapObject) {
+      return ErrorBuilder::wrap(ExpectedSummaryDataMapObject.takeError())
+          .context(ErrorMessages::WritingToIndex, "SummaryData entry", Index)
+          .build();
+    }
+
+    Result.push_back(std::move(*ExpectedSummaryDataMapObject));
+  }
+
+  return std::move(Result);
+}
+
+//----------------------------------------------------------------------------
+// EncodingDataMapEntry
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::pair<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
+JSONFormat::encodingDataMapEntryFromJSON(
+    const Object &EntityDataMapEntryObject) const {
+  const Value *EntityIdIntValue = EntityDataMapEntryObject.get("entity_id");
+  if (!EntityIdIntValue) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntityId", "entity_id",
+                                "number (unsigned 64-bit integer)")
+        .build();
+  }
+
+  const std::optional<uint64_t> OptEntityIdInt =
+      EntityIdIntValue->getAsUINT64();
+  if (!OptEntityIdInt) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntityId", "entity_id",
+                                "number (unsigned 64-bit integer)")
+        .build();
+  }
+
+  EntityId EI = entityIdFromJSON(*OptEntityIdInt);
+
+  const Object *OptEntitySummaryObject =
+      EntityDataMapEntryObject.getObject("entity_summary");
+  if (!OptEntitySummaryObject) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntitySummary", "entity_summary", "object")
+        .build();
+  }
+
+  std::unique_ptr<EntitySummaryEncoding> Encoding(
+      new JSONEntitySummaryEncoding(Value(Object(*OptEntitySummaryObject))));
+
+  return std::make_pair(std::move(EI), std::move(Encoding));
+}
+
+Object JSONFormat::encodingDataMapEntryToJSON(
+    EntityId EI, const std::unique_ptr<EntitySummaryEncoding> &Encoding) const {
+  Object Entry;
+  Entry["entity_id"] = entityIdToJSON(EI);
+
+  // All EntitySummaryEncoding objects stored in a TUSummaryEncoding or
+  // LUSummaryEncoding read by JSONFormat are JSONEntitySummaryEncoding
+  // instances, since encodingDataMapEntryFromJSON is the only place that
+  // creates them.
+  auto *JSONEncoding = static_cast<JSONEntitySummaryEncoding *>(Encoding.get());
+  Entry["entity_summary"] = JSONEncoding->Data;
+
+  return Entry;
+}
+
+//----------------------------------------------------------------------------
+// EncodingDataMap
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
+JSONFormat::encodingDataMapFromJSON(const Array &EntityDataArray) const {
+  std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>> EncodingDataMap;
+
+  for (const auto &[Index, EntityDataMapEntryValue] :
+       llvm::enumerate(EntityDataArray)) {
+
+    const Object *OptEntityDataMapEntryObject =
+        EntityDataMapEntryValue.getAsObject();
+    if (!OptEntityDataMapEntryObject) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtIndex,
+                                  "EntitySummary entry", Index, "object")
+          .build();
+    }
+
+    auto ExpectedEntry =
+        encodingDataMapEntryFromJSON(*OptEntityDataMapEntryObject);
+    if (!ExpectedEntry) {
+      return ErrorBuilder::wrap(ExpectedEntry.takeError())
+          .context(ErrorMessages::ReadingFromIndex, "EntitySummary entry",
+                   Index)
+          .build();
+    }
+
+    auto [DataIt, DataInserted] =
+        EncodingDataMap.insert(std::move(*ExpectedEntry));
+    if (!DataInserted) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedInsertionOnDuplication,
+                                  "EntitySummary entry", Index, DataIt->first)
+          .build();
+    }
+  }
+
+  return std::move(EncodingDataMap);
+}
+
+Array JSONFormat::encodingDataMapToJSON(
+    const std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>
+        &EncodingDataMap) const {
+  Array Result;
+  Result.reserve(EncodingDataMap.size());
+
+  for (const auto &[EI, Encoding] : EncodingDataMap) {
+    Result.push_back(encodingDataMapEntryToJSON(EI, Encoding));
+  }
+
+  return Result;
+}
+
+//----------------------------------------------------------------------------
+// EncodingSummaryDataMapEntry
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::pair<
+    SummaryName, std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>>
+JSONFormat::encodingSummaryDataMapEntryFromJSON(
+    const Object &SummaryDataMapEntryObject) const {
+  std::optional<llvm::StringRef> OptSummaryNameStr =
+      SummaryDataMapEntryObject.getString("summary_name");
+  if (!OptSummaryNameStr) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "SummaryName", "summary_name", "string")
+        .build();
+  }
+
+  SummaryName SN = summaryNameFromJSON(*OptSummaryNameStr);
+
+  const Array *OptEntityDataArray =
+      SummaryDataMapEntryObject.getArray("summary_data");
+  if (!OptEntityDataArray) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntitySummary entries", "summary_data",
+                                "array")
+        .build();
+  }
+
+  auto ExpectedEncodingDataMap = encodingDataMapFromJSON(*OptEntityDataArray);
+  if (!ExpectedEncodingDataMap)
+    return ErrorBuilder::wrap(ExpectedEncodingDataMap.takeError())
+        .context(ErrorMessages::ReadingFromField, "EntitySummary entries",
+                 "summary_data")
+        .build();
+
+  return std::make_pair(std::move(SN), std::move(*ExpectedEncodingDataMap));
+}
+
+Object JSONFormat::encodingSummaryDataMapEntryToJSON(
+    const SummaryName &SN,
+    const std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>
+        &EncodingMap) const {
+  Object Result;
+
+  Result["summary_name"] = summaryNameToJSON(SN);
+  Result["summary_data"] = encodingDataMapToJSON(EncodingMap);
+
+  return Result;
+}
+
+//----------------------------------------------------------------------------
+// EncodingSummaryDataMap
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::map<
+    SummaryName, std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>>
+JSONFormat::encodingSummaryDataMapFromJSON(
+    const Array &SummaryDataArray) const {
+  std::map<SummaryName,
+           std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
+      EncodingSummaryDataMap;
+
+  for (const auto &[Index, SummaryDataMapEntryValue] :
+       llvm::enumerate(SummaryDataArray)) {
+
+    const Object *OptSummaryDataMapEntryObject =
+        SummaryDataMapEntryValue.getAsObject();
+    if (!OptSummaryDataMapEntryObject) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtIndex,
+                                  "SummaryData entry", Index, "object")
+          .build();
+    }
+
+    auto ExpectedEntry =
+        encodingSummaryDataMapEntryFromJSON(*OptSummaryDataMapEntryObject);
+    if (!ExpectedEntry) {
+      return ErrorBuilder::wrap(ExpectedEntry.takeError())
+          .context(ErrorMessages::ReadingFromIndex, "SummaryData entry", Index)
+          .build();
+    }
+
+    auto [SummaryIt, SummaryInserted] =
+        EncodingSummaryDataMap.emplace(std::move(*ExpectedEntry));
+    if (!SummaryInserted) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedInsertionOnDuplication,
+                                  "SummaryData entry", Index, SummaryIt->first)
+          .build();
+    }
+  }
+
+  return std::move(EncodingSummaryDataMap);
+}
+
+Array JSONFormat::encodingSummaryDataMapToJSON(
+    const std::map<SummaryName,
+                   std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
+        &EncodingSummaryDataMap) const {
+  Array Result;
+  Result.reserve(EncodingSummaryDataMap.size());
+
+  for (const auto &[SN, EncodingMap] : EncodingSummaryDataMap) {
+    Result.push_back(encodingSummaryDataMapEntryToJSON(SN, EncodingMap));
+  }
+
+  return Result;
+}
+
 } // namespace clang::ssaf
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.h b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.h
index 3ba651bbc507f..b51313c5e6877 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.h
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.h
@@ -15,6 +15,7 @@
 #define CLANG_LIB_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_JSONFORMATIMPL_H
 
 #include "../../ModelStringConversions.h"
+#include "clang/Analysis/Scalable/EntityLinker/EntitySummaryEncoding.h"
 #include "clang/Analysis/Scalable/Model/EntityLinkage.h"
 #include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
 #include "clang/Analysis/Scalable/Support/ErrorBuilder.h"
@@ -109,6 +110,30 @@ inline constexpr const char *FailedToDeserializeLinkageTableMissingId =
 
 } // namespace ErrorMessages
 
+//----------------------------------------------------------------------------
+// JSONEntitySummaryEncoding
+//
+// Concrete EntitySummaryEncoding used by JSONFormat for both TUSummaryEncoding
+// and LUSummaryEncoding. Stores the raw EntitySummary JSON value opaquely so
+// the linker can patch and emit it without knowing the analysis schema.
+//----------------------------------------------------------------------------
+
+class JSONEntitySummaryEncoding final : public EntitySummaryEncoding {
+  friend JSONFormat;
+
+public:
+  void
+  patch(const std::map<EntityId, EntityId> &EntityResolutionTable) override {
+    ErrorBuilder::fatal("will be implemented in the future");
+  }
+
+private:
+  explicit JSONEntitySummaryEncoding(llvm::json::Value Data)
+      : Data(std::move(Data)) {}
+
+  llvm::json::Value Data;
+};
+
 //----------------------------------------------------------------------------
 // JSON Reader and Writer
 //----------------------------------------------------------------------------

>From d2b4ab25499c8a5bcbc104ef2a91f17d4cdba4ab Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Sun, 1 Mar 2026 12:42:43 -0800
Subject: [PATCH 4/9] Add tests

---
 .../Analysis/Scalable/CMakeLists.txt          |    1 +
 .../Registries/MockSerializationFormat.cpp    |    1 -
 .../JSONFormatTest/JSONFormatTest.cpp         |  626 ++++
 .../JSONFormatTest/JSONFormatTest.h           |  239 +-
 .../JSONFormatTest/LUSummaryTest.cpp          | 2518 +++++++++++++++++
 .../JSONFormatTest/TUSummaryTest.cpp          |  644 +----
 6 files changed, 3303 insertions(+), 726 deletions(-)
 create mode 100644 clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.cpp
 create mode 100644 clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp

diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt b/clang/unittests/Analysis/Scalable/CMakeLists.txt
index 379e97cd67251..f021263312d29 100644
--- a/clang/unittests/Analysis/Scalable/CMakeLists.txt
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -15,6 +15,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
   Registries/MockSummaryExtractor2.cpp
   Registries/SerializationFormatRegistryTest.cpp
   Registries/SummaryExtractorRegistryTest.cpp
+  Serialization/JSONFormatTest/JSONFormatTest.cpp
   Serialization/JSONFormatTest/LUSummaryTest.cpp
   Serialization/JSONFormatTest/TUSummaryTest.cpp
   SummaryNameTest.cpp
diff --git a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
index c792de5d380f8..13c8e103768a1 100644
--- a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
@@ -9,7 +9,6 @@
 #include "Registries/MockSerializationFormat.h"
 #include "clang/Analysis/Scalable/EntityLinker/LUSummary.h"
 #include "clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h"
-#include "clang/Analysis/Scalable/EntityLinker/TUSummary.h"
 #include "clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h"
 #include "clang/Analysis/Scalable/Model/BuildNamespace.h"
 #include "clang/Analysis/Scalable/Model/EntityName.h"
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.cpp
new file mode 100644
index 0000000000000..b723647a75bfc
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.cpp
@@ -0,0 +1,626 @@
+//===- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Shared normalization helpers for SSAF JSON serialization format unit tests.
+//
+//===----------------------------------------------------------------------===//
+
+#include "JSONFormatTest.h"
+
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/Registry.h"
+#include "llvm/Testing/Support/Error.h"
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
+using namespace clang::ssaf;
+using namespace llvm;
+using PathString = JSONFormatTest::PathString;
+
+// ============================================================================
+// Test Fixture
+// ============================================================================
+
+void JSONFormatTest::SetUp() {
+  std::error_code EC =
+      llvm::sys::fs::createUniqueDirectory("json-format-test", TestDir);
+  ASSERT_FALSE(EC) << "Failed to create temp directory: " << EC.message();
+}
+
+void JSONFormatTest::TearDown() { llvm::sys::fs::remove_directories(TestDir); }
+
+JSONFormatTest::PathString
+JSONFormatTest::makePath(llvm::StringRef FileOrDirectoryName) const {
+  PathString FullPath = TestDir;
+  llvm::sys::path::append(FullPath, FileOrDirectoryName);
+
+  return FullPath;
+}
+
+PathString JSONFormatTest::makePath(llvm::StringRef Dir,
+                                    llvm::StringRef FileName) const {
+  PathString FullPath = TestDir;
+  llvm::sys::path::append(FullPath, Dir, FileName);
+
+  return FullPath;
+}
+
+llvm::Expected<PathString>
+JSONFormatTest::makeDirectory(llvm::StringRef DirectoryName) const {
+  PathString DirPath = makePath(DirectoryName);
+
+  std::error_code EC = llvm::sys::fs::create_directory(DirPath);
+  if (EC) {
+    return llvm::createStringError(EC, "Failed to create directory '%s': %s",
+                                   DirPath.c_str(), EC.message().c_str());
+  }
+
+  return DirPath;
+}
+
+llvm::Expected<PathString>
+JSONFormatTest::makeSymlink(llvm::StringRef TargetFileName,
+                            llvm::StringRef SymlinkFileName) const {
+  PathString TargetPath = makePath(TargetFileName);
+  PathString SymlinkPath = makePath(SymlinkFileName);
+
+  std::error_code EC = llvm::sys::fs::create_link(TargetPath, SymlinkPath);
+  if (EC) {
+    return llvm::createStringError(
+        EC, "Failed to create symlink '%s' -> '%s': %s", SymlinkPath.c_str(),
+        TargetPath.c_str(), EC.message().c_str());
+  }
+
+  return SymlinkPath;
+}
+
+llvm::Error JSONFormatTest::setPermission(llvm::StringRef FileName,
+                                          llvm::sys::fs::perms Perms) const {
+  PathString Path = makePath(FileName);
+
+  std::error_code EC = llvm::sys::fs::setPermissions(Path, Perms);
+  if (EC) {
+    return llvm::createStringError(EC, "Failed to set permissions on '%s': %s",
+                                   Path.c_str(), EC.message().c_str());
+  }
+
+  return llvm::Error::success();
+}
+
+bool JSONFormatTest::permissionsAreEnforced() const {
+#ifdef _WIN32
+  return false;
+#else
+  if (getuid() == 0) {
+    return false;
+  }
+
+  // Write a probe file, remove read permission, and try to open it.
+  PathString ProbePath = makePath("perm-probe.json");
+
+  {
+    std::error_code EC;
+    llvm::raw_fd_ostream OS(ProbePath, EC);
+    if (EC) {
+      return true; // Probe setup failed; assume enforced to avoid
+                   // silently suppressing the test.
+    }
+    OS << "{}";
+  }
+
+  std::error_code PermEC = llvm::sys::fs::setPermissions(
+      ProbePath, llvm::sys::fs::perms::owner_write);
+  if (PermEC) {
+    return true; // Probe setup failed; assume enforced to avoid
+                 // silently suppressing the test.
+  }
+
+  auto Buffer = llvm::MemoryBuffer::getFile(ProbePath);
+  bool Enforced = !Buffer; // If open failed, permissions are enforced.
+
+  // Restore permissions so TearDown can clean up the temp directory.
+  llvm::sys::fs::setPermissions(ProbePath, llvm::sys::fs::perms::all_all);
+
+  return Enforced;
+#endif
+}
+
+llvm::Expected<llvm::json::Value>
+JSONFormatTest::readJSONFromFile(llvm::StringRef FileName) const {
+  PathString FilePath = makePath(FileName);
+
+  auto BufferOrError = llvm::MemoryBuffer::getFile(FilePath);
+  if (!BufferOrError) {
+    return llvm::createStringError(BufferOrError.getError(),
+                                   "Failed to read file: %s", FilePath.c_str());
+  }
+
+  llvm::Expected<llvm::json::Value> ExpectedValue =
+      llvm::json::parse(BufferOrError.get()->getBuffer());
+  if (!ExpectedValue) {
+    return ExpectedValue.takeError();
+  }
+
+  return *ExpectedValue;
+}
+
+llvm::Expected<PathString>
+JSONFormatTest::writeJSON(llvm::StringRef JSON,
+                          llvm::StringRef FileName) const {
+  PathString FilePath = makePath(FileName);
+
+  std::error_code EC;
+  llvm::raw_fd_ostream OS(FilePath, EC);
+  if (EC) {
+    return llvm::createStringError(EC, "Failed to create file '%s': %s",
+                                   FilePath.c_str(), EC.message().c_str());
+  }
+
+  OS << JSON;
+  OS.close();
+
+  if (OS.has_error()) {
+    return llvm::createStringError(
+        OS.error(), "Failed to write to file '%s': %s", FilePath.c_str(),
+        OS.error().message().c_str());
+  }
+
+  return FilePath;
+}
+
+// ============================================================================
+// Summary JSON Normalization Helpers
+// ============================================================================
+
+namespace {
+
+llvm::Error normalizeIDTable(json::Array &IDTable,
+                             llvm::StringRef SummaryTypeName) {
+  for (const auto &[Index, Entry] : llvm::enumerate(IDTable)) {
+    const auto *EntryObj = Entry.getAsObject();
+    if (!EntryObj) {
+      return createStringError(
+          inconvertibleErrorCode(),
+          "Cannot normalize %s JSON: id_table entry at index %zu "
+          "is not an object",
+          SummaryTypeName.data(), Index);
+    }
+
+    const auto *IDValue = EntryObj->get("id");
+    if (!IDValue) {
+      return createStringError(
+          inconvertibleErrorCode(),
+          "Cannot normalize %s JSON: id_table entry at index %zu "
+          "does not contain an 'id' field",
+          SummaryTypeName.data(), Index);
+    }
+
+    if (!IDValue->getAsUINT64()) {
+      return createStringError(
+          inconvertibleErrorCode(),
+          "Cannot normalize %s JSON: id_table entry at index %zu "
+          "does not contain a valid 'id' uint64_t field",
+          SummaryTypeName.data(), Index);
+    }
+  }
+
+  // Safe to dereference: all entries were validated above.
+  llvm::sort(IDTable, [](const json::Value &A, const json::Value &B) {
+    return *A.getAsObject()->get("id")->getAsUINT64() <
+           *B.getAsObject()->get("id")->getAsUINT64();
+  });
+
+  return llvm::Error::success();
+}
+
+llvm::Error normalizeLinkageTable(json::Array &LinkageTable,
+                                  llvm::StringRef SummaryTypeName) {
+  for (const auto &[Index, Entry] : llvm::enumerate(LinkageTable)) {
+    const auto *EntryObj = Entry.getAsObject();
+    if (!EntryObj) {
+      return createStringError(
+          inconvertibleErrorCode(),
+          "Cannot normalize %s JSON: linkage_table entry at index "
+          "%zu is not an object",
+          SummaryTypeName.data(), Index);
+    }
+
+    const auto *IDValue = EntryObj->get("id");
+    if (!IDValue) {
+      return createStringError(
+          inconvertibleErrorCode(),
+          "Cannot normalize %s JSON: linkage_table entry at index "
+          "%zu does not contain an 'id' field",
+          SummaryTypeName.data(), Index);
+    }
+
+    if (!IDValue->getAsUINT64()) {
+      return createStringError(
+          inconvertibleErrorCode(),
+          "Cannot normalize %s JSON: linkage_table entry at index "
+          "%zu does not contain a valid 'id' uint64_t field",
+          SummaryTypeName.data(), Index);
+    }
+  }
+
+  // Safe to dereference: all entries were validated above.
+  llvm::sort(LinkageTable, [](const json::Value &A, const json::Value &B) {
+    return *A.getAsObject()->get("id")->getAsUINT64() <
+           *B.getAsObject()->get("id")->getAsUINT64();
+  });
+
+  return llvm::Error::success();
+}
+
+llvm::Error normalizeSummaryData(json::Array &SummaryData, size_t DataIndex,
+                                 llvm::StringRef SummaryTypeName) {
+  for (const auto &[SummaryIndex, SummaryEntry] :
+       llvm::enumerate(SummaryData)) {
+    const auto *SummaryEntryObj = SummaryEntry.getAsObject();
+    if (!SummaryEntryObj) {
+      return createStringError(
+          inconvertibleErrorCode(),
+          "Cannot normalize %s JSON: data entry at index %zu, "
+          "summary_data entry at index %zu is not an object",
+          SummaryTypeName.data(), DataIndex, SummaryIndex);
+    }
+
+    const auto *EntityIDValue = SummaryEntryObj->get("entity_id");
+    if (!EntityIDValue) {
+      return createStringError(
+          inconvertibleErrorCode(),
+          "Cannot normalize %s JSON: data entry at index %zu, "
+          "summary_data entry at index %zu does not contain an "
+          "'entity_id' field",
+          SummaryTypeName.data(), DataIndex, SummaryIndex);
+    }
+
+    if (!EntityIDValue->getAsUINT64()) {
+      return createStringError(
+          inconvertibleErrorCode(),
+          "Cannot normalize %s JSON: data entry at index %zu, "
+          "summary_data entry at index %zu does not contain a valid "
+          "'entity_id' uint64_t field",
+          SummaryTypeName.data(), DataIndex, SummaryIndex);
+    }
+  }
+
+  // Safe to dereference: all entries were validated above.
+  llvm::sort(SummaryData, [](const json::Value &A, const json::Value &B) {
+    return *A.getAsObject()->get("entity_id")->getAsUINT64() <
+           *B.getAsObject()->get("entity_id")->getAsUINT64();
+  });
+
+  return llvm::Error::success();
+}
+
+llvm::Error normalizeData(json::Array &Data, llvm::StringRef SummaryTypeName) {
+  for (const auto &[DataIndex, DataEntry] : llvm::enumerate(Data)) {
+    auto *DataEntryObj = DataEntry.getAsObject();
+    if (!DataEntryObj) {
+      return createStringError(
+          inconvertibleErrorCode(),
+          "Cannot normalize %s JSON: data entry at index %zu "
+          "is not an object",
+          SummaryTypeName.data(), DataIndex);
+    }
+
+    if (!DataEntryObj->getString("summary_name")) {
+      return createStringError(
+          inconvertibleErrorCode(),
+          "Cannot normalize %s JSON: data entry at index %zu "
+          "does not contain a 'summary_name' string field",
+          SummaryTypeName.data(), DataIndex);
+    }
+
+    auto *SummaryData = DataEntryObj->getArray("summary_data");
+    if (!SummaryData) {
+      return createStringError(
+          inconvertibleErrorCode(),
+          "Cannot normalize %s JSON: data entry at index %zu "
+          "does not contain a 'summary_data' array field",
+          SummaryTypeName.data(), DataIndex);
+    }
+
+    if (auto Err =
+            normalizeSummaryData(*SummaryData, DataIndex, SummaryTypeName)) {
+      return Err;
+    }
+  }
+
+  // Safe to dereference: all entries were validated above.
+  llvm::sort(Data, [](const json::Value &A, const json::Value &B) {
+    return *A.getAsObject()->getString("summary_name") <
+           *B.getAsObject()->getString("summary_name");
+  });
+
+  return llvm::Error::success();
+}
+
+Expected<json::Value> normalizeSummaryJSON(json::Value Val,
+                                           llvm::StringRef SummaryTypeName) {
+  auto *Obj = Val.getAsObject();
+  if (!Obj) {
+    return createStringError(inconvertibleErrorCode(),
+                             "Cannot normalize %s JSON: expected an object",
+                             SummaryTypeName.data());
+  }
+
+  auto *IDTable = Obj->getArray("id_table");
+  if (!IDTable) {
+    return createStringError(inconvertibleErrorCode(),
+                             "Cannot normalize %s JSON: 'id_table' "
+                             "field is either missing or has the wrong type",
+                             SummaryTypeName.data());
+  }
+  if (auto Err = normalizeIDTable(*IDTable, SummaryTypeName)) {
+    return std::move(Err);
+  }
+
+  auto *LinkageTable = Obj->getArray("linkage_table");
+  if (!LinkageTable) {
+    return createStringError(inconvertibleErrorCode(),
+                             "Cannot normalize %s JSON: 'linkage_table' "
+                             "field is either missing or has the wrong type",
+                             SummaryTypeName.data());
+  }
+  if (auto Err = normalizeLinkageTable(*LinkageTable, SummaryTypeName)) {
+    return std::move(Err);
+  }
+
+  auto *Data = Obj->getArray("data");
+  if (!Data) {
+    return createStringError(inconvertibleErrorCode(),
+                             "Cannot normalize %s JSON: 'data' "
+                             "field is either missing or has the wrong type",
+                             SummaryTypeName.data());
+  }
+  if (auto Err = normalizeData(*Data, SummaryTypeName)) {
+    return std::move(Err);
+  }
+
+  return Val;
+}
+
+} // namespace
+
+// ============================================================================
+// SummaryTest Fixture Implementation
+// ============================================================================
+
+llvm::Error SummaryTest::readFromString(StringRef JSON,
+                                        StringRef FileName) const {
+  auto ExpectedFilePath = writeJSON(JSON, FileName);
+  if (!ExpectedFilePath) {
+    return ExpectedFilePath.takeError();
+  }
+  return GetParam().ReadFromFile(*ExpectedFilePath);
+}
+
+llvm::Error SummaryTest::readFromFile(StringRef FileName) const {
+  return GetParam().ReadFromFile(makePath(FileName));
+}
+
+llvm::Error SummaryTest::writeEmpty(StringRef FileName) const {
+  return GetParam().WriteEmpty(makePath(FileName));
+}
+
+llvm::Error SummaryTest::readWriteRoundTrip(StringRef InputFileName,
+                                            StringRef OutputFileName) const {
+  return GetParam().ReadWriteRoundTrip(makePath(InputFileName),
+                                       makePath(OutputFileName));
+}
+
+void SummaryTest::readWriteCompare(StringRef JSON) const {
+  const PathString InputFileName("input.json");
+  const PathString OutputFileName("output.json");
+
+  auto ExpectedInputFilePath = writeJSON(JSON, InputFileName);
+  ASSERT_THAT_EXPECTED(ExpectedInputFilePath, Succeeded());
+
+  ASSERT_THAT_ERROR(readWriteRoundTrip(InputFileName, OutputFileName),
+                    Succeeded());
+
+  auto ExpectedInputJSON = readJSONFromFile(InputFileName);
+  ASSERT_THAT_EXPECTED(ExpectedInputJSON, Succeeded());
+
+  auto ExpectedOutputJSON = readJSONFromFile(OutputFileName);
+  ASSERT_THAT_EXPECTED(ExpectedOutputJSON, Succeeded());
+
+  auto ExpectedNormalizedInputJSON =
+      normalizeSummaryJSON(*ExpectedInputJSON, GetParam().SummaryTypeName);
+  ASSERT_THAT_EXPECTED(ExpectedNormalizedInputJSON, Succeeded());
+
+  auto ExpectedNormalizedOutputJSON =
+      normalizeSummaryJSON(*ExpectedOutputJSON, GetParam().SummaryTypeName);
+  ASSERT_THAT_EXPECTED(ExpectedNormalizedOutputJSON, Succeeded());
+
+  ASSERT_EQ(*ExpectedNormalizedInputJSON, *ExpectedNormalizedOutputJSON)
+      << "Serialization is broken: input is different from output\n"
+      << "Input:  "
+      << llvm::formatv("{0:2}", *ExpectedNormalizedInputJSON).str() << "\n"
+      << "Output: "
+      << llvm::formatv("{0:2}", *ExpectedNormalizedOutputJSON).str();
+}
+
+namespace {
+
+// ============================================================================
+// First Test Analysis - Simple analysis for testing JSON serialization.
+// ============================================================================
+
+json::Object serializePairsEntitySummaryForJSONFormatTest(
+    const EntitySummary &Summary,
+    const JSONFormat::EntityIdConverter &Converter) {
+  const auto &TA =
+      static_cast<const PairsEntitySummaryForJSONFormatTest &>(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)}};
+}
+
+Expected<std::unique_ptr<EntitySummary>>
+deserializePairsEntitySummaryForJSONFormatTest(
+    const json::Object &Obj, EntityIdTable &IdTable,
+    const JSONFormat::EntityIdConverter &Converter) {
+  auto Result = std::make_unique<PairsEntitySummaryForJSONFormatTest>();
+  const json::Array *PairsArray = Obj.getArray("pairs");
+  if (!PairsArray) {
+    return createStringError(inconvertibleErrorCode(),
+                             "missing or invalid field 'pairs'");
+  }
+  for (const auto &[Index, Value] : llvm::enumerate(*PairsArray)) {
+    const json::Object *Pair = Value.getAsObject();
+    if (!Pair) {
+      return createStringError(
+          inconvertibleErrorCode(),
+          "pairs element at index %zu is not a JSON object", Index);
+    }
+    auto FirstOpt = Pair->getInteger("first");
+    if (!FirstOpt) {
+      return createStringError(
+          inconvertibleErrorCode(),
+          "missing or invalid 'first' field at index '%zu'", Index);
+    }
+    auto SecondOpt = Pair->getInteger("second");
+    if (!SecondOpt) {
+      return createStringError(
+          inconvertibleErrorCode(),
+          "missing or invalid 'second' field at index '%zu'", Index);
+    }
+    Result->Pairs.emplace_back(Converter.fromJSON(*FirstOpt),
+                               Converter.fromJSON(*SecondOpt));
+  }
+  return std::move(Result);
+}
+
+struct PairsEntitySummaryForJSONFormatTestFormatInfo final
+    : JSONFormat::FormatInfo {
+  PairsEntitySummaryForJSONFormatTestFormatInfo()
+      : JSONFormat::FormatInfo(
+            SummaryName("PairsEntitySummaryForJSONFormatTest"),
+            serializePairsEntitySummaryForJSONFormatTest,
+            deserializePairsEntitySummaryForJSONFormatTest) {}
+};
+
+llvm::Registry<JSONFormat::FormatInfo>::Add<
+    PairsEntitySummaryForJSONFormatTestFormatInfo>
+    RegisterPairsEntitySummaryForJSONFormatTest(
+        "PairsEntitySummaryForJSONFormatTest",
+        "Format info for PairsArrayEntitySummary");
+
+// ============================================================================
+// Second Test Analysis - Simple analysis for multi-summary round-trip tests.
+// ============================================================================
+
+json::Object serializeTagsEntitySummaryForJSONFormatTest(
+    const EntitySummary &Summary, const JSONFormat::EntityIdConverter &) {
+  const auto &TA =
+      static_cast<const TagsEntitySummaryForJSONFormatTest &>(Summary);
+  json::Array TagsArray;
+  for (const auto &Tag : TA.Tags) {
+    TagsArray.push_back(Tag);
+  }
+  return json::Object{{"tags", std::move(TagsArray)}};
+}
+
+Expected<std::unique_ptr<EntitySummary>>
+deserializeTagsEntitySummaryForJSONFormatTest(
+    const json::Object &Obj, EntityIdTable &,
+    const JSONFormat::EntityIdConverter &) {
+  auto Result = std::make_unique<TagsEntitySummaryForJSONFormatTest>();
+  const json::Array *TagsArray = Obj.getArray("tags");
+  if (!TagsArray) {
+    return createStringError(inconvertibleErrorCode(),
+                             "missing or invalid field 'tags'");
+  }
+  for (const auto &[Index, Value] : llvm::enumerate(*TagsArray)) {
+    auto Tag = Value.getAsString();
+    if (!Tag) {
+      return createStringError(inconvertibleErrorCode(),
+                               "tags element at index %zu is not a string",
+                               Index);
+    }
+    Result->Tags.push_back(Tag->str());
+  }
+  return std::move(Result);
+}
+
+struct TagsEntitySummaryForJSONFormatTestFormatInfo final
+    : JSONFormat::FormatInfo {
+  TagsEntitySummaryForJSONFormatTestFormatInfo()
+      : JSONFormat::FormatInfo(
+            SummaryName("TagsEntitySummaryForJSONFormatTest"),
+            serializeTagsEntitySummaryForJSONFormatTest,
+            deserializeTagsEntitySummaryForJSONFormatTest) {}
+};
+
+llvm::Registry<JSONFormat::FormatInfo>::Add<
+    TagsEntitySummaryForJSONFormatTestFormatInfo>
+    RegisterTagsEntitySummaryForJSONFormatTest(
+        "TagsEntitySummaryForJSONFormatTest",
+        "Format info for TagsEntitySummary");
+
+// ============================================================================
+// NullEntitySummaryForJSONFormatTest - For null data checks
+// ============================================================================
+
+struct NullEntitySummaryForJSONFormatTestFormatInfo final
+    : JSONFormat::FormatInfo {
+  NullEntitySummaryForJSONFormatTestFormatInfo()
+      : JSONFormat::FormatInfo(
+            SummaryName("NullEntitySummaryForJSONFormatTest"),
+            [](const EntitySummary &, const JSONFormat::EntityIdConverter &)
+                -> json::Object { return json::Object{}; },
+            [](const json::Object &, EntityIdTable &,
+               const JSONFormat::EntityIdConverter &)
+                -> llvm::Expected<std::unique_ptr<EntitySummary>> {
+              return nullptr;
+            }) {}
+};
+
+llvm::Registry<JSONFormat::FormatInfo>::Add<
+    NullEntitySummaryForJSONFormatTestFormatInfo>
+    RegisterNullEntitySummaryForJSONFormatTest(
+        "NullEntitySummaryForJSONFormatTest",
+        "Format info for NullEntitySummary");
+
+// ============================================================================
+// MismatchedEntitySummaryForJSONFormatTest - For mismatched SummaryName checks
+// ============================================================================
+
+struct MismatchedEntitySummaryForJSONFormatTestFormatInfo final
+    : JSONFormat::FormatInfo {
+  MismatchedEntitySummaryForJSONFormatTestFormatInfo()
+      : JSONFormat::FormatInfo(
+            SummaryName("MismatchedEntitySummaryForJSONFormatTest"),
+            [](const EntitySummary &, const JSONFormat::EntityIdConverter &)
+                -> json::Object { return json::Object{}; },
+            [](const json::Object &, EntityIdTable &,
+               const JSONFormat::EntityIdConverter &)
+                -> llvm::Expected<std::unique_ptr<EntitySummary>> {
+              return std::make_unique<
+                  MismatchedEntitySummaryForJSONFormatTest>();
+            }) {}
+};
+
+llvm::Registry<JSONFormat::FormatInfo>::Add<
+    MismatchedEntitySummaryForJSONFormatTestFormatInfo>
+    RegisterMismatchedEntitySummaryForJSONFormatTest(
+        "MismatchedEntitySummaryForJSONFormatTest",
+        "Format info for MismatchedEntitySummary");
+
+} // namespace
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.h b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.h
index a317e8f23a1b9..684a012efa98d 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.h
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.h
@@ -21,171 +21,146 @@
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/raw_ostream.h"
-#ifndef _WIN32
-#include <unistd.h>
-#endif
+
+#include <functional>
+#include <string>
+
+namespace clang::ssaf {
 
 // ============================================================================
 // Test Fixture
 // ============================================================================
 
-class JSONFormatTest : public clang::ssaf::TestFixture {
+class JSONFormatTest : public TestFixture {
 public:
   using PathString = llvm::SmallString<128>;
 
 protected:
   llvm::SmallString<128> TestDir;
 
-  void SetUp() override {
-    std::error_code EC =
-        llvm::sys::fs::createUniqueDirectory("json-format-test", TestDir);
-    ASSERT_FALSE(EC) << "Failed to create temp directory: " << EC.message();
-  }
+  void SetUp() override;
 
-  void TearDown() override { llvm::sys::fs::remove_directories(TestDir); }
+  void TearDown() override;
 
-  PathString makePath(llvm::StringRef FileOrDirectoryName) const {
-    PathString FullPath = TestDir;
-    llvm::sys::path::append(FullPath, FileOrDirectoryName);
+  PathString makePath(llvm::StringRef FileOrDirectoryName) const;
 
-    return FullPath;
-  }
+  PathString makePath(llvm::StringRef Dir, llvm::StringRef FileName) const;
 
-  PathString makePath(llvm::StringRef Dir, llvm::StringRef FileName) const {
-    PathString FullPath = TestDir;
-    llvm::sys::path::append(FullPath, Dir, FileName);
+  llvm::Expected<PathString> makeDirectory(llvm::StringRef DirectoryName) const;
 
-    return FullPath;
-  }
+  llvm::Expected<PathString> makeSymlink(llvm::StringRef TargetFileName,
+                                         llvm::StringRef SymlinkFileName) const;
+
+  llvm::Error setPermission(llvm::StringRef FileName,
+                            llvm::sys::fs::perms Perms) const;
 
-  llvm::Expected<PathString>
-  makeDirectory(llvm::StringRef DirectoryName) const {
-    PathString DirPath = makePath(DirectoryName);
+  // Returns true if Unix file permission checks are enforced in this
+  // environment. Returns false if running as root (uid 0), or if a probe
+  // file with read permission removed can still be opened, indicating that
+  // permission checks are not enforced (e.g. certain container setups).
+  // Tests that rely on permission-based failure conditions should skip
+  // themselves when this returns false.
+  bool permissionsAreEnforced() const;
 
-    std::error_code EC = llvm::sys::fs::create_directory(DirPath);
-    if (EC) {
-      return llvm::createStringError(EC, "Failed to create directory '%s': %s",
-                                     DirPath.c_str(), EC.message().c_str());
-    }
+  llvm::Expected<llvm::json::Value>
+  readJSONFromFile(llvm::StringRef FileName) const;
 
-    return DirPath;
-  }
+  llvm::Expected<PathString> writeJSON(llvm::StringRef JSON,
+                                       llvm::StringRef FileName) const;
+};
+
+// ============================================================================
+// SummaryOps - Parameterization struct for TUSummary/LUSummary test suites
+// ============================================================================
+
+struct SummaryOps {
+  std::string Name;
+  std::string SummaryTypeName;
+
+  std::function<llvm::Error(llvm::StringRef FilePath)> ReadFromFile;
+
+  std::function<llvm::Error(llvm::StringRef FilePath)> WriteEmpty;
+
+  std::function<llvm::Error(llvm::StringRef InputFilePath,
+                            llvm::StringRef OutputFilePath)>
+      ReadWriteRoundTrip;
+};
+
+// ============================================================================
+// SummaryTest Test Fixture
+// ============================================================================
 
-  llvm::Expected<PathString>
-  makeSymlink(llvm::StringRef TargetFileName,
-              llvm::StringRef SymlinkFileName) const {
-    PathString TargetPath = makePath(TargetFileName);
-    PathString SymlinkPath = makePath(SymlinkFileName);
+class SummaryTest : public JSONFormatTest,
+                    public ::testing::WithParamInterface<SummaryOps> {
+protected:
+  llvm::Error readFromString(llvm::StringRef JSON,
+                             llvm::StringRef FileName = "test.json") const;
+
+  llvm::Error readFromFile(llvm::StringRef FileName) const;
 
-    std::error_code EC = llvm::sys::fs::create_link(TargetPath, SymlinkPath);
-    if (EC) {
-      return llvm::createStringError(
-          EC, "Failed to create symlink '%s' -> '%s': %s", SymlinkPath.c_str(),
-          TargetPath.c_str(), EC.message().c_str());
-    }
+  llvm::Error writeEmpty(llvm::StringRef FileName) const;
 
-    return SymlinkPath;
+  llvm::Error readWriteRoundTrip(llvm::StringRef InputFileName,
+                                 llvm::StringRef OutputFileName) const;
+
+  void readWriteCompare(llvm::StringRef JSON) const;
+};
+
+// ============================================================================
+// First Test Analysis - Simple analysis for testing JSON serialization.
+// ============================================================================
+
+struct PairsEntitySummaryForJSONFormatTest final : EntitySummary {
+
+  SummaryName getSummaryName() const override {
+    return SummaryName("PairsEntitySummaryForJSONFormatTest");
   }
 
-  llvm::Error setPermission(llvm::StringRef FileName,
-                            llvm::sys::fs::perms Perms) const {
-    PathString Path = makePath(FileName);
+  std::vector<std::pair<EntityId, EntityId>> Pairs;
+};
 
-    std::error_code EC = llvm::sys::fs::setPermissions(Path, Perms);
-    if (EC) {
-      return llvm::createStringError(EC,
-                                     "Failed to set permissions on '%s': %s",
-                                     Path.c_str(), EC.message().c_str());
-    }
+// ============================================================================
+// Second Test Analysis - Simple analysis for multi-summary round-trip tests.
+// ============================================================================
 
-    return llvm::Error::success();
+struct TagsEntitySummaryForJSONFormatTest final : EntitySummary {
+  SummaryName getSummaryName() const override {
+    return SummaryName("TagsEntitySummaryForJSONFormatTest");
   }
 
-  // Returns true if Unix file permission checks are enforced in this
-  // environment. Returns false if running as root (uid 0), or if a probe
-  // file with read permission removed can still be opened, indicating that
-  // permission checks are not enforced (e.g. certain container setups).
-  // Tests that rely on permission-based failure conditions should skip
-  // themselves when this returns false.
-  bool permissionsAreEnforced() const {
-#ifdef _WIN32
-    return false;
-#else
-    if (getuid() == 0) {
-      return false;
-    }
-
-    // Write a probe file, remove read permission, and try to open it.
-    PathString ProbePath = makePath("perm-probe.json");
-
-    {
-      std::error_code EC;
-      llvm::raw_fd_ostream OS(ProbePath, EC);
-      if (EC) {
-        return true; // Probe setup failed; assume enforced to avoid
-                     // silently suppressing the test.
-      }
-      OS << "{}";
-    }
-
-    std::error_code PermEC = llvm::sys::fs::setPermissions(
-        ProbePath, llvm::sys::fs::perms::owner_write);
-    if (PermEC) {
-      return true; // Probe setup failed; assume enforced to avoid
-                   // silently suppressing the test.
-    }
-
-    auto Buffer = llvm::MemoryBuffer::getFile(ProbePath);
-    bool Enforced = !Buffer; // If open failed, permissions are enforced.
-
-    // Restore permissions so TearDown can clean up the temp directory.
-    llvm::sys::fs::setPermissions(ProbePath, llvm::sys::fs::perms::all_all);
-
-    return Enforced;
-#endif
+  std::vector<std::string> Tags;
+};
+
+// ============================================================================
+// NullEntitySummaryForJSONFormatTest - For null data checks
+// ============================================================================
+
+struct NullEntitySummaryForJSONFormatTest final : EntitySummary {
+  SummaryName getSummaryName() const override {
+    return SummaryName("NullEntitySummaryForJSONFormatTest");
   }
+};
 
-  llvm::Expected<llvm::json::Value>
-  readJSONFromFile(llvm::StringRef FileName) const {
-    PathString FilePath = makePath(FileName);
-
-    auto BufferOrError = llvm::MemoryBuffer::getFile(FilePath);
-    if (!BufferOrError) {
-      return llvm::createStringError(BufferOrError.getError(),
-                                     "Failed to read file: %s",
-                                     FilePath.c_str());
-    }
-
-    llvm::Expected<llvm::json::Value> ExpectedValue =
-        llvm::json::parse(BufferOrError.get()->getBuffer());
-    if (!ExpectedValue)
-      return ExpectedValue.takeError();
-
-    return *ExpectedValue;
+// ============================================================================
+// UnregisteredEntitySummaryForJSONFormatTest - For missing FormatInfo checks
+// ============================================================================
+
+struct UnregisteredEntitySummaryForJSONFormatTest final : EntitySummary {
+  SummaryName getSummaryName() const override {
+    return SummaryName("UnregisteredEntitySummaryForJSONFormatTest");
   }
+};
 
-  llvm::Expected<PathString> writeJSON(llvm::StringRef JSON,
-                                       llvm::StringRef FileName) const {
-    PathString FilePath = makePath(FileName);
-
-    std::error_code EC;
-    llvm::raw_fd_ostream OS(FilePath, EC);
-    if (EC) {
-      return llvm::createStringError(EC, "Failed to create file '%s': %s",
-                                     FilePath.c_str(), EC.message().c_str());
-    }
-
-    OS << JSON;
-    OS.close();
-
-    if (OS.has_error()) {
-      return llvm::createStringError(
-          OS.error(), "Failed to write to file '%s': %s", FilePath.c_str(),
-          OS.error().message().c_str());
-    }
-
-    return FilePath;
+// ============================================================================
+// MismatchedEntitySummaryForJSONFormatTest - For mismatched SummaryName checks
+// ============================================================================
+
+struct MismatchedEntitySummaryForJSONFormatTest final : EntitySummary {
+  SummaryName getSummaryName() const override {
+    return SummaryName("MismatchedEntitySummaryForJSONFormatTest_WrongName");
   }
 };
 
+} // namespace clang::ssaf
+
 #endif // LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMATTEST_JSONFORMATTEST_H
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp
new file mode 100644
index 0000000000000..b70b947a403b7
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp
@@ -0,0 +1,2518 @@
+//===- LUSummaryTest.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 of
+// LUSummary and LUSummaryEncoding.
+//
+//===----------------------------------------------------------------------===//
+
+#include "JSONFormatTest.h"
+
+#include "clang/Analysis/Scalable/EntityLinker/LUSummary.h"
+#include "clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h"
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gmock/gmock.h"
+
+#include <memory>
+#include <vector>
+
+using namespace clang::ssaf;
+using namespace llvm;
+using ::testing::AllOf;
+using ::testing::HasSubstr;
+
+namespace {
+
+// ============================================================================
+// LUSummaryOps - Parameterization for LUSummary/LUSummaryEncoding tests
+// ============================================================================
+
+SummaryOps LUSummaryOps{
+    "Resolved", "LUSummary",
+    [](llvm::StringRef FilePath) -> llvm::Error {
+      auto Result = JSONFormat().readLUSummary(FilePath);
+      return Result ? llvm::Error::success() : Result.takeError();
+    },
+    [](llvm::StringRef FilePath) -> llvm::Error {
+      BuildNamespace BN(BuildNamespaceKind::CompilationUnit, "test.cpp");
+      NestedBuildNamespace NBN(std::move(BN));
+      LUSummary S(std::move(NBN));
+      return JSONFormat().writeLUSummary(S, FilePath);
+    },
+    [](llvm::StringRef InputFilePath,
+       llvm::StringRef OutputFilePath) -> llvm::Error {
+      auto ExpectedS = JSONFormat().readLUSummary(InputFilePath);
+      if (!ExpectedS) {
+        return ExpectedS.takeError();
+      }
+      return JSONFormat().writeLUSummary(*ExpectedS, OutputFilePath);
+    }};
+
+SummaryOps LUSummaryEncodingOps{
+    "Encoding", "LUSummary",
+    [](llvm::StringRef FilePath) -> llvm::Error {
+      auto Result = JSONFormat().readLUSummaryEncoding(FilePath);
+      return Result ? llvm::Error::success() : Result.takeError();
+    },
+    [](llvm::StringRef FilePath) -> llvm::Error {
+      BuildNamespace BN(BuildNamespaceKind::CompilationUnit, "test.cpp");
+      NestedBuildNamespace NBN(std::move(BN));
+      LUSummaryEncoding E(std::move(NBN));
+      return JSONFormat().writeLUSummaryEncoding(E, FilePath);
+    },
+    [](llvm::StringRef InputFilePath,
+       llvm::StringRef OutputFilePath) -> llvm::Error {
+      auto ExpectedE = JSONFormat().readLUSummaryEncoding(InputFilePath);
+      if (!ExpectedE) {
+        return ExpectedE.takeError();
+      }
+      return JSONFormat().writeLUSummaryEncoding(*ExpectedE, OutputFilePath);
+    }};
+
+// ============================================================================
+// LUSummaryTest Test Fixture
+// ============================================================================
+
+class LUSummaryTest : public SummaryTest {};
+
+INSTANTIATE_TEST_SUITE_P(JSONFormat, LUSummaryTest,
+                         ::testing::Values(LUSummaryOps, LUSummaryEncodingOps),
+                         [](const ::testing::TestParamInfo<SummaryOps> &Info) {
+                           return Info.param.Name;
+                         });
+
+// ============================================================================
+// JSONFormatLUSummaryTest Test Fixture
+// ============================================================================
+
+class JSONFormatLUSummaryTest : public JSONFormatTest {
+protected:
+  llvm::Expected<LUSummary> readLUSummaryFromFile(StringRef FileName) const {
+    PathString FilePath = makePath(FileName);
+    return JSONFormat().readLUSummary(FilePath);
+  }
+
+  llvm::Expected<LUSummary>
+  readLUSummaryFromString(StringRef JSON,
+                          StringRef FileName = "test.json") const {
+    auto ExpectedFilePath = writeJSON(JSON, FileName);
+    if (!ExpectedFilePath) {
+      return ExpectedFilePath.takeError();
+    }
+
+    return readLUSummaryFromFile(FileName);
+  }
+
+  llvm::Error writeLUSummary(const LUSummary &Summary,
+                             StringRef FileName) const {
+    PathString FilePath = makePath(FileName);
+    return JSONFormat().writeLUSummary(Summary, FilePath);
+  }
+};
+
+// ============================================================================
+// readJSON() Error Tests
+// ============================================================================
+
+TEST_P(LUSummaryTest, NonexistentFile) {
+  auto Result = readFromFile("nonexistent.json");
+
+  EXPECT_THAT_ERROR(std::move(Result),
+                    FailedWithMessage(AllOf(HasSubstr("reading LUSummary from"),
+                                            HasSubstr("file does not exist"))));
+}
+
+TEST_P(LUSummaryTest, PathIsDirectory) {
+  PathString DirName("test_directory.json");
+
+  auto ExpectedDirPath = makeDirectory(DirName);
+  ASSERT_THAT_EXPECTED(ExpectedDirPath, Succeeded());
+
+  auto Result = readFromFile(DirName);
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(HasSubstr("reading LUSummary from"),
+                              HasSubstr("path is a directory, not a file"))));
+}
+
+TEST_P(LUSummaryTest, NotJsonExtension) {
+  PathString FileName("test.txt");
+
+  auto ExpectedFilePath = writeJSON("{}", FileName);
+  ASSERT_THAT_EXPECTED(ExpectedFilePath, Succeeded());
+
+  auto Result = readFromFile(FileName);
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary from file"),
+                HasSubstr("failed to read file"),
+                HasSubstr("file does not end with '.json' extension"))));
+}
+
+TEST_P(LUSummaryTest, BrokenSymlink) {
+#ifdef _WIN32
+  GTEST_SKIP() << "Symlink model differs on Windows";
+#endif
+
+  // Create a symlink pointing to a non-existent file
+  auto ExpectedSymlinkPath =
+      makeSymlink("nonexistent_target.json", "broken_symlink.json");
+  ASSERT_THAT_EXPECTED(ExpectedSymlinkPath, Succeeded());
+
+  auto Result = readFromFile(*ExpectedSymlinkPath);
+
+  EXPECT_THAT_ERROR(std::move(Result),
+                    FailedWithMessage(AllOf(HasSubstr("reading LUSummary from"),
+                                            HasSubstr("failed to read file"))));
+}
+
+TEST_P(LUSummaryTest, NoReadPermission) {
+  if (!permissionsAreEnforced()) {
+    GTEST_SKIP() << "File permission checks are not enforced in this "
+                    "environment";
+  }
+
+  PathString FileName("no-read-permission.json");
+
+  auto ExpectedFilePath = writeJSON(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": []
+  })",
+                                    FileName);
+  ASSERT_THAT_EXPECTED(ExpectedFilePath, Succeeded());
+
+  auto PermError = setPermission(FileName, sys::fs::perms::owner_write |
+                                               sys::fs::perms::owner_exe);
+  ASSERT_THAT_ERROR(std::move(PermError), Succeeded());
+
+  auto Result = readFromFile(FileName);
+
+  EXPECT_THAT_ERROR(std::move(Result),
+                    FailedWithMessage(AllOf(HasSubstr("reading LUSummary from"),
+                                            HasSubstr("failed to read file"))));
+
+  // Restore permissions for cleanup
+  auto RestoreError = setPermission(FileName, sys::fs::perms::all_all);
+  EXPECT_THAT_ERROR(std::move(RestoreError), Succeeded());
+}
+
+TEST_P(LUSummaryTest, InvalidSyntax) {
+  auto Result = readFromString("{ invalid json }");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(HasSubstr("reading LUSummary from file"),
+                              HasSubstr("Expected object key"))));
+}
+
+TEST_P(LUSummaryTest, NotObject) {
+  auto Result = readFromString("[]");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(HasSubstr("reading LUSummary from file"),
+                              HasSubstr("failed to read LUSummary"),
+                              HasSubstr("expected JSON object"))));
+}
+
+// ============================================================================
+// JSONFormat::entityLinkageFromJSON() Error Tests
+// ============================================================================
+
+TEST_P(LUSummaryTest, LinkageTableEntryLinkageMissingType) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [
+      {
+        "id": 0,
+        "linkage": {}
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary from file"),
+                HasSubstr("reading LinkageTable from field 'linkage_table'"),
+                HasSubstr("reading LinkageTable entry from index '0'"),
+                HasSubstr("reading EntityLinkage from field 'linkage'"),
+                HasSubstr("failed to read EntityLinkageType from field 'type'"),
+                HasSubstr("expected JSON string"))));
+}
+
+TEST_P(LUSummaryTest, LinkageTableEntryLinkageInvalidType) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [
+      {
+        "id": 0,
+        "linkage": { "type": "invalid_type" }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary from file"),
+                HasSubstr("reading LinkageTable from field 'linkage_table'"),
+                HasSubstr("reading LinkageTable entry from index '0'"),
+                HasSubstr("reading EntityLinkage from field 'linkage'"),
+                HasSubstr("invalid EntityLinkageType value 'invalid_type' for "
+                          "field 'type'"))));
+}
+
+// ============================================================================
+// JSONFormat::linkageTableEntryFromJSON() Error Tests
+// ============================================================================
+
+TEST_P(LUSummaryTest, LinkageTableEntryMissingId) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [
+      {
+        "linkage": { "type": "External" }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary from file"),
+                HasSubstr("reading LinkageTable from field 'linkage_table'"),
+                HasSubstr("reading LinkageTable entry from index '0'"),
+                HasSubstr("failed to read EntityId from field 'id'"),
+                HasSubstr("expected JSON number (unsigned 64-bit integer)"))));
+}
+
+TEST_P(LUSummaryTest, LinkageTableEntryIdNotUInt64) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [
+      {
+        "id": "not_a_number",
+        "linkage": { "type": "External" }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary from file"),
+                HasSubstr("reading LinkageTable from field 'linkage_table'"),
+                HasSubstr("reading LinkageTable entry from index '0'"),
+                HasSubstr("failed to read EntityId from field 'id'"),
+                HasSubstr("expected JSON number (unsigned 64-bit integer)"))));
+}
+
+TEST_P(LUSummaryTest, LinkageTableEntryMissingLinkage) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [
+      {
+        "id": 0
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary from file"),
+                HasSubstr("reading LinkageTable from field 'linkage_table'"),
+                HasSubstr("reading LinkageTable entry from index '0'"),
+                HasSubstr("failed to read EntityLinkage from field 'linkage'"),
+                HasSubstr("expected JSON object"))));
+}
+
+// ============================================================================
+// JSONFormat::linkageTableFromJSON() Error Tests
+// ============================================================================
+
+TEST_P(LUSummaryTest, LinkageTableNotArray) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": {},
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary from file"),
+          HasSubstr("failed to read LinkageTable from field 'linkage_table'"),
+          HasSubstr("expected JSON array"))));
+}
+
+TEST_P(LUSummaryTest, LinkageTableElementNotObject) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": ["invalid"],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary from file"),
+                HasSubstr("reading LinkageTable from field 'linkage_table'"),
+                HasSubstr("failed to read LinkageTable entry from index '0'"),
+                HasSubstr("expected JSON object"))));
+}
+
+TEST_P(LUSummaryTest, LinkageTableExtraId) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [
+      {
+        "id": 0,
+        "linkage": { "type": "External" }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary from file"),
+                HasSubstr("reading LinkageTable from field 'linkage_table'"),
+                HasSubstr("reading LinkageTable entry from index '0'"),
+                HasSubstr("failed to deserialize LinkageTable"),
+                HasSubstr("extra 'EntityId(0)' not present in IdTable"))));
+}
+
+TEST_P(LUSummaryTest, LinkageTableMissingId) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            },
+            {
+              "kind": "LinkUnit",
+              "name": "test.exe"
+            }
+          ]
+        }
+      }
+    ],
+    "linkage_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary from file"),
+                HasSubstr("reading LinkageTable from field 'linkage_table'"),
+                HasSubstr("failed to deserialize LinkageTable"),
+                HasSubstr("missing 'EntityId(0)' present in IdTable"))));
+}
+
+TEST_P(LUSummaryTest, LinkageTableDuplicateId) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            },
+            {
+              "kind": "LinkUnit",
+              "name": "test.exe"
+            }
+          ]
+        }
+      }
+    ],
+    "linkage_table": [
+      {
+        "id": 0,
+        "linkage": { "type": "External" }
+      },
+      {
+        "id": 0,
+        "linkage": { "type": "Internal" }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary from file"),
+                HasSubstr("reading LinkageTable from field 'linkage_table'"),
+                HasSubstr("failed to insert LinkageTable entry at index '1'"),
+                HasSubstr("encountered duplicate 'EntityId(0)'"))));
+}
+
+// ============================================================================
+// JSONFormat::nestedBuildNamespaceFromJSON() Error Tests (lu_namespace field)
+// ============================================================================
+
+TEST_P(LUSummaryTest, LUNamespaceNotArray) {
+  auto Result = readFromString(R"({
+    "lu_namespace": { "kind": "CompilationUnit", "name": "test.cpp" },
+    "id_table": [],
+    "linkage_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary from file"),
+          HasSubstr(
+              "failed to read NestedBuildNamespace from field 'lu_namespace'"),
+          HasSubstr("expected JSON array"))));
+}
+
+TEST_P(LUSummaryTest, LUNamespaceElementNotObject) {
+  auto Result = readFromString(R"({
+    "lu_namespace": ["invalid"],
+    "id_table": [],
+    "linkage_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary from file"),
+          HasSubstr("reading NestedBuildNamespace from field 'lu_namespace'"),
+          HasSubstr("failed to read BuildNamespace from index '0'"),
+          HasSubstr("expected JSON object"))));
+}
+
+TEST_P(LUSummaryTest, LUNamespaceElementMissingKind) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [{ "name": "test.cpp" }],
+    "id_table": [],
+    "linkage_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary from file"),
+          HasSubstr("reading NestedBuildNamespace from field 'lu_namespace'"),
+          HasSubstr("reading BuildNamespace from index '0'"),
+          HasSubstr("failed to read BuildNamespaceKind from field 'kind'"),
+          HasSubstr("expected JSON string"))));
+}
+
+TEST_P(LUSummaryTest, LUNamespaceElementInvalidKind) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [{ "kind": "invalid_kind", "name": "test.cpp" }],
+    "id_table": [],
+    "linkage_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary from file"),
+          HasSubstr("reading NestedBuildNamespace from field 'lu_namespace'"),
+          HasSubstr("reading BuildNamespace from index '0'"),
+          HasSubstr("reading BuildNamespaceKind from field 'kind'"),
+          HasSubstr("invalid BuildNamespaceKind value 'invalid_kind' for "
+                    "field 'kind'"))));
+}
+
+TEST_P(LUSummaryTest, LUNamespaceElementMissingName) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [{ "kind": "CompilationUnit" }],
+    "id_table": [],
+    "linkage_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary from file"),
+          HasSubstr("reading NestedBuildNamespace from field 'lu_namespace'"),
+          HasSubstr("reading BuildNamespace from index '0'"),
+          HasSubstr("failed to read BuildNamespaceName from field 'name'"),
+          HasSubstr("expected JSON string"))));
+}
+
+// ============================================================================
+// JSONFormat::nestedBuildNamespaceFromJSON() Error Tests (EntityName namespace)
+// ============================================================================
+
+TEST_P(LUSummaryTest, NamespaceElementNotObject) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": ["invalid"]
+        }
+      }
+    ],
+    "linkage_table": [
+      {
+        "id": 0,
+        "linkage": { "type": "None" }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary from file"),
+          HasSubstr("reading IdTable from field 'id_table'"),
+          HasSubstr("reading EntityIdTable entry from index '0'"),
+          HasSubstr("reading EntityName from field 'name'"),
+          HasSubstr("reading NestedBuildNamespace from field 'namespace'"),
+          HasSubstr("failed to read BuildNamespace from index '0'"),
+          HasSubstr("expected JSON object"))));
+}
+
+TEST_P(LUSummaryTest, NamespaceElementMissingKind) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "name": "test.cpp"
+            }
+          ]
+        }
+      }
+    ],
+    "linkage_table": [
+      {
+        "id": 0,
+        "linkage": { "type": "Internal" }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary from file"),
+          HasSubstr("reading IdTable from field 'id_table'"),
+          HasSubstr("reading EntityIdTable entry from index '0'"),
+          HasSubstr("reading EntityName from field 'name'"),
+          HasSubstr("reading NestedBuildNamespace from field 'namespace'"),
+          HasSubstr("reading BuildNamespace from index '0'"),
+          HasSubstr("failed to read BuildNamespaceKind from field 'kind'"),
+          HasSubstr("expected JSON string"))));
+}
+
+TEST_P(LUSummaryTest, NamespaceElementInvalidKind) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "invalid_kind",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      }
+    ],
+    "linkage_table": [
+      {
+        "id": 0,
+        "linkage": { "type": "External" }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary from file"),
+          HasSubstr("reading IdTable from field 'id_table'"),
+          HasSubstr("reading EntityIdTable entry from index '0'"),
+          HasSubstr("reading EntityName from field 'name'"),
+          HasSubstr("reading NestedBuildNamespace from field 'namespace'"),
+          HasSubstr("reading BuildNamespace from index '0'"),
+          HasSubstr("reading BuildNamespaceKind from field 'kind'"),
+          HasSubstr("invalid BuildNamespaceKind value 'invalid_kind' for "
+                    "field 'kind'"))));
+}
+
+TEST_P(LUSummaryTest, NamespaceElementMissingName) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit"
+            }
+          ]
+        }
+      }
+    ],
+    "linkage_table": [
+      {
+        "id": 0,
+        "linkage": { "type": "None" }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary from file"),
+          HasSubstr("reading IdTable from field 'id_table'"),
+          HasSubstr("reading EntityIdTable entry from index '0'"),
+          HasSubstr("reading EntityName from field 'name'"),
+          HasSubstr("reading NestedBuildNamespace from field 'namespace'"),
+          HasSubstr("reading BuildNamespace from index '0'"),
+          HasSubstr("failed to read BuildNamespaceName from field 'name'"),
+          HasSubstr("expected JSON string"))));
+}
+
+// ============================================================================
+// JSONFormat::entityNameFromJSON() Error Tests
+// ============================================================================
+
+TEST_P(LUSummaryTest, EntityNameMissingUSR) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            },
+            {
+              "kind": "LinkUnit",
+              "name": "test.exe"
+            }
+          ]
+        }
+      }
+    ],
+    "linkage_table": [
+      {
+        "id": 0,
+        "linkage": { "type": "None" }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(std::move(Result),
+                    FailedWithMessage(AllOf(
+                        HasSubstr("reading LUSummary 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"))));
+}
+
+TEST_P(LUSummaryTest, EntityNameMissingSuffix) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            },
+            {
+              "kind": "LinkUnit",
+              "name": "test.exe"
+            }
+          ]
+        }
+      }
+    ],
+    "linkage_table": [
+      {
+        "id": 0,
+        "linkage": { "type": "None" }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(std::move(Result),
+                    FailedWithMessage(AllOf(
+                        HasSubstr("reading LUSummary 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_P(LUSummaryTest, EntityNameMissingNamespace) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": ""
+        }
+      }
+    ],
+    "linkage_table": [
+      {
+        "id": 0,
+        "linkage": { "type": "None" }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary 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 NestedBuildNamespace from field "
+                          "'namespace'"),
+                HasSubstr("expected JSON array"))));
+}
+
+// ============================================================================
+// JSONFormat::entityIdTableEntryFromJSON() Error Tests
+// ============================================================================
+
+TEST_P(LUSummaryTest, IDTableEntryMissingID) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [
+      {
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            },
+            {
+              "kind": "LinkUnit",
+              "name": "test.exe"
+            }
+          ]
+        }
+      }
+    ],
+    "linkage_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary 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 number (unsigned 64-bit integer)"))));
+}
+
+TEST_P(LUSummaryTest, IDTableEntryMissingName) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [
+      {
+        "id": 0
+      }
+    ],
+    "linkage_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary 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_P(LUSummaryTest, IDTableEntryIDNotUInt64) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [
+      {
+        "id": "not_a_number",
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            },
+            {
+              "kind": "LinkUnit",
+              "name": "test.exe"
+            }
+          ]
+        }
+      }
+    ],
+    "linkage_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary 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 number (unsigned 64-bit integer)"))));
+}
+
+// ============================================================================
+// JSONFormat::entityIdTableFromJSON() Error Tests
+// ============================================================================
+
+TEST_P(LUSummaryTest, IDTableNotArray) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": {},
+    "linkage_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary from file"),
+                HasSubstr("failed to read IdTable from field 'id_table'"),
+                HasSubstr("expected JSON array"))));
+}
+
+TEST_P(LUSummaryTest, IDTableElementNotObject) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": ["invalid"],
+    "linkage_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary from file"),
+                HasSubstr("reading IdTable from field 'id_table'"),
+                HasSubstr("failed to read EntityIdTable entry from index '0'"),
+                HasSubstr("expected JSON object"))));
+}
+
+TEST_P(LUSummaryTest, DuplicateEntity) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            },
+            {
+              "kind": "LinkUnit",
+              "name": "test.exe"
+            }
+          ]
+        }
+      },
+      {
+        "id": 1,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            },
+            {
+              "kind": "LinkUnit",
+              "name": "test.exe"
+            }
+          ]
+        }
+      }
+    ],
+    "linkage_table": [
+      { "id": 0, "linkage": { "type": "None" } },
+      { "id": 1, "linkage": { "type": "None" } }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary from file"),
+                HasSubstr("reading IdTable from field 'id_table'"),
+                HasSubstr("failed to insert EntityIdTable entry at index '1'"),
+                HasSubstr("encountered duplicate 'EntityId(0)'"))));
+}
+
+// ============================================================================
+// JSONFormat::readLUSummary() Error Tests (LUSummary-only)
+// ============================================================================
+
+TEST_F(JSONFormatLUSummaryTest, ReadEntitySummaryNoFormatInfo) {
+  auto Result = readLUSummaryFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": [
+      {
+        "summary_name": "UnregisteredEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {}
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary 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 "
+              "'SummaryName(UnregisteredEntitySummaryForJSONFormatTest)'"))));
+}
+
+// ============================================================================
+// PairsEntitySummaryForJSONFormatTest Deserialization Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatLUSummaryTest,
+       PairsEntitySummaryForJSONFormatTestMissingPairsField) {
+  auto Result = readLUSummaryFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {}
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary 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(JSONFormatLUSummaryTest,
+       PairsEntitySummaryForJSONFormatTestInvalidPairsFieldType) {
+  auto Result = readLUSummaryFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": "not_an_array"
+            }
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary 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(JSONFormatLUSummaryTest,
+       PairsEntitySummaryForJSONFormatTestPairsElementNotObject) {
+  auto Result = readLUSummaryFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": ["not_an_object"]
+            }
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary 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("pairs element at index 0 is not a JSON object"))));
+}
+
+TEST_F(JSONFormatLUSummaryTest,
+       PairsEntitySummaryForJSONFormatTestMissingFirstField) {
+  auto Result = readLUSummaryFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": [
+                {
+                  "second": 1
+                }
+              ]
+            }
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary 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 'first' field at index '0'"))));
+}
+
+TEST_F(JSONFormatLUSummaryTest,
+       PairsEntitySummaryForJSONFormatTestInvalidFirstField) {
+  auto Result = readLUSummaryFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": [
+                {
+                  "first": "not_a_number",
+                  "second": 1
+                }
+              ]
+            }
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary 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 'first' field at index '0'"))));
+}
+
+TEST_F(JSONFormatLUSummaryTest,
+       PairsEntitySummaryForJSONFormatTestMissingSecondField) {
+  auto Result = readLUSummaryFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": [
+                {
+                  "first": 0
+                }
+              ]
+            }
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary 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(JSONFormatLUSummaryTest,
+       PairsEntitySummaryForJSONFormatTestInvalidSecondField) {
+  auto Result = readLUSummaryFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": [
+                {
+                  "first": 0,
+                  "second": "not_a_number"
+                }
+              ]
+            }
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary 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'"))));
+}
+
+// ============================================================================
+// JSONFormat::entityDataMapFromJSON() Error Tests
+// ============================================================================
+
+TEST_P(LUSummaryTest, EntityDataMissingEntityID) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_summary": {}
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary 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 number (unsigned 64-bit integer)"))));
+}
+
+TEST_P(LUSummaryTest, EntityDataMissingEntitySummary) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary 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_P(LUSummaryTest, EntityIDNotUInt64) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": "not_a_number",
+            "entity_summary": {}
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary 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 number (unsigned 64-bit integer)"))));
+}
+
+TEST_F(JSONFormatLUSummaryTest, ReadEntitySummaryMissingData) {
+  auto Result = readLUSummaryFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": [
+      {
+        "summary_name": "NullEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {}
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary 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 deserialize EntitySummary"),
+          HasSubstr("null EntitySummary data for "
+                    "'SummaryName(NullEntitySummaryForJSONFormatTest)'"))));
+}
+
+TEST_F(JSONFormatLUSummaryTest, ReadEntitySummaryMismatchedSummaryName) {
+  auto Result = readLUSummaryFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": [
+      {
+        "summary_name": "MismatchedEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {}
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary 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 deserialize EntitySummary"),
+          HasSubstr(
+              "EntitySummary data for "
+              "'SummaryName(MismatchedEntitySummaryForJSONFormatTest)' reports "
+              "mismatched "
+              "'SummaryName(MismatchedEntitySummaryForJSONFormatTest_WrongName)"
+              "'"))));
+}
+
+// ============================================================================
+// JSONFormat::entityDataMapEntryToJSON() Fatal Tests
+// ============================================================================
+
+TEST_F(JSONFormatLUSummaryTest, WriteEntitySummaryMissingData) {
+  LUSummary Summary(NestedBuildNamespace(std::vector<BuildNamespace>{}));
+
+  NestedBuildNamespace Namespace =
+      NestedBuildNamespace::makeCompilationUnit("test.cpp");
+  EntityId EI = getIdTable(Summary).getId(
+      EntityName{"c:@F at foo", "", std::move(Namespace)});
+
+  SummaryName SN("NullEntitySummaryForJSONFormatTest");
+  getData(Summary)[SN][EI] = nullptr;
+
+  EXPECT_DEATH(
+      { (void)writeLUSummary(Summary, "output.json"); },
+      "JSONFormat - null EntitySummary data for "
+      "'SummaryName\\(NullEntitySummaryForJSONFormatTest\\)'");
+}
+
+TEST_F(JSONFormatLUSummaryTest, WriteEntitySummaryMismatchedSummaryName) {
+  LUSummary Summary(NestedBuildNamespace(std::vector<BuildNamespace>{}));
+
+  NestedBuildNamespace Namespace =
+      NestedBuildNamespace::makeCompilationUnit("test.cpp");
+  EntityId EI = getIdTable(Summary).getId(
+      EntityName{"c:@F at foo", "", std::move(Namespace)});
+
+  SummaryName SN("MismatchedEntitySummaryForJSONFormatSummaryTest");
+  getData(Summary)[SN][EI] =
+      std::make_unique<MismatchedEntitySummaryForJSONFormatTest>();
+
+  EXPECT_DEATH(
+      { (void)writeLUSummary(Summary, "output.json"); },
+      "JSONFormat - EntitySummary data for "
+      "'SummaryName\\(MismatchedEntitySummaryForJSONFormatSummaryTest\\)' "
+      "reports "
+      "mismatched "
+      "'SummaryName\\(MismatchedEntitySummaryForJSONFormatTest_WrongName\\)'");
+}
+
+TEST_P(LUSummaryTest, EntityDataElementNotObject) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": ["invalid"]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary 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_P(LUSummaryTest, DuplicateEntityIdInDataMap) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            },
+            {
+              "kind": "LinkUnit",
+              "name": "test.exe"
+            }
+          ]
+        }
+      }
+    ],
+    "linkage_table": [
+      {
+        "id": 0,
+        "linkage": {
+          "type": "None"
+        }
+      }
+    ],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": { "pairs": [] }
+          },
+          {
+            "entity_id": 0,
+            "entity_summary": { "pairs": [] }
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary 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)'"))));
+}
+
+// ============================================================================
+// JSONFormat::summaryDataMapFromJSON() Error Tests
+// ============================================================================
+
+TEST_P(LUSummaryTest, DataEntryMissingSummaryName) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": [
+      {
+        "summary_data": []
+      }
+    ]
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary 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_P(LUSummaryTest, DataEntryMissingData) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest"
+      }
+    ]
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary 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"))));
+}
+
+// ============================================================================
+// JSONFormat::summaryDataMapFromJSON() Error Tests
+// ============================================================================
+
+TEST_P(LUSummaryTest, DataNotArray) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": {}
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary from file"),
+          HasSubstr("failed to read SummaryData entries from field 'data'"),
+          HasSubstr("expected JSON array"))));
+}
+
+TEST_P(LUSummaryTest, DataElementNotObject) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": ["invalid"]
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary from file"),
+                HasSubstr("reading SummaryData entries from field 'data'"),
+                HasSubstr("failed to read SummaryData entry from index '0'"),
+                HasSubstr("expected JSON object"))));
+}
+
+TEST_P(LUSummaryTest, DuplicateSummaryName) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": []
+      },
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": []
+      }
+    ]
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("failed to insert SummaryData entry at index '1'"),
+          HasSubstr("encountered duplicate "
+                    "'SummaryName(PairsEntitySummaryForJSONFormatTest)'"))));
+}
+
+// ============================================================================
+// JSONFormat::readLUSummary() / readLUSummaryEncoding() Missing Field Tests
+// ============================================================================
+
+TEST_P(LUSummaryTest, MissingLUNamespace) {
+  auto Result = readFromString(R"({
+    "id_table": [],
+    "linkage_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary from file"),
+          HasSubstr(
+              "failed to read NestedBuildNamespace from field 'lu_namespace'"),
+          HasSubstr("expected JSON array"))));
+}
+
+TEST_P(LUSummaryTest, MissingIDTable) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "linkage_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("reading LUSummary from file"),
+                HasSubstr("failed to read IdTable from field 'id_table'"),
+                HasSubstr("expected JSON array"))));
+}
+
+TEST_P(LUSummaryTest, MissingLinkageTable) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary from file"),
+          HasSubstr("failed to read LinkageTable from field 'linkage_table'"),
+          HasSubstr("expected JSON array"))));
+}
+
+TEST_P(LUSummaryTest, MissingData) {
+  auto Result = readFromString(R"({
+    "lu_namespace": [
+      {
+        "kind": "LinkUnit",
+        "name": "test.exe"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": []
+  })");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("reading LUSummary from file"),
+          HasSubstr("failed to read SummaryData entries from field 'data'"),
+          HasSubstr("expected JSON array"))));
+}
+
+// ============================================================================
+// JSONFormat::writeJSON() Error Tests
+// ============================================================================
+
+TEST_P(LUSummaryTest, WriteFileAlreadyExists) {
+  PathString FileName("existing.json");
+
+  auto ExpectedFilePath = writeJSON("{}", FileName);
+  ASSERT_THAT_EXPECTED(ExpectedFilePath, Succeeded());
+
+  auto Result = writeEmpty(FileName);
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(HasSubstr("writing LUSummary to file"),
+                              HasSubstr("failed to write file"),
+                              HasSubstr("file already exists"))));
+}
+
+TEST_P(LUSummaryTest, WriteParentDirectoryNotFound) {
+  PathString FilePath = makePath("nonexistent-dir", "test.json");
+
+  auto Result = GetParam().WriteEmpty(FilePath);
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(HasSubstr("writing LUSummary to file"),
+                              HasSubstr("failed to write file"),
+                              HasSubstr("parent directory does not exist"))));
+}
+
+TEST_P(LUSummaryTest, WriteNotJsonExtension) {
+  auto Result = writeEmpty("test.txt");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("writing LUSummary to file"),
+                HasSubstr("failed to write file"),
+                HasSubstr("file does not end with '.json' extension"))));
+}
+
+TEST_P(LUSummaryTest, WriteStreamOpenFailure) {
+  if (!permissionsAreEnforced()) {
+    GTEST_SKIP() << "File permission checks are not enforced in this "
+                    "environment";
+  }
+
+  const PathString DirName("write-protected-dir");
+
+  auto ExpectedDirPath = makeDirectory(DirName);
+  ASSERT_THAT_EXPECTED(ExpectedDirPath, Succeeded());
+
+  auto PermError = setPermission(DirName, sys::fs::perms::owner_read |
+                                              sys::fs::perms::owner_exe);
+  ASSERT_THAT_ERROR(std::move(PermError), Succeeded());
+
+  PathString FilePath = makePath(DirName, "test.json");
+
+  auto Result = GetParam().WriteEmpty(FilePath);
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(HasSubstr("writing LUSummary to file"),
+                              HasSubstr("failed to write file"))));
+
+  // Restore permissions for cleanup
+  auto RestoreError = setPermission(DirName, sys::fs::perms::all_all);
+  EXPECT_THAT_ERROR(std::move(RestoreError), Succeeded());
+}
+
+// ============================================================================
+// JSONFormat::writeLUSummary() Error Tests (LUSummary-only)
+// ============================================================================
+
+TEST_F(JSONFormatLUSummaryTest, WriteEntitySummaryNoFormatInfo) {
+  LUSummary Summary(NestedBuildNamespace(std::vector<BuildNamespace>{}));
+
+  NestedBuildNamespace Namespace =
+      NestedBuildNamespace::makeCompilationUnit("test.cpp");
+  EntityId EI = getIdTable(Summary).getId(
+      EntityName{"c:@F at foo", "", std::move(Namespace)});
+
+  SummaryName UnknownSN("UnregisteredEntitySummaryForJSONFormatTest");
+  getData(Summary)[UnknownSN][EI] =
+      std::make_unique<UnregisteredEntitySummaryForJSONFormatTest>();
+
+  auto Result = writeLUSummary(Summary, "output.json");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(
+          HasSubstr("writing LUSummary to file"),
+          HasSubstr("writing SummaryData entry to index '0'"),
+          HasSubstr("writing EntitySummary entries to field "
+                    "'summary_data'"),
+          HasSubstr("writing EntitySummary entry to index '0'"),
+          HasSubstr("writing EntitySummary to field 'entity_summary'"),
+          HasSubstr("failed to serialize EntitySummary"),
+          HasSubstr(
+              "no FormatInfo registered for "
+              "'SummaryName(UnregisteredEntitySummaryForJSONFormatTest)'"))));
+}
+
+// ============================================================================
+// Round-Trip Tests - Serialization Verification
+// ============================================================================
+
+TEST_P(LUSummaryTest, RoundTripEmptyNamespace) {
+  readWriteCompare(R"({
+    "lu_namespace": [
+      {
+        "kind": "CompilationUnit",
+        "name": "test.cpp"
+      }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": []
+  })");
+}
+
+TEST_P(LUSummaryTest, RoundTripSingleNamespaceElement) {
+  readWriteCompare(R"({
+    "lu_namespace": [
+      { "kind": "CompilationUnit", "name": "test.cpp" }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": []
+  })");
+}
+
+TEST_P(LUSummaryTest, RoundTripMultipleNamespaceElements) {
+  readWriteCompare(R"({
+    "lu_namespace": [
+      { "kind": "CompilationUnit", "name": "a.cpp" },
+      { "kind": "CompilationUnit", "name": "b.cpp" },
+      { "kind": "LinkUnit", "name": "libtest.so" }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": []
+  })");
+}
+
+TEST_P(LUSummaryTest, RoundTripWithTwoSummaryTypes) {
+  readWriteCompare(R"({
+    "lu_namespace": [
+      {
+        "kind": "CompilationUnit",
+        "name": "test.cpp"
+      }
+    ],
+    "id_table": [
+      {
+        "id": 3,
+        "name": {
+          "usr": "c:@F at qux",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      },
+      {
+        "id": 1,
+        "name": {
+          "usr": "c:@F at bar",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      },
+      {
+        "id": 4,
+        "name": {
+          "usr": "c:@F at quux",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      },
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      },
+      {
+        "id": 2,
+        "name": {
+          "usr": "c:@F at baz",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      }
+    ],
+    "linkage_table": [
+      {
+        "id": 3,
+        "linkage": { "type": "Internal" }
+      },
+      {
+        "id": 1,
+        "linkage": { "type": "None" }
+      },
+      {
+        "id": 4,
+        "linkage": { "type": "External" }
+      },
+      {
+        "id": 0,
+        "linkage": { "type": "None" }
+      },
+      {
+        "id": 2,
+        "linkage": { "type": "Internal" }
+      }
+    ],
+    "data": [
+      {
+        "summary_name": "TagsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 4,
+            "entity_summary": { "tags": ["exported", "hot"] }
+          },
+          {
+            "entity_id": 1,
+            "entity_summary": { "tags": ["internal-only"] }
+          },
+          {
+            "entity_id": 3,
+            "entity_summary": { "tags": ["internal-only"] }
+          },
+          {
+            "entity_id": 0,
+            "entity_summary": { "tags": ["entry-point"] }
+          },
+          {
+            "entity_id": 2,
+            "entity_summary": { "tags": [] }
+          }
+        ]
+      },
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 1,
+            "entity_summary": {
+              "pairs": [
+                { "first": 1, "second": 3 }
+              ]
+            }
+          },
+          {
+            "entity_id": 4,
+            "entity_summary": {
+              "pairs": [
+                { "first": 4, "second": 0 },
+                { "first": 4, "second": 2 }
+              ]
+            }
+          },
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": []
+            }
+          },
+          {
+            "entity_id": 3,
+            "entity_summary": {
+              "pairs": [
+                { "first": 3, "second": 1 }
+              ]
+            }
+          },
+          {
+            "entity_id": 2,
+            "entity_summary": {
+              "pairs": [
+                { "first": 2, "second": 4 },
+                { "first": 2, "second": 3 }
+              ]
+            }
+          }
+        ]
+      }
+    ]
+  })");
+}
+
+TEST_P(LUSummaryTest, RoundTripWithEmptyDataEntry) {
+  readWriteCompare(R"({
+    "lu_namespace": [
+      { "kind": "CompilationUnit", "name": "test.cpp" }
+    ],
+    "id_table": [],
+    "linkage_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": []
+      }
+    ]
+  })");
+}
+
+TEST_P(LUSummaryTest, RoundTripLinkageTableWithNoneLinkage) {
+  readWriteCompare(R"({
+    "lu_namespace": [
+      { "kind": "CompilationUnit", "name": "test.cpp" }
+    ],
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      }
+    ],
+    "linkage_table": [
+      {
+        "id": 0,
+        "linkage": { "type": "None" }
+      }
+    ],
+    "data": []
+  })");
+}
+
+TEST_P(LUSummaryTest, RoundTripLinkageTableWithInternalLinkage) {
+  readWriteCompare(R"({
+    "lu_namespace": [
+      { "kind": "CompilationUnit", "name": "test.cpp" }
+    ],
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at bar",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      }
+    ],
+    "linkage_table": [
+      {
+        "id": 0,
+        "linkage": { "type": "Internal" }
+      }
+    ],
+    "data": []
+  })");
+}
+
+TEST_P(LUSummaryTest, RoundTripLinkageTableWithExternalLinkage) {
+  readWriteCompare(R"({
+    "lu_namespace": [
+      { "kind": "CompilationUnit", "name": "test.cpp" }
+    ],
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at baz",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      }
+    ],
+    "linkage_table": [
+      {
+        "id": 0,
+        "linkage": { "type": "External" }
+      }
+    ],
+    "data": []
+  })");
+}
+
+TEST_P(LUSummaryTest, RoundTripLinkageTableWithMultipleEntries) {
+  readWriteCompare(R"({
+    "lu_namespace": [
+      { "kind": "CompilationUnit", "name": "test.cpp" }
+    ],
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      },
+      {
+        "id": 1,
+        "name": {
+          "usr": "c:@F at bar",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      },
+      {
+        "id": 2,
+        "name": {
+          "usr": "c:@F at baz",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "CompilationUnit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      }
+    ],
+    "linkage_table": [
+      {
+        "id": 0,
+        "linkage": { "type": "None" }
+      },
+      {
+        "id": 1,
+        "linkage": { "type": "Internal" }
+      },
+      {
+        "id": 2,
+        "linkage": { "type": "External" }
+      }
+    ],
+    "data": []
+  })");
+}
+
+} // anonymous namespace
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp
index c6098ed547529..a6c335e33ca62 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp
@@ -7,7 +7,7 @@
 //===----------------------------------------------------------------------===//
 //
 // Unit tests for SSAF JSON serialization format reading and writing of
-// TUSummary.
+// TUSummary and TUSummaryEncoding.
 //
 //===----------------------------------------------------------------------===//
 
@@ -16,535 +16,73 @@
 #include "clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h"
 #include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
 #include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
-#include "llvm/Support/Registry.h"
 #include "llvm/Testing/Support/Error.h"
 #include "gmock/gmock.h"
 
 #include <memory>
-#include <vector>
 
 using namespace clang::ssaf;
 using namespace llvm;
 using ::testing::AllOf;
 using ::testing::HasSubstr;
-namespace {
-
-// ============================================================================
-// First Test Analysis - Simple analysis for testing JSON serialization.
-// ============================================================================
-
-struct PairsEntitySummaryForJSONFormatTest final : EntitySummary {
-
-  SummaryName getSummaryName() const override {
-    return SummaryName("PairsEntitySummaryForJSONFormatTest");
-  }
-
-  std::vector<std::pair<EntityId, EntityId>> Pairs;
-};
-
-static json::Object serializePairsEntitySummaryForJSONFormatTest(
-    const EntitySummary &Summary,
-    const JSONFormat::EntityIdConverter &Converter) {
-  const auto &TA =
-      static_cast<const PairsEntitySummaryForJSONFormatTest &>(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>>
-deserializePairsEntitySummaryForJSONFormatTest(
-    const json::Object &Obj, EntityIdTable &IdTable,
-    const JSONFormat::EntityIdConverter &Converter) {
-  auto Result = std::make_unique<PairsEntitySummaryForJSONFormatTest>();
-  const json::Array *PairsArray = Obj.getArray("pairs");
-  if (!PairsArray)
-    return createStringError(inconvertibleErrorCode(),
-                             "missing or invalid field 'pairs'");
-  for (const auto &[Index, Value] : llvm::enumerate(*PairsArray)) {
-    const json::Object *Pair = Value.getAsObject();
-    if (!Pair)
-      return createStringError(
-          inconvertibleErrorCode(),
-          "pairs element at index %zu is not a JSON object", Index);
-    auto FirstOpt = Pair->getInteger("first");
-    if (!FirstOpt)
-      return createStringError(
-          inconvertibleErrorCode(),
-          "missing or invalid 'first' field at index '%zu'", Index);
-    auto SecondOpt = Pair->getInteger("second");
-    if (!SecondOpt)
-      return createStringError(
-          inconvertibleErrorCode(),
-          "missing or invalid 'second' field at index '%zu'", Index);
-    Result->Pairs.emplace_back(Converter.fromJSON(*FirstOpt),
-                               Converter.fromJSON(*SecondOpt));
-  }
-  return std::move(Result);
-}
-
-struct PairsEntitySummaryForJSONFormatTestFormatInfo final
-    : JSONFormat::FormatInfo {
-  PairsEntitySummaryForJSONFormatTestFormatInfo()
-      : JSONFormat::FormatInfo(
-            SummaryName("PairsEntitySummaryForJSONFormatTest"),
-            serializePairsEntitySummaryForJSONFormatTest,
-            deserializePairsEntitySummaryForJSONFormatTest) {}
-};
-
-static llvm::Registry<JSONFormat::FormatInfo>::Add<
-    PairsEntitySummaryForJSONFormatTestFormatInfo>
-    RegisterPairsEntitySummaryForJSONFormatTest(
-        "PairsEntitySummaryForJSONFormatTest",
-        "Format info for PairsArrayEntitySummary");
-
-// ============================================================================
-// Second Test Analysis - Simple analysis for multi-summary round-trip tests.
-// ============================================================================
-
-struct TagsEntitySummaryForJSONFormatTest final : EntitySummary {
-  SummaryName getSummaryName() const override {
-    return SummaryName("TagsEntitySummaryForJSONFormatTest");
-  }
-
-  std::vector<std::string> Tags;
-};
-
-static json::Object serializeTagsEntitySummaryForJSONFormatTest(
-    const EntitySummary &Summary, const JSONFormat::EntityIdConverter &) {
-  const auto &TA =
-      static_cast<const TagsEntitySummaryForJSONFormatTest &>(Summary);
-  json::Array TagsArray;
-  for (const auto &Tag : TA.Tags) {
-    TagsArray.push_back(Tag);
-  }
-  return json::Object{{"tags", std::move(TagsArray)}};
-}
-
-static Expected<std::unique_ptr<EntitySummary>>
-deserializeTagsEntitySummaryForJSONFormatTest(
-    const json::Object &Obj, EntityIdTable &,
-    const JSONFormat::EntityIdConverter &) {
-  auto Result = std::make_unique<TagsEntitySummaryForJSONFormatTest>();
-  const json::Array *TagsArray = Obj.getArray("tags");
-  if (!TagsArray) {
-    return createStringError(inconvertibleErrorCode(),
-                             "missing or invalid field 'tags'");
-  }
-  for (const auto &[Index, Value] : llvm::enumerate(*TagsArray)) {
-    auto Tag = Value.getAsString();
-    if (!Tag) {
-      return createStringError(inconvertibleErrorCode(),
-                               "tags element at index %zu is not a string",
-                               Index);
-    }
-    Result->Tags.push_back(Tag->str());
-  }
-  return std::move(Result);
-}
-
-struct TagsEntitySummaryForJSONFormatTestFormatInfo final
-    : JSONFormat::FormatInfo {
-  TagsEntitySummaryForJSONFormatTestFormatInfo()
-      : JSONFormat::FormatInfo(
-            SummaryName("TagsEntitySummaryForJSONFormatTest"),
-            serializeTagsEntitySummaryForJSONFormatTest,
-            deserializeTagsEntitySummaryForJSONFormatTest) {}
-};
-
-static llvm::Registry<JSONFormat::FormatInfo>::Add<
-    TagsEntitySummaryForJSONFormatTestFormatInfo>
-    RegisterTagsEntitySummaryForJSONFormatTest(
-        "TagsEntitySummaryForJSONFormatTest",
-        "Format info for TagsEntitySummary");
-
-// ============================================================================
-// NullEntitySummaryForJSONFormatTest - For null data checks
-// ============================================================================
-
-struct NullEntitySummaryForJSONFormatTest final : EntitySummary {
-  SummaryName getSummaryName() const override {
-    return SummaryName("NullEntitySummaryForJSONFormatTest");
-  }
-};
 
-struct NullEntitySummaryForJSONFormatTestFormatInfo final
-    : JSONFormat::FormatInfo {
-  NullEntitySummaryForJSONFormatTestFormatInfo()
-      : JSONFormat::FormatInfo(
-            SummaryName("NullEntitySummaryForJSONFormatTest"),
-            [](const EntitySummary &, const JSONFormat::EntityIdConverter &)
-                -> json::Object { return json::Object{}; },
-            [](const json::Object &, EntityIdTable &,
-               const JSONFormat::EntityIdConverter &)
-                -> llvm::Expected<std::unique_ptr<EntitySummary>> {
-              return nullptr;
-            }) {}
-};
-
-static llvm::Registry<JSONFormat::FormatInfo>::Add<
-    NullEntitySummaryForJSONFormatTestFormatInfo>
-    RegisterNullEntitySummaryForJSONFormatTest(
-        "NullEntitySummaryForJSONFormatTest",
-        "Format info for NullEntitySummary");
-
-// ============================================================================
-// UnregisteredEntitySummaryForJSONFormatTest - For missing FormatInfo checks
-// ============================================================================
-
-struct UnregisteredEntitySummaryForJSONFormatTest final : EntitySummary {
-  SummaryName getSummaryName() const override {
-    return SummaryName("UnregisteredEntitySummaryForJSONFormatTest");
-  }
-};
-
-// ============================================================================
-// MismatchedEntitySummaryForJSONFormatTest - For mismatched SummaryName checks
-// ============================================================================
-
-struct MismatchedEntitySummaryForJSONFormatTest final : EntitySummary {
-  SummaryName getSummaryName() const override {
-    return SummaryName("MismatchedEntitySummaryForJSONFormatTest_WrongName");
-  }
-};
-
-struct MismatchedEntitySummaryForJSONFormatTestFormatInfo final
-    : JSONFormat::FormatInfo {
-  MismatchedEntitySummaryForJSONFormatTestFormatInfo()
-      : JSONFormat::FormatInfo(
-            SummaryName("MismatchedEntitySummaryForJSONFormatTest"),
-            [](const EntitySummary &, const JSONFormat::EntityIdConverter &)
-                -> json::Object { return json::Object{}; },
-            [](const json::Object &, EntityIdTable &,
-               const JSONFormat::EntityIdConverter &)
-                -> llvm::Expected<std::unique_ptr<EntitySummary>> {
-              return std::make_unique<
-                  MismatchedEntitySummaryForJSONFormatTest>();
-            }) {}
-};
-
-static llvm::Registry<JSONFormat::FormatInfo>::Add<
-    MismatchedEntitySummaryForJSONFormatTestFormatInfo>
-    RegisterMismatchedEntitySummaryForJSONFormatTest(
-        "MismatchedEntitySummaryForJSONFormatTest",
-        "Format info for MismatchedEntitySummary");
-
-// ============================================================================
-// TUSummaryOps - Parameterization support for TUSummary/TUSummaryEncoding tests
-// ============================================================================
-
-struct TUSummaryOps {
-  std::string Name;
-  std::function<llvm::Error(llvm::StringRef FilePath)> ReadFromFile;
-  std::function<llvm::Error(llvm::StringRef FilePath)> WriteEmpty;
-  std::function<llvm::Error(llvm::StringRef InputFilePath,
-                            llvm::StringRef OutputFilePath)>
-      ReadWriteRoundTrip;
-};
-
-static TUSummaryOps makeTUSummaryOps() {
-  return TUSummaryOps{
-      "Resolved",
-      [](llvm::StringRef FilePath) -> llvm::Error {
-        auto Result = JSONFormat().readTUSummary(FilePath);
-        return Result ? llvm::Error::success() : Result.takeError();
-      },
-      [](llvm::StringRef FilePath) -> llvm::Error {
-        TUSummary S(
-            BuildNamespace(BuildNamespaceKind::CompilationUnit, "test.cpp"));
-        return JSONFormat().writeTUSummary(S, FilePath);
-      },
-      [](llvm::StringRef InputFilePath,
-         llvm::StringRef OutputFilePath) -> llvm::Error {
-        auto ExpectedS = JSONFormat().readTUSummary(InputFilePath);
-        if (!ExpectedS)
-          return ExpectedS.takeError();
-        return JSONFormat().writeTUSummary(*ExpectedS, OutputFilePath);
-      }};
-}
-
-static TUSummaryOps makeTUSummaryEncodingOps() {
-  return TUSummaryOps{
-      "Encoding",
-      [](llvm::StringRef FilePath) -> llvm::Error {
-        auto Result = JSONFormat().readTUSummaryEncoding(FilePath);
-        return Result ? llvm::Error::success() : Result.takeError();
-      },
-      [](llvm::StringRef FilePath) -> llvm::Error {
-        TUSummaryEncoding E(
-            BuildNamespace(BuildNamespaceKind::CompilationUnit, "test.cpp"));
-        return JSONFormat().writeTUSummaryEncoding(E, FilePath);
-      },
-      [](llvm::StringRef InputFilePath,
-         llvm::StringRef OutputFilePath) -> llvm::Error {
-        auto ExpectedE = JSONFormat().readTUSummaryEncoding(InputFilePath);
-        if (!ExpectedE)
-          return ExpectedE.takeError();
-        return JSONFormat().writeTUSummaryEncoding(*ExpectedE, OutputFilePath);
-      }};
-}
+namespace {
 
 // ============================================================================
-// TUSummaryTest Test Fixture
+// TUSummaryOps - Parameterization for TUSummary/TUSummaryEncoding tests
 // ============================================================================
 
-class TUSummaryTest : public JSONFormatTest,
-                      public ::testing::WithParamInterface<TUSummaryOps> {
-protected:
-  llvm::Error readFromString(StringRef JSON,
-                             StringRef FileName = "test.json") const {
-    auto ExpectedFilePath = writeJSON(JSON, FileName);
-    if (!ExpectedFilePath)
-      return ExpectedFilePath.takeError();
-    return GetParam().ReadFromFile(*ExpectedFilePath);
-  }
-
-  llvm::Error readFromFile(StringRef FileName) const {
-    return GetParam().ReadFromFile(makePath(FileName));
-  }
-
-  llvm::Error writeEmpty(StringRef FileName) const {
-    return GetParam().WriteEmpty(makePath(FileName));
-  }
-
-  llvm::Error readWriteRoundTrip(StringRef InputFileName,
-                                 StringRef OutputFileName) const {
-    return GetParam().ReadWriteRoundTrip(makePath(InputFileName),
-                                         makePath(OutputFileName));
-  }
-
-  void readWriteCompare(StringRef JSON) const;
-};
-
-INSTANTIATE_TEST_SUITE_P(
-    JSONFormat, TUSummaryTest,
-    ::testing::Values(makeTUSummaryOps(), makeTUSummaryEncodingOps()),
-    [](const ::testing::TestParamInfo<TUSummaryOps> &Info) {
-      return Info.param.Name;
-    });
+SummaryOps TUSummaryOps{
+    "Resolved", "TUSummary",
+    [](llvm::StringRef FilePath) -> llvm::Error {
+      auto Result = JSONFormat().readTUSummary(FilePath);
+      return Result ? llvm::Error::success() : Result.takeError();
+    },
+    [](llvm::StringRef FilePath) -> llvm::Error {
+      BuildNamespace BN(BuildNamespaceKind::CompilationUnit, "test.cpp");
+      TUSummary S(std::move(BN));
+      return JSONFormat().writeTUSummary(S, FilePath);
+    },
+    [](llvm::StringRef InputFilePath,
+       llvm::StringRef OutputFilePath) -> llvm::Error {
+      auto ExpectedS = JSONFormat().readTUSummary(InputFilePath);
+      if (!ExpectedS) {
+        return ExpectedS.takeError();
+      }
+      return JSONFormat().writeTUSummary(*ExpectedS, OutputFilePath);
+    }};
+
+SummaryOps TUSummaryEncodingOps{
+    "Encoding", "TUSummary",
+    [](llvm::StringRef FilePath) -> llvm::Error {
+      auto Result = JSONFormat().readTUSummaryEncoding(FilePath);
+      return Result ? llvm::Error::success() : Result.takeError();
+    },
+    [](llvm::StringRef FilePath) -> llvm::Error {
+      BuildNamespace BN(BuildNamespaceKind::CompilationUnit, "test.cpp");
+      TUSummaryEncoding E(std::move(BN));
+      return JSONFormat().writeTUSummaryEncoding(E, FilePath);
+    },
+    [](llvm::StringRef InputFilePath,
+       llvm::StringRef OutputFilePath) -> llvm::Error {
+      auto ExpectedE = JSONFormat().readTUSummaryEncoding(InputFilePath);
+      if (!ExpectedE) {
+        return ExpectedE.takeError();
+      }
+      return JSONFormat().writeTUSummaryEncoding(*ExpectedE, OutputFilePath);
+    }};
 
 // ============================================================================
-// TUSummary JSON Normalization Helpers
+// LUSummaryTest Test Fixture
 // ============================================================================
 
-static llvm::Error normalizeIDTable(json::Array &IDTable) {
-  for (const auto &[Index, Entry] : llvm::enumerate(IDTable)) {
-    const auto *EntryObj = Entry.getAsObject();
-    if (!EntryObj) {
-      return createStringError(
-          inconvertibleErrorCode(),
-          "Cannot normalize TUSummary JSON: id_table entry at index %zu "
-          "is not an object",
-          Index);
-    }
-
-    const auto *IDValue = EntryObj->get("id");
-    if (!IDValue) {
-      return createStringError(
-          inconvertibleErrorCode(),
-          "Cannot normalize TUSummary JSON: id_table entry at index %zu "
-          "does not contain an 'id' field",
-          Index);
-    }
-
-    if (!IDValue->getAsUINT64()) {
-      return createStringError(
-          inconvertibleErrorCode(),
-          "Cannot normalize TUSummary JSON: id_table entry at index %zu "
-          "does not contain a valid 'id' uint64_t field",
-          Index);
-    }
-  }
-
-  // Safe to dereference: all entries were validated above.
-  llvm::sort(IDTable, [](const json::Value &A, const json::Value &B) {
-    return *A.getAsObject()->get("id")->getAsUINT64() <
-           *B.getAsObject()->get("id")->getAsUINT64();
-  });
-
-  return llvm::Error::success();
-}
-
-static llvm::Error normalizeLinkageTable(json::Array &LinkageTable) {
-  for (const auto &[Index, Entry] : llvm::enumerate(LinkageTable)) {
-    const auto *EntryObj = Entry.getAsObject();
-    if (!EntryObj) {
-      return createStringError(
-          inconvertibleErrorCode(),
-          "Cannot normalize TUSummary JSON: linkage_table entry at index "
-          "%zu is not an object",
-          Index);
-    }
-
-    const auto *IDValue = EntryObj->get("id");
-    if (!IDValue) {
-      return createStringError(
-          inconvertibleErrorCode(),
-          "Cannot normalize TUSummary JSON: linkage_table entry at index "
-          "%zu does not contain an 'id' field",
-          Index);
-    }
-
-    if (!IDValue->getAsUINT64()) {
-      return createStringError(
-          inconvertibleErrorCode(),
-          "Cannot normalize TUSummary JSON: linkage_table entry at index "
-          "%zu does not contain a valid 'id' uint64_t field",
-          Index);
-    }
-  }
-
-  // Safe to dereference: all entries were validated above.
-  llvm::sort(LinkageTable, [](const json::Value &A, const json::Value &B) {
-    return *A.getAsObject()->get("id")->getAsUINT64() <
-           *B.getAsObject()->get("id")->getAsUINT64();
-  });
-
-  return llvm::Error::success();
-}
-
-static llvm::Error normalizeSummaryData(json::Array &SummaryData,
-                                        size_t DataIndex) {
-  for (const auto &[SummaryIndex, SummaryEntry] :
-       llvm::enumerate(SummaryData)) {
-    const auto *SummaryEntryObj = SummaryEntry.getAsObject();
-    if (!SummaryEntryObj) {
-      return createStringError(
-          inconvertibleErrorCode(),
-          "Cannot normalize TUSummary JSON: data entry at index %zu, "
-          "summary_data entry at index %zu is not an object",
-          DataIndex, SummaryIndex);
-    }
-
-    const auto *EntityIDValue = SummaryEntryObj->get("entity_id");
-    if (!EntityIDValue) {
-      return createStringError(
-          inconvertibleErrorCode(),
-          "Cannot normalize TUSummary JSON: data entry at index %zu, "
-          "summary_data entry at index %zu does not contain an "
-          "'entity_id' field",
-          DataIndex, SummaryIndex);
-    }
-
-    if (!EntityIDValue->getAsUINT64()) {
-      return createStringError(
-          inconvertibleErrorCode(),
-          "Cannot normalize TUSummary JSON: data entry at index %zu, "
-          "summary_data entry at index %zu does not contain a valid "
-          "'entity_id' uint64_t field",
-          DataIndex, SummaryIndex);
-    }
-  }
-
-  // Safe to dereference: all entries were validated above.
-  llvm::sort(SummaryData, [](const json::Value &A, const json::Value &B) {
-    return *A.getAsObject()->get("entity_id")->getAsUINT64() <
-           *B.getAsObject()->get("entity_id")->getAsUINT64();
-  });
-
-  return llvm::Error::success();
-}
-
-static llvm::Error normalizeData(json::Array &Data) {
-  for (const auto &[DataIndex, DataEntry] : llvm::enumerate(Data)) {
-    auto *DataEntryObj = DataEntry.getAsObject();
-    if (!DataEntryObj) {
-      return createStringError(
-          inconvertibleErrorCode(),
-          "Cannot normalize TUSummary JSON: data entry at index %zu "
-          "is not an object",
-          DataIndex);
-    }
-
-    if (!DataEntryObj->getString("summary_name")) {
-      return createStringError(
-          inconvertibleErrorCode(),
-          "Cannot normalize TUSummary JSON: data entry at index %zu "
-          "does not contain a 'summary_name' string field",
-          DataIndex);
-    }
-
-    auto *SummaryData = DataEntryObj->getArray("summary_data");
-    if (!SummaryData) {
-      return createStringError(
-          inconvertibleErrorCode(),
-          "Cannot normalize TUSummary JSON: data entry at index %zu "
-          "does not contain a 'summary_data' array field",
-          DataIndex);
-    }
-
-    if (auto Err = normalizeSummaryData(*SummaryData, DataIndex)) {
-      return Err;
-    }
-  }
-
-  // Safe to dereference: all entries were validated above.
-  llvm::sort(Data, [](const json::Value &A, const json::Value &B) {
-    return *A.getAsObject()->getString("summary_name") <
-           *B.getAsObject()->getString("summary_name");
-  });
+class TUSummaryTest : public SummaryTest {};
 
-  return llvm::Error::success();
-}
-
-static Expected<json::Value> normalizeTUSummaryJSON(json::Value Val) {
-  auto *Obj = Val.getAsObject();
-  if (!Obj) {
-    return createStringError(
-        inconvertibleErrorCode(),
-        "Cannot normalize TUSummary JSON: expected an object");
-  }
-
-  auto *IDTable = Obj->getArray("id_table");
-  if (!IDTable) {
-    return createStringError(inconvertibleErrorCode(),
-                             "Cannot normalize TUSummary JSON: 'id_table' "
-                             "field is either missing or has the wrong type");
-  }
-  if (auto Err = normalizeIDTable(*IDTable)) {
-    return std::move(Err);
-  }
-
-  auto *LinkageTable = Obj->getArray("linkage_table");
-  if (!LinkageTable) {
-    return createStringError(inconvertibleErrorCode(),
-                             "Cannot normalize TUSummary JSON: 'linkage_table' "
-                             "field is either missing or has the wrong type");
-  }
-  if (auto Err = normalizeLinkageTable(*LinkageTable)) {
-    return std::move(Err);
-  }
-
-  auto *Data = Obj->getArray("data");
-  if (!Data) {
-    return createStringError(inconvertibleErrorCode(),
-                             "Cannot normalize TUSummary JSON: 'data' "
-                             "field is either missing or has the wrong type");
-  }
-  if (auto Err = normalizeData(*Data)) {
-    return std::move(Err);
-  }
-
-  return Val;
-}
-
-// Compare two TUSummary JSON values with normalization.
-static Expected<bool> compareTUSummaryJSON(json::Value A, json::Value B) {
-  auto ExpectedNormalizedA = normalizeTUSummaryJSON(std::move(A));
-  if (!ExpectedNormalizedA)
-    return ExpectedNormalizedA.takeError();
-
-  auto ExpectedNormalizedB = normalizeTUSummaryJSON(std::move(B));
-  if (!ExpectedNormalizedB)
-    return ExpectedNormalizedB.takeError();
-
-  return *ExpectedNormalizedA == *ExpectedNormalizedB;
-}
+INSTANTIATE_TEST_SUITE_P(JSONFormat, TUSummaryTest,
+                         ::testing::Values(TUSummaryOps, TUSummaryEncodingOps),
+                         [](const ::testing::TestParamInfo<SummaryOps> &Info) {
+                           return Info.param.Name;
+                         });
 
 // ============================================================================
 // JSONFormatTUSummaryTest Test Fixture
@@ -554,7 +92,6 @@ class JSONFormatTUSummaryTest : public JSONFormatTest {
 protected:
   llvm::Expected<TUSummary> readTUSummaryFromFile(StringRef FileName) const {
     PathString FilePath = makePath(FileName);
-
     return JSONFormat().readTUSummary(FilePath);
   }
 
@@ -562,8 +99,9 @@ class JSONFormatTUSummaryTest : public JSONFormatTest {
   readTUSummaryFromString(StringRef JSON,
                           StringRef FileName = "test.json") const {
     auto ExpectedFilePath = writeJSON(JSON, FileName);
-    if (!ExpectedFilePath)
+    if (!ExpectedFilePath) {
       return ExpectedFilePath.takeError();
+    }
 
     return readTUSummaryFromFile(FileName);
   }
@@ -571,90 +109,10 @@ class JSONFormatTUSummaryTest : public JSONFormatTest {
   llvm::Error writeTUSummary(const TUSummary &Summary,
                              StringRef FileName) const {
     PathString FilePath = makePath(FileName);
-
     return JSONFormat().writeTUSummary(Summary, FilePath);
   }
-
-  void readWriteCompareTUSummary(StringRef JSON) const {
-    const PathString InputFileName("input.json");
-    const PathString OutputFileName("output.json");
-
-    auto ExpectedInputFilePath = writeJSON(JSON, InputFileName);
-    ASSERT_THAT_EXPECTED(ExpectedInputFilePath, Succeeded());
-
-    auto ExpectedTUSummary = readTUSummaryFromFile(InputFileName);
-    ASSERT_THAT_EXPECTED(ExpectedTUSummary, Succeeded());
-
-    auto WriteError = writeTUSummary(*ExpectedTUSummary, OutputFileName);
-    ASSERT_THAT_ERROR(std::move(WriteError), Succeeded())
-        << "Failed to write to file: " << OutputFileName;
-
-    auto ExpectedInputJSON = readJSONFromFile(InputFileName);
-    ASSERT_THAT_EXPECTED(ExpectedInputJSON, Succeeded());
-    auto ExpectedOutputJSON = readJSONFromFile(OutputFileName);
-    ASSERT_THAT_EXPECTED(ExpectedOutputJSON, Succeeded());
-
-    auto ExpectedComparisonResult =
-        compareTUSummaryJSON(*ExpectedInputJSON, *ExpectedOutputJSON);
-    ASSERT_THAT_EXPECTED(ExpectedComparisonResult, Succeeded())
-        << "Failed to normalize JSON for comparison";
-
-    if (!*ExpectedComparisonResult) {
-      auto ExpectedNormalizedInput = normalizeTUSummaryJSON(*ExpectedInputJSON);
-      auto ExpectedNormalizedOutput =
-          normalizeTUSummaryJSON(*ExpectedOutputJSON);
-      FAIL() << "Serialization is broken: input JSON is different from output "
-                "json\n"
-             << "Input:  "
-             << (ExpectedNormalizedInput
-                     ? llvm::formatv("{0:2}", *ExpectedNormalizedInput).str()
-                     : "normalization failed")
-             << "\n"
-             << "Output: "
-             << (ExpectedNormalizedOutput
-                     ? llvm::formatv("{0:2}", *ExpectedNormalizedOutput).str()
-                     : "normalization failed");
-    }
-  }
 };
 
-void TUSummaryTest::readWriteCompare(StringRef JSON) const {
-  const PathString InputFileName("input.json");
-  const PathString OutputFileName("output.json");
-
-  auto ExpectedInputFilePath = writeJSON(JSON, InputFileName);
-  ASSERT_THAT_EXPECTED(ExpectedInputFilePath, Succeeded());
-
-  ASSERT_THAT_ERROR(readWriteRoundTrip(InputFileName, OutputFileName),
-                    Succeeded());
-
-  auto ExpectedInputJSON = readJSONFromFile(InputFileName);
-  ASSERT_THAT_EXPECTED(ExpectedInputJSON, Succeeded());
-  auto ExpectedOutputJSON = readJSONFromFile(OutputFileName);
-  ASSERT_THAT_EXPECTED(ExpectedOutputJSON, Succeeded());
-
-  auto ExpectedComparisonResult =
-      compareTUSummaryJSON(*ExpectedInputJSON, *ExpectedOutputJSON);
-  ASSERT_THAT_EXPECTED(ExpectedComparisonResult, Succeeded())
-      << "Failed to normalize JSON for comparison";
-
-  if (!*ExpectedComparisonResult) {
-    auto ExpectedNormalizedInput = normalizeTUSummaryJSON(*ExpectedInputJSON);
-    auto ExpectedNormalizedOutput = normalizeTUSummaryJSON(*ExpectedOutputJSON);
-    FAIL() << "Serialization is broken: input JSON is different from output "
-              "json\n"
-           << "Input:  "
-           << (ExpectedNormalizedInput
-                   ? llvm::formatv("{0:2}", *ExpectedNormalizedInput).str()
-                   : "normalization failed")
-           << "\n"
-           << "Output: "
-           << (ExpectedNormalizedOutput
-                   ? llvm::formatv("{0:2}", *ExpectedNormalizedOutput).str()
-                   : "normalization failed");
-  }
-}
-
 // ============================================================================
 // readJSON() Error Tests
 // ============================================================================

>From df6d50d1492d65e623fc1e200a0ccfd5773fb18b Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Sun, 1 Mar 2026 16:30:43 -0800
Subject: [PATCH 5/9] Formatting changes

---
 .../JSONFormat/JSONFormatImpl.cpp             | 28 +++++++++----------
 .../Serialization/JSONFormat/LUSummary.cpp    |  4 ++-
 .../JSONFormat/LUSummaryEncoding.cpp          |  4 ++-
 .../Serialization/JSONFormat/TUSummary.cpp    |  2 +-
 .../JSONFormat/TUSummaryEncoding.cpp          |  2 +-
 .../JSONFormatTest/LUSummaryTest.cpp          |  6 ++--
 .../JSONFormatTest/TUSummaryTest.cpp          |  6 ++--
 7 files changed, 29 insertions(+), 23 deletions(-)

diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
index 8717a7d73b936..aef02eb36c477 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
@@ -1,4 +1,4 @@
-//===- JSONFormatImpl.cpp -----------------------------------------------===//
+//===- JSONFormatImpl.cpp -------------------------------------------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -34,7 +34,7 @@ llvm::Expected<Value> readJSON(llvm::StringRef Path) {
         .build();
   }
 
-  if (!Path.ends_with_insensitive(JSONFormatFileExtension)) {
+  if (!Path.ends_with(JSONFormatFileExtension)) {
     return ErrorBuilder::create(std::errc::invalid_argument,
                                 ErrorMessages::FailedToReadFile, Path,
                                 llvm::formatv(ErrorMessages::FileIsNotJSON,
@@ -53,7 +53,7 @@ llvm::Expected<Value> readJSON(llvm::StringRef Path) {
   return llvm::json::parse(BufferOrError.get()->getBuffer());
 }
 
-llvm::Error writeJSON(Value &&Value, llvm::StringRef Path) {
+llvm::Error writeJSON(Value &&Val, llvm::StringRef Path) {
   if (llvm::sys::fs::exists(Path)) {
     return ErrorBuilder::create(std::errc::file_exists,
                                 ErrorMessages::FailedToWriteFile, Path,
@@ -69,7 +69,7 @@ llvm::Error writeJSON(Value &&Value, llvm::StringRef Path) {
         .build();
   }
 
-  if (!Path.ends_with_insensitive(JSONFormatFileExtension)) {
+  if (!Path.ends_with(JSONFormatFileExtension)) {
     return ErrorBuilder::create(std::errc::invalid_argument,
                                 ErrorMessages::FailedToWriteFile, Path,
                                 llvm::formatv(ErrorMessages::FileIsNotJSON,
@@ -86,7 +86,7 @@ llvm::Error writeJSON(Value &&Value, llvm::StringRef Path) {
         .build();
   }
 
-  OutStream << llvm::formatv("{0:2}\n", Value);
+  OutStream << llvm::formatv("{0:2}\n", Val);
   OutStream.flush();
 
   // This path handles post-write stream errors (e.g. ENOSPC after buffered
@@ -110,7 +110,8 @@ std::map<SummaryName, JSONFormat::FormatInfo> JSONFormat::initFormatInfos() {
   std::map<SummaryName, FormatInfo> FormatInfos;
   for (const auto &FormatInfoEntry : llvm::Registry<FormatInfo>::entries()) {
     std::unique_ptr<FormatInfo> Info = FormatInfoEntry.instantiate();
-    bool Inserted = FormatInfos.try_emplace(Info->ForSummary, *Info).second;
+    bool Inserted =
+        FormatInfos.try_emplace(Info->ForSummary, std::move(*Info)).second;
     if (!Inserted) {
       llvm::report_fatal_error(
           "FormatInfo is already registered for summary: " +
@@ -216,7 +217,6 @@ llvm::Expected<NestedBuildNamespace> JSONFormat::nestedBuildNamespaceFromJSON(
 
   for (const auto &[Index, BuildNamespaceValue] :
        llvm::enumerate(NestedBuildNamespaceArray)) {
-
     const Object *BuildNamespaceObject = BuildNamespaceValue.getAsObject();
     if (!BuildNamespaceObject) {
       return ErrorBuilder::create(std::errc::invalid_argument,
@@ -418,7 +418,6 @@ JSONFormat::entityIdTableFromJSON(const Array &EntityIdTableArray) const {
 
   for (const auto &[Index, EntityIdTableEntryValue] :
        llvm::enumerate(EntityIdTableArray)) {
-
     const Object *OptEntityIdTableEntryObject =
         EntityIdTableEntryValue.getAsObject();
     if (!OptEntityIdTableEntryObject) {
@@ -430,11 +429,12 @@ JSONFormat::entityIdTableFromJSON(const Array &EntityIdTableArray) const {
 
     auto ExpectedEntityIdTableEntry =
         entityIdTableEntryFromJSON(*OptEntityIdTableEntryObject);
-    if (!ExpectedEntityIdTableEntry)
+    if (!ExpectedEntityIdTableEntry) {
       return ErrorBuilder::wrap(ExpectedEntityIdTableEntry.takeError())
           .context(ErrorMessages::ReadingFromIndex, "EntityIdTable entry",
                    Index)
           .build();
+    }
 
     auto [EntityIt, EntityInserted] =
         Entities.emplace(std::move(*ExpectedEntityIdTableEntry));
@@ -746,7 +746,6 @@ JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
 
   for (const auto &[Index, EntityDataMapEntryValue] :
        llvm::enumerate(EntityDataArray)) {
-
     const Object *OptEntityDataMapEntryObject =
         EntityDataMapEntryValue.getAsObject();
     if (!OptEntityDataMapEntryObject) {
@@ -836,11 +835,12 @@ JSONFormat::summaryDataMapEntryFromJSON(const Object &SummaryDataMapEntryObject,
 
   auto ExpectedEntityDataMap =
       entityDataMapFromJSON(SN, *OptEntityDataArray, IdTable);
-  if (!ExpectedEntityDataMap)
+  if (!ExpectedEntityDataMap) {
     return ErrorBuilder::wrap(ExpectedEntityDataMap.takeError())
         .context(ErrorMessages::ReadingFromField, "EntitySummary entries",
                  "summary_data")
         .build();
+  }
 
   return std::make_pair(std::move(SN), std::move(*ExpectedEntityDataMap));
 }
@@ -878,7 +878,6 @@ JSONFormat::summaryDataMapFromJSON(const Array &SummaryDataArray,
 
   for (const auto &[Index, SummaryDataMapEntryValue] :
        llvm::enumerate(SummaryDataArray)) {
-
     const Object *OptSummaryDataMapEntryObject =
         SummaryDataMapEntryValue.getAsObject();
     if (!OptSummaryDataMapEntryObject) {
@@ -1002,7 +1001,6 @@ JSONFormat::encodingDataMapFromJSON(const Array &EntityDataArray) const {
 
   for (const auto &[Index, EntityDataMapEntryValue] :
        llvm::enumerate(EntityDataArray)) {
-
     const Object *OptEntityDataMapEntryObject =
         EntityDataMapEntryValue.getAsObject();
     if (!OptEntityDataMapEntryObject) {
@@ -1077,11 +1075,12 @@ JSONFormat::encodingSummaryDataMapEntryFromJSON(
   }
 
   auto ExpectedEncodingDataMap = encodingDataMapFromJSON(*OptEntityDataArray);
-  if (!ExpectedEncodingDataMap)
+  if (!ExpectedEncodingDataMap) {
     return ErrorBuilder::wrap(ExpectedEncodingDataMap.takeError())
         .context(ErrorMessages::ReadingFromField, "EntitySummary entries",
                  "summary_data")
         .build();
+  }
 
   return std::make_pair(std::move(SN), std::move(*ExpectedEncodingDataMap));
 }
@@ -1112,7 +1111,6 @@ JSONFormat::encodingSummaryDataMapFromJSON(
 
   for (const auto &[Index, SummaryDataMapEntryValue] :
        llvm::enumerate(SummaryDataArray)) {
-
     const Object *OptSummaryDataMapEntryObject =
         SummaryDataMapEntryValue.getAsObject();
     if (!OptSummaryDataMapEntryObject) {
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/LUSummary.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/LUSummary.cpp
index dc05ef5e4e07d..08d081089976b 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/LUSummary.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/LUSummary.cpp
@@ -1,4 +1,4 @@
-//===- LUSummary.cpp ----------------------------------------------------===//
+//===- LUSummary.cpp ------------------------------------------------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -10,6 +10,8 @@
 
 #include "clang/Analysis/Scalable/EntityLinker/LUSummary.h"
 
+#include <set>
+
 namespace clang::ssaf {
 
 //----------------------------------------------------------------------------
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/LUSummaryEncoding.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/LUSummaryEncoding.cpp
index c4912f286d81a..e99ea231e2c61 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/LUSummaryEncoding.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/LUSummaryEncoding.cpp
@@ -1,4 +1,4 @@
-//===- LUSummaryEncoding.cpp --------------------------------------------===//
+//===- LUSummaryEncoding.cpp ----------------------------------------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -10,6 +10,8 @@
 
 #include "clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h"
 
+#include <set>
+
 namespace clang::ssaf {
 
 //----------------------------------------------------------------------------
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummary.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummary.cpp
index bf22a744a3c15..034662a456a65 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummary.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummary.cpp
@@ -1,4 +1,4 @@
-//===- TUSummary.cpp ----------------------------------------------------===//
+//===- TUSummary.cpp ------------------------------------------------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummaryEncoding.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummaryEncoding.cpp
index 56713f375c6f2..f1087e8b7903e 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummaryEncoding.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummaryEncoding.cpp
@@ -1,4 +1,4 @@
-//===- TUSummaryEncoding.cpp --------------------------------------------===//
+//===- TUSummaryEncoding.cpp ----------------------------------------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp
index b70b947a403b7..be52faa27b81b 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp
@@ -163,12 +163,14 @@ TEST_P(LUSummaryTest, BrokenSymlink) {
   GTEST_SKIP() << "Symlink model differs on Windows";
 #endif
 
+  const PathString SymlinkFileName("broken_symlink.json");
+
   // Create a symlink pointing to a non-existent file
   auto ExpectedSymlinkPath =
       makeSymlink("nonexistent_target.json", "broken_symlink.json");
   ASSERT_THAT_EXPECTED(ExpectedSymlinkPath, Succeeded());
 
-  auto Result = readFromFile(*ExpectedSymlinkPath);
+  auto Result = readFromFile(SymlinkFileName);
 
   EXPECT_THAT_ERROR(std::move(Result),
                     FailedWithMessage(AllOf(HasSubstr("reading LUSummary from"),
@@ -1869,7 +1871,7 @@ TEST_P(LUSummaryTest, DataEntryMissingData) {
 }
 
 // ============================================================================
-// JSONFormat::summaryDataMapFromJSON() Error Tests
+// JSONFormat::summaryDataMapFromJSON() Error Tests (array-level)
 // ============================================================================
 
 TEST_P(LUSummaryTest, DataNotArray) {
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp
index a6c335e33ca62..8cc3d5b2ae85a 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp
@@ -160,12 +160,14 @@ TEST_P(TUSummaryTest, BrokenSymlink) {
   GTEST_SKIP() << "Symlink model differs on Windows";
 #endif
 
+  const PathString SymlinkFileName("broken_symlink.json");
+
   // Create a symlink pointing to a non-existent file
   auto ExpectedSymlinkPath =
-      makeSymlink("nonexistent_target.json", "broken_symlink.json");
+      makeSymlink("nonexistent_target.json", SymlinkFileName);
   ASSERT_THAT_EXPECTED(ExpectedSymlinkPath, Succeeded());
 
-  auto Result = readFromFile(*ExpectedSymlinkPath);
+  auto Result = readFromFile(SymlinkFileName);
 
   EXPECT_THAT_ERROR(std::move(Result),
                     FailedWithMessage(AllOf(HasSubstr("reading TUSummary from"),

>From 36046015cda409f1a1af940ee1cb181aee9c622f Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Sun, 1 Mar 2026 16:56:44 -0800
Subject: [PATCH 6/9] Fix

---
 .../Serialization/JSONFormat/TUSummary.cpp    |  2 ++
 .../JSONFormat/TUSummaryEncoding.cpp          |  2 ++
 .../JSONFormatTest/LUSummaryTest.cpp          | 31 +++++++++++--------
 3 files changed, 22 insertions(+), 13 deletions(-)

diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummary.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummary.cpp
index 034662a456a65..dfb6aa608046c 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummary.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummary.cpp
@@ -10,6 +10,8 @@
 
 #include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
 
+#include <set>
+
 namespace clang::ssaf {
 
 //----------------------------------------------------------------------------
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummaryEncoding.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummaryEncoding.cpp
index f1087e8b7903e..3d24ca8c087bb 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummaryEncoding.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/TUSummaryEncoding.cpp
@@ -10,6 +10,8 @@
 
 #include "clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h"
 
+#include <set>
+
 namespace clang::ssaf {
 
 //----------------------------------------------------------------------------
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp
index be52faa27b81b..d7eccda317aaa 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp
@@ -878,10 +878,6 @@ TEST_P(LUSummaryTest, EntityNameMissingSuffix) {
         "name": {
           "usr": "c:@F at foo",
           "namespace": [
-            {
-              "kind": "CompilationUnit",
-              "name": "test.cpp"
-            },
             {
               "kind": "LinkUnit",
               "name": "test.exe"
@@ -893,7 +889,7 @@ TEST_P(LUSummaryTest, EntityNameMissingSuffix) {
     "linkage_table": [
       {
         "id": 0,
-        "linkage": { "type": "None" }
+        "linkage": { "type": "External" }
       }
     ],
     "data": []
@@ -1682,7 +1678,9 @@ TEST_F(JSONFormatLUSummaryTest, ReadEntitySummaryMismatchedSummaryName) {
 // ============================================================================
 
 TEST_F(JSONFormatLUSummaryTest, WriteEntitySummaryMissingData) {
-  LUSummary Summary(NestedBuildNamespace(std::vector<BuildNamespace>{}));
+  NestedBuildNamespace NBN(
+      BuildNamespace(BuildNamespaceKind::LinkUnit, "test.exe"));
+  LUSummary Summary(std::move(NBN));
 
   NestedBuildNamespace Namespace =
       NestedBuildNamespace::makeCompilationUnit("test.cpp");
@@ -1699,26 +1697,32 @@ TEST_F(JSONFormatLUSummaryTest, WriteEntitySummaryMissingData) {
 }
 
 TEST_F(JSONFormatLUSummaryTest, WriteEntitySummaryMismatchedSummaryName) {
-  LUSummary Summary(NestedBuildNamespace(std::vector<BuildNamespace>{}));
+  NestedBuildNamespace NBN(
+      BuildNamespace(BuildNamespaceKind::LinkUnit, "test.exe"));
+  LUSummary Summary(std::move(NBN));
 
   NestedBuildNamespace Namespace =
       NestedBuildNamespace::makeCompilationUnit("test.cpp");
   EntityId EI = getIdTable(Summary).getId(
       EntityName{"c:@F at foo", "", std::move(Namespace)});
 
-  SummaryName SN("MismatchedEntitySummaryForJSONFormatSummaryTest");
+  SummaryName SN("MismatchedEntitySummaryForJSONFormatTest");
   getData(Summary)[SN][EI] =
       std::make_unique<MismatchedEntitySummaryForJSONFormatTest>();
 
   EXPECT_DEATH(
       { (void)writeLUSummary(Summary, "output.json"); },
       "JSONFormat - EntitySummary data for "
-      "'SummaryName\\(MismatchedEntitySummaryForJSONFormatSummaryTest\\)' "
+      "'SummaryName\\(MismatchedEntitySummaryForJSONFormatTest\\)' "
       "reports "
       "mismatched "
       "'SummaryName\\(MismatchedEntitySummaryForJSONFormatTest_WrongName\\)'");
 }
 
+// ============================================================================
+// JSONFormat::entityDataMapFromJSON() Error Tests
+// ============================================================================
+
 TEST_P(LUSummaryTest, EntityDataElementNotObject) {
   auto Result = readFromString(R"({
     "lu_namespace": [
@@ -1871,7 +1875,7 @@ TEST_P(LUSummaryTest, DataEntryMissingData) {
 }
 
 // ============================================================================
-// JSONFormat::summaryDataMapFromJSON() Error Tests (array-level)
+// JSONFormat::summaryDataMapFromJSON() / encodingSummaryDataMapFromJSON() Tests
 // ============================================================================
 
 TEST_P(LUSummaryTest, DataNotArray) {
@@ -1950,7 +1954,7 @@ TEST_P(LUSummaryTest, DuplicateSummaryName) {
 }
 
 // ============================================================================
-// JSONFormat::readLUSummary() / readLUSummaryEncoding() Missing Field Tests
+// JSONFormat::readLUSummary() / readLUSummaryEncoding() Error Tests
 // ============================================================================
 
 TEST_P(LUSummaryTest, MissingLUNamespace) {
@@ -1977,7 +1981,6 @@ TEST_P(LUSummaryTest, MissingIDTable) {
         "name": "test.exe"
       }
     ],
-    "linkage_table": [],
     "data": []
   })");
 
@@ -2105,7 +2108,9 @@ TEST_P(LUSummaryTest, WriteStreamOpenFailure) {
 // ============================================================================
 
 TEST_F(JSONFormatLUSummaryTest, WriteEntitySummaryNoFormatInfo) {
-  LUSummary Summary(NestedBuildNamespace(std::vector<BuildNamespace>{}));
+  NestedBuildNamespace NBN(
+      BuildNamespace(BuildNamespaceKind::LinkUnit, "test.exe"));
+  LUSummary Summary(std::move(NBN));
 
   NestedBuildNamespace Namespace =
       NestedBuildNamespace::makeCompilationUnit("test.cpp");

>From 03a2004f812bcf72a33e543c5e97a2ebbd17345c Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Mon, 2 Mar 2026 07:24:07 -0800
Subject: [PATCH 7/9] Balazs

---
 .../JSONFormat/JSONFormatImpl.cpp             |  4 +-
 .../JSONFormatTest/JSONFormatTest.cpp         | 54 +++++++++----------
 .../JSONFormatTest/JSONFormatTest.h           | 10 +++-
 .../JSONFormatTest/LUSummaryTest.cpp          | 13 +++--
 .../JSONFormatTest/TUSummaryTest.cpp          |  2 +-
 5 files changed, 44 insertions(+), 39 deletions(-)

diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
index aef02eb36c477..6a2ce6f21b4db 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
@@ -53,7 +53,7 @@ llvm::Expected<Value> readJSON(llvm::StringRef Path) {
   return llvm::json::parse(BufferOrError.get()->getBuffer());
 }
 
-llvm::Error writeJSON(Value &&Val, llvm::StringRef Path) {
+llvm::Error writeJSON(Value &&V, llvm::StringRef Path) {
   if (llvm::sys::fs::exists(Path)) {
     return ErrorBuilder::create(std::errc::file_exists,
                                 ErrorMessages::FailedToWriteFile, Path,
@@ -86,7 +86,7 @@ llvm::Error writeJSON(Value &&Val, llvm::StringRef Path) {
         .build();
   }
 
-  OutStream << llvm::formatv("{0:2}\n", Val);
+  OutStream << llvm::formatv("{0:2}\n", V);
   OutStream.flush();
 
   // This path handles post-write stream errors (e.g. ENOSPC after buffered
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.cpp
index b723647a75bfc..92966f22c5971 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.cpp
@@ -183,7 +183,7 @@ JSONFormatTest::writeJSON(llvm::StringRef JSON,
 namespace {
 
 llvm::Error normalizeIDTable(json::Array &IDTable,
-                             llvm::StringRef SummaryTypeName) {
+                             llvm::StringRef SummaryClassName) {
   for (const auto &[Index, Entry] : llvm::enumerate(IDTable)) {
     const auto *EntryObj = Entry.getAsObject();
     if (!EntryObj) {
@@ -191,7 +191,7 @@ llvm::Error normalizeIDTable(json::Array &IDTable,
           inconvertibleErrorCode(),
           "Cannot normalize %s JSON: id_table entry at index %zu "
           "is not an object",
-          SummaryTypeName.data(), Index);
+          SummaryClassName.data(), Index);
     }
 
     const auto *IDValue = EntryObj->get("id");
@@ -200,7 +200,7 @@ llvm::Error normalizeIDTable(json::Array &IDTable,
           inconvertibleErrorCode(),
           "Cannot normalize %s JSON: id_table entry at index %zu "
           "does not contain an 'id' field",
-          SummaryTypeName.data(), Index);
+          SummaryClassName.data(), Index);
     }
 
     if (!IDValue->getAsUINT64()) {
@@ -208,7 +208,7 @@ llvm::Error normalizeIDTable(json::Array &IDTable,
           inconvertibleErrorCode(),
           "Cannot normalize %s JSON: id_table entry at index %zu "
           "does not contain a valid 'id' uint64_t field",
-          SummaryTypeName.data(), Index);
+          SummaryClassName.data(), Index);
     }
   }
 
@@ -222,7 +222,7 @@ llvm::Error normalizeIDTable(json::Array &IDTable,
 }
 
 llvm::Error normalizeLinkageTable(json::Array &LinkageTable,
-                                  llvm::StringRef SummaryTypeName) {
+                                  llvm::StringRef SummaryClassName) {
   for (const auto &[Index, Entry] : llvm::enumerate(LinkageTable)) {
     const auto *EntryObj = Entry.getAsObject();
     if (!EntryObj) {
@@ -230,7 +230,7 @@ llvm::Error normalizeLinkageTable(json::Array &LinkageTable,
           inconvertibleErrorCode(),
           "Cannot normalize %s JSON: linkage_table entry at index "
           "%zu is not an object",
-          SummaryTypeName.data(), Index);
+          SummaryClassName.data(), Index);
     }
 
     const auto *IDValue = EntryObj->get("id");
@@ -239,7 +239,7 @@ llvm::Error normalizeLinkageTable(json::Array &LinkageTable,
           inconvertibleErrorCode(),
           "Cannot normalize %s JSON: linkage_table entry at index "
           "%zu does not contain an 'id' field",
-          SummaryTypeName.data(), Index);
+          SummaryClassName.data(), Index);
     }
 
     if (!IDValue->getAsUINT64()) {
@@ -247,7 +247,7 @@ llvm::Error normalizeLinkageTable(json::Array &LinkageTable,
           inconvertibleErrorCode(),
           "Cannot normalize %s JSON: linkage_table entry at index "
           "%zu does not contain a valid 'id' uint64_t field",
-          SummaryTypeName.data(), Index);
+          SummaryClassName.data(), Index);
     }
   }
 
@@ -261,7 +261,7 @@ llvm::Error normalizeLinkageTable(json::Array &LinkageTable,
 }
 
 llvm::Error normalizeSummaryData(json::Array &SummaryData, size_t DataIndex,
-                                 llvm::StringRef SummaryTypeName) {
+                                 llvm::StringRef SummaryClassName) {
   for (const auto &[SummaryIndex, SummaryEntry] :
        llvm::enumerate(SummaryData)) {
     const auto *SummaryEntryObj = SummaryEntry.getAsObject();
@@ -270,7 +270,7 @@ llvm::Error normalizeSummaryData(json::Array &SummaryData, size_t DataIndex,
           inconvertibleErrorCode(),
           "Cannot normalize %s JSON: data entry at index %zu, "
           "summary_data entry at index %zu is not an object",
-          SummaryTypeName.data(), DataIndex, SummaryIndex);
+          SummaryClassName.data(), DataIndex, SummaryIndex);
     }
 
     const auto *EntityIDValue = SummaryEntryObj->get("entity_id");
@@ -280,7 +280,7 @@ llvm::Error normalizeSummaryData(json::Array &SummaryData, size_t DataIndex,
           "Cannot normalize %s JSON: data entry at index %zu, "
           "summary_data entry at index %zu does not contain an "
           "'entity_id' field",
-          SummaryTypeName.data(), DataIndex, SummaryIndex);
+          SummaryClassName.data(), DataIndex, SummaryIndex);
     }
 
     if (!EntityIDValue->getAsUINT64()) {
@@ -289,7 +289,7 @@ llvm::Error normalizeSummaryData(json::Array &SummaryData, size_t DataIndex,
           "Cannot normalize %s JSON: data entry at index %zu, "
           "summary_data entry at index %zu does not contain a valid "
           "'entity_id' uint64_t field",
-          SummaryTypeName.data(), DataIndex, SummaryIndex);
+          SummaryClassName.data(), DataIndex, SummaryIndex);
     }
   }
 
@@ -302,7 +302,7 @@ llvm::Error normalizeSummaryData(json::Array &SummaryData, size_t DataIndex,
   return llvm::Error::success();
 }
 
-llvm::Error normalizeData(json::Array &Data, llvm::StringRef SummaryTypeName) {
+llvm::Error normalizeData(json::Array &Data, llvm::StringRef SummaryClassName) {
   for (const auto &[DataIndex, DataEntry] : llvm::enumerate(Data)) {
     auto *DataEntryObj = DataEntry.getAsObject();
     if (!DataEntryObj) {
@@ -310,7 +310,7 @@ llvm::Error normalizeData(json::Array &Data, llvm::StringRef SummaryTypeName) {
           inconvertibleErrorCode(),
           "Cannot normalize %s JSON: data entry at index %zu "
           "is not an object",
-          SummaryTypeName.data(), DataIndex);
+          SummaryClassName.data(), DataIndex);
     }
 
     if (!DataEntryObj->getString("summary_name")) {
@@ -318,7 +318,7 @@ llvm::Error normalizeData(json::Array &Data, llvm::StringRef SummaryTypeName) {
           inconvertibleErrorCode(),
           "Cannot normalize %s JSON: data entry at index %zu "
           "does not contain a 'summary_name' string field",
-          SummaryTypeName.data(), DataIndex);
+          SummaryClassName.data(), DataIndex);
     }
 
     auto *SummaryData = DataEntryObj->getArray("summary_data");
@@ -327,11 +327,11 @@ llvm::Error normalizeData(json::Array &Data, llvm::StringRef SummaryTypeName) {
           inconvertibleErrorCode(),
           "Cannot normalize %s JSON: data entry at index %zu "
           "does not contain a 'summary_data' array field",
-          SummaryTypeName.data(), DataIndex);
+          SummaryClassName.data(), DataIndex);
     }
 
     if (auto Err =
-            normalizeSummaryData(*SummaryData, DataIndex, SummaryTypeName)) {
+            normalizeSummaryData(*SummaryData, DataIndex, SummaryClassName)) {
       return Err;
     }
   }
@@ -346,12 +346,12 @@ llvm::Error normalizeData(json::Array &Data, llvm::StringRef SummaryTypeName) {
 }
 
 Expected<json::Value> normalizeSummaryJSON(json::Value Val,
-                                           llvm::StringRef SummaryTypeName) {
+                                           llvm::StringRef SummaryClassName) {
   auto *Obj = Val.getAsObject();
   if (!Obj) {
     return createStringError(inconvertibleErrorCode(),
                              "Cannot normalize %s JSON: expected an object",
-                             SummaryTypeName.data());
+                             SummaryClassName.data());
   }
 
   auto *IDTable = Obj->getArray("id_table");
@@ -359,9 +359,9 @@ Expected<json::Value> normalizeSummaryJSON(json::Value Val,
     return createStringError(inconvertibleErrorCode(),
                              "Cannot normalize %s JSON: 'id_table' "
                              "field is either missing or has the wrong type",
-                             SummaryTypeName.data());
+                             SummaryClassName.data());
   }
-  if (auto Err = normalizeIDTable(*IDTable, SummaryTypeName)) {
+  if (auto Err = normalizeIDTable(*IDTable, SummaryClassName)) {
     return std::move(Err);
   }
 
@@ -370,9 +370,9 @@ Expected<json::Value> normalizeSummaryJSON(json::Value Val,
     return createStringError(inconvertibleErrorCode(),
                              "Cannot normalize %s JSON: 'linkage_table' "
                              "field is either missing or has the wrong type",
-                             SummaryTypeName.data());
+                             SummaryClassName.data());
   }
-  if (auto Err = normalizeLinkageTable(*LinkageTable, SummaryTypeName)) {
+  if (auto Err = normalizeLinkageTable(*LinkageTable, SummaryClassName)) {
     return std::move(Err);
   }
 
@@ -381,9 +381,9 @@ Expected<json::Value> normalizeSummaryJSON(json::Value Val,
     return createStringError(inconvertibleErrorCode(),
                              "Cannot normalize %s JSON: 'data' "
                              "field is either missing or has the wrong type",
-                             SummaryTypeName.data());
+                             SummaryClassName.data());
   }
-  if (auto Err = normalizeData(*Data, SummaryTypeName)) {
+  if (auto Err = normalizeData(*Data, SummaryClassName)) {
     return std::move(Err);
   }
 
@@ -436,11 +436,11 @@ void SummaryTest::readWriteCompare(StringRef JSON) const {
   ASSERT_THAT_EXPECTED(ExpectedOutputJSON, Succeeded());
 
   auto ExpectedNormalizedInputJSON =
-      normalizeSummaryJSON(*ExpectedInputJSON, GetParam().SummaryTypeName);
+      normalizeSummaryJSON(*ExpectedInputJSON, GetParam().SummaryClassName);
   ASSERT_THAT_EXPECTED(ExpectedNormalizedInputJSON, Succeeded());
 
   auto ExpectedNormalizedOutputJSON =
-      normalizeSummaryJSON(*ExpectedOutputJSON, GetParam().SummaryTypeName);
+      normalizeSummaryJSON(*ExpectedOutputJSON, GetParam().SummaryClassName);
   ASSERT_THAT_EXPECTED(ExpectedNormalizedOutputJSON, Succeeded());
 
   ASSERT_EQ(*ExpectedNormalizedInputJSON, *ExpectedNormalizedOutputJSON)
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.h b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.h
index 684a012efa98d..a57b5224c3795 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.h
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.h
@@ -74,8 +74,14 @@ class JSONFormatTest : public TestFixture {
 // ============================================================================
 
 struct SummaryOps {
-  std::string Name;
-  std::string SummaryTypeName;
+  // Suffix appended to the test name by GTest to identify this parameter
+  // instantiation (e.g. "Resolved", "Encoding").
+  std::string GTestInstantiationSuffix;
+
+  // Human-readable name of the summary class under test (e.g. "TUSummary").
+  // Used in diagnostic messages to identify which summary type an error
+  // originated from.
+  std::string SummaryClassName;
 
   std::function<llvm::Error(llvm::StringRef FilePath)> ReadFromFile;
 
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp
index d7eccda317aaa..33aa9875ca46f 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp
@@ -84,7 +84,7 @@ class LUSummaryTest : public SummaryTest {};
 INSTANTIATE_TEST_SUITE_P(JSONFormat, LUSummaryTest,
                          ::testing::Values(LUSummaryOps, LUSummaryEncodingOps),
                          [](const ::testing::TestParamInfo<SummaryOps> &Info) {
-                           return Info.param.Name;
+                           return Info.param.GTestInstantiationSuffix;
                          });
 
 // ============================================================================
@@ -1665,12 +1665,11 @@ TEST_F(JSONFormatLUSummaryTest, ReadEntitySummaryMismatchedSummaryName) {
           HasSubstr("reading EntitySummary entries from field 'summary_data'"),
           HasSubstr("reading EntitySummary entry from index '0'"),
           HasSubstr("failed to deserialize EntitySummary"),
-          HasSubstr(
-              "EntitySummary data for "
-              "'SummaryName(MismatchedEntitySummaryForJSONFormatTest)' reports "
-              "mismatched "
-              "'SummaryName(MismatchedEntitySummaryForJSONFormatTest_WrongName)"
-              "'"))));
+          HasSubstr("EntitySummary data for"
+                    "'SummaryName(MismatchedEntitySummaryForJSONFormatTest)'"
+                    " reports mismatched"
+                    "'SummaryName(MismatchedEntitySummaryForJSONFormatTest_"
+                    "WrongName)'"))));
 }
 
 // ============================================================================
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp
index 8cc3d5b2ae85a..a8cf82806aecc 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp
@@ -81,7 +81,7 @@ class TUSummaryTest : public SummaryTest {};
 INSTANTIATE_TEST_SUITE_P(JSONFormat, TUSummaryTest,
                          ::testing::Values(TUSummaryOps, TUSummaryEncodingOps),
                          [](const ::testing::TestParamInfo<SummaryOps> &Info) {
-                           return Info.param.Name;
+                           return Info.param.GTestInstantiationSuffix;
                          });
 
 // ============================================================================

>From 623f6a786e905def2818516bfee2794beb196a85 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Mon, 2 Mar 2026 07:53:24 -0800
Subject: [PATCH 8/9] Add spaces

---
 .../Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp
index 33aa9875ca46f..5c6f32192b2c5 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp
@@ -1665,9 +1665,9 @@ TEST_F(JSONFormatLUSummaryTest, ReadEntitySummaryMismatchedSummaryName) {
           HasSubstr("reading EntitySummary entries from field 'summary_data'"),
           HasSubstr("reading EntitySummary entry from index '0'"),
           HasSubstr("failed to deserialize EntitySummary"),
-          HasSubstr("EntitySummary data for"
+          HasSubstr("EntitySummary data for "
                     "'SummaryName(MismatchedEntitySummaryForJSONFormatTest)'"
-                    " reports mismatched"
+                    " reports mismatched "
                     "'SummaryName(MismatchedEntitySummaryForJSONFormatTest_"
                     "WrongName)'"))));
 }

>From e9a791d62691c91cfec1212d773097b476464b0a Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Mon, 2 Mar 2026 07:55:47 -0800
Subject: [PATCH 9/9] Unmove

---
 .../Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp       | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
index 6a2ce6f21b4db..0cf35fdae6927 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
@@ -110,8 +110,7 @@ std::map<SummaryName, JSONFormat::FormatInfo> JSONFormat::initFormatInfos() {
   std::map<SummaryName, FormatInfo> FormatInfos;
   for (const auto &FormatInfoEntry : llvm::Registry<FormatInfo>::entries()) {
     std::unique_ptr<FormatInfo> Info = FormatInfoEntry.instantiate();
-    bool Inserted =
-        FormatInfos.try_emplace(Info->ForSummary, std::move(*Info)).second;
+    bool Inserted = FormatInfos.try_emplace(Info->ForSummary, *Info).second;
     if (!Inserted) {
       llvm::report_fatal_error(
           "FormatInfo is already registered for summary: " +



More information about the cfe-commits mailing list