[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