[clang] SSAF JSON Format (PR #180021)
Aviral Goel via cfe-commits
cfe-commits at lists.llvm.org
Tue Feb 10 16:51:32 PST 2026
https://github.com/aviralg updated https://github.com/llvm/llvm-project/pull/180021
>From 5ae260fc6191ef087118a619214684fe0d471739 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Wed, 28 Jan 2026 09:21:20 -0800
Subject: [PATCH 01/19] Replace const and non-const static methods with
templated methods
---
.../Serialization/SerializationFormat.h | 30 ++++-----
clang/lib/Analysis/Scalable/CMakeLists.txt | 1 -
.../Serialization/SerializationFormat.cpp | 61 -------------------
3 files changed, 16 insertions(+), 76 deletions(-)
delete mode 100644 clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index a53a315f461df..30d728239e5c3 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -32,20 +32,22 @@ class SerializationFormat {
protected:
// Helpers providing access to implementation details of basic data structures
// for efficient serialization/deserialization.
- static EntityIdTable &getIdTableForDeserialization(TUSummary &S);
- static BuildNamespace &getTUNamespaceForDeserialization(TUSummary &S);
- static const EntityIdTable &getIdTable(const TUSummary &S);
- static const BuildNamespace &getTUNamespace(const TUSummary &S);
-
- static BuildNamespaceKind getBuildNamespaceKind(const BuildNamespace &BN);
- static llvm::StringRef getBuildNamespaceName(const BuildNamespace &BN);
- static const std::vector<BuildNamespace> &
- getNestedBuildNamespaces(const NestedBuildNamespace &NBN);
-
- static llvm::StringRef getEntityNameUSR(const EntityName &EN);
- static const llvm::SmallString<16> &getEntityNameSuffix(const EntityName &EN);
- static const NestedBuildNamespace &
- getEntityNameNamespace(const EntityName &EN);
+
+ // Accessors for TUSummary:
+ template <class T> static auto &IdTableOf(T &X) { return X.IdTable; }
+ template <class T> static auto &TUNamespaceOf(T &X) { return X.TUNamespace; }
+
+ // Accessors for BuildNamespace:
+ template <class T> static auto &KindOf(T &X) { return X.Kind; }
+ template <class T> static auto &NameOf(T &X) { return X.Name; }
+
+ // Accessors for NestedBuildNamespace:
+ template <class T> static auto &NamespacesOf(T &X) { return X.Namespaces; }
+
+ // Accessors for EntityName:
+ template <class T> static auto &USROf(T &X) { return X.USR; }
+ template <class T> static auto &SuffixOf(T &X) { return X.Suffix; }
+ template <class T> static auto &NamespaceOf(T &X) { return X.Namespace; }
public:
virtual ~SerializationFormat() = default;
diff --git a/clang/lib/Analysis/Scalable/CMakeLists.txt b/clang/lib/Analysis/Scalable/CMakeLists.txt
index 36365b1fb87e1..566edca552388 100644
--- a/clang/lib/Analysis/Scalable/CMakeLists.txt
+++ b/clang/lib/Analysis/Scalable/CMakeLists.txt
@@ -7,7 +7,6 @@ add_clang_library(clangAnalysisScalable
Model/BuildNamespace.cpp
Model/EntityIdTable.cpp
Model/EntityName.cpp
- Serialization/SerializationFormat.cpp
TUSummary/ExtractorRegistry.cpp
LINK_LIBS
diff --git a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
deleted file mode 100644
index ee155d22afa9b..0000000000000
--- a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-//===- SerializationFormat.cpp ----------------------------------*- C++ -*-===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-#include "clang/Analysis/Scalable/Serialization/SerializationFormat.h"
-#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
-#include "clang/Analysis/Scalable/Model/EntityId.h"
-#include "clang/Analysis/Scalable/Model/EntityName.h"
-#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
-
-using namespace clang::ssaf;
-
-EntityIdTable &SerializationFormat::getIdTableForDeserialization(TUSummary &S) {
- return S.IdTable;
-}
-
-BuildNamespace &
-SerializationFormat::getTUNamespaceForDeserialization(TUSummary &S) {
- return S.TUNamespace;
-}
-
-const EntityIdTable &SerializationFormat::getIdTable(const TUSummary &S) {
- return S.IdTable;
-}
-
-const BuildNamespace &SerializationFormat::getTUNamespace(const TUSummary &S) {
- return S.TUNamespace;
-}
-
-BuildNamespaceKind
-SerializationFormat::getBuildNamespaceKind(const BuildNamespace &BN) {
- return BN.Kind;
-}
-
-llvm::StringRef
-SerializationFormat::getBuildNamespaceName(const BuildNamespace &BN) {
- return BN.Name;
-}
-
-const std::vector<BuildNamespace> &
-SerializationFormat::getNestedBuildNamespaces(const NestedBuildNamespace &NBN) {
- return NBN.Namespaces;
-}
-
-llvm::StringRef SerializationFormat::getEntityNameUSR(const EntityName &EN) {
- return EN.USR;
-}
-
-const llvm::SmallString<16> &
-SerializationFormat::getEntityNameSuffix(const EntityName &EN) {
- return EN.Suffix;
-}
-
-const NestedBuildNamespace &
-SerializationFormat::getEntityNameNamespace(const EntityName &EN) {
- return EN.Namespace;
-}
>From b5ae6874e8cbcc6d8eeb3737b7bffea7a0506eaa Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Wed, 28 Jan 2026 13:52:12 -0800
Subject: [PATCH 02/19] Revert "Replace const and non-const static methods with
templated methods"
This reverts commit c93430657298a6ac87ac51be6bd310cc652aa52d.
---
.../Serialization/SerializationFormat.h | 30 +++++----
clang/lib/Analysis/Scalable/CMakeLists.txt | 1 +
.../Serialization/SerializationFormat.cpp | 61 +++++++++++++++++++
3 files changed, 76 insertions(+), 16 deletions(-)
create mode 100644 clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index 30d728239e5c3..a53a315f461df 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -32,22 +32,20 @@ class SerializationFormat {
protected:
// Helpers providing access to implementation details of basic data structures
// for efficient serialization/deserialization.
-
- // Accessors for TUSummary:
- template <class T> static auto &IdTableOf(T &X) { return X.IdTable; }
- template <class T> static auto &TUNamespaceOf(T &X) { return X.TUNamespace; }
-
- // Accessors for BuildNamespace:
- template <class T> static auto &KindOf(T &X) { return X.Kind; }
- template <class T> static auto &NameOf(T &X) { return X.Name; }
-
- // Accessors for NestedBuildNamespace:
- template <class T> static auto &NamespacesOf(T &X) { return X.Namespaces; }
-
- // Accessors for EntityName:
- template <class T> static auto &USROf(T &X) { return X.USR; }
- template <class T> static auto &SuffixOf(T &X) { return X.Suffix; }
- template <class T> static auto &NamespaceOf(T &X) { return X.Namespace; }
+ static EntityIdTable &getIdTableForDeserialization(TUSummary &S);
+ static BuildNamespace &getTUNamespaceForDeserialization(TUSummary &S);
+ static const EntityIdTable &getIdTable(const TUSummary &S);
+ static const BuildNamespace &getTUNamespace(const TUSummary &S);
+
+ static BuildNamespaceKind getBuildNamespaceKind(const BuildNamespace &BN);
+ static llvm::StringRef getBuildNamespaceName(const BuildNamespace &BN);
+ static const std::vector<BuildNamespace> &
+ getNestedBuildNamespaces(const NestedBuildNamespace &NBN);
+
+ static llvm::StringRef getEntityNameUSR(const EntityName &EN);
+ static const llvm::SmallString<16> &getEntityNameSuffix(const EntityName &EN);
+ static const NestedBuildNamespace &
+ getEntityNameNamespace(const EntityName &EN);
public:
virtual ~SerializationFormat() = default;
diff --git a/clang/lib/Analysis/Scalable/CMakeLists.txt b/clang/lib/Analysis/Scalable/CMakeLists.txt
index 566edca552388..36365b1fb87e1 100644
--- a/clang/lib/Analysis/Scalable/CMakeLists.txt
+++ b/clang/lib/Analysis/Scalable/CMakeLists.txt
@@ -7,6 +7,7 @@ add_clang_library(clangAnalysisScalable
Model/BuildNamespace.cpp
Model/EntityIdTable.cpp
Model/EntityName.cpp
+ Serialization/SerializationFormat.cpp
TUSummary/ExtractorRegistry.cpp
LINK_LIBS
diff --git a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
new file mode 100644
index 0000000000000..ee155d22afa9b
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
@@ -0,0 +1,61 @@
+//===- SerializationFormat.cpp ----------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/Serialization/SerializationFormat.h"
+#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
+#include "clang/Analysis/Scalable/Model/EntityId.h"
+#include "clang/Analysis/Scalable/Model/EntityName.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+
+using namespace clang::ssaf;
+
+EntityIdTable &SerializationFormat::getIdTableForDeserialization(TUSummary &S) {
+ return S.IdTable;
+}
+
+BuildNamespace &
+SerializationFormat::getTUNamespaceForDeserialization(TUSummary &S) {
+ return S.TUNamespace;
+}
+
+const EntityIdTable &SerializationFormat::getIdTable(const TUSummary &S) {
+ return S.IdTable;
+}
+
+const BuildNamespace &SerializationFormat::getTUNamespace(const TUSummary &S) {
+ return S.TUNamespace;
+}
+
+BuildNamespaceKind
+SerializationFormat::getBuildNamespaceKind(const BuildNamespace &BN) {
+ return BN.Kind;
+}
+
+llvm::StringRef
+SerializationFormat::getBuildNamespaceName(const BuildNamespace &BN) {
+ return BN.Name;
+}
+
+const std::vector<BuildNamespace> &
+SerializationFormat::getNestedBuildNamespaces(const NestedBuildNamespace &NBN) {
+ return NBN.Namespaces;
+}
+
+llvm::StringRef SerializationFormat::getEntityNameUSR(const EntityName &EN) {
+ return EN.USR;
+}
+
+const llvm::SmallString<16> &
+SerializationFormat::getEntityNameSuffix(const EntityName &EN) {
+ return EN.Suffix;
+}
+
+const NestedBuildNamespace &
+SerializationFormat::getEntityNameNamespace(const EntityName &EN) {
+ return EN.Namespace;
+}
>From 1d12d1eebf9a5db3208d8ccf3911d7035e4d3d45 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Wed, 4 Feb 2026 16:12:00 -0800
Subject: [PATCH 03/19] Initial implementation of JSONFormat with tests.
---
.../clang/Analysis/Scalable/Model/EntityId.h | 1 +
.../Analysis/Scalable/Model/EntityIdTable.h | 2 +
.../Analysis/Scalable/Model/SummaryName.h | 2 +
.../Scalable/Serialization/JSONFormat.h | 77 ++
.../Serialization/SerializationFormat.h | 25 +-
clang/lib/Analysis/Scalable/CMakeLists.txt | 1 +
.../Scalable/Serialization/JSONFormat.cpp | 860 +++++++++++++
.../Serialization/SerializationFormat.cpp | 29 +
.../Analysis/Scalable/CMakeLists.txt | 1 +
.../Analysis/Scalable/JSONFormatTest.cpp | 1127 +++++++++++++++++
10 files changed, 2122 insertions(+), 3 deletions(-)
create mode 100644 clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
create mode 100644 clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
create mode 100644 clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityId.h b/clang/include/clang/Analysis/Scalable/Model/EntityId.h
index 6fa059445d853..e348486386cb6 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityId.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityId.h
@@ -29,6 +29,7 @@ class EntityIdTable;
/// \see EntityIdTable
class EntityId {
friend class EntityIdTable;
+ friend class SerializationFormat;
size_t Index;
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h b/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
index cf4bb83efddd0..a1099c4e4d0f8 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
@@ -21,6 +21,8 @@ namespace clang::ssaf {
/// The table maps each unique EntityName to exactly one EntityId.
/// Entities are never removed.
class EntityIdTable {
+ friend class SerializationFormat;
+
std::map<EntityName, EntityId> Entities;
public:
diff --git a/clang/include/clang/Analysis/Scalable/Model/SummaryName.h b/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
index 785fe0eb10372..08ed67fe17bed 100644
--- a/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
+++ b/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
@@ -10,6 +10,8 @@
#define LLVM_CLANG_ANALYSIS_SCALABLE_MODEL_SUMMARYNAME_H
#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Path.h"
+#include <cassert>
#include <string>
namespace clang::ssaf {
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
new file mode 100644
index 0000000000000..476853af0dce4
--- /dev/null
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -0,0 +1,77 @@
+//===- JSONFormat.h ---------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// JSON serialization format implementation
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef CLANG_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_H
+#define CLANG_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_H
+
+#include "clang/Analysis/Scalable/Serialization/SerializationFormat.h"
+#include "llvm/Support/JSON.h"
+
+namespace clang::ssaf {
+
+class EntitySummary;
+class SummaryName;
+
+class JSONFormat : public SerializationFormat {
+public:
+ JSONFormat() = default;
+
+ ~JSONFormat() = default;
+
+ llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) override;
+
+ llvm::Error writeTUSummary(const TUSummary &Summary,
+ llvm::StringRef OutputDir) override;
+
+private:
+ EntityId entityIdFromJSON(const uint64_t EntityIdIndex);
+ uint64_t entityIdToJSON(EntityId EI) const;
+
+ llvm::json::Object buildNamespaceToJSON(const BuildNamespace &BN) const;
+ llvm::json::Array
+ nestedBuildNamespaceToJSON(const NestedBuildNamespace &NBN) const;
+ llvm::json::Object entityNameToJSON(const EntityName &EN) const;
+ llvm::Expected<std::pair<EntityName, EntityId>>
+ entityIdTableEntryFromJSON(const llvm::json::Object &EntityIdTableEntryObject,
+ llvm::StringRef Path);
+ llvm::Expected<EntityIdTable>
+ entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray,
+ llvm::StringRef Path);
+ llvm::json::Array entityIdTableToJSON(const EntityIdTable &IdTable) const;
+
+ llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
+ entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
+ llvm::StringRef Path);
+ llvm::json::Array entityDataMapToJSON(
+ const std::map<EntityId, std::unique_ptr<EntitySummary>> &EntityDataMap)
+ const;
+
+ llvm::Expected<std::pair<SummaryName,
+ std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+ summaryDataMapEntryFromJSON(const llvm::json::Object &SummaryDataObject,
+ llvm::StringRef Path);
+ llvm::json::Object summaryDataMapEntryToJSON(
+ const SummaryName &SN,
+ const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const;
+
+ llvm::Expected<
+ std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+ readTUSummaryData(llvm::StringRef Path);
+ llvm::Error writeTUSummaryData(
+ const std::map<SummaryName,
+ std::map<EntityId, std::unique_ptr<EntitySummary>>> &Data,
+ llvm::StringRef Path);
+};
+
+} // namespace clang::ssaf
+
+#endif // CLANG_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_H
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index a53a315f461df..3d79f49c12dcb 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -17,6 +17,8 @@
#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include <map>
#include <vector>
namespace clang::ssaf {
@@ -26,16 +28,33 @@ class EntityIdTable;
class EntityName;
class TUSummary;
class TUSummaryData;
+class SummaryName;
+class EntitySummary;
/// Abstract base class for serialization formats.
class SerializationFormat {
protected:
// Helpers providing access to implementation details of basic data structures
// for efficient serialization/deserialization.
+
+ static size_t getEntityIdIndex(const EntityId &EI);
+ static EntityId makeEntityId(const size_t Index);
+
+ static const std::map<EntityName, EntityId> &
+ getEntities(const EntityIdTable &EIT);
+ static std::map<EntityName, EntityId> &
+ getEntitiesForDeserialization(EntityIdTable &EIT);
+
static EntityIdTable &getIdTableForDeserialization(TUSummary &S);
static BuildNamespace &getTUNamespaceForDeserialization(TUSummary &S);
+ static std::map<SummaryName,
+ std::map<EntityId, std::unique_ptr<EntitySummary>>> &
+ getDataForDeserialization(TUSummary &S);
static const EntityIdTable &getIdTable(const TUSummary &S);
static const BuildNamespace &getTUNamespace(const TUSummary &S);
+ static const std::map<SummaryName,
+ std::map<EntityId, std::unique_ptr<EntitySummary>>> &
+ getData(const TUSummary &S);
static BuildNamespaceKind getBuildNamespaceKind(const BuildNamespace &BN);
static llvm::StringRef getBuildNamespaceName(const BuildNamespace &BN);
@@ -50,10 +69,10 @@ class SerializationFormat {
public:
virtual ~SerializationFormat() = default;
- virtual TUSummary readTUSummary(llvm::StringRef Path) = 0;
+ virtual llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) = 0;
- virtual void writeTUSummary(const TUSummary &Summary,
- llvm::StringRef OutputDir) = 0;
+ virtual llvm::Error writeTUSummary(const TUSummary &Summary,
+ llvm::StringRef OutputDir) = 0;
};
} // namespace clang::ssaf
diff --git a/clang/lib/Analysis/Scalable/CMakeLists.txt b/clang/lib/Analysis/Scalable/CMakeLists.txt
index 36365b1fb87e1..6592d01bd8f38 100644
--- a/clang/lib/Analysis/Scalable/CMakeLists.txt
+++ b/clang/lib/Analysis/Scalable/CMakeLists.txt
@@ -7,6 +7,7 @@ add_clang_library(clangAnalysisScalable
Model/BuildNamespace.cpp
Model/EntityIdTable.cpp
Model/EntityName.cpp
+ Serialization/JSONFormat.cpp
Serialization/SerializationFormat.cpp
TUSummary/ExtractorRegistry.cpp
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
new file mode 100644
index 0000000000000..cca3ca36eb9ae
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -0,0 +1,860 @@
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+
+using namespace clang::ssaf;
+
+namespace {
+constexpr size_t kPathBufferSize = 128;
+constexpr const char *TUSummaryTUNamespaceFilename = "tu_namespace.json";
+constexpr const char *TUSummaryIdTableFilename = "id_table.json";
+constexpr const char *TUSummaryDataDirname = "data";
+
+// Helper to wrap an error with additional context
+template <typename... Args>
+llvm::Error wrapError(llvm::Error E, std::errc ErrorCode, const char *Fmt,
+ Args &&...Vals) {
+ return llvm::joinErrors(
+ llvm::createStringError(ErrorCode, Fmt, std::forward<Args>(Vals)...),
+ std::move(E));
+}
+
+// Convenience version that defaults to invalid_argument
+template <typename... Args>
+llvm::Error wrapError(llvm::Error E, const char *Fmt, Args &&...Vals) {
+ return wrapError(std::move(E), std::errc::invalid_argument, Fmt,
+ std::forward<Args>(Vals)...);
+}
+
+} // namespace
+
+//----------------------------------------------------------------------------
+// JSON Reader and Writer
+//----------------------------------------------------------------------------
+
+llvm::Error isJSONFile(llvm::StringRef Path) {
+ if (!llvm::sys::fs::exists(Path))
+ return llvm::createStringError(std::errc::no_such_file_or_directory,
+ "file does not exist: '%s'",
+ Path.str().c_str());
+
+ if (!Path.ends_with_insensitive(".json"))
+ return llvm::createStringError(std::errc::invalid_argument,
+ "not a JSON file: '%s'", Path.str().c_str());
+
+ return llvm::Error::success();
+}
+
+llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
+ if (llvm::Error Err = isJSONFile(Path))
+ return wrapError(std::move(Err), "failed to validate JSON file '%s'",
+ Path.str().c_str());
+
+ auto BufferOrError = llvm::MemoryBuffer::getFile(Path);
+ if (!BufferOrError) {
+ return llvm::createStringError(BufferOrError.getError(),
+ "failed to read file '%s'",
+ Path.str().c_str());
+ }
+
+ return llvm::json::parse(BufferOrError.get()->getBuffer());
+}
+
+llvm::Expected<llvm::json::Object> readJSONObject(llvm::StringRef Path) {
+ auto ExpectedJSON = readJSON(Path);
+ if (!ExpectedJSON)
+ return wrapError(ExpectedJSON.takeError(),
+ "failed to read JSON from file '%s'", Path.str().c_str());
+
+ llvm::json::Object *Object = ExpectedJSON->getAsObject();
+ if (!Object) {
+ return llvm::createStringError(std::errc::invalid_argument,
+ "failed to read JSON object from file '%s'",
+ Path.str().c_str());
+ }
+ return std::move(*Object);
+}
+
+llvm::Expected<llvm::json::Array> readJSONArray(llvm::StringRef Path) {
+ auto ExpectedJSON = readJSON(Path);
+ if (!ExpectedJSON)
+ return wrapError(ExpectedJSON.takeError(),
+ "failed to read JSON from file '%s'", Path.str().c_str());
+
+ llvm::json::Array *Array = ExpectedJSON->getAsArray();
+ if (!Array) {
+ return llvm::createStringError(std::errc::invalid_argument,
+ "failed to read JSON array from file '%s'",
+ Path.str().c_str());
+ }
+ return std::move(*Array);
+}
+
+llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
+ std::error_code EC;
+ llvm::raw_fd_ostream OS(Path, EC);
+ if (EC) {
+ return llvm::createStringError(EC, "failed to open '%s'",
+ Path.str().c_str());
+ }
+
+ OS << llvm::formatv("{0:2}\n", Value);
+
+ OS.flush();
+ if (OS.has_error()) {
+ return llvm::createStringError(OS.error(), "write failed");
+ }
+
+ return llvm::Error::success();
+}
+
+//----------------------------------------------------------------------------
+// EntityId
+//----------------------------------------------------------------------------
+
+EntityId JSONFormat::entityIdFromJSON(const uint64_t EntityIdIndex) {
+ return makeEntityId(static_cast<size_t>(EntityIdIndex));
+}
+
+uint64_t JSONFormat::entityIdToJSON(EntityId EI) const {
+ return static_cast<uint64_t>(getEntityIdIndex(EI));
+}
+
+//----------------------------------------------------------------------------
+// BuildNamespaceKind
+//----------------------------------------------------------------------------
+
+llvm::Expected<BuildNamespaceKind>
+buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr,
+ llvm::StringRef Path) {
+ auto OptBuildNamespaceKind = parseBuildNamespaceKind(BuildNamespaceKindStr);
+ if (!OptBuildNamespaceKind) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "invalid 'kind' BuildNamespaceKind value '%s' in file '%s'",
+ BuildNamespaceKindStr.str().c_str(), Path.str().c_str());
+ }
+
+ return *OptBuildNamespaceKind;
+}
+
+llvm::StringRef buildNamespaceKindToJSON(BuildNamespaceKind BNK) {
+ return toString(BNK);
+}
+
+//----------------------------------------------------------------------------
+// BuildNamespace
+//----------------------------------------------------------------------------
+
+llvm::Expected<BuildNamespace>
+buildNamespaceFromJSON(const llvm::json::Object &BuildNamespaceObject,
+ llvm::StringRef Path) {
+ auto OptBuildNamespaceKindStr = BuildNamespaceObject.getString("kind");
+ if (!OptBuildNamespaceKindStr) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize BuildNamespace from file '%s': "
+ "missing required field 'kind' (expected BuildNamespaceKind)",
+ Path.str().c_str());
+ }
+
+ auto ExpectedKind =
+ buildNamespaceKindFromJSON(*OptBuildNamespaceKindStr, Path);
+ if (!ExpectedKind)
+ return wrapError(ExpectedKind.takeError(),
+ "failed to deserialize BuildNamespace from file '%s'",
+ Path.str().c_str());
+
+ auto OptNameStr = BuildNamespaceObject.getString("name");
+ if (!OptNameStr) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize BuildNamespace from file '%s': "
+ "missing required field 'name'",
+ Path.str().c_str());
+ }
+
+ return {BuildNamespace(*ExpectedKind, *OptNameStr)};
+}
+
+llvm::json::Object
+JSONFormat::buildNamespaceToJSON(const BuildNamespace &BN) const {
+ llvm::json::Object Result;
+ Result["kind"] = buildNamespaceKindToJSON(getBuildNamespaceKind(BN));
+ Result["name"] = getBuildNamespaceName(BN);
+ return Result;
+}
+
+//----------------------------------------------------------------------------
+// NestedBuildNamespace
+//----------------------------------------------------------------------------
+
+llvm::Expected<NestedBuildNamespace>
+nestedBuildNamespaceFromJSON(const llvm::json::Array &NestedBuildNamespaceArray,
+ llvm::StringRef Path) {
+ std::vector<BuildNamespace> Namespaces;
+
+ size_t NamespaceCount = NestedBuildNamespaceArray.size();
+ Namespaces.reserve(NamespaceCount);
+ for (size_t Index = 0; Index < NamespaceCount; ++Index) {
+ const llvm::json::Value &BuildNamespaceValue =
+ NestedBuildNamespaceArray[Index];
+
+ const llvm::json::Object *BuildNamespaceObject =
+ BuildNamespaceValue.getAsObject();
+ if (!BuildNamespaceObject) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize NestedBuildNamespace from file '%s': "
+ "element at index %zu is not a JSON object "
+ "(expected BuildNamespace object)",
+ Path.str().c_str(), Index);
+ }
+
+ auto ExpectedBuildNamespace =
+ buildNamespaceFromJSON(*BuildNamespaceObject, Path);
+ if (!ExpectedBuildNamespace)
+ return wrapError(
+ ExpectedBuildNamespace.takeError(),
+ "failed to deserialize NestedBuildNamespace from file '%s' "
+ "at index %zu",
+ Path.str().c_str(), Index);
+
+ Namespaces.push_back(std::move(*ExpectedBuildNamespace));
+ }
+
+ return NestedBuildNamespace(std::move(Namespaces));
+}
+
+llvm::json::Array
+JSONFormat::nestedBuildNamespaceToJSON(const NestedBuildNamespace &NBN) const {
+ llvm::json::Array Result;
+ const auto &Namespaces = getNestedBuildNamespaces(NBN);
+ Result.reserve(Namespaces.size());
+
+ for (const auto &BN : Namespaces) {
+ Result.push_back(buildNamespaceToJSON(BN));
+ }
+
+ return Result;
+}
+
+//----------------------------------------------------------------------------
+// EntityName
+//----------------------------------------------------------------------------
+
+llvm::Expected<EntityName>
+entityNameFromJSON(const llvm::json::Object &EntityNameObject,
+ llvm::StringRef Path) {
+ const auto OptUSR = EntityNameObject.getString("usr");
+ if (!OptUSR) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntityName from file '%s': "
+ "missing required field 'usr' (Unified Symbol Resolution string)",
+ Path.str().c_str());
+ }
+
+ const auto OptSuffix = EntityNameObject.getString("suffix");
+ if (!OptSuffix) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntityName from file '%s': "
+ "missing required field 'suffix'",
+ Path.str().c_str());
+ }
+
+ const llvm::json::Array *OptNamespaceArray =
+ EntityNameObject.getArray("namespace");
+ if (!OptNamespaceArray) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntityName from file '%s': "
+ "missing or invalid field 'namespace' "
+ "(expected JSON array of BuildNamespace objects)",
+ Path.str().c_str());
+ }
+
+ auto ExpectedNamespace =
+ nestedBuildNamespaceFromJSON(*OptNamespaceArray, Path);
+ if (!ExpectedNamespace)
+ return wrapError(ExpectedNamespace.takeError(),
+ "failed to deserialize EntityName from file '%s'",
+ Path.str().c_str());
+
+ return EntityName{*OptUSR, *OptSuffix, std::move(*ExpectedNamespace)};
+}
+
+llvm::json::Object JSONFormat::entityNameToJSON(const EntityName &EN) const {
+ llvm::json::Object Result;
+ Result["usr"] = getEntityNameUSR(EN);
+ Result["suffix"] = getEntityNameSuffix(EN);
+ Result["namespace"] = nestedBuildNamespaceToJSON(getEntityNameNamespace(EN));
+ return Result;
+}
+
+//----------------------------------------------------------------------------
+// SummaryName
+//----------------------------------------------------------------------------
+
+SummaryName summaryNameFromJSON(llvm::StringRef SummaryNameStr) {
+ return SummaryName(SummaryNameStr.str());
+}
+
+llvm::StringRef summaryNameToJSON(const SummaryName &SN) { return SN.str(); }
+
+//----------------------------------------------------------------------------
+// EntityIdTable
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::pair<EntityName, EntityId>>
+JSONFormat::entityIdTableEntryFromJSON(
+ const llvm::json::Object &EntityIdTableEntryObject, llvm::StringRef Path) {
+
+ const llvm::json::Object *OptEntityNameObject =
+ EntityIdTableEntryObject.getObject("name");
+ if (!OptEntityNameObject) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntityIdTable entry from file '%s': "
+ "missing or invalid field 'name' (expected EntityName JSON object)",
+ Path.str().c_str());
+ }
+
+ auto ExpectedEntityName = entityNameFromJSON(*OptEntityNameObject, Path);
+ if (!ExpectedEntityName)
+ return wrapError(ExpectedEntityName.takeError(),
+ "failed to deserialize EntityIdTable entry from file '%s'",
+ Path.str().c_str());
+
+ const llvm::json::Value *EntityIdIntValue =
+ EntityIdTableEntryObject.get("id");
+ if (!EntityIdIntValue) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntityIdTable entry from file '%s': "
+ "missing required field 'id' (expected unsigned integer EntityId)",
+ Path.str().c_str());
+ }
+
+ const std::optional<uint64_t> OptEntityIdInt =
+ EntityIdIntValue->getAsUINT64();
+ if (!OptEntityIdInt) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntityIdTable entry from file '%s': "
+ "field 'id' is not a valid unsigned 64-bit integer "
+ "(expected non-negative EntityId value)",
+ Path.str().c_str());
+ }
+
+ EntityId EI = entityIdFromJSON(*OptEntityIdInt);
+
+ return std::make_pair(std::move(*ExpectedEntityName), std::move(EI));
+}
+
+llvm::Expected<EntityIdTable>
+JSONFormat::entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray,
+ llvm::StringRef Path) {
+ const size_t EntityCount = EntityIdTableArray.size();
+
+ EntityIdTable IdTable;
+ std::map<EntityName, EntityId> &Entities =
+ getEntitiesForDeserialization(IdTable);
+
+ for (size_t Index = 0; Index < EntityCount; ++Index) {
+ const llvm::json::Value &EntityIdTableEntryValue =
+ EntityIdTableArray[Index];
+
+ const llvm::json::Object *OptEntityIdTableEntryObject =
+ EntityIdTableEntryValue.getAsObject();
+
+ if (!OptEntityIdTableEntryObject) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntityIdTable from file '%s': "
+ "element at index %zu is not a JSON object "
+ "(expected EntityIdTable entry with 'id' and 'name' fields)",
+ Path.str().c_str(), Index);
+ }
+
+ auto ExpectedEntityIdTableEntry =
+ entityIdTableEntryFromJSON(*OptEntityIdTableEntryObject, Path);
+ if (!ExpectedEntityIdTableEntry)
+ return wrapError(
+ ExpectedEntityIdTableEntry.takeError(),
+ "failed to deserialize EntityIdTable from file '%s' at index %zu",
+ Path.str().c_str(), Index);
+
+ auto [EntityIt, EntityInserted] =
+ Entities.emplace(std::move(*ExpectedEntityIdTableEntry));
+ if (!EntityInserted) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntityIdTable from file '%s': "
+ "duplicate EntityName found at index %zu "
+ "(EntityId=%zu already exists in table)",
+ Path.str().c_str(), Index, getEntityIdIndex(EntityIt->second));
+ }
+ }
+
+ return IdTable;
+}
+
+llvm::json::Array
+JSONFormat::entityIdTableToJSON(const EntityIdTable &IdTable) const {
+ llvm::json::Array EntityIdTableArray;
+ const auto &Entities = getEntities(IdTable);
+ EntityIdTableArray.reserve(Entities.size());
+
+ for (const auto &[EntityName, EntityId] : Entities) {
+ llvm::json::Object Entry;
+ Entry["id"] = entityIdToJSON(EntityId);
+ Entry["name"] = entityNameToJSON(EntityName);
+ EntityIdTableArray.push_back(std::move(Entry));
+ }
+
+ return EntityIdTableArray;
+}
+
+//----------------------------------------------------------------------------
+// EntitySummary
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::unique_ptr<EntitySummary>>
+entitySummaryFromJSON(const llvm::json::Object &EntitySummaryObject,
+ llvm::StringRef Path) {
+ return llvm::createStringError(
+ std::errc::function_not_supported,
+ "EntitySummary deserialization from file '%s' is not yet implemented",
+ Path.str().c_str());
+}
+
+llvm::json::Object entitySummaryToJSON(const EntitySummary &ES) {
+ // TODO
+ llvm::json::Object Result;
+ return Result;
+}
+
+//----------------------------------------------------------------------------
+// SummaryData
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
+JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
+ llvm::StringRef Path) {
+ std::map<EntityId, std::unique_ptr<EntitySummary>> EntityDataMap;
+
+ size_t Index = 0;
+ for (const llvm::json::Value &EntityDataEntryValue : EntityDataArray) {
+ const llvm::json::Object *OptEntityDataEntryObject =
+ EntityDataEntryValue.getAsObject();
+ if (!OptEntityDataEntryObject) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize entity data map from file '%s': "
+ "element at index %zu is not a JSON object "
+ "(expected object with 'entity_id' and 'entity_summary' fields)",
+ Path.str().c_str(), Index);
+ }
+
+ const llvm::json::Value *EntityIdIntValue =
+ OptEntityDataEntryObject->get("entity_id");
+ if (!EntityIdIntValue) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize entity data map entry from file '%s' "
+ "at index %zu: missing required field 'entity_id' "
+ "(expected unsigned integer EntityId)",
+ Path.str().c_str(), Index);
+ }
+
+ const std::optional<uint64_t> OptEntityIdInt =
+ EntityIdIntValue->getAsUINT64();
+ if (!OptEntityIdInt) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize entity data map entry from file '%s' "
+ "at index %zu: field 'entity_id' is not a valid unsigned 64-bit "
+ "integer",
+ Path.str().c_str(), Index);
+ }
+
+ EntityId EI = entityIdFromJSON(*OptEntityIdInt);
+
+ const llvm::json::Object *OptEntitySummaryObject =
+ OptEntityDataEntryObject->getObject("entity_summary");
+ if (!OptEntitySummaryObject) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize entity data map entry from file '%s' "
+ "at index %zu: missing or invalid field 'entity_summary' "
+ "(expected EntitySummary JSON object)",
+ Path.str().c_str(), Index);
+ }
+
+ auto ExpectedEntitySummary =
+ entitySummaryFromJSON(*OptEntitySummaryObject, Path);
+
+ if (!ExpectedEntitySummary) {
+ return wrapError(
+ ExpectedEntitySummary.takeError(),
+ "failed to deserialize entity data map entry from file '%s' "
+ "at index %zu",
+ Path.str().c_str(), Index);
+ }
+
+ auto [DataIt, DataInserted] = EntityDataMap.insert(
+ {std::move(EI), std::move(*ExpectedEntitySummary)});
+ if (!DataInserted) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize entity data map from file '%s': "
+ "duplicate EntityId (%zu) found at index %zu",
+ Path.str().c_str(), getEntityIdIndex(DataIt->first), Index);
+ }
+
+ ++Index;
+ }
+
+ return EntityDataMap;
+}
+
+llvm::json::Array JSONFormat::entityDataMapToJSON(
+ const std::map<EntityId, std::unique_ptr<EntitySummary>> &EntityDataMap)
+ const {
+ llvm::json::Array Result;
+ Result.reserve(EntityDataMap.size());
+ for (const auto &[EntityId, EntitySummary] : EntityDataMap) {
+ llvm::json::Object Entry;
+ Entry["entity_id"] = entityIdToJSON(EntityId);
+ Entry["entity_summary"] = entitySummaryToJSON(*EntitySummary);
+ Result.push_back(std::move(Entry));
+ }
+ return Result;
+}
+
+llvm::Expected<
+ std::pair<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+JSONFormat::summaryDataMapEntryFromJSON(
+ const llvm::json::Object &SummaryDataObject, llvm::StringRef Path) {
+
+ std::optional<llvm::StringRef> OptSummaryNameStr =
+ SummaryDataObject.getString("summary_name");
+
+ if (!OptSummaryNameStr) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize summary data from file '%s': "
+ "missing required field 'summary_name' "
+ "(expected string identifier for the analysis summary)",
+ Path.str().c_str());
+ }
+
+ SummaryName SN = summaryNameFromJSON(*OptSummaryNameStr);
+
+ const llvm::json::Array *OptEntityDataArray =
+ SummaryDataObject.getArray("summary_data");
+ if (!OptEntityDataArray) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize summary data from file '%s' for summary '%s': "
+ "missing or invalid field 'summary_data' "
+ "(expected JSON array of entity summaries)",
+ Path.str().c_str(), SN.str().data());
+ }
+
+ auto ExpectedEntityDataMap = entityDataMapFromJSON(*OptEntityDataArray, Path);
+ if (!ExpectedEntityDataMap)
+ return wrapError(
+ ExpectedEntityDataMap.takeError(),
+ "failed to deserialize summary data from file '%s' for summary '%s'",
+ Path.str().c_str(), SN.str().data());
+
+ return std::make_pair(std::move(SN), std::move(*ExpectedEntityDataMap));
+}
+
+llvm::json::Object JSONFormat::summaryDataMapEntryToJSON(
+ const SummaryName &SN,
+ const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const {
+ llvm::json::Object Result;
+ Result["summary_name"] = summaryNameToJSON(SN);
+ Result["summary_data"] = entityDataMapToJSON(SD);
+ return Result;
+}
+
+//----------------------------------------------------------------------------
+// SummaryDataMap
+//----------------------------------------------------------------------------
+
+llvm::Expected<
+ std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+JSONFormat::readTUSummaryData(llvm::StringRef Path) {
+ if (!llvm::sys::fs::exists(Path)) {
+ return llvm::createStringError(
+ std::errc::no_such_file_or_directory,
+ "failed to read TUSummary data: directory does not exist: '%s'",
+ Path.str().c_str());
+ }
+
+ if (!llvm::sys::fs::is_directory(Path)) {
+ return llvm::createStringError(
+ std::errc::not_a_directory,
+ "failed to read TUSummary data: path is not a directory: '%s'",
+ Path.str().c_str());
+ }
+
+ std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>
+ Data;
+ std::error_code EC;
+
+ llvm::sys::fs::directory_iterator Dir(Path, EC);
+ if (EC) {
+ return llvm::createStringError(
+ EC, "failed to read TUSummary data: cannot iterate directory '%s'",
+ Path.str().c_str());
+ }
+
+ for (llvm::sys::fs::directory_iterator End; Dir != End && !EC;
+ Dir.increment(EC)) {
+ std::string SummaryPath = Dir->path();
+
+ auto ExpectedObject = readJSONObject(SummaryPath);
+ if (!ExpectedObject)
+ return wrapError(ExpectedObject.takeError(),
+ "failed to read TUSummary data from file '%s'",
+ SummaryPath.c_str());
+
+ auto ExpectedSummaryDataMap =
+ summaryDataMapEntryFromJSON(*ExpectedObject, SummaryPath);
+ if (!ExpectedSummaryDataMap)
+ return wrapError(ExpectedSummaryDataMap.takeError(),
+ "failed to read TUSummary data from file '%s'",
+ SummaryPath.c_str());
+
+ auto [SummaryIt, SummaryInserted] =
+ Data.emplace(std::move(*ExpectedSummaryDataMap));
+ if (!SummaryInserted) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to read TUSummary data from directory '%s': "
+ "duplicate SummaryName '%s' encountered in file '%s'",
+ Path.str().c_str(), SummaryIt->first.str().data(),
+ SummaryPath.c_str());
+ }
+ }
+
+ if (EC) {
+ return llvm::createStringError(EC,
+ "failed to read TUSummary data: "
+ "error during directory iteration of '%s'",
+ Path.str().c_str());
+ }
+
+ return Data;
+}
+
+std::string makeValidFilename(llvm::StringRef Name, size_t Prefix,
+ char Replacement) {
+ std::string Result;
+ llvm::raw_string_ostream OS(Result);
+ OS << llvm::format("%02d-%s", Prefix, Name.str().c_str());
+
+ for (size_t Index = 0; Index < Result.size(); ++Index) {
+ char &Actual = Result[Index];
+ if (llvm::isAlnum(Actual) || Actual == '-' || Actual == '_')
+ continue;
+ Actual = Replacement;
+ }
+
+ return Result;
+}
+
+llvm::Error JSONFormat::writeTUSummaryData(
+ const std::map<SummaryName,
+ std::map<EntityId, std::unique_ptr<EntitySummary>>> &Data,
+ llvm::StringRef Path) {
+ if (!llvm::sys::fs::exists(Path)) {
+ return llvm::createStringError(
+ std::errc::no_such_file_or_directory,
+ "failed to write TUSummary data: directory does not exist: '%s'",
+ Path.str().c_str());
+ }
+
+ if (!llvm::sys::fs::is_directory(Path)) {
+ return llvm::createStringError(
+ std::errc::not_a_directory,
+ "failed to write TUSummary data: path is not a directory: '%s'",
+ Path.str().c_str());
+ }
+
+ size_t Index = 0;
+ for (const auto &[SummaryName, DataMap] : Data) {
+ llvm::SmallString<kPathBufferSize> SummaryPath(Path);
+ llvm::sys::path::append(SummaryPath,
+ makeValidFilename(SummaryName.str(), Index, '_'));
+ llvm::sys::path::replace_extension(SummaryPath, ".json");
+
+ llvm::json::Object Result = summaryDataMapEntryToJSON(SummaryName, DataMap);
+ if (auto Error = writeJSON(std::move(Result), SummaryPath)) {
+ return wrapError(
+ std::move(Error), std::errc::io_error,
+ "failed to write TUSummary data to directory '%s': cannot write "
+ "summary '%s' to file '%s'",
+ Path.str().c_str(), SummaryName.str().data(),
+ SummaryPath.str().data());
+ }
+
+ ++Index;
+ }
+
+ return llvm::Error::success();
+}
+
+//----------------------------------------------------------------------------
+// TUSummary
+//----------------------------------------------------------------------------
+
+llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
+
+ // Populate TUNamespace field.
+ llvm::SmallString<kPathBufferSize> TUNamespacePath(Path);
+ llvm::sys::path::append(TUNamespacePath, TUSummaryTUNamespaceFilename);
+
+ auto ExpectedObject = readJSONObject(TUNamespacePath);
+ if (!ExpectedObject)
+ return wrapError(ExpectedObject.takeError(),
+ "failed to read TUSummary from '%s'", Path.str().c_str());
+
+ auto ExpectedTUNamespace =
+ buildNamespaceFromJSON(*ExpectedObject, TUNamespacePath);
+ if (!ExpectedTUNamespace)
+ return wrapError(ExpectedTUNamespace.takeError(),
+ "failed to read TUSummary from '%s'", Path.str().c_str());
+
+ TUSummary Summary(std::move(*ExpectedTUNamespace));
+
+ // Populate IdTable field.
+ {
+ llvm::SmallString<kPathBufferSize> IdTablePath(Path);
+ llvm::sys::path::append(IdTablePath, TUSummaryIdTableFilename);
+
+ auto ExpectedArray = readJSONArray(IdTablePath);
+ if (!ExpectedArray)
+ return wrapError(ExpectedArray.takeError(),
+ "failed to read TUSummary from '%s'",
+ Path.str().c_str());
+
+ auto ExpectedIdTable = entityIdTableFromJSON(*ExpectedArray, IdTablePath);
+ if (!ExpectedIdTable)
+ return wrapError(ExpectedIdTable.takeError(),
+ "failed to read TUSummary from '%s'",
+ Path.str().c_str());
+
+ getIdTableForDeserialization(Summary) = std::move(*ExpectedIdTable);
+ }
+
+ // Populate Data field.
+ {
+ llvm::SmallString<kPathBufferSize> DataPath(Path);
+ llvm::sys::path::append(DataPath, TUSummaryDataDirname);
+
+ if (!llvm::sys::fs::exists(DataPath)) {
+ return llvm::createStringError(std::errc::no_such_file_or_directory,
+ "failed to read TUSummary from '%s': "
+ "data directory does not exist: '%s'",
+ Path.str().c_str(), DataPath.str().data());
+ }
+
+ if (!llvm::sys::fs::is_directory(DataPath)) {
+ return llvm::createStringError(std::errc::not_a_directory,
+ "failed to read TUSummary from '%s': "
+ "data path is not a directory: '%s'",
+ Path.str().c_str(), DataPath.str().data());
+ }
+
+ auto ExpectedData = readTUSummaryData(DataPath);
+ if (!ExpectedData)
+ return wrapError(ExpectedData.takeError(),
+ "failed to read TUSummary from '%s'",
+ Path.str().c_str());
+
+ getDataForDeserialization(Summary) = std::move(*ExpectedData);
+ }
+
+ return Summary;
+}
+
+llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
+ llvm::StringRef Dir) {
+ // Serialize TUNamespace field.
+ {
+ llvm::SmallString<kPathBufferSize> TUNamespacePath(Dir);
+ llvm::sys::path::append(TUNamespacePath, TUSummaryTUNamespaceFilename);
+
+ llvm::json::Object BuildNamespaceObj =
+ buildNamespaceToJSON(getTUNamespace(S));
+ if (auto Error = writeJSON(std::move(BuildNamespaceObj), TUNamespacePath)) {
+ return wrapError(std::move(Error), std::errc::io_error,
+ "failed to write TUSummary to '%s': "
+ "cannot write TUNamespace file '%s'",
+ Dir.str().c_str(), TUNamespacePath.str().data());
+ }
+ }
+
+ // Serialize IdTable field.
+ {
+ llvm::SmallString<kPathBufferSize> IdTablePath(Dir);
+ llvm::sys::path::append(IdTablePath, TUSummaryIdTableFilename);
+
+ llvm::json::Array IdTableObj = entityIdTableToJSON(getIdTable(S));
+ if (auto Error = writeJSON(std::move(IdTableObj), IdTablePath)) {
+ return wrapError(std::move(Error), std::errc::io_error,
+ "failed to write TUSummary to '%s': "
+ "cannot write IdTable file '%s'",
+ Dir.str().c_str(), IdTablePath.str().data());
+ }
+ }
+
+ // Serialize Data field.
+ {
+ llvm::SmallString<kPathBufferSize> DataPath(Dir);
+ llvm::sys::path::append(DataPath, TUSummaryDataDirname);
+
+ // Create the data directory if it doesn't exist
+ if (std::error_code EC = llvm::sys::fs::create_directory(DataPath)) {
+ // If error is not "already exists", return error
+ if (EC != std::errc::file_exists) {
+ return llvm::createStringError(EC,
+ "failed to write TUSummary to '%s': "
+ "cannot create data directory '%s'",
+ Dir.str().c_str(),
+ DataPath.str().data());
+ }
+ }
+
+ // Verify it's a directory (could be a file with the same name)
+ if (llvm::sys::fs::exists(DataPath) &&
+ !llvm::sys::fs::is_directory(DataPath)) {
+ return llvm::createStringError(
+ std::errc::not_a_directory,
+ "failed to write TUSummary to '%s': data path exists but is not a "
+ "directory: '%s'",
+ Dir.str().c_str(), DataPath.str().data());
+ }
+
+ if (auto Error = writeTUSummaryData(getData(S), DataPath)) {
+ return wrapError(std::move(Error), std::errc::io_error,
+ "failed to write TUSummary to '%s': cannot write data",
+ Dir.str().c_str());
+ }
+ }
+
+ return llvm::Error::success();
+}
diff --git a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
index ee155d22afa9b..4e5dd47b7b17e 100644
--- a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
@@ -14,6 +14,24 @@
using namespace clang::ssaf;
+size_t SerializationFormat::getEntityIdIndex(const EntityId &EI) {
+ return EI.Index;
+}
+
+EntityId SerializationFormat::makeEntityId(const size_t Index) {
+ return EntityId(Index);
+}
+
+const std::map<EntityName, EntityId> &
+SerializationFormat::getEntities(const EntityIdTable &EIT) {
+ return EIT.Entities;
+}
+
+std::map<EntityName, EntityId> &
+SerializationFormat::getEntitiesForDeserialization(EntityIdTable &EIT) {
+ return EIT.Entities;
+}
+
EntityIdTable &SerializationFormat::getIdTableForDeserialization(TUSummary &S) {
return S.IdTable;
}
@@ -23,6 +41,11 @@ SerializationFormat::getTUNamespaceForDeserialization(TUSummary &S) {
return S.TUNamespace;
}
+std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>> &
+SerializationFormat::getDataForDeserialization(TUSummary &S) {
+ return S.Data;
+}
+
const EntityIdTable &SerializationFormat::getIdTable(const TUSummary &S) {
return S.IdTable;
}
@@ -31,6 +54,12 @@ const BuildNamespace &SerializationFormat::getTUNamespace(const TUSummary &S) {
return S.TUNamespace;
}
+const std::map<SummaryName,
+ std::map<EntityId, std::unique_ptr<EntitySummary>>> &
+SerializationFormat::getData(const TUSummary &S) {
+ return S.Data;
+}
+
BuildNamespaceKind
SerializationFormat::getBuildNamespaceKind(const BuildNamespace &BN) {
return BN.Kind;
diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt b/clang/unittests/Analysis/Scalable/CMakeLists.txt
index a21002e313ead..4732f5f600bcf 100644
--- a/clang/unittests/Analysis/Scalable/CMakeLists.txt
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -4,6 +4,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
EntityIdTest.cpp
EntityIdTableTest.cpp
EntityNameTest.cpp
+ JSONFormatTest.cpp
Registries/MockSummaryExtractor1.cpp
Registries/MockSummaryExtractor2.cpp
Registries/SummaryExtractorRegistryTest.cpp
diff --git a/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
new file mode 100644
index 0000000000000..db38ce23565da
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
@@ -0,0 +1,1127 @@
+//===- unittests/Analysis/Scalable/JSONFormatTest.cpp -------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
+#include "clang/Analysis/Scalable/Model/EntityId.h"
+#include "clang/Analysis/Scalable/Model/EntityIdTable.h"
+#include "clang/Analysis/Scalable/Model/EntityName.h"
+#include "clang/Analysis/Scalable/Model/SummaryName.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <fstream>
+
+using namespace clang::ssaf;
+using llvm::Failed;
+using llvm::Succeeded;
+using ::testing::AllOf;
+using ::testing::HasSubstr;
+
+namespace {
+
+// Helper function to check that an error message contains all specified
+// substrings
+::testing::Matcher<std::string>
+ContainsAllSubstrings(std::initializer_list<const char *> substrings) {
+ std::vector<::testing::Matcher<std::string>> matchers;
+ for (const char *substr : substrings) {
+ matchers.push_back(HasSubstr(substr));
+ }
+ return ::testing::AllOfArray(matchers);
+}
+
+//===----------------------------------------------------------------------===//
+// Test Fixtures and Helpers
+//===----------------------------------------------------------------------===//
+
+// Helper class to manage temporary directories for testing
+class TempDir {
+ llvm::SmallString<128> Path;
+ std::error_code EC;
+
+public:
+ TempDir() {
+ EC = llvm::sys::fs::createUniqueDirectory("JSONFormatTest", Path);
+ }
+
+ ~TempDir() {
+ if (!EC && llvm::sys::fs::exists(Path)) {
+ llvm::sys::fs::remove_directories(Path);
+ }
+ }
+
+ llvm::StringRef path() const { return Path; }
+ bool isValid() const { return !EC; }
+};
+
+// Helper function to write a file with content
+void writeFile(llvm::StringRef Path, llvm::StringRef Content) {
+ std::ofstream File(Path.str());
+ File << Content.str();
+ File.close();
+}
+
+// Helper function to create a directory
+void createDir(llvm::StringRef Path) {
+ llvm::sys::fs::create_directories(Path);
+}
+
+// Base test fixture for JSONFormat tests
+class JSONFormatTestBase : public ::testing::Test {
+protected:
+ JSONFormat Format;
+};
+
+//===----------------------------------------------------------------------===//
+// TUSummary Read Tests - TUNamespace
+//===----------------------------------------------------------------------===//
+
+class JSONFormatReadTUNamespaceTest : public JSONFormatTestBase {};
+
+TEST_F(JSONFormatReadTUNamespaceTest, NonexistentDirectory) {
+ auto Result = Format.readTUSummary("/nonexistent/path");
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from '/nonexistent/path'",
+ "failed to read JSON from file", "tu_namespace.json",
+ "failed to validate JSON file", "file does not exist"}));
+}
+
+TEST_F(JSONFormatReadTUNamespaceTest, MissingTUNamespaceFile) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg, ContainsAllSubstrings({"failed to read TUSummary from",
+ "failed to read JSON from file",
+ "tu_namespace.json",
+ "file does not exist"}));
+}
+
+TEST_F(JSONFormatReadTUNamespaceTest, InvalidJSONSyntax) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath, "{ invalid json }");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg, ContainsAllSubstrings({"failed to read TUSummary from",
+ "failed to read JSON from file",
+ "tu_namespace.json"}));
+}
+
+TEST_F(JSONFormatReadTUNamespaceTest, NotJSONObject) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath, "[]");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings({"failed to read TUSummary from",
+ "failed to read JSON object from file",
+ "tu_namespace.json"}));
+}
+
+TEST_F(JSONFormatReadTUNamespaceTest, MissingKindField) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath, R"({"name": "test.cpp"})");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(
+ ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize BuildNamespace from file",
+ "tu_namespace.json",
+ "missing required field 'kind' (expected BuildNamespaceKind)"}));
+}
+
+TEST_F(JSONFormatReadTUNamespaceTest, InvalidKindValue) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath, R"({"kind": "InvalidKind", "name": "test.cpp"})");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize BuildNamespace from file",
+ "tu_namespace.json",
+ "invalid 'kind' BuildNamespaceKind value 'InvalidKind'"}));
+}
+
+TEST_F(JSONFormatReadTUNamespaceTest, MissingNameField) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath, R"({"kind": "compilation_unit"})");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize BuildNamespace from file",
+ "tu_namespace.json", "missing required field 'name'"}));
+}
+
+//===----------------------------------------------------------------------===//
+// TUSummary Read Tests - IdTable
+//===----------------------------------------------------------------------===//
+
+class JSONFormatReadIdTableTest : public JSONFormatTestBase {
+protected:
+ void SetUpValidTUNamespace(TempDir &Dir) {
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath,
+ R"({"kind": "compilation_unit", "name": "test.cpp"})");
+ }
+};
+
+TEST_F(JSONFormatReadIdTableTest, MissingIdTableFile) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespace(Dir);
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings({"failed to read TUSummary from",
+ "failed to read JSON from file",
+ "id_table.json", "file does not exist"}));
+}
+
+TEST_F(JSONFormatReadIdTableTest, NotJSONArray) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespace(Dir);
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, "{}");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings({"failed to read TUSummary from",
+ "failed to read JSON array from file",
+ "id_table.json"}));
+}
+
+TEST_F(JSONFormatReadIdTableTest, ElementNotObject) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespace(Dir);
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, "[\"not an object\"]");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(
+ ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize EntityIdTable from file", "id_table.json",
+ "element at index 0 is not a JSON object",
+ "expected EntityIdTable entry with 'id' and 'name' fields"}));
+}
+
+TEST_F(JSONFormatReadIdTableTest, EntryMissingName) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespace(Dir);
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, R"([{"id": 0}])");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize EntityIdTable entry from file",
+ "id_table.json",
+ "missing or invalid field 'name' (expected EntityName JSON "
+ "object)"}));
+}
+
+TEST_F(JSONFormatReadIdTableTest, EntryMissingId) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespace(Dir);
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, R"([{
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+ }
+ }])");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize EntityIdTable entry from file",
+ "id_table.json",
+ "missing required field 'id' (expected unsigned integer "
+ "EntityId)"}));
+}
+
+TEST_F(JSONFormatReadIdTableTest, EntryIdNotInteger) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespace(Dir);
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, R"([{
+ "id": "not a number",
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+ }
+ }])");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(
+ ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize EntityIdTable entry from file",
+ "id_table.json", "field 'id' is not a valid unsigned 64-bit integer",
+ "expected non-negative EntityId value"}));
+}
+
+TEST_F(JSONFormatReadIdTableTest, DuplicateEntityName) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespace(Dir);
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, R"([
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+ }
+ }
+ ])");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize EntityIdTable from file",
+ "id_table.json", "duplicate EntityName found at index 1",
+ "EntityId=0 already exists in table"}));
+}
+
+//===----------------------------------------------------------------------===//
+// TUSummary Read Tests - EntityName
+//===----------------------------------------------------------------------===//
+
+class JSONFormatReadEntityNameTest : public JSONFormatTestBase {
+protected:
+ void SetUpValidTUNamespaceAndPartialIdTable(TempDir &Dir,
+ llvm::StringRef EntityNameJson) {
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath,
+ R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ std::string JsonContent = "[{\"id\": 0, \"name\": ";
+ JsonContent += EntityNameJson.str();
+ JsonContent += "}]";
+ writeFile(IdTablePath, JsonContent);
+ }
+};
+
+TEST_F(JSONFormatReadEntityNameTest, MissingUSR) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+ "suffix": "",
+ "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+ })");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(
+ ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize EntityName from file", "id_table.json",
+ "missing required field 'usr' (Unified Symbol Resolution string)"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, MissingSuffix) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+ "usr": "c:@F at foo",
+ "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+ })");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize EntityName from file",
+ "id_table.json", "missing required field 'suffix'"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, MissingNamespace) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+ "usr": "c:@F at foo",
+ "suffix": ""
+ })");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize EntityName from file",
+ "id_table.json", "missing or invalid field 'namespace'",
+ "expected JSON array of BuildNamespace objects"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, NamespaceNotArray) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": "not an array"
+ })");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize EntityName from file",
+ "id_table.json", "missing or invalid field 'namespace'",
+ "expected JSON array of BuildNamespace objects"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, NamespaceElementNotObject) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": ["not an object"]
+ })");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize NestedBuildNamespace from file",
+ "id_table.json", "element at index 0 is not a JSON object",
+ "expected BuildNamespace object"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, NamespaceMissingKind) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [{"name": "test.cpp"}]
+ })");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(
+ ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize NestedBuildNamespace from file",
+ "id_table.json", "at index 0",
+ "failed to deserialize BuildNamespace from file",
+ "missing required field 'kind' (expected BuildNamespaceKind)"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, NamespaceInvalidKind) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [{"kind": "InvalidKind", "name": "test.cpp"}]
+ })");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize NestedBuildNamespace from file",
+ "id_table.json", "at index 0",
+ "failed to deserialize BuildNamespace from file",
+ "invalid 'kind' BuildNamespaceKind value 'InvalidKind'"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, NamespaceMissingName) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [{"kind": "compilation_unit"}]
+ })");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize NestedBuildNamespace from file",
+ "id_table.json", "at index 0",
+ "failed to deserialize BuildNamespace from file",
+ "missing required field 'name'"}));
+}
+
+//===----------------------------------------------------------------------===//
+// TUSummary Read Tests - Data Directory and Files
+//===----------------------------------------------------------------------===//
+
+class JSONFormatReadDataTest : public JSONFormatTestBase {
+protected:
+ void SetUpValidTUNamespaceAndIdTable(TempDir &Dir) {
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath,
+ R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, "[]");
+ }
+};
+
+TEST_F(JSONFormatReadDataTest, MissingDataDirectory) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndIdTable(Dir);
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings({"failed to read TUSummary from",
+ "data directory does not exist"}));
+}
+
+TEST_F(JSONFormatReadDataTest, DataPathIsFile) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndIdTable(Dir);
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ writeFile(DataPath, "content");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings({"failed to read TUSummary from",
+ "data path is not a directory"}));
+}
+
+TEST_F(JSONFormatReadDataTest, NonJSONFileInDataDirectory) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndIdTable(Dir);
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ llvm::SmallString<128> SummaryPath(DataPath);
+ llvm::sys::path::append(SummaryPath, "summary.txt");
+ writeFile(SummaryPath, "{}");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to read TUSummary data from file", "summary.txt",
+ "failed to validate JSON file", "not a JSON file"}));
+}
+
+TEST_F(JSONFormatReadDataTest, FileNotJSONObject) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndIdTable(Dir);
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ llvm::SmallString<128> SummaryPath(DataPath);
+ llvm::sys::path::append(SummaryPath, "summary.json");
+ writeFile(SummaryPath, "[]");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings({"failed to read TUSummary from",
+ "failed to read TUSummary data from file",
+ "summary.json",
+ "failed to read JSON object from file"}));
+}
+
+TEST_F(JSONFormatReadDataTest, MissingSummaryName) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndIdTable(Dir);
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ llvm::SmallString<128> SummaryPath(DataPath);
+ llvm::sys::path::append(SummaryPath, "summary.json");
+ writeFile(SummaryPath, R"({"summary_data": []})");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize summary data from file",
+ "summary.json", "missing required field 'summary_name'",
+ "expected string identifier for the analysis summary"}));
+}
+
+TEST_F(JSONFormatReadDataTest, MissingSummaryData) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndIdTable(Dir);
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ llvm::SmallString<128> SummaryPath(DataPath);
+ llvm::sys::path::append(SummaryPath, "summary.json");
+ writeFile(SummaryPath, R"({"summary_name": "test"})");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg, ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize summary data from file",
+ "summary.json", "for summary 'test'",
+ "missing or invalid field 'summary_data'",
+ "expected JSON array of entity summaries"}));
+}
+
+TEST_F(JSONFormatReadDataTest, SummaryDataNotArray) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndIdTable(Dir);
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ llvm::SmallString<128> SummaryPath(DataPath);
+ llvm::sys::path::append(SummaryPath, "summary.json");
+ writeFile(SummaryPath,
+ R"({"summary_name": "test", "summary_data": "not an array"})");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg, ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize summary data from file",
+ "summary.json", "for summary 'test'",
+ "missing or invalid field 'summary_data'",
+ "expected JSON array of entity summaries"}));
+}
+
+TEST_F(JSONFormatReadDataTest, DuplicateSummaryName) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndIdTable(Dir);
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ llvm::SmallString<128> SummaryPath1(DataPath);
+ llvm::sys::path::append(SummaryPath1, "summary1.json");
+ writeFile(SummaryPath1, R"({"summary_name": "test", "summary_data": []})");
+
+ llvm::SmallString<128> SummaryPath2(DataPath);
+ llvm::sys::path::append(SummaryPath2, "summary2.json");
+ writeFile(SummaryPath2, R"({"summary_name": "test", "summary_data": []})");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(
+ ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from", "failed to read TUSummary data",
+ "duplicate SummaryName 'test' encountered in file"}));
+}
+
+//===----------------------------------------------------------------------===//
+// TUSummary Read Tests - Entity Data
+//===----------------------------------------------------------------------===//
+
+class JSONFormatReadEntityDataTest : public JSONFormatTestBase {
+protected:
+ void SetUpValidTUNamespaceIdTableAndDataDir(TempDir &Dir,
+ llvm::StringRef SummaryDataJson) {
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath,
+ R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, "[]");
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ llvm::SmallString<128> SummaryPath(DataPath);
+ llvm::sys::path::append(SummaryPath, "summary.json");
+ std::string JsonContent = R"({"summary_name": "test", "summary_data": )";
+ JsonContent += SummaryDataJson.str();
+ JsonContent += "}";
+ writeFile(SummaryPath, JsonContent);
+ }
+};
+
+TEST_F(JSONFormatReadEntityDataTest, ElementNotObject) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceIdTableAndDataDir(Dir, "[\"not an object\"]");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(
+ ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize entity data map from file", "summary.json",
+ "element at index 0 is not a JSON object",
+ "expected object with 'entity_id' and 'entity_summary' fields"}));
+}
+
+TEST_F(JSONFormatReadEntityDataTest, MissingEntityId) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceIdTableAndDataDir(Dir, R"([{"entity_summary": {}}])");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(
+ ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize entity data map entry from file",
+ "summary.json", "at index 0", "missing required field 'entity_id'",
+ "expected unsigned integer EntityId"}));
+}
+
+TEST_F(JSONFormatReadEntityDataTest, EntityIdNotInteger) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceIdTableAndDataDir(
+ Dir, R"([{"entity_id": "not a number", "entity_summary": {}}])");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(
+ ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize entity data map entry from file",
+ "summary.json", "at index 0",
+ "field 'entity_id' is not a valid unsigned 64-bit integer"}));
+}
+
+TEST_F(JSONFormatReadEntityDataTest, MissingEntitySummary) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceIdTableAndDataDir(Dir, R"([{"entity_id": 0}])");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize entity data map entry from file",
+ "summary.json", "at index 0",
+ "missing or invalid field 'entity_summary'",
+ "expected EntitySummary JSON object"}));
+}
+
+TEST_F(JSONFormatReadEntityDataTest, EntitySummaryNotObject) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceIdTableAndDataDir(
+ Dir, R"([{"entity_id": 0, "entity_summary": "not an object"}])");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize entity data map entry from file",
+ "summary.json", "at index 0",
+ "missing or invalid field 'entity_summary'",
+ "expected EntitySummary JSON object"}));
+}
+
+TEST_F(JSONFormatReadEntityDataTest,
+ EntitySummaryDeserializationNotImplemented) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceIdTableAndDataDir(
+ Dir, R"([{"entity_id": 0, "entity_summary": {}}])");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize entity data map entry from file",
+ "summary.json", "at index 0",
+ "EntitySummary deserialization from file",
+ "is not yet implemented"}));
+}
+
+// Note: DuplicateEntityId test cannot be implemented without EntitySummary
+// deserialization support, as the error occurs during EntitySummary parsing
+// before the duplicate check is reached.
+
+//===----------------------------------------------------------------------===//
+// TUSummary Write Tests
+//===----------------------------------------------------------------------===//
+
+class JSONFormatWriteTest : public JSONFormatTestBase {};
+
+TEST_F(JSONFormatWriteTest, Success) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ BuildNamespace TUNamespace = BuildNamespace::makeCompilationUnit("test.cpp");
+ TUSummary Summary(TUNamespace);
+
+ auto Error = Format.writeTUSummary(Summary, Dir.path());
+ EXPECT_THAT_ERROR(std::move(Error), Succeeded());
+
+ // Verify files were created
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ EXPECT_TRUE(llvm::sys::fs::exists(TUNamespacePath));
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ EXPECT_TRUE(llvm::sys::fs::exists(IdTablePath));
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ EXPECT_TRUE(llvm::sys::fs::exists(DataPath));
+ EXPECT_TRUE(llvm::sys::fs::is_directory(DataPath));
+}
+
+TEST_F(JSONFormatWriteTest, DataDirectoryExistsAsFile) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ // Create 'data' as a file instead of directory
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ writeFile(DataPath, "content");
+
+ BuildNamespace TUNamespace = BuildNamespace::makeCompilationUnit("test.cpp");
+ TUSummary Summary(TUNamespace);
+
+ auto Error = Format.writeTUSummary(Summary, Dir.path());
+ ASSERT_TRUE(!!Error);
+ std::string ErrorMsg = llvm::toString(std::move(Error));
+ EXPECT_THAT(ErrorMsg, ContainsAllSubstrings(
+ {"failed to write TUSummary to",
+ "data path exists but is not a directory"}));
+}
+
+//===----------------------------------------------------------------------===//
+// TUSummary Success Cases
+//===----------------------------------------------------------------------===//
+
+class JSONFormatSuccessTest : public JSONFormatTestBase {};
+
+TEST_F(JSONFormatSuccessTest, ReadWithEmptyIdTable) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath,
+ R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, "[]");
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ auto Result = Format.readTUSummary(Dir.path());
+ EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
+}
+
+TEST_F(JSONFormatSuccessTest, ReadWithNonEmptyIdTable) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath,
+ R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, R"([
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "usr": "c:@F at bar",
+ "suffix": "1",
+ "namespace": [
+ {"kind": "compilation_unit", "name": "test.cpp"},
+ {"kind": "link_unit", "name": "libtest.so"}
+ ]
+ }
+ }
+ ])");
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ auto Result = Format.readTUSummary(Dir.path());
+ EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
+}
+
+TEST_F(JSONFormatSuccessTest, ReadWithEmptyData) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath,
+ R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, "[]");
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ // Add an empty summary data file
+ llvm::SmallString<128> SummaryPath(DataPath);
+ llvm::sys::path::append(SummaryPath, "summary.json");
+ writeFile(SummaryPath, R"({"summary_name": "test", "summary_data": []})");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
+}
+
+TEST_F(JSONFormatSuccessTest, ReadWithLinkUnitNamespace) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath, R"({"kind": "link_unit", "name": "libtest.so"})");
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, "[]");
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ auto Result = Format.readTUSummary(Dir.path());
+ EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
+}
+
+//===----------------------------------------------------------------------===//
+// Round-Trip Tests
+//===----------------------------------------------------------------------===//
+
+class JSONFormatRoundTripTest : public JSONFormatTestBase {};
+
+TEST_F(JSONFormatRoundTripTest, EmptyIdTable) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ BuildNamespace TUNamespace = BuildNamespace::makeCompilationUnit("test.cpp");
+ TUSummary Summary(TUNamespace);
+
+ auto WriteError = Format.writeTUSummary(Summary, Dir.path());
+ EXPECT_THAT_ERROR(std::move(WriteError), Succeeded());
+
+ auto ReadResult = Format.readTUSummary(Dir.path());
+ EXPECT_THAT_ERROR(ReadResult.takeError(), Succeeded());
+}
+
+TEST_F(JSONFormatRoundTripTest, NonEmptyIdTable) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ // Manually create the files to test roundtrip
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath,
+ R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, R"([
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+ }
+ }
+ ])");
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ auto ReadResult = Format.readTUSummary(Dir.path());
+ ASSERT_THAT_EXPECTED(ReadResult, Succeeded());
+
+ TempDir Dir2;
+ ASSERT_TRUE(Dir2.isValid());
+
+ auto WriteError = Format.writeTUSummary(*ReadResult, Dir2.path());
+ EXPECT_THAT_ERROR(std::move(WriteError), Succeeded());
+
+ // Verify the written files
+ llvm::SmallString<128> TUNamespacePath2(Dir2.path());
+ llvm::sys::path::append(TUNamespacePath2, "tu_namespace.json");
+ EXPECT_TRUE(llvm::sys::fs::exists(TUNamespacePath2));
+}
+
+} // namespace
>From 47ed1daa2b4db92b08656ed509e95410e92d378d Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 5 Feb 2026 13:51:50 -0800
Subject: [PATCH 04/19] Fix return types, incorporate VFS APIs, and fix tests.
---
.../Scalable/Serialization/JSONFormat.h | 3 +-
.../Serialization/SerializationFormat.h | 21 +--
.../Scalable/Serialization/JSONFormat.cpp | 132 ++++++++++++------
.../Serialization/SerializationFormat.cpp | 40 ++----
.../Analysis/Scalable/JSONFormatTest.cpp | 2 +-
.../Registries/MockSerializationFormat.cpp | 46 +++---
.../Registries/MockSerializationFormat.h | 6 +-
.../SerializationFormatRegistryTest.cpp | 7 +-
8 files changed, 151 insertions(+), 106 deletions(-)
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
index 476853af0dce4..23e37138d6926 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -23,7 +23,8 @@ class SummaryName;
class JSONFormat : public SerializationFormat {
public:
- JSONFormat() = default;
+ explicit JSONFormat(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
+ : SerializationFormat(FS) {}
~JSONFormat() = default;
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index 1d3df0dc55e08..54098939cdfe4 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -42,21 +42,16 @@ class SerializationFormat {
static size_t getEntityIdIndex(const EntityId &EI);
static EntityId makeEntityId(const size_t Index);
- static const std::map<EntityName, EntityId> &
+ static const decltype(EntityIdTable::Entities) &
getEntities(const EntityIdTable &EIT);
- static std::map<EntityName, EntityId> &
- getEntitiesForDeserialization(EntityIdTable &EIT);
-
- static EntityIdTable &getIdTableForDeserialization(TUSummary &S);
- static BuildNamespace &getTUNamespaceForDeserialization(TUSummary &S);
- static std::map<SummaryName,
- std::map<EntityId, std::unique_ptr<EntitySummary>>> &
- getDataForDeserialization(TUSummary &S);
+ static decltype(EntityIdTable::Entities) &getEntities(EntityIdTable &EIT);
+
static const EntityIdTable &getIdTable(const TUSummary &S);
+ static EntityIdTable &getIdTable(TUSummary &S);
static const BuildNamespace &getTUNamespace(const TUSummary &S);
- static const std::map<SummaryName,
- std::map<EntityId, std::unique_ptr<EntitySummary>>> &
- getData(const TUSummary &S);
+ static BuildNamespace &getTUNamespace(TUSummary &S);
+ static const decltype(TUSummary::Data) &getData(const TUSummary &S);
+ static decltype(TUSummary::Data) &getData(TUSummary &S);
static BuildNamespaceKind getBuildNamespaceKind(const BuildNamespace &BN);
static llvm::StringRef getBuildNamespaceName(const BuildNamespace &BN);
@@ -67,8 +62,6 @@ class SerializationFormat {
static const llvm::SmallString<16> &getEntityNameSuffix(const EntityName &EN);
static const NestedBuildNamespace &
getEntityNameNamespace(const EntityName &EN);
- static decltype(TUSummary::Data) &getData(TUSummary &S);
- static const decltype(TUSummary::Data) &getData(const TUSummary &S);
public:
explicit SerializationFormat(
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index cca3ca36eb9ae..7a9d301c2791b 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -38,8 +38,8 @@ llvm::Error wrapError(llvm::Error E, const char *Fmt, Args &&...Vals) {
// JSON Reader and Writer
//----------------------------------------------------------------------------
-llvm::Error isJSONFile(llvm::StringRef Path) {
- if (!llvm::sys::fs::exists(Path))
+llvm::Error isJSONFile(llvm::vfs::FileSystem &FS, llvm::StringRef Path) {
+ if (!FS.exists(Path))
return llvm::createStringError(std::errc::no_such_file_or_directory,
"file does not exist: '%s'",
Path.str().c_str());
@@ -51,12 +51,13 @@ llvm::Error isJSONFile(llvm::StringRef Path) {
return llvm::Error::success();
}
-llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
- if (llvm::Error Err = isJSONFile(Path))
+llvm::Expected<llvm::json::Value> readJSON(llvm::vfs::FileSystem &FS,
+ llvm::StringRef Path) {
+ if (llvm::Error Err = isJSONFile(FS, Path))
return wrapError(std::move(Err), "failed to validate JSON file '%s'",
Path.str().c_str());
- auto BufferOrError = llvm::MemoryBuffer::getFile(Path);
+ auto BufferOrError = FS.getBufferForFile(Path);
if (!BufferOrError) {
return llvm::createStringError(BufferOrError.getError(),
"failed to read file '%s'",
@@ -66,8 +67,9 @@ llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
return llvm::json::parse(BufferOrError.get()->getBuffer());
}
-llvm::Expected<llvm::json::Object> readJSONObject(llvm::StringRef Path) {
- auto ExpectedJSON = readJSON(Path);
+llvm::Expected<llvm::json::Object> readJSONObject(llvm::vfs::FileSystem &FS,
+ llvm::StringRef Path) {
+ auto ExpectedJSON = readJSON(FS, Path);
if (!ExpectedJSON)
return wrapError(ExpectedJSON.takeError(),
"failed to read JSON from file '%s'", Path.str().c_str());
@@ -81,8 +83,9 @@ llvm::Expected<llvm::json::Object> readJSONObject(llvm::StringRef Path) {
return std::move(*Object);
}
-llvm::Expected<llvm::json::Array> readJSONArray(llvm::StringRef Path) {
- auto ExpectedJSON = readJSON(Path);
+llvm::Expected<llvm::json::Array> readJSONArray(llvm::vfs::FileSystem &FS,
+ llvm::StringRef Path) {
+ auto ExpectedJSON = readJSON(FS, Path);
if (!ExpectedJSON)
return wrapError(ExpectedJSON.takeError(),
"failed to read JSON from file '%s'", Path.str().c_str());
@@ -96,19 +99,28 @@ llvm::Expected<llvm::json::Array> readJSONArray(llvm::StringRef Path) {
return std::move(*Array);
}
-llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
+llvm::Error writeJSON(llvm::vfs::FileSystem &FS, llvm::json::Value &&Value,
+ llvm::StringRef Path) {
+ std::string Content;
+ llvm::raw_string_ostream OS(Content);
+ OS << llvm::formatv("{0:2}\n", Value);
+ OS.flush();
+
std::error_code EC;
- llvm::raw_fd_ostream OS(Path, EC);
+
+ // For real file system, we can use the atomic file approach
+ // For VFS, we need to write the content directly
+ llvm::raw_fd_ostream OutStream(Path, EC, llvm::sys::fs::OF_Text);
if (EC) {
return llvm::createStringError(EC, "failed to open '%s'",
Path.str().c_str());
}
- OS << llvm::formatv("{0:2}\n", Value);
+ OutStream << Content;
+ OutStream.flush();
- OS.flush();
- if (OS.has_error()) {
- return llvm::createStringError(OS.error(), "write failed");
+ if (OutStream.has_error()) {
+ return llvm::createStringError(OutStream.error(), "write failed");
}
return llvm::Error::success();
@@ -365,8 +377,7 @@ JSONFormat::entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray,
const size_t EntityCount = EntityIdTableArray.size();
EntityIdTable IdTable;
- std::map<EntityName, EntityId> &Entities =
- getEntitiesForDeserialization(IdTable);
+ std::map<EntityName, EntityId> &Entities = getEntities(IdTable);
for (size_t Index = 0; Index < EntityCount; ++Index) {
const llvm::json::Value &EntityIdTableEntryValue =
@@ -596,14 +607,22 @@ llvm::json::Object JSONFormat::summaryDataMapEntryToJSON(
llvm::Expected<
std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
JSONFormat::readTUSummaryData(llvm::StringRef Path) {
- if (!llvm::sys::fs::exists(Path)) {
+ if (!FS->exists(Path)) {
return llvm::createStringError(
std::errc::no_such_file_or_directory,
"failed to read TUSummary data: directory does not exist: '%s'",
Path.str().c_str());
}
- if (!llvm::sys::fs::is_directory(Path)) {
+ auto StatusOrErr = FS->status(Path);
+ if (!StatusOrErr) {
+ return llvm::createStringError(
+ StatusOrErr.getError(),
+ "failed to read TUSummary data: cannot get status of '%s'",
+ Path.str().c_str());
+ }
+
+ if (!StatusOrErr->isDirectory()) {
return llvm::createStringError(
std::errc::not_a_directory,
"failed to read TUSummary data: path is not a directory: '%s'",
@@ -614,18 +633,19 @@ JSONFormat::readTUSummaryData(llvm::StringRef Path) {
Data;
std::error_code EC;
- llvm::sys::fs::directory_iterator Dir(Path, EC);
+ auto DirIter = FS->dir_begin(Path, EC);
if (EC) {
return llvm::createStringError(
EC, "failed to read TUSummary data: cannot iterate directory '%s'",
Path.str().c_str());
}
- for (llvm::sys::fs::directory_iterator End; Dir != End && !EC;
- Dir.increment(EC)) {
- std::string SummaryPath = Dir->path();
+ for (llvm::vfs::directory_iterator End; DirIter != End && !EC;
+ DirIter.increment(EC)) {
+ llvm::StringRef SummaryPathRef = DirIter->path();
+ std::string SummaryPath = SummaryPathRef.str();
- auto ExpectedObject = readJSONObject(SummaryPath);
+ auto ExpectedObject = readJSONObject(*FS, SummaryPath);
if (!ExpectedObject)
return wrapError(ExpectedObject.takeError(),
"failed to read TUSummary data from file '%s'",
@@ -680,14 +700,22 @@ llvm::Error JSONFormat::writeTUSummaryData(
const std::map<SummaryName,
std::map<EntityId, std::unique_ptr<EntitySummary>>> &Data,
llvm::StringRef Path) {
- if (!llvm::sys::fs::exists(Path)) {
+ if (!FS->exists(Path)) {
return llvm::createStringError(
std::errc::no_such_file_or_directory,
"failed to write TUSummary data: directory does not exist: '%s'",
Path.str().c_str());
}
- if (!llvm::sys::fs::is_directory(Path)) {
+ auto StatusOrErr = FS->status(Path);
+ if (!StatusOrErr) {
+ return llvm::createStringError(
+ StatusOrErr.getError(),
+ "failed to write TUSummary data: cannot get status of '%s'",
+ Path.str().c_str());
+ }
+
+ if (!StatusOrErr->isDirectory()) {
return llvm::createStringError(
std::errc::not_a_directory,
"failed to write TUSummary data: path is not a directory: '%s'",
@@ -702,7 +730,7 @@ llvm::Error JSONFormat::writeTUSummaryData(
llvm::sys::path::replace_extension(SummaryPath, ".json");
llvm::json::Object Result = summaryDataMapEntryToJSON(SummaryName, DataMap);
- if (auto Error = writeJSON(std::move(Result), SummaryPath)) {
+ if (auto Error = writeJSON(*FS, std::move(Result), SummaryPath)) {
return wrapError(
std::move(Error), std::errc::io_error,
"failed to write TUSummary data to directory '%s': cannot write "
@@ -727,7 +755,7 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
llvm::SmallString<kPathBufferSize> TUNamespacePath(Path);
llvm::sys::path::append(TUNamespacePath, TUSummaryTUNamespaceFilename);
- auto ExpectedObject = readJSONObject(TUNamespacePath);
+ auto ExpectedObject = readJSONObject(*FS, TUNamespacePath);
if (!ExpectedObject)
return wrapError(ExpectedObject.takeError(),
"failed to read TUSummary from '%s'", Path.str().c_str());
@@ -745,7 +773,7 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
llvm::SmallString<kPathBufferSize> IdTablePath(Path);
llvm::sys::path::append(IdTablePath, TUSummaryIdTableFilename);
- auto ExpectedArray = readJSONArray(IdTablePath);
+ auto ExpectedArray = readJSONArray(*FS, IdTablePath);
if (!ExpectedArray)
return wrapError(ExpectedArray.takeError(),
"failed to read TUSummary from '%s'",
@@ -757,7 +785,7 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
"failed to read TUSummary from '%s'",
Path.str().c_str());
- getIdTableForDeserialization(Summary) = std::move(*ExpectedIdTable);
+ getIdTable(Summary) = std::move(*ExpectedIdTable);
}
// Populate Data field.
@@ -765,14 +793,22 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
llvm::SmallString<kPathBufferSize> DataPath(Path);
llvm::sys::path::append(DataPath, TUSummaryDataDirname);
- if (!llvm::sys::fs::exists(DataPath)) {
+ if (!FS->exists(DataPath)) {
return llvm::createStringError(std::errc::no_such_file_or_directory,
"failed to read TUSummary from '%s': "
"data directory does not exist: '%s'",
Path.str().c_str(), DataPath.str().data());
}
- if (!llvm::sys::fs::is_directory(DataPath)) {
+ auto StatusOrErr = FS->status(DataPath);
+ if (!StatusOrErr) {
+ return llvm::createStringError(StatusOrErr.getError(),
+ "failed to read TUSummary from '%s': "
+ "cannot get status of data path: '%s'",
+ Path.str().c_str(), DataPath.str().data());
+ }
+
+ if (!StatusOrErr->isDirectory()) {
return llvm::createStringError(std::errc::not_a_directory,
"failed to read TUSummary from '%s': "
"data path is not a directory: '%s'",
@@ -785,7 +821,7 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
"failed to read TUSummary from '%s'",
Path.str().c_str());
- getDataForDeserialization(Summary) = std::move(*ExpectedData);
+ getData(Summary) = std::move(*ExpectedData);
}
return Summary;
@@ -800,7 +836,8 @@ llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
llvm::json::Object BuildNamespaceObj =
buildNamespaceToJSON(getTUNamespace(S));
- if (auto Error = writeJSON(std::move(BuildNamespaceObj), TUNamespacePath)) {
+ if (auto Error =
+ writeJSON(*FS, std::move(BuildNamespaceObj), TUNamespacePath)) {
return wrapError(std::move(Error), std::errc::io_error,
"failed to write TUSummary to '%s': "
"cannot write TUNamespace file '%s'",
@@ -814,7 +851,7 @@ llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
llvm::sys::path::append(IdTablePath, TUSummaryIdTableFilename);
llvm::json::Array IdTableObj = entityIdTableToJSON(getIdTable(S));
- if (auto Error = writeJSON(std::move(IdTableObj), IdTablePath)) {
+ if (auto Error = writeJSON(*FS, std::move(IdTableObj), IdTablePath)) {
return wrapError(std::move(Error), std::errc::io_error,
"failed to write TUSummary to '%s': "
"cannot write IdTable file '%s'",
@@ -828,6 +865,8 @@ llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
llvm::sys::path::append(DataPath, TUSummaryDataDirname);
// Create the data directory if it doesn't exist
+ // Use the real filesystem for directory creation as VFS doesn't always
+ // support this
if (std::error_code EC = llvm::sys::fs::create_directory(DataPath)) {
// If error is not "already exists", return error
if (EC != std::errc::file_exists) {
@@ -840,13 +879,22 @@ llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
}
// Verify it's a directory (could be a file with the same name)
- if (llvm::sys::fs::exists(DataPath) &&
- !llvm::sys::fs::is_directory(DataPath)) {
- return llvm::createStringError(
- std::errc::not_a_directory,
- "failed to write TUSummary to '%s': data path exists but is not a "
- "directory: '%s'",
- Dir.str().c_str(), DataPath.str().data());
+ if (FS->exists(DataPath)) {
+ auto StatusOrErr = FS->status(DataPath);
+ if (!StatusOrErr) {
+ return llvm::createStringError(StatusOrErr.getError(),
+ "failed to write TUSummary to '%s': "
+ "cannot get status of data path '%s'",
+ Dir.str().c_str(),
+ DataPath.str().data());
+ }
+ if (!StatusOrErr->isDirectory()) {
+ return llvm::createStringError(
+ std::errc::not_a_directory,
+ "failed to write TUSummary to '%s': data path exists but is not a "
+ "directory: '%s'",
+ Dir.str().c_str(), DataPath.str().data());
+ }
}
if (auto Error = writeTUSummaryData(getData(S), DataPath)) {
diff --git a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
index 1208780510b18..02f64f112761a 100644
--- a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
@@ -26,31 +26,21 @@ EntityId SerializationFormat::makeEntityId(const size_t Index) {
return EntityId(Index);
}
-const std::map<EntityName, EntityId> &
+const decltype(EntityIdTable::Entities) &
SerializationFormat::getEntities(const EntityIdTable &EIT) {
return EIT.Entities;
}
-std::map<EntityName, EntityId> &
-SerializationFormat::getEntitiesForDeserialization(EntityIdTable &EIT) {
+decltype(EntityIdTable::Entities) &
+SerializationFormat::getEntities(EntityIdTable &EIT) {
return EIT.Entities;
}
-EntityIdTable &SerializationFormat::getIdTableForDeserialization(TUSummary &S) {
+const EntityIdTable &SerializationFormat::getIdTable(const TUSummary &S) {
return S.IdTable;
}
-BuildNamespace &
-SerializationFormat::getTUNamespaceForDeserialization(TUSummary &S) {
- return S.TUNamespace;
-}
-
-std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>> &
-SerializationFormat::getDataForDeserialization(TUSummary &S) {
- return S.Data;
-}
-
-const EntityIdTable &SerializationFormat::getIdTable(const TUSummary &S) {
+EntityIdTable &SerializationFormat::getIdTable(TUSummary &S) {
return S.IdTable;
}
@@ -58,12 +48,19 @@ const BuildNamespace &SerializationFormat::getTUNamespace(const TUSummary &S) {
return S.TUNamespace;
}
-const std::map<SummaryName,
- std::map<EntityId, std::unique_ptr<EntitySummary>>> &
+BuildNamespace &SerializationFormat::getTUNamespace(TUSummary &S) {
+ return S.TUNamespace;
+}
+
+const decltype(TUSummary::Data) &
SerializationFormat::getData(const TUSummary &S) {
return S.Data;
}
+decltype(TUSummary::Data) &SerializationFormat::getData(TUSummary &S) {
+ return S.Data;
+}
+
BuildNamespaceKind
SerializationFormat::getBuildNamespaceKind(const BuildNamespace &BN) {
return BN.Kind;
@@ -92,12 +89,3 @@ const NestedBuildNamespace &
SerializationFormat::getEntityNameNamespace(const EntityName &EN) {
return EN.Namespace;
}
-
-const decltype(TUSummary::Data) &
-SerializationFormat::getData(const TUSummary &S) {
- return S.Data;
-}
-
-decltype(TUSummary::Data) &SerializationFormat::getData(TUSummary &S) {
- return S.Data;
-}
diff --git a/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
index db38ce23565da..eb2ed77976bda 100644
--- a/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
+++ b/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
@@ -78,7 +78,7 @@ void createDir(llvm::StringRef Path) {
// Base test fixture for JSONFormat tests
class JSONFormatTestBase : public ::testing::Test {
protected:
- JSONFormat Format;
+ JSONFormat Format{llvm::vfs::getRealFileSystem()};
};
//===----------------------------------------------------------------------===//
diff --git a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
index 44904d53d2412..2ff75c9c5b1c3 100644
--- a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
@@ -40,12 +40,16 @@ MockSerializationFormat::MockSerializationFormat(
}
}
-TUSummary MockSerializationFormat::readTUSummary(llvm::StringRef Path) {
+llvm::Expected<TUSummary>
+MockSerializationFormat::readTUSummary(llvm::StringRef Path) {
BuildNamespace NS(BuildNamespaceKind::CompilationUnit, "Mock.cpp");
TUSummary Summary(NS);
auto ManifestFile = FS->getBufferForFile(Path + "/analyses.txt");
- assert(ManifestFile); // TODO Handle error.
+ if (!ManifestFile) {
+ return llvm::createStringError(ManifestFile.getError(),
+ "Failed to read manifest file");
+ }
llvm::StringRef ManifestFileContent = (*ManifestFile)->getBuffer();
llvm::SmallVector<llvm::StringRef, 5> Analyses;
@@ -55,20 +59,24 @@ TUSummary MockSerializationFormat::readTUSummary(llvm::StringRef Path) {
for (llvm::StringRef Analysis : Analyses) {
SummaryName Name(Analysis.str());
auto InputFile = FS->getBufferForFile(Path + "/" + Name.str() + ".special");
- assert(InputFile);
+ if (!InputFile) {
+ return llvm::createStringError(InputFile.getError(),
+ "Failed to read analysis file");
+ }
auto InfoIt = FormatInfos.find(Name);
if (InfoIt == FormatInfos.end()) {
- llvm::report_fatal_error(
+ return llvm::createStringError(
+ std::make_error_code(std::errc::invalid_argument),
"No FormatInfo was registered for summary name: " + Name.str());
}
const auto &InfoEntry = InfoIt->second;
assert(InfoEntry.ForSummary == Name);
SpecialFileRepresentation Repr{(*InputFile)->getBuffer().str()};
- auto &Table = getIdTableForDeserialization(Summary);
+ auto &Table = getIdTable(Summary);
std::unique_ptr<EntitySummary> Result = InfoEntry.Deserialize(Repr, Table);
- if (!Result) // TODO: Handle error.
+ if (!Result)
continue;
EntityId FooId = Table.getId(EntityName{"c:@F at foo", "", /*Namespace=*/{}});
@@ -81,16 +89,16 @@ TUSummary MockSerializationFormat::readTUSummary(llvm::StringRef Path) {
return Summary;
}
-void MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
- llvm::StringRef OutputDir) {
+llvm::Error MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
+ llvm::StringRef OutputDir) {
std::error_code EC;
// Check if output directory exists, create if needed
if (!llvm::sys::fs::exists(OutputDir)) {
EC = llvm::sys::fs::create_directories(OutputDir);
if (EC) {
- llvm::report_fatal_error("Failed to create output directory '" +
- OutputDir + "': " + EC.message());
+ return llvm::createStringError(EC, "Failed to create output directory '" +
+ OutputDir + "': " + EC.message());
}
}
@@ -101,9 +109,10 @@ void MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
for (const auto &Data : llvm::make_second_range(EntityMappings)) {
auto InfoIt = FormatInfos.find(SummaryName);
if (InfoIt == FormatInfos.end()) {
- llvm::report_fatal_error(
+ return llvm::createStringError(
+ std::make_error_code(std::errc::invalid_argument),
"There was no FormatInfo registered for summary name '" +
- SummaryName.str() + "'");
+ SummaryName.str() + "'");
}
const auto &InfoEntry = InfoIt->second;
assert(InfoEntry.ForSummary == SummaryName);
@@ -114,8 +123,9 @@ void MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
(OutputDir + "/" + SummaryName.str() + ".special").str();
llvm::raw_fd_ostream AnalysisOutputFile(AnalysisFilePath, EC);
if (EC) {
- llvm::report_fatal_error("Failed to create file '" + AnalysisFilePath +
- "': " + llvm::StringRef(EC.message()));
+ return llvm::createStringError(
+ EC, "Failed to create file '" + AnalysisFilePath +
+ "': " + llvm::StringRef(EC.message()));
}
AnalysisOutputFile << Output.MockRepresentation;
}
@@ -124,14 +134,16 @@ void MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
std::string ManifestFilePath = (OutputDir + "/analyses.txt").str();
llvm::raw_fd_ostream ManifestFile(ManifestFilePath, EC);
if (EC) {
- llvm::report_fatal_error("Failed to create manifest file '" +
- ManifestFilePath +
- "': " + llvm::StringRef(EC.message()));
+ return llvm::createStringError(
+ EC, "Failed to create manifest file '" + ManifestFilePath +
+ "': " + llvm::StringRef(EC.message()));
}
interleave(map_range(Analyses, std::mem_fn(&SummaryName::str)), ManifestFile,
"\n");
ManifestFile << "\n";
+
+ return llvm::Error::success();
}
static SerializationFormatRegistry::Add<MockSerializationFormat>
diff --git a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h
index a106e53fc20ac..c8b146821a91f 100644
--- a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h
+++ b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h
@@ -21,10 +21,10 @@ class MockSerializationFormat final : public SerializationFormat {
explicit MockSerializationFormat(
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS);
- TUSummary readTUSummary(llvm::StringRef Path) override;
+ llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) override;
- void writeTUSummary(const TUSummary &Summary,
- llvm::StringRef OutputDir) override;
+ llvm::Error writeTUSummary(const TUSummary &Summary,
+ llvm::StringRef OutputDir) override;
struct SpecialFileRepresentation {
std::string MockRepresentation;
diff --git a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
index 484c38309e3d8..79a4adf218aac 100644
--- a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
@@ -68,7 +68,9 @@ TEST(SerializationFormatRegistryTest, Roundtrip) {
makeFormat(Inputs, "MockSerializationFormat");
ASSERT_TRUE(Format);
- TUSummary LoadedSummary = Format->readTUSummary("input");
+ auto LoadedSummaryOrErr = Format->readTUSummary("input");
+ ASSERT_TRUE(!!LoadedSummaryOrErr);
+ TUSummary LoadedSummary = std::move(*LoadedSummaryOrErr);
// Create a temporary output directory
SmallString<128> OutputDir;
@@ -77,7 +79,8 @@ TEST(SerializationFormatRegistryTest, Roundtrip) {
llvm::scope_exit CleanupOnExit(
[&] { sys::fs::remove_directories(OutputDir); });
- Format->writeTUSummary(LoadedSummary, OutputDir);
+ auto WriteErr = Format->writeTUSummary(LoadedSummary, OutputDir);
+ ASSERT_FALSE(!!WriteErr);
EXPECT_EQ(readFilesFromDir(OutputDir),
(std::map<std::string, std::string>{
>From b45cf5fc2bcb870c68d582ec731fc481b3b956f1 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 12:22:53 -0800
Subject: [PATCH 05/19] Fix JSONFormat to use a single file and add tests
---
.../Scalable/Serialization/JSONFormat.h | 82 ++-
.../Scalable/Serialization/JSONFormat.cpp | 640 ++++++------------
.../error-callgraph-invalid-caller.test | 8 +
.../error-callgraph-missing-field.test | 8 +
.../error-data-element-not-object.test | 6 +
.../error-data-entry-missing-data.test | 8 +
.../error-data-missing-summary-name.test | 7 +
.../Serialization/error-data-not-array.test | 7 +
.../error-defuse-invalid-use.test | 8 +
.../error-defuse-missing-field.test | 8 +
.../Serialization/error-duplicate-entity.test | 8 +
.../error-duplicate-summary-name.test | 6 +
.../error-entity-data-element-not-object.test | 8 +
.../error-entity-data-missing-entity-id.test | 9 +
...or-entity-data-missing-entity-summary.test | 9 +
.../error-entity-id-not-uint64.test | 8 +
.../error-entity-name-missing-namespace.test | 9 +
.../error-entity-name-missing-suffix.test | 8 +
.../error-entity-name-missing-usr.test | 9 +
.../error-entity-summary-no-format-info.test | 7 +
.../error-id-table-element-not-object.test | 8 +
.../error-id-table-entry-id-not-uint64.test | 8 +
.../error-id-table-entry-missing-id.test | 8 +
.../error-id-table-entry-missing-name.test | 8 +
.../error-id-table-not-array.test | 7 +
.../Serialization/error-invalid-kind.test | 7 +
.../Serialization/error-invalid-syntax.test | 6 +
.../Serialization/error-missing-data.test | 7 +
.../Serialization/error-missing-id-table.test | 7 +
.../Serialization/error-missing-kind.test | 8 +
.../Serialization/error-missing-name.test | 7 +
.../error-missing-tu-namespace.test | 7 +
.../error-namespace-element-not-object.test | 10 +
.../Serialization/error-nonexistent-file.test | 6 +
.../error-not-json-extension.test | 8 +
.../Serialization/error-not-object.test | 6 +
.../Serialization/valid-callgraph.test | 9 +
.../Scalable/Serialization/valid-defuse.test | 10 +
.../Scalable/Serialization/valid-empty.test | 4 +
.../Serialization/valid-link-unit.test | 4 +
.../valid-with-empty-data-entry.test | 4 +
.../Serialization/valid-with-idtable.test | 4 +
clang/test/lit.cfg.py | 1 +
.../Analysis/Scalable/CMakeLists.txt | 3 +
.../Analysis/Scalable/tools/CMakeLists.txt | 1 +
.../CMakeLists.txt | 9 +
.../ExampleAnalyses.cpp | 303 +++++++++
.../SSAFSerializationFormatTest.cpp | 84 +++
48 files changed, 975 insertions(+), 442 deletions(-)
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-not-array.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-not-array.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-data.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-id-table.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-name.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-tu-namespace.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-nonexistent-file.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-not-object.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/valid-callgraph.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/valid-defuse.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/valid-empty.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/valid-link-unit.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/valid-with-empty-data-entry.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/valid-with-idtable.test
create mode 100644 clang/unittests/Analysis/Scalable/tools/CMakeLists.txt
create mode 100644 clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/CMakeLists.txt
create mode 100644 clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
create mode 100644 clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
index 23e37138d6926..ac809cd00e0cd 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -14,17 +14,34 @@
#define CLANG_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_H
#include "clang/Analysis/Scalable/Serialization/SerializationFormat.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/Support/JSON.h"
namespace clang::ssaf {
class EntitySummary;
+class EntityIdTable;
class SummaryName;
class JSONFormat : public SerializationFormat {
public:
- explicit JSONFormat(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
- : SerializationFormat(FS) {}
+ // Helper class to provide limited access to EntityId conversion methods
+ // Only exposes EntityId serialization/deserialization to format handlers
+ class EntityIdConverter {
+ public:
+ EntityId fromJSON(uint64_t EntityIdIndex) const {
+ return Format.entityIdFromJSON(EntityIdIndex);
+ }
+
+ uint64_t toJSON(EntityId EI) const { return Format.entityIdToJSON(EI); }
+
+ private:
+ friend class JSONFormat;
+ EntityIdConverter(const JSONFormat &Format) : Format(Format) {}
+ const JSONFormat &Format;
+ };
+
+ explicit JSONFormat(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS);
~JSONFormat() = default;
@@ -33,44 +50,65 @@ class JSONFormat : public SerializationFormat {
llvm::Error writeTUSummary(const TUSummary &Summary,
llvm::StringRef OutputDir) override;
+ using SerializerFn = llvm::function_ref<llvm::json::Object(
+ const EntitySummary &, const EntityIdConverter &)>;
+ using DeserializerFn =
+ llvm::function_ref<llvm::Expected<std::unique_ptr<EntitySummary>>(
+ const llvm::json::Object &, EntityIdTable &,
+ const EntityIdConverter &)>;
+
+ using FormatInfo = FormatInfoEntry<SerializerFn, DeserializerFn>;
+ std::map<SummaryName, FormatInfo> FormatInfos;
+
private:
- EntityId entityIdFromJSON(const uint64_t EntityIdIndex);
+ EntityId entityIdFromJSON(const uint64_t EntityIdIndex) const;
uint64_t entityIdToJSON(EntityId EI) const;
+ llvm::Expected<BuildNamespaceKind>
+ buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr);
+
+ llvm::Expected<BuildNamespace>
+ buildNamespaceFromJSON(const llvm::json::Object &BuildNamespaceObject);
llvm::json::Object buildNamespaceToJSON(const BuildNamespace &BN) const;
+
+ llvm::Expected<NestedBuildNamespace> nestedBuildNamespaceFromJSON(
+ const llvm::json::Array &NestedBuildNamespaceArray);
llvm::json::Array
nestedBuildNamespaceToJSON(const NestedBuildNamespace &NBN) const;
+
+ llvm::Expected<EntityName>
+ entityNameFromJSON(const llvm::json::Object &EntityNameObject);
llvm::json::Object entityNameToJSON(const EntityName &EN) const;
- llvm::Expected<std::pair<EntityName, EntityId>>
- entityIdTableEntryFromJSON(const llvm::json::Object &EntityIdTableEntryObject,
- llvm::StringRef Path);
+
+ llvm::Expected<std::pair<EntityName, EntityId>> entityIdTableEntryFromJSON(
+ const llvm::json::Object &EntityIdTableEntryObject);
llvm::Expected<EntityIdTable>
- entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray,
- llvm::StringRef Path);
+ entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray);
llvm::json::Array entityIdTableToJSON(const EntityIdTable &IdTable) const;
+ llvm::Expected<std::unique_ptr<EntitySummary>>
+ entitySummaryFromJSON(const SummaryName &SN,
+ const llvm::json::Object &EntitySummaryObject,
+ EntityIdTable &IdTable);
+ llvm::json::Object entitySummaryToJSON(const SummaryName &SN,
+ const EntitySummary &ES) const;
+
llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
- entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
- llvm::StringRef Path);
- llvm::json::Array entityDataMapToJSON(
- const std::map<EntityId, std::unique_ptr<EntitySummary>> &EntityDataMap)
- const;
+ entityDataMapFromJSON(const SummaryName &SN,
+ const llvm::json::Array &EntityDataArray,
+ EntityIdTable &IdTable);
+ llvm::json::Array
+ entityDataMapToJSON(const SummaryName &SN,
+ const std::map<EntityId, std::unique_ptr<EntitySummary>>
+ &EntityDataMap) const;
llvm::Expected<std::pair<SummaryName,
std::map<EntityId, std::unique_ptr<EntitySummary>>>>
summaryDataMapEntryFromJSON(const llvm::json::Object &SummaryDataObject,
- llvm::StringRef Path);
+ EntityIdTable &IdTable);
llvm::json::Object summaryDataMapEntryToJSON(
const SummaryName &SN,
const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const;
-
- llvm::Expected<
- std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
- readTUSummaryData(llvm::StringRef Path);
- llvm::Error writeTUSummaryData(
- const std::map<SummaryName,
- std::map<EntityId, std::unique_ptr<EntitySummary>>> &Data,
- llvm::StringRef Path);
};
} // namespace clang::ssaf
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index 7a9d301c2791b..1d3376ef2c9f2 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -7,15 +7,11 @@
#include "llvm/Support/JSON.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
+#include "llvm/Support/Registry.h"
using namespace clang::ssaf;
namespace {
-constexpr size_t kPathBufferSize = 128;
-constexpr const char *TUSummaryTUNamespaceFilename = "tu_namespace.json";
-constexpr const char *TUSummaryIdTableFilename = "id_table.json";
-constexpr const char *TUSummaryDataDirname = "data";
-
// Helper to wrap an error with additional context
template <typename... Args>
llvm::Error wrapError(llvm::Error E, std::errc ErrorCode, const char *Fmt,
@@ -38,8 +34,8 @@ llvm::Error wrapError(llvm::Error E, const char *Fmt, Args &&...Vals) {
// JSON Reader and Writer
//----------------------------------------------------------------------------
-llvm::Error isJSONFile(llvm::vfs::FileSystem &FS, llvm::StringRef Path) {
- if (!FS.exists(Path))
+llvm::Error isJSONFile(llvm::StringRef Path) {
+ if (!llvm::sys::fs::exists(Path))
return llvm::createStringError(std::errc::no_such_file_or_directory,
"file does not exist: '%s'",
Path.str().c_str());
@@ -51,13 +47,12 @@ llvm::Error isJSONFile(llvm::vfs::FileSystem &FS, llvm::StringRef Path) {
return llvm::Error::success();
}
-llvm::Expected<llvm::json::Value> readJSON(llvm::vfs::FileSystem &FS,
- llvm::StringRef Path) {
- if (llvm::Error Err = isJSONFile(FS, Path))
+llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
+ if (llvm::Error Err = isJSONFile(Path))
return wrapError(std::move(Err), "failed to validate JSON file '%s'",
Path.str().c_str());
- auto BufferOrError = FS.getBufferForFile(Path);
+ auto BufferOrError = llvm::MemoryBuffer::getFile(Path);
if (!BufferOrError) {
return llvm::createStringError(BufferOrError.getError(),
"failed to read file '%s'",
@@ -67,12 +62,12 @@ llvm::Expected<llvm::json::Value> readJSON(llvm::vfs::FileSystem &FS,
return llvm::json::parse(BufferOrError.get()->getBuffer());
}
-llvm::Expected<llvm::json::Object> readJSONObject(llvm::vfs::FileSystem &FS,
- llvm::StringRef Path) {
- auto ExpectedJSON = readJSON(FS, Path);
+llvm::Expected<llvm::json::Object> readJSONObject(llvm::StringRef Path) {
+ auto ExpectedJSON = readJSON(Path);
if (!ExpectedJSON)
return wrapError(ExpectedJSON.takeError(),
- "failed to read JSON from file '%s'", Path.str().c_str());
+ "failed to read JSON object from file '%s'",
+ Path.str().c_str());
llvm::json::Object *Object = ExpectedJSON->getAsObject();
if (!Object) {
@@ -83,40 +78,15 @@ llvm::Expected<llvm::json::Object> readJSONObject(llvm::vfs::FileSystem &FS,
return std::move(*Object);
}
-llvm::Expected<llvm::json::Array> readJSONArray(llvm::vfs::FileSystem &FS,
- llvm::StringRef Path) {
- auto ExpectedJSON = readJSON(FS, Path);
- if (!ExpectedJSON)
- return wrapError(ExpectedJSON.takeError(),
- "failed to read JSON from file '%s'", Path.str().c_str());
-
- llvm::json::Array *Array = ExpectedJSON->getAsArray();
- if (!Array) {
- return llvm::createStringError(std::errc::invalid_argument,
- "failed to read JSON array from file '%s'",
- Path.str().c_str());
- }
- return std::move(*Array);
-}
-
-llvm::Error writeJSON(llvm::vfs::FileSystem &FS, llvm::json::Value &&Value,
- llvm::StringRef Path) {
- std::string Content;
- llvm::raw_string_ostream OS(Content);
- OS << llvm::formatv("{0:2}\n", Value);
- OS.flush();
-
+llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
std::error_code EC;
-
- // For real file system, we can use the atomic file approach
- // For VFS, we need to write the content directly
llvm::raw_fd_ostream OutStream(Path, EC, llvm::sys::fs::OF_Text);
if (EC) {
return llvm::createStringError(EC, "failed to open '%s'",
Path.str().c_str());
}
- OutStream << Content;
+ OutStream << llvm::formatv("{0:2}\n", Value);
OutStream.flush();
if (OutStream.has_error()) {
@@ -126,16 +96,33 @@ llvm::Error writeJSON(llvm::vfs::FileSystem &FS, llvm::json::Value &&Value,
return llvm::Error::success();
}
+//----------------------------------------------------------------------------
+// JSONFormat Constructor
+//----------------------------------------------------------------------------
+
+JSONFormat::JSONFormat(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
+ : SerializationFormat(FS) {
+ for (const auto &FormatInfoEntry : llvm::Registry<FormatInfo>::entries()) {
+ std::unique_ptr<FormatInfo> Info = FormatInfoEntry.instantiate();
+ bool Inserted = FormatInfos.try_emplace(Info->ForSummary, *Info).second;
+ if (!Inserted) {
+ llvm::report_fatal_error(
+ "Format info was already registered for summary name: " +
+ Info->ForSummary.str());
+ }
+ }
+}
+
//----------------------------------------------------------------------------
// EntityId
//----------------------------------------------------------------------------
-EntityId JSONFormat::entityIdFromJSON(const uint64_t EntityIdIndex) {
- return makeEntityId(static_cast<size_t>(EntityIdIndex));
+EntityId JSONFormat::entityIdFromJSON(const uint64_t EntityIdIndex) const {
+ return SerializationFormat::makeEntityId(static_cast<size_t>(EntityIdIndex));
}
uint64_t JSONFormat::entityIdToJSON(EntityId EI) const {
- return static_cast<uint64_t>(getEntityIdIndex(EI));
+ return static_cast<uint64_t>(SerializationFormat::getEntityIdIndex(EI));
}
//----------------------------------------------------------------------------
@@ -143,14 +130,13 @@ uint64_t JSONFormat::entityIdToJSON(EntityId EI) const {
//----------------------------------------------------------------------------
llvm::Expected<BuildNamespaceKind>
-buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr,
- llvm::StringRef Path) {
+JSONFormat::buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr) {
auto OptBuildNamespaceKind = parseBuildNamespaceKind(BuildNamespaceKindStr);
if (!OptBuildNamespaceKind) {
return llvm::createStringError(
std::errc::invalid_argument,
- "invalid 'kind' BuildNamespaceKind value '%s' in file '%s'",
- BuildNamespaceKindStr.str().c_str(), Path.str().c_str());
+ "invalid 'kind' BuildNamespaceKind value '%s'",
+ BuildNamespaceKindStr.str().c_str());
}
return *OptBuildNamespaceKind;
@@ -164,32 +150,26 @@ llvm::StringRef buildNamespaceKindToJSON(BuildNamespaceKind BNK) {
// BuildNamespace
//----------------------------------------------------------------------------
-llvm::Expected<BuildNamespace>
-buildNamespaceFromJSON(const llvm::json::Object &BuildNamespaceObject,
- llvm::StringRef Path) {
+llvm::Expected<BuildNamespace> JSONFormat::buildNamespaceFromJSON(
+ const llvm::json::Object &BuildNamespaceObject) {
auto OptBuildNamespaceKindStr = BuildNamespaceObject.getString("kind");
if (!OptBuildNamespaceKindStr) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize BuildNamespace from file '%s': "
- "missing required field 'kind' (expected BuildNamespaceKind)",
- Path.str().c_str());
+ "failed to deserialize BuildNamespace: "
+ "missing required field 'kind' (expected BuildNamespaceKind)");
}
- auto ExpectedKind =
- buildNamespaceKindFromJSON(*OptBuildNamespaceKindStr, Path);
+ auto ExpectedKind = buildNamespaceKindFromJSON(*OptBuildNamespaceKindStr);
if (!ExpectedKind)
return wrapError(ExpectedKind.takeError(),
- "failed to deserialize BuildNamespace from file '%s'",
- Path.str().c_str());
+ "failed to deserialize BuildNamespace");
auto OptNameStr = BuildNamespaceObject.getString("name");
if (!OptNameStr) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize BuildNamespace from file '%s': "
- "missing required field 'name'",
- Path.str().c_str());
+ return llvm::createStringError(std::errc::invalid_argument,
+ "failed to deserialize BuildNamespace: "
+ "missing required field 'name'");
}
return {BuildNamespace(*ExpectedKind, *OptNameStr)};
@@ -207,9 +187,8 @@ JSONFormat::buildNamespaceToJSON(const BuildNamespace &BN) const {
// NestedBuildNamespace
//----------------------------------------------------------------------------
-llvm::Expected<NestedBuildNamespace>
-nestedBuildNamespaceFromJSON(const llvm::json::Array &NestedBuildNamespaceArray,
- llvm::StringRef Path) {
+llvm::Expected<NestedBuildNamespace> JSONFormat::nestedBuildNamespaceFromJSON(
+ const llvm::json::Array &NestedBuildNamespaceArray) {
std::vector<BuildNamespace> Namespaces;
size_t NamespaceCount = NestedBuildNamespaceArray.size();
@@ -223,20 +202,17 @@ nestedBuildNamespaceFromJSON(const llvm::json::Array &NestedBuildNamespaceArray,
if (!BuildNamespaceObject) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize NestedBuildNamespace from file '%s': "
+ "failed to deserialize NestedBuildNamespace: "
"element at index %zu is not a JSON object "
"(expected BuildNamespace object)",
- Path.str().c_str(), Index);
+ Index);
}
- auto ExpectedBuildNamespace =
- buildNamespaceFromJSON(*BuildNamespaceObject, Path);
+ auto ExpectedBuildNamespace = buildNamespaceFromJSON(*BuildNamespaceObject);
if (!ExpectedBuildNamespace)
return wrapError(
ExpectedBuildNamespace.takeError(),
- "failed to deserialize NestedBuildNamespace from file '%s' "
- "at index %zu",
- Path.str().c_str(), Index);
+ "failed to deserialize NestedBuildNamespace at index %zu", Index);
Namespaces.push_back(std::move(*ExpectedBuildNamespace));
}
@@ -262,24 +238,20 @@ JSONFormat::nestedBuildNamespaceToJSON(const NestedBuildNamespace &NBN) const {
//----------------------------------------------------------------------------
llvm::Expected<EntityName>
-entityNameFromJSON(const llvm::json::Object &EntityNameObject,
- llvm::StringRef Path) {
+JSONFormat::entityNameFromJSON(const llvm::json::Object &EntityNameObject) {
const auto OptUSR = EntityNameObject.getString("usr");
if (!OptUSR) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize EntityName from file '%s': "
- "missing required field 'usr' (Unified Symbol Resolution string)",
- Path.str().c_str());
+ "failed to deserialize EntityName: "
+ "missing required field 'usr' (Unified Symbol Resolution string)");
}
const auto OptSuffix = EntityNameObject.getString("suffix");
if (!OptSuffix) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize EntityName from file '%s': "
- "missing required field 'suffix'",
- Path.str().c_str());
+ return llvm::createStringError(std::errc::invalid_argument,
+ "failed to deserialize EntityName: "
+ "missing required field 'suffix'");
}
const llvm::json::Array *OptNamespaceArray =
@@ -287,18 +259,15 @@ entityNameFromJSON(const llvm::json::Object &EntityNameObject,
if (!OptNamespaceArray) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize EntityName from file '%s': "
+ "failed to deserialize EntityName: "
"missing or invalid field 'namespace' "
- "(expected JSON array of BuildNamespace objects)",
- Path.str().c_str());
+ "(expected JSON array of BuildNamespace objects)");
}
- auto ExpectedNamespace =
- nestedBuildNamespaceFromJSON(*OptNamespaceArray, Path);
+ auto ExpectedNamespace = nestedBuildNamespaceFromJSON(*OptNamespaceArray);
if (!ExpectedNamespace)
return wrapError(ExpectedNamespace.takeError(),
- "failed to deserialize EntityName from file '%s'",
- Path.str().c_str());
+ "failed to deserialize EntityName");
return EntityName{*OptUSR, *OptSuffix, std::move(*ExpectedNamespace)};
}
@@ -327,32 +296,29 @@ llvm::StringRef summaryNameToJSON(const SummaryName &SN) { return SN.str(); }
llvm::Expected<std::pair<EntityName, EntityId>>
JSONFormat::entityIdTableEntryFromJSON(
- const llvm::json::Object &EntityIdTableEntryObject, llvm::StringRef Path) {
+ const llvm::json::Object &EntityIdTableEntryObject) {
const llvm::json::Object *OptEntityNameObject =
EntityIdTableEntryObject.getObject("name");
if (!OptEntityNameObject) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize EntityIdTable entry from file '%s': "
- "missing or invalid field 'name' (expected EntityName JSON object)",
- Path.str().c_str());
+ "failed to deserialize EntityIdTable entry: "
+ "missing or invalid field 'name' (expected EntityName JSON object)");
}
- auto ExpectedEntityName = entityNameFromJSON(*OptEntityNameObject, Path);
+ auto ExpectedEntityName = entityNameFromJSON(*OptEntityNameObject);
if (!ExpectedEntityName)
return wrapError(ExpectedEntityName.takeError(),
- "failed to deserialize EntityIdTable entry from file '%s'",
- Path.str().c_str());
+ "failed to deserialize EntityIdTable entry");
const llvm::json::Value *EntityIdIntValue =
EntityIdTableEntryObject.get("id");
if (!EntityIdIntValue) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize EntityIdTable entry from file '%s': "
- "missing required field 'id' (expected unsigned integer EntityId)",
- Path.str().c_str());
+ "failed to deserialize EntityIdTable entry: "
+ "missing required field 'id' (expected unsigned integer EntityId)");
}
const std::optional<uint64_t> OptEntityIdInt =
@@ -360,10 +326,9 @@ JSONFormat::entityIdTableEntryFromJSON(
if (!OptEntityIdInt) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize EntityIdTable entry from file '%s': "
+ "failed to deserialize EntityIdTable entry: "
"field 'id' is not a valid unsigned 64-bit integer "
- "(expected non-negative EntityId value)",
- Path.str().c_str());
+ "(expected non-negative EntityId value)");
}
EntityId EI = entityIdFromJSON(*OptEntityIdInt);
@@ -372,8 +337,7 @@ JSONFormat::entityIdTableEntryFromJSON(
}
llvm::Expected<EntityIdTable>
-JSONFormat::entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray,
- llvm::StringRef Path) {
+JSONFormat::entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray) {
const size_t EntityCount = EntityIdTableArray.size();
EntityIdTable IdTable;
@@ -389,29 +353,27 @@ JSONFormat::entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray,
if (!OptEntityIdTableEntryObject) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize EntityIdTable from file '%s': "
+ "failed to deserialize EntityIdTable: "
"element at index %zu is not a JSON object "
"(expected EntityIdTable entry with 'id' and 'name' fields)",
- Path.str().c_str(), Index);
+ Index);
}
auto ExpectedEntityIdTableEntry =
- entityIdTableEntryFromJSON(*OptEntityIdTableEntryObject, Path);
+ entityIdTableEntryFromJSON(*OptEntityIdTableEntryObject);
if (!ExpectedEntityIdTableEntry)
- return wrapError(
- ExpectedEntityIdTableEntry.takeError(),
- "failed to deserialize EntityIdTable from file '%s' at index %zu",
- Path.str().c_str(), Index);
+ return wrapError(ExpectedEntityIdTableEntry.takeError(),
+ "failed to deserialize EntityIdTable at index %zu",
+ Index);
auto [EntityIt, EntityInserted] =
Entities.emplace(std::move(*ExpectedEntityIdTableEntry));
if (!EntityInserted) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize EntityIdTable from file '%s': "
- "duplicate EntityName found at index %zu "
- "(EntityId=%zu already exists in table)",
- Path.str().c_str(), Index, getEntityIdIndex(EntityIt->second));
+ return llvm::createStringError(std::errc::invalid_argument,
+ "failed to deserialize EntityIdTable: "
+ "duplicate EntityName found at index %zu "
+ "(EntityId=%zu already exists in table)",
+ Index, getEntityIdIndex(EntityIt->second));
}
}
@@ -439,18 +401,39 @@ JSONFormat::entityIdTableToJSON(const EntityIdTable &IdTable) const {
//----------------------------------------------------------------------------
llvm::Expected<std::unique_ptr<EntitySummary>>
-entitySummaryFromJSON(const llvm::json::Object &EntitySummaryObject,
- llvm::StringRef Path) {
- return llvm::createStringError(
- std::errc::function_not_supported,
- "EntitySummary deserialization from file '%s' is not yet implemented",
- Path.str().c_str());
+JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
+ const llvm::json::Object &EntitySummaryObject,
+ EntityIdTable &IdTable) {
+ auto InfoIt = FormatInfos.find(SN);
+ if (InfoIt == FormatInfos.end()) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntitySummary: "
+ "no FormatInfo was registered for summary name: %s",
+ SN.str().data());
+ }
+ const auto &InfoEntry = InfoIt->second;
+ assert(InfoEntry.ForSummary == SN);
+
+ EntityIdConverter Converter(*this);
+ return InfoEntry.Deserialize(EntitySummaryObject, IdTable, Converter);
}
-llvm::json::Object entitySummaryToJSON(const EntitySummary &ES) {
- // TODO
- llvm::json::Object Result;
- return Result;
+llvm::json::Object
+JSONFormat::entitySummaryToJSON(const SummaryName &SN,
+ const EntitySummary &ES) const {
+ auto InfoIt = FormatInfos.find(SN);
+ if (InfoIt == FormatInfos.end()) {
+ llvm::report_fatal_error(
+ "Failed to serialize EntitySummary: no FormatInfo was registered for "
+ "summary name: " +
+ SN.str());
+ }
+ const auto &InfoEntry = InfoIt->second;
+ assert(InfoEntry.ForSummary == SN);
+
+ EntityIdConverter Converter(*this);
+ return InfoEntry.Serialize(ES, Converter);
}
//----------------------------------------------------------------------------
@@ -458,8 +441,9 @@ llvm::json::Object entitySummaryToJSON(const EntitySummary &ES) {
//----------------------------------------------------------------------------
llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
-JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
- llvm::StringRef Path) {
+JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
+ const llvm::json::Array &EntityDataArray,
+ EntityIdTable &IdTable) {
std::map<EntityId, std::unique_ptr<EntitySummary>> EntityDataMap;
size_t Index = 0;
@@ -469,10 +453,10 @@ JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
if (!OptEntityDataEntryObject) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize entity data map from file '%s': "
+ "failed to deserialize entity data map: "
"element at index %zu is not a JSON object "
"(expected object with 'entity_id' and 'entity_summary' fields)",
- Path.str().c_str(), Index);
+ Index);
}
const llvm::json::Value *EntityIdIntValue =
@@ -480,10 +464,10 @@ JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
if (!EntityIdIntValue) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize entity data map entry from file '%s' "
+ "failed to deserialize entity data map entry "
"at index %zu: missing required field 'entity_id' "
"(expected unsigned integer EntityId)",
- Path.str().c_str(), Index);
+ Index);
}
const std::optional<uint64_t> OptEntityIdInt =
@@ -491,10 +475,10 @@ JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
if (!OptEntityIdInt) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize entity data map entry from file '%s' "
+ "failed to deserialize entity data map entry "
"at index %zu: field 'entity_id' is not a valid unsigned 64-bit "
"integer",
- Path.str().c_str(), Index);
+ Index);
}
EntityId EI = entityIdFromJSON(*OptEntityIdInt);
@@ -504,21 +488,19 @@ JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
if (!OptEntitySummaryObject) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize entity data map entry from file '%s' "
+ "failed to deserialize entity data map entry "
"at index %zu: missing or invalid field 'entity_summary' "
"(expected EntitySummary JSON object)",
- Path.str().c_str(), Index);
+ Index);
}
auto ExpectedEntitySummary =
- entitySummaryFromJSON(*OptEntitySummaryObject, Path);
+ entitySummaryFromJSON(SN, *OptEntitySummaryObject, IdTable);
if (!ExpectedEntitySummary) {
return wrapError(
ExpectedEntitySummary.takeError(),
- "failed to deserialize entity data map entry from file '%s' "
- "at index %zu",
- Path.str().c_str(), Index);
+ "failed to deserialize entity data map entry at index %zu", Index);
}
auto [DataIt, DataInserted] = EntityDataMap.insert(
@@ -526,9 +508,9 @@ JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
if (!DataInserted) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize entity data map from file '%s': "
+ "failed to deserialize entity data map: "
"duplicate EntityId (%zu) found at index %zu",
- Path.str().c_str(), getEntityIdIndex(DataIt->first), Index);
+ getEntityIdIndex(DataIt->first), Index);
}
++Index;
@@ -538,6 +520,7 @@ JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
}
llvm::json::Array JSONFormat::entityDataMapToJSON(
+ const SummaryName &SN,
const std::map<EntityId, std::unique_ptr<EntitySummary>> &EntityDataMap)
const {
llvm::json::Array Result;
@@ -545,7 +528,7 @@ llvm::json::Array JSONFormat::entityDataMapToJSON(
for (const auto &[EntityId, EntitySummary] : EntityDataMap) {
llvm::json::Object Entry;
Entry["entity_id"] = entityIdToJSON(EntityId);
- Entry["entity_summary"] = entitySummaryToJSON(*EntitySummary);
+ Entry["entity_summary"] = entitySummaryToJSON(SN, *EntitySummary);
Result.push_back(std::move(Entry));
}
return Result;
@@ -554,7 +537,7 @@ llvm::json::Array JSONFormat::entityDataMapToJSON(
llvm::Expected<
std::pair<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
JSONFormat::summaryDataMapEntryFromJSON(
- const llvm::json::Object &SummaryDataObject, llvm::StringRef Path) {
+ const llvm::json::Object &SummaryDataObject, EntityIdTable &IdTable) {
std::optional<llvm::StringRef> OptSummaryNameStr =
SummaryDataObject.getString("summary_name");
@@ -562,31 +545,30 @@ JSONFormat::summaryDataMapEntryFromJSON(
if (!OptSummaryNameStr) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize summary data from file '%s': "
+ "failed to deserialize summary data: "
"missing required field 'summary_name' "
- "(expected string identifier for the analysis summary)",
- Path.str().c_str());
+ "(expected string identifier for the analysis summary)");
}
SummaryName SN = summaryNameFromJSON(*OptSummaryNameStr);
const llvm::json::Array *OptEntityDataArray =
- SummaryDataObject.getArray("summary_data");
+ SummaryDataObject.getArray("data");
if (!OptEntityDataArray) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize summary data from file '%s' for summary '%s': "
- "missing or invalid field 'summary_data' "
+ "failed to deserialize summary data for summary '%s': "
+ "missing or invalid field 'data' "
"(expected JSON array of entity summaries)",
- Path.str().c_str(), SN.str().data());
+ SN.str().data());
}
- auto ExpectedEntityDataMap = entityDataMapFromJSON(*OptEntityDataArray, Path);
+ auto ExpectedEntityDataMap =
+ entityDataMapFromJSON(SN, *OptEntityDataArray, IdTable);
if (!ExpectedEntityDataMap)
- return wrapError(
- ExpectedEntityDataMap.takeError(),
- "failed to deserialize summary data from file '%s' for summary '%s'",
- Path.str().c_str(), SN.str().data());
+ return wrapError(ExpectedEntityDataMap.takeError(),
+ "failed to deserialize summary data for summary '%s'",
+ SN.str().data());
return std::make_pair(std::move(SN), std::move(*ExpectedEntityDataMap));
}
@@ -596,190 +578,53 @@ llvm::json::Object JSONFormat::summaryDataMapEntryToJSON(
const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const {
llvm::json::Object Result;
Result["summary_name"] = summaryNameToJSON(SN);
- Result["summary_data"] = entityDataMapToJSON(SD);
+ Result["data"] = entityDataMapToJSON(SN, SD);
return Result;
}
//----------------------------------------------------------------------------
-// SummaryDataMap
+// TUSummary
//----------------------------------------------------------------------------
-llvm::Expected<
- std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
-JSONFormat::readTUSummaryData(llvm::StringRef Path) {
- if (!FS->exists(Path)) {
- return llvm::createStringError(
- std::errc::no_such_file_or_directory,
- "failed to read TUSummary data: directory does not exist: '%s'",
- Path.str().c_str());
- }
-
- auto StatusOrErr = FS->status(Path);
- if (!StatusOrErr) {
- return llvm::createStringError(
- StatusOrErr.getError(),
- "failed to read TUSummary data: cannot get status of '%s'",
- Path.str().c_str());
- }
-
- if (!StatusOrErr->isDirectory()) {
- return llvm::createStringError(
- std::errc::not_a_directory,
- "failed to read TUSummary data: path is not a directory: '%s'",
- Path.str().c_str());
- }
-
- std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>
- Data;
- std::error_code EC;
-
- auto DirIter = FS->dir_begin(Path, EC);
- if (EC) {
- return llvm::createStringError(
- EC, "failed to read TUSummary data: cannot iterate directory '%s'",
- Path.str().c_str());
- }
-
- for (llvm::vfs::directory_iterator End; DirIter != End && !EC;
- DirIter.increment(EC)) {
- llvm::StringRef SummaryPathRef = DirIter->path();
- std::string SummaryPath = SummaryPathRef.str();
-
- auto ExpectedObject = readJSONObject(*FS, SummaryPath);
- if (!ExpectedObject)
- return wrapError(ExpectedObject.takeError(),
- "failed to read TUSummary data from file '%s'",
- SummaryPath.c_str());
-
- auto ExpectedSummaryDataMap =
- summaryDataMapEntryFromJSON(*ExpectedObject, SummaryPath);
- if (!ExpectedSummaryDataMap)
- return wrapError(ExpectedSummaryDataMap.takeError(),
- "failed to read TUSummary data from file '%s'",
- SummaryPath.c_str());
-
- auto [SummaryIt, SummaryInserted] =
- Data.emplace(std::move(*ExpectedSummaryDataMap));
- if (!SummaryInserted) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to read TUSummary data from directory '%s': "
- "duplicate SummaryName '%s' encountered in file '%s'",
- Path.str().c_str(), SummaryIt->first.str().data(),
- SummaryPath.c_str());
- }
- }
-
- if (EC) {
- return llvm::createStringError(EC,
- "failed to read TUSummary data: "
- "error during directory iteration of '%s'",
- Path.str().c_str());
- }
-
- return Data;
-}
-
-std::string makeValidFilename(llvm::StringRef Name, size_t Prefix,
- char Replacement) {
- std::string Result;
- llvm::raw_string_ostream OS(Result);
- OS << llvm::format("%02d-%s", Prefix, Name.str().c_str());
-
- for (size_t Index = 0; Index < Result.size(); ++Index) {
- char &Actual = Result[Index];
- if (llvm::isAlnum(Actual) || Actual == '-' || Actual == '_')
- continue;
- Actual = Replacement;
- }
-
- return Result;
-}
-
-llvm::Error JSONFormat::writeTUSummaryData(
- const std::map<SummaryName,
- std::map<EntityId, std::unique_ptr<EntitySummary>>> &Data,
- llvm::StringRef Path) {
- if (!FS->exists(Path)) {
- return llvm::createStringError(
- std::errc::no_such_file_or_directory,
- "failed to write TUSummary data: directory does not exist: '%s'",
- Path.str().c_str());
- }
+llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
+ // Read the JSON object from the file
+ auto ExpectedRootObject = readJSONObject(Path);
+ if (!ExpectedRootObject)
+ return wrapError(ExpectedRootObject.takeError(),
+ "failed to read TUSummary from '%s'", Path.str().c_str());
- auto StatusOrErr = FS->status(Path);
- if (!StatusOrErr) {
- return llvm::createStringError(
- StatusOrErr.getError(),
- "failed to write TUSummary data: cannot get status of '%s'",
- Path.str().c_str());
- }
+ const llvm::json::Object &RootObject = *ExpectedRootObject;
- if (!StatusOrErr->isDirectory()) {
+ // Parse TUNamespace field
+ const llvm::json::Object *TUNamespaceObject =
+ RootObject.getObject("tu_namespace");
+ if (!TUNamespaceObject) {
return llvm::createStringError(
- std::errc::not_a_directory,
- "failed to write TUSummary data: path is not a directory: '%s'",
+ std::errc::invalid_argument,
+ "failed to read TUSummary from '%s': "
+ "missing or invalid field 'tu_namespace' (expected JSON object)",
Path.str().c_str());
}
- size_t Index = 0;
- for (const auto &[SummaryName, DataMap] : Data) {
- llvm::SmallString<kPathBufferSize> SummaryPath(Path);
- llvm::sys::path::append(SummaryPath,
- makeValidFilename(SummaryName.str(), Index, '_'));
- llvm::sys::path::replace_extension(SummaryPath, ".json");
-
- llvm::json::Object Result = summaryDataMapEntryToJSON(SummaryName, DataMap);
- if (auto Error = writeJSON(*FS, std::move(Result), SummaryPath)) {
- return wrapError(
- std::move(Error), std::errc::io_error,
- "failed to write TUSummary data to directory '%s': cannot write "
- "summary '%s' to file '%s'",
- Path.str().c_str(), SummaryName.str().data(),
- SummaryPath.str().data());
- }
-
- ++Index;
- }
-
- return llvm::Error::success();
-}
-
-//----------------------------------------------------------------------------
-// TUSummary
-//----------------------------------------------------------------------------
-
-llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
-
- // Populate TUNamespace field.
- llvm::SmallString<kPathBufferSize> TUNamespacePath(Path);
- llvm::sys::path::append(TUNamespacePath, TUSummaryTUNamespaceFilename);
-
- auto ExpectedObject = readJSONObject(*FS, TUNamespacePath);
- if (!ExpectedObject)
- return wrapError(ExpectedObject.takeError(),
- "failed to read TUSummary from '%s'", Path.str().c_str());
-
- auto ExpectedTUNamespace =
- buildNamespaceFromJSON(*ExpectedObject, TUNamespacePath);
+ auto ExpectedTUNamespace = buildNamespaceFromJSON(*TUNamespaceObject);
if (!ExpectedTUNamespace)
return wrapError(ExpectedTUNamespace.takeError(),
"failed to read TUSummary from '%s'", Path.str().c_str());
TUSummary Summary(std::move(*ExpectedTUNamespace));
- // Populate IdTable field.
+ // Parse IdTable field
{
- llvm::SmallString<kPathBufferSize> IdTablePath(Path);
- llvm::sys::path::append(IdTablePath, TUSummaryIdTableFilename);
-
- auto ExpectedArray = readJSONArray(*FS, IdTablePath);
- if (!ExpectedArray)
- return wrapError(ExpectedArray.takeError(),
- "failed to read TUSummary from '%s'",
- Path.str().c_str());
+ const llvm::json::Array *IdTableArray = RootObject.getArray("id_table");
+ if (!IdTableArray) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to read TUSummary from '%s': "
+ "missing or invalid field 'id_table' (expected JSON array)",
+ Path.str().c_str());
+ }
- auto ExpectedIdTable = entityIdTableFromJSON(*ExpectedArray, IdTablePath);
+ auto ExpectedIdTable = entityIdTableFromJSON(*IdTableArray);
if (!ExpectedIdTable)
return wrapError(ExpectedIdTable.takeError(),
"failed to read TUSummary from '%s'",
@@ -788,120 +633,75 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
getIdTable(Summary) = std::move(*ExpectedIdTable);
}
- // Populate Data field.
+ // Parse data field
{
- llvm::SmallString<kPathBufferSize> DataPath(Path);
- llvm::sys::path::append(DataPath, TUSummaryDataDirname);
-
- if (!FS->exists(DataPath)) {
- return llvm::createStringError(std::errc::no_such_file_or_directory,
- "failed to read TUSummary from '%s': "
- "data directory does not exist: '%s'",
- Path.str().c_str(), DataPath.str().data());
+ const llvm::json::Array *SummaryDataArray = RootObject.getArray("data");
+ if (!SummaryDataArray) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to read TUSummary from '%s': "
+ "missing or invalid field 'data' (expected JSON array)",
+ Path.str().c_str());
}
- auto StatusOrErr = FS->status(DataPath);
- if (!StatusOrErr) {
- return llvm::createStringError(StatusOrErr.getError(),
- "failed to read TUSummary from '%s': "
- "cannot get status of data path: '%s'",
- Path.str().c_str(), DataPath.str().data());
- }
+ // Parse each summary data entry
+ std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>
+ Data;
+ for (const llvm::json::Value &SummaryDataValue : *SummaryDataArray) {
+ const llvm::json::Object *SummaryDataObject =
+ SummaryDataValue.getAsObject();
+ if (!SummaryDataObject) {
+ return llvm::createStringError(std::errc::invalid_argument,
+ "failed to read TUSummary from '%s': "
+ "data array contains non-object element",
+ Path.str().c_str());
+ }
- if (!StatusOrErr->isDirectory()) {
- return llvm::createStringError(std::errc::not_a_directory,
- "failed to read TUSummary from '%s': "
- "data path is not a directory: '%s'",
- Path.str().c_str(), DataPath.str().data());
+ auto ExpectedSummaryDataEntry =
+ summaryDataMapEntryFromJSON(*SummaryDataObject, getIdTable(Summary));
+ if (!ExpectedSummaryDataEntry)
+ return wrapError(ExpectedSummaryDataEntry.takeError(),
+ "failed to read TUSummary from '%s'",
+ Path.str().c_str());
+
+ auto [SummaryIt, SummaryInserted] =
+ Data.emplace(std::move(*ExpectedSummaryDataEntry));
+ if (!SummaryInserted) {
+ return llvm::createStringError(std::errc::invalid_argument,
+ "failed to read TUSummary from '%s': "
+ "duplicate SummaryName '%s' found",
+ Path.str().c_str(),
+ SummaryIt->first.str().data());
+ }
}
- auto ExpectedData = readTUSummaryData(DataPath);
- if (!ExpectedData)
- return wrapError(ExpectedData.takeError(),
- "failed to read TUSummary from '%s'",
- Path.str().c_str());
-
- getData(Summary) = std::move(*ExpectedData);
+ getData(Summary) = std::move(Data);
}
return Summary;
}
llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
- llvm::StringRef Dir) {
- // Serialize TUNamespace field.
- {
- llvm::SmallString<kPathBufferSize> TUNamespacePath(Dir);
- llvm::sys::path::append(TUNamespacePath, TUSummaryTUNamespaceFilename);
-
- llvm::json::Object BuildNamespaceObj =
- buildNamespaceToJSON(getTUNamespace(S));
- if (auto Error =
- writeJSON(*FS, std::move(BuildNamespaceObj), TUNamespacePath)) {
- return wrapError(std::move(Error), std::errc::io_error,
- "failed to write TUSummary to '%s': "
- "cannot write TUNamespace file '%s'",
- Dir.str().c_str(), TUNamespacePath.str().data());
- }
- }
+ llvm::StringRef Path) {
+ llvm::json::Object RootObject;
- // Serialize IdTable field.
- {
- llvm::SmallString<kPathBufferSize> IdTablePath(Dir);
- llvm::sys::path::append(IdTablePath, TUSummaryIdTableFilename);
-
- llvm::json::Array IdTableObj = entityIdTableToJSON(getIdTable(S));
- if (auto Error = writeJSON(*FS, std::move(IdTableObj), IdTablePath)) {
- return wrapError(std::move(Error), std::errc::io_error,
- "failed to write TUSummary to '%s': "
- "cannot write IdTable file '%s'",
- Dir.str().c_str(), IdTablePath.str().data());
- }
- }
+ RootObject["tu_namespace"] = buildNamespaceToJSON(getTUNamespace(S));
- // Serialize Data field.
- {
- llvm::SmallString<kPathBufferSize> DataPath(Dir);
- llvm::sys::path::append(DataPath, TUSummaryDataDirname);
-
- // Create the data directory if it doesn't exist
- // Use the real filesystem for directory creation as VFS doesn't always
- // support this
- if (std::error_code EC = llvm::sys::fs::create_directory(DataPath)) {
- // If error is not "already exists", return error
- if (EC != std::errc::file_exists) {
- return llvm::createStringError(EC,
- "failed to write TUSummary to '%s': "
- "cannot create data directory '%s'",
- Dir.str().c_str(),
- DataPath.str().data());
- }
- }
+ RootObject["id_table"] = entityIdTableToJSON(getIdTable(S));
- // Verify it's a directory (could be a file with the same name)
- if (FS->exists(DataPath)) {
- auto StatusOrErr = FS->status(DataPath);
- if (!StatusOrErr) {
- return llvm::createStringError(StatusOrErr.getError(),
- "failed to write TUSummary to '%s': "
- "cannot get status of data path '%s'",
- Dir.str().c_str(),
- DataPath.str().data());
- }
- if (!StatusOrErr->isDirectory()) {
- return llvm::createStringError(
- std::errc::not_a_directory,
- "failed to write TUSummary to '%s': data path exists but is not a "
- "directory: '%s'",
- Dir.str().c_str(), DataPath.str().data());
- }
+ {
+ llvm::json::Array SummaryDataArray;
+ for (const auto &[SummaryName, DataMap] : getData(S)) {
+ SummaryDataArray.push_back(
+ summaryDataMapEntryToJSON(SummaryName, DataMap));
}
+ RootObject["data"] = std::move(SummaryDataArray);
+ }
- if (auto Error = writeTUSummaryData(getData(S), DataPath)) {
- return wrapError(std::move(Error), std::errc::io_error,
- "failed to write TUSummary to '%s': cannot write data",
- Dir.str().c_str());
- }
+ // Write the JSON to file
+ if (auto Error = writeJSON(std::move(RootObject), Path)) {
+ return wrapError(std::move(Error), std::errc::io_error,
+ "failed to write TUSummary to '%s'", Path.str().c_str());
}
return llvm::Error::success();
diff --git a/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test b/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
new file mode 100644
index 0000000000000..c051f41af4fc4
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/callgraph-invalid-caller.json 2>&1 | FileCheck %s
+
+// Test that CallGraph analysis validates caller field type
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize EntitySummary
+// CHECK: CallGraph: 'caller' at index 0 is not a valid uint64
diff --git a/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test b/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
new file mode 100644
index 0000000000000..0e5e2877d06d2
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/callgraph-missing-field.json 2>&1 | FileCheck %s
+
+// Test that CallGraph analysis requires 'call_graph' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize EntitySummary
+// CHECK: CallGraph: missing or invalid 'call_graph' field
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
new file mode 100644
index 0000000000000..90dea14d683db
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
@@ -0,0 +1,6 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/data-element-not-object.json 2>&1 | FileCheck %s
+
+// Test that 'data' array elements must be objects
+
+// CHECK: failed to read TUSummary
+// CHECK: data array contains non-object element
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test b/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
new file mode 100644
index 0000000000000..865db1e8709cd
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/data-entry-missing-data.json 2>&1 | FileCheck %s
+
+// Test that data array entries must have 'data' field
+
+// CHECK: failed to read TUSummary
+// CHECK: for summary 'test'
+// CHECK: missing or invalid field 'data'
+// CHECK: expected JSON array of entity summaries
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test b/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
new file mode 100644
index 0000000000000..a9bf765424b7e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/data-missing-summary-name.json 2>&1 | FileCheck %s
+
+// Test that data array entries must have 'summary_name' field
+
+// CHECK: failed to read TUSummary
+// CHECK: missing required field 'summary_name'
+// CHECK: expected string identifier for the analysis summary
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-not-array.test b/clang/test/Analysis/Scalable/Serialization/error-data-not-array.test
new file mode 100644
index 0000000000000..058be1a4bddbb
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-not-array.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/data-not-array.json 2>&1 | FileCheck %s
+
+// Test that root 'data' field must be an array
+
+// CHECK: failed to read TUSummary
+// CHECK: missing or invalid field 'data'
+// CHECK: expected JSON array
diff --git a/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test b/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
new file mode 100644
index 0000000000000..64675a201dd59
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/defuse-invalid-use.json 2>&1 | FileCheck %s
+
+// Test that DefUse analysis validates use value types
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize EntitySummary
+// CHECK: DefUse: use at index 0,0,0 is not a valid uint64
diff --git a/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test b/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
new file mode 100644
index 0000000000000..73409a2d46503
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/defuse-missing-field.json 2>&1 | FileCheck %s
+
+// Test that DefUse analysis requires 'def_use_chains' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize EntitySummary
+// CHECK: DefUse: missing or invalid 'def_use_chains' field
diff --git a/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test b/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
new file mode 100644
index 0000000000000..8e4d594cc75e6
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/duplicate-entity.json 2>&1 | FileCheck %s
+
+// Test that duplicate EntityName in id_table is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable from file
+// CHECK: duplicate EntityName found at index 1
+// CHECK: EntityId=0 already exists in table
diff --git a/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test b/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
new file mode 100644
index 0000000000000..568db0a339620
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
@@ -0,0 +1,6 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/duplicate-summary-name.json 2>&1 | FileCheck %s
+
+// Test that duplicate summary names are detected
+
+// CHECK: failed to read TUSummary
+// CHECK: duplicate SummaryName 'test' found
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
new file mode 100644
index 0000000000000..72bf04adc267c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-data-element-not-object.json 2>&1 | FileCheck %s
+
+// Test that entity data elements must be objects
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map from file
+// CHECK: element at index 0 is not a JSON object
+// CHECK: expected object with 'entity_id' and 'entity_summary' fields
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
new file mode 100644
index 0000000000000..e2dd9a34b34d3
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
@@ -0,0 +1,9 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-data-missing-entity-id.json 2>&1 | FileCheck %s
+
+// Test that entity data entries must have 'entity_id' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry from file
+// CHECK: at index 0
+// CHECK: missing required field 'entity_id'
+// CHECK: expected unsigned integer EntityId
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
new file mode 100644
index 0000000000000..078bc4b068258
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
@@ -0,0 +1,9 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-data-missing-entity-summary.json 2>&1 | FileCheck %s
+
+// Test that entity data entries must have 'entity_summary' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry from file
+// CHECK: at index 0
+// CHECK: missing or invalid field 'entity_summary'
+// CHECK: expected EntitySummary JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test b/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
new file mode 100644
index 0000000000000..f5903b02d7eeb
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-id-not-uint64.json 2>&1 | FileCheck %s
+
+// Test that entity_id field must be a valid uint64
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry from file
+// CHECK: at index 0
+// CHECK: field 'entity_id' is not a valid unsigned 64-bit integer
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
new file mode 100644
index 0000000000000..f9230bed9b25f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
@@ -0,0 +1,9 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-name-missing-namespace.json 2>&1 | FileCheck %s
+
+// Test that EntityName must have 'namespace' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: failed to deserialize EntityName from file
+// CHECK: missing or invalid field 'namespace'
+// CHECK: expected JSON array of BuildNamespace objects
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
new file mode 100644
index 0000000000000..70a75f8b63433
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-name-missing-suffix.json 2>&1 | FileCheck %s
+
+// Test that EntityName must have 'suffix' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: failed to deserialize EntityName from file
+// CHECK: missing required field 'suffix'
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
new file mode 100644
index 0000000000000..8531cfa1bd307
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
@@ -0,0 +1,9 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-name-missing-usr.json 2>&1 | FileCheck %s
+
+// Test that EntityName must have 'usr' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: failed to deserialize EntityName from file
+// CHECK: missing required field 'usr'
+// CHECK: Unified Symbol Resolution string
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test b/clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test
new file mode 100644
index 0000000000000..9a317f1270c4a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-summary-no-format-info.json 2>&1 | FileCheck %s
+
+// Test that EntitySummary deserialization requires registered FormatInfo
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntitySummary from file
+// CHECK: no FormatInfo was registered for summary name
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test
new file mode 100644
index 0000000000000..5e1359a3ce94c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-element-not-object.json 2>&1 | FileCheck %s
+
+// Test that id_table elements must be objects
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable from file
+// CHECK: element at index 0 is not a JSON object
+// CHECK: expected EntityIdTable entry with 'id' and 'name' fields
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test
new file mode 100644
index 0000000000000..4e2706d89f52d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-entry-id-not-uint64.json 2>&1 | FileCheck %s
+
+// Test that id_table entry 'id' field must be a valid uint64
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: field 'id' is not a valid unsigned 64-bit integer
+// CHECK: expected non-negative EntityId value
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test
new file mode 100644
index 0000000000000..423cd54b3c1f6
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-entry-missing-id.json 2>&1 | FileCheck %s
+
+// Test that id_table entries must have 'id' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: missing required field 'id'
+// CHECK: expected unsigned integer EntityId
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test
new file mode 100644
index 0000000000000..de56b93de491b
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-entry-missing-name.json 2>&1 | FileCheck %s
+
+// Test that id_table entries must have 'name' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: missing or invalid field 'name'
+// CHECK: expected EntityName JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-not-array.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-not-array.test
new file mode 100644
index 0000000000000..8d7a9bd640247
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-not-array.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-not-array.json 2>&1 | FileCheck %s
+
+// Test that id_table must be an array
+
+// CHECK: failed to read TUSummary
+// CHECK: missing or invalid field 'id_table'
+// CHECK: expected JSON array
diff --git a/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test b/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
new file mode 100644
index 0000000000000..cecc51e633f40
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/invalid-kind.json 2>&1 | FileCheck %s
+
+// Test that invalid kind value is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize BuildNamespace from file
+// CHECK: invalid 'kind' BuildNamespaceKind value 'InvalidKind'
diff --git a/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test b/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
new file mode 100644
index 0000000000000..1724b13031c13
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
@@ -0,0 +1,6 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/invalid-syntax.json 2>&1 | FileCheck %s
+
+// Test that invalid JSON syntax is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to read JSON object from file
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-data.test b/clang/test/Analysis/Scalable/Serialization/error-missing-data.test
new file mode 100644
index 0000000000000..8d4b654a8715d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-data.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/missing-data.json 2>&1 | FileCheck %s
+
+// Test that missing root 'data' field is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: missing or invalid field 'data'
+// CHECK: expected JSON array
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-id-table.test b/clang/test/Analysis/Scalable/Serialization/error-missing-id-table.test
new file mode 100644
index 0000000000000..a71bb4e361fdd
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-id-table.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/missing-id-table.json 2>&1 | FileCheck %s
+
+// Test that missing id_table field is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: missing or invalid field 'id_table'
+// CHECK: expected JSON array
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test b/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
new file mode 100644
index 0000000000000..d26a39d46c4b2
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/missing-kind.json 2>&1 | FileCheck %s
+
+// Test that missing kind field in tu_namespace is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize BuildNamespace from file
+// CHECK: missing required field 'kind'
+// CHECK: expected BuildNamespaceKind
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-name.test b/clang/test/Analysis/Scalable/Serialization/error-missing-name.test
new file mode 100644
index 0000000000000..d88a8c46bc85f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-name.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/missing-name.json 2>&1 | FileCheck %s
+
+// Test that missing name field in tu_namespace is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize BuildNamespace from file
+// CHECK: missing required field 'name'
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-tu-namespace.test b/clang/test/Analysis/Scalable/Serialization/error-missing-tu-namespace.test
new file mode 100644
index 0000000000000..fb6b58693edf6
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-tu-namespace.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/missing-tu-namespace.json 2>&1 | FileCheck %s
+
+// Test that missing tu_namespace field is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: missing or invalid field 'tu_namespace'
+// CHECK: expected JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
new file mode 100644
index 0000000000000..4723d9d4434d9
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
@@ -0,0 +1,10 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/namespace-element-not-object.json 2>&1 | FileCheck %s
+
+// Test that namespace array elements must be objects
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: failed to deserialize EntityName from file
+// CHECK: failed to deserialize NestedBuildNamespace from file
+// CHECK: element at index 0 is not a JSON object
+// CHECK: expected BuildNamespace object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-nonexistent-file.test b/clang/test/Analysis/Scalable/Serialization/error-nonexistent-file.test
new file mode 100644
index 0000000000000..3748d54622e75
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-nonexistent-file.test
@@ -0,0 +1,6 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/nonexistent.json 2>&1 | FileCheck %s
+
+// Test that nonexistent file is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: file does not exist
diff --git a/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test b/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
new file mode 100644
index 0000000000000..57bcab0bf8212
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/not_json-extension.txt 2>&1 | FileCheck %s
+
+// Test that files with non-.json extensions are rejected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to read JSON object from file
+// CHECK: failed to validate JSON file
+// CHECK: not a JSON file
diff --git a/clang/test/Analysis/Scalable/Serialization/error-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-not-object.test
new file mode 100644
index 0000000000000..ec34a20c9f641
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-not-object.test
@@ -0,0 +1,6 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/not-object.json 2>&1 | FileCheck %s
+
+// Test that JSON array (not object) is rejected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to read JSON object from file
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-callgraph.test b/clang/test/Analysis/Scalable/Serialization/valid-callgraph.test
new file mode 100644
index 0000000000000..09722d11a796e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/valid-callgraph.test
@@ -0,0 +1,9 @@
+// RUN: ssaf-serialization-format-test %S/Inputs/valid-callgraph.json -q -o %t.json
+// RUN: diff %t.json %S/Inputs/valid-callgraph-expected.json
+
+// Test CallGraph analysis round-trip serialization
+// This test verifies that:
+// - CallGraph analysis data can be read from JSON
+// - The call graph structure (caller -> callees) is preserved
+// - EntityId references are correctly serialized/deserialized
+// - Output matches expected format with alphabetically sorted fields
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-defuse.test b/clang/test/Analysis/Scalable/Serialization/valid-defuse.test
new file mode 100644
index 0000000000000..311bd02fdde3d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/valid-defuse.test
@@ -0,0 +1,10 @@
+// RUN: ssaf-serialization-format-test %S/Inputs/valid-defuse.json -q -o %t.json
+// RUN: diff %t.json %S/Inputs/valid-defuse-expected.json
+
+// Test DefUse analysis round-trip serialization
+// This test verifies that:
+// - DefUse analysis data can be read from JSON
+// - The def-use chain structure (variable -> definitions -> uses) is preserved
+// - Nested maps and sets are correctly serialized/deserialized
+// - EntityId references are correctly converted using EntityIdConverter
+// - Output matches expected format with alphabetically sorted fields
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-empty.test b/clang/test/Analysis/Scalable/Serialization/valid-empty.test
new file mode 100644
index 0000000000000..30712a3e8d637
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/valid-empty.test
@@ -0,0 +1,4 @@
+// RUN: ssaf-serialization-format-test %S/Inputs/valid-empty.json -q -o %t.json
+// RUN: diff %t.json %S/Inputs/valid-empty-expected.json
+
+// Test that an empty valid TUSummary can be read and written back
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-link-unit.test b/clang/test/Analysis/Scalable/Serialization/valid-link-unit.test
new file mode 100644
index 0000000000000..b9533904c0bd1
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/valid-link-unit.test
@@ -0,0 +1,4 @@
+// RUN: ssaf-serialization-format-test %S/Inputs/valid-link-unit.json -q -o %t.json
+// RUN: diff %t.json %S/Inputs/valid-link-unit-expected.json
+
+// Test that a TUSummary with link_unit namespace can be read and written back
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-with-empty-data-entry.test b/clang/test/Analysis/Scalable/Serialization/valid-with-empty-data-entry.test
new file mode 100644
index 0000000000000..a9be32902a10a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/valid-with-empty-data-entry.test
@@ -0,0 +1,4 @@
+// RUN: ssaf-serialization-format-test %S/Inputs/valid-with-empty-data-entry.json -q -o %t.json
+// RUN: diff %t.json %S/Inputs/valid-with-empty-data-entry-expected.json
+
+// Test that a TUSummary with an empty data entry can be read and written back
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-with-idtable.test b/clang/test/Analysis/Scalable/Serialization/valid-with-idtable.test
new file mode 100644
index 0000000000000..a714aa7dcf2d1
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/valid-with-idtable.test
@@ -0,0 +1,4 @@
+// RUN: ssaf-serialization-format-test %S/Inputs/valid-with-idtable.json -q -o %t.json
+// RUN: diff %t.json %S/Inputs/valid-with-idtable-expected.json
+
+// Test that a TUSummary with id_table entries can be read and written back
diff --git a/clang/test/lit.cfg.py b/clang/test/lit.cfg.py
index a622f5335354a..bb5c9b331a43f 100644
--- a/clang/test/lit.cfg.py
+++ b/clang/test/lit.cfg.py
@@ -103,6 +103,7 @@
"clang-diff",
"clang-format",
"clang-repl",
+ "ssaf-serialization-format-test",
"llvm-offload-binary",
"clang-tblgen",
"clang-scan-deps",
diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt b/clang/unittests/Analysis/Scalable/CMakeLists.txt
index e9bfca2c8fe7f..993ca3669228c 100644
--- a/clang/unittests/Analysis/Scalable/CMakeLists.txt
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -28,3 +28,6 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
FrontendOpenMP
Support
)
+
+add_subdirectory(tools)
+
diff --git a/clang/unittests/Analysis/Scalable/tools/CMakeLists.txt b/clang/unittests/Analysis/Scalable/tools/CMakeLists.txt
new file mode 100644
index 0000000000000..69e51c6a6aba5
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/tools/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(ssaf-serialization-format-test)
diff --git a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/CMakeLists.txt b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/CMakeLists.txt
new file mode 100644
index 0000000000000..ea008bee8d2db
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/CMakeLists.txt
@@ -0,0 +1,9 @@
+add_clang_tool(ssaf-serialization-format-test
+ SSAFSerializationFormatTest.cpp
+ ExampleAnalyses.cpp
+ )
+
+target_link_libraries(ssaf-serialization-format-test
+ PRIVATE
+ clangAnalysisScalable
+ )
diff --git a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
new file mode 100644
index 0000000000000..f8b4a535f00f3
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
@@ -0,0 +1,303 @@
+//===- ExampleAnalyses.cpp - Example analysis data for clang-ssaf-dump ---===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines example analysis data structures and their serialization/
+// deserialization functions for the JSONFormat.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/TUSummary/EntitySummary.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/Registry.h"
+#include <map>
+#include <set>
+
+using namespace clang::ssaf;
+
+//===----------------------------------------------------------------------===//
+// CallGraphAnalysis - Tracks caller-to-callees relationships
+//===----------------------------------------------------------------------===//
+
+namespace {
+struct CallGraphAnalysis : EntitySummary {
+ CallGraphAnalysis() : EntitySummary(SummaryName("CallGraph")) {}
+
+ // Maps each function (EntityId) to the set of functions it calls
+ std::map<EntityId, std::set<EntityId>> CallGraph;
+};
+} // namespace
+
+static llvm::json::Object
+serializeCallGraph(const EntitySummary &Data,
+ const JSONFormat::EntityIdConverter &Converter) {
+ const auto &CG = static_cast<const CallGraphAnalysis &>(Data);
+ llvm::json::Object Result;
+
+ // Serialize the call graph as an array of objects
+ llvm::json::Array CallGraphArray;
+ for (const auto &[Caller, Callees] : CG.CallGraph) {
+ llvm::json::Object Entry;
+ Entry["caller"] = Converter.toJSON(Caller);
+
+ llvm::json::Array CalleesArray;
+ for (const auto &Callee : Callees) {
+ CalleesArray.push_back(Converter.toJSON(Callee));
+ }
+ Entry["callees"] = std::move(CalleesArray);
+
+ CallGraphArray.push_back(std::move(Entry));
+ }
+
+ Result["call_graph"] = std::move(CallGraphArray);
+ return Result;
+}
+
+static llvm::Expected<std::unique_ptr<EntitySummary>>
+deserializeCallGraph(const llvm::json::Object &JSONObj, EntityIdTable &Table,
+ const JSONFormat::EntityIdConverter &Converter) {
+ auto Result = std::make_unique<CallGraphAnalysis>();
+
+ const llvm::json::Array *CallGraphArray = JSONObj.getArray("call_graph");
+ if (!CallGraphArray) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "CallGraph: missing or invalid 'call_graph' field");
+ }
+
+ for (size_t Index = 0; Index < CallGraphArray->size(); ++Index) {
+ const llvm::json::Object *EntryObj = (*CallGraphArray)[Index].getAsObject();
+ if (!EntryObj) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "CallGraph: call_graph entry at index %zu is not an object", Index);
+ }
+
+ // Parse caller
+ const llvm::json::Value *CallerValue = EntryObj->get("caller");
+ if (!CallerValue) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "CallGraph: entry at index %zu missing 'caller' field", Index);
+ }
+ auto CallerInt = CallerValue->getAsUINT64();
+ if (!CallerInt) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "CallGraph: 'caller' at index %zu is not a valid uint64", Index);
+ }
+ EntityId Caller = Converter.fromJSON(*CallerInt);
+
+ // Parse callees array
+ const llvm::json::Array *CalleesArray = EntryObj->getArray("callees");
+ if (!CalleesArray) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "CallGraph: entry at index %zu missing or invalid 'callees' field",
+ Index);
+ }
+
+ std::set<EntityId> Callees;
+ for (size_t CalleeIndex = 0; CalleeIndex < CalleesArray->size();
+ ++CalleeIndex) {
+ auto CalleeInt = (*CalleesArray)[CalleeIndex].getAsUINT64();
+ if (!CalleeInt) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "CallGraph: callee at index %zu,%zu is not a valid uint64", Index,
+ CalleeIndex);
+ }
+ Callees.insert(Converter.fromJSON(*CalleeInt));
+ }
+
+ Result->CallGraph[Caller] = std::move(Callees);
+ }
+
+ return std::move(Result);
+}
+
+namespace {
+using FormatInfo = JSONFormat::FormatInfo;
+struct CallGraphFormatInfo : FormatInfo {
+ CallGraphFormatInfo()
+ : FormatInfo{
+ SummaryName("CallGraph"),
+ serializeCallGraph,
+ deserializeCallGraph,
+ } {}
+};
+} // namespace
+
+static llvm::Registry<JSONFormat::FormatInfo>::Add<CallGraphFormatInfo>
+ RegisterCallGraph("CallGraphAnalysis",
+ "Format info for CallGraph analysis data");
+
+//===----------------------------------------------------------------------===//
+// DefUseAnalysis - Tracks definition-use chains
+//===----------------------------------------------------------------------===//
+
+namespace {
+struct DefUseAnalysis : EntitySummary {
+ DefUseAnalysis() : EntitySummary(SummaryName("DefUse")) {}
+
+ // For each variable (EntityId), maps definitions (EntityId) to their use
+ // sites (set of EntityId)
+ std::map<EntityId, std::map<EntityId, std::set<EntityId>>> DefUseChains;
+};
+} // namespace
+
+static llvm::json::Object
+serializeDefUse(const EntitySummary &Data,
+ const JSONFormat::EntityIdConverter &Converter) {
+ const auto &DU = static_cast<const DefUseAnalysis &>(Data);
+ llvm::json::Object Result;
+
+ // Serialize def-use chains as an array of objects
+ llvm::json::Array ChainsArray;
+ for (const auto &[Variable, DefUseMap] : DU.DefUseChains) {
+ llvm::json::Object VarEntry;
+ VarEntry["variable"] = Converter.toJSON(Variable);
+
+ llvm::json::Array DefsArray;
+ for (const auto &[Definition, Uses] : DefUseMap) {
+ llvm::json::Object DefEntry;
+ DefEntry["definition"] = Converter.toJSON(Definition);
+
+ llvm::json::Array UsesArray;
+ for (const auto &Use : Uses) {
+ UsesArray.push_back(Converter.toJSON(Use));
+ }
+ DefEntry["uses"] = std::move(UsesArray);
+
+ DefsArray.push_back(std::move(DefEntry));
+ }
+ VarEntry["definitions"] = std::move(DefsArray);
+
+ ChainsArray.push_back(std::move(VarEntry));
+ }
+
+ Result["def_use_chains"] = std::move(ChainsArray);
+ return Result;
+}
+
+static llvm::Expected<std::unique_ptr<EntitySummary>>
+deserializeDefUse(const llvm::json::Object &JSONObj, EntityIdTable &Table,
+ const JSONFormat::EntityIdConverter &Converter) {
+ auto Result = std::make_unique<DefUseAnalysis>();
+
+ const llvm::json::Array *ChainsArray = JSONObj.getArray("def_use_chains");
+ if (!ChainsArray) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: missing or invalid 'def_use_chains' field");
+ }
+
+ for (size_t VarIndex = 0; VarIndex < ChainsArray->size(); ++VarIndex) {
+ const llvm::json::Object *VarObj = (*ChainsArray)[VarIndex].getAsObject();
+ if (!VarObj) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: def_use_chains entry at index %zu is not an object",
+ VarIndex);
+ }
+
+ // Parse variable
+ const llvm::json::Value *VarValue = VarObj->get("variable");
+ if (!VarValue) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: entry at index %zu missing 'variable' field", VarIndex);
+ }
+ auto VarInt = VarValue->getAsUINT64();
+ if (!VarInt) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: 'variable' at index %zu is not a valid uint64", VarIndex);
+ }
+ EntityId Variable = Converter.fromJSON(*VarInt);
+
+ // Parse definitions array
+ const llvm::json::Array *DefsArray = VarObj->getArray("definitions");
+ if (!DefsArray) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: entry at index %zu missing or invalid 'definitions' field",
+ VarIndex);
+ }
+
+ std::map<EntityId, std::set<EntityId>> DefUseMap;
+ for (size_t DefIndex = 0; DefIndex < DefsArray->size(); ++DefIndex) {
+ const llvm::json::Object *DefObj = (*DefsArray)[DefIndex].getAsObject();
+ if (!DefObj) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: definition at index %zu,%zu is not an object", VarIndex,
+ DefIndex);
+ }
+
+ // Parse definition
+ const llvm::json::Value *DefValue = DefObj->get("definition");
+ if (!DefValue) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: definition at index %zu,%zu missing 'definition' field",
+ VarIndex, DefIndex);
+ }
+ auto DefInt = DefValue->getAsUINT64();
+ if (!DefInt) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: 'definition' at index %zu,%zu is not a valid uint64",
+ VarIndex, DefIndex);
+ }
+ EntityId Definition = Converter.fromJSON(*DefInt);
+
+ // Parse uses array
+ const llvm::json::Array *UsesArray = DefObj->getArray("uses");
+ if (!UsesArray) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: definition at index %zu,%zu missing or invalid 'uses' "
+ "field",
+ VarIndex, DefIndex);
+ }
+
+ std::set<EntityId> Uses;
+ for (size_t UseIndex = 0; UseIndex < UsesArray->size(); ++UseIndex) {
+ auto UseInt = (*UsesArray)[UseIndex].getAsUINT64();
+ if (!UseInt) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: use at index %zu,%zu,%zu is not a valid uint64",
+ VarIndex, DefIndex, UseIndex);
+ }
+ Uses.insert(Converter.fromJSON(*UseInt));
+ }
+
+ DefUseMap[Definition] = std::move(Uses);
+ }
+
+ Result->DefUseChains[Variable] = std::move(DefUseMap);
+ }
+
+ return std::move(Result);
+}
+
+namespace {
+struct DefUseFormatInfo : FormatInfo {
+ DefUseFormatInfo()
+ : FormatInfo{
+ SummaryName("DefUse"),
+ serializeDefUse,
+ deserializeDefUse,
+ } {}
+};
+} // namespace
+
+static llvm::Registry<JSONFormat::FormatInfo>::Add<DefUseFormatInfo>
+ RegisterDefUse("DefUseAnalysis", "Format info for DefUse analysis data");
diff --git a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp
new file mode 100644
index 0000000000000..2262b9117e7d5
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp
@@ -0,0 +1,84 @@
+//===- SSAFSerializationFormatTest.cpp - Test SSAF JSON serialization ---===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This tool reads SSAF TUSummary JSON files and can:
+// - Validate the JSON format
+// - Print summary information
+// - Write back to JSON (for round-trip testing)
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/VirtualFileSystem.h"
+#include "llvm/Support/WithColor.h"
+
+using namespace clang::ssaf;
+using namespace llvm;
+
+static cl::opt<std::string>
+ InputFilename(cl::Positional, cl::desc("<input file>"), cl::Required);
+
+static cl::opt<std::string> OutputFilename("o", cl::desc("Output filename"),
+ cl::value_desc("filename"),
+ cl::init("-"));
+
+static cl::opt<bool> PrintSummary("print-summary",
+ cl::desc("Print summary information"),
+ cl::init(false));
+
+static cl::opt<bool> Quiet("q", cl::desc("Suppress diagnostic messages"),
+ cl::init(false));
+
+static void reportError(StringRef Prefix, Error E) {
+ if (Quiet)
+ return;
+ WithColor::error(errs(), "ssaf-serialization-format-test")
+ << Prefix << ": " << toString(std::move(E)) << "\n";
+}
+
+int main(int argc, char **argv) {
+ InitLLVM X(argc, argv);
+ cl::ParseCommandLineOptions(argc, argv,
+ "SSAF JSON format validator and dumper\n");
+
+ // Create JSONFormat instance
+ auto FS = vfs::getRealFileSystem();
+ JSONFormat Format(FS);
+
+ // Read the input file
+ auto SummaryOrErr = Format.readTUSummary(InputFilename);
+ if (!SummaryOrErr) {
+ reportError("failed to read TUSummary", SummaryOrErr.takeError());
+ return 1;
+ }
+
+ TUSummary &Summary = *SummaryOrErr;
+
+ // Print summary information if requested
+ if (PrintSummary) {
+ outs() << "TUSummary successfully read from: " << InputFilename << "\n";
+ // Could add more detailed summary info here
+ }
+
+ // Write output if specified
+ if (OutputFilename != "-") {
+ if (auto Err = Format.writeTUSummary(Summary, OutputFilename)) {
+ reportError("failed to write TUSummary", std::move(Err));
+ return 1;
+ }
+ if (!Quiet) {
+ outs() << "TUSummary written to: " << OutputFilename << "\n";
+ }
+ }
+
+ return 0;
+}
>From cf6dd82d2c785089894daf09d88f1fe85538f9a6 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 12:35:00 -0800
Subject: [PATCH 06/19] Remove JSONFormatTest since we are using lit tests
---
.../Analysis/Scalable/CMakeLists.txt | 1 -
.../Analysis/Scalable/JSONFormatTest.cpp | 1127 -----------------
2 files changed, 1128 deletions(-)
delete mode 100644 clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt b/clang/unittests/Analysis/Scalable/CMakeLists.txt
index 993ca3669228c..3c642dd4b2e67 100644
--- a/clang/unittests/Analysis/Scalable/CMakeLists.txt
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -4,7 +4,6 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
EntityIdTest.cpp
EntityIdTableTest.cpp
EntityNameTest.cpp
- JSONFormatTest.cpp
Registries/FancyAnalysisData.cpp
Registries/MockSerializationFormat.cpp
Registries/MockSummaryExtractor1.cpp
diff --git a/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
deleted file mode 100644
index eb2ed77976bda..0000000000000
--- a/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
+++ /dev/null
@@ -1,1127 +0,0 @@
-//===- unittests/Analysis/Scalable/JSONFormatTest.cpp -------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
-#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
-#include "clang/Analysis/Scalable/Model/EntityId.h"
-#include "clang/Analysis/Scalable/Model/EntityIdTable.h"
-#include "clang/Analysis/Scalable/Model/EntityName.h"
-#include "clang/Analysis/Scalable/Model/SummaryName.h"
-#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
-#include "llvm/Support/FileSystem.h"
-#include "llvm/Support/Path.h"
-#include "llvm/Testing/Support/Error.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include <fstream>
-
-using namespace clang::ssaf;
-using llvm::Failed;
-using llvm::Succeeded;
-using ::testing::AllOf;
-using ::testing::HasSubstr;
-
-namespace {
-
-// Helper function to check that an error message contains all specified
-// substrings
-::testing::Matcher<std::string>
-ContainsAllSubstrings(std::initializer_list<const char *> substrings) {
- std::vector<::testing::Matcher<std::string>> matchers;
- for (const char *substr : substrings) {
- matchers.push_back(HasSubstr(substr));
- }
- return ::testing::AllOfArray(matchers);
-}
-
-//===----------------------------------------------------------------------===//
-// Test Fixtures and Helpers
-//===----------------------------------------------------------------------===//
-
-// Helper class to manage temporary directories for testing
-class TempDir {
- llvm::SmallString<128> Path;
- std::error_code EC;
-
-public:
- TempDir() {
- EC = llvm::sys::fs::createUniqueDirectory("JSONFormatTest", Path);
- }
-
- ~TempDir() {
- if (!EC && llvm::sys::fs::exists(Path)) {
- llvm::sys::fs::remove_directories(Path);
- }
- }
-
- llvm::StringRef path() const { return Path; }
- bool isValid() const { return !EC; }
-};
-
-// Helper function to write a file with content
-void writeFile(llvm::StringRef Path, llvm::StringRef Content) {
- std::ofstream File(Path.str());
- File << Content.str();
- File.close();
-}
-
-// Helper function to create a directory
-void createDir(llvm::StringRef Path) {
- llvm::sys::fs::create_directories(Path);
-}
-
-// Base test fixture for JSONFormat tests
-class JSONFormatTestBase : public ::testing::Test {
-protected:
- JSONFormat Format{llvm::vfs::getRealFileSystem()};
-};
-
-//===----------------------------------------------------------------------===//
-// TUSummary Read Tests - TUNamespace
-//===----------------------------------------------------------------------===//
-
-class JSONFormatReadTUNamespaceTest : public JSONFormatTestBase {};
-
-TEST_F(JSONFormatReadTUNamespaceTest, NonexistentDirectory) {
- auto Result = Format.readTUSummary("/nonexistent/path");
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from '/nonexistent/path'",
- "failed to read JSON from file", "tu_namespace.json",
- "failed to validate JSON file", "file does not exist"}));
-}
-
-TEST_F(JSONFormatReadTUNamespaceTest, MissingTUNamespaceFile) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg, ContainsAllSubstrings({"failed to read TUSummary from",
- "failed to read JSON from file",
- "tu_namespace.json",
- "file does not exist"}));
-}
-
-TEST_F(JSONFormatReadTUNamespaceTest, InvalidJSONSyntax) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath, "{ invalid json }");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg, ContainsAllSubstrings({"failed to read TUSummary from",
- "failed to read JSON from file",
- "tu_namespace.json"}));
-}
-
-TEST_F(JSONFormatReadTUNamespaceTest, NotJSONObject) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath, "[]");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings({"failed to read TUSummary from",
- "failed to read JSON object from file",
- "tu_namespace.json"}));
-}
-
-TEST_F(JSONFormatReadTUNamespaceTest, MissingKindField) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath, R"({"name": "test.cpp"})");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(
- ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize BuildNamespace from file",
- "tu_namespace.json",
- "missing required field 'kind' (expected BuildNamespaceKind)"}));
-}
-
-TEST_F(JSONFormatReadTUNamespaceTest, InvalidKindValue) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath, R"({"kind": "InvalidKind", "name": "test.cpp"})");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize BuildNamespace from file",
- "tu_namespace.json",
- "invalid 'kind' BuildNamespaceKind value 'InvalidKind'"}));
-}
-
-TEST_F(JSONFormatReadTUNamespaceTest, MissingNameField) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath, R"({"kind": "compilation_unit"})");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize BuildNamespace from file",
- "tu_namespace.json", "missing required field 'name'"}));
-}
-
-//===----------------------------------------------------------------------===//
-// TUSummary Read Tests - IdTable
-//===----------------------------------------------------------------------===//
-
-class JSONFormatReadIdTableTest : public JSONFormatTestBase {
-protected:
- void SetUpValidTUNamespace(TempDir &Dir) {
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath,
- R"({"kind": "compilation_unit", "name": "test.cpp"})");
- }
-};
-
-TEST_F(JSONFormatReadIdTableTest, MissingIdTableFile) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespace(Dir);
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings({"failed to read TUSummary from",
- "failed to read JSON from file",
- "id_table.json", "file does not exist"}));
-}
-
-TEST_F(JSONFormatReadIdTableTest, NotJSONArray) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespace(Dir);
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, "{}");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings({"failed to read TUSummary from",
- "failed to read JSON array from file",
- "id_table.json"}));
-}
-
-TEST_F(JSONFormatReadIdTableTest, ElementNotObject) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespace(Dir);
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, "[\"not an object\"]");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(
- ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize EntityIdTable from file", "id_table.json",
- "element at index 0 is not a JSON object",
- "expected EntityIdTable entry with 'id' and 'name' fields"}));
-}
-
-TEST_F(JSONFormatReadIdTableTest, EntryMissingName) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespace(Dir);
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, R"([{"id": 0}])");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize EntityIdTable entry from file",
- "id_table.json",
- "missing or invalid field 'name' (expected EntityName JSON "
- "object)"}));
-}
-
-TEST_F(JSONFormatReadIdTableTest, EntryMissingId) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespace(Dir);
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, R"([{
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
- }
- }])");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize EntityIdTable entry from file",
- "id_table.json",
- "missing required field 'id' (expected unsigned integer "
- "EntityId)"}));
-}
-
-TEST_F(JSONFormatReadIdTableTest, EntryIdNotInteger) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespace(Dir);
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, R"([{
- "id": "not a number",
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
- }
- }])");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(
- ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize EntityIdTable entry from file",
- "id_table.json", "field 'id' is not a valid unsigned 64-bit integer",
- "expected non-negative EntityId value"}));
-}
-
-TEST_F(JSONFormatReadIdTableTest, DuplicateEntityName) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespace(Dir);
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, R"([
- {
- "id": 0,
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
- }
- },
- {
- "id": 1,
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
- }
- }
- ])");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize EntityIdTable from file",
- "id_table.json", "duplicate EntityName found at index 1",
- "EntityId=0 already exists in table"}));
-}
-
-//===----------------------------------------------------------------------===//
-// TUSummary Read Tests - EntityName
-//===----------------------------------------------------------------------===//
-
-class JSONFormatReadEntityNameTest : public JSONFormatTestBase {
-protected:
- void SetUpValidTUNamespaceAndPartialIdTable(TempDir &Dir,
- llvm::StringRef EntityNameJson) {
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath,
- R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- std::string JsonContent = "[{\"id\": 0, \"name\": ";
- JsonContent += EntityNameJson.str();
- JsonContent += "}]";
- writeFile(IdTablePath, JsonContent);
- }
-};
-
-TEST_F(JSONFormatReadEntityNameTest, MissingUSR) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
- "suffix": "",
- "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
- })");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(
- ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize EntityName from file", "id_table.json",
- "missing required field 'usr' (Unified Symbol Resolution string)"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, MissingSuffix) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
- "usr": "c:@F at foo",
- "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
- })");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize EntityName from file",
- "id_table.json", "missing required field 'suffix'"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, MissingNamespace) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
- "usr": "c:@F at foo",
- "suffix": ""
- })");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize EntityName from file",
- "id_table.json", "missing or invalid field 'namespace'",
- "expected JSON array of BuildNamespace objects"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, NamespaceNotArray) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": "not an array"
- })");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize EntityName from file",
- "id_table.json", "missing or invalid field 'namespace'",
- "expected JSON array of BuildNamespace objects"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, NamespaceElementNotObject) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": ["not an object"]
- })");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize NestedBuildNamespace from file",
- "id_table.json", "element at index 0 is not a JSON object",
- "expected BuildNamespace object"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, NamespaceMissingKind) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [{"name": "test.cpp"}]
- })");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(
- ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize NestedBuildNamespace from file",
- "id_table.json", "at index 0",
- "failed to deserialize BuildNamespace from file",
- "missing required field 'kind' (expected BuildNamespaceKind)"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, NamespaceInvalidKind) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [{"kind": "InvalidKind", "name": "test.cpp"}]
- })");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize NestedBuildNamespace from file",
- "id_table.json", "at index 0",
- "failed to deserialize BuildNamespace from file",
- "invalid 'kind' BuildNamespaceKind value 'InvalidKind'"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, NamespaceMissingName) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [{"kind": "compilation_unit"}]
- })");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize NestedBuildNamespace from file",
- "id_table.json", "at index 0",
- "failed to deserialize BuildNamespace from file",
- "missing required field 'name'"}));
-}
-
-//===----------------------------------------------------------------------===//
-// TUSummary Read Tests - Data Directory and Files
-//===----------------------------------------------------------------------===//
-
-class JSONFormatReadDataTest : public JSONFormatTestBase {
-protected:
- void SetUpValidTUNamespaceAndIdTable(TempDir &Dir) {
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath,
- R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, "[]");
- }
-};
-
-TEST_F(JSONFormatReadDataTest, MissingDataDirectory) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndIdTable(Dir);
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings({"failed to read TUSummary from",
- "data directory does not exist"}));
-}
-
-TEST_F(JSONFormatReadDataTest, DataPathIsFile) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndIdTable(Dir);
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- writeFile(DataPath, "content");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings({"failed to read TUSummary from",
- "data path is not a directory"}));
-}
-
-TEST_F(JSONFormatReadDataTest, NonJSONFileInDataDirectory) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndIdTable(Dir);
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- llvm::SmallString<128> SummaryPath(DataPath);
- llvm::sys::path::append(SummaryPath, "summary.txt");
- writeFile(SummaryPath, "{}");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to read TUSummary data from file", "summary.txt",
- "failed to validate JSON file", "not a JSON file"}));
-}
-
-TEST_F(JSONFormatReadDataTest, FileNotJSONObject) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndIdTable(Dir);
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- llvm::SmallString<128> SummaryPath(DataPath);
- llvm::sys::path::append(SummaryPath, "summary.json");
- writeFile(SummaryPath, "[]");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings({"failed to read TUSummary from",
- "failed to read TUSummary data from file",
- "summary.json",
- "failed to read JSON object from file"}));
-}
-
-TEST_F(JSONFormatReadDataTest, MissingSummaryName) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndIdTable(Dir);
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- llvm::SmallString<128> SummaryPath(DataPath);
- llvm::sys::path::append(SummaryPath, "summary.json");
- writeFile(SummaryPath, R"({"summary_data": []})");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize summary data from file",
- "summary.json", "missing required field 'summary_name'",
- "expected string identifier for the analysis summary"}));
-}
-
-TEST_F(JSONFormatReadDataTest, MissingSummaryData) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndIdTable(Dir);
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- llvm::SmallString<128> SummaryPath(DataPath);
- llvm::sys::path::append(SummaryPath, "summary.json");
- writeFile(SummaryPath, R"({"summary_name": "test"})");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg, ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize summary data from file",
- "summary.json", "for summary 'test'",
- "missing or invalid field 'summary_data'",
- "expected JSON array of entity summaries"}));
-}
-
-TEST_F(JSONFormatReadDataTest, SummaryDataNotArray) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndIdTable(Dir);
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- llvm::SmallString<128> SummaryPath(DataPath);
- llvm::sys::path::append(SummaryPath, "summary.json");
- writeFile(SummaryPath,
- R"({"summary_name": "test", "summary_data": "not an array"})");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg, ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize summary data from file",
- "summary.json", "for summary 'test'",
- "missing or invalid field 'summary_data'",
- "expected JSON array of entity summaries"}));
-}
-
-TEST_F(JSONFormatReadDataTest, DuplicateSummaryName) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndIdTable(Dir);
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- llvm::SmallString<128> SummaryPath1(DataPath);
- llvm::sys::path::append(SummaryPath1, "summary1.json");
- writeFile(SummaryPath1, R"({"summary_name": "test", "summary_data": []})");
-
- llvm::SmallString<128> SummaryPath2(DataPath);
- llvm::sys::path::append(SummaryPath2, "summary2.json");
- writeFile(SummaryPath2, R"({"summary_name": "test", "summary_data": []})");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(
- ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from", "failed to read TUSummary data",
- "duplicate SummaryName 'test' encountered in file"}));
-}
-
-//===----------------------------------------------------------------------===//
-// TUSummary Read Tests - Entity Data
-//===----------------------------------------------------------------------===//
-
-class JSONFormatReadEntityDataTest : public JSONFormatTestBase {
-protected:
- void SetUpValidTUNamespaceIdTableAndDataDir(TempDir &Dir,
- llvm::StringRef SummaryDataJson) {
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath,
- R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, "[]");
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- llvm::SmallString<128> SummaryPath(DataPath);
- llvm::sys::path::append(SummaryPath, "summary.json");
- std::string JsonContent = R"({"summary_name": "test", "summary_data": )";
- JsonContent += SummaryDataJson.str();
- JsonContent += "}";
- writeFile(SummaryPath, JsonContent);
- }
-};
-
-TEST_F(JSONFormatReadEntityDataTest, ElementNotObject) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceIdTableAndDataDir(Dir, "[\"not an object\"]");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(
- ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize entity data map from file", "summary.json",
- "element at index 0 is not a JSON object",
- "expected object with 'entity_id' and 'entity_summary' fields"}));
-}
-
-TEST_F(JSONFormatReadEntityDataTest, MissingEntityId) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceIdTableAndDataDir(Dir, R"([{"entity_summary": {}}])");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(
- ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize entity data map entry from file",
- "summary.json", "at index 0", "missing required field 'entity_id'",
- "expected unsigned integer EntityId"}));
-}
-
-TEST_F(JSONFormatReadEntityDataTest, EntityIdNotInteger) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceIdTableAndDataDir(
- Dir, R"([{"entity_id": "not a number", "entity_summary": {}}])");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(
- ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize entity data map entry from file",
- "summary.json", "at index 0",
- "field 'entity_id' is not a valid unsigned 64-bit integer"}));
-}
-
-TEST_F(JSONFormatReadEntityDataTest, MissingEntitySummary) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceIdTableAndDataDir(Dir, R"([{"entity_id": 0}])");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize entity data map entry from file",
- "summary.json", "at index 0",
- "missing or invalid field 'entity_summary'",
- "expected EntitySummary JSON object"}));
-}
-
-TEST_F(JSONFormatReadEntityDataTest, EntitySummaryNotObject) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceIdTableAndDataDir(
- Dir, R"([{"entity_id": 0, "entity_summary": "not an object"}])");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize entity data map entry from file",
- "summary.json", "at index 0",
- "missing or invalid field 'entity_summary'",
- "expected EntitySummary JSON object"}));
-}
-
-TEST_F(JSONFormatReadEntityDataTest,
- EntitySummaryDeserializationNotImplemented) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceIdTableAndDataDir(
- Dir, R"([{"entity_id": 0, "entity_summary": {}}])");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize entity data map entry from file",
- "summary.json", "at index 0",
- "EntitySummary deserialization from file",
- "is not yet implemented"}));
-}
-
-// Note: DuplicateEntityId test cannot be implemented without EntitySummary
-// deserialization support, as the error occurs during EntitySummary parsing
-// before the duplicate check is reached.
-
-//===----------------------------------------------------------------------===//
-// TUSummary Write Tests
-//===----------------------------------------------------------------------===//
-
-class JSONFormatWriteTest : public JSONFormatTestBase {};
-
-TEST_F(JSONFormatWriteTest, Success) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- BuildNamespace TUNamespace = BuildNamespace::makeCompilationUnit("test.cpp");
- TUSummary Summary(TUNamespace);
-
- auto Error = Format.writeTUSummary(Summary, Dir.path());
- EXPECT_THAT_ERROR(std::move(Error), Succeeded());
-
- // Verify files were created
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- EXPECT_TRUE(llvm::sys::fs::exists(TUNamespacePath));
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- EXPECT_TRUE(llvm::sys::fs::exists(IdTablePath));
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- EXPECT_TRUE(llvm::sys::fs::exists(DataPath));
- EXPECT_TRUE(llvm::sys::fs::is_directory(DataPath));
-}
-
-TEST_F(JSONFormatWriteTest, DataDirectoryExistsAsFile) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- // Create 'data' as a file instead of directory
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- writeFile(DataPath, "content");
-
- BuildNamespace TUNamespace = BuildNamespace::makeCompilationUnit("test.cpp");
- TUSummary Summary(TUNamespace);
-
- auto Error = Format.writeTUSummary(Summary, Dir.path());
- ASSERT_TRUE(!!Error);
- std::string ErrorMsg = llvm::toString(std::move(Error));
- EXPECT_THAT(ErrorMsg, ContainsAllSubstrings(
- {"failed to write TUSummary to",
- "data path exists but is not a directory"}));
-}
-
-//===----------------------------------------------------------------------===//
-// TUSummary Success Cases
-//===----------------------------------------------------------------------===//
-
-class JSONFormatSuccessTest : public JSONFormatTestBase {};
-
-TEST_F(JSONFormatSuccessTest, ReadWithEmptyIdTable) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath,
- R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, "[]");
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- auto Result = Format.readTUSummary(Dir.path());
- EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
-}
-
-TEST_F(JSONFormatSuccessTest, ReadWithNonEmptyIdTable) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath,
- R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, R"([
- {
- "id": 0,
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
- }
- },
- {
- "id": 1,
- "name": {
- "usr": "c:@F at bar",
- "suffix": "1",
- "namespace": [
- {"kind": "compilation_unit", "name": "test.cpp"},
- {"kind": "link_unit", "name": "libtest.so"}
- ]
- }
- }
- ])");
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- auto Result = Format.readTUSummary(Dir.path());
- EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
-}
-
-TEST_F(JSONFormatSuccessTest, ReadWithEmptyData) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath,
- R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, "[]");
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- // Add an empty summary data file
- llvm::SmallString<128> SummaryPath(DataPath);
- llvm::sys::path::append(SummaryPath, "summary.json");
- writeFile(SummaryPath, R"({"summary_name": "test", "summary_data": []})");
-
- auto Result = Format.readTUSummary(Dir.path());
- EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
-}
-
-TEST_F(JSONFormatSuccessTest, ReadWithLinkUnitNamespace) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath, R"({"kind": "link_unit", "name": "libtest.so"})");
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, "[]");
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- auto Result = Format.readTUSummary(Dir.path());
- EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
-}
-
-//===----------------------------------------------------------------------===//
-// Round-Trip Tests
-//===----------------------------------------------------------------------===//
-
-class JSONFormatRoundTripTest : public JSONFormatTestBase {};
-
-TEST_F(JSONFormatRoundTripTest, EmptyIdTable) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- BuildNamespace TUNamespace = BuildNamespace::makeCompilationUnit("test.cpp");
- TUSummary Summary(TUNamespace);
-
- auto WriteError = Format.writeTUSummary(Summary, Dir.path());
- EXPECT_THAT_ERROR(std::move(WriteError), Succeeded());
-
- auto ReadResult = Format.readTUSummary(Dir.path());
- EXPECT_THAT_ERROR(ReadResult.takeError(), Succeeded());
-}
-
-TEST_F(JSONFormatRoundTripTest, NonEmptyIdTable) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- // Manually create the files to test roundtrip
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath,
- R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, R"([
- {
- "id": 0,
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
- }
- }
- ])");
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- auto ReadResult = Format.readTUSummary(Dir.path());
- ASSERT_THAT_EXPECTED(ReadResult, Succeeded());
-
- TempDir Dir2;
- ASSERT_TRUE(Dir2.isValid());
-
- auto WriteError = Format.writeTUSummary(*ReadResult, Dir2.path());
- EXPECT_THAT_ERROR(std::move(WriteError), Succeeded());
-
- // Verify the written files
- llvm::SmallString<128> TUNamespacePath2(Dir2.path());
- llvm::sys::path::append(TUNamespacePath2, "tu_namespace.json");
- EXPECT_TRUE(llvm::sys::fs::exists(TUNamespacePath2));
-}
-
-} // namespace
>From 7296235a0a53762ba7a25fe88eb1a94ff5e7ab6d Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 12:53:02 -0800
Subject: [PATCH 07/19] Cleanup changes to SummaryName.h
---
clang/include/clang/Analysis/Scalable/Model/SummaryName.h | 2 --
1 file changed, 2 deletions(-)
diff --git a/clang/include/clang/Analysis/Scalable/Model/SummaryName.h b/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
index 08ed67fe17bed..785fe0eb10372 100644
--- a/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
+++ b/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
@@ -10,8 +10,6 @@
#define LLVM_CLANG_ANALYSIS_SCALABLE_MODEL_SUMMARYNAME_H
#include "llvm/ADT/StringRef.h"
-#include "llvm/Support/Path.h"
-#include <cassert>
#include <string>
namespace clang::ssaf {
>From 0e8b42cb7eafb368557e44f1cbaa16eaf915e2ca Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 13:07:55 -0800
Subject: [PATCH 08/19] Cleanup changes to SerializationFormat.h and
JSONFormat.h
---
.../clang/Analysis/Scalable/Serialization/JSONFormat.h | 2 +-
.../Analysis/Scalable/Serialization/SerializationFormat.h | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
index ac809cd00e0cd..8d2e4ed6ea70b 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -48,7 +48,7 @@ class JSONFormat : public SerializationFormat {
llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) override;
llvm::Error writeTUSummary(const TUSummary &Summary,
- llvm::StringRef OutputDir) override;
+ llvm::StringRef Path) override;
using SerializerFn = llvm::function_ref<llvm::json::Object(
const EntitySummary &, const EntityIdConverter &)>;
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index 54098939cdfe4..adebf5a5d6b5b 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -46,10 +46,10 @@ class SerializationFormat {
getEntities(const EntityIdTable &EIT);
static decltype(EntityIdTable::Entities) &getEntities(EntityIdTable &EIT);
- static const EntityIdTable &getIdTable(const TUSummary &S);
- static EntityIdTable &getIdTable(TUSummary &S);
static const BuildNamespace &getTUNamespace(const TUSummary &S);
static BuildNamespace &getTUNamespace(TUSummary &S);
+ static const EntityIdTable &getIdTable(const TUSummary &S);
+ static EntityIdTable &getIdTable(TUSummary &S);
static const decltype(TUSummary::Data) &getData(const TUSummary &S);
static decltype(TUSummary::Data) &getData(TUSummary &S);
@@ -71,7 +71,7 @@ class SerializationFormat {
virtual llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) = 0;
virtual llvm::Error writeTUSummary(const TUSummary &Summary,
- llvm::StringRef OutputDir) = 0;
+ llvm::StringRef Path) = 0;
protected:
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS;
>From 6fa82c995cd32a807038e703b7be9b3b7d87bdce Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 13:11:34 -0800
Subject: [PATCH 09/19] Fix tests
---
.../Serialization/error-callgraph-invalid-caller.test | 2 +-
.../Serialization/error-callgraph-missing-field.test | 2 +-
.../Scalable/Serialization/error-defuse-invalid-use.test | 2 +-
.../Scalable/Serialization/error-defuse-missing-field.test | 2 +-
.../Scalable/Serialization/error-duplicate-entity.test | 2 +-
.../Serialization/error-entity-data-element-not-object.test | 2 +-
.../Serialization/error-entity-data-missing-entity-id.test | 2 +-
.../error-entity-data-missing-entity-summary.test | 2 +-
.../Scalable/Serialization/error-entity-id-not-uint64.test | 2 +-
.../Serialization/error-entity-name-missing-namespace.test | 4 ++--
.../Serialization/error-entity-name-missing-suffix.test | 4 ++--
.../Serialization/error-entity-name-missing-usr.test | 4 ++--
.../Serialization/error-entity-summary-no-format-info.test | 2 +-
.../Serialization/error-id-table-element-not-object.test | 2 +-
.../Serialization/error-id-table-entry-id-not-uint64.test | 2 +-
.../Serialization/error-id-table-entry-missing-id.test | 2 +-
.../Serialization/error-id-table-entry-missing-name.test | 2 +-
.../Analysis/Scalable/Serialization/error-invalid-kind.test | 2 +-
.../Scalable/Serialization/error-invalid-syntax.test | 2 +-
.../Analysis/Scalable/Serialization/error-missing-kind.test | 2 +-
.../Analysis/Scalable/Serialization/error-missing-name.test | 2 +-
.../Serialization/error-namespace-element-not-object.test | 6 +++---
.../Scalable/Serialization/error-not-json-extension.test | 4 ++--
.../Analysis/Scalable/Serialization/error-not-object.test | 2 +-
24 files changed, 30 insertions(+), 30 deletions(-)
diff --git a/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test b/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
index c051f41af4fc4..e10b242087ef3 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
@@ -3,6 +3,6 @@
// Test that CallGraph analysis validates caller field type
// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize summary data for summary 'CallGraph'
// CHECK: failed to deserialize entity data map entry at index 0
-// CHECK: failed to deserialize EntitySummary
// CHECK: CallGraph: 'caller' at index 0 is not a valid uint64
diff --git a/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test b/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
index 0e5e2877d06d2..a95855a99d257 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
@@ -3,6 +3,6 @@
// Test that CallGraph analysis requires 'call_graph' field
// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize summary data for summary 'CallGraph'
// CHECK: failed to deserialize entity data map entry at index 0
-// CHECK: failed to deserialize EntitySummary
// CHECK: CallGraph: missing or invalid 'call_graph' field
diff --git a/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test b/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
index 64675a201dd59..27b20b2c9fa62 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
@@ -3,6 +3,6 @@
// Test that DefUse analysis validates use value types
// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize summary data for summary 'DefUse'
// CHECK: failed to deserialize entity data map entry at index 0
-// CHECK: failed to deserialize EntitySummary
// CHECK: DefUse: use at index 0,0,0 is not a valid uint64
diff --git a/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test b/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
index 73409a2d46503..64574f78a0315 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
@@ -3,6 +3,6 @@
// Test that DefUse analysis requires 'def_use_chains' field
// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize summary data for summary 'DefUse'
// CHECK: failed to deserialize entity data map entry at index 0
-// CHECK: failed to deserialize EntitySummary
// CHECK: DefUse: missing or invalid 'def_use_chains' field
diff --git a/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test b/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
index 8e4d594cc75e6..542ea9fa23552 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
@@ -3,6 +3,6 @@
// Test that duplicate EntityName in id_table is detected
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable from file
+// CHECK: failed to deserialize EntityIdTable
// CHECK: duplicate EntityName found at index 1
// CHECK: EntityId=0 already exists in table
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
index 72bf04adc267c..9b247c7ee39e5 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
@@ -3,6 +3,6 @@
// Test that entity data elements must be objects
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize entity data map from file
+// CHECK: failed to deserialize entity data map
// CHECK: element at index 0 is not a JSON object
// CHECK: expected object with 'entity_id' and 'entity_summary' fields
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
index e2dd9a34b34d3..3d4f30805af5a 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
@@ -3,7 +3,7 @@
// Test that entity data entries must have 'entity_id' field
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize entity data map entry from file
+// CHECK: failed to deserialize entity data map entry
// CHECK: at index 0
// CHECK: missing required field 'entity_id'
// CHECK: expected unsigned integer EntityId
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
index 078bc4b068258..3229e02650360 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
@@ -3,7 +3,7 @@
// Test that entity data entries must have 'entity_summary' field
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize entity data map entry from file
+// CHECK: failed to deserialize entity data map entry
// CHECK: at index 0
// CHECK: missing or invalid field 'entity_summary'
// CHECK: expected EntitySummary JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test b/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
index f5903b02d7eeb..eede8033fc575 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
@@ -3,6 +3,6 @@
// Test that entity_id field must be a valid uint64
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize entity data map entry from file
+// CHECK: failed to deserialize entity data map entry
// CHECK: at index 0
// CHECK: field 'entity_id' is not a valid unsigned 64-bit integer
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
index f9230bed9b25f..da2f56d600b58 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
@@ -3,7 +3,7 @@
// Test that EntityName must have 'namespace' field
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable entry from file
-// CHECK: failed to deserialize EntityName from file
+// CHECK: failed to deserialize EntityIdTable entry
+// CHECK: failed to deserialize EntityName
// CHECK: missing or invalid field 'namespace'
// CHECK: expected JSON array of BuildNamespace objects
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
index 70a75f8b63433..ae0975fd850d9 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
@@ -3,6 +3,6 @@
// Test that EntityName must have 'suffix' field
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable entry from file
-// CHECK: failed to deserialize EntityName from file
+// CHECK: failed to deserialize EntityIdTable entry
+// CHECK: failed to deserialize EntityName
// CHECK: missing required field 'suffix'
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
index 8531cfa1bd307..db48593c232d7 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
@@ -3,7 +3,7 @@
// Test that EntityName must have 'usr' field
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable entry from file
-// CHECK: failed to deserialize EntityName from file
+// CHECK: failed to deserialize EntityIdTable entry
+// CHECK: failed to deserialize EntityName
// CHECK: missing required field 'usr'
// CHECK: Unified Symbol Resolution string
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test b/clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test
index 9a317f1270c4a..075eddaf271c8 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test
@@ -3,5 +3,5 @@
// Test that EntitySummary deserialization requires registered FormatInfo
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntitySummary from file
+// CHECK: failed to deserialize EntitySummary
// CHECK: no FormatInfo was registered for summary name
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test
index 5e1359a3ce94c..eb8a70b5d8933 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test
@@ -3,6 +3,6 @@
// Test that id_table elements must be objects
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable from file
+// CHECK: failed to deserialize EntityIdTable
// CHECK: element at index 0 is not a JSON object
// CHECK: expected EntityIdTable entry with 'id' and 'name' fields
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test
index 4e2706d89f52d..7d95da6189017 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test
@@ -3,6 +3,6 @@
// Test that id_table entry 'id' field must be a valid uint64
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: failed to deserialize EntityIdTable entry
// CHECK: field 'id' is not a valid unsigned 64-bit integer
// CHECK: expected non-negative EntityId value
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test
index 423cd54b3c1f6..846d0088ec974 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test
@@ -3,6 +3,6 @@
// Test that id_table entries must have 'id' field
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: failed to deserialize EntityIdTable entry
// CHECK: missing required field 'id'
// CHECK: expected unsigned integer EntityId
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test
index de56b93de491b..2404199b14c44 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test
@@ -3,6 +3,6 @@
// Test that id_table entries must have 'name' field
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: failed to deserialize EntityIdTable entry
// CHECK: missing or invalid field 'name'
// CHECK: expected EntityName JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test b/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
index cecc51e633f40..41b994f625d87 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
@@ -3,5 +3,5 @@
// Test that invalid kind value is detected
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize BuildNamespace from file
+// CHECK: failed to deserialize BuildNamespace
// CHECK: invalid 'kind' BuildNamespaceKind value 'InvalidKind'
diff --git a/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test b/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
index 1724b13031c13..529ebd0f9a6e2 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
@@ -3,4 +3,4 @@
// Test that invalid JSON syntax is detected
// CHECK: failed to read TUSummary
-// CHECK: failed to read JSON object from file
+// CHECK: failed to read JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test b/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
index d26a39d46c4b2..24c276a825c7a 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
@@ -3,6 +3,6 @@
// Test that missing kind field in tu_namespace is detected
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize BuildNamespace from file
+// CHECK: failed to deserialize BuildNamespace
// CHECK: missing required field 'kind'
// CHECK: expected BuildNamespaceKind
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-name.test b/clang/test/Analysis/Scalable/Serialization/error-missing-name.test
index d88a8c46bc85f..2fb17423943dc 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-missing-name.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-name.test
@@ -3,5 +3,5 @@
// Test that missing name field in tu_namespace is detected
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize BuildNamespace from file
+// CHECK: failed to deserialize BuildNamespace
// CHECK: missing required field 'name'
diff --git a/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
index 4723d9d4434d9..55006eb230c21 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
@@ -3,8 +3,8 @@
// Test that namespace array elements must be objects
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable entry from file
-// CHECK: failed to deserialize EntityName from file
-// CHECK: failed to deserialize NestedBuildNamespace from file
+// CHECK: failed to deserialize EntityIdTable entry
+// CHECK: failed to deserialize EntityName
+// CHECK: failed to deserialize NestedBuildNamespace
// CHECK: element at index 0 is not a JSON object
// CHECK: expected BuildNamespace object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test b/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
index 57bcab0bf8212..1a56fc6e13dc6 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
@@ -1,8 +1,8 @@
-// RUN: not ssaf-serialization-format-test %S/not_json-extension.txt 2>&1 | FileCheck %s
+// RUN: not ssaf-serialization-format-test %S/Inputs/not-json-extension.txt 2>&1 | FileCheck %s
// Test that files with non-.json extensions are rejected
// CHECK: failed to read TUSummary
-// CHECK: failed to read JSON object from file
+// CHECK: failed to read JSON object
// CHECK: failed to validate JSON file
// CHECK: not a JSON file
diff --git a/clang/test/Analysis/Scalable/Serialization/error-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-not-object.test
index ec34a20c9f641..20409e7642b32 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-not-object.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-not-object.test
@@ -3,4 +3,4 @@
// Test that JSON array (not object) is rejected
// CHECK: failed to read TUSummary
-// CHECK: failed to read JSON object from file
+// CHECK: failed to read JSON object
>From 566a25292baa78c334e1fd0a6694a79694d67206 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 14:02:47 -0800
Subject: [PATCH 10/19] Add check for directory
---
.../Scalable/Serialization/JSONFormat.cpp | 5 +
.../Inputs/callgraph-invalid-caller.json | 25 ++++
.../Inputs/callgraph-missing-field.json | 18 +++
.../Inputs/data-element-not-object.json | 8 ++
.../Inputs/data-entry-missing-data.json | 12 ++
.../Inputs/data-missing-summary-name.json | 12 ++
.../Serialization/Inputs/data-not-array.json | 8 ++
.../Inputs/defuse-invalid-use.json | 30 +++++
.../Inputs/defuse-missing-field.json | 18 +++
.../Inputs/directory.json/.gitkeep | 0
.../Inputs/duplicate-entity.json | 35 ++++++
.../Inputs/duplicate-summary-name.json | 17 +++
.../entity-data-element-not-object.json | 13 +++
.../Inputs/entity-data-missing-entity-id.json | 17 +++
.../entity-data-missing-entity-summary.json | 17 +++
.../Inputs/entity-id-not-uint64.json | 18 +++
.../Inputs/entity-name-missing-namespace.json | 16 +++
.../Inputs/entity-name-missing-suffix.json | 21 ++++
.../Inputs/entity-name-missing-usr.json | 21 ++++
.../Inputs/entity-summary-no-format-info.json | 18 +++
.../Inputs/id-table-element-not-object.json | 8 ++
.../Inputs/id-table-entry-id-not-uint64.json | 22 ++++
.../Inputs/id-table-entry-missing-id.json | 21 ++++
.../Inputs/id-table-entry-missing-name.json | 10 ++
.../Inputs/id-table-not-array.json | 8 ++
.../Serialization/Inputs/invalid-kind.json | 8 ++
.../Serialization/Inputs/invalid-syntax.json | 1 +
.../Serialization/Inputs/missing-data.json | 7 ++
.../Inputs/missing-id-table.json | 7 ++
.../Serialization/Inputs/missing-kind.json | 7 ++
.../Serialization/Inputs/missing-name.json | 7 ++
.../Inputs/missing-tu-namespace.json | 4 +
.../Inputs/namespace-element-not-object.json | 17 +++
.../Inputs/not-json-extension.txt | 8 ++
.../Serialization/Inputs/not-object.json | 1 +
.../Inputs/valid-callgraph-expected.json | 74 ++++++++++++
.../Serialization/Inputs/valid-callgraph.json | 66 +++++++++++
.../Inputs/valid-defuse-expected.json | 108 ++++++++++++++++++
.../Serialization/Inputs/valid-defuse.json | 100 ++++++++++++++++
.../Inputs/valid-empty-expected.json | 8 ++
.../Serialization/Inputs/valid-empty.json | 8 ++
.../Inputs/valid-link-unit-expected.json | 8 ++
.../Serialization/Inputs/valid-link-unit.json | 8 ++
.../valid-with-empty-data-entry-expected.json | 13 +++
.../Inputs/valid-with-empty-data-entry.json | 13 +++
.../Inputs/valid-with-idtable-expected.json | 39 +++++++
.../Inputs/valid-with-idtable.json | 39 +++++++
.../Serialization/error-is-directory.test | 7 ++
48 files changed, 956 insertions(+)
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/data-element-not-object.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/data-entry-missing-data.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/data-missing-summary-name.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/data-not-array.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/directory.json/.gitkeep
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-entity.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-namespace.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-suffix.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-usr.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-element-not-object.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-id-not-uint64.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-id.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-name.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-not-array.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/invalid-kind.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/invalid-syntax.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-data.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-id-table.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-kind.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-name.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-tu-namespace.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/namespace-element-not-object.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/not-json-extension.txt
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/not-object.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty-expected.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit-expected.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable-expected.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-is-directory.test
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index 1d3376ef2c9f2..21f239d9d1894 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -40,6 +40,11 @@ llvm::Error isJSONFile(llvm::StringRef Path) {
"file does not exist: '%s'",
Path.str().c_str());
+ if (llvm::sys::fs::is_directory(Path))
+ return llvm::createStringError(std::errc::is_a_directory,
+ "path is a directory, not a file: '%s'",
+ Path.str().c_str());
+
if (!Path.ends_with_insensitive(".json"))
return llvm::createStringError(std::errc::invalid_argument,
"not a JSON file: '%s'", Path.str().c_str());
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json
new file mode 100644
index 0000000000000..9cb082d00fc4c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json
@@ -0,0 +1,25 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "CallGraph",
+ "data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "call_graph": [
+ {
+ "caller": "not_a_number",
+ "callees": []
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json
new file mode 100644
index 0000000000000..243842fdc1f57
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json
@@ -0,0 +1,18 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "CallGraph",
+ "data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {}
+ }
+ ]
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/data-element-not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/data-element-not-object.json
new file mode 100644
index 0000000000000..ca953efa67e77
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/data-element-not-object.json
@@ -0,0 +1,8 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": ["not an object"]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/data-entry-missing-data.json b/clang/test/Analysis/Scalable/Serialization/Inputs/data-entry-missing-data.json
new file mode 100644
index 0000000000000..a5631d1a60559
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/data-entry-missing-data.json
@@ -0,0 +1,12 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "test"
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/data-missing-summary-name.json b/clang/test/Analysis/Scalable/Serialization/Inputs/data-missing-summary-name.json
new file mode 100644
index 0000000000000..d4f48d9d785f2
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/data-missing-summary-name.json
@@ -0,0 +1,12 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "data": []
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/data-not-array.json b/clang/test/Analysis/Scalable/Serialization/Inputs/data-not-array.json
new file mode 100644
index 0000000000000..70daf052792bd
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/data-not-array.json
@@ -0,0 +1,8 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": "not an array"
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json
new file mode 100644
index 0000000000000..4fb91b8382648
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json
@@ -0,0 +1,30 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "DefUse",
+ "data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "def_use_chains": [
+ {
+ "variable": 0,
+ "definitions": [
+ {
+ "definition": 10,
+ "uses": ["invalid"]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json
new file mode 100644
index 0000000000000..b7dea48c27899
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json
@@ -0,0 +1,18 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "DefUse",
+ "data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {}
+ }
+ ]
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/directory.json/.gitkeep b/clang/test/Analysis/Scalable/Serialization/Inputs/directory.json/.gitkeep
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-entity.json b/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-entity.json
new file mode 100644
index 0000000000000..dd8962c409cc8
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-entity.json
@@ -0,0 +1,35 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [
+ {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ }
+ ]
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [
+ {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ }
+ ]
+ }
+ }
+ ],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json b/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json
new file mode 100644
index 0000000000000..5c39b179364ca
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json
@@ -0,0 +1,17 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "test",
+ "data": []
+ },
+ {
+ "summary_name": "test",
+ "data": []
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json
new file mode 100644
index 0000000000000..98018650c368f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json
@@ -0,0 +1,13 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "test",
+ "data": ["not an object"]
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json
new file mode 100644
index 0000000000000..c80f4e7a4ce64
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json
@@ -0,0 +1,17 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "test",
+ "data": [
+ {
+ "entity_summary": {}
+ }
+ ]
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json
new file mode 100644
index 0000000000000..692369d342904
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json
@@ -0,0 +1,17 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "test",
+ "data": [
+ {
+ "entity_id": 0
+ }
+ ]
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json
new file mode 100644
index 0000000000000..33d9855fb1da4
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json
@@ -0,0 +1,18 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "test",
+ "data": [
+ {
+ "entity_id": "not a number",
+ "entity_summary": {}
+ }
+ ]
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-namespace.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-namespace.json
new file mode 100644
index 0000000000000..d11c1064bbeb9
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-namespace.json
@@ -0,0 +1,16 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": ""
+ }
+ }
+ ],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-suffix.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-suffix.json
new file mode 100644
index 0000000000000..3598e8d95b2ea
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-suffix.json
@@ -0,0 +1,21 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "namespace": [
+ {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ }
+ ]
+ }
+ }
+ ],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-usr.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-usr.json
new file mode 100644
index 0000000000000..3fab36bcb4916
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-usr.json
@@ -0,0 +1,21 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "namespace": [
+ {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ }
+ ]
+ }
+ }
+ ],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json
new file mode 100644
index 0000000000000..c56402615956c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json
@@ -0,0 +1,18 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "test",
+ "data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {}
+ }
+ ]
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-element-not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-element-not-object.json
new file mode 100644
index 0000000000000..ea604010a81e9
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-element-not-object.json
@@ -0,0 +1,8 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": ["not an object"],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-id-not-uint64.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-id-not-uint64.json
new file mode 100644
index 0000000000000..5a736543c5dbc
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-id-not-uint64.json
@@ -0,0 +1,22 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "id": -1,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [
+ {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ }
+ ]
+ }
+ }
+ ],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-id.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-id.json
new file mode 100644
index 0000000000000..f911bfb7ab94e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-id.json
@@ -0,0 +1,21 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [
+ {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ }
+ ]
+ }
+ }
+ ],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-name.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-name.json
new file mode 100644
index 0000000000000..e5cd1aea3e1ae
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-name.json
@@ -0,0 +1,10 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {"id": 0}
+ ],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-not-array.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-not-array.json
new file mode 100644
index 0000000000000..fb5624207bc2c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-not-array.json
@@ -0,0 +1,8 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": {},
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-kind.json b/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-kind.json
new file mode 100644
index 0000000000000..a39c20d3016a0
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-kind.json
@@ -0,0 +1,8 @@
+{
+ "tu_namespace": {
+ "kind": "InvalidKind",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-syntax.json b/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-syntax.json
new file mode 100644
index 0000000000000..b0e13f61aa06e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-syntax.json
@@ -0,0 +1 @@
+{ invalid json }
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-data.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-data.json
new file mode 100644
index 0000000000000..071de7214fd23
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-data.json
@@ -0,0 +1,7 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-id-table.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-id-table.json
new file mode 100644
index 0000000000000..126e8fba55b5f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-id-table.json
@@ -0,0 +1,7 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-kind.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-kind.json
new file mode 100644
index 0000000000000..8aa5c5fb115e1
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-kind.json
@@ -0,0 +1,7 @@
+{
+ "tu_namespace": {
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-name.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-name.json
new file mode 100644
index 0000000000000..5a7c2f2f80c08
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-name.json
@@ -0,0 +1,7 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit"
+ },
+ "id_table": [],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-tu-namespace.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-tu-namespace.json
new file mode 100644
index 0000000000000..536918c99e182
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-tu-namespace.json
@@ -0,0 +1,4 @@
+{
+ "id_table": [],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/namespace-element-not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/namespace-element-not-object.json
new file mode 100644
index 0000000000000..c6715f2e0e5cc
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/namespace-element-not-object.json
@@ -0,0 +1,17 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": ["not an object"]
+ }
+ }
+ ],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/not-json-extension.txt b/clang/test/Analysis/Scalable/Serialization/Inputs/not-json-extension.txt
new file mode 100644
index 0000000000000..11966839e2292
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/not-json-extension.txt
@@ -0,0 +1,8 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/not-object.json
new file mode 100644
index 0000000000000..fe51488c7066f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/not-object.json
@@ -0,0 +1 @@
+[]
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json
new file mode 100644
index 0000000000000..53af0a652cca7
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json
@@ -0,0 +1,74 @@
+{
+ "data": [
+ {
+ "data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "call_graph": [
+ {
+ "callees": [
+ 1,
+ 2
+ ],
+ "caller": 0
+ },
+ {
+ "callees": [
+ 2,
+ 3
+ ],
+ "caller": 1
+ },
+ {
+ "callees": [
+ 3
+ ],
+ "caller": 2
+ }
+ ]
+ }
+ }
+ ],
+ "summary_name": "CallGraph"
+ }
+ ],
+ "id_table": [
+ {
+ "id": 2,
+ "name": {
+ "namespace": [],
+ "suffix": "",
+ "usr": "c:@F at bar"
+ }
+ },
+ {
+ "id": 3,
+ "name": {
+ "namespace": [],
+ "suffix": "",
+ "usr": "c:@F at baz"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "namespace": [],
+ "suffix": "",
+ "usr": "c:@F at foo"
+ }
+ },
+ {
+ "id": 0,
+ "name": {
+ "namespace": [],
+ "suffix": "",
+ "usr": "c:@F at main"
+ }
+ }
+ ],
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "call_graph_test.cpp"
+ }
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json
new file mode 100644
index 0000000000000..1545092b614ff
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json
@@ -0,0 +1,66 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "call_graph_test.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at main",
+ "suffix": "",
+ "namespace": []
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": []
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "usr": "c:@F at bar",
+ "suffix": "",
+ "namespace": []
+ }
+ },
+ {
+ "id": 3,
+ "name": {
+ "usr": "c:@F at baz",
+ "suffix": "",
+ "namespace": []
+ }
+ }
+ ],
+ "data": [
+ {
+ "summary_name": "CallGraph",
+ "data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "call_graph": [
+ {
+ "caller": 0,
+ "callees": [1, 2]
+ },
+ {
+ "caller": 1,
+ "callees": [2, 3]
+ },
+ {
+ "caller": 2,
+ "callees": [3]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json
new file mode 100644
index 0000000000000..915d4261b2bc1
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json
@@ -0,0 +1,108 @@
+{
+ "data": [
+ {
+ "data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "def_use_chains": [
+ {
+ "definitions": [
+ {
+ "definition": 10,
+ "uses": [
+ 12,
+ 13
+ ]
+ },
+ {
+ "definition": 14,
+ "uses": [
+ 13
+ ]
+ }
+ ],
+ "variable": 0
+ },
+ {
+ "definitions": [
+ {
+ "definition": 11,
+ "uses": [
+ 12,
+ 14
+ ]
+ }
+ ],
+ "variable": 1
+ }
+ ]
+ }
+ }
+ ],
+ "summary_name": "DefUse"
+ }
+ ],
+ "id_table": [
+ {
+ "id": 12,
+ "name": {
+ "namespace": [],
+ "suffix": "",
+ "usr": "c:@Stmt at line10"
+ }
+ },
+ {
+ "id": 13,
+ "name": {
+ "namespace": [],
+ "suffix": "",
+ "usr": "c:@Stmt at line12"
+ }
+ },
+ {
+ "id": 14,
+ "name": {
+ "namespace": [],
+ "suffix": "",
+ "usr": "c:@Stmt at line15"
+ }
+ },
+ {
+ "id": 10,
+ "name": {
+ "namespace": [],
+ "suffix": "",
+ "usr": "c:@Stmt at line5"
+ }
+ },
+ {
+ "id": 11,
+ "name": {
+ "namespace": [],
+ "suffix": "",
+ "usr": "c:@Stmt at line8"
+ }
+ },
+ {
+ "id": 0,
+ "name": {
+ "namespace": [],
+ "suffix": "",
+ "usr": "c:@V at x"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "namespace": [],
+ "suffix": "",
+ "usr": "c:@V at y"
+ }
+ }
+ ],
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "defuse_test.cpp"
+ }
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json
new file mode 100644
index 0000000000000..88581b4813a58
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json
@@ -0,0 +1,100 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "defuse_test.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@V at x",
+ "suffix": "",
+ "namespace": []
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "usr": "c:@V at y",
+ "suffix": "",
+ "namespace": []
+ }
+ },
+ {
+ "id": 10,
+ "name": {
+ "usr": "c:@Stmt at line5",
+ "suffix": "",
+ "namespace": []
+ }
+ },
+ {
+ "id": 11,
+ "name": {
+ "usr": "c:@Stmt at line8",
+ "suffix": "",
+ "namespace": []
+ }
+ },
+ {
+ "id": 12,
+ "name": {
+ "usr": "c:@Stmt at line10",
+ "suffix": "",
+ "namespace": []
+ }
+ },
+ {
+ "id": 13,
+ "name": {
+ "usr": "c:@Stmt at line12",
+ "suffix": "",
+ "namespace": []
+ }
+ },
+ {
+ "id": 14,
+ "name": {
+ "usr": "c:@Stmt at line15",
+ "suffix": "",
+ "namespace": []
+ }
+ }
+ ],
+ "data": [
+ {
+ "summary_name": "DefUse",
+ "data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "def_use_chains": [
+ {
+ "variable": 0,
+ "definitions": [
+ {
+ "definition": 10,
+ "uses": [12, 13]
+ },
+ {
+ "definition": 14,
+ "uses": [13]
+ }
+ ]
+ },
+ {
+ "variable": 1,
+ "definitions": [
+ {
+ "definition": 11,
+ "uses": [12, 14]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty-expected.json
new file mode 100644
index 0000000000000..e7bd20a47921c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty-expected.json
@@ -0,0 +1,8 @@
+{
+ "data": [],
+ "id_table": [],
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ }
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty.json
new file mode 100644
index 0000000000000..11966839e2292
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty.json
@@ -0,0 +1,8 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit-expected.json
new file mode 100644
index 0000000000000..840b5289f010a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit-expected.json
@@ -0,0 +1,8 @@
+{
+ "data": [],
+ "id_table": [],
+ "tu_namespace": {
+ "kind": "link_unit",
+ "name": "libtest.so"
+ }
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit.json
new file mode 100644
index 0000000000000..98e1cce334fd7
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit.json
@@ -0,0 +1,8 @@
+{
+ "tu_namespace": {
+ "kind": "link_unit",
+ "name": "libtest.so"
+ },
+ "id_table": [],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json
new file mode 100644
index 0000000000000..ddda2507cdfec
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json
@@ -0,0 +1,13 @@
+{
+ "data": [
+ {
+ "data": [],
+ "summary_name": "test"
+ }
+ ],
+ "id_table": [],
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ }
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json
new file mode 100644
index 0000000000000..0d62ee2a6d232
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json
@@ -0,0 +1,13 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "test",
+ "data": []
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable-expected.json
new file mode 100644
index 0000000000000..14142a8a22c88
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable-expected.json
@@ -0,0 +1,39 @@
+{
+ "data": [],
+ "id_table": [
+ {
+ "id": 1,
+ "name": {
+ "namespace": [
+ {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ {
+ "kind": "link_unit",
+ "name": "libtest.so"
+ }
+ ],
+ "suffix": "1",
+ "usr": "c:@F at bar"
+ }
+ },
+ {
+ "id": 0,
+ "name": {
+ "namespace": [
+ {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at foo"
+ }
+ }
+ ],
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ }
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable.json
new file mode 100644
index 0000000000000..76515b8ba750d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable.json
@@ -0,0 +1,39 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [
+ {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ }
+ ]
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "usr": "c:@F at bar",
+ "suffix": "1",
+ "namespace": [
+ {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ {
+ "kind": "link_unit",
+ "name": "libtest.so"
+ }
+ ]
+ }
+ }
+ ],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/error-is-directory.test b/clang/test/Analysis/Scalable/Serialization/error-is-directory.test
new file mode 100644
index 0000000000000..e30bdd137af89
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-is-directory.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/directory.json 2>&1 | FileCheck %s
+
+// Test that directories with .json extension are rejected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to validate JSON file
+// CHECK: path is a directory, not a file
>From 6cc40551b27012582f5a7508f62057c35712e290 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 14:13:40 -0800
Subject: [PATCH 11/19] More tests
---
.../Serialization/Inputs/no-read-permission.json | 8 ++++++++
.../Serialization/error-broken-symlink.test | 14 ++++++++++++++
.../Serialization/error-permission-denied.test | 14 ++++++++++++++
3 files changed, 36 insertions(+)
create mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/no-read-permission.json
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-broken-symlink.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-permission-denied.test
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/no-read-permission.json b/clang/test/Analysis/Scalable/Serialization/Inputs/no-read-permission.json
new file mode 100644
index 0000000000000..11966839e2292
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/no-read-permission.json
@@ -0,0 +1,8 @@
+{
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/Serialization/error-broken-symlink.test b/clang/test/Analysis/Scalable/Serialization/error-broken-symlink.test
new file mode 100644
index 0000000000000..d1e61603c06cc
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-broken-symlink.test
@@ -0,0 +1,14 @@
+## Test that broken symlinks with .json extension are rejected.
+## Unsupported on Windows as symlinks behave differently there.
+
+# UNSUPPORTED: system-windows
+
+# RUN: ln -sf non-existent-file.json %t-broken-symlink.json
+# RUN: not ssaf-serialization-format-test %t-broken-symlink.json 2>&1 | FileCheck %s
+# RUN: rm %t-broken-symlink.json
+
+## Test that broken symlinks are detected as non-existent files
+
+# CHECK: failed to read TUSummary
+# CHECK: failed to validate JSON file
+# CHECK: file does not exist
diff --git a/clang/test/Analysis/Scalable/Serialization/error-permission-denied.test b/clang/test/Analysis/Scalable/Serialization/error-permission-denied.test
new file mode 100644
index 0000000000000..b89f32d19620d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-permission-denied.test
@@ -0,0 +1,14 @@
+## Test that files without read permissions are handled correctly.
+## Unsupported on Windows as chmod doesn't work reliably there.
+
+# UNSUPPORTED: system-windows
+# REQUIRES: non-root-user
+
+# RUN: chmod 000 %S/Inputs/no-read-permission.json
+# RUN: not ssaf-serialization-format-test %S/Inputs/no-read-permission.json 2>&1 | FileCheck %s
+# RUN: chmod 644 %S/Inputs/no-read-permission.json
+
+## Test that permission denied error is reported when file cannot be read
+
+# CHECK: failed to read TUSummary
+# CHECK: failed to read file
>From b3cc14c04dcb85eba524b3a7bbf1f3506df8741f Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 17:18:08 -0800
Subject: [PATCH 12/19] Test more code paths
---
.../Serialization/error-write-flush-failure.test | 13 +++++++++++++
.../Serialization/error-write-readonly-dir.test | 16 ++++++++++++++++
2 files changed, 29 insertions(+)
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-write-flush-failure.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-write-readonly-dir.test
diff --git a/clang/test/Analysis/Scalable/Serialization/error-write-flush-failure.test b/clang/test/Analysis/Scalable/Serialization/error-write-flush-failure.test
new file mode 100644
index 0000000000000..9bbdc4e8a611e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-write-flush-failure.test
@@ -0,0 +1,13 @@
+## Test that write flush failures are handled correctly.
+## Uses /dev/full which always reports "disk full" errors on write.
+## This special device file successfully opens but fails on write/flush operations.
+
+# REQUIRES: system-linux
+
+# RUN: not ssaf-serialization-format-test %S/Inputs/valid-defuse.json -o /dev/full 2>&1 | FileCheck %s
+
+## Test that flush/write error is reported when device is full
+## This tests JSONFormat.cpp:97-99 (write/flush failure path)
+
+# CHECK: failed to write TUSummary
+# CHECK: write failed
diff --git a/clang/test/Analysis/Scalable/Serialization/error-write-readonly-dir.test b/clang/test/Analysis/Scalable/Serialization/error-write-readonly-dir.test
new file mode 100644
index 0000000000000..47087f0a1de12
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-write-readonly-dir.test
@@ -0,0 +1,16 @@
+## Test that write failures to read-only directories are handled correctly.
+## Unsupported on Windows as chmod doesn't work reliably there.
+
+# UNSUPPORTED: system-windows
+# REQUIRES: non-root-user
+
+# RUN: rm -rf %t-readonly && mkdir -p %t-readonly
+# RUN: chmod 444 %t-readonly
+# RUN: not ssaf-serialization-format-test %S/Inputs/valid-empty.json -o %t-readonly/output.json 2>&1 | FileCheck %s
+# RUN: chmod 755 %t-readonly && rm -rf %t-readonly
+
+## Test that write error is reported when output directory is read-only
+## This tests JSONFormat.cpp:89-92 (file open failure path)
+
+# CHECK: failed to write TUSummary
+# CHECK: failed to open
>From 6d468d6e4f93a72068c0d5fcc359d03e8e73bb26 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Sun, 8 Feb 2026 09:25:21 -0800
Subject: [PATCH 13/19] Fix JSONFormat and update tests
---
.../Scalable/Serialization/JSONFormat.h | 34 +-
.../Scalable/Serialization/JSONFormat.cpp | 310 ++++++++++--------
.../error-callgraph-invalid-caller.test | 5 +-
.../error-callgraph-missing-field.test | 5 +-
.../error-data-element-not-object.test | 4 +-
.../error-data-entry-missing-data.test | 5 +-
.../error-data-missing-summary-name.test | 2 +
.../error-defuse-invalid-use.test | 5 +-
.../error-defuse-missing-field.test | 5 +-
.../error-duplicate-summary-name.test | 3 +-
.../error-entity-data-element-not-object.test | 4 +-
.../error-entity-data-missing-entity-id.test | 4 +-
...or-entity-data-missing-entity-summary.test | 4 +-
.../error-entity-id-not-uint64.test | 4 +-
.../ExampleAnalyses.cpp | 45 ++-
.../SSAFSerializationFormatTest.cpp | 2 +-
16 files changed, 269 insertions(+), 172 deletions(-)
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
index 8d2e4ed6ea70b..5c8e95b35a87b 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -58,45 +58,50 @@ class JSONFormat : public SerializationFormat {
const EntityIdConverter &)>;
using FormatInfo = FormatInfoEntry<SerializerFn, DeserializerFn>;
- std::map<SummaryName, FormatInfo> FormatInfos;
private:
+ std::map<SummaryName, FormatInfo> FormatInfos;
+
EntityId entityIdFromJSON(const uint64_t EntityIdIndex) const;
uint64_t entityIdToJSON(EntityId EI) const;
llvm::Expected<BuildNamespaceKind>
- buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr);
+ buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr) const;
llvm::Expected<BuildNamespace>
- buildNamespaceFromJSON(const llvm::json::Object &BuildNamespaceObject);
+ buildNamespaceFromJSON(const llvm::json::Object &BuildNamespaceObject) const;
llvm::json::Object buildNamespaceToJSON(const BuildNamespace &BN) const;
llvm::Expected<NestedBuildNamespace> nestedBuildNamespaceFromJSON(
- const llvm::json::Array &NestedBuildNamespaceArray);
+ const llvm::json::Array &NestedBuildNamespaceArray) const;
llvm::json::Array
nestedBuildNamespaceToJSON(const NestedBuildNamespace &NBN) const;
llvm::Expected<EntityName>
- entityNameFromJSON(const llvm::json::Object &EntityNameObject);
+ entityNameFromJSON(const llvm::json::Object &EntityNameObject) const;
llvm::json::Object entityNameToJSON(const EntityName &EN) const;
llvm::Expected<std::pair<EntityName, EntityId>> entityIdTableEntryFromJSON(
- const llvm::json::Object &EntityIdTableEntryObject);
+ const llvm::json::Object &EntityIdTableEntryObject) const;
llvm::Expected<EntityIdTable>
- entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray);
+ entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray) const;
llvm::json::Array entityIdTableToJSON(const EntityIdTable &IdTable) const;
llvm::Expected<std::unique_ptr<EntitySummary>>
entitySummaryFromJSON(const SummaryName &SN,
const llvm::json::Object &EntitySummaryObject,
- EntityIdTable &IdTable);
+ EntityIdTable &IdTable) const;
llvm::json::Object entitySummaryToJSON(const SummaryName &SN,
const EntitySummary &ES) const;
+ llvm::Expected<std::pair<EntityId, std::unique_ptr<EntitySummary>>>
+ entityDataMapEntryFromJSON(const llvm::json::Object &EntityDataMapEntryObject,
+ const SummaryName &SN,
+ EntityIdTable &IdTable) const;
llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
entityDataMapFromJSON(const SummaryName &SN,
const llvm::json::Array &EntityDataArray,
- EntityIdTable &IdTable);
+ EntityIdTable &IdTable) const;
llvm::json::Array
entityDataMapToJSON(const SummaryName &SN,
const std::map<EntityId, std::unique_ptr<EntitySummary>>
@@ -105,10 +110,19 @@ class JSONFormat : public SerializationFormat {
llvm::Expected<std::pair<SummaryName,
std::map<EntityId, std::unique_ptr<EntitySummary>>>>
summaryDataMapEntryFromJSON(const llvm::json::Object &SummaryDataObject,
- EntityIdTable &IdTable);
+ EntityIdTable &IdTable) const;
llvm::json::Object summaryDataMapEntryToJSON(
const SummaryName &SN,
const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const;
+
+ llvm::Expected<
+ std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+ summaryDataMapFromJSON(const llvm::json::Array &SummaryDataArray,
+ EntityIdTable &IdTable) const;
+ llvm::json::Array summaryDataMapToJSON(
+ const std::map<SummaryName,
+ std::map<EntityId, std::unique_ptr<EntitySummary>>>
+ &SummaryDataMap) const;
};
} // namespace clang::ssaf
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index 21f239d9d1894..f374041f23827 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -28,8 +28,6 @@ llvm::Error wrapError(llvm::Error E, const char *Fmt, Args &&...Vals) {
std::forward<Args>(Vals)...);
}
-} // namespace
-
//----------------------------------------------------------------------------
// JSON Reader and Writer
//----------------------------------------------------------------------------
@@ -80,7 +78,7 @@ llvm::Expected<llvm::json::Object> readJSONObject(llvm::StringRef Path) {
"failed to read JSON object from file '%s'",
Path.str().c_str());
}
- return std::move(*Object);
+ return *Object;
}
llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
@@ -101,6 +99,18 @@ llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
return llvm::Error::success();
}
+llvm::StringRef buildNamespaceKindToJSON(BuildNamespaceKind BNK) {
+ return toString(BNK);
+}
+
+SummaryName summaryNameFromJSON(llvm::StringRef SummaryNameStr) {
+ return SummaryName(SummaryNameStr.str());
+}
+
+llvm::StringRef summaryNameToJSON(const SummaryName &SN) { return SN.str(); }
+
+} // namespace
+
//----------------------------------------------------------------------------
// JSONFormat Constructor
//----------------------------------------------------------------------------
@@ -134,8 +144,8 @@ uint64_t JSONFormat::entityIdToJSON(EntityId EI) const {
// BuildNamespaceKind
//----------------------------------------------------------------------------
-llvm::Expected<BuildNamespaceKind>
-JSONFormat::buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr) {
+llvm::Expected<BuildNamespaceKind> JSONFormat::buildNamespaceKindFromJSON(
+ llvm::StringRef BuildNamespaceKindStr) const {
auto OptBuildNamespaceKind = parseBuildNamespaceKind(BuildNamespaceKindStr);
if (!OptBuildNamespaceKind) {
return llvm::createStringError(
@@ -147,16 +157,12 @@ JSONFormat::buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr) {
return *OptBuildNamespaceKind;
}
-llvm::StringRef buildNamespaceKindToJSON(BuildNamespaceKind BNK) {
- return toString(BNK);
-}
-
//----------------------------------------------------------------------------
// BuildNamespace
//----------------------------------------------------------------------------
llvm::Expected<BuildNamespace> JSONFormat::buildNamespaceFromJSON(
- const llvm::json::Object &BuildNamespaceObject) {
+ const llvm::json::Object &BuildNamespaceObject) const {
auto OptBuildNamespaceKindStr = BuildNamespaceObject.getString("kind");
if (!OptBuildNamespaceKindStr) {
return llvm::createStringError(
@@ -193,7 +199,7 @@ JSONFormat::buildNamespaceToJSON(const BuildNamespace &BN) const {
//----------------------------------------------------------------------------
llvm::Expected<NestedBuildNamespace> JSONFormat::nestedBuildNamespaceFromJSON(
- const llvm::json::Array &NestedBuildNamespaceArray) {
+ const llvm::json::Array &NestedBuildNamespaceArray) const {
std::vector<BuildNamespace> Namespaces;
size_t NamespaceCount = NestedBuildNamespaceArray.size();
@@ -242,8 +248,8 @@ JSONFormat::nestedBuildNamespaceToJSON(const NestedBuildNamespace &NBN) const {
// EntityName
//----------------------------------------------------------------------------
-llvm::Expected<EntityName>
-JSONFormat::entityNameFromJSON(const llvm::json::Object &EntityNameObject) {
+llvm::Expected<EntityName> JSONFormat::entityNameFromJSON(
+ const llvm::json::Object &EntityNameObject) const {
const auto OptUSR = EntityNameObject.getString("usr");
if (!OptUSR) {
return llvm::createStringError(
@@ -285,23 +291,13 @@ llvm::json::Object JSONFormat::entityNameToJSON(const EntityName &EN) const {
return Result;
}
-//----------------------------------------------------------------------------
-// SummaryName
-//----------------------------------------------------------------------------
-
-SummaryName summaryNameFromJSON(llvm::StringRef SummaryNameStr) {
- return SummaryName(SummaryNameStr.str());
-}
-
-llvm::StringRef summaryNameToJSON(const SummaryName &SN) { return SN.str(); }
-
//----------------------------------------------------------------------------
// EntityIdTable
//----------------------------------------------------------------------------
llvm::Expected<std::pair<EntityName, EntityId>>
JSONFormat::entityIdTableEntryFromJSON(
- const llvm::json::Object &EntityIdTableEntryObject) {
+ const llvm::json::Object &EntityIdTableEntryObject) const {
const llvm::json::Object *OptEntityNameObject =
EntityIdTableEntryObject.getObject("name");
@@ -341,14 +337,12 @@ JSONFormat::entityIdTableEntryFromJSON(
return std::make_pair(std::move(*ExpectedEntityName), std::move(EI));
}
-llvm::Expected<EntityIdTable>
-JSONFormat::entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray) {
- const size_t EntityCount = EntityIdTableArray.size();
-
+llvm::Expected<EntityIdTable> JSONFormat::entityIdTableFromJSON(
+ const llvm::json::Array &EntityIdTableArray) const {
EntityIdTable IdTable;
std::map<EntityName, EntityId> &Entities = getEntities(IdTable);
- for (size_t Index = 0; Index < EntityCount; ++Index) {
+ for (size_t Index = 0; Index < EntityIdTableArray.size(); ++Index) {
const llvm::json::Value &EntityIdTableEntryValue =
EntityIdTableArray[Index];
@@ -408,7 +402,7 @@ JSONFormat::entityIdTableToJSON(const EntityIdTable &IdTable) const {
llvm::Expected<std::unique_ptr<EntitySummary>>
JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
const llvm::json::Object &EntitySummaryObject,
- EntityIdTable &IdTable) {
+ EntityIdTable &IdTable) const {
auto InfoIt = FormatInfos.find(SN);
if (InfoIt == FormatInfos.end()) {
return llvm::createStringError(
@@ -442,83 +436,94 @@ JSONFormat::entitySummaryToJSON(const SummaryName &SN,
}
//----------------------------------------------------------------------------
-// SummaryData
+// EntityDataMapEntry
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::pair<EntityId, std::unique_ptr<EntitySummary>>>
+JSONFormat::entityDataMapEntryFromJSON(
+ const llvm::json::Object &EntityDataMapEntryObject, const SummaryName &SN,
+ EntityIdTable &IdTable) const {
+
+ const llvm::json::Value *EntityIdIntValue =
+ EntityDataMapEntryObject.get("entity_id");
+ if (!EntityIdIntValue) {
+ return llvm::createStringError(std::errc::invalid_argument,
+ "failed to deserialize EntityDataMap entry: "
+ "missing required field 'entity_id' "
+ "(expected unsigned integer EntityId)");
+ }
+
+ const std::optional<uint64_t> OptEntityIdInt =
+ EntityIdIntValue->getAsUINT64();
+ if (!OptEntityIdInt) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntityDataMap entry: "
+ "field 'entity_id' is not a valid unsigned 64-bit integer "
+ "(expected non-negative EntityId value)");
+ }
+
+ EntityId EI = entityIdFromJSON(*OptEntityIdInt);
+
+ const llvm::json::Object *OptEntitySummaryObject =
+ EntityDataMapEntryObject.getObject("entity_summary");
+ if (!OptEntitySummaryObject) {
+ return llvm::createStringError(std::errc::invalid_argument,
+ "failed to deserialize EntityDataMap entry: "
+ "missing or invalid field 'entity_summary' "
+ "(expected EntitySummary JSON object)");
+ }
+
+ auto ExpectedEntitySummary =
+ entitySummaryFromJSON(SN, *OptEntitySummaryObject, IdTable);
+ if (!ExpectedEntitySummary)
+ return wrapError(ExpectedEntitySummary.takeError(),
+ "failed to deserialize EntityDataMap entry");
+
+ return std::make_pair(std::move(EI), std::move(*ExpectedEntitySummary));
+}
+
+//----------------------------------------------------------------------------
+// EntityDataMap
//----------------------------------------------------------------------------
llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
const llvm::json::Array &EntityDataArray,
- EntityIdTable &IdTable) {
+ EntityIdTable &IdTable) const {
std::map<EntityId, std::unique_ptr<EntitySummary>> EntityDataMap;
- size_t Index = 0;
- for (const llvm::json::Value &EntityDataEntryValue : EntityDataArray) {
- const llvm::json::Object *OptEntityDataEntryObject =
- EntityDataEntryValue.getAsObject();
- if (!OptEntityDataEntryObject) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize entity data map: "
- "element at index %zu is not a JSON object "
- "(expected object with 'entity_id' and 'entity_summary' fields)",
- Index);
- }
-
- const llvm::json::Value *EntityIdIntValue =
- OptEntityDataEntryObject->get("entity_id");
- if (!EntityIdIntValue) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize entity data map entry "
- "at index %zu: missing required field 'entity_id' "
- "(expected unsigned integer EntityId)",
- Index);
- }
-
- const std::optional<uint64_t> OptEntityIdInt =
- EntityIdIntValue->getAsUINT64();
- if (!OptEntityIdInt) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize entity data map entry "
- "at index %zu: field 'entity_id' is not a valid unsigned 64-bit "
- "integer",
- Index);
- }
-
- EntityId EI = entityIdFromJSON(*OptEntityIdInt);
+ for (size_t Index = 0; Index < EntityDataArray.size(); ++Index) {
+ const llvm::json::Value &EntityDataMapEntryValue = EntityDataArray[Index];
- const llvm::json::Object *OptEntitySummaryObject =
- OptEntityDataEntryObject->getObject("entity_summary");
- if (!OptEntitySummaryObject) {
+ const llvm::json::Object *OptEntityDataMapEntryObject =
+ EntityDataMapEntryValue.getAsObject();
+ if (!OptEntityDataMapEntryObject) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize entity data map entry "
- "at index %zu: missing or invalid field 'entity_summary' "
- "(expected EntitySummary JSON object)",
+ "failed to deserialize EntityDataMap: "
+ "element at index %zu is not a JSON object "
+ "(expected EntityDataMap entry with 'entity_id' and 'entity_summary' "
+ "fields)",
Index);
}
- auto ExpectedEntitySummary =
- entitySummaryFromJSON(SN, *OptEntitySummaryObject, IdTable);
-
- if (!ExpectedEntitySummary) {
- return wrapError(
- ExpectedEntitySummary.takeError(),
- "failed to deserialize entity data map entry at index %zu", Index);
- }
+ auto ExpectedEntityDataMapEntry =
+ entityDataMapEntryFromJSON(*OptEntityDataMapEntryObject, SN, IdTable);
+ if (!ExpectedEntityDataMapEntry)
+ return wrapError(ExpectedEntityDataMapEntry.takeError(),
+ "failed to deserialize EntityDataMap at index %zu",
+ Index);
- auto [DataIt, DataInserted] = EntityDataMap.insert(
- {std::move(EI), std::move(*ExpectedEntitySummary)});
+ auto [DataIt, DataInserted] =
+ EntityDataMap.insert(std::move(*ExpectedEntityDataMapEntry));
if (!DataInserted) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize entity data map: "
+ "failed to deserialize EntityDataMap: "
"duplicate EntityId (%zu) found at index %zu",
getEntityIdIndex(DataIt->first), Index);
}
-
- ++Index;
}
return EntityDataMap;
@@ -539,18 +544,23 @@ llvm::json::Array JSONFormat::entityDataMapToJSON(
return Result;
}
+//----------------------------------------------------------------------------
+// SummaryDataMapEntry
+//----------------------------------------------------------------------------
+
llvm::Expected<
std::pair<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
JSONFormat::summaryDataMapEntryFromJSON(
- const llvm::json::Object &SummaryDataObject, EntityIdTable &IdTable) {
+ const llvm::json::Object &SummaryDataMapEntryObject,
+ EntityIdTable &IdTable) const {
std::optional<llvm::StringRef> OptSummaryNameStr =
- SummaryDataObject.getString("summary_name");
+ SummaryDataMapEntryObject.getString("summary_name");
if (!OptSummaryNameStr) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize summary data: "
+ "failed to deserialize SummaryDataMap entry: "
"missing required field 'summary_name' "
"(expected string identifier for the analysis summary)");
}
@@ -558,22 +568,22 @@ JSONFormat::summaryDataMapEntryFromJSON(
SummaryName SN = summaryNameFromJSON(*OptSummaryNameStr);
const llvm::json::Array *OptEntityDataArray =
- SummaryDataObject.getArray("data");
+ SummaryDataMapEntryObject.getArray("data");
if (!OptEntityDataArray) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize summary data for summary '%s': "
+ "failed to deserialize SummaryDataMap entry: "
"missing or invalid field 'data' "
- "(expected JSON array of entity summaries)",
- SN.str().data());
+ "(expected JSON array of entity data entries)");
}
auto ExpectedEntityDataMap =
entityDataMapFromJSON(SN, *OptEntityDataArray, IdTable);
if (!ExpectedEntityDataMap)
- return wrapError(ExpectedEntityDataMap.takeError(),
- "failed to deserialize summary data for summary '%s'",
- SN.str().data());
+ return wrapError(
+ ExpectedEntityDataMap.takeError(),
+ "failed to deserialize SummaryDataMap entry for summary '%s'",
+ SN.str().data());
return std::make_pair(std::move(SN), std::move(*ExpectedEntityDataMap));
}
@@ -587,6 +597,65 @@ llvm::json::Object JSONFormat::summaryDataMapEntryToJSON(
return Result;
}
+//----------------------------------------------------------------------------
+// SummaryDataMap
+//----------------------------------------------------------------------------
+
+llvm::Expected<
+ std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+JSONFormat::summaryDataMapFromJSON(const llvm::json::Array &SummaryDataArray,
+ EntityIdTable &IdTable) const {
+ std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>
+ SummaryDataMap;
+
+ for (size_t Index = 0; Index < SummaryDataArray.size(); ++Index) {
+ const llvm::json::Value &SummaryDataMapEntryValue = SummaryDataArray[Index];
+
+ const llvm::json::Object *OptSummaryDataMapEntryObject =
+ SummaryDataMapEntryValue.getAsObject();
+ if (!OptSummaryDataMapEntryObject) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize SummaryDataMap: "
+ "element at index %zu is not a JSON object "
+ "(expected SummaryDataMap entry with 'summary_name' and 'data' "
+ "fields)",
+ Index);
+ }
+
+ auto ExpectedSummaryDataMapEntry =
+ summaryDataMapEntryFromJSON(*OptSummaryDataMapEntryObject, IdTable);
+ if (!ExpectedSummaryDataMapEntry)
+ return wrapError(ExpectedSummaryDataMapEntry.takeError(),
+ "failed to deserialize SummaryDataMap at index %zu",
+ Index);
+
+ auto [SummaryIt, SummaryInserted] =
+ SummaryDataMap.emplace(std::move(*ExpectedSummaryDataMapEntry));
+ if (!SummaryInserted) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize SummaryDataMap: "
+ "duplicate SummaryName '%s' found at index %zu",
+ SummaryIt->first.str().data(), Index);
+ }
+ }
+
+ return SummaryDataMap;
+}
+
+llvm::json::Array JSONFormat::summaryDataMapToJSON(
+ const std::map<SummaryName,
+ std::map<EntityId, std::unique_ptr<EntitySummary>>>
+ &SummaryDataMap) const {
+ llvm::json::Array Result;
+ Result.reserve(SummaryDataMap.size());
+ for (const auto &[SummaryName, DataMap] : SummaryDataMap) {
+ Result.push_back(summaryDataMapEntryToJSON(SummaryName, DataMap));
+ }
+ return Result;
+}
+
//----------------------------------------------------------------------------
// TUSummary
//----------------------------------------------------------------------------
@@ -649,38 +718,14 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
Path.str().c_str());
}
- // Parse each summary data entry
- std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>
- Data;
- for (const llvm::json::Value &SummaryDataValue : *SummaryDataArray) {
- const llvm::json::Object *SummaryDataObject =
- SummaryDataValue.getAsObject();
- if (!SummaryDataObject) {
- return llvm::createStringError(std::errc::invalid_argument,
- "failed to read TUSummary from '%s': "
- "data array contains non-object element",
- Path.str().c_str());
- }
-
- auto ExpectedSummaryDataEntry =
- summaryDataMapEntryFromJSON(*SummaryDataObject, getIdTable(Summary));
- if (!ExpectedSummaryDataEntry)
- return wrapError(ExpectedSummaryDataEntry.takeError(),
- "failed to read TUSummary from '%s'",
- Path.str().c_str());
-
- auto [SummaryIt, SummaryInserted] =
- Data.emplace(std::move(*ExpectedSummaryDataEntry));
- if (!SummaryInserted) {
- return llvm::createStringError(std::errc::invalid_argument,
- "failed to read TUSummary from '%s': "
- "duplicate SummaryName '%s' found",
- Path.str().c_str(),
- SummaryIt->first.str().data());
- }
- }
+ auto ExpectedSummaryDataMap =
+ summaryDataMapFromJSON(*SummaryDataArray, getIdTable(Summary));
+ if (!ExpectedSummaryDataMap)
+ return wrapError(ExpectedSummaryDataMap.takeError(),
+ "failed to read TUSummary from '%s'",
+ Path.str().c_str());
- getData(Summary) = std::move(Data);
+ getData(Summary) = std::move(*ExpectedSummaryDataMap);
}
return Summary;
@@ -694,14 +739,7 @@ llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
RootObject["id_table"] = entityIdTableToJSON(getIdTable(S));
- {
- llvm::json::Array SummaryDataArray;
- for (const auto &[SummaryName, DataMap] : getData(S)) {
- SummaryDataArray.push_back(
- summaryDataMapEntryToJSON(SummaryName, DataMap));
- }
- RootObject["data"] = std::move(SummaryDataArray);
- }
+ RootObject["data"] = summaryDataMapToJSON(getData(S));
// Write the JSON to file
if (auto Error = writeJSON(std::move(RootObject), Path)) {
diff --git a/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test b/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
index e10b242087ef3..5ee78f60bcb2f 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
@@ -3,6 +3,7 @@
// Test that CallGraph analysis validates caller field type
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize summary data for summary 'CallGraph'
-// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize SummaryDataMap entry for summary 'CallGraph'
+// CHECK: failed to deserialize EntityDataMap at index 0
+// CHECK: failed to deserialize EntityDataMap entry
// CHECK: CallGraph: 'caller' at index 0 is not a valid uint64
diff --git a/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test b/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
index a95855a99d257..2abbf1438654c 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
@@ -3,6 +3,7 @@
// Test that CallGraph analysis requires 'call_graph' field
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize summary data for summary 'CallGraph'
-// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize SummaryDataMap entry for summary 'CallGraph'
+// CHECK: failed to deserialize EntityDataMap at index 0
+// CHECK: failed to deserialize EntityDataMap entry
// CHECK: CallGraph: missing or invalid 'call_graph' field
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
index 90dea14d683db..b44c7b85c3e49 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
@@ -3,4 +3,6 @@
// Test that 'data' array elements must be objects
// CHECK: failed to read TUSummary
-// CHECK: data array contains non-object element
+// CHECK: failed to deserialize SummaryDataMap
+// CHECK: element at index
+// CHECK: expected SummaryDataMap entry with 'summary_name' and 'data' fields
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test b/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
index 865db1e8709cd..25328c0eb603f 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
@@ -3,6 +3,7 @@
// Test that data array entries must have 'data' field
// CHECK: failed to read TUSummary
-// CHECK: for summary 'test'
+// CHECK: failed to deserialize SummaryDataMap at index
+// CHECK: failed to deserialize SummaryDataMap entry
// CHECK: missing or invalid field 'data'
-// CHECK: expected JSON array of entity summaries
+// CHECK: expected JSON array of entity data entries
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test b/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
index a9bf765424b7e..03807ee6c44cb 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
@@ -3,5 +3,7 @@
// Test that data array entries must have 'summary_name' field
// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize SummaryDataMap at index
+// CHECK: failed to deserialize SummaryDataMap entry
// CHECK: missing required field 'summary_name'
// CHECK: expected string identifier for the analysis summary
diff --git a/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test b/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
index 27b20b2c9fa62..9674ec9b4ada1 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
@@ -3,6 +3,7 @@
// Test that DefUse analysis validates use value types
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize summary data for summary 'DefUse'
-// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize SummaryDataMap entry for summary 'DefUse'
+// CHECK: failed to deserialize EntityDataMap at index 0
+// CHECK: failed to deserialize EntityDataMap entry
// CHECK: DefUse: use at index 0,0,0 is not a valid uint64
diff --git a/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test b/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
index 64574f78a0315..f03b4a1900307 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
@@ -3,6 +3,7 @@
// Test that DefUse analysis requires 'def_use_chains' field
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize summary data for summary 'DefUse'
-// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize SummaryDataMap entry for summary 'DefUse'
+// CHECK: failed to deserialize EntityDataMap at index 0
+// CHECK: failed to deserialize EntityDataMap entry
// CHECK: DefUse: missing or invalid 'def_use_chains' field
diff --git a/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test b/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
index 568db0a339620..b8f6e1335b02f 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
@@ -3,4 +3,5 @@
// Test that duplicate summary names are detected
// CHECK: failed to read TUSummary
-// CHECK: duplicate SummaryName 'test' found
+// CHECK: failed to deserialize SummaryDataMap
+// CHECK: duplicate SummaryName 'test' found at index
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
index 9b247c7ee39e5..43c0c9d164116 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
@@ -3,6 +3,6 @@
// Test that entity data elements must be objects
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize entity data map
+// CHECK: failed to deserialize EntityDataMap
// CHECK: element at index 0 is not a JSON object
-// CHECK: expected object with 'entity_id' and 'entity_summary' fields
+// CHECK: expected EntityDataMap entry with 'entity_id' and 'entity_summary' fields
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
index 3d4f30805af5a..7c80a3e121279 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
@@ -3,7 +3,7 @@
// Test that entity data entries must have 'entity_id' field
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize entity data map entry
-// CHECK: at index 0
+// CHECK: failed to deserialize EntityDataMap at index 0
+// CHECK: failed to deserialize EntityDataMap entry
// CHECK: missing required field 'entity_id'
// CHECK: expected unsigned integer EntityId
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
index 3229e02650360..57ed34e4eec26 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
@@ -3,7 +3,7 @@
// Test that entity data entries must have 'entity_summary' field
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize entity data map entry
-// CHECK: at index 0
+// CHECK: failed to deserialize EntityDataMap at index 0
+// CHECK: failed to deserialize EntityDataMap entry
// CHECK: missing or invalid field 'entity_summary'
// CHECK: expected EntitySummary JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test b/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
index eede8033fc575..bacd042fa34e5 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
@@ -3,6 +3,6 @@
// Test that entity_id field must be a valid uint64
// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize entity data map entry
-// CHECK: at index 0
+// CHECK: failed to deserialize EntityDataMap at index 0
+// CHECK: failed to deserialize EntityDataMap entry
// CHECK: field 'entity_id' is not a valid unsigned 64-bit integer
diff --git a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
index f8b4a535f00f3..fa93c8d64e9a3 100644
--- a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
+++ b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
@@ -25,10 +25,24 @@ using namespace clang::ssaf;
//===----------------------------------------------------------------------===//
namespace {
+/// Example analysis that tracks function call relationships.
+///
+/// This analysis builds a call graph where each function (represented by an
+/// EntityId) is mapped to the set of functions it directly calls. This is
+/// useful for understanding control flow and dependencies between functions.
+///
+/// Example structure:
+/// CallGraph[functionA] = {functionB, functionC}
+/// CallGraph[functionB] = {functionD}
+///
+/// This indicates that functionA calls functionB and functionC, and
+/// functionB calls functionD.
struct CallGraphAnalysis : EntitySummary {
CallGraphAnalysis() : EntitySummary(SummaryName("CallGraph")) {}
- // Maps each function (EntityId) to the set of functions it calls
+ /// Maps each caller function (EntityId) to the set of functions it calls.
+ /// Key: Caller function EntityId
+ /// Value: Set of callee function EntityIds
std::map<EntityId, std::set<EntityId>> CallGraph;
};
} // namespace
@@ -118,7 +132,8 @@ deserializeCallGraph(const llvm::json::Object &JSONObj, EntityIdTable &Table,
Result->CallGraph[Caller] = std::move(Callees);
}
- return std::move(Result);
+ // Return by value to enable NRVO (Named Return Value Optimization)
+ return Result;
}
namespace {
@@ -142,11 +157,31 @@ static llvm::Registry<JSONFormat::FormatInfo>::Add<CallGraphFormatInfo>
//===----------------------------------------------------------------------===//
namespace {
+/// Example analysis that tracks definition-use chains for variables.
+///
+/// This analysis builds def-use chains showing how variable definitions flow
+/// to their use sites. The structure is a three-level nested map:
+///
+/// Level 1: Variable EntityId
+/// Level 2: Definition EntityId (where the variable is defined)
+/// Level 3: Set of Use EntityIds (where that definition is used)
+///
+/// Example structure:
+/// DefUseChains[varX][def1] = {use1, use2}
+/// DefUseChains[varX][def2] = {use3}
+/// DefUseChains[varY][def3] = {use4, use5, use6}
+///
+/// This indicates that:
+/// - Variable varX has two definitions (def1, def2)
+/// - def1 is used at use1 and use2
+/// - def2 is used at use3
+/// - Variable varY has one definition (def3) used at use4, use5, use6
struct DefUseAnalysis : EntitySummary {
DefUseAnalysis() : EntitySummary(SummaryName("DefUse")) {}
- // For each variable (EntityId), maps definitions (EntityId) to their use
- // sites (set of EntityId)
+ /// Maps variables to their definition-use chains.
+ /// Key: Variable EntityId
+ /// Value: Map from definition EntityId to set of use EntityIds
std::map<EntityId, std::map<EntityId, std::set<EntityId>>> DefUseChains;
};
} // namespace
@@ -285,7 +320,7 @@ deserializeDefUse(const llvm::json::Object &JSONObj, EntityIdTable &Table,
Result->DefUseChains[Variable] = std::move(DefUseMap);
}
- return std::move(Result);
+ return Result;
}
namespace {
diff --git a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp
index 2262b9117e7d5..2153b1dc4e52e 100644
--- a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp
+++ b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp
@@ -61,7 +61,7 @@ int main(int argc, char **argv) {
return 1;
}
- TUSummary &Summary = *SummaryOrErr;
+ const TUSummary &Summary = *SummaryOrErr;
// Print summary information if requested
if (PrintSummary) {
>From 1dd02e0fdc53fe2f2c18e6fb24ef91d7286004a8 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Sun, 8 Feb 2026 12:29:34 -0800
Subject: [PATCH 14/19] Fix error messages and rename json field
---
.../Scalable/Serialization/JSONFormat.h | 2 +
.../Scalable/Serialization/JSONFormat.cpp | 623 ++++++++++++------
.../Inputs/callgraph-invalid-caller.json | 2 +-
.../Inputs/callgraph-missing-field.json | 2 +-
.../Inputs/defuse-invalid-use.json | 6 +-
.../Inputs/defuse-missing-field.json | 2 +-
.../Inputs/duplicate-summary-name.json | 4 +-
.../entity-data-element-not-object.json | 4 +-
.../Inputs/entity-data-missing-entity-id.json | 2 +-
.../entity-data-missing-entity-summary.json | 2 +-
.../Inputs/entity-id-not-uint64.json | 2 +-
.../Inputs/entity-summary-no-format-info.json | 2 +-
.../Inputs/valid-callgraph-expected.json | 2 +-
.../Serialization/Inputs/valid-callgraph.json | 16 +-
.../Inputs/valid-defuse-expected.json | 2 +-
.../Serialization/Inputs/valid-defuse.json | 16 +-
.../valid-with-empty-data-entry-expected.json | 2 +-
.../Inputs/valid-with-empty-data-entry.json | 2 +-
.../error-data-element-not-object.test | 2 +-
.../error-data-entry-missing-data.test | 4 +-
20 files changed, 458 insertions(+), 241 deletions(-)
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
index 5c8e95b35a87b..fb48e0122ade9 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -85,6 +85,8 @@ class JSONFormat : public SerializationFormat {
const llvm::json::Object &EntityIdTableEntryObject) const;
llvm::Expected<EntityIdTable>
entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray) const;
+ llvm::json::Object entityIdTableEntryToJSON(const EntityName &EN,
+ EntityId EI) const;
llvm::json::Array entityIdTableToJSON(const EntityIdTable &IdTable) const;
llvm::Expected<std::unique_ptr<EntitySummary>>
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index f374041f23827..1a4e0047c709f 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -12,21 +12,207 @@
using namespace clang::ssaf;
namespace {
-// Helper to wrap an error with additional context
-template <typename... Args>
-llvm::Error wrapError(llvm::Error E, std::errc ErrorCode, const char *Fmt,
- Args &&...Vals) {
- return llvm::joinErrors(
- llvm::createStringError(ErrorCode, Fmt, std::forward<Args>(Vals)...),
- std::move(E));
-}
+//----------------------------------------------------------------------------
+// ErrorBuilder - Fluent API for constructing contextual errors
+//----------------------------------------------------------------------------
-// Convenience version that defaults to invalid_argument
-template <typename... Args>
-llvm::Error wrapError(llvm::Error E, const char *Fmt, Args &&...Vals) {
- return wrapError(std::move(E), std::errc::invalid_argument, Fmt,
- std::forward<Args>(Vals)...);
-}
+class ErrorBuilder {
+private:
+ std::error_code Code;
+ std::vector<std::string> ContextStack;
+ llvm::Error WrappedError = llvm::Error::success();
+
+public:
+ explicit ErrorBuilder(std::errc EC) : Code(std::make_error_code(EC)) {}
+ explicit ErrorBuilder(std::error_code EC) : Code(EC) {}
+
+ // Add context message without formatting (for plain strings)
+ ErrorBuilder &context(const char *Msg) {
+ ContextStack.push_back(Msg);
+ return *this;
+ }
+
+ // Add context message with formatting
+ template <typename... Args>
+ ErrorBuilder &context(const char *Fmt, Args &&...ArgVals) {
+ char Buffer[2048];
+ snprintf(Buffer, sizeof(Buffer), Fmt, std::forward<Args>(ArgVals)...);
+ ContextStack.push_back(Buffer);
+ return *this;
+ }
+
+ // Wrap an existing error as the cause
+ ErrorBuilder &cause(llvm::Error E) {
+ // Consume the old WrappedError before assigning (LLVM Error requires
+ // checking)
+ llvm::consumeError(std::move(WrappedError));
+ WrappedError = std::move(E);
+ return *this;
+ }
+
+ // Build the final error
+ llvm::Error build() {
+ if (ContextStack.empty() && !WrappedError)
+ return llvm::Error::success();
+
+ if (ContextStack.empty())
+ return std::move(WrappedError);
+
+ std::string FinalMessage = llvm::join(ContextStack, ": ");
+ auto E = llvm::createStringError(Code, "%s", FinalMessage.c_str());
+
+ if (WrappedError)
+ return llvm::joinErrors(std::move(E), std::move(WrappedError));
+
+ return E;
+ }
+};
+
+//----------------------------------------------------------------------------
+// Error Message Constants
+//----------------------------------------------------------------------------
+
+namespace ErrorMessages {
+// File validation errors
+constexpr const char *FileNotFound = "file does not exist: '%s'";
+constexpr const char *IsDirectory = "path is a directory, not a file: '%s'";
+constexpr const char *NotJSONFile = "not a JSON file: '%s'";
+constexpr const char *FailedToValidateJSONFile =
+ "failed to validate JSON file '%s'";
+constexpr const char *FailedToReadFile = "failed to read file '%s'";
+constexpr const char *FailedToReadJSONObject =
+ "failed to read JSON object from file '%s'";
+constexpr const char *FailedToOpenFile = "failed to open '%s'";
+constexpr const char *WriteFailed = "write failed";
+
+// BuildNamespace errors
+constexpr const char *InvalidBuildNamespaceKind =
+ "invalid 'kind' BuildNamespaceKind value '%s'";
+constexpr const char *MissingBuildNamespaceKind =
+ "failed to deserialize BuildNamespace: "
+ "missing required field 'kind' (expected BuildNamespaceKind)";
+constexpr const char *MissingBuildNamespaceName =
+ "failed to deserialize BuildNamespace: "
+ "missing required field 'name'";
+constexpr const char *FailedToDeserializeBuildNamespace =
+ "failed to deserialize BuildNamespace";
+
+// NestedBuildNamespace errors
+constexpr const char *NestedBuildNamespaceElementNotObject =
+ "failed to deserialize NestedBuildNamespace: "
+ "element at index %zu is not a JSON object "
+ "(expected BuildNamespace object)";
+constexpr const char *FailedToDeserializeNestedBuildNamespace =
+ "failed to deserialize NestedBuildNamespace at index %zu";
+
+// EntityName errors
+constexpr const char *MissingEntityNameUSR =
+ "failed to deserialize EntityName: "
+ "missing required field 'usr' (Unified Symbol Resolution string)";
+constexpr const char *MissingEntityNameSuffix =
+ "failed to deserialize EntityName: "
+ "missing required field 'suffix'";
+constexpr const char *MissingEntityNameNamespace =
+ "failed to deserialize EntityName: "
+ "missing or invalid field 'namespace' "
+ "(expected JSON array of BuildNamespace objects)";
+constexpr const char *FailedToDeserializeEntityName =
+ "failed to deserialize EntityName";
+
+// EntityIdTable entry errors
+constexpr const char *MissingEntityIdTableEntryName =
+ "failed to deserialize EntityIdTable entry: "
+ "missing or invalid field 'name' (expected EntityName JSON object)";
+constexpr const char *MissingEntityIdTableEntryId =
+ "failed to deserialize EntityIdTable entry: "
+ "missing required field 'id' (expected unsigned integer EntityId)";
+constexpr const char *InvalidEntityIdTableEntryId =
+ "failed to deserialize EntityIdTable entry: "
+ "field 'id' is not a valid unsigned 64-bit integer "
+ "(expected non-negative EntityId value)";
+constexpr const char *FailedToDeserializeEntityIdTableEntry =
+ "failed to deserialize EntityIdTable entry";
+
+// EntityIdTable errors
+constexpr const char *EntityIdTableElementNotObject =
+ "failed to deserialize EntityIdTable: "
+ "element at index %zu is not a JSON object "
+ "(expected EntityIdTable entry with 'id' and 'name' fields)";
+constexpr const char *FailedToDeserializeEntityIdTable =
+ "failed to deserialize EntityIdTable at index %zu";
+constexpr const char *DuplicateEntityName =
+ "failed to deserialize EntityIdTable: "
+ "duplicate EntityName found at index %zu "
+ "(EntityId=%zu already exists in table)";
+
+// EntitySummary errors
+constexpr const char *NoFormatInfoForSummary =
+ "failed to deserialize EntitySummary: "
+ "no FormatInfo was registered for summary name: %s";
+
+// EntityDataMap entry errors
+constexpr const char *MissingEntityDataMapEntryEntityId =
+ "failed to deserialize EntityDataMap entry: "
+ "missing required field 'entity_id' (expected unsigned integer EntityId)";
+constexpr const char *InvalidEntityDataMapEntryEntityId =
+ "failed to deserialize EntityDataMap entry: "
+ "field 'entity_id' is not a valid unsigned 64-bit integer "
+ "(expected non-negative EntityId value)";
+constexpr const char *MissingEntityDataMapEntryEntitySummary =
+ "failed to deserialize EntityDataMap entry: "
+ "missing or invalid field 'entity_summary' "
+ "(expected EntitySummary JSON object)";
+constexpr const char *FailedToDeserializeEntityDataMapEntry =
+ "failed to deserialize EntityDataMap entry";
+
+// EntityDataMap errors
+constexpr const char *EntityDataMapElementNotObject =
+ "failed to deserialize EntityDataMap: "
+ "element at index %zu is not a JSON object "
+ "(expected EntityDataMap entry with 'entity_id' and 'entity_summary' "
+ "fields)";
+constexpr const char *FailedToDeserializeEntityDataMap =
+ "failed to deserialize EntityDataMap at index %zu";
+constexpr const char *DuplicateEntityId =
+ "failed to deserialize EntityDataMap: "
+ "duplicate EntityId (%zu) found at index %zu";
+
+// SummaryDataMap entry errors
+constexpr const char *MissingSummaryDataMapEntrySummaryName =
+ "failed to deserialize SummaryDataMap entry: "
+ "missing required field 'summary_name' "
+ "(expected string identifier for the analysis summary)";
+constexpr const char *MissingSummaryDataMapEntrySummaryData =
+ "failed to deserialize SummaryDataMap entry: "
+ "missing or invalid field 'summary_data' "
+ "(expected JSON array of entity data entries)";
+constexpr const char *FailedToDeserializeSummaryDataMapEntry =
+ "failed to deserialize SummaryDataMap entry for summary '%s'";
+
+// SummaryDataMap errors
+constexpr const char *SummaryDataMapElementNotObject =
+ "failed to deserialize SummaryDataMap: "
+ "element at index %zu is not a JSON object "
+ "(expected SummaryDataMap entry with 'summary_name' and 'summary_data' "
+ "fields)";
+constexpr const char *FailedToDeserializeSummaryDataMap =
+ "failed to deserialize SummaryDataMap at index %zu";
+constexpr const char *DuplicateSummaryName =
+ "failed to deserialize SummaryDataMap: "
+ "duplicate SummaryName '%s' found at index %zu";
+
+// TUSummary errors
+constexpr const char *ReadingTUSummaryFrom = "reading TUSummary from '%s'";
+constexpr const char *MissingTUNamespace =
+ "missing or invalid field 'tu_namespace' (expected JSON object)";
+constexpr const char *MissingIdTable =
+ "missing or invalid field 'id_table' (expected JSON array)";
+constexpr const char *MissingData =
+ "missing or invalid field 'data' (expected JSON array)";
+constexpr const char *WritingTUSummaryTo = "writing TUSummary to '%s'";
+} // namespace ErrorMessages
+
+} // namespace
//----------------------------------------------------------------------------
// JSON Reader and Writer
@@ -34,32 +220,35 @@ llvm::Error wrapError(llvm::Error E, const char *Fmt, Args &&...Vals) {
llvm::Error isJSONFile(llvm::StringRef Path) {
if (!llvm::sys::fs::exists(Path))
- return llvm::createStringError(std::errc::no_such_file_or_directory,
- "file does not exist: '%s'",
- Path.str().c_str());
+ return ErrorBuilder(std::errc::no_such_file_or_directory)
+ .context(ErrorMessages::FileNotFound, Path.str().c_str())
+ .build();
if (llvm::sys::fs::is_directory(Path))
- return llvm::createStringError(std::errc::is_a_directory,
- "path is a directory, not a file: '%s'",
- Path.str().c_str());
+ return ErrorBuilder(std::errc::is_a_directory)
+ .context(ErrorMessages::IsDirectory, Path.str().c_str())
+ .build();
if (!Path.ends_with_insensitive(".json"))
- return llvm::createStringError(std::errc::invalid_argument,
- "not a JSON file: '%s'", Path.str().c_str());
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::NotJSONFile, Path.str().c_str())
+ .build();
return llvm::Error::success();
}
llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
if (llvm::Error Err = isJSONFile(Path))
- return wrapError(std::move(Err), "failed to validate JSON file '%s'",
- Path.str().c_str());
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::FailedToValidateJSONFile, Path.str().c_str())
+ .cause(std::move(Err))
+ .build();
auto BufferOrError = llvm::MemoryBuffer::getFile(Path);
if (!BufferOrError) {
- return llvm::createStringError(BufferOrError.getError(),
- "failed to read file '%s'",
- Path.str().c_str());
+ return ErrorBuilder(BufferOrError.getError())
+ .context(ErrorMessages::FailedToReadFile, Path.str().c_str())
+ .build();
}
return llvm::json::parse(BufferOrError.get()->getBuffer());
@@ -68,15 +257,16 @@ llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
llvm::Expected<llvm::json::Object> readJSONObject(llvm::StringRef Path) {
auto ExpectedJSON = readJSON(Path);
if (!ExpectedJSON)
- return wrapError(ExpectedJSON.takeError(),
- "failed to read JSON object from file '%s'",
- Path.str().c_str());
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::FailedToReadJSONObject, Path.str().c_str())
+ .cause(ExpectedJSON.takeError())
+ .build();
llvm::json::Object *Object = ExpectedJSON->getAsObject();
if (!Object) {
- return llvm::createStringError(std::errc::invalid_argument,
- "failed to read JSON object from file '%s'",
- Path.str().c_str());
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::FailedToReadJSONObject, Path.str().c_str())
+ .build();
}
return *Object;
}
@@ -85,32 +275,23 @@ llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
std::error_code EC;
llvm::raw_fd_ostream OutStream(Path, EC, llvm::sys::fs::OF_Text);
if (EC) {
- return llvm::createStringError(EC, "failed to open '%s'",
- Path.str().c_str());
+ return ErrorBuilder(EC)
+ .context(ErrorMessages::FailedToOpenFile, Path.str().c_str())
+ .build();
}
OutStream << llvm::formatv("{0:2}\n", Value);
OutStream.flush();
if (OutStream.has_error()) {
- return llvm::createStringError(OutStream.error(), "write failed");
+ return ErrorBuilder(OutStream.error())
+ .context(ErrorMessages::WriteFailed)
+ .build();
}
return llvm::Error::success();
}
-llvm::StringRef buildNamespaceKindToJSON(BuildNamespaceKind BNK) {
- return toString(BNK);
-}
-
-SummaryName summaryNameFromJSON(llvm::StringRef SummaryNameStr) {
- return SummaryName(SummaryNameStr.str());
-}
-
-llvm::StringRef summaryNameToJSON(const SummaryName &SN) { return SN.str(); }
-
-} // namespace
-
//----------------------------------------------------------------------------
// JSONFormat Constructor
//----------------------------------------------------------------------------
@@ -128,6 +309,20 @@ JSONFormat::JSONFormat(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
}
}
+//----------------------------------------------------------------------------
+// SummaryName
+//----------------------------------------------------------------------------
+
+namespace {
+
+SummaryName summaryNameFromJSON(llvm::StringRef SummaryNameStr) {
+ return SummaryName(SummaryNameStr.str());
+}
+
+llvm::StringRef summaryNameToJSON(const SummaryName &SN) { return SN.str(); }
+
+} // namespace
+
//----------------------------------------------------------------------------
// EntityId
//----------------------------------------------------------------------------
@@ -148,15 +343,23 @@ llvm::Expected<BuildNamespaceKind> JSONFormat::buildNamespaceKindFromJSON(
llvm::StringRef BuildNamespaceKindStr) const {
auto OptBuildNamespaceKind = parseBuildNamespaceKind(BuildNamespaceKindStr);
if (!OptBuildNamespaceKind) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "invalid 'kind' BuildNamespaceKind value '%s'",
- BuildNamespaceKindStr.str().c_str());
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::InvalidBuildNamespaceKind,
+ BuildNamespaceKindStr.str().c_str())
+ .build();
}
return *OptBuildNamespaceKind;
}
+namespace {
+
+llvm::StringRef buildNamespaceKindToJSON(BuildNamespaceKind BNK) {
+ return toString(BNK);
+}
+
+} // namespace
+
//----------------------------------------------------------------------------
// BuildNamespace
//----------------------------------------------------------------------------
@@ -165,22 +368,23 @@ llvm::Expected<BuildNamespace> JSONFormat::buildNamespaceFromJSON(
const llvm::json::Object &BuildNamespaceObject) const {
auto OptBuildNamespaceKindStr = BuildNamespaceObject.getString("kind");
if (!OptBuildNamespaceKindStr) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize BuildNamespace: "
- "missing required field 'kind' (expected BuildNamespaceKind)");
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::MissingBuildNamespaceKind)
+ .build();
}
auto ExpectedKind = buildNamespaceKindFromJSON(*OptBuildNamespaceKindStr);
if (!ExpectedKind)
- return wrapError(ExpectedKind.takeError(),
- "failed to deserialize BuildNamespace");
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::FailedToDeserializeBuildNamespace)
+ .cause(ExpectedKind.takeError())
+ .build();
auto OptNameStr = BuildNamespaceObject.getString("name");
if (!OptNameStr) {
- return llvm::createStringError(std::errc::invalid_argument,
- "failed to deserialize BuildNamespace: "
- "missing required field 'name'");
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::MissingBuildNamespaceName)
+ .build();
}
return {BuildNamespace(*ExpectedKind, *OptNameStr)};
@@ -211,19 +415,18 @@ llvm::Expected<NestedBuildNamespace> JSONFormat::nestedBuildNamespaceFromJSON(
const llvm::json::Object *BuildNamespaceObject =
BuildNamespaceValue.getAsObject();
if (!BuildNamespaceObject) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize NestedBuildNamespace: "
- "element at index %zu is not a JSON object "
- "(expected BuildNamespace object)",
- Index);
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::NestedBuildNamespaceElementNotObject, Index)
+ .build();
}
auto ExpectedBuildNamespace = buildNamespaceFromJSON(*BuildNamespaceObject);
if (!ExpectedBuildNamespace)
- return wrapError(
- ExpectedBuildNamespace.takeError(),
- "failed to deserialize NestedBuildNamespace at index %zu", Index);
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::FailedToDeserializeNestedBuildNamespace,
+ Index)
+ .cause(ExpectedBuildNamespace.takeError())
+ .build();
Namespaces.push_back(std::move(*ExpectedBuildNamespace));
}
@@ -252,33 +455,32 @@ llvm::Expected<EntityName> JSONFormat::entityNameFromJSON(
const llvm::json::Object &EntityNameObject) const {
const auto OptUSR = EntityNameObject.getString("usr");
if (!OptUSR) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize EntityName: "
- "missing required field 'usr' (Unified Symbol Resolution string)");
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::MissingEntityNameUSR)
+ .build();
}
const auto OptSuffix = EntityNameObject.getString("suffix");
if (!OptSuffix) {
- return llvm::createStringError(std::errc::invalid_argument,
- "failed to deserialize EntityName: "
- "missing required field 'suffix'");
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::MissingEntityNameSuffix)
+ .build();
}
const llvm::json::Array *OptNamespaceArray =
EntityNameObject.getArray("namespace");
if (!OptNamespaceArray) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize EntityName: "
- "missing or invalid field 'namespace' "
- "(expected JSON array of BuildNamespace objects)");
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::MissingEntityNameNamespace)
+ .build();
}
auto ExpectedNamespace = nestedBuildNamespaceFromJSON(*OptNamespaceArray);
if (!ExpectedNamespace)
- return wrapError(ExpectedNamespace.takeError(),
- "failed to deserialize EntityName");
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::FailedToDeserializeEntityName)
+ .cause(ExpectedNamespace.takeError())
+ .build();
return EntityName{*OptUSR, *OptSuffix, std::move(*ExpectedNamespace)};
}
@@ -292,7 +494,7 @@ llvm::json::Object JSONFormat::entityNameToJSON(const EntityName &EN) const {
}
//----------------------------------------------------------------------------
-// EntityIdTable
+// EntityIdTableEntry
//----------------------------------------------------------------------------
llvm::Expected<std::pair<EntityName, EntityId>>
@@ -302,34 +504,32 @@ JSONFormat::entityIdTableEntryFromJSON(
const llvm::json::Object *OptEntityNameObject =
EntityIdTableEntryObject.getObject("name");
if (!OptEntityNameObject) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize EntityIdTable entry: "
- "missing or invalid field 'name' (expected EntityName JSON object)");
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::MissingEntityIdTableEntryName)
+ .build();
}
auto ExpectedEntityName = entityNameFromJSON(*OptEntityNameObject);
if (!ExpectedEntityName)
- return wrapError(ExpectedEntityName.takeError(),
- "failed to deserialize EntityIdTable entry");
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::FailedToDeserializeEntityIdTableEntry)
+ .cause(ExpectedEntityName.takeError())
+ .build();
const llvm::json::Value *EntityIdIntValue =
EntityIdTableEntryObject.get("id");
if (!EntityIdIntValue) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize EntityIdTable entry: "
- "missing required field 'id' (expected unsigned integer EntityId)");
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::MissingEntityIdTableEntryId)
+ .build();
}
const std::optional<uint64_t> OptEntityIdInt =
EntityIdIntValue->getAsUINT64();
if (!OptEntityIdInt) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize EntityIdTable entry: "
- "field 'id' is not a valid unsigned 64-bit integer "
- "(expected non-negative EntityId value)");
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::InvalidEntityIdTableEntryId)
+ .build();
}
EntityId EI = entityIdFromJSON(*OptEntityIdInt);
@@ -337,6 +537,18 @@ JSONFormat::entityIdTableEntryFromJSON(
return std::make_pair(std::move(*ExpectedEntityName), std::move(EI));
}
+llvm::json::Object JSONFormat::entityIdTableEntryToJSON(const EntityName &EN,
+ EntityId EI) const {
+ llvm::json::Object Entry;
+ Entry["id"] = entityIdToJSON(EI);
+ Entry["name"] = entityNameToJSON(EN);
+ return Entry;
+}
+
+//----------------------------------------------------------------------------
+// EntityIdTable
+//----------------------------------------------------------------------------
+
llvm::Expected<EntityIdTable> JSONFormat::entityIdTableFromJSON(
const llvm::json::Array &EntityIdTableArray) const {
EntityIdTable IdTable;
@@ -350,29 +562,26 @@ llvm::Expected<EntityIdTable> JSONFormat::entityIdTableFromJSON(
EntityIdTableEntryValue.getAsObject();
if (!OptEntityIdTableEntryObject) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize EntityIdTable: "
- "element at index %zu is not a JSON object "
- "(expected EntityIdTable entry with 'id' and 'name' fields)",
- Index);
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::EntityIdTableElementNotObject, Index)
+ .build();
}
auto ExpectedEntityIdTableEntry =
entityIdTableEntryFromJSON(*OptEntityIdTableEntryObject);
if (!ExpectedEntityIdTableEntry)
- return wrapError(ExpectedEntityIdTableEntry.takeError(),
- "failed to deserialize EntityIdTable at index %zu",
- Index);
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::FailedToDeserializeEntityIdTable, Index)
+ .cause(ExpectedEntityIdTableEntry.takeError())
+ .build();
auto [EntityIt, EntityInserted] =
Entities.emplace(std::move(*ExpectedEntityIdTableEntry));
if (!EntityInserted) {
- return llvm::createStringError(std::errc::invalid_argument,
- "failed to deserialize EntityIdTable: "
- "duplicate EntityName found at index %zu "
- "(EntityId=%zu already exists in table)",
- Index, getEntityIdIndex(EntityIt->second));
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::DuplicateEntityName, Index,
+ getEntityIdIndex(EntityIt->second))
+ .build();
}
}
@@ -386,10 +595,8 @@ JSONFormat::entityIdTableToJSON(const EntityIdTable &IdTable) const {
EntityIdTableArray.reserve(Entities.size());
for (const auto &[EntityName, EntityId] : Entities) {
- llvm::json::Object Entry;
- Entry["id"] = entityIdToJSON(EntityId);
- Entry["name"] = entityNameToJSON(EntityName);
- EntityIdTableArray.push_back(std::move(Entry));
+ EntityIdTableArray.push_back(
+ entityIdTableEntryToJSON(EntityName, EntityId));
}
return EntityIdTableArray;
@@ -405,11 +612,9 @@ JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
EntityIdTable &IdTable) const {
auto InfoIt = FormatInfos.find(SN);
if (InfoIt == FormatInfos.end()) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize EntitySummary: "
- "no FormatInfo was registered for summary name: %s",
- SN.str().data());
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::NoFormatInfoForSummary, SN.str().data())
+ .build();
}
const auto &InfoEntry = InfoIt->second;
assert(InfoEntry.ForSummary == SN);
@@ -447,20 +652,17 @@ JSONFormat::entityDataMapEntryFromJSON(
const llvm::json::Value *EntityIdIntValue =
EntityDataMapEntryObject.get("entity_id");
if (!EntityIdIntValue) {
- return llvm::createStringError(std::errc::invalid_argument,
- "failed to deserialize EntityDataMap entry: "
- "missing required field 'entity_id' "
- "(expected unsigned integer EntityId)");
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::MissingEntityDataMapEntryEntityId)
+ .build();
}
const std::optional<uint64_t> OptEntityIdInt =
EntityIdIntValue->getAsUINT64();
if (!OptEntityIdInt) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize EntityDataMap entry: "
- "field 'entity_id' is not a valid unsigned 64-bit integer "
- "(expected non-negative EntityId value)");
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::InvalidEntityDataMapEntryEntityId)
+ .build();
}
EntityId EI = entityIdFromJSON(*OptEntityIdInt);
@@ -468,17 +670,18 @@ JSONFormat::entityDataMapEntryFromJSON(
const llvm::json::Object *OptEntitySummaryObject =
EntityDataMapEntryObject.getObject("entity_summary");
if (!OptEntitySummaryObject) {
- return llvm::createStringError(std::errc::invalid_argument,
- "failed to deserialize EntityDataMap entry: "
- "missing or invalid field 'entity_summary' "
- "(expected EntitySummary JSON object)");
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::MissingEntityDataMapEntryEntitySummary)
+ .build();
}
auto ExpectedEntitySummary =
entitySummaryFromJSON(SN, *OptEntitySummaryObject, IdTable);
if (!ExpectedEntitySummary)
- return wrapError(ExpectedEntitySummary.takeError(),
- "failed to deserialize EntityDataMap entry");
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::FailedToDeserializeEntityDataMapEntry)
+ .cause(ExpectedEntitySummary.takeError())
+ .build();
return std::make_pair(std::move(EI), std::move(*ExpectedEntitySummary));
}
@@ -499,30 +702,26 @@ JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
const llvm::json::Object *OptEntityDataMapEntryObject =
EntityDataMapEntryValue.getAsObject();
if (!OptEntityDataMapEntryObject) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize EntityDataMap: "
- "element at index %zu is not a JSON object "
- "(expected EntityDataMap entry with 'entity_id' and 'entity_summary' "
- "fields)",
- Index);
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::EntityDataMapElementNotObject, Index)
+ .build();
}
auto ExpectedEntityDataMapEntry =
entityDataMapEntryFromJSON(*OptEntityDataMapEntryObject, SN, IdTable);
if (!ExpectedEntityDataMapEntry)
- return wrapError(ExpectedEntityDataMapEntry.takeError(),
- "failed to deserialize EntityDataMap at index %zu",
- Index);
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::FailedToDeserializeEntityDataMap, Index)
+ .cause(ExpectedEntityDataMapEntry.takeError())
+ .build();
auto [DataIt, DataInserted] =
EntityDataMap.insert(std::move(*ExpectedEntityDataMapEntry));
if (!DataInserted) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize EntityDataMap: "
- "duplicate EntityId (%zu) found at index %zu",
- getEntityIdIndex(DataIt->first), Index);
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::DuplicateEntityId,
+ getEntityIdIndex(DataIt->first), Index)
+ .build();
}
}
@@ -558,32 +757,29 @@ JSONFormat::summaryDataMapEntryFromJSON(
SummaryDataMapEntryObject.getString("summary_name");
if (!OptSummaryNameStr) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize SummaryDataMap entry: "
- "missing required field 'summary_name' "
- "(expected string identifier for the analysis summary)");
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::MissingSummaryDataMapEntrySummaryName)
+ .build();
}
SummaryName SN = summaryNameFromJSON(*OptSummaryNameStr);
const llvm::json::Array *OptEntityDataArray =
- SummaryDataMapEntryObject.getArray("data");
+ SummaryDataMapEntryObject.getArray("summary_data");
if (!OptEntityDataArray) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize SummaryDataMap entry: "
- "missing or invalid field 'data' "
- "(expected JSON array of entity data entries)");
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::MissingSummaryDataMapEntrySummaryData)
+ .build();
}
auto ExpectedEntityDataMap =
entityDataMapFromJSON(SN, *OptEntityDataArray, IdTable);
if (!ExpectedEntityDataMap)
- return wrapError(
- ExpectedEntityDataMap.takeError(),
- "failed to deserialize SummaryDataMap entry for summary '%s'",
- SN.str().data());
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::FailedToDeserializeSummaryDataMapEntry,
+ SN.str().data())
+ .cause(ExpectedEntityDataMap.takeError())
+ .build();
return std::make_pair(std::move(SN), std::move(*ExpectedEntityDataMap));
}
@@ -593,7 +789,7 @@ llvm::json::Object JSONFormat::summaryDataMapEntryToJSON(
const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const {
llvm::json::Object Result;
Result["summary_name"] = summaryNameToJSON(SN);
- Result["data"] = entityDataMapToJSON(SN, SD);
+ Result["summary_data"] = entityDataMapToJSON(SN, SD);
return Result;
}
@@ -614,30 +810,26 @@ JSONFormat::summaryDataMapFromJSON(const llvm::json::Array &SummaryDataArray,
const llvm::json::Object *OptSummaryDataMapEntryObject =
SummaryDataMapEntryValue.getAsObject();
if (!OptSummaryDataMapEntryObject) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize SummaryDataMap: "
- "element at index %zu is not a JSON object "
- "(expected SummaryDataMap entry with 'summary_name' and 'data' "
- "fields)",
- Index);
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::SummaryDataMapElementNotObject, Index)
+ .build();
}
auto ExpectedSummaryDataMapEntry =
summaryDataMapEntryFromJSON(*OptSummaryDataMapEntryObject, IdTable);
if (!ExpectedSummaryDataMapEntry)
- return wrapError(ExpectedSummaryDataMapEntry.takeError(),
- "failed to deserialize SummaryDataMap at index %zu",
- Index);
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::FailedToDeserializeSummaryDataMap, Index)
+ .cause(ExpectedSummaryDataMapEntry.takeError())
+ .build();
auto [SummaryIt, SummaryInserted] =
SummaryDataMap.emplace(std::move(*ExpectedSummaryDataMapEntry));
if (!SummaryInserted) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize SummaryDataMap: "
- "duplicate SummaryName '%s' found at index %zu",
- SummaryIt->first.str().data(), Index);
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::DuplicateSummaryName,
+ SummaryIt->first.str().data(), Index)
+ .build();
}
}
@@ -661,11 +853,12 @@ llvm::json::Array JSONFormat::summaryDataMapToJSON(
//----------------------------------------------------------------------------
llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
- // Read the JSON object from the file
auto ExpectedRootObject = readJSONObject(Path);
if (!ExpectedRootObject)
- return wrapError(ExpectedRootObject.takeError(),
- "failed to read TUSummary from '%s'", Path.str().c_str());
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
+ .cause(ExpectedRootObject.takeError())
+ .build();
const llvm::json::Object &RootObject = *ExpectedRootObject;
@@ -673,17 +866,18 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
const llvm::json::Object *TUNamespaceObject =
RootObject.getObject("tu_namespace");
if (!TUNamespaceObject) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to read TUSummary from '%s': "
- "missing or invalid field 'tu_namespace' (expected JSON object)",
- Path.str().c_str());
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
+ .context(ErrorMessages::MissingTUNamespace)
+ .build();
}
auto ExpectedTUNamespace = buildNamespaceFromJSON(*TUNamespaceObject);
if (!ExpectedTUNamespace)
- return wrapError(ExpectedTUNamespace.takeError(),
- "failed to read TUSummary from '%s'", Path.str().c_str());
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
+ .cause(ExpectedTUNamespace.takeError())
+ .build();
TUSummary Summary(std::move(*ExpectedTUNamespace));
@@ -691,39 +885,39 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
{
const llvm::json::Array *IdTableArray = RootObject.getArray("id_table");
if (!IdTableArray) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to read TUSummary from '%s': "
- "missing or invalid field 'id_table' (expected JSON array)",
- Path.str().c_str());
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
+ .context(ErrorMessages::MissingIdTable)
+ .build();
}
auto ExpectedIdTable = entityIdTableFromJSON(*IdTableArray);
if (!ExpectedIdTable)
- return wrapError(ExpectedIdTable.takeError(),
- "failed to read TUSummary from '%s'",
- Path.str().c_str());
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
+ .cause(ExpectedIdTable.takeError())
+ .build();
getIdTable(Summary) = std::move(*ExpectedIdTable);
}
- // Parse data field
+ // Parse Data field
{
const llvm::json::Array *SummaryDataArray = RootObject.getArray("data");
if (!SummaryDataArray) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to read TUSummary from '%s': "
- "missing or invalid field 'data' (expected JSON array)",
- Path.str().c_str());
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
+ .context(ErrorMessages::MissingData)
+ .build();
}
auto ExpectedSummaryDataMap =
summaryDataMapFromJSON(*SummaryDataArray, getIdTable(Summary));
if (!ExpectedSummaryDataMap)
- return wrapError(ExpectedSummaryDataMap.takeError(),
- "failed to read TUSummary from '%s'",
- Path.str().c_str());
+ return ErrorBuilder(std::errc::invalid_argument)
+ .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
+ .cause(ExpectedSummaryDataMap.takeError())
+ .build();
getData(Summary) = std::move(*ExpectedSummaryDataMap);
}
@@ -741,10 +935,11 @@ llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
RootObject["data"] = summaryDataMapToJSON(getData(S));
- // Write the JSON to file
if (auto Error = writeJSON(std::move(RootObject), Path)) {
- return wrapError(std::move(Error), std::errc::io_error,
- "failed to write TUSummary to '%s'", Path.str().c_str());
+ return ErrorBuilder(std::errc::io_error)
+ .context(ErrorMessages::WritingTUSummaryTo, Path.str().c_str())
+ .cause(std::move(Error))
+ .build();
}
return llvm::Error::success();
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json
index 9cb082d00fc4c..ce1ab6423d847 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json
@@ -7,7 +7,7 @@
"data": [
{
"summary_name": "CallGraph",
- "data": [
+ "summary_data": [
{
"entity_id": 0,
"entity_summary": {
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json
index 243842fdc1f57..bd23319d80cec 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json
@@ -7,7 +7,7 @@
"data": [
{
"summary_name": "CallGraph",
- "data": [
+ "summary_data": [
{
"entity_id": 0,
"entity_summary": {}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json
index 4fb91b8382648..de3ee8d56682f 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json
@@ -7,7 +7,7 @@
"data": [
{
"summary_name": "DefUse",
- "data": [
+ "summary_data": [
{
"entity_id": 0,
"entity_summary": {
@@ -17,7 +17,9 @@
"definitions": [
{
"definition": 10,
- "uses": ["invalid"]
+ "uses": [
+ "invalid"
+ ]
}
]
}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json
index b7dea48c27899..351825e77f74c 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json
@@ -7,7 +7,7 @@
"data": [
{
"summary_name": "DefUse",
- "data": [
+ "summary_data": [
{
"entity_id": 0,
"entity_summary": {}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json b/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json
index 5c39b179364ca..d16d61501a361 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json
@@ -7,11 +7,11 @@
"data": [
{
"summary_name": "test",
- "data": []
+ "summary_data": []
},
{
"summary_name": "test",
- "data": []
+ "summary_data": []
}
]
}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json
index 98018650c368f..d892ee698ee19 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json
@@ -7,7 +7,9 @@
"data": [
{
"summary_name": "test",
- "data": ["not an object"]
+ "summary_data": [
+ "not an object"
+ ]
}
]
}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json
index c80f4e7a4ce64..3b53bcb3564e1 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json
@@ -7,7 +7,7 @@
"data": [
{
"summary_name": "test",
- "data": [
+ "summary_data": [
{
"entity_summary": {}
}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json
index 692369d342904..6d8a43a10c80c 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json
@@ -7,7 +7,7 @@
"data": [
{
"summary_name": "test",
- "data": [
+ "summary_data": [
{
"entity_id": 0
}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json
index 33d9855fb1da4..246a99c6537c0 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json
@@ -7,7 +7,7 @@
"data": [
{
"summary_name": "test",
- "data": [
+ "summary_data": [
{
"entity_id": "not a number",
"entity_summary": {}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json
index c56402615956c..179f8e5d33c47 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json
@@ -7,7 +7,7 @@
"data": [
{
"summary_name": "test",
- "data": [
+ "summary_data": [
{
"entity_id": 0,
"entity_summary": {}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json
index 53af0a652cca7..a9939b5467719 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json
@@ -1,7 +1,7 @@
{
"data": [
{
- "data": [
+ "summary_data": [
{
"entity_id": 0,
"entity_summary": {
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json
index 1545092b614ff..831cd5000d1b2 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json
@@ -40,22 +40,30 @@
"data": [
{
"summary_name": "CallGraph",
- "data": [
+ "summary_data": [
{
"entity_id": 0,
"entity_summary": {
"call_graph": [
{
"caller": 0,
- "callees": [1, 2]
+ "callees": [
+ 1,
+ 2
+ ]
},
{
"caller": 1,
- "callees": [2, 3]
+ "callees": [
+ 2,
+ 3
+ ]
},
{
"caller": 2,
- "callees": [3]
+ "callees": [
+ 3
+ ]
}
]
}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json
index 915d4261b2bc1..72ac6212c0266 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json
@@ -1,7 +1,7 @@
{
"data": [
{
- "data": [
+ "summary_data": [
{
"entity_id": 0,
"entity_summary": {
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json
index 88581b4813a58..b541843bfd7c7 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json
@@ -64,7 +64,7 @@
"data": [
{
"summary_name": "DefUse",
- "data": [
+ "summary_data": [
{
"entity_id": 0,
"entity_summary": {
@@ -74,11 +74,16 @@
"definitions": [
{
"definition": 10,
- "uses": [12, 13]
+ "uses": [
+ 12,
+ 13
+ ]
},
{
"definition": 14,
- "uses": [13]
+ "uses": [
+ 13
+ ]
}
]
},
@@ -87,7 +92,10 @@
"definitions": [
{
"definition": 11,
- "uses": [12, 14]
+ "uses": [
+ 12,
+ 14
+ ]
}
]
}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json
index ddda2507cdfec..2e5e958748263 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json
@@ -1,7 +1,7 @@
{
"data": [
{
- "data": [],
+ "summary_data": [],
"summary_name": "test"
}
],
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json
index 0d62ee2a6d232..13a6cb34ae255 100644
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json
+++ b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json
@@ -7,7 +7,7 @@
"data": [
{
"summary_name": "test",
- "data": []
+ "summary_data": []
}
]
}
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
index b44c7b85c3e49..1c7adf5437f6e 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
@@ -5,4 +5,4 @@
// CHECK: failed to read TUSummary
// CHECK: failed to deserialize SummaryDataMap
// CHECK: element at index
-// CHECK: expected SummaryDataMap entry with 'summary_name' and 'data' fields
+// CHECK: expected SummaryDataMap entry with 'summary_name' and 'summary_data' fields
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test b/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
index 25328c0eb603f..f7e25e81bc2b1 100644
--- a/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
@@ -1,9 +1,9 @@
// RUN: not ssaf-serialization-format-test %S/Inputs/data-entry-missing-data.json 2>&1 | FileCheck %s
-// Test that data array entries must have 'data' field
+// Test that data array entries must have 'summary_data' field
// CHECK: failed to read TUSummary
// CHECK: failed to deserialize SummaryDataMap at index
// CHECK: failed to deserialize SummaryDataMap entry
-// CHECK: missing or invalid field 'data'
+// CHECK: missing or invalid field 'summary_data'
// CHECK: expected JSON array of entity data entries
>From c7118c1fb4a74a7db2a21be891a01cf67786ca79 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Sun, 8 Feb 2026 12:42:47 -0800
Subject: [PATCH 15/19] Use formatv
---
.../Scalable/Serialization/JSONFormat.cpp | 53 +++++++++----------
1 file changed, 26 insertions(+), 27 deletions(-)
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index 1a4e0047c709f..022f787f09182 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -35,9 +35,8 @@ class ErrorBuilder {
// Add context message with formatting
template <typename... Args>
ErrorBuilder &context(const char *Fmt, Args &&...ArgVals) {
- char Buffer[2048];
- snprintf(Buffer, sizeof(Buffer), Fmt, std::forward<Args>(ArgVals)...);
- ContextStack.push_back(Buffer);
+ ContextStack.push_back(
+ llvm::formatv(Fmt, std::forward<Args>(ArgVals)...).str());
return *this;
}
@@ -74,20 +73,20 @@ class ErrorBuilder {
namespace ErrorMessages {
// File validation errors
-constexpr const char *FileNotFound = "file does not exist: '%s'";
-constexpr const char *IsDirectory = "path is a directory, not a file: '%s'";
-constexpr const char *NotJSONFile = "not a JSON file: '%s'";
+constexpr const char *FileNotFound = "file does not exist: '{0}'";
+constexpr const char *IsDirectory = "path is a directory, not a file: '{0}'";
+constexpr const char *NotJSONFile = "not a JSON file: '{0}'";
constexpr const char *FailedToValidateJSONFile =
- "failed to validate JSON file '%s'";
-constexpr const char *FailedToReadFile = "failed to read file '%s'";
+ "failed to validate JSON file '{0}'";
+constexpr const char *FailedToReadFile = "failed to read file '{0}'";
constexpr const char *FailedToReadJSONObject =
- "failed to read JSON object from file '%s'";
-constexpr const char *FailedToOpenFile = "failed to open '%s'";
+ "failed to read JSON object from file '{0}'";
+constexpr const char *FailedToOpenFile = "failed to open '{0}'";
constexpr const char *WriteFailed = "write failed";
// BuildNamespace errors
constexpr const char *InvalidBuildNamespaceKind =
- "invalid 'kind' BuildNamespaceKind value '%s'";
+ "invalid 'kind' BuildNamespaceKind value '{0}'";
constexpr const char *MissingBuildNamespaceKind =
"failed to deserialize BuildNamespace: "
"missing required field 'kind' (expected BuildNamespaceKind)";
@@ -100,10 +99,10 @@ constexpr const char *FailedToDeserializeBuildNamespace =
// NestedBuildNamespace errors
constexpr const char *NestedBuildNamespaceElementNotObject =
"failed to deserialize NestedBuildNamespace: "
- "element at index %zu is not a JSON object "
+ "element at index {0} is not a JSON object "
"(expected BuildNamespace object)";
constexpr const char *FailedToDeserializeNestedBuildNamespace =
- "failed to deserialize NestedBuildNamespace at index %zu";
+ "failed to deserialize NestedBuildNamespace at index {0}";
// EntityName errors
constexpr const char *MissingEntityNameUSR =
@@ -136,19 +135,19 @@ constexpr const char *FailedToDeserializeEntityIdTableEntry =
// EntityIdTable errors
constexpr const char *EntityIdTableElementNotObject =
"failed to deserialize EntityIdTable: "
- "element at index %zu is not a JSON object "
+ "element at index {0} is not a JSON object "
"(expected EntityIdTable entry with 'id' and 'name' fields)";
constexpr const char *FailedToDeserializeEntityIdTable =
- "failed to deserialize EntityIdTable at index %zu";
+ "failed to deserialize EntityIdTable at index {0}";
constexpr const char *DuplicateEntityName =
"failed to deserialize EntityIdTable: "
- "duplicate EntityName found at index %zu "
- "(EntityId=%zu already exists in table)";
+ "duplicate EntityName found at index {0} "
+ "(EntityId={1} already exists in table)";
// EntitySummary errors
constexpr const char *NoFormatInfoForSummary =
"failed to deserialize EntitySummary: "
- "no FormatInfo was registered for summary name: %s";
+ "no FormatInfo was registered for summary name: {0}";
// EntityDataMap entry errors
constexpr const char *MissingEntityDataMapEntryEntityId =
@@ -168,14 +167,14 @@ constexpr const char *FailedToDeserializeEntityDataMapEntry =
// EntityDataMap errors
constexpr const char *EntityDataMapElementNotObject =
"failed to deserialize EntityDataMap: "
- "element at index %zu is not a JSON object "
+ "element at index {0} is not a JSON object "
"(expected EntityDataMap entry with 'entity_id' and 'entity_summary' "
"fields)";
constexpr const char *FailedToDeserializeEntityDataMap =
- "failed to deserialize EntityDataMap at index %zu";
+ "failed to deserialize EntityDataMap at index {0}";
constexpr const char *DuplicateEntityId =
"failed to deserialize EntityDataMap: "
- "duplicate EntityId (%zu) found at index %zu";
+ "duplicate EntityId ({0}) found at index {1}";
// SummaryDataMap entry errors
constexpr const char *MissingSummaryDataMapEntrySummaryName =
@@ -187,29 +186,29 @@ constexpr const char *MissingSummaryDataMapEntrySummaryData =
"missing or invalid field 'summary_data' "
"(expected JSON array of entity data entries)";
constexpr const char *FailedToDeserializeSummaryDataMapEntry =
- "failed to deserialize SummaryDataMap entry for summary '%s'";
+ "failed to deserialize SummaryDataMap entry for summary '{0}'";
// SummaryDataMap errors
constexpr const char *SummaryDataMapElementNotObject =
"failed to deserialize SummaryDataMap: "
- "element at index %zu is not a JSON object "
+ "element at index {0} is not a JSON object "
"(expected SummaryDataMap entry with 'summary_name' and 'summary_data' "
"fields)";
constexpr const char *FailedToDeserializeSummaryDataMap =
- "failed to deserialize SummaryDataMap at index %zu";
+ "failed to deserialize SummaryDataMap at index {0}";
constexpr const char *DuplicateSummaryName =
"failed to deserialize SummaryDataMap: "
- "duplicate SummaryName '%s' found at index %zu";
+ "duplicate SummaryName '{0}' found at index {1}";
// TUSummary errors
-constexpr const char *ReadingTUSummaryFrom = "reading TUSummary from '%s'";
+constexpr const char *ReadingTUSummaryFrom = "reading TUSummary from '{0}'";
constexpr const char *MissingTUNamespace =
"missing or invalid field 'tu_namespace' (expected JSON object)";
constexpr const char *MissingIdTable =
"missing or invalid field 'id_table' (expected JSON array)";
constexpr const char *MissingData =
"missing or invalid field 'data' (expected JSON array)";
-constexpr const char *WritingTUSummaryTo = "writing TUSummary to '%s'";
+constexpr const char *WritingTUSummaryTo = "writing TUSummary to '{0}'";
} // namespace ErrorMessages
} // namespace
>From d8fc6f85303e6b7af61fd37e461595a791f91e42 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Sun, 8 Feb 2026 15:45:17 -0800
Subject: [PATCH 16/19] Replace lit tests with unit tests
---
.../Inputs/callgraph-invalid-caller.json | 25 -
.../Inputs/callgraph-missing-field.json | 18 -
.../Inputs/data-element-not-object.json | 8 -
.../Inputs/data-entry-missing-data.json | 12 -
.../Inputs/data-missing-summary-name.json | 12 -
.../Serialization/Inputs/data-not-array.json | 8 -
.../Inputs/defuse-invalid-use.json | 32 -
.../Inputs/defuse-missing-field.json | 18 -
.../Inputs/directory.json/.gitkeep | 0
.../Inputs/duplicate-entity.json | 35 -
.../Inputs/duplicate-summary-name.json | 17 -
.../entity-data-element-not-object.json | 15 -
.../Inputs/entity-data-missing-entity-id.json | 17 -
.../entity-data-missing-entity-summary.json | 17 -
.../Inputs/entity-id-not-uint64.json | 18 -
.../Inputs/entity-name-missing-namespace.json | 16 -
.../Inputs/entity-name-missing-suffix.json | 21 -
.../Inputs/entity-name-missing-usr.json | 21 -
.../Inputs/entity-summary-no-format-info.json | 18 -
.../Inputs/id-table-element-not-object.json | 8 -
.../Inputs/id-table-entry-id-not-uint64.json | 22 -
.../Inputs/id-table-entry-missing-id.json | 21 -
.../Inputs/id-table-entry-missing-name.json | 10 -
.../Inputs/id-table-not-array.json | 8 -
.../Serialization/Inputs/invalid-kind.json | 8 -
.../Serialization/Inputs/invalid-syntax.json | 1 -
.../Serialization/Inputs/missing-data.json | 7 -
.../Inputs/missing-id-table.json | 7 -
.../Serialization/Inputs/missing-kind.json | 7 -
.../Serialization/Inputs/missing-name.json | 7 -
.../Inputs/missing-tu-namespace.json | 4 -
.../Inputs/namespace-element-not-object.json | 17 -
.../Inputs/no-read-permission.json | 8 -
.../Inputs/not-json-extension.txt | 8 -
.../Serialization/Inputs/not-object.json | 1 -
.../Inputs/valid-callgraph-expected.json | 74 --
.../Serialization/Inputs/valid-callgraph.json | 74 --
.../Inputs/valid-defuse-expected.json | 108 --
.../Serialization/Inputs/valid-defuse.json | 108 --
.../Inputs/valid-empty-expected.json | 8 -
.../Serialization/Inputs/valid-empty.json | 8 -
.../Inputs/valid-link-unit-expected.json | 8 -
.../Serialization/Inputs/valid-link-unit.json | 8 -
.../valid-with-empty-data-entry-expected.json | 13 -
.../Inputs/valid-with-empty-data-entry.json | 13 -
.../Inputs/valid-with-idtable-expected.json | 39 -
.../Inputs/valid-with-idtable.json | 39 -
.../Serialization/error-broken-symlink.test | 14 -
.../error-callgraph-invalid-caller.test | 9 -
.../error-callgraph-missing-field.test | 9 -
.../error-data-element-not-object.test | 8 -
.../error-data-entry-missing-data.test | 9 -
.../error-data-missing-summary-name.test | 9 -
.../Serialization/error-data-not-array.test | 7 -
.../error-defuse-invalid-use.test | 9 -
.../error-defuse-missing-field.test | 9 -
.../Serialization/error-duplicate-entity.test | 8 -
.../error-duplicate-summary-name.test | 7 -
.../error-entity-data-element-not-object.test | 8 -
.../error-entity-data-missing-entity-id.test | 9 -
...or-entity-data-missing-entity-summary.test | 9 -
.../error-entity-id-not-uint64.test | 8 -
.../error-entity-name-missing-namespace.test | 9 -
.../error-entity-name-missing-suffix.test | 8 -
.../error-entity-name-missing-usr.test | 9 -
.../error-entity-summary-no-format-info.test | 7 -
.../error-id-table-element-not-object.test | 8 -
.../error-id-table-entry-id-not-uint64.test | 8 -
.../error-id-table-entry-missing-id.test | 8 -
.../error-id-table-entry-missing-name.test | 8 -
.../error-id-table-not-array.test | 7 -
.../Serialization/error-invalid-kind.test | 7 -
.../Serialization/error-invalid-syntax.test | 6 -
.../Serialization/error-is-directory.test | 7 -
.../Serialization/error-missing-data.test | 7 -
.../Serialization/error-missing-id-table.test | 7 -
.../Serialization/error-missing-kind.test | 8 -
.../Serialization/error-missing-name.test | 7 -
.../error-missing-tu-namespace.test | 7 -
.../error-namespace-element-not-object.test | 10 -
.../Serialization/error-nonexistent-file.test | 6 -
.../error-not-json-extension.test | 8 -
.../Serialization/error-not-object.test | 6 -
.../error-permission-denied.test | 14 -
.../error-write-flush-failure.test | 13 -
.../error-write-readonly-dir.test | 16 -
.../Serialization/valid-callgraph.test | 9 -
.../Scalable/Serialization/valid-defuse.test | 10 -
.../Scalable/Serialization/valid-empty.test | 4 -
.../Serialization/valid-link-unit.test | 4 -
.../valid-with-empty-data-entry.test | 4 -
.../Serialization/valid-with-idtable.test | 4 -
clang/test/lit.cfg.py | 1 -
.../Analysis/Scalable/CMakeLists.txt | 3 +-
.../Scalable/Serialization/JSONFormatTest.cpp | 1091 +++++++++++++++++
.../Analysis/Scalable/tools/CMakeLists.txt | 1 -
.../CMakeLists.txt | 9 -
.../ExampleAnalyses.cpp | 338 -----
.../SSAFSerializationFormatTest.cpp | 84 --
99 files changed, 1092 insertions(+), 1775 deletions(-)
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/data-element-not-object.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/data-entry-missing-data.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/data-missing-summary-name.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/data-not-array.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/directory.json/.gitkeep
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-entity.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-namespace.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-suffix.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-usr.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-element-not-object.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-id-not-uint64.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-id.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-name.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/id-table-not-array.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/invalid-kind.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/invalid-syntax.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-data.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-id-table.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-kind.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-name.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/missing-tu-namespace.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/namespace-element-not-object.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/no-read-permission.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/not-json-extension.txt
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/not-object.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty-expected.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit-expected.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable-expected.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable.json
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-broken-symlink.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-not-array.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-not-array.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-is-directory.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-data.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-id-table.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-name.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-tu-namespace.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-nonexistent-file.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-not-object.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-permission-denied.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-write-flush-failure.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/error-write-readonly-dir.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/valid-callgraph.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/valid-defuse.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/valid-empty.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/valid-link-unit.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/valid-with-empty-data-entry.test
delete mode 100644 clang/test/Analysis/Scalable/Serialization/valid-with-idtable.test
create mode 100644 clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
delete mode 100644 clang/unittests/Analysis/Scalable/tools/CMakeLists.txt
delete mode 100644 clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/CMakeLists.txt
delete mode 100644 clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
delete mode 100644 clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json
deleted file mode 100644
index ce1ab6423d847..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-invalid-caller.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": [
- {
- "summary_name": "CallGraph",
- "summary_data": [
- {
- "entity_id": 0,
- "entity_summary": {
- "call_graph": [
- {
- "caller": "not_a_number",
- "callees": []
- }
- ]
- }
- }
- ]
- }
- ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json b/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json
deleted file mode 100644
index bd23319d80cec..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/callgraph-missing-field.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": [
- {
- "summary_name": "CallGraph",
- "summary_data": [
- {
- "entity_id": 0,
- "entity_summary": {}
- }
- ]
- }
- ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/data-element-not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/data-element-not-object.json
deleted file mode 100644
index ca953efa67e77..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/data-element-not-object.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": ["not an object"]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/data-entry-missing-data.json b/clang/test/Analysis/Scalable/Serialization/Inputs/data-entry-missing-data.json
deleted file mode 100644
index a5631d1a60559..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/data-entry-missing-data.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": [
- {
- "summary_name": "test"
- }
- ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/data-missing-summary-name.json b/clang/test/Analysis/Scalable/Serialization/Inputs/data-missing-summary-name.json
deleted file mode 100644
index d4f48d9d785f2..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/data-missing-summary-name.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": [
- {
- "data": []
- }
- ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/data-not-array.json b/clang/test/Analysis/Scalable/Serialization/Inputs/data-not-array.json
deleted file mode 100644
index 70daf052792bd..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/data-not-array.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": "not an array"
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json
deleted file mode 100644
index de3ee8d56682f..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-invalid-use.json
+++ /dev/null
@@ -1,32 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": [
- {
- "summary_name": "DefUse",
- "summary_data": [
- {
- "entity_id": 0,
- "entity_summary": {
- "def_use_chains": [
- {
- "variable": 0,
- "definitions": [
- {
- "definition": 10,
- "uses": [
- "invalid"
- ]
- }
- ]
- }
- ]
- }
- }
- ]
- }
- ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json b/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json
deleted file mode 100644
index 351825e77f74c..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/defuse-missing-field.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": [
- {
- "summary_name": "DefUse",
- "summary_data": [
- {
- "entity_id": 0,
- "entity_summary": {}
- }
- ]
- }
- ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/directory.json/.gitkeep b/clang/test/Analysis/Scalable/Serialization/Inputs/directory.json/.gitkeep
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-entity.json b/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-entity.json
deleted file mode 100644
index dd8962c409cc8..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-entity.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [
- {
- "id": 0,
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [
- {
- "kind": "compilation_unit",
- "name": "test.cpp"
- }
- ]
- }
- },
- {
- "id": 1,
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [
- {
- "kind": "compilation_unit",
- "name": "test.cpp"
- }
- ]
- }
- }
- ],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json b/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json
deleted file mode 100644
index d16d61501a361..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/duplicate-summary-name.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": [
- {
- "summary_name": "test",
- "summary_data": []
- },
- {
- "summary_name": "test",
- "summary_data": []
- }
- ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json
deleted file mode 100644
index d892ee698ee19..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-element-not-object.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": [
- {
- "summary_name": "test",
- "summary_data": [
- "not an object"
- ]
- }
- ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json
deleted file mode 100644
index 3b53bcb3564e1..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-id.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": [
- {
- "summary_name": "test",
- "summary_data": [
- {
- "entity_summary": {}
- }
- ]
- }
- ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json
deleted file mode 100644
index 6d8a43a10c80c..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-data-missing-entity-summary.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": [
- {
- "summary_name": "test",
- "summary_data": [
- {
- "entity_id": 0
- }
- ]
- }
- ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json
deleted file mode 100644
index 246a99c6537c0..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-id-not-uint64.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": [
- {
- "summary_name": "test",
- "summary_data": [
- {
- "entity_id": "not a number",
- "entity_summary": {}
- }
- ]
- }
- ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-namespace.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-namespace.json
deleted file mode 100644
index d11c1064bbeb9..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-namespace.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [
- {
- "id": 0,
- "name": {
- "usr": "c:@F at foo",
- "suffix": ""
- }
- }
- ],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-suffix.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-suffix.json
deleted file mode 100644
index 3598e8d95b2ea..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-suffix.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [
- {
- "id": 0,
- "name": {
- "usr": "c:@F at foo",
- "namespace": [
- {
- "kind": "compilation_unit",
- "name": "test.cpp"
- }
- ]
- }
- }
- ],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-usr.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-usr.json
deleted file mode 100644
index 3fab36bcb4916..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-name-missing-usr.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [
- {
- "id": 0,
- "name": {
- "suffix": "",
- "namespace": [
- {
- "kind": "compilation_unit",
- "name": "test.cpp"
- }
- ]
- }
- }
- ],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json b/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json
deleted file mode 100644
index 179f8e5d33c47..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/entity-summary-no-format-info.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": [
- {
- "summary_name": "test",
- "summary_data": [
- {
- "entity_id": 0,
- "entity_summary": {}
- }
- ]
- }
- ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-element-not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-element-not-object.json
deleted file mode 100644
index ea604010a81e9..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-element-not-object.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": ["not an object"],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-id-not-uint64.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-id-not-uint64.json
deleted file mode 100644
index 5a736543c5dbc..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-id-not-uint64.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [
- {
- "id": -1,
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [
- {
- "kind": "compilation_unit",
- "name": "test.cpp"
- }
- ]
- }
- }
- ],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-id.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-id.json
deleted file mode 100644
index f911bfb7ab94e..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-id.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [
- {
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [
- {
- "kind": "compilation_unit",
- "name": "test.cpp"
- }
- ]
- }
- }
- ],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-name.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-name.json
deleted file mode 100644
index e5cd1aea3e1ae..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-entry-missing-name.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [
- {"id": 0}
- ],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-not-array.json b/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-not-array.json
deleted file mode 100644
index fb5624207bc2c..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/id-table-not-array.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": {},
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-kind.json b/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-kind.json
deleted file mode 100644
index a39c20d3016a0..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-kind.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "tu_namespace": {
- "kind": "InvalidKind",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-syntax.json b/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-syntax.json
deleted file mode 100644
index b0e13f61aa06e..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/invalid-syntax.json
+++ /dev/null
@@ -1 +0,0 @@
-{ invalid json }
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-data.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-data.json
deleted file mode 100644
index 071de7214fd23..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-data.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-id-table.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-id-table.json
deleted file mode 100644
index 126e8fba55b5f..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-id-table.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-kind.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-kind.json
deleted file mode 100644
index 8aa5c5fb115e1..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-kind.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "tu_namespace": {
- "name": "test.cpp"
- },
- "id_table": [],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-name.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-name.json
deleted file mode 100644
index 5a7c2f2f80c08..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-name.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit"
- },
- "id_table": [],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-tu-namespace.json b/clang/test/Analysis/Scalable/Serialization/Inputs/missing-tu-namespace.json
deleted file mode 100644
index 536918c99e182..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/missing-tu-namespace.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "id_table": [],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/namespace-element-not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/namespace-element-not-object.json
deleted file mode 100644
index c6715f2e0e5cc..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/namespace-element-not-object.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [
- {
- "id": 0,
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": ["not an object"]
- }
- }
- ],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/no-read-permission.json b/clang/test/Analysis/Scalable/Serialization/Inputs/no-read-permission.json
deleted file mode 100644
index 11966839e2292..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/no-read-permission.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/not-json-extension.txt b/clang/test/Analysis/Scalable/Serialization/Inputs/not-json-extension.txt
deleted file mode 100644
index 11966839e2292..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/not-json-extension.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/not-object.json b/clang/test/Analysis/Scalable/Serialization/Inputs/not-object.json
deleted file mode 100644
index fe51488c7066f..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/not-object.json
+++ /dev/null
@@ -1 +0,0 @@
-[]
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json
deleted file mode 100644
index a9939b5467719..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph-expected.json
+++ /dev/null
@@ -1,74 +0,0 @@
-{
- "data": [
- {
- "summary_data": [
- {
- "entity_id": 0,
- "entity_summary": {
- "call_graph": [
- {
- "callees": [
- 1,
- 2
- ],
- "caller": 0
- },
- {
- "callees": [
- 2,
- 3
- ],
- "caller": 1
- },
- {
- "callees": [
- 3
- ],
- "caller": 2
- }
- ]
- }
- }
- ],
- "summary_name": "CallGraph"
- }
- ],
- "id_table": [
- {
- "id": 2,
- "name": {
- "namespace": [],
- "suffix": "",
- "usr": "c:@F at bar"
- }
- },
- {
- "id": 3,
- "name": {
- "namespace": [],
- "suffix": "",
- "usr": "c:@F at baz"
- }
- },
- {
- "id": 1,
- "name": {
- "namespace": [],
- "suffix": "",
- "usr": "c:@F at foo"
- }
- },
- {
- "id": 0,
- "name": {
- "namespace": [],
- "suffix": "",
- "usr": "c:@F at main"
- }
- }
- ],
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "call_graph_test.cpp"
- }
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json
deleted file mode 100644
index 831cd5000d1b2..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-callgraph.json
+++ /dev/null
@@ -1,74 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "call_graph_test.cpp"
- },
- "id_table": [
- {
- "id": 0,
- "name": {
- "usr": "c:@F at main",
- "suffix": "",
- "namespace": []
- }
- },
- {
- "id": 1,
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": []
- }
- },
- {
- "id": 2,
- "name": {
- "usr": "c:@F at bar",
- "suffix": "",
- "namespace": []
- }
- },
- {
- "id": 3,
- "name": {
- "usr": "c:@F at baz",
- "suffix": "",
- "namespace": []
- }
- }
- ],
- "data": [
- {
- "summary_name": "CallGraph",
- "summary_data": [
- {
- "entity_id": 0,
- "entity_summary": {
- "call_graph": [
- {
- "caller": 0,
- "callees": [
- 1,
- 2
- ]
- },
- {
- "caller": 1,
- "callees": [
- 2,
- 3
- ]
- },
- {
- "caller": 2,
- "callees": [
- 3
- ]
- }
- ]
- }
- }
- ]
- }
- ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json
deleted file mode 100644
index 72ac6212c0266..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse-expected.json
+++ /dev/null
@@ -1,108 +0,0 @@
-{
- "data": [
- {
- "summary_data": [
- {
- "entity_id": 0,
- "entity_summary": {
- "def_use_chains": [
- {
- "definitions": [
- {
- "definition": 10,
- "uses": [
- 12,
- 13
- ]
- },
- {
- "definition": 14,
- "uses": [
- 13
- ]
- }
- ],
- "variable": 0
- },
- {
- "definitions": [
- {
- "definition": 11,
- "uses": [
- 12,
- 14
- ]
- }
- ],
- "variable": 1
- }
- ]
- }
- }
- ],
- "summary_name": "DefUse"
- }
- ],
- "id_table": [
- {
- "id": 12,
- "name": {
- "namespace": [],
- "suffix": "",
- "usr": "c:@Stmt at line10"
- }
- },
- {
- "id": 13,
- "name": {
- "namespace": [],
- "suffix": "",
- "usr": "c:@Stmt at line12"
- }
- },
- {
- "id": 14,
- "name": {
- "namespace": [],
- "suffix": "",
- "usr": "c:@Stmt at line15"
- }
- },
- {
- "id": 10,
- "name": {
- "namespace": [],
- "suffix": "",
- "usr": "c:@Stmt at line5"
- }
- },
- {
- "id": 11,
- "name": {
- "namespace": [],
- "suffix": "",
- "usr": "c:@Stmt at line8"
- }
- },
- {
- "id": 0,
- "name": {
- "namespace": [],
- "suffix": "",
- "usr": "c:@V at x"
- }
- },
- {
- "id": 1,
- "name": {
- "namespace": [],
- "suffix": "",
- "usr": "c:@V at y"
- }
- }
- ],
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "defuse_test.cpp"
- }
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json
deleted file mode 100644
index b541843bfd7c7..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-defuse.json
+++ /dev/null
@@ -1,108 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "defuse_test.cpp"
- },
- "id_table": [
- {
- "id": 0,
- "name": {
- "usr": "c:@V at x",
- "suffix": "",
- "namespace": []
- }
- },
- {
- "id": 1,
- "name": {
- "usr": "c:@V at y",
- "suffix": "",
- "namespace": []
- }
- },
- {
- "id": 10,
- "name": {
- "usr": "c:@Stmt at line5",
- "suffix": "",
- "namespace": []
- }
- },
- {
- "id": 11,
- "name": {
- "usr": "c:@Stmt at line8",
- "suffix": "",
- "namespace": []
- }
- },
- {
- "id": 12,
- "name": {
- "usr": "c:@Stmt at line10",
- "suffix": "",
- "namespace": []
- }
- },
- {
- "id": 13,
- "name": {
- "usr": "c:@Stmt at line12",
- "suffix": "",
- "namespace": []
- }
- },
- {
- "id": 14,
- "name": {
- "usr": "c:@Stmt at line15",
- "suffix": "",
- "namespace": []
- }
- }
- ],
- "data": [
- {
- "summary_name": "DefUse",
- "summary_data": [
- {
- "entity_id": 0,
- "entity_summary": {
- "def_use_chains": [
- {
- "variable": 0,
- "definitions": [
- {
- "definition": 10,
- "uses": [
- 12,
- 13
- ]
- },
- {
- "definition": 14,
- "uses": [
- 13
- ]
- }
- ]
- },
- {
- "variable": 1,
- "definitions": [
- {
- "definition": 11,
- "uses": [
- 12,
- 14
- ]
- }
- ]
- }
- ]
- }
- }
- ]
- }
- ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty-expected.json
deleted file mode 100644
index e7bd20a47921c..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty-expected.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "data": [],
- "id_table": [],
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- }
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty.json
deleted file mode 100644
index 11966839e2292..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-empty.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit-expected.json
deleted file mode 100644
index 840b5289f010a..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit-expected.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "data": [],
- "id_table": [],
- "tu_namespace": {
- "kind": "link_unit",
- "name": "libtest.so"
- }
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit.json
deleted file mode 100644
index 98e1cce334fd7..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-link-unit.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "tu_namespace": {
- "kind": "link_unit",
- "name": "libtest.so"
- },
- "id_table": [],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json
deleted file mode 100644
index 2e5e958748263..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry-expected.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "data": [
- {
- "summary_data": [],
- "summary_name": "test"
- }
- ],
- "id_table": [],
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- }
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json
deleted file mode 100644
index 13a6cb34ae255..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-empty-data-entry.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": [
- {
- "summary_name": "test",
- "summary_data": []
- }
- ]
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable-expected.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable-expected.json
deleted file mode 100644
index 14142a8a22c88..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable-expected.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
- "data": [],
- "id_table": [
- {
- "id": 1,
- "name": {
- "namespace": [
- {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- {
- "kind": "link_unit",
- "name": "libtest.so"
- }
- ],
- "suffix": "1",
- "usr": "c:@F at bar"
- }
- },
- {
- "id": 0,
- "name": {
- "namespace": [
- {
- "kind": "compilation_unit",
- "name": "test.cpp"
- }
- ],
- "suffix": "",
- "usr": "c:@F at foo"
- }
- }
- ],
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- }
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable.json b/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable.json
deleted file mode 100644
index 76515b8ba750d..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/Inputs/valid-with-idtable.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [
- {
- "id": 0,
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [
- {
- "kind": "compilation_unit",
- "name": "test.cpp"
- }
- ]
- }
- },
- {
- "id": 1,
- "name": {
- "usr": "c:@F at bar",
- "suffix": "1",
- "namespace": [
- {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- {
- "kind": "link_unit",
- "name": "libtest.so"
- }
- ]
- }
- }
- ],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/Serialization/error-broken-symlink.test b/clang/test/Analysis/Scalable/Serialization/error-broken-symlink.test
deleted file mode 100644
index d1e61603c06cc..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-broken-symlink.test
+++ /dev/null
@@ -1,14 +0,0 @@
-## Test that broken symlinks with .json extension are rejected.
-## Unsupported on Windows as symlinks behave differently there.
-
-# UNSUPPORTED: system-windows
-
-# RUN: ln -sf non-existent-file.json %t-broken-symlink.json
-# RUN: not ssaf-serialization-format-test %t-broken-symlink.json 2>&1 | FileCheck %s
-# RUN: rm %t-broken-symlink.json
-
-## Test that broken symlinks are detected as non-existent files
-
-# CHECK: failed to read TUSummary
-# CHECK: failed to validate JSON file
-# CHECK: file does not exist
diff --git a/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test b/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
deleted file mode 100644
index 5ee78f60bcb2f..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/callgraph-invalid-caller.json 2>&1 | FileCheck %s
-
-// Test that CallGraph analysis validates caller field type
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize SummaryDataMap entry for summary 'CallGraph'
-// CHECK: failed to deserialize EntityDataMap at index 0
-// CHECK: failed to deserialize EntityDataMap entry
-// CHECK: CallGraph: 'caller' at index 0 is not a valid uint64
diff --git a/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test b/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
deleted file mode 100644
index 2abbf1438654c..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/callgraph-missing-field.json 2>&1 | FileCheck %s
-
-// Test that CallGraph analysis requires 'call_graph' field
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize SummaryDataMap entry for summary 'CallGraph'
-// CHECK: failed to deserialize EntityDataMap at index 0
-// CHECK: failed to deserialize EntityDataMap entry
-// CHECK: CallGraph: missing or invalid 'call_graph' field
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
deleted file mode 100644
index 1c7adf5437f6e..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/data-element-not-object.json 2>&1 | FileCheck %s
-
-// Test that 'data' array elements must be objects
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize SummaryDataMap
-// CHECK: element at index
-// CHECK: expected SummaryDataMap entry with 'summary_name' and 'summary_data' fields
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test b/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
deleted file mode 100644
index f7e25e81bc2b1..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/data-entry-missing-data.json 2>&1 | FileCheck %s
-
-// Test that data array entries must have 'summary_data' field
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize SummaryDataMap at index
-// CHECK: failed to deserialize SummaryDataMap entry
-// CHECK: missing or invalid field 'summary_data'
-// CHECK: expected JSON array of entity data entries
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test b/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
deleted file mode 100644
index 03807ee6c44cb..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/data-missing-summary-name.json 2>&1 | FileCheck %s
-
-// Test that data array entries must have 'summary_name' field
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize SummaryDataMap at index
-// CHECK: failed to deserialize SummaryDataMap entry
-// CHECK: missing required field 'summary_name'
-// CHECK: expected string identifier for the analysis summary
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-not-array.test b/clang/test/Analysis/Scalable/Serialization/error-data-not-array.test
deleted file mode 100644
index 058be1a4bddbb..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-data-not-array.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/data-not-array.json 2>&1 | FileCheck %s
-
-// Test that root 'data' field must be an array
-
-// CHECK: failed to read TUSummary
-// CHECK: missing or invalid field 'data'
-// CHECK: expected JSON array
diff --git a/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test b/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
deleted file mode 100644
index 9674ec9b4ada1..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/defuse-invalid-use.json 2>&1 | FileCheck %s
-
-// Test that DefUse analysis validates use value types
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize SummaryDataMap entry for summary 'DefUse'
-// CHECK: failed to deserialize EntityDataMap at index 0
-// CHECK: failed to deserialize EntityDataMap entry
-// CHECK: DefUse: use at index 0,0,0 is not a valid uint64
diff --git a/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test b/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
deleted file mode 100644
index f03b4a1900307..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/defuse-missing-field.json 2>&1 | FileCheck %s
-
-// Test that DefUse analysis requires 'def_use_chains' field
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize SummaryDataMap entry for summary 'DefUse'
-// CHECK: failed to deserialize EntityDataMap at index 0
-// CHECK: failed to deserialize EntityDataMap entry
-// CHECK: DefUse: missing or invalid 'def_use_chains' field
diff --git a/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test b/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
deleted file mode 100644
index 542ea9fa23552..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/duplicate-entity.json 2>&1 | FileCheck %s
-
-// Test that duplicate EntityName in id_table is detected
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable
-// CHECK: duplicate EntityName found at index 1
-// CHECK: EntityId=0 already exists in table
diff --git a/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test b/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
deleted file mode 100644
index b8f6e1335b02f..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/duplicate-summary-name.json 2>&1 | FileCheck %s
-
-// Test that duplicate summary names are detected
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize SummaryDataMap
-// CHECK: duplicate SummaryName 'test' found at index
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
deleted file mode 100644
index 43c0c9d164116..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/entity-data-element-not-object.json 2>&1 | FileCheck %s
-
-// Test that entity data elements must be objects
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityDataMap
-// CHECK: element at index 0 is not a JSON object
-// CHECK: expected EntityDataMap entry with 'entity_id' and 'entity_summary' fields
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
deleted file mode 100644
index 7c80a3e121279..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/entity-data-missing-entity-id.json 2>&1 | FileCheck %s
-
-// Test that entity data entries must have 'entity_id' field
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityDataMap at index 0
-// CHECK: failed to deserialize EntityDataMap entry
-// CHECK: missing required field 'entity_id'
-// CHECK: expected unsigned integer EntityId
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
deleted file mode 100644
index 57ed34e4eec26..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/entity-data-missing-entity-summary.json 2>&1 | FileCheck %s
-
-// Test that entity data entries must have 'entity_summary' field
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityDataMap at index 0
-// CHECK: failed to deserialize EntityDataMap entry
-// CHECK: missing or invalid field 'entity_summary'
-// CHECK: expected EntitySummary JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test b/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
deleted file mode 100644
index bacd042fa34e5..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/entity-id-not-uint64.json 2>&1 | FileCheck %s
-
-// Test that entity_id field must be a valid uint64
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityDataMap at index 0
-// CHECK: failed to deserialize EntityDataMap entry
-// CHECK: field 'entity_id' is not a valid unsigned 64-bit integer
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
deleted file mode 100644
index da2f56d600b58..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/entity-name-missing-namespace.json 2>&1 | FileCheck %s
-
-// Test that EntityName must have 'namespace' field
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable entry
-// CHECK: failed to deserialize EntityName
-// CHECK: missing or invalid field 'namespace'
-// CHECK: expected JSON array of BuildNamespace objects
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
deleted file mode 100644
index ae0975fd850d9..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/entity-name-missing-suffix.json 2>&1 | FileCheck %s
-
-// Test that EntityName must have 'suffix' field
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable entry
-// CHECK: failed to deserialize EntityName
-// CHECK: missing required field 'suffix'
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
deleted file mode 100644
index db48593c232d7..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/entity-name-missing-usr.json 2>&1 | FileCheck %s
-
-// Test that EntityName must have 'usr' field
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable entry
-// CHECK: failed to deserialize EntityName
-// CHECK: missing required field 'usr'
-// CHECK: Unified Symbol Resolution string
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test b/clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test
deleted file mode 100644
index 075eddaf271c8..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/entity-summary-no-format-info.json 2>&1 | FileCheck %s
-
-// Test that EntitySummary deserialization requires registered FormatInfo
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntitySummary
-// CHECK: no FormatInfo was registered for summary name
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test
deleted file mode 100644
index eb8a70b5d8933..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-element-not-object.json 2>&1 | FileCheck %s
-
-// Test that id_table elements must be objects
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable
-// CHECK: element at index 0 is not a JSON object
-// CHECK: expected EntityIdTable entry with 'id' and 'name' fields
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test
deleted file mode 100644
index 7d95da6189017..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-entry-id-not-uint64.json 2>&1 | FileCheck %s
-
-// Test that id_table entry 'id' field must be a valid uint64
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable entry
-// CHECK: field 'id' is not a valid unsigned 64-bit integer
-// CHECK: expected non-negative EntityId value
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test
deleted file mode 100644
index 846d0088ec974..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-entry-missing-id.json 2>&1 | FileCheck %s
-
-// Test that id_table entries must have 'id' field
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable entry
-// CHECK: missing required field 'id'
-// CHECK: expected unsigned integer EntityId
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test
deleted file mode 100644
index 2404199b14c44..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-entry-missing-name.json 2>&1 | FileCheck %s
-
-// Test that id_table entries must have 'name' field
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable entry
-// CHECK: missing or invalid field 'name'
-// CHECK: expected EntityName JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-not-array.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-not-array.test
deleted file mode 100644
index 8d7a9bd640247..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-id-table-not-array.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-not-array.json 2>&1 | FileCheck %s
-
-// Test that id_table must be an array
-
-// CHECK: failed to read TUSummary
-// CHECK: missing or invalid field 'id_table'
-// CHECK: expected JSON array
diff --git a/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test b/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
deleted file mode 100644
index 41b994f625d87..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/invalid-kind.json 2>&1 | FileCheck %s
-
-// Test that invalid kind value is detected
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize BuildNamespace
-// CHECK: invalid 'kind' BuildNamespaceKind value 'InvalidKind'
diff --git a/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test b/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
deleted file mode 100644
index 529ebd0f9a6e2..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
+++ /dev/null
@@ -1,6 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/invalid-syntax.json 2>&1 | FileCheck %s
-
-// Test that invalid JSON syntax is detected
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to read JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-is-directory.test b/clang/test/Analysis/Scalable/Serialization/error-is-directory.test
deleted file mode 100644
index e30bdd137af89..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-is-directory.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/directory.json 2>&1 | FileCheck %s
-
-// Test that directories with .json extension are rejected
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to validate JSON file
-// CHECK: path is a directory, not a file
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-data.test b/clang/test/Analysis/Scalable/Serialization/error-missing-data.test
deleted file mode 100644
index 8d4b654a8715d..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-missing-data.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/missing-data.json 2>&1 | FileCheck %s
-
-// Test that missing root 'data' field is detected
-
-// CHECK: failed to read TUSummary
-// CHECK: missing or invalid field 'data'
-// CHECK: expected JSON array
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-id-table.test b/clang/test/Analysis/Scalable/Serialization/error-missing-id-table.test
deleted file mode 100644
index a71bb4e361fdd..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-missing-id-table.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/missing-id-table.json 2>&1 | FileCheck %s
-
-// Test that missing id_table field is detected
-
-// CHECK: failed to read TUSummary
-// CHECK: missing or invalid field 'id_table'
-// CHECK: expected JSON array
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test b/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
deleted file mode 100644
index 24c276a825c7a..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/missing-kind.json 2>&1 | FileCheck %s
-
-// Test that missing kind field in tu_namespace is detected
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize BuildNamespace
-// CHECK: missing required field 'kind'
-// CHECK: expected BuildNamespaceKind
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-name.test b/clang/test/Analysis/Scalable/Serialization/error-missing-name.test
deleted file mode 100644
index 2fb17423943dc..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-missing-name.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/missing-name.json 2>&1 | FileCheck %s
-
-// Test that missing name field in tu_namespace is detected
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize BuildNamespace
-// CHECK: missing required field 'name'
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-tu-namespace.test b/clang/test/Analysis/Scalable/Serialization/error-missing-tu-namespace.test
deleted file mode 100644
index fb6b58693edf6..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-missing-tu-namespace.test
+++ /dev/null
@@ -1,7 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/missing-tu-namespace.json 2>&1 | FileCheck %s
-
-// Test that missing tu_namespace field is detected
-
-// CHECK: failed to read TUSummary
-// CHECK: missing or invalid field 'tu_namespace'
-// CHECK: expected JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
deleted file mode 100644
index 55006eb230c21..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
+++ /dev/null
@@ -1,10 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/namespace-element-not-object.json 2>&1 | FileCheck %s
-
-// Test that namespace array elements must be objects
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to deserialize EntityIdTable entry
-// CHECK: failed to deserialize EntityName
-// CHECK: failed to deserialize NestedBuildNamespace
-// CHECK: element at index 0 is not a JSON object
-// CHECK: expected BuildNamespace object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-nonexistent-file.test b/clang/test/Analysis/Scalable/Serialization/error-nonexistent-file.test
deleted file mode 100644
index 3748d54622e75..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-nonexistent-file.test
+++ /dev/null
@@ -1,6 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/nonexistent.json 2>&1 | FileCheck %s
-
-// Test that nonexistent file is detected
-
-// CHECK: failed to read TUSummary
-// CHECK: file does not exist
diff --git a/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test b/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
deleted file mode 100644
index 1a56fc6e13dc6..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
+++ /dev/null
@@ -1,8 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/not-json-extension.txt 2>&1 | FileCheck %s
-
-// Test that files with non-.json extensions are rejected
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to read JSON object
-// CHECK: failed to validate JSON file
-// CHECK: not a JSON file
diff --git a/clang/test/Analysis/Scalable/Serialization/error-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-not-object.test
deleted file mode 100644
index 20409e7642b32..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-not-object.test
+++ /dev/null
@@ -1,6 +0,0 @@
-// RUN: not ssaf-serialization-format-test %S/Inputs/not-object.json 2>&1 | FileCheck %s
-
-// Test that JSON array (not object) is rejected
-
-// CHECK: failed to read TUSummary
-// CHECK: failed to read JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-permission-denied.test b/clang/test/Analysis/Scalable/Serialization/error-permission-denied.test
deleted file mode 100644
index b89f32d19620d..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-permission-denied.test
+++ /dev/null
@@ -1,14 +0,0 @@
-## Test that files without read permissions are handled correctly.
-## Unsupported on Windows as chmod doesn't work reliably there.
-
-# UNSUPPORTED: system-windows
-# REQUIRES: non-root-user
-
-# RUN: chmod 000 %S/Inputs/no-read-permission.json
-# RUN: not ssaf-serialization-format-test %S/Inputs/no-read-permission.json 2>&1 | FileCheck %s
-# RUN: chmod 644 %S/Inputs/no-read-permission.json
-
-## Test that permission denied error is reported when file cannot be read
-
-# CHECK: failed to read TUSummary
-# CHECK: failed to read file
diff --git a/clang/test/Analysis/Scalable/Serialization/error-write-flush-failure.test b/clang/test/Analysis/Scalable/Serialization/error-write-flush-failure.test
deleted file mode 100644
index 9bbdc4e8a611e..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-write-flush-failure.test
+++ /dev/null
@@ -1,13 +0,0 @@
-## Test that write flush failures are handled correctly.
-## Uses /dev/full which always reports "disk full" errors on write.
-## This special device file successfully opens but fails on write/flush operations.
-
-# REQUIRES: system-linux
-
-# RUN: not ssaf-serialization-format-test %S/Inputs/valid-defuse.json -o /dev/full 2>&1 | FileCheck %s
-
-## Test that flush/write error is reported when device is full
-## This tests JSONFormat.cpp:97-99 (write/flush failure path)
-
-# CHECK: failed to write TUSummary
-# CHECK: write failed
diff --git a/clang/test/Analysis/Scalable/Serialization/error-write-readonly-dir.test b/clang/test/Analysis/Scalable/Serialization/error-write-readonly-dir.test
deleted file mode 100644
index 47087f0a1de12..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/error-write-readonly-dir.test
+++ /dev/null
@@ -1,16 +0,0 @@
-## Test that write failures to read-only directories are handled correctly.
-## Unsupported on Windows as chmod doesn't work reliably there.
-
-# UNSUPPORTED: system-windows
-# REQUIRES: non-root-user
-
-# RUN: rm -rf %t-readonly && mkdir -p %t-readonly
-# RUN: chmod 444 %t-readonly
-# RUN: not ssaf-serialization-format-test %S/Inputs/valid-empty.json -o %t-readonly/output.json 2>&1 | FileCheck %s
-# RUN: chmod 755 %t-readonly && rm -rf %t-readonly
-
-## Test that write error is reported when output directory is read-only
-## This tests JSONFormat.cpp:89-92 (file open failure path)
-
-# CHECK: failed to write TUSummary
-# CHECK: failed to open
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-callgraph.test b/clang/test/Analysis/Scalable/Serialization/valid-callgraph.test
deleted file mode 100644
index 09722d11a796e..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/valid-callgraph.test
+++ /dev/null
@@ -1,9 +0,0 @@
-// RUN: ssaf-serialization-format-test %S/Inputs/valid-callgraph.json -q -o %t.json
-// RUN: diff %t.json %S/Inputs/valid-callgraph-expected.json
-
-// Test CallGraph analysis round-trip serialization
-// This test verifies that:
-// - CallGraph analysis data can be read from JSON
-// - The call graph structure (caller -> callees) is preserved
-// - EntityId references are correctly serialized/deserialized
-// - Output matches expected format with alphabetically sorted fields
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-defuse.test b/clang/test/Analysis/Scalable/Serialization/valid-defuse.test
deleted file mode 100644
index 311bd02fdde3d..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/valid-defuse.test
+++ /dev/null
@@ -1,10 +0,0 @@
-// RUN: ssaf-serialization-format-test %S/Inputs/valid-defuse.json -q -o %t.json
-// RUN: diff %t.json %S/Inputs/valid-defuse-expected.json
-
-// Test DefUse analysis round-trip serialization
-// This test verifies that:
-// - DefUse analysis data can be read from JSON
-// - The def-use chain structure (variable -> definitions -> uses) is preserved
-// - Nested maps and sets are correctly serialized/deserialized
-// - EntityId references are correctly converted using EntityIdConverter
-// - Output matches expected format with alphabetically sorted fields
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-empty.test b/clang/test/Analysis/Scalable/Serialization/valid-empty.test
deleted file mode 100644
index 30712a3e8d637..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/valid-empty.test
+++ /dev/null
@@ -1,4 +0,0 @@
-// RUN: ssaf-serialization-format-test %S/Inputs/valid-empty.json -q -o %t.json
-// RUN: diff %t.json %S/Inputs/valid-empty-expected.json
-
-// Test that an empty valid TUSummary can be read and written back
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-link-unit.test b/clang/test/Analysis/Scalable/Serialization/valid-link-unit.test
deleted file mode 100644
index b9533904c0bd1..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/valid-link-unit.test
+++ /dev/null
@@ -1,4 +0,0 @@
-// RUN: ssaf-serialization-format-test %S/Inputs/valid-link-unit.json -q -o %t.json
-// RUN: diff %t.json %S/Inputs/valid-link-unit-expected.json
-
-// Test that a TUSummary with link_unit namespace can be read and written back
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-with-empty-data-entry.test b/clang/test/Analysis/Scalable/Serialization/valid-with-empty-data-entry.test
deleted file mode 100644
index a9be32902a10a..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/valid-with-empty-data-entry.test
+++ /dev/null
@@ -1,4 +0,0 @@
-// RUN: ssaf-serialization-format-test %S/Inputs/valid-with-empty-data-entry.json -q -o %t.json
-// RUN: diff %t.json %S/Inputs/valid-with-empty-data-entry-expected.json
-
-// Test that a TUSummary with an empty data entry can be read and written back
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-with-idtable.test b/clang/test/Analysis/Scalable/Serialization/valid-with-idtable.test
deleted file mode 100644
index a714aa7dcf2d1..0000000000000
--- a/clang/test/Analysis/Scalable/Serialization/valid-with-idtable.test
+++ /dev/null
@@ -1,4 +0,0 @@
-// RUN: ssaf-serialization-format-test %S/Inputs/valid-with-idtable.json -q -o %t.json
-// RUN: diff %t.json %S/Inputs/valid-with-idtable-expected.json
-
-// Test that a TUSummary with id_table entries can be read and written back
diff --git a/clang/test/lit.cfg.py b/clang/test/lit.cfg.py
index bb5c9b331a43f..a622f5335354a 100644
--- a/clang/test/lit.cfg.py
+++ b/clang/test/lit.cfg.py
@@ -103,7 +103,6 @@
"clang-diff",
"clang-format",
"clang-repl",
- "ssaf-serialization-format-test",
"llvm-offload-binary",
"clang-tblgen",
"clang-scan-deps",
diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt b/clang/unittests/Analysis/Scalable/CMakeLists.txt
index 3c642dd4b2e67..cdcd62f797ba8 100644
--- a/clang/unittests/Analysis/Scalable/CMakeLists.txt
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -10,6 +10,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
Registries/MockSummaryExtractor2.cpp
Registries/SerializationFormatRegistryTest.cpp
Registries/SummaryExtractorRegistryTest.cpp
+ Serialization/JSONFormatTest.cpp
SummaryNameTest.cpp
CLANG_LIBS
@@ -28,5 +29,3 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
Support
)
-add_subdirectory(tools)
-
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
new file mode 100644
index 0000000000000..3aec2483766cd
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
@@ -0,0 +1,1091 @@
+//===- unittests/Analysis/Scalable/JSONFormatTest.cpp --------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Unit tests for SSAF JSON serialization format reading and writing.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Registry.h"
+#include "llvm/Support/VirtualFileSystem.h"
+#include "llvm/Support/raw_ostream.h"
+#include "gtest/gtest.h"
+#include <memory>
+#include <string>
+#include <vector>
+
+using namespace clang::ssaf;
+using namespace llvm;
+
+namespace {
+
+// ============================================================================
+// Test Analysis - Simple analysis for testing JSON serialization
+// ============================================================================
+
+struct TestAnalysis : EntitySummary {
+ TestAnalysis() : EntitySummary(SummaryName("test_summary")) {}
+ std::vector<std::pair<EntityId, EntityId>> Pairs;
+};
+
+static json::Object
+serializeTestAnalysis(const EntitySummary &Summary,
+ const JSONFormat::EntityIdConverter &Converter) {
+ const auto &TA = static_cast<const TestAnalysis &>(Summary);
+ json::Array PairsArray;
+ for (const auto &[First, Second] : TA.Pairs) {
+ PairsArray.push_back(json::Object{
+ {"first", Converter.toJSON(First)},
+ {"second", Converter.toJSON(Second)},
+ });
+ }
+ return json::Object{{"pairs", std::move(PairsArray)}};
+}
+
+static Expected<std::unique_ptr<EntitySummary>>
+deserializeTestAnalysis(const json::Object &Obj, EntityIdTable &IdTable,
+ const JSONFormat::EntityIdConverter &Converter) {
+ auto Result = std::make_unique<TestAnalysis>();
+ const json::Array *PairsArray = Obj.getArray("pairs");
+ if (!PairsArray)
+ return createStringError(inconvertibleErrorCode(),
+ "missing required field 'pairs'");
+ for (size_t I = 0; I < PairsArray->size(); ++I) {
+ const json::Object *Pair = (*PairsArray)[I].getAsObject();
+ if (!Pair)
+ return createStringError(
+ inconvertibleErrorCode(),
+ "pairs element at index %zu is not a JSON object", I);
+ auto FirstOpt = Pair->getInteger("first");
+ if (!FirstOpt)
+ return createStringError(inconvertibleErrorCode(),
+ "missing or invalid 'first' field at index %zu",
+ I);
+ auto SecondOpt = Pair->getInteger("second");
+ if (!SecondOpt)
+ return createStringError(inconvertibleErrorCode(),
+ "missing or invalid 'second' field at index %zu",
+ I);
+ Result->Pairs.emplace_back(Converter.fromJSON(*FirstOpt),
+ Converter.fromJSON(*SecondOpt));
+ }
+ return std::move(Result);
+}
+
+struct TestAnalysisFormatInfo : JSONFormat::FormatInfo {
+ TestAnalysisFormatInfo()
+ : JSONFormat::FormatInfo(SummaryName("test_summary"),
+ serializeTestAnalysis, deserializeTestAnalysis) {
+ }
+};
+
+static llvm::Registry<JSONFormat::FormatInfo>::Add<TestAnalysisFormatInfo>
+ RegisterTestAnalysis("TestAnalysis", "Format info for test analysis data");
+
+// ============================================================================
+// Base Fixture with Common Utilities
+// ============================================================================
+
+class JSONFormatTestBase : public ::testing::Test {
+protected:
+ SmallString<128> TestDir;
+
+ void SetUp() override {
+ std::error_code EC =
+ sys::fs::createUniqueDirectory("json-format-test", TestDir);
+ ASSERT_FALSE(EC) << "Failed to create temp directory: " << EC.message();
+ }
+
+ void TearDown() override { sys::fs::remove_directories(TestDir); }
+
+ // Helper to create a temporary JSON file and read it using JSONFormat
+ std::pair<JSONFormat, SmallString<128>>
+ readJSON(StringRef JSON, StringRef Filename = "test.json") {
+ SmallString<128> FilePath = TestDir;
+ sys::path::append(FilePath, Filename);
+
+ std::error_code EC;
+ raw_fd_ostream OS(FilePath, EC);
+ EXPECT_FALSE(EC) << "Failed to create file: " << EC.message();
+ OS << JSON;
+ OS.close();
+
+ return {JSONFormat(vfs::getRealFileSystem()), FilePath};
+ }
+
+ // Helper to check if error message contains expected substrings
+ bool errorContains(Error &Err, ArrayRef<StringRef> Parts) {
+ std::string ErrorMsg = toString(std::move(Err));
+ for (StringRef Part : Parts) {
+ if (ErrorMsg.find(Part.str()) == std::string::npos)
+ return false;
+ }
+ return true;
+ }
+};
+
+// ============================================================================
+// Fixture for Error Tests
+// ============================================================================
+
+class JSONFormatErrorTest : public JSONFormatTestBase {
+protected:
+ void expectError(StringRef JSON, ArrayRef<StringRef> ErrorParts,
+ StringRef Filename = "test.json") {
+ auto [Format, FilePath] = createFormat(JSON, Filename);
+ auto Result = Format.readTUSummary(FilePath);
+ ASSERT_FALSE(Result) << "Expected error but read succeeded";
+
+ Error Err = Result.takeError();
+ std::string ErrorMsg = toString(std::move(Err));
+
+ bool allFound = true;
+ for (StringRef Part : ErrorParts) {
+ if (ErrorMsg.find(Part.str()) == std::string::npos) {
+ allFound = false;
+ break;
+ }
+ }
+
+ EXPECT_TRUE(allFound) << "Error message didn't contain expected parts.\n"
+ << "Actual error: " << ErrorMsg << "\n"
+ << "Expected parts: [" <<
+ [&]() {
+ std::string result;
+ for (size_t i = 0; i < ErrorParts.size(); ++i) {
+ if (i > 0)
+ result += ", ";
+ result += "\"" + ErrorParts[i].str() + "\"";
+ }
+ return result;
+ }() << "]";
+ }
+};
+
+// ============================================================================
+// Fixture for Valid Configuration Tests
+// ============================================================================
+
+class JSONFormatValidTest : public JSONFormatTestBase {
+protected:
+ void expectSuccess(StringRef JSON, StringRef Filename = "test.json") {
+ auto [Format, FilePath] = createFormat(JSON, Filename);
+ auto Result = Format.readTUSummary(FilePath);
+ if (!Result) {
+ FAIL() << "Read failed: " << toString(Result.takeError());
+ }
+ }
+};
+
+// ============================================================================
+// Fixture for Round-Trip Tests
+// ============================================================================
+
+class JSONFormatRoundTripTest : public JSONFormatTestBase {
+protected:
+ void testRoundTrip(StringRef InputJSON) {
+ // Read the input
+ auto [InputFormat, InputPath] = createFormat(InputJSON, "input.json");
+ auto Summary = InputFormat.readTUSummary(InputPath);
+ if (!Summary) {
+ FAIL() << "Failed to read input: " << toString(Summary.takeError());
+ }
+
+ // Write to output file
+ SmallString<128> OutputPath = TestDir;
+ sys::path::append(OutputPath, "output.json");
+
+ JSONFormat OutputFormat(vfs::getRealFileSystem());
+ auto WriteErr = OutputFormat.writeTUSummary(*Summary, OutputPath);
+ if (WriteErr) {
+ FAIL() << "Failed to write output: " << toString(std::move(WriteErr));
+ }
+
+ // Read back the written file
+ auto RoundTrip = OutputFormat.readTUSummary(OutputPath);
+ if (!RoundTrip) {
+ FAIL() << "Failed to read round-trip output: "
+ << toString(RoundTrip.takeError());
+ }
+ }
+};
+
+// ============================================================================
+// File Access Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatErrorTest, NonexistentFile) {
+ SmallString<128> NonexistentPath = TestDir;
+ sys::path::append(NonexistentPath, "nonexistent.json");
+
+ JSONFormat Format(vfs::getRealFileSystem());
+ auto Result = Format.readTUSummary(NonexistentPath);
+ ASSERT_FALSE(Result);
+
+ Error Err = Result.takeError();
+ EXPECT_TRUE(
+ errorContains(Err, {"reading TUSummary from", "file does not exist"}));
+}
+
+TEST_F(JSONFormatErrorTest, NotJsonExtension) {
+ expectError("{}", {"reading TUSummary from", "not a JSON file"}, "test.txt");
+}
+
+// ============================================================================
+// JSON Syntax Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatErrorTest, InvalidSyntax) {
+ expectError("{ invalid json }", {"reading TUSummary from",
+ "failed to read JSON object from file"});
+}
+
+TEST_F(JSONFormatErrorTest, NotObject) {
+ expectError(
+ "[]", {"reading TUSummary from", "failed to read JSON object from file"});
+}
+
+// ============================================================================
+// Root Structure Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatErrorTest, MissingTUNamespace) {
+ expectError(
+ R"({
+ "id_table": [],
+ "data": []
+ })",
+ {"reading TUSummary from", "missing or invalid field 'tu_namespace'"});
+}
+
+TEST_F(JSONFormatErrorTest, MissingKind) {
+ expectError(R"({
+ "tu_namespace": {
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": []
+ })",
+ {"reading TUSummary from", "failed to deserialize BuildNamespace",
+ "missing required field 'kind' (expected BuildNamespaceKind)"});
+}
+
+TEST_F(JSONFormatErrorTest, MissingName) {
+ expectError(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit"
+ },
+ "id_table": [],
+ "data": []
+ })",
+ {"reading TUSummary from", "failed to deserialize BuildNamespace",
+ "missing required field 'name'"});
+}
+
+TEST_F(JSONFormatErrorTest, InvalidKind) {
+ expectError(R"({
+ "tu_namespace": {
+ "kind": "invalid_kind",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": []
+ })",
+ {"reading TUSummary from", "failed to deserialize BuildNamespace",
+ "invalid 'kind' BuildNamespaceKind value"});
+}
+
+TEST_F(JSONFormatErrorTest, MissingIDTable) {
+ expectError(
+ R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "data": []
+ })",
+ {"reading TUSummary from", "missing or invalid field 'id_table'"});
+}
+
+TEST_F(JSONFormatErrorTest, MissingData) {
+ expectError(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": []
+ })",
+ {"reading TUSummary from", "missing or invalid field 'data'"});
+}
+
+// ============================================================================
+// ID Table Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatErrorTest, IDTableNotArray) {
+ expectError(
+ R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": {},
+ "data": []
+ })",
+ {"reading TUSummary from", "missing or invalid field 'id_table'"});
+}
+
+TEST_F(JSONFormatErrorTest, IDTableElementNotObject) {
+ expectError(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [123],
+ "data": []
+ })",
+ {"reading TUSummary from", "failed to deserialize EntityIdTable",
+ "element at index 0 is not a JSON object",
+ "(expected EntityIdTable entry with 'id' and 'name' fields)"});
+}
+
+TEST_F(JSONFormatErrorTest, IDTableEntryMissingID) {
+ expectError(
+ R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": []
+ }
+ }
+ ],
+ "data": []
+ })",
+ {"reading TUSummary from",
+ "failed to deserialize EntityIdTable at index 0",
+ "failed to deserialize EntityIdTable entry",
+ "missing required field 'id' (expected unsigned integer EntityId)"});
+}
+
+TEST_F(JSONFormatErrorTest, IDTableEntryMissingName) {
+ expectError(
+ R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0
+ }
+ ],
+ "data": []
+ })",
+ {"reading TUSummary from",
+ "failed to deserialize EntityIdTable at index 0",
+ "failed to deserialize EntityIdTable entry",
+ "missing or invalid field 'name' (expected EntityName JSON object)"});
+}
+
+TEST_F(JSONFormatErrorTest, IDTableEntryIDNotUInt64) {
+ expectError(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "id": "not_a_number",
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": []
+ }
+ }
+ ],
+ "data": []
+ })",
+ {"reading TUSummary from",
+ "failed to deserialize EntityIdTable at index 0",
+ "failed to deserialize EntityIdTable entry",
+ "field 'id' is not a valid unsigned 64-bit integer",
+ "(expected non-negative EntityId value)"});
+}
+
+TEST_F(JSONFormatErrorTest, DuplicateEntity) {
+ expectError(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [
+ {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ }
+ ]
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [
+ {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ }
+ ]
+ }
+ }
+ ],
+ "data": []
+ })",
+ {"reading TUSummary from", "failed to deserialize EntityIdTable",
+ "duplicate EntityName found at index",
+ "(EntityId=0 already exists in table)"});
+}
+
+// ============================================================================
+// Entity Name Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatErrorTest, EntityNameMissingUSR) {
+ expectError(
+ R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "namespace": []
+ }
+ }
+ ],
+ "data": []
+ })",
+ {"reading TUSummary from",
+ "failed to deserialize EntityIdTable at index 0",
+ "failed to deserialize EntityIdTable entry",
+ "failed to deserialize EntityName",
+ "missing required field 'usr' (Unified Symbol Resolution string)"});
+}
+
+TEST_F(JSONFormatErrorTest, EntityNameMissingSuffix) {
+ expectError(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "namespace": []
+ }
+ }
+ ],
+ "data": []
+ })",
+ {"reading TUSummary from",
+ "failed to deserialize EntityIdTable at index 0",
+ "failed to deserialize EntityIdTable entry",
+ "failed to deserialize EntityName",
+ "missing required field 'suffix'"});
+}
+
+TEST_F(JSONFormatErrorTest, EntityNameMissingNamespace) {
+ expectError(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": ""
+ }
+ }
+ ],
+ "data": []
+ })",
+ {"reading TUSummary from",
+ "failed to deserialize EntityIdTable at index 0",
+ "failed to deserialize EntityIdTable entry",
+ "failed to deserialize EntityName",
+ "missing or invalid field 'namespace'",
+ "(expected JSON array of BuildNamespace objects)"});
+}
+
+TEST_F(JSONFormatErrorTest, NamespaceElementNotObject) {
+ expectError(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": ["invalid"]
+ }
+ }
+ ],
+ "data": []
+ })",
+ {"reading TUSummary from",
+ "failed to deserialize EntityIdTable at index 0",
+ "failed to deserialize EntityIdTable entry",
+ "failed to deserialize EntityName",
+ "failed to deserialize NestedBuildNamespace",
+ "element at index 0 is not a JSON object"});
+}
+
+// ============================================================================
+// Data Array Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatErrorTest, DataNotArray) {
+ expectError(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": {}
+ })",
+ {"reading TUSummary from", "missing or invalid field 'data'"});
+}
+
+TEST_F(JSONFormatErrorTest, DataElementNotObject) {
+ expectError(
+ R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": ["invalid"]
+ })",
+ {"reading TUSummary from", "failed to deserialize SummaryDataMap",
+ "element at index 0 is not a JSON object",
+ "(expected SummaryDataMap entry with 'summary_name' and 'summary_data'",
+ "fields)"});
+}
+
+TEST_F(JSONFormatErrorTest, DataEntryMissingSummaryName) {
+ expectError(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_data": []
+ }
+ ]
+ })",
+ {"reading TUSummary from",
+ "failed to deserialize SummaryDataMap at index 0",
+ "failed to deserialize SummaryDataMap entry",
+ "missing required field 'summary_name'"});
+}
+
+TEST_F(JSONFormatErrorTest, DataEntryMissingData) {
+ expectError(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "test_summary"
+ }
+ ]
+ })",
+ {"reading TUSummary from",
+ "failed to deserialize SummaryDataMap at index 0",
+ "failed to deserialize SummaryDataMap entry",
+ "missing or invalid field 'summary_data'"});
+}
+
+TEST_F(JSONFormatErrorTest, DuplicateSummaryName) {
+ expectError(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "test_summary",
+ "summary_data": []
+ },
+ {
+ "summary_name": "test_summary",
+ "summary_data": []
+ }
+ ]
+ })",
+ {"reading TUSummary from", "failed to deserialize SummaryDataMap",
+ "duplicate SummaryName 'test_summary' found at index"});
+}
+
+// ============================================================================
+// Entity Data Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatErrorTest, EntityDataElementNotObject) {
+ expectError(
+ R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "test_summary",
+ "summary_data": ["invalid"]
+ }
+ ]
+ })",
+ {"reading TUSummary from",
+ "failed to deserialize SummaryDataMap at index 0",
+ "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
+ "failed to deserialize EntityDataMap",
+ "element at index 0 is not a JSON object",
+ "(expected EntityDataMap entry with 'entity_id' and 'entity_summary'",
+ "fields)"});
+}
+
+TEST_F(JSONFormatErrorTest, EntityDataMissingEntityID) {
+ expectError(
+ R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "test_summary",
+ "summary_data": [
+ {
+ "entity_summary": {}
+ }
+ ]
+ }
+ ]
+ })",
+ {"reading TUSummary from",
+ "failed to deserialize SummaryDataMap at index 0",
+ "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
+ "failed to deserialize EntityDataMap at index 0",
+ "failed to deserialize EntityDataMap entry",
+ "missing required field 'entity_id' (expected unsigned integer "
+ "EntityId)"});
+}
+
+TEST_F(JSONFormatErrorTest, EntityDataMissingEntitySummary) {
+ expectError(
+ R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "test_summary",
+ "summary_data": [
+ {
+ "entity_id": 0
+ }
+ ]
+ }
+ ]
+ })",
+ {"reading TUSummary from",
+ "failed to deserialize SummaryDataMap at index 0",
+ "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
+ "failed to deserialize EntityDataMap at index 0",
+ "failed to deserialize EntityDataMap entry",
+ "missing or invalid field 'entity_summary'",
+ "(expected EntitySummary JSON object)"});
+}
+
+TEST_F(JSONFormatErrorTest, EntityIDNotUInt64) {
+ expectError(
+ R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "test_summary",
+ "summary_data": [
+ {
+ "entity_id": "not_a_number",
+ "entity_summary": {}
+ }
+ ]
+ }
+ ]
+ })",
+ {"reading TUSummary from",
+ "failed to deserialize SummaryDataMap at index 0",
+ "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
+ "failed to deserialize EntityDataMap at index 0",
+ "failed to deserialize EntityDataMap entry",
+ "field 'entity_id' is not a valid unsigned 64-bit integer",
+ "(expected non-negative EntityId value)"});
+}
+
+TEST_F(JSONFormatErrorTest, EntitySummaryNoFormatInfo) {
+ expectError(
+ R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "unknown_summary_type",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {}
+ }
+ ]
+ }
+ ]
+ })",
+ {"reading TUSummary from",
+ "failed to deserialize SummaryDataMap at index 0",
+ "failed to deserialize SummaryDataMap entry for summary "
+ "'unknown_summary_type'",
+ "failed to deserialize EntityDataMap at index 0",
+ "failed to deserialize EntityDataMap entry",
+ "failed to deserialize EntitySummary",
+ "no FormatInfo was registered for summary name: unknown_summary_type"});
+}
+
+// ============================================================================
+// Analysis-Specific Error Tests - TestAnalysis
+// ============================================================================
+
+TEST_F(JSONFormatErrorTest, TestAnalysisMissingField) {
+ expectError(
+ R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "test_summary",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {}
+ }
+ ]
+ }
+ ]
+ })",
+ {"reading TUSummary from",
+ "failed to deserialize SummaryDataMap at index 0",
+ "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
+ "failed to deserialize EntityDataMap at index 0",
+ "failed to deserialize EntityDataMap entry",
+ "missing required field 'pairs'"});
+}
+
+TEST_F(JSONFormatErrorTest, TestAnalysisInvalidPair) {
+ expectError(
+ R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "test_summary",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "pairs": [
+ {
+ "first": 0,
+ "second": "not_a_number"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ })",
+ {"reading TUSummary from",
+ "failed to deserialize SummaryDataMap at index 0",
+ "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
+ "failed to deserialize EntityDataMap at index 0",
+ "failed to deserialize EntityDataMap entry",
+ "missing or invalid 'second' field at index 0"});
+}
+
+// ============================================================================
+// Valid Configuration Tests
+// ============================================================================
+
+TEST_F(JSONFormatValidTest, Empty) {
+ expectSuccess(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": []
+ })");
+}
+
+TEST_F(JSONFormatValidTest, LinkUnit) {
+ expectSuccess(R"({
+ "tu_namespace": {
+ "kind": "link_unit",
+ "name": "libtest.so"
+ },
+ "id_table": [],
+ "data": []
+ })");
+}
+
+TEST_F(JSONFormatValidTest, WithIDTable) {
+ expectSuccess(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [
+ {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ }
+ ]
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "usr": "c:@F at bar",
+ "suffix": "1",
+ "namespace": [
+ {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ {
+ "kind": "link_unit",
+ "name": "libtest.so"
+ }
+ ]
+ }
+ }
+ ],
+ "data": []
+ })");
+}
+
+TEST_F(JSONFormatValidTest, WithEmptyDataEntry) {
+ expectSuccess(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": [
+ {
+ "summary_name": "test_summary",
+ "summary_data": []
+ }
+ ]
+ })");
+}
+
+// ============================================================================
+// Round-Trip Tests
+// ============================================================================
+
+TEST_F(JSONFormatRoundTripTest, Empty) {
+ testRoundTrip(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": []
+ })");
+}
+
+TEST_F(JSONFormatRoundTripTest, WithIDTable) {
+ testRoundTrip(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [
+ {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ }
+ ]
+ }
+ }
+ ],
+ "data": []
+ })");
+}
+
+TEST_F(JSONFormatRoundTripTest, LinkUnit) {
+ testRoundTrip(R"({
+ "tu_namespace": {
+ "kind": "link_unit",
+ "name": "libtest.so"
+ },
+ "id_table": [],
+ "data": []
+ })");
+}
+
+// ============================================================================
+// Analysis-Specific Round-Trip Tests
+// ============================================================================
+
+TEST_F(JSONFormatRoundTripTest, TestAnalysis) {
+ testRoundTrip(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at main",
+ "suffix": "",
+ "namespace": [
+ {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ }
+ ]
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [
+ {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ }
+ ]
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "usr": "c:@F at bar",
+ "suffix": "",
+ "namespace": [
+ {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ }
+ ]
+ }
+ }
+ ],
+ "data": [
+ {
+ "summary_name": "test_summary",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "pairs": [
+ {
+ "first": 0,
+ "second": 1
+ },
+ {
+ "first": 1,
+ "second": 2
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ })");
+}
+
+} // anonymous namespace
diff --git a/clang/unittests/Analysis/Scalable/tools/CMakeLists.txt b/clang/unittests/Analysis/Scalable/tools/CMakeLists.txt
deleted file mode 100644
index 69e51c6a6aba5..0000000000000
--- a/clang/unittests/Analysis/Scalable/tools/CMakeLists.txt
+++ /dev/null
@@ -1 +0,0 @@
-add_subdirectory(ssaf-serialization-format-test)
diff --git a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/CMakeLists.txt b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/CMakeLists.txt
deleted file mode 100644
index ea008bee8d2db..0000000000000
--- a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/CMakeLists.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-add_clang_tool(ssaf-serialization-format-test
- SSAFSerializationFormatTest.cpp
- ExampleAnalyses.cpp
- )
-
-target_link_libraries(ssaf-serialization-format-test
- PRIVATE
- clangAnalysisScalable
- )
diff --git a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
deleted file mode 100644
index fa93c8d64e9a3..0000000000000
--- a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
+++ /dev/null
@@ -1,338 +0,0 @@
-//===- ExampleAnalyses.cpp - Example analysis data for clang-ssaf-dump ---===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This file defines example analysis data structures and their serialization/
-// deserialization functions for the JSONFormat.
-//
-//===----------------------------------------------------------------------===//
-
-#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
-#include "clang/Analysis/Scalable/TUSummary/EntitySummary.h"
-#include "llvm/Support/JSON.h"
-#include "llvm/Support/Registry.h"
-#include <map>
-#include <set>
-
-using namespace clang::ssaf;
-
-//===----------------------------------------------------------------------===//
-// CallGraphAnalysis - Tracks caller-to-callees relationships
-//===----------------------------------------------------------------------===//
-
-namespace {
-/// Example analysis that tracks function call relationships.
-///
-/// This analysis builds a call graph where each function (represented by an
-/// EntityId) is mapped to the set of functions it directly calls. This is
-/// useful for understanding control flow and dependencies between functions.
-///
-/// Example structure:
-/// CallGraph[functionA] = {functionB, functionC}
-/// CallGraph[functionB] = {functionD}
-///
-/// This indicates that functionA calls functionB and functionC, and
-/// functionB calls functionD.
-struct CallGraphAnalysis : EntitySummary {
- CallGraphAnalysis() : EntitySummary(SummaryName("CallGraph")) {}
-
- /// Maps each caller function (EntityId) to the set of functions it calls.
- /// Key: Caller function EntityId
- /// Value: Set of callee function EntityIds
- std::map<EntityId, std::set<EntityId>> CallGraph;
-};
-} // namespace
-
-static llvm::json::Object
-serializeCallGraph(const EntitySummary &Data,
- const JSONFormat::EntityIdConverter &Converter) {
- const auto &CG = static_cast<const CallGraphAnalysis &>(Data);
- llvm::json::Object Result;
-
- // Serialize the call graph as an array of objects
- llvm::json::Array CallGraphArray;
- for (const auto &[Caller, Callees] : CG.CallGraph) {
- llvm::json::Object Entry;
- Entry["caller"] = Converter.toJSON(Caller);
-
- llvm::json::Array CalleesArray;
- for (const auto &Callee : Callees) {
- CalleesArray.push_back(Converter.toJSON(Callee));
- }
- Entry["callees"] = std::move(CalleesArray);
-
- CallGraphArray.push_back(std::move(Entry));
- }
-
- Result["call_graph"] = std::move(CallGraphArray);
- return Result;
-}
-
-static llvm::Expected<std::unique_ptr<EntitySummary>>
-deserializeCallGraph(const llvm::json::Object &JSONObj, EntityIdTable &Table,
- const JSONFormat::EntityIdConverter &Converter) {
- auto Result = std::make_unique<CallGraphAnalysis>();
-
- const llvm::json::Array *CallGraphArray = JSONObj.getArray("call_graph");
- if (!CallGraphArray) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "CallGraph: missing or invalid 'call_graph' field");
- }
-
- for (size_t Index = 0; Index < CallGraphArray->size(); ++Index) {
- const llvm::json::Object *EntryObj = (*CallGraphArray)[Index].getAsObject();
- if (!EntryObj) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "CallGraph: call_graph entry at index %zu is not an object", Index);
- }
-
- // Parse caller
- const llvm::json::Value *CallerValue = EntryObj->get("caller");
- if (!CallerValue) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "CallGraph: entry at index %zu missing 'caller' field", Index);
- }
- auto CallerInt = CallerValue->getAsUINT64();
- if (!CallerInt) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "CallGraph: 'caller' at index %zu is not a valid uint64", Index);
- }
- EntityId Caller = Converter.fromJSON(*CallerInt);
-
- // Parse callees array
- const llvm::json::Array *CalleesArray = EntryObj->getArray("callees");
- if (!CalleesArray) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "CallGraph: entry at index %zu missing or invalid 'callees' field",
- Index);
- }
-
- std::set<EntityId> Callees;
- for (size_t CalleeIndex = 0; CalleeIndex < CalleesArray->size();
- ++CalleeIndex) {
- auto CalleeInt = (*CalleesArray)[CalleeIndex].getAsUINT64();
- if (!CalleeInt) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "CallGraph: callee at index %zu,%zu is not a valid uint64", Index,
- CalleeIndex);
- }
- Callees.insert(Converter.fromJSON(*CalleeInt));
- }
-
- Result->CallGraph[Caller] = std::move(Callees);
- }
-
- // Return by value to enable NRVO (Named Return Value Optimization)
- return Result;
-}
-
-namespace {
-using FormatInfo = JSONFormat::FormatInfo;
-struct CallGraphFormatInfo : FormatInfo {
- CallGraphFormatInfo()
- : FormatInfo{
- SummaryName("CallGraph"),
- serializeCallGraph,
- deserializeCallGraph,
- } {}
-};
-} // namespace
-
-static llvm::Registry<JSONFormat::FormatInfo>::Add<CallGraphFormatInfo>
- RegisterCallGraph("CallGraphAnalysis",
- "Format info for CallGraph analysis data");
-
-//===----------------------------------------------------------------------===//
-// DefUseAnalysis - Tracks definition-use chains
-//===----------------------------------------------------------------------===//
-
-namespace {
-/// Example analysis that tracks definition-use chains for variables.
-///
-/// This analysis builds def-use chains showing how variable definitions flow
-/// to their use sites. The structure is a three-level nested map:
-///
-/// Level 1: Variable EntityId
-/// Level 2: Definition EntityId (where the variable is defined)
-/// Level 3: Set of Use EntityIds (where that definition is used)
-///
-/// Example structure:
-/// DefUseChains[varX][def1] = {use1, use2}
-/// DefUseChains[varX][def2] = {use3}
-/// DefUseChains[varY][def3] = {use4, use5, use6}
-///
-/// This indicates that:
-/// - Variable varX has two definitions (def1, def2)
-/// - def1 is used at use1 and use2
-/// - def2 is used at use3
-/// - Variable varY has one definition (def3) used at use4, use5, use6
-struct DefUseAnalysis : EntitySummary {
- DefUseAnalysis() : EntitySummary(SummaryName("DefUse")) {}
-
- /// Maps variables to their definition-use chains.
- /// Key: Variable EntityId
- /// Value: Map from definition EntityId to set of use EntityIds
- std::map<EntityId, std::map<EntityId, std::set<EntityId>>> DefUseChains;
-};
-} // namespace
-
-static llvm::json::Object
-serializeDefUse(const EntitySummary &Data,
- const JSONFormat::EntityIdConverter &Converter) {
- const auto &DU = static_cast<const DefUseAnalysis &>(Data);
- llvm::json::Object Result;
-
- // Serialize def-use chains as an array of objects
- llvm::json::Array ChainsArray;
- for (const auto &[Variable, DefUseMap] : DU.DefUseChains) {
- llvm::json::Object VarEntry;
- VarEntry["variable"] = Converter.toJSON(Variable);
-
- llvm::json::Array DefsArray;
- for (const auto &[Definition, Uses] : DefUseMap) {
- llvm::json::Object DefEntry;
- DefEntry["definition"] = Converter.toJSON(Definition);
-
- llvm::json::Array UsesArray;
- for (const auto &Use : Uses) {
- UsesArray.push_back(Converter.toJSON(Use));
- }
- DefEntry["uses"] = std::move(UsesArray);
-
- DefsArray.push_back(std::move(DefEntry));
- }
- VarEntry["definitions"] = std::move(DefsArray);
-
- ChainsArray.push_back(std::move(VarEntry));
- }
-
- Result["def_use_chains"] = std::move(ChainsArray);
- return Result;
-}
-
-static llvm::Expected<std::unique_ptr<EntitySummary>>
-deserializeDefUse(const llvm::json::Object &JSONObj, EntityIdTable &Table,
- const JSONFormat::EntityIdConverter &Converter) {
- auto Result = std::make_unique<DefUseAnalysis>();
-
- const llvm::json::Array *ChainsArray = JSONObj.getArray("def_use_chains");
- if (!ChainsArray) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "DefUse: missing or invalid 'def_use_chains' field");
- }
-
- for (size_t VarIndex = 0; VarIndex < ChainsArray->size(); ++VarIndex) {
- const llvm::json::Object *VarObj = (*ChainsArray)[VarIndex].getAsObject();
- if (!VarObj) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "DefUse: def_use_chains entry at index %zu is not an object",
- VarIndex);
- }
-
- // Parse variable
- const llvm::json::Value *VarValue = VarObj->get("variable");
- if (!VarValue) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "DefUse: entry at index %zu missing 'variable' field", VarIndex);
- }
- auto VarInt = VarValue->getAsUINT64();
- if (!VarInt) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "DefUse: 'variable' at index %zu is not a valid uint64", VarIndex);
- }
- EntityId Variable = Converter.fromJSON(*VarInt);
-
- // Parse definitions array
- const llvm::json::Array *DefsArray = VarObj->getArray("definitions");
- if (!DefsArray) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "DefUse: entry at index %zu missing or invalid 'definitions' field",
- VarIndex);
- }
-
- std::map<EntityId, std::set<EntityId>> DefUseMap;
- for (size_t DefIndex = 0; DefIndex < DefsArray->size(); ++DefIndex) {
- const llvm::json::Object *DefObj = (*DefsArray)[DefIndex].getAsObject();
- if (!DefObj) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "DefUse: definition at index %zu,%zu is not an object", VarIndex,
- DefIndex);
- }
-
- // Parse definition
- const llvm::json::Value *DefValue = DefObj->get("definition");
- if (!DefValue) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "DefUse: definition at index %zu,%zu missing 'definition' field",
- VarIndex, DefIndex);
- }
- auto DefInt = DefValue->getAsUINT64();
- if (!DefInt) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "DefUse: 'definition' at index %zu,%zu is not a valid uint64",
- VarIndex, DefIndex);
- }
- EntityId Definition = Converter.fromJSON(*DefInt);
-
- // Parse uses array
- const llvm::json::Array *UsesArray = DefObj->getArray("uses");
- if (!UsesArray) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "DefUse: definition at index %zu,%zu missing or invalid 'uses' "
- "field",
- VarIndex, DefIndex);
- }
-
- std::set<EntityId> Uses;
- for (size_t UseIndex = 0; UseIndex < UsesArray->size(); ++UseIndex) {
- auto UseInt = (*UsesArray)[UseIndex].getAsUINT64();
- if (!UseInt) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "DefUse: use at index %zu,%zu,%zu is not a valid uint64",
- VarIndex, DefIndex, UseIndex);
- }
- Uses.insert(Converter.fromJSON(*UseInt));
- }
-
- DefUseMap[Definition] = std::move(Uses);
- }
-
- Result->DefUseChains[Variable] = std::move(DefUseMap);
- }
-
- return Result;
-}
-
-namespace {
-struct DefUseFormatInfo : FormatInfo {
- DefUseFormatInfo()
- : FormatInfo{
- SummaryName("DefUse"),
- serializeDefUse,
- deserializeDefUse,
- } {}
-};
-} // namespace
-
-static llvm::Registry<JSONFormat::FormatInfo>::Add<DefUseFormatInfo>
- RegisterDefUse("DefUseAnalysis", "Format info for DefUse analysis data");
diff --git a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp
deleted file mode 100644
index 2153b1dc4e52e..0000000000000
--- a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-//===- SSAFSerializationFormatTest.cpp - Test SSAF JSON serialization ---===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This tool reads SSAF TUSummary JSON files and can:
-// - Validate the JSON format
-// - Print summary information
-// - Write back to JSON (for round-trip testing)
-//
-//===----------------------------------------------------------------------===//
-
-#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
-#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
-#include "llvm/Support/CommandLine.h"
-#include "llvm/Support/Error.h"
-#include "llvm/Support/InitLLVM.h"
-#include "llvm/Support/VirtualFileSystem.h"
-#include "llvm/Support/WithColor.h"
-
-using namespace clang::ssaf;
-using namespace llvm;
-
-static cl::opt<std::string>
- InputFilename(cl::Positional, cl::desc("<input file>"), cl::Required);
-
-static cl::opt<std::string> OutputFilename("o", cl::desc("Output filename"),
- cl::value_desc("filename"),
- cl::init("-"));
-
-static cl::opt<bool> PrintSummary("print-summary",
- cl::desc("Print summary information"),
- cl::init(false));
-
-static cl::opt<bool> Quiet("q", cl::desc("Suppress diagnostic messages"),
- cl::init(false));
-
-static void reportError(StringRef Prefix, Error E) {
- if (Quiet)
- return;
- WithColor::error(errs(), "ssaf-serialization-format-test")
- << Prefix << ": " << toString(std::move(E)) << "\n";
-}
-
-int main(int argc, char **argv) {
- InitLLVM X(argc, argv);
- cl::ParseCommandLineOptions(argc, argv,
- "SSAF JSON format validator and dumper\n");
-
- // Create JSONFormat instance
- auto FS = vfs::getRealFileSystem();
- JSONFormat Format(FS);
-
- // Read the input file
- auto SummaryOrErr = Format.readTUSummary(InputFilename);
- if (!SummaryOrErr) {
- reportError("failed to read TUSummary", SummaryOrErr.takeError());
- return 1;
- }
-
- const TUSummary &Summary = *SummaryOrErr;
-
- // Print summary information if requested
- if (PrintSummary) {
- outs() << "TUSummary successfully read from: " << InputFilename << "\n";
- // Could add more detailed summary info here
- }
-
- // Write output if specified
- if (OutputFilename != "-") {
- if (auto Err = Format.writeTUSummary(Summary, OutputFilename)) {
- reportError("failed to write TUSummary", std::move(Err));
- return 1;
- }
- if (!Quiet) {
- outs() << "TUSummary written to: " << OutputFilename << "\n";
- }
- }
-
- return 0;
-}
>From cd78788efbfb7491991b2e5eb0058ab9ee8ed545 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Sun, 8 Feb 2026 16:19:11 -0800
Subject: [PATCH 17/19] Fix tests
---
.../Scalable/Serialization/JSONFormat.cpp | 4 +
.../Scalable/Serialization/JSONFormatTest.cpp | 774 ++++++++++--------
2 files changed, 449 insertions(+), 329 deletions(-)
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index 022f787f09182..889783a8eb6f5 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -217,6 +217,8 @@ constexpr const char *WritingTUSummaryTo = "writing TUSummary to '{0}'";
// JSON Reader and Writer
//----------------------------------------------------------------------------
+namespace {
+
llvm::Error isJSONFile(llvm::StringRef Path) {
if (!llvm::sys::fs::exists(Path))
return ErrorBuilder(std::errc::no_such_file_or_directory)
@@ -291,6 +293,8 @@ llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
return llvm::Error::success();
}
+} // namespace
+
//----------------------------------------------------------------------------
// JSONFormat Constructor
//----------------------------------------------------------------------------
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
index 3aec2483766cd..70fb0701b3065 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
@@ -21,6 +21,7 @@
#include "llvm/Support/Registry.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/Support/raw_ostream.h"
+#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <memory>
#include <string>
@@ -28,9 +29,73 @@
using namespace clang::ssaf;
using namespace llvm;
+using ::testing::AllOf;
+using ::testing::HasSubstr;
namespace {
+// ============================================================================
+// Custom Matchers
+// ============================================================================
+
+// Helper to check if an Error or Expected succeeded
+template <typename T> struct SuccessChecker {
+ static bool isSuccess(T &val) {
+ // For Expected<U>
+ return static_cast<bool>(val);
+ }
+ static std::string getError(T &val) { return toString(val.takeError()); }
+};
+
+// Specialization for Error type
+template <> struct SuccessChecker<Error> {
+ static bool isSuccess(Error &val) {
+ // For Error, success means no error (false/empty)
+ return !static_cast<bool>(val);
+ }
+ static std::string getError(Error &val) { return toString(std::move(val)); }
+};
+
+// Matcher for Expected<T> or Error success
+MATCHER(Succeeded, "") {
+ // Cast away constness to get mutable access
+ auto &mutable_arg =
+ const_cast<std::remove_const_t<std::remove_reference_t<decltype(arg)>> &>(
+ arg);
+
+ using ArgType = std::remove_const_t<std::remove_reference_t<decltype(arg)>>;
+
+ if (!SuccessChecker<ArgType>::isSuccess(mutable_arg)) {
+ *result_listener << "Operation failed with error: "
+ << SuccessChecker<ArgType>::getError(mutable_arg);
+ return false;
+ }
+ return true;
+}
+
+// Matcher for Expected<T> or Error failure with specific error message
+MATCHER_P(FailedWith, SubstrMatcher, "") {
+ // Cast away constness to get mutable access
+ auto &mutable_arg =
+ const_cast<std::remove_const_t<std::remove_reference_t<decltype(arg)>> &>(
+ arg);
+
+ using ArgType = std::remove_const_t<std::remove_reference_t<decltype(arg)>>;
+
+ if (SuccessChecker<ArgType>::isSuccess(mutable_arg)) {
+ *result_listener << "Expected operation to fail, but it succeeded";
+ return false;
+ }
+
+ std::string ErrorMsg = SuccessChecker<ArgType>::getError(mutable_arg);
+
+ if (!::testing::Matches(SubstrMatcher)(ErrorMsg)) {
+ *result_listener << "Error message was: " << ErrorMsg;
+ return false;
+ }
+ return true;
+}
+
// ============================================================================
// Test Analysis - Simple analysis for testing JSON serialization
// ============================================================================
@@ -95,10 +160,10 @@ static llvm::Registry<JSONFormat::FormatInfo>::Add<TestAnalysisFormatInfo>
RegisterTestAnalysis("TestAnalysis", "Format info for test analysis data");
// ============================================================================
-// Base Fixture with Common Utilities
+// Test Fixture
// ============================================================================
-class JSONFormatTestBase : public ::testing::Test {
+class JSONFormatTest : public ::testing::Test {
protected:
SmallString<128> TestDir;
@@ -110,9 +175,7 @@ class JSONFormatTestBase : public ::testing::Test {
void TearDown() override { sys::fs::remove_directories(TestDir); }
- // Helper to create a temporary JSON file and read it using JSONFormat
- std::pair<JSONFormat, SmallString<128>>
- readJSON(StringRef JSON, StringRef Filename = "test.json") {
+ auto readJSON(StringRef JSON, StringRef Filename = "test.json") {
SmallString<128> FilePath = TestDir;
sys::path::append(FilePath, Filename);
@@ -122,103 +185,22 @@ class JSONFormatTestBase : public ::testing::Test {
OS << JSON;
OS.close();
- return {JSONFormat(vfs::getRealFileSystem()), FilePath};
- }
-
- // Helper to check if error message contains expected substrings
- bool errorContains(Error &Err, ArrayRef<StringRef> Parts) {
- std::string ErrorMsg = toString(std::move(Err));
- for (StringRef Part : Parts) {
- if (ErrorMsg.find(Part.str()) == std::string::npos)
- return false;
- }
- return true;
- }
-};
-
-// ============================================================================
-// Fixture for Error Tests
-// ============================================================================
-
-class JSONFormatErrorTest : public JSONFormatTestBase {
-protected:
- void expectError(StringRef JSON, ArrayRef<StringRef> ErrorParts,
- StringRef Filename = "test.json") {
- auto [Format, FilePath] = createFormat(JSON, Filename);
- auto Result = Format.readTUSummary(FilePath);
- ASSERT_FALSE(Result) << "Expected error but read succeeded";
-
- Error Err = Result.takeError();
- std::string ErrorMsg = toString(std::move(Err));
-
- bool allFound = true;
- for (StringRef Part : ErrorParts) {
- if (ErrorMsg.find(Part.str()) == std::string::npos) {
- allFound = false;
- break;
- }
- }
-
- EXPECT_TRUE(allFound) << "Error message didn't contain expected parts.\n"
- << "Actual error: " << ErrorMsg << "\n"
- << "Expected parts: [" <<
- [&]() {
- std::string result;
- for (size_t i = 0; i < ErrorParts.size(); ++i) {
- if (i > 0)
- result += ", ";
- result += "\"" + ErrorParts[i].str() + "\"";
- }
- return result;
- }() << "]";
+ return JSONFormat(vfs::getRealFileSystem()).readTUSummary(FilePath);
}
-};
-
-// ============================================================================
-// Fixture for Valid Configuration Tests
-// ============================================================================
-
-class JSONFormatValidTest : public JSONFormatTestBase {
-protected:
- void expectSuccess(StringRef JSON, StringRef Filename = "test.json") {
- auto [Format, FilePath] = createFormat(JSON, Filename);
- auto Result = Format.readTUSummary(FilePath);
- if (!Result) {
- FAIL() << "Read failed: " << toString(Result.takeError());
- }
- }
-};
-
-// ============================================================================
-// Fixture for Round-Trip Tests
-// ============================================================================
-class JSONFormatRoundTripTest : public JSONFormatTestBase {
-protected:
void testRoundTrip(StringRef InputJSON) {
- // Read the input
- auto [InputFormat, InputPath] = createFormat(InputJSON, "input.json");
- auto Summary = InputFormat.readTUSummary(InputPath);
- if (!Summary) {
- FAIL() << "Failed to read input: " << toString(Summary.takeError());
- }
-
- // Write to output file
+ auto Summary = readJSON(InputJSON, "input.json");
+ ASSERT_THAT(Summary, Succeeded());
+
SmallString<128> OutputPath = TestDir;
sys::path::append(OutputPath, "output.json");
JSONFormat OutputFormat(vfs::getRealFileSystem());
auto WriteErr = OutputFormat.writeTUSummary(*Summary, OutputPath);
- if (WriteErr) {
- FAIL() << "Failed to write output: " << toString(std::move(WriteErr));
- }
+ ASSERT_THAT(WriteErr, Succeeded());
- // Read back the written file
auto RoundTrip = OutputFormat.readTUSummary(OutputPath);
- if (!RoundTrip) {
- FAIL() << "Failed to read round-trip output: "
- << toString(RoundTrip.takeError());
- }
+ ASSERT_THAT(RoundTrip, Succeeded());
}
};
@@ -226,144 +208,192 @@ class JSONFormatRoundTripTest : public JSONFormatTestBase {
// File Access Error Tests
// ============================================================================
-TEST_F(JSONFormatErrorTest, NonexistentFile) {
+TEST_F(JSONFormatTest, NonexistentFile) {
SmallString<128> NonexistentPath = TestDir;
sys::path::append(NonexistentPath, "nonexistent.json");
JSONFormat Format(vfs::getRealFileSystem());
auto Result = Format.readTUSummary(NonexistentPath);
- ASSERT_FALSE(Result);
- Error Err = Result.takeError();
- EXPECT_TRUE(
- errorContains(Err, {"reading TUSummary from", "file does not exist"}));
+ EXPECT_THAT(Result, FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("file does not exist"))));
+}
+
+TEST_F(JSONFormatTest, PathIsDirectory) {
+ SmallString<128> DirPath = TestDir;
+ sys::path::append(DirPath, "test_directory.json");
+
+ std::error_code EC = sys::fs::create_directory(DirPath);
+ ASSERT_FALSE(EC) << "Failed to create directory: " << EC.message();
+
+ JSONFormat Format(vfs::getRealFileSystem());
+ auto Result = Format.readTUSummary(DirPath);
+
+ EXPECT_THAT(Result,
+ FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("path is a directory, not a file"))));
}
-TEST_F(JSONFormatErrorTest, NotJsonExtension) {
- expectError("{}", {"reading TUSummary from", "not a JSON file"}, "test.txt");
+TEST_F(JSONFormatTest, NotJsonExtension) {
+ auto Result = readJSON("{}", "test.txt");
+
+ EXPECT_THAT(Result, FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("not a JSON file"))));
}
// ============================================================================
// JSON Syntax Error Tests
// ============================================================================
-TEST_F(JSONFormatErrorTest, InvalidSyntax) {
- expectError("{ invalid json }", {"reading TUSummary from",
- "failed to read JSON object from file"});
+TEST_F(JSONFormatTest, InvalidSyntax) {
+ auto Result = readJSON("{ invalid json }");
+
+ EXPECT_THAT(Result, FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read JSON object from file"))));
}
-TEST_F(JSONFormatErrorTest, NotObject) {
- expectError(
- "[]", {"reading TUSummary from", "failed to read JSON object from file"});
+TEST_F(JSONFormatTest, NotObject) {
+ auto Result = readJSON("[]");
+
+ EXPECT_THAT(Result, FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read JSON object from file"))));
}
// ============================================================================
// Root Structure Error Tests
// ============================================================================
-TEST_F(JSONFormatErrorTest, MissingTUNamespace) {
- expectError(
- R"({
+TEST_F(JSONFormatTest, MissingTUNamespace) {
+ auto Result = readJSON(R"({
"id_table": [],
"data": []
- })",
- {"reading TUSummary from", "missing or invalid field 'tu_namespace'"});
+ })");
+
+ EXPECT_THAT(
+ Result,
+ FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("missing or invalid field 'tu_namespace'"))));
}
-TEST_F(JSONFormatErrorTest, MissingKind) {
- expectError(R"({
+TEST_F(JSONFormatTest, MissingKind) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"name": "test.cpp"
},
"id_table": [],
"data": []
- })",
- {"reading TUSummary from", "failed to deserialize BuildNamespace",
- "missing required field 'kind' (expected BuildNamespaceKind)"});
+ })");
+
+ EXPECT_THAT(Result, FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize BuildNamespace"),
+ HasSubstr("missing required field 'kind' "
+ "(expected BuildNamespaceKind)"))));
}
-TEST_F(JSONFormatErrorTest, MissingName) {
- expectError(R"({
+TEST_F(JSONFormatTest, MissingName) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit"
},
"id_table": [],
"data": []
- })",
- {"reading TUSummary from", "failed to deserialize BuildNamespace",
- "missing required field 'name'"});
+ })");
+
+ EXPECT_THAT(Result, FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize BuildNamespace"),
+ HasSubstr("missing required field 'name'"))));
}
-TEST_F(JSONFormatErrorTest, InvalidKind) {
- expectError(R"({
+TEST_F(JSONFormatTest, InvalidKind) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "invalid_kind",
"name": "test.cpp"
},
"id_table": [],
"data": []
- })",
- {"reading TUSummary from", "failed to deserialize BuildNamespace",
- "invalid 'kind' BuildNamespaceKind value"});
+ })");
+
+ EXPECT_THAT(Result, FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize BuildNamespace"),
+ HasSubstr("invalid 'kind' BuildNamespaceKind "
+ "value"))));
}
-TEST_F(JSONFormatErrorTest, MissingIDTable) {
- expectError(
- R"({
+TEST_F(JSONFormatTest, MissingIDTable) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
"data": []
- })",
- {"reading TUSummary from", "missing or invalid field 'id_table'"});
+ })");
+
+ EXPECT_THAT(Result, FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("missing or invalid field 'id_table'"))));
}
-TEST_F(JSONFormatErrorTest, MissingData) {
- expectError(R"({
+TEST_F(JSONFormatTest, MissingData) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
"id_table": []
- })",
- {"reading TUSummary from", "missing or invalid field 'data'"});
+ })");
+
+ EXPECT_THAT(Result,
+ FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("missing or invalid field 'data'"))));
}
// ============================================================================
// ID Table Error Tests
// ============================================================================
-TEST_F(JSONFormatErrorTest, IDTableNotArray) {
- expectError(
- R"({
+TEST_F(JSONFormatTest, IDTableNotArray) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
"id_table": {},
"data": []
- })",
- {"reading TUSummary from", "missing or invalid field 'id_table'"});
+ })");
+
+ EXPECT_THAT(Result, FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("missing or invalid field 'id_table'"))));
}
-TEST_F(JSONFormatErrorTest, IDTableElementNotObject) {
- expectError(R"({
+TEST_F(JSONFormatTest, IDTableElementNotObject) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
"id_table": [123],
"data": []
- })",
- {"reading TUSummary from", "failed to deserialize EntityIdTable",
- "element at index 0 is not a JSON object",
- "(expected EntityIdTable entry with 'id' and 'name' fields)"});
+ })");
+
+ EXPECT_THAT(
+ Result,
+ FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize EntityIdTable"),
+ HasSubstr("element at index 0 is not a JSON object"),
+ HasSubstr(
+ "(expected EntityIdTable entry with 'id' and 'name' fields)"))));
}
-TEST_F(JSONFormatErrorTest, IDTableEntryMissingID) {
- expectError(
- R"({
+TEST_F(JSONFormatTest, IDTableEntryMissingID) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -378,16 +408,19 @@ TEST_F(JSONFormatErrorTest, IDTableEntryMissingID) {
}
],
"data": []
- })",
- {"reading TUSummary from",
- "failed to deserialize EntityIdTable at index 0",
- "failed to deserialize EntityIdTable entry",
- "missing required field 'id' (expected unsigned integer EntityId)"});
+ })");
+
+ EXPECT_THAT(Result,
+ FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize EntityIdTable at index 0"),
+ HasSubstr("failed to deserialize EntityIdTable entry"),
+ HasSubstr("missing required field 'id' (expected unsigned "
+ "integer EntityId)"))));
}
-TEST_F(JSONFormatErrorTest, IDTableEntryMissingName) {
- expectError(
- R"({
+TEST_F(JSONFormatTest, IDTableEntryMissingName) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -398,15 +431,20 @@ TEST_F(JSONFormatErrorTest, IDTableEntryMissingName) {
}
],
"data": []
- })",
- {"reading TUSummary from",
- "failed to deserialize EntityIdTable at index 0",
- "failed to deserialize EntityIdTable entry",
- "missing or invalid field 'name' (expected EntityName JSON object)"});
+ })");
+
+ EXPECT_THAT(
+ Result,
+ FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize EntityIdTable at index 0"),
+ HasSubstr("failed to deserialize EntityIdTable entry"),
+ HasSubstr("missing or invalid field 'name' (expected EntityName JSON "
+ "object)"))));
}
-TEST_F(JSONFormatErrorTest, IDTableEntryIDNotUInt64) {
- expectError(R"({
+TEST_F(JSONFormatTest, IDTableEntryIDNotUInt64) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -422,16 +460,20 @@ TEST_F(JSONFormatErrorTest, IDTableEntryIDNotUInt64) {
}
],
"data": []
- })",
- {"reading TUSummary from",
- "failed to deserialize EntityIdTable at index 0",
- "failed to deserialize EntityIdTable entry",
- "field 'id' is not a valid unsigned 64-bit integer",
- "(expected non-negative EntityId value)"});
+ })");
+
+ EXPECT_THAT(
+ Result,
+ FailedWith(
+ AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize EntityIdTable at index 0"),
+ HasSubstr("failed to deserialize EntityIdTable entry"),
+ HasSubstr("field 'id' is not a valid unsigned 64-bit integer"),
+ HasSubstr("(expected non-negative EntityId value)"))));
}
-TEST_F(JSONFormatErrorTest, DuplicateEntity) {
- expectError(R"({
+TEST_F(JSONFormatTest, DuplicateEntity) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -465,19 +507,21 @@ TEST_F(JSONFormatErrorTest, DuplicateEntity) {
}
],
"data": []
- })",
- {"reading TUSummary from", "failed to deserialize EntityIdTable",
- "duplicate EntityName found at index",
- "(EntityId=0 already exists in table)"});
+ })");
+
+ EXPECT_THAT(Result, FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize EntityIdTable"),
+ HasSubstr("duplicate EntityName found at index"),
+ HasSubstr("(EntityId=0 already exists in table)"))));
}
// ============================================================================
// Entity Name Error Tests
// ============================================================================
-TEST_F(JSONFormatErrorTest, EntityNameMissingUSR) {
- expectError(
- R"({
+TEST_F(JSONFormatTest, EntityNameMissingUSR) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -492,16 +536,20 @@ TEST_F(JSONFormatErrorTest, EntityNameMissingUSR) {
}
],
"data": []
- })",
- {"reading TUSummary from",
- "failed to deserialize EntityIdTable at index 0",
- "failed to deserialize EntityIdTable entry",
- "failed to deserialize EntityName",
- "missing required field 'usr' (Unified Symbol Resolution string)"});
+ })");
+
+ EXPECT_THAT(Result,
+ FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize EntityIdTable at index 0"),
+ HasSubstr("failed to deserialize EntityIdTable entry"),
+ HasSubstr("failed to deserialize EntityName"),
+ HasSubstr("missing required field 'usr' (Unified Symbol "
+ "Resolution string)"))));
}
-TEST_F(JSONFormatErrorTest, EntityNameMissingSuffix) {
- expectError(R"({
+TEST_F(JSONFormatTest, EntityNameMissingSuffix) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -516,16 +564,20 @@ TEST_F(JSONFormatErrorTest, EntityNameMissingSuffix) {
}
],
"data": []
- })",
- {"reading TUSummary from",
- "failed to deserialize EntityIdTable at index 0",
- "failed to deserialize EntityIdTable entry",
- "failed to deserialize EntityName",
- "missing required field 'suffix'"});
+ })");
+
+ EXPECT_THAT(
+ Result,
+ FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize EntityIdTable at "
+ "index 0"),
+ HasSubstr("failed to deserialize EntityIdTable entry"),
+ HasSubstr("failed to deserialize EntityName"),
+ HasSubstr("missing required field 'suffix'"))));
}
-TEST_F(JSONFormatErrorTest, EntityNameMissingNamespace) {
- expectError(R"({
+TEST_F(JSONFormatTest, EntityNameMissingNamespace) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -540,17 +592,21 @@ TEST_F(JSONFormatErrorTest, EntityNameMissingNamespace) {
}
],
"data": []
- })",
- {"reading TUSummary from",
- "failed to deserialize EntityIdTable at index 0",
- "failed to deserialize EntityIdTable entry",
- "failed to deserialize EntityName",
- "missing or invalid field 'namespace'",
- "(expected JSON array of BuildNamespace objects)"});
+ })");
+
+ EXPECT_THAT(
+ Result,
+ FailedWith(
+ AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize EntityIdTable at index 0"),
+ HasSubstr("failed to deserialize EntityIdTable entry"),
+ HasSubstr("failed to deserialize EntityName"),
+ HasSubstr("missing or invalid field 'namespace'"),
+ HasSubstr("(expected JSON array of BuildNamespace objects)"))));
}
-TEST_F(JSONFormatErrorTest, NamespaceElementNotObject) {
- expectError(R"({
+TEST_F(JSONFormatTest, NamespaceElementNotObject) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -566,49 +622,58 @@ TEST_F(JSONFormatErrorTest, NamespaceElementNotObject) {
}
],
"data": []
- })",
- {"reading TUSummary from",
- "failed to deserialize EntityIdTable at index 0",
- "failed to deserialize EntityIdTable entry",
- "failed to deserialize EntityName",
- "failed to deserialize NestedBuildNamespace",
- "element at index 0 is not a JSON object"});
+ })");
+
+ EXPECT_THAT(Result,
+ FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize EntityIdTable at index 0"),
+ HasSubstr("failed to deserialize EntityIdTable entry"),
+ HasSubstr("failed to deserialize EntityName"),
+ HasSubstr("failed to deserialize NestedBuildNamespace"),
+ HasSubstr("element at index 0 is not a JSON object"))));
}
// ============================================================================
// Data Array Error Tests
// ============================================================================
-TEST_F(JSONFormatErrorTest, DataNotArray) {
- expectError(R"({
+TEST_F(JSONFormatTest, DataNotArray) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
"id_table": [],
"data": {}
- })",
- {"reading TUSummary from", "missing or invalid field 'data'"});
+ })");
+
+ EXPECT_THAT(Result,
+ FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("missing or invalid field 'data'"))));
}
-TEST_F(JSONFormatErrorTest, DataElementNotObject) {
- expectError(
- R"({
+TEST_F(JSONFormatTest, DataElementNotObject) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
"id_table": [],
"data": ["invalid"]
- })",
- {"reading TUSummary from", "failed to deserialize SummaryDataMap",
- "element at index 0 is not a JSON object",
- "(expected SummaryDataMap entry with 'summary_name' and 'summary_data'",
- "fields)"});
+ })");
+
+ EXPECT_THAT(Result, FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize SummaryDataMap"),
+ HasSubstr("element at index 0 is not a JSON object"),
+ HasSubstr("(expected SummaryDataMap entry with "
+ "'summary_name' and 'summary_data'"),
+ HasSubstr("fields)"))));
}
-TEST_F(JSONFormatErrorTest, DataEntryMissingSummaryName) {
- expectError(R"({
+TEST_F(JSONFormatTest, DataEntryMissingSummaryName) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -619,15 +684,19 @@ TEST_F(JSONFormatErrorTest, DataEntryMissingSummaryName) {
"summary_data": []
}
]
- })",
- {"reading TUSummary from",
- "failed to deserialize SummaryDataMap at index 0",
- "failed to deserialize SummaryDataMap entry",
- "missing required field 'summary_name'"});
+ })");
+
+ EXPECT_THAT(
+ Result,
+ FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize SummaryDataMap at "
+ "index 0"),
+ HasSubstr("failed to deserialize SummaryDataMap entry"),
+ HasSubstr("missing required field 'summary_name'"))));
}
-TEST_F(JSONFormatErrorTest, DataEntryMissingData) {
- expectError(R"({
+TEST_F(JSONFormatTest, DataEntryMissingData) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -638,15 +707,18 @@ TEST_F(JSONFormatErrorTest, DataEntryMissingData) {
"summary_name": "test_summary"
}
]
- })",
- {"reading TUSummary from",
- "failed to deserialize SummaryDataMap at index 0",
- "failed to deserialize SummaryDataMap entry",
- "missing or invalid field 'summary_data'"});
+ })");
+
+ EXPECT_THAT(Result,
+ FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize SummaryDataMap at index 0"),
+ HasSubstr("failed to deserialize SummaryDataMap entry"),
+ HasSubstr("missing or invalid field 'summary_data'"))));
}
-TEST_F(JSONFormatErrorTest, DuplicateSummaryName) {
- expectError(R"({
+TEST_F(JSONFormatTest, DuplicateSummaryName) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -662,18 +734,21 @@ TEST_F(JSONFormatErrorTest, DuplicateSummaryName) {
"summary_data": []
}
]
- })",
- {"reading TUSummary from", "failed to deserialize SummaryDataMap",
- "duplicate SummaryName 'test_summary' found at index"});
+ })");
+
+ EXPECT_THAT(Result, FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize SummaryDataMap"),
+ HasSubstr("duplicate SummaryName 'test_summary' "
+ "found at index"))));
}
// ============================================================================
// Entity Data Error Tests
// ============================================================================
-TEST_F(JSONFormatErrorTest, EntityDataElementNotObject) {
- expectError(
- R"({
+TEST_F(JSONFormatTest, EntityDataElementNotObject) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -685,19 +760,24 @@ TEST_F(JSONFormatErrorTest, EntityDataElementNotObject) {
"summary_data": ["invalid"]
}
]
- })",
- {"reading TUSummary from",
- "failed to deserialize SummaryDataMap at index 0",
- "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
- "failed to deserialize EntityDataMap",
- "element at index 0 is not a JSON object",
- "(expected EntityDataMap entry with 'entity_id' and 'entity_summary'",
- "fields)"});
+ })");
+
+ EXPECT_THAT(
+ Result,
+ FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize SummaryDataMap at index 0"),
+ HasSubstr("failed to deserialize SummaryDataMap entry for summary "
+ "'test_summary'"),
+ HasSubstr("failed to deserialize EntityDataMap"),
+ HasSubstr("element at index 0 is not a JSON object"),
+ HasSubstr("(expected EntityDataMap entry with 'entity_id' and "
+ "'entity_summary'"),
+ HasSubstr("fields)"))));
}
-TEST_F(JSONFormatErrorTest, EntityDataMissingEntityID) {
- expectError(
- R"({
+TEST_F(JSONFormatTest, EntityDataMissingEntityID) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -713,19 +793,23 @@ TEST_F(JSONFormatErrorTest, EntityDataMissingEntityID) {
]
}
]
- })",
- {"reading TUSummary from",
- "failed to deserialize SummaryDataMap at index 0",
- "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
- "failed to deserialize EntityDataMap at index 0",
- "failed to deserialize EntityDataMap entry",
- "missing required field 'entity_id' (expected unsigned integer "
- "EntityId)"});
+ })");
+
+ EXPECT_THAT(
+ Result,
+ FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize SummaryDataMap at index 0"),
+ HasSubstr("failed to deserialize SummaryDataMap entry for summary "
+ "'test_summary'"),
+ HasSubstr("failed to deserialize EntityDataMap at index 0"),
+ HasSubstr("failed to deserialize EntityDataMap entry"),
+ HasSubstr("missing required field 'entity_id' (expected unsigned "
+ "integer EntityId)"))));
}
-TEST_F(JSONFormatErrorTest, EntityDataMissingEntitySummary) {
- expectError(
- R"({
+TEST_F(JSONFormatTest, EntityDataMissingEntitySummary) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -741,19 +825,23 @@ TEST_F(JSONFormatErrorTest, EntityDataMissingEntitySummary) {
]
}
]
- })",
- {"reading TUSummary from",
- "failed to deserialize SummaryDataMap at index 0",
- "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
- "failed to deserialize EntityDataMap at index 0",
- "failed to deserialize EntityDataMap entry",
- "missing or invalid field 'entity_summary'",
- "(expected EntitySummary JSON object)"});
+ })");
+
+ EXPECT_THAT(
+ Result,
+ FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize SummaryDataMap at index 0"),
+ HasSubstr("failed to deserialize SummaryDataMap entry for summary "
+ "'test_summary'"),
+ HasSubstr("failed to deserialize EntityDataMap at index 0"),
+ HasSubstr("failed to deserialize EntityDataMap entry"),
+ HasSubstr("missing or invalid field 'entity_summary'"),
+ HasSubstr("(expected EntitySummary JSON object)"))));
}
-TEST_F(JSONFormatErrorTest, EntityIDNotUInt64) {
- expectError(
- R"({
+TEST_F(JSONFormatTest, EntityIDNotUInt64) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -770,19 +858,23 @@ TEST_F(JSONFormatErrorTest, EntityIDNotUInt64) {
]
}
]
- })",
- {"reading TUSummary from",
- "failed to deserialize SummaryDataMap at index 0",
- "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
- "failed to deserialize EntityDataMap at index 0",
- "failed to deserialize EntityDataMap entry",
- "field 'entity_id' is not a valid unsigned 64-bit integer",
- "(expected non-negative EntityId value)"});
+ })");
+
+ EXPECT_THAT(
+ Result,
+ FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize SummaryDataMap at index 0"),
+ HasSubstr("failed to deserialize SummaryDataMap entry for summary "
+ "'test_summary'"),
+ HasSubstr("failed to deserialize EntityDataMap at index 0"),
+ HasSubstr("failed to deserialize EntityDataMap entry"),
+ HasSubstr("field 'entity_id' is not a valid unsigned 64-bit integer"),
+ HasSubstr("(expected non-negative EntityId value)"))));
}
-TEST_F(JSONFormatErrorTest, EntitySummaryNoFormatInfo) {
- expectError(
- R"({
+TEST_F(JSONFormatTest, EntitySummaryNoFormatInfo) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -799,24 +891,28 @@ TEST_F(JSONFormatErrorTest, EntitySummaryNoFormatInfo) {
]
}
]
- })",
- {"reading TUSummary from",
- "failed to deserialize SummaryDataMap at index 0",
- "failed to deserialize SummaryDataMap entry for summary "
- "'unknown_summary_type'",
- "failed to deserialize EntityDataMap at index 0",
- "failed to deserialize EntityDataMap entry",
- "failed to deserialize EntitySummary",
- "no FormatInfo was registered for summary name: unknown_summary_type"});
+ })");
+
+ EXPECT_THAT(
+ Result,
+ FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize SummaryDataMap at index 0"),
+ HasSubstr("failed to deserialize SummaryDataMap entry for summary "
+ "'unknown_summary_type'"),
+ HasSubstr("failed to deserialize EntityDataMap at index 0"),
+ HasSubstr("failed to deserialize EntityDataMap entry"),
+ HasSubstr("failed to deserialize EntitySummary"),
+ HasSubstr("no FormatInfo was registered for summary name: "
+ "unknown_summary_type"))));
}
// ============================================================================
// Analysis-Specific Error Tests - TestAnalysis
// ============================================================================
-TEST_F(JSONFormatErrorTest, TestAnalysisMissingField) {
- expectError(
- R"({
+TEST_F(JSONFormatTest, TestAnalysisMissingField) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -833,18 +929,23 @@ TEST_F(JSONFormatErrorTest, TestAnalysisMissingField) {
]
}
]
- })",
- {"reading TUSummary from",
- "failed to deserialize SummaryDataMap at index 0",
- "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
- "failed to deserialize EntityDataMap at index 0",
- "failed to deserialize EntityDataMap entry",
- "missing required field 'pairs'"});
+ })");
+
+ EXPECT_THAT(
+ Result,
+ FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize SummaryDataMap at "
+ "index 0"),
+ HasSubstr("failed to deserialize SummaryDataMap entry "
+ "for summary 'test_summary'"),
+ HasSubstr("failed to deserialize EntityDataMap at "
+ "index 0"),
+ HasSubstr("failed to deserialize EntityDataMap entry"),
+ HasSubstr("missing required field 'pairs'"))));
}
-TEST_F(JSONFormatErrorTest, TestAnalysisInvalidPair) {
- expectError(
- R"({
+TEST_F(JSONFormatTest, TestAnalysisInvalidPair) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -868,21 +969,28 @@ TEST_F(JSONFormatErrorTest, TestAnalysisInvalidPair) {
]
}
]
- })",
- {"reading TUSummary from",
- "failed to deserialize SummaryDataMap at index 0",
- "failed to deserialize SummaryDataMap entry for summary 'test_summary'",
- "failed to deserialize EntityDataMap at index 0",
- "failed to deserialize EntityDataMap entry",
- "missing or invalid 'second' field at index 0"});
+ })");
+
+ EXPECT_THAT(
+ Result,
+ FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to deserialize SummaryDataMap at "
+ "index 0"),
+ HasSubstr("failed to deserialize SummaryDataMap entry "
+ "for summary 'test_summary'"),
+ HasSubstr("failed to deserialize EntityDataMap at "
+ "index 0"),
+ HasSubstr("failed to deserialize EntityDataMap entry"),
+ HasSubstr("missing or invalid 'second' field at "
+ "index 0"))));
}
// ============================================================================
// Valid Configuration Tests
// ============================================================================
-TEST_F(JSONFormatValidTest, Empty) {
- expectSuccess(R"({
+TEST_F(JSONFormatTest, Empty) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -890,10 +998,12 @@ TEST_F(JSONFormatValidTest, Empty) {
"id_table": [],
"data": []
})");
+
+ EXPECT_THAT(Result, Succeeded());
}
-TEST_F(JSONFormatValidTest, LinkUnit) {
- expectSuccess(R"({
+TEST_F(JSONFormatTest, LinkUnit) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "link_unit",
"name": "libtest.so"
@@ -901,10 +1011,12 @@ TEST_F(JSONFormatValidTest, LinkUnit) {
"id_table": [],
"data": []
})");
+
+ EXPECT_THAT(Result, Succeeded());
}
-TEST_F(JSONFormatValidTest, WithIDTable) {
- expectSuccess(R"({
+TEST_F(JSONFormatTest, WithIDTable) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -943,10 +1055,12 @@ TEST_F(JSONFormatValidTest, WithIDTable) {
],
"data": []
})");
+
+ EXPECT_THAT(Result, Succeeded());
}
-TEST_F(JSONFormatValidTest, WithEmptyDataEntry) {
- expectSuccess(R"({
+TEST_F(JSONFormatTest, WithEmptyDataEntry) {
+ auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -959,13 +1073,15 @@ TEST_F(JSONFormatValidTest, WithEmptyDataEntry) {
}
]
})");
+
+ EXPECT_THAT(Result, Succeeded());
}
// ============================================================================
// Round-Trip Tests
// ============================================================================
-TEST_F(JSONFormatRoundTripTest, Empty) {
+TEST_F(JSONFormatTest, RoundTripEmpty) {
testRoundTrip(R"({
"tu_namespace": {
"kind": "compilation_unit",
@@ -976,7 +1092,7 @@ TEST_F(JSONFormatRoundTripTest, Empty) {
})");
}
-TEST_F(JSONFormatRoundTripTest, WithIDTable) {
+TEST_F(JSONFormatTest, RoundTripWithIDTable) {
testRoundTrip(R"({
"tu_namespace": {
"kind": "compilation_unit",
@@ -1001,7 +1117,7 @@ TEST_F(JSONFormatRoundTripTest, WithIDTable) {
})");
}
-TEST_F(JSONFormatRoundTripTest, LinkUnit) {
+TEST_F(JSONFormatTest, RoundTripLinkUnit) {
testRoundTrip(R"({
"tu_namespace": {
"kind": "link_unit",
@@ -1016,7 +1132,7 @@ TEST_F(JSONFormatRoundTripTest, LinkUnit) {
// Analysis-Specific Round-Trip Tests
// ============================================================================
-TEST_F(JSONFormatRoundTripTest, TestAnalysis) {
+TEST_F(JSONFormatTest, RoundTripTestAnalysis) {
testRoundTrip(R"({
"tu_namespace": {
"kind": "compilation_unit",
>From 9d7712501c55d7764f29228aaff7ec990e7dc9a9 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Mon, 9 Feb 2026 10:21:03 -0800
Subject: [PATCH 18/19] Fix error message templates and add new tests
---
.../Scalable/Serialization/JSONFormat.cpp | 264 ++++++++----------
.../Scalable/Serialization/JSONFormatTest.cpp | 219 +++++++++++----
2 files changed, 279 insertions(+), 204 deletions(-)
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index 889783a8eb6f5..de4003498195a 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -81,133 +81,41 @@ constexpr const char *FailedToValidateJSONFile =
constexpr const char *FailedToReadFile = "failed to read file '{0}'";
constexpr const char *FailedToReadJSONObject =
"failed to read JSON object from file '{0}'";
+constexpr const char *FailedToReadJSONArray =
+ "failed to read JSON array from field '{0}'";
+constexpr const char *FailedToReadJSONObjectField =
+ "failed to read JSON object from field '{0}'";
constexpr const char *FailedToOpenFile = "failed to open '{0}'";
constexpr const char *WriteFailed = "write failed";
-// BuildNamespace errors
+// Generic deserialization error templates
+constexpr const char *FailedToDeserialize = "failed to deserialize {0}";
+constexpr const char *AtIndex = "at index {0}";
+constexpr const char *ForSummary = "for summary '{0}'";
+
+// Specific error details (to be stacked with FailedToDeserialize)
+constexpr const char *MissingOrInvalidField =
+ "missing or invalid field '{0}' (expected {1})";
+constexpr const char *ElementNotObject =
+ "element at index {0} is not a JSON object (expected {1})";
+constexpr const char *InvalidUInt64Field =
+ "field '{0}' is not a valid unsigned 64-bit integer (expected "
+ "non-negative EntityId value)";
+constexpr const char *DuplicateWithExistingId =
+ "duplicate {0} found at index {1} (EntityId={2} already exists in table)";
+constexpr const char *DuplicateEntityIdAtIndex =
+ "duplicate EntityId ({0}) found at index {1}";
+constexpr const char *DuplicateAtIndex =
+ "duplicate {0} '{1}' found at index {2}";
+
+// Special cases
constexpr const char *InvalidBuildNamespaceKind =
"invalid 'kind' BuildNamespaceKind value '{0}'";
-constexpr const char *MissingBuildNamespaceKind =
- "failed to deserialize BuildNamespace: "
- "missing required field 'kind' (expected BuildNamespaceKind)";
-constexpr const char *MissingBuildNamespaceName =
- "failed to deserialize BuildNamespace: "
- "missing required field 'name'";
-constexpr const char *FailedToDeserializeBuildNamespace =
- "failed to deserialize BuildNamespace";
-
-// NestedBuildNamespace errors
-constexpr const char *NestedBuildNamespaceElementNotObject =
- "failed to deserialize NestedBuildNamespace: "
- "element at index {0} is not a JSON object "
- "(expected BuildNamespace object)";
-constexpr const char *FailedToDeserializeNestedBuildNamespace =
- "failed to deserialize NestedBuildNamespace at index {0}";
-
-// EntityName errors
-constexpr const char *MissingEntityNameUSR =
- "failed to deserialize EntityName: "
- "missing required field 'usr' (Unified Symbol Resolution string)";
-constexpr const char *MissingEntityNameSuffix =
- "failed to deserialize EntityName: "
- "missing required field 'suffix'";
-constexpr const char *MissingEntityNameNamespace =
- "failed to deserialize EntityName: "
- "missing or invalid field 'namespace' "
- "(expected JSON array of BuildNamespace objects)";
-constexpr const char *FailedToDeserializeEntityName =
- "failed to deserialize EntityName";
-
-// EntityIdTable entry errors
-constexpr const char *MissingEntityIdTableEntryName =
- "failed to deserialize EntityIdTable entry: "
- "missing or invalid field 'name' (expected EntityName JSON object)";
-constexpr const char *MissingEntityIdTableEntryId =
- "failed to deserialize EntityIdTable entry: "
- "missing required field 'id' (expected unsigned integer EntityId)";
-constexpr const char *InvalidEntityIdTableEntryId =
- "failed to deserialize EntityIdTable entry: "
- "field 'id' is not a valid unsigned 64-bit integer "
- "(expected non-negative EntityId value)";
-constexpr const char *FailedToDeserializeEntityIdTableEntry =
- "failed to deserialize EntityIdTable entry";
-
-// EntityIdTable errors
-constexpr const char *EntityIdTableElementNotObject =
- "failed to deserialize EntityIdTable: "
- "element at index {0} is not a JSON object "
- "(expected EntityIdTable entry with 'id' and 'name' fields)";
-constexpr const char *FailedToDeserializeEntityIdTable =
- "failed to deserialize EntityIdTable at index {0}";
-constexpr const char *DuplicateEntityName =
- "failed to deserialize EntityIdTable: "
- "duplicate EntityName found at index {0} "
- "(EntityId={1} already exists in table)";
-
-// EntitySummary errors
-constexpr const char *NoFormatInfoForSummary =
- "failed to deserialize EntitySummary: "
+constexpr const char *NoFormatInfoForSummaryName =
"no FormatInfo was registered for summary name: {0}";
-// EntityDataMap entry errors
-constexpr const char *MissingEntityDataMapEntryEntityId =
- "failed to deserialize EntityDataMap entry: "
- "missing required field 'entity_id' (expected unsigned integer EntityId)";
-constexpr const char *InvalidEntityDataMapEntryEntityId =
- "failed to deserialize EntityDataMap entry: "
- "field 'entity_id' is not a valid unsigned 64-bit integer "
- "(expected non-negative EntityId value)";
-constexpr const char *MissingEntityDataMapEntryEntitySummary =
- "failed to deserialize EntityDataMap entry: "
- "missing or invalid field 'entity_summary' "
- "(expected EntitySummary JSON object)";
-constexpr const char *FailedToDeserializeEntityDataMapEntry =
- "failed to deserialize EntityDataMap entry";
-
-// EntityDataMap errors
-constexpr const char *EntityDataMapElementNotObject =
- "failed to deserialize EntityDataMap: "
- "element at index {0} is not a JSON object "
- "(expected EntityDataMap entry with 'entity_id' and 'entity_summary' "
- "fields)";
-constexpr const char *FailedToDeserializeEntityDataMap =
- "failed to deserialize EntityDataMap at index {0}";
-constexpr const char *DuplicateEntityId =
- "failed to deserialize EntityDataMap: "
- "duplicate EntityId ({0}) found at index {1}";
-
-// SummaryDataMap entry errors
-constexpr const char *MissingSummaryDataMapEntrySummaryName =
- "failed to deserialize SummaryDataMap entry: "
- "missing required field 'summary_name' "
- "(expected string identifier for the analysis summary)";
-constexpr const char *MissingSummaryDataMapEntrySummaryData =
- "failed to deserialize SummaryDataMap entry: "
- "missing or invalid field 'summary_data' "
- "(expected JSON array of entity data entries)";
-constexpr const char *FailedToDeserializeSummaryDataMapEntry =
- "failed to deserialize SummaryDataMap entry for summary '{0}'";
-
-// SummaryDataMap errors
-constexpr const char *SummaryDataMapElementNotObject =
- "failed to deserialize SummaryDataMap: "
- "element at index {0} is not a JSON object "
- "(expected SummaryDataMap entry with 'summary_name' and 'summary_data' "
- "fields)";
-constexpr const char *FailedToDeserializeSummaryDataMap =
- "failed to deserialize SummaryDataMap at index {0}";
-constexpr const char *DuplicateSummaryName =
- "failed to deserialize SummaryDataMap: "
- "duplicate SummaryName '{0}' found at index {1}";
-
-// TUSummary errors
+// Context messages
constexpr const char *ReadingTUSummaryFrom = "reading TUSummary from '{0}'";
-constexpr const char *MissingTUNamespace =
- "missing or invalid field 'tu_namespace' (expected JSON object)";
-constexpr const char *MissingIdTable =
- "missing or invalid field 'id_table' (expected JSON array)";
-constexpr const char *MissingData =
- "missing or invalid field 'data' (expected JSON array)";
constexpr const char *WritingTUSummaryTo = "writing TUSummary to '{0}'";
} // namespace ErrorMessages
@@ -372,21 +280,25 @@ llvm::Expected<BuildNamespace> JSONFormat::buildNamespaceFromJSON(
auto OptBuildNamespaceKindStr = BuildNamespaceObject.getString("kind");
if (!OptBuildNamespaceKindStr) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::MissingBuildNamespaceKind)
+ .context(ErrorMessages::FailedToDeserialize, "BuildNamespace")
+ .context(ErrorMessages::MissingOrInvalidField, "kind",
+ "BuildNamespaceKind")
.build();
}
auto ExpectedKind = buildNamespaceKindFromJSON(*OptBuildNamespaceKindStr);
if (!ExpectedKind)
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserializeBuildNamespace)
+ .context(ErrorMessages::FailedToDeserialize, "BuildNamespace")
+ .context("while parsing field 'kind'")
.cause(ExpectedKind.takeError())
.build();
auto OptNameStr = BuildNamespaceObject.getString("name");
if (!OptNameStr) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::MissingBuildNamespaceName)
+ .context(ErrorMessages::FailedToDeserialize, "BuildNamespace")
+ .context(ErrorMessages::MissingOrInvalidField, "name", "string")
.build();
}
@@ -419,15 +331,17 @@ llvm::Expected<NestedBuildNamespace> JSONFormat::nestedBuildNamespaceFromJSON(
BuildNamespaceValue.getAsObject();
if (!BuildNamespaceObject) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::NestedBuildNamespaceElementNotObject, Index)
+ .context(ErrorMessages::FailedToDeserialize, "NestedBuildNamespace")
+ .context(ErrorMessages::ElementNotObject, Index,
+ "BuildNamespace object")
.build();
}
auto ExpectedBuildNamespace = buildNamespaceFromJSON(*BuildNamespaceObject);
if (!ExpectedBuildNamespace)
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserializeNestedBuildNamespace,
- Index)
+ .context(ErrorMessages::FailedToDeserialize, "NestedBuildNamespace")
+ .context(ErrorMessages::AtIndex, Index)
.cause(ExpectedBuildNamespace.takeError())
.build();
@@ -459,14 +373,17 @@ llvm::Expected<EntityName> JSONFormat::entityNameFromJSON(
const auto OptUSR = EntityNameObject.getString("usr");
if (!OptUSR) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::MissingEntityNameUSR)
+ .context(ErrorMessages::FailedToDeserialize, "EntityName")
+ .context(ErrorMessages::MissingOrInvalidField, "usr",
+ "string (Unified Symbol Resolution)")
.build();
}
const auto OptSuffix = EntityNameObject.getString("suffix");
if (!OptSuffix) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::MissingEntityNameSuffix)
+ .context(ErrorMessages::FailedToDeserialize, "EntityName")
+ .context(ErrorMessages::MissingOrInvalidField, "suffix", "string")
.build();
}
@@ -474,14 +391,17 @@ llvm::Expected<EntityName> JSONFormat::entityNameFromJSON(
EntityNameObject.getArray("namespace");
if (!OptNamespaceArray) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::MissingEntityNameNamespace)
+ .context(ErrorMessages::FailedToDeserialize, "EntityName")
+ .context(ErrorMessages::MissingOrInvalidField, "namespace",
+ "JSON array of BuildNamespace objects")
.build();
}
auto ExpectedNamespace = nestedBuildNamespaceFromJSON(*OptNamespaceArray);
if (!ExpectedNamespace)
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserializeEntityName)
+ .context(ErrorMessages::FailedToDeserialize, "EntityName")
+ .context(ErrorMessages::FailedToReadJSONArray, "namespace")
.cause(ExpectedNamespace.takeError())
.build();
@@ -508,14 +428,17 @@ JSONFormat::entityIdTableEntryFromJSON(
EntityIdTableEntryObject.getObject("name");
if (!OptEntityNameObject) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::MissingEntityIdTableEntryName)
+ .context(ErrorMessages::FailedToDeserialize, "EntityIdTable entry")
+ .context(ErrorMessages::MissingOrInvalidField, "name",
+ "EntityName JSON object")
.build();
}
auto ExpectedEntityName = entityNameFromJSON(*OptEntityNameObject);
if (!ExpectedEntityName)
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserializeEntityIdTableEntry)
+ .context(ErrorMessages::FailedToDeserialize, "EntityIdTable entry")
+ .context(ErrorMessages::FailedToReadJSONObjectField, "name")
.cause(ExpectedEntityName.takeError())
.build();
@@ -523,7 +446,9 @@ JSONFormat::entityIdTableEntryFromJSON(
EntityIdTableEntryObject.get("id");
if (!EntityIdIntValue) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::MissingEntityIdTableEntryId)
+ .context(ErrorMessages::FailedToDeserialize, "EntityIdTable entry")
+ .context(ErrorMessages::MissingOrInvalidField, "id",
+ "unsigned integer EntityId")
.build();
}
@@ -531,7 +456,8 @@ JSONFormat::entityIdTableEntryFromJSON(
EntityIdIntValue->getAsUINT64();
if (!OptEntityIdInt) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::InvalidEntityIdTableEntryId)
+ .context(ErrorMessages::FailedToDeserialize, "EntityIdTable entry")
+ .context(ErrorMessages::InvalidUInt64Field, "id")
.build();
}
@@ -566,7 +492,9 @@ llvm::Expected<EntityIdTable> JSONFormat::entityIdTableFromJSON(
if (!OptEntityIdTableEntryObject) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::EntityIdTableElementNotObject, Index)
+ .context(ErrorMessages::FailedToDeserialize, "EntityIdTable")
+ .context(ErrorMessages::ElementNotObject, Index,
+ "EntityIdTable entry with 'id' and 'name' fields")
.build();
}
@@ -574,7 +502,8 @@ llvm::Expected<EntityIdTable> JSONFormat::entityIdTableFromJSON(
entityIdTableEntryFromJSON(*OptEntityIdTableEntryObject);
if (!ExpectedEntityIdTableEntry)
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserializeEntityIdTable, Index)
+ .context(ErrorMessages::FailedToDeserialize, "EntityIdTable")
+ .context(ErrorMessages::AtIndex, Index)
.cause(ExpectedEntityIdTableEntry.takeError())
.build();
@@ -582,7 +511,8 @@ llvm::Expected<EntityIdTable> JSONFormat::entityIdTableFromJSON(
Entities.emplace(std::move(*ExpectedEntityIdTableEntry));
if (!EntityInserted) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::DuplicateEntityName, Index,
+ .context(ErrorMessages::FailedToDeserialize, "EntityIdTable")
+ .context(ErrorMessages::DuplicateWithExistingId, "EntityName", Index,
getEntityIdIndex(EntityIt->second))
.build();
}
@@ -616,7 +546,8 @@ JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
auto InfoIt = FormatInfos.find(SN);
if (InfoIt == FormatInfos.end()) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::NoFormatInfoForSummary, SN.str().data())
+ .context(ErrorMessages::FailedToDeserialize, "EntitySummary")
+ .context(ErrorMessages::NoFormatInfoForSummaryName, SN.str().data())
.build();
}
const auto &InfoEntry = InfoIt->second;
@@ -656,7 +587,9 @@ JSONFormat::entityDataMapEntryFromJSON(
EntityDataMapEntryObject.get("entity_id");
if (!EntityIdIntValue) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::MissingEntityDataMapEntryEntityId)
+ .context(ErrorMessages::FailedToDeserialize, "EntityDataMap entry")
+ .context(ErrorMessages::MissingOrInvalidField, "entity_id",
+ "unsigned integer EntityId")
.build();
}
@@ -664,7 +597,8 @@ JSONFormat::entityDataMapEntryFromJSON(
EntityIdIntValue->getAsUINT64();
if (!OptEntityIdInt) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::InvalidEntityDataMapEntryEntityId)
+ .context(ErrorMessages::FailedToDeserialize, "EntityDataMap entry")
+ .context(ErrorMessages::InvalidUInt64Field, "entity_id")
.build();
}
@@ -674,7 +608,9 @@ JSONFormat::entityDataMapEntryFromJSON(
EntityDataMapEntryObject.getObject("entity_summary");
if (!OptEntitySummaryObject) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::MissingEntityDataMapEntryEntitySummary)
+ .context(ErrorMessages::FailedToDeserialize, "EntityDataMap entry")
+ .context(ErrorMessages::MissingOrInvalidField, "entity_summary",
+ "EntitySummary JSON object")
.build();
}
@@ -682,7 +618,8 @@ JSONFormat::entityDataMapEntryFromJSON(
entitySummaryFromJSON(SN, *OptEntitySummaryObject, IdTable);
if (!ExpectedEntitySummary)
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserializeEntityDataMapEntry)
+ .context(ErrorMessages::FailedToDeserialize, "EntityDataMap entry")
+ .context(ErrorMessages::FailedToReadJSONObjectField, "entity_summary")
.cause(ExpectedEntitySummary.takeError())
.build();
@@ -706,7 +643,10 @@ JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
EntityDataMapEntryValue.getAsObject();
if (!OptEntityDataMapEntryObject) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::EntityDataMapElementNotObject, Index)
+ .context(ErrorMessages::FailedToDeserialize, "EntityDataMap")
+ .context(ErrorMessages::ElementNotObject, Index,
+ "EntityDataMap entry with 'entity_id' and 'entity_summary' "
+ "fields")
.build();
}
@@ -714,7 +654,8 @@ JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
entityDataMapEntryFromJSON(*OptEntityDataMapEntryObject, SN, IdTable);
if (!ExpectedEntityDataMapEntry)
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserializeEntityDataMap, Index)
+ .context(ErrorMessages::FailedToDeserialize, "EntityDataMap")
+ .context(ErrorMessages::AtIndex, Index)
.cause(ExpectedEntityDataMapEntry.takeError())
.build();
@@ -722,7 +663,8 @@ JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
EntityDataMap.insert(std::move(*ExpectedEntityDataMapEntry));
if (!DataInserted) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::DuplicateEntityId,
+ .context(ErrorMessages::FailedToDeserialize, "EntityDataMap")
+ .context(ErrorMessages::DuplicateEntityIdAtIndex,
getEntityIdIndex(DataIt->first), Index)
.build();
}
@@ -761,7 +703,9 @@ JSONFormat::summaryDataMapEntryFromJSON(
if (!OptSummaryNameStr) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::MissingSummaryDataMapEntrySummaryName)
+ .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap entry")
+ .context(ErrorMessages::MissingOrInvalidField, "summary_name",
+ "string (analysis summary identifier)")
.build();
}
@@ -771,7 +715,9 @@ JSONFormat::summaryDataMapEntryFromJSON(
SummaryDataMapEntryObject.getArray("summary_data");
if (!OptEntityDataArray) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::MissingSummaryDataMapEntrySummaryData)
+ .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap entry")
+ .context(ErrorMessages::MissingOrInvalidField, "summary_data",
+ "JSON array of entity data entries")
.build();
}
@@ -779,8 +725,9 @@ JSONFormat::summaryDataMapEntryFromJSON(
entityDataMapFromJSON(SN, *OptEntityDataArray, IdTable);
if (!ExpectedEntityDataMap)
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserializeSummaryDataMapEntry,
- SN.str().data())
+ .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap entry")
+ .context(ErrorMessages::ForSummary, SN.str().data())
+ .context(ErrorMessages::FailedToReadJSONArray, "summary_data")
.cause(ExpectedEntityDataMap.takeError())
.build();
@@ -814,7 +761,10 @@ JSONFormat::summaryDataMapFromJSON(const llvm::json::Array &SummaryDataArray,
SummaryDataMapEntryValue.getAsObject();
if (!OptSummaryDataMapEntryObject) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::SummaryDataMapElementNotObject, Index)
+ .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap")
+ .context(ErrorMessages::ElementNotObject, Index,
+ "SummaryDataMap entry with 'summary_name' and "
+ "'summary_data' fields")
.build();
}
@@ -822,7 +772,8 @@ JSONFormat::summaryDataMapFromJSON(const llvm::json::Array &SummaryDataArray,
summaryDataMapEntryFromJSON(*OptSummaryDataMapEntryObject, IdTable);
if (!ExpectedSummaryDataMapEntry)
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserializeSummaryDataMap, Index)
+ .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap")
+ .context(ErrorMessages::AtIndex, Index)
.cause(ExpectedSummaryDataMapEntry.takeError())
.build();
@@ -830,7 +781,8 @@ JSONFormat::summaryDataMapFromJSON(const llvm::json::Array &SummaryDataArray,
SummaryDataMap.emplace(std::move(*ExpectedSummaryDataMapEntry));
if (!SummaryInserted) {
return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::DuplicateSummaryName,
+ .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap")
+ .context(ErrorMessages::DuplicateAtIndex, "SummaryName",
SummaryIt->first.str().data(), Index)
.build();
}
@@ -871,7 +823,8 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
if (!TUNamespaceObject) {
return ErrorBuilder(std::errc::invalid_argument)
.context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
- .context(ErrorMessages::MissingTUNamespace)
+ .context(ErrorMessages::MissingOrInvalidField, "tu_namespace",
+ "JSON object")
.build();
}
@@ -890,7 +843,8 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
if (!IdTableArray) {
return ErrorBuilder(std::errc::invalid_argument)
.context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
- .context(ErrorMessages::MissingIdTable)
+ .context(ErrorMessages::MissingOrInvalidField, "id_table",
+ "JSON array")
.build();
}
@@ -898,6 +852,7 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
if (!ExpectedIdTable)
return ErrorBuilder(std::errc::invalid_argument)
.context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
+ .context(ErrorMessages::FailedToReadJSONArray, "id_table")
.cause(ExpectedIdTable.takeError())
.build();
@@ -910,7 +865,7 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
if (!SummaryDataArray) {
return ErrorBuilder(std::errc::invalid_argument)
.context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
- .context(ErrorMessages::MissingData)
+ .context(ErrorMessages::MissingOrInvalidField, "data", "JSON array")
.build();
}
@@ -919,6 +874,7 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
if (!ExpectedSummaryDataMap)
return ErrorBuilder(std::errc::invalid_argument)
.context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
+ .context(ErrorMessages::FailedToReadJSONArray, "data")
.cause(ExpectedSummaryDataMap.takeError())
.build();
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
index 70fb0701b3065..567f294700d9e 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
@@ -234,6 +234,84 @@ TEST_F(JSONFormatTest, PathIsDirectory) {
HasSubstr("path is a directory, not a file"))));
}
+TEST_F(JSONFormatTest, BrokenSymlink) {
+#ifdef _WIN32
+ GTEST_SKIP() << "Symlink test skipped on Windows";
+#else
+ SmallString<128> SymlinkPath = TestDir;
+ sys::path::append(SymlinkPath, "symlink.json");
+
+ // Create a symlink to a non-existent file
+ SmallString<128> NonexistentTarget = TestDir;
+ sys::path::append(NonexistentTarget, "does_not_exist.json");
+
+ std::error_code EC = sys::fs::create_link(NonexistentTarget, SymlinkPath);
+ if (EC) {
+ GTEST_SKIP() << "Failed to create symlink (may need elevated privileges): "
+ << EC.message();
+ }
+
+ // Verify the symlink points to a non-existent file by checking file status
+ sys::fs::file_status Status;
+ EC = sys::fs::status(SymlinkPath, Status);
+ if (!EC) {
+ // If status succeeds, the target exists - skip test
+ GTEST_SKIP() << "Symlink unexpectedly points to existing file";
+ }
+
+ JSONFormat Format(vfs::getRealFileSystem());
+ auto Result = Format.readTUSummary(SymlinkPath);
+
+ EXPECT_THAT(Result, FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("file does not exist"))));
+#endif
+}
+
+TEST_F(JSONFormatTest, FileWithoutReadPermission) {
+#ifdef _WIN32
+ GTEST_SKIP() << "Permission test skipped on Windows (uses different ACL "
+ "model)";
+#else
+ SmallString<128> FilePath = TestDir;
+ sys::path::append(FilePath, "no_read.json");
+
+ // Create a file with valid JSON content
+ std::error_code EC;
+ raw_fd_ostream OS(FilePath, EC);
+ ASSERT_FALSE(EC) << "Failed to create file: " << EC.message();
+ OS << R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "data": []
+ })";
+ OS.close();
+
+ // Remove read permissions (chmod 000)
+ auto Perms = sys::fs::perms::all_all;
+ EC = sys::fs::setPermissions(FilePath, Perms);
+ ASSERT_FALSE(EC) << "Failed to set permissions: " << EC.message();
+
+ // Now remove all permissions
+ EC = sys::fs::setPermissions(FilePath, static_cast<sys::fs::perms>(0));
+ if (EC) {
+ GTEST_SKIP() << "Failed to remove permissions (may be running as root): "
+ << EC.message();
+ }
+
+ JSONFormat Format(vfs::getRealFileSystem());
+ auto Result = Format.readTUSummary(FilePath);
+
+ // Restore permissions for cleanup
+ sys::fs::setPermissions(FilePath, sys::fs::perms::all_all);
+
+ EXPECT_THAT(Result, FailedWith(AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read file"))));
+#endif
+}
+
TEST_F(JSONFormatTest, NotJsonExtension) {
auto Result = readJSON("{}", "test.txt");
@@ -289,7 +367,7 @@ TEST_F(JSONFormatTest, MissingKind) {
EXPECT_THAT(Result, FailedWith(AllOf(
HasSubstr("reading TUSummary from"),
HasSubstr("failed to deserialize BuildNamespace"),
- HasSubstr("missing required field 'kind' "
+ HasSubstr("missing or invalid field 'kind' "
"(expected BuildNamespaceKind)"))));
}
@@ -305,7 +383,8 @@ TEST_F(JSONFormatTest, MissingName) {
EXPECT_THAT(Result, FailedWith(AllOf(
HasSubstr("reading TUSummary from"),
HasSubstr("failed to deserialize BuildNamespace"),
- HasSubstr("missing required field 'name'"))));
+ HasSubstr("missing or invalid field 'name' "
+ "(expected string)"))));
}
TEST_F(JSONFormatTest, InvalidKind) {
@@ -321,6 +400,7 @@ TEST_F(JSONFormatTest, InvalidKind) {
EXPECT_THAT(Result, FailedWith(AllOf(
HasSubstr("reading TUSummary from"),
HasSubstr("failed to deserialize BuildNamespace"),
+ HasSubstr("while parsing field 'kind'"),
HasSubstr("invalid 'kind' BuildNamespaceKind "
"value"))));
}
@@ -413,10 +493,11 @@ TEST_F(JSONFormatTest, IDTableEntryMissingID) {
EXPECT_THAT(Result,
FailedWith(AllOf(
HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read JSON array from field 'id_table'"),
HasSubstr("failed to deserialize EntityIdTable at index 0"),
HasSubstr("failed to deserialize EntityIdTable entry"),
- HasSubstr("missing required field 'id' (expected unsigned "
- "integer EntityId)"))));
+ HasSubstr("missing or invalid field 'id' "
+ "(expected unsigned integer EntityId)"))));
}
TEST_F(JSONFormatTest, IDTableEntryMissingName) {
@@ -437,8 +518,10 @@ TEST_F(JSONFormatTest, IDTableEntryMissingName) {
Result,
FailedWith(AllOf(
HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read JSON array from field 'id_table'"),
HasSubstr("failed to deserialize EntityIdTable at index 0"),
HasSubstr("failed to deserialize EntityIdTable entry"),
+ HasSubstr("failed to read JSON object from field 'name'"),
HasSubstr("missing or invalid field 'name' (expected EntityName JSON "
"object)"))));
}
@@ -466,6 +549,7 @@ TEST_F(JSONFormatTest, IDTableEntryIDNotUInt64) {
Result,
FailedWith(
AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read JSON array from field 'id_table'"),
HasSubstr("failed to deserialize EntityIdTable at index 0"),
HasSubstr("failed to deserialize EntityIdTable entry"),
HasSubstr("field 'id' is not a valid unsigned 64-bit integer"),
@@ -541,11 +625,13 @@ TEST_F(JSONFormatTest, EntityNameMissingUSR) {
EXPECT_THAT(Result,
FailedWith(AllOf(
HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read JSON array from field 'id_table'"),
HasSubstr("failed to deserialize EntityIdTable at index 0"),
HasSubstr("failed to deserialize EntityIdTable entry"),
+ HasSubstr("failed to read JSON object from field 'name'"),
HasSubstr("failed to deserialize EntityName"),
- HasSubstr("missing required field 'usr' (Unified Symbol "
- "Resolution string)"))));
+ HasSubstr("missing or invalid field 'usr' "
+ "(expected string (Unified Symbol Resolution))"))));
}
TEST_F(JSONFormatTest, EntityNameMissingSuffix) {
@@ -566,14 +652,17 @@ TEST_F(JSONFormatTest, EntityNameMissingSuffix) {
"data": []
})");
- EXPECT_THAT(
- Result,
- FailedWith(AllOf(HasSubstr("reading TUSummary from"),
- HasSubstr("failed to deserialize EntityIdTable at "
- "index 0"),
- HasSubstr("failed to deserialize EntityIdTable entry"),
- HasSubstr("failed to deserialize EntityName"),
- HasSubstr("missing required field 'suffix'"))));
+ EXPECT_THAT(Result,
+ FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read JSON array from field 'id_table'"),
+ HasSubstr("failed to deserialize EntityIdTable at "
+ "index 0"),
+ HasSubstr("failed to deserialize EntityIdTable entry"),
+ HasSubstr("failed to read JSON object from field 'name'"),
+ HasSubstr("failed to deserialize EntityName"),
+ HasSubstr("missing or invalid field 'suffix' "
+ "(expected string)"))));
}
TEST_F(JSONFormatTest, EntityNameMissingNamespace) {
@@ -598,9 +687,12 @@ TEST_F(JSONFormatTest, EntityNameMissingNamespace) {
Result,
FailedWith(
AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read JSON array from field 'id_table'"),
HasSubstr("failed to deserialize EntityIdTable at index 0"),
HasSubstr("failed to deserialize EntityIdTable entry"),
+ HasSubstr("failed to read JSON object from field 'name'"),
HasSubstr("failed to deserialize EntityName"),
+ HasSubstr("failed to read JSON array from field 'namespace'"),
HasSubstr("missing or invalid field 'namespace'"),
HasSubstr("(expected JSON array of BuildNamespace objects)"))));
}
@@ -627,9 +719,12 @@ TEST_F(JSONFormatTest, NamespaceElementNotObject) {
EXPECT_THAT(Result,
FailedWith(AllOf(
HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read JSON array from field 'id_table'"),
HasSubstr("failed to deserialize EntityIdTable at index 0"),
HasSubstr("failed to deserialize EntityIdTable entry"),
+ HasSubstr("failed to read JSON object from field 'name'"),
HasSubstr("failed to deserialize EntityName"),
+ HasSubstr("failed to read JSON array from field 'namespace'"),
HasSubstr("failed to deserialize NestedBuildNamespace"),
HasSubstr("element at index 0 is not a JSON object"))));
}
@@ -688,11 +783,14 @@ TEST_F(JSONFormatTest, DataEntryMissingSummaryName) {
EXPECT_THAT(
Result,
- FailedWith(AllOf(HasSubstr("reading TUSummary from"),
- HasSubstr("failed to deserialize SummaryDataMap at "
- "index 0"),
- HasSubstr("failed to deserialize SummaryDataMap entry"),
- HasSubstr("missing required field 'summary_name'"))));
+ FailedWith(
+ AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read JSON array from field 'data'"),
+ HasSubstr("failed to deserialize SummaryDataMap at "
+ "index 0"),
+ HasSubstr("failed to deserialize SummaryDataMap entry"),
+ HasSubstr("missing or invalid field 'summary_name' "
+ "(expected string (analysis summary identifier))"))));
}
TEST_F(JSONFormatTest, DataEntryMissingData) {
@@ -712,6 +810,7 @@ TEST_F(JSONFormatTest, DataEntryMissingData) {
EXPECT_THAT(Result,
FailedWith(AllOf(
HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read JSON array from field 'data'"),
HasSubstr("failed to deserialize SummaryDataMap at index 0"),
HasSubstr("failed to deserialize SummaryDataMap entry"),
HasSubstr("missing or invalid field 'summary_data'"))));
@@ -766,9 +865,11 @@ TEST_F(JSONFormatTest, EntityDataElementNotObject) {
Result,
FailedWith(AllOf(
HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read JSON array from field 'data'"),
HasSubstr("failed to deserialize SummaryDataMap at index 0"),
- HasSubstr("failed to deserialize SummaryDataMap entry for summary "
- "'test_summary'"),
+ HasSubstr("failed to deserialize SummaryDataMap entry"),
+ HasSubstr("for summary 'test_summary'"),
+ HasSubstr("failed to read JSON array from field 'summary_data'"),
HasSubstr("failed to deserialize EntityDataMap"),
HasSubstr("element at index 0 is not a JSON object"),
HasSubstr("(expected EntityDataMap entry with 'entity_id' and "
@@ -799,13 +900,15 @@ TEST_F(JSONFormatTest, EntityDataMissingEntityID) {
Result,
FailedWith(AllOf(
HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read JSON array from field 'data'"),
HasSubstr("failed to deserialize SummaryDataMap at index 0"),
- HasSubstr("failed to deserialize SummaryDataMap entry for summary "
- "'test_summary'"),
+ HasSubstr("failed to deserialize SummaryDataMap entry"),
+ HasSubstr("for summary 'test_summary'"),
+ HasSubstr("failed to read JSON array from field 'summary_data'"),
HasSubstr("failed to deserialize EntityDataMap at index 0"),
HasSubstr("failed to deserialize EntityDataMap entry"),
- HasSubstr("missing required field 'entity_id' (expected unsigned "
- "integer EntityId)"))));
+ HasSubstr("missing or invalid field 'entity_id' "
+ "(expected unsigned integer EntityId)"))));
}
TEST_F(JSONFormatTest, EntityDataMissingEntitySummary) {
@@ -831,11 +934,14 @@ TEST_F(JSONFormatTest, EntityDataMissingEntitySummary) {
Result,
FailedWith(AllOf(
HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read JSON array from field 'data'"),
HasSubstr("failed to deserialize SummaryDataMap at index 0"),
- HasSubstr("failed to deserialize SummaryDataMap entry for summary "
- "'test_summary'"),
+ HasSubstr("failed to deserialize SummaryDataMap entry"),
+ HasSubstr("for summary 'test_summary'"),
+ HasSubstr("failed to read JSON array from field 'summary_data'"),
HasSubstr("failed to deserialize EntityDataMap at index 0"),
HasSubstr("failed to deserialize EntityDataMap entry"),
+ HasSubstr("failed to read JSON object from field 'entity_summary'"),
HasSubstr("missing or invalid field 'entity_summary'"),
HasSubstr("(expected EntitySummary JSON object)"))));
}
@@ -864,9 +970,11 @@ TEST_F(JSONFormatTest, EntityIDNotUInt64) {
Result,
FailedWith(AllOf(
HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read JSON array from field 'data'"),
HasSubstr("failed to deserialize SummaryDataMap at index 0"),
- HasSubstr("failed to deserialize SummaryDataMap entry for summary "
- "'test_summary'"),
+ HasSubstr("failed to deserialize SummaryDataMap entry"),
+ HasSubstr("for summary 'test_summary'"),
+ HasSubstr("failed to read JSON array from field 'summary_data'"),
HasSubstr("failed to deserialize EntityDataMap at index 0"),
HasSubstr("failed to deserialize EntityDataMap entry"),
HasSubstr("field 'entity_id' is not a valid unsigned 64-bit integer"),
@@ -897,11 +1005,14 @@ TEST_F(JSONFormatTest, EntitySummaryNoFormatInfo) {
Result,
FailedWith(AllOf(
HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read JSON array from field 'data'"),
HasSubstr("failed to deserialize SummaryDataMap at index 0"),
- HasSubstr("failed to deserialize SummaryDataMap entry for summary "
- "'unknown_summary_type'"),
+ HasSubstr("failed to deserialize SummaryDataMap entry"),
+ HasSubstr("for summary 'unknown_summary_type'"),
+ HasSubstr("failed to read JSON array from field 'summary_data'"),
HasSubstr("failed to deserialize EntityDataMap at index 0"),
HasSubstr("failed to deserialize EntityDataMap entry"),
+ HasSubstr("failed to read JSON object from field 'entity_summary'"),
HasSubstr("failed to deserialize EntitySummary"),
HasSubstr("no FormatInfo was registered for summary name: "
"unknown_summary_type"))));
@@ -933,15 +1044,19 @@ TEST_F(JSONFormatTest, TestAnalysisMissingField) {
EXPECT_THAT(
Result,
- FailedWith(AllOf(HasSubstr("reading TUSummary from"),
- HasSubstr("failed to deserialize SummaryDataMap at "
- "index 0"),
- HasSubstr("failed to deserialize SummaryDataMap entry "
- "for summary 'test_summary'"),
- HasSubstr("failed to deserialize EntityDataMap at "
- "index 0"),
- HasSubstr("failed to deserialize EntityDataMap entry"),
- HasSubstr("missing required field 'pairs'"))));
+ FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read JSON array from field 'data'"),
+ HasSubstr("failed to deserialize SummaryDataMap at "
+ "index 0"),
+ HasSubstr("failed to deserialize SummaryDataMap entry"),
+ HasSubstr("for summary 'test_summary'"),
+ HasSubstr("failed to read JSON array from field 'summary_data'"),
+ HasSubstr("failed to deserialize EntityDataMap at "
+ "index 0"),
+ HasSubstr("failed to deserialize EntityDataMap entry"),
+ HasSubstr("failed to read JSON object from field 'entity_summary'"),
+ HasSubstr("missing required field 'pairs'"))));
}
TEST_F(JSONFormatTest, TestAnalysisInvalidPair) {
@@ -973,16 +1088,20 @@ TEST_F(JSONFormatTest, TestAnalysisInvalidPair) {
EXPECT_THAT(
Result,
- FailedWith(AllOf(HasSubstr("reading TUSummary from"),
- HasSubstr("failed to deserialize SummaryDataMap at "
- "index 0"),
- HasSubstr("failed to deserialize SummaryDataMap entry "
- "for summary 'test_summary'"),
- HasSubstr("failed to deserialize EntityDataMap at "
- "index 0"),
- HasSubstr("failed to deserialize EntityDataMap entry"),
- HasSubstr("missing or invalid 'second' field at "
- "index 0"))));
+ FailedWith(AllOf(
+ HasSubstr("reading TUSummary from"),
+ HasSubstr("failed to read JSON array from field 'data'"),
+ HasSubstr("failed to deserialize SummaryDataMap at "
+ "index 0"),
+ HasSubstr("failed to deserialize SummaryDataMap entry"),
+ HasSubstr("for summary 'test_summary'"),
+ HasSubstr("failed to read JSON array from field 'summary_data'"),
+ HasSubstr("failed to deserialize EntityDataMap at "
+ "index 0"),
+ HasSubstr("failed to deserialize EntityDataMap entry"),
+ HasSubstr("failed to read JSON object from field 'entity_summary'"),
+ HasSubstr("missing or invalid 'second' field at "
+ "index 0"))));
}
// ============================================================================
>From edc0eaf2082e5c28c0b67eba291b4ac27d4af5f0 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Tue, 10 Feb 2026 16:50:44 -0800
Subject: [PATCH 19/19] Fix ErrorBuilder, and tests
---
.../Scalable/Serialization/JSONFormat.h | 10 +-
.../Scalable/Serialization/JSONFormat.cpp | 642 +++++----
.../Scalable/Serialization/JSONFormatTest.cpp | 1228 +++++++++--------
3 files changed, 999 insertions(+), 881 deletions(-)
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
index fb48e0122ade9..8078937a94b6f 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -93,8 +93,8 @@ class JSONFormat : public SerializationFormat {
entitySummaryFromJSON(const SummaryName &SN,
const llvm::json::Object &EntitySummaryObject,
EntityIdTable &IdTable) const;
- llvm::json::Object entitySummaryToJSON(const SummaryName &SN,
- const EntitySummary &ES) const;
+ llvm::Expected<llvm::json::Object>
+ entitySummaryToJSON(const SummaryName &SN, const EntitySummary &ES) const;
llvm::Expected<std::pair<EntityId, std::unique_ptr<EntitySummary>>>
entityDataMapEntryFromJSON(const llvm::json::Object &EntityDataMapEntryObject,
@@ -104,7 +104,7 @@ class JSONFormat : public SerializationFormat {
entityDataMapFromJSON(const SummaryName &SN,
const llvm::json::Array &EntityDataArray,
EntityIdTable &IdTable) const;
- llvm::json::Array
+ llvm::Expected<llvm::json::Array>
entityDataMapToJSON(const SummaryName &SN,
const std::map<EntityId, std::unique_ptr<EntitySummary>>
&EntityDataMap) const;
@@ -113,7 +113,7 @@ class JSONFormat : public SerializationFormat {
std::map<EntityId, std::unique_ptr<EntitySummary>>>>
summaryDataMapEntryFromJSON(const llvm::json::Object &SummaryDataObject,
EntityIdTable &IdTable) const;
- llvm::json::Object summaryDataMapEntryToJSON(
+ llvm::Expected<llvm::json::Object> summaryDataMapEntryToJSON(
const SummaryName &SN,
const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const;
@@ -121,7 +121,7 @@ class JSONFormat : public SerializationFormat {
std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
summaryDataMapFromJSON(const llvm::json::Array &SummaryDataArray,
EntityIdTable &IdTable) const;
- llvm::json::Array summaryDataMapToJSON(
+ llvm::Expected<llvm::json::Array> summaryDataMapToJSON(
const std::map<SummaryName,
std::map<EntityId, std::unique_ptr<EntitySummary>>>
&SummaryDataMap) const;
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index de4003498195a..31b2d48fa6a4e 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -11,112 +11,142 @@
using namespace clang::ssaf;
-namespace {
//----------------------------------------------------------------------------
// ErrorBuilder - Fluent API for constructing contextual errors
//----------------------------------------------------------------------------
+namespace {
+
class ErrorBuilder {
private:
std::error_code Code;
std::vector<std::string> ContextStack;
- llvm::Error WrappedError = llvm::Error::success();
-public:
- explicit ErrorBuilder(std::errc EC) : Code(std::make_error_code(EC)) {}
+ // Private constructor - only accessible via static factories
explicit ErrorBuilder(std::error_code EC) : Code(EC) {}
- // Add context message without formatting (for plain strings)
+ // Helper: Format message and add to context stack
+ template <typename... Args>
+ void addFormattedContext(const char *Fmt, Args &&...ArgVals) {
+ std::string Message =
+ llvm::formatv(Fmt, std::forward<Args>(ArgVals)...).str();
+ ContextStack.push_back(std::move(Message));
+ }
+
+public:
+ // Static factory: Create new error from error code and formatted message
+ template <typename... Args>
+ static ErrorBuilder create(std::error_code EC, const char *Fmt,
+ Args &&...ArgVals) {
+ ErrorBuilder Builder(EC);
+ Builder.addFormattedContext(Fmt, std::forward<Args>(ArgVals)...);
+ return Builder;
+ }
+
+ // Convenience overload for std::errc
+ template <typename... Args>
+ static ErrorBuilder create(std::errc EC, const char *Fmt, Args &&...ArgVals) {
+ return create(std::make_error_code(EC), Fmt,
+ std::forward<Args>(ArgVals)...);
+ }
+
+ // Static factory: Wrap existing error and optionally add context
+ static ErrorBuilder wrap(llvm::Error E) {
+ if (!E) {
+ llvm::consumeError(std::move(E));
+ // Return builder with generic error code for success case
+ return ErrorBuilder(std::make_error_code(std::errc::invalid_argument));
+ }
+
+ std::error_code EC;
+ std::string ErrorMsg;
+
+ llvm::handleAllErrors(std::move(E), [&](const llvm::ErrorInfoBase &EI) {
+ EC = EI.convertToErrorCode();
+ ErrorMsg = EI.message();
+ });
+
+ ErrorBuilder Builder(EC);
+ if (!ErrorMsg.empty()) {
+ Builder.ContextStack.push_back(std::move(ErrorMsg));
+ }
+ return Builder;
+ }
+
+ // Add context (plain string)
ErrorBuilder &context(const char *Msg) {
ContextStack.push_back(Msg);
return *this;
}
- // Add context message with formatting
+ // Add context (formatted string)
template <typename... Args>
ErrorBuilder &context(const char *Fmt, Args &&...ArgVals) {
- ContextStack.push_back(
- llvm::formatv(Fmt, std::forward<Args>(ArgVals)...).str());
- return *this;
- }
-
- // Wrap an existing error as the cause
- ErrorBuilder &cause(llvm::Error E) {
- // Consume the old WrappedError before assigning (LLVM Error requires
- // checking)
- llvm::consumeError(std::move(WrappedError));
- WrappedError = std::move(E);
+ addFormattedContext(Fmt, std::forward<Args>(ArgVals)...);
return *this;
}
// Build the final error
llvm::Error build() {
- if (ContextStack.empty() && !WrappedError)
- return llvm::Error::success();
-
if (ContextStack.empty())
- return std::move(WrappedError);
-
- std::string FinalMessage = llvm::join(ContextStack, ": ");
- auto E = llvm::createStringError(Code, "%s", FinalMessage.c_str());
-
- if (WrappedError)
- return llvm::joinErrors(std::move(E), std::move(WrappedError));
+ return llvm::Error::success();
- return E;
+ // Reverse the context stack so that the most recent context appears first
+ // and the wrapped error (if any) appears last
+ std::vector<std::string> ReversedContext(ContextStack.rbegin(),
+ ContextStack.rend());
+ std::string FinalMessage = llvm::join(ReversedContext, "\n");
+ return llvm::createStringError(Code, "%s", FinalMessage.c_str());
}
};
+} // namespace
+
//----------------------------------------------------------------------------
// Error Message Constants
//----------------------------------------------------------------------------
+namespace {
+
namespace ErrorMessages {
-// File validation errors
-constexpr const char *FileNotFound = "file does not exist: '{0}'";
-constexpr const char *IsDirectory = "path is a directory, not a file: '{0}'";
-constexpr const char *NotJSONFile = "not a JSON file: '{0}'";
-constexpr const char *FailedToValidateJSONFile =
- "failed to validate JSON file '{0}'";
-constexpr const char *FailedToReadFile = "failed to read file '{0}'";
-constexpr const char *FailedToReadJSONObject =
- "failed to read JSON object from file '{0}'";
-constexpr const char *FailedToReadJSONArray =
- "failed to read JSON array from field '{0}'";
-constexpr const char *FailedToReadJSONObjectField =
- "failed to read JSON object from field '{0}'";
-constexpr const char *FailedToOpenFile = "failed to open '{0}'";
-constexpr const char *WriteFailed = "write failed";
-
-// Generic deserialization error templates
-constexpr const char *FailedToDeserialize = "failed to deserialize {0}";
-constexpr const char *AtIndex = "at index {0}";
-constexpr const char *ForSummary = "for summary '{0}'";
-
-// Specific error details (to be stacked with FailedToDeserialize)
-constexpr const char *MissingOrInvalidField =
- "missing or invalid field '{0}' (expected {1})";
-constexpr const char *ElementNotObject =
- "element at index {0} is not a JSON object (expected {1})";
-constexpr const char *InvalidUInt64Field =
- "field '{0}' is not a valid unsigned 64-bit integer (expected "
- "non-negative EntityId value)";
-constexpr const char *DuplicateWithExistingId =
- "duplicate {0} found at index {1} (EntityId={2} already exists in table)";
-constexpr const char *DuplicateEntityIdAtIndex =
- "duplicate EntityId ({0}) found at index {1}";
-constexpr const char *DuplicateAtIndex =
- "duplicate {0} '{1}' found at index {2}";
-
-// Special cases
+
+constexpr const char *FailedToReadFile = "failed to read file '{0}': {1}";
+constexpr const char *FailedToWriteFile = "failed to write file '{0}': {1}";
+constexpr const char *FileNotFound = "file does not exist";
+constexpr const char *FileIsDirectory = "path is a directory, not a file";
+constexpr const char *FileIsNotJSON =
+ "file does not end with '.json' extension";
+constexpr const char *FileExists = "file already exists";
+constexpr const char *ParentDirectoryNotFound =
+ "parent directory does not exist";
+
+constexpr const char *ReadingFromField = "reading {0} from field '{1}'";
+constexpr const char *WritingToField = "writing {0} to field '{1}'";
+constexpr const char *ReadingFromIndex = "reading {0} from index '{1}'";
+constexpr const char *WritingToIndex = "writing {0} to index '{1}'";
+constexpr const char *ReadingFromFile = "reading {0} from file '{1}'";
+constexpr const char *WritingToFile = "writing {0} to file '{1}'";
+
+constexpr const char *FailedInsertionOnDuplication =
+ "failed to insert {0} at index '{1}': encountered duplicate {2} '{3}'";
+
+constexpr const char *FailedToReadObject =
+ "failed to read {0}: expected JSON {1}";
+constexpr const char *FailedToReadObjectAtField =
+ "failed to read {0} from field '{1}': expected JSON {2}";
+constexpr const char *FailedToReadObjectAtIndex =
+ "failed to read {0} from index '{1}': expected JSON {2}";
+
+constexpr const char *FailedToDeserializeEntitySummary =
+ "failed to deserialize EntitySummary: no FormatInfo registered for summary "
+ "'{0}'";
+constexpr const char *FailedToSerializeEntitySummary =
+ "failed to serialize EntitySummary: no FormatInfo registered for summary "
+ "'{0}'";
+
constexpr const char *InvalidBuildNamespaceKind =
"invalid 'kind' BuildNamespaceKind value '{0}'";
-constexpr const char *NoFormatInfoForSummaryName =
- "no FormatInfo was registered for summary name: {0}";
-// Context messages
-constexpr const char *ReadingTUSummaryFrom = "reading TUSummary from '{0}'";
-constexpr const char *WritingTUSummaryTo = "writing TUSummary to '{0}'";
} // namespace ErrorMessages
} // namespace
@@ -127,65 +157,68 @@ constexpr const char *WritingTUSummaryTo = "writing TUSummary to '{0}'";
namespace {
-llvm::Error isJSONFile(llvm::StringRef Path) {
- if (!llvm::sys::fs::exists(Path))
- return ErrorBuilder(std::errc::no_such_file_or_directory)
- .context(ErrorMessages::FileNotFound, Path.str().c_str())
- .build();
-
- if (llvm::sys::fs::is_directory(Path))
- return ErrorBuilder(std::errc::is_a_directory)
- .context(ErrorMessages::IsDirectory, Path.str().c_str())
+llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
+ if (!llvm::sys::fs::exists(Path)) {
+ return ErrorBuilder::create(std::errc::no_such_file_or_directory,
+ ErrorMessages::FailedToReadFile, Path,
+ ErrorMessages::FileNotFound)
.build();
+ }
- if (!Path.ends_with_insensitive(".json"))
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::NotJSONFile, Path.str().c_str())
+ if (llvm::sys::fs::is_directory(Path)) {
+ return ErrorBuilder::create(std::errc::is_a_directory,
+ ErrorMessages::FailedToReadFile, Path,
+ ErrorMessages::FileIsDirectory)
.build();
+ }
- return llvm::Error::success();
-}
-
-llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
- if (llvm::Error Err = isJSONFile(Path))
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToValidateJSONFile, Path.str().c_str())
- .cause(std::move(Err))
+ if (!Path.ends_with_insensitive(".json")) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadFile, Path,
+ ErrorMessages::FileIsNotJSON)
.build();
+ }
auto BufferOrError = llvm::MemoryBuffer::getFile(Path);
if (!BufferOrError) {
- return ErrorBuilder(BufferOrError.getError())
- .context(ErrorMessages::FailedToReadFile, Path.str().c_str())
+ const std::error_code EC = BufferOrError.getError();
+ return ErrorBuilder::create(EC, ErrorMessages::FailedToReadFile, Path,
+ EC.message())
.build();
}
return llvm::json::parse(BufferOrError.get()->getBuffer());
}
-llvm::Expected<llvm::json::Object> readJSONObject(llvm::StringRef Path) {
- auto ExpectedJSON = readJSON(Path);
- if (!ExpectedJSON)
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToReadJSONObject, Path.str().c_str())
- .cause(ExpectedJSON.takeError())
+llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
+ if (llvm::sys::fs::exists(Path)) {
+ return ErrorBuilder::create(std::errc::file_exists,
+ ErrorMessages::FailedToWriteFile, Path,
+ ErrorMessages::FileExists)
.build();
+ }
- llvm::json::Object *Object = ExpectedJSON->getAsObject();
- if (!Object) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToReadJSONObject, Path.str().c_str())
+ llvm::StringRef Dir = llvm::sys::path::parent_path(Path);
+ if (!Dir.empty() && !llvm::sys::fs::is_directory(Dir)) {
+ return ErrorBuilder::create(std::errc::no_such_file_or_directory,
+ ErrorMessages::FailedToWriteFile, Path,
+ ErrorMessages::ParentDirectoryNotFound)
+ .build();
+ }
+
+ if (!Path.ends_with_insensitive(".json")) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToWriteFile, Path,
+ ErrorMessages::FileIsNotJSON)
.build();
}
- return *Object;
-}
-llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
std::error_code EC;
llvm::raw_fd_ostream OutStream(Path, EC, llvm::sys::fs::OF_Text);
+
if (EC) {
- return ErrorBuilder(EC)
- .context(ErrorMessages::FailedToOpenFile, Path.str().c_str())
+ return ErrorBuilder::create(EC, ErrorMessages::FailedToWriteFile, Path,
+ EC.message())
.build();
}
@@ -193,8 +226,9 @@ llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
OutStream.flush();
if (OutStream.has_error()) {
- return ErrorBuilder(OutStream.error())
- .context(ErrorMessages::WriteFailed)
+ return ErrorBuilder::create(OutStream.error(),
+ ErrorMessages::FailedToWriteFile, Path,
+ OutStream.error().message())
.build();
}
@@ -214,7 +248,7 @@ JSONFormat::JSONFormat(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
bool Inserted = FormatInfos.try_emplace(Info->ForSummary, *Info).second;
if (!Inserted) {
llvm::report_fatal_error(
- "Format info was already registered for summary name: " +
+ "FormatInfo is already registered for summary: " +
Info->ForSummary.str());
}
}
@@ -254,9 +288,9 @@ llvm::Expected<BuildNamespaceKind> JSONFormat::buildNamespaceKindFromJSON(
llvm::StringRef BuildNamespaceKindStr) const {
auto OptBuildNamespaceKind = parseBuildNamespaceKind(BuildNamespaceKindStr);
if (!OptBuildNamespaceKind) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::InvalidBuildNamespaceKind,
- BuildNamespaceKindStr.str().c_str())
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::InvalidBuildNamespaceKind,
+ BuildNamespaceKindStr)
.build();
}
@@ -279,26 +313,23 @@ llvm::Expected<BuildNamespace> JSONFormat::buildNamespaceFromJSON(
const llvm::json::Object &BuildNamespaceObject) const {
auto OptBuildNamespaceKindStr = BuildNamespaceObject.getString("kind");
if (!OptBuildNamespaceKindStr) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "BuildNamespace")
- .context(ErrorMessages::MissingOrInvalidField, "kind",
- "BuildNamespaceKind")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "BuildNamespaceKind", "kind", "string")
.build();
}
auto ExpectedKind = buildNamespaceKindFromJSON(*OptBuildNamespaceKindStr);
if (!ExpectedKind)
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "BuildNamespace")
- .context("while parsing field 'kind'")
- .cause(ExpectedKind.takeError())
+ return ErrorBuilder::wrap(ExpectedKind.takeError())
+ .context(ErrorMessages::ReadingFromField, "BuildNamespaceKind", "kind")
.build();
auto OptNameStr = BuildNamespaceObject.getString("name");
if (!OptNameStr) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "BuildNamespace")
- .context(ErrorMessages::MissingOrInvalidField, "name", "string")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "BuildNamespaceName", "name", "string")
.build();
}
@@ -323,6 +354,7 @@ llvm::Expected<NestedBuildNamespace> JSONFormat::nestedBuildNamespaceFromJSON(
size_t NamespaceCount = NestedBuildNamespaceArray.size();
Namespaces.reserve(NamespaceCount);
+
for (size_t Index = 0; Index < NamespaceCount; ++Index) {
const llvm::json::Value &BuildNamespaceValue =
NestedBuildNamespaceArray[Index];
@@ -330,20 +362,18 @@ llvm::Expected<NestedBuildNamespace> JSONFormat::nestedBuildNamespaceFromJSON(
const llvm::json::Object *BuildNamespaceObject =
BuildNamespaceValue.getAsObject();
if (!BuildNamespaceObject) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "NestedBuildNamespace")
- .context(ErrorMessages::ElementNotObject, Index,
- "BuildNamespace object")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtIndex,
+ "BuildNamespace", Index, "object")
.build();
}
auto ExpectedBuildNamespace = buildNamespaceFromJSON(*BuildNamespaceObject);
- if (!ExpectedBuildNamespace)
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "NestedBuildNamespace")
- .context(ErrorMessages::AtIndex, Index)
- .cause(ExpectedBuildNamespace.takeError())
+ if (!ExpectedBuildNamespace) {
+ return ErrorBuilder::wrap(ExpectedBuildNamespace.takeError())
+ .context(ErrorMessages::ReadingFromIndex, "BuildNamespace", Index)
.build();
+ }
Namespaces.push_back(std::move(*ExpectedBuildNamespace));
}
@@ -372,38 +402,36 @@ llvm::Expected<EntityName> JSONFormat::entityNameFromJSON(
const llvm::json::Object &EntityNameObject) const {
const auto OptUSR = EntityNameObject.getString("usr");
if (!OptUSR) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntityName")
- .context(ErrorMessages::MissingOrInvalidField, "usr",
- "string (Unified Symbol Resolution)")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField, "USR",
+ "usr", "string")
.build();
}
const auto OptSuffix = EntityNameObject.getString("suffix");
if (!OptSuffix) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntityName")
- .context(ErrorMessages::MissingOrInvalidField, "suffix", "string")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "Suffix", "suffix", "string")
.build();
}
const llvm::json::Array *OptNamespaceArray =
EntityNameObject.getArray("namespace");
if (!OptNamespaceArray) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntityName")
- .context(ErrorMessages::MissingOrInvalidField, "namespace",
- "JSON array of BuildNamespace objects")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "NestedBuildNamespace", "namespace", "array")
.build();
}
auto ExpectedNamespace = nestedBuildNamespaceFromJSON(*OptNamespaceArray);
- if (!ExpectedNamespace)
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntityName")
- .context(ErrorMessages::FailedToReadJSONArray, "namespace")
- .cause(ExpectedNamespace.takeError())
+ if (!ExpectedNamespace) {
+ return ErrorBuilder::wrap(ExpectedNamespace.takeError())
+ .context(ErrorMessages::ReadingFromField, "NesteBuildNamespace",
+ "namespace")
.build();
+ }
return EntityName{*OptUSR, *OptSuffix, std::move(*ExpectedNamespace)};
}
@@ -427,37 +455,34 @@ JSONFormat::entityIdTableEntryFromJSON(
const llvm::json::Object *OptEntityNameObject =
EntityIdTableEntryObject.getObject("name");
if (!OptEntityNameObject) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntityIdTable entry")
- .context(ErrorMessages::MissingOrInvalidField, "name",
- "EntityName JSON object")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "EntityName", "name", "object")
.build();
}
auto ExpectedEntityName = entityNameFromJSON(*OptEntityNameObject);
- if (!ExpectedEntityName)
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntityIdTable entry")
- .context(ErrorMessages::FailedToReadJSONObjectField, "name")
- .cause(ExpectedEntityName.takeError())
+ if (!ExpectedEntityName) {
+ return ErrorBuilder::wrap(ExpectedEntityName.takeError())
+ .context(ErrorMessages::ReadingFromField, "EntityName", "name")
.build();
+ }
const llvm::json::Value *EntityIdIntValue =
EntityIdTableEntryObject.get("id");
if (!EntityIdIntValue) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntityIdTable entry")
- .context(ErrorMessages::MissingOrInvalidField, "id",
- "unsigned integer EntityId")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "EntityId", "id", "(unsigned 64-bit)")
.build();
}
const std::optional<uint64_t> OptEntityIdInt =
EntityIdIntValue->getAsUINT64();
if (!OptEntityIdInt) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntityIdTable entry")
- .context(ErrorMessages::InvalidUInt64Field, "id")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "EntityId", "id", "integer (unsigned 64-bit)")
.build();
}
@@ -489,31 +514,28 @@ llvm::Expected<EntityIdTable> JSONFormat::entityIdTableFromJSON(
const llvm::json::Object *OptEntityIdTableEntryObject =
EntityIdTableEntryValue.getAsObject();
-
if (!OptEntityIdTableEntryObject) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntityIdTable")
- .context(ErrorMessages::ElementNotObject, Index,
- "EntityIdTable entry with 'id' and 'name' fields")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtIndex,
+ "EntityIdTable entry", Index, "object")
.build();
}
auto ExpectedEntityIdTableEntry =
entityIdTableEntryFromJSON(*OptEntityIdTableEntryObject);
if (!ExpectedEntityIdTableEntry)
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntityIdTable")
- .context(ErrorMessages::AtIndex, Index)
- .cause(ExpectedEntityIdTableEntry.takeError())
+ return ErrorBuilder::wrap(ExpectedEntityIdTableEntry.takeError())
+ .context(ErrorMessages::ReadingFromIndex, "EntityIdTable entry",
+ Index)
.build();
auto [EntityIt, EntityInserted] =
Entities.emplace(std::move(*ExpectedEntityIdTableEntry));
if (!EntityInserted) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntityIdTable")
- .context(ErrorMessages::DuplicateWithExistingId, "EntityName", Index,
- getEntityIdIndex(EntityIt->second))
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedInsertionOnDuplication,
+ "EntityIdTable entry", Index, "EntityId",
+ getEntityIdIndex(EntityIt->second))
.build();
}
}
@@ -545,11 +567,12 @@ JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
EntityIdTable &IdTable) const {
auto InfoIt = FormatInfos.find(SN);
if (InfoIt == FormatInfos.end()) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntitySummary")
- .context(ErrorMessages::NoFormatInfoForSummaryName, SN.str().data())
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToDeserializeEntitySummary,
+ SN.str())
.build();
}
+
const auto &InfoEntry = InfoIt->second;
assert(InfoEntry.ForSummary == SN);
@@ -557,16 +580,17 @@ JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
return InfoEntry.Deserialize(EntitySummaryObject, IdTable, Converter);
}
-llvm::json::Object
+llvm::Expected<llvm::json::Object>
JSONFormat::entitySummaryToJSON(const SummaryName &SN,
const EntitySummary &ES) const {
auto InfoIt = FormatInfos.find(SN);
if (InfoIt == FormatInfos.end()) {
- llvm::report_fatal_error(
- "Failed to serialize EntitySummary: no FormatInfo was registered for "
- "summary name: " +
- SN.str());
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToSerializeEntitySummary,
+ SN.str())
+ .build();
}
+
const auto &InfoEntry = InfoIt->second;
assert(InfoEntry.ForSummary == SN);
@@ -586,19 +610,18 @@ JSONFormat::entityDataMapEntryFromJSON(
const llvm::json::Value *EntityIdIntValue =
EntityDataMapEntryObject.get("entity_id");
if (!EntityIdIntValue) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntityDataMap entry")
- .context(ErrorMessages::MissingOrInvalidField, "entity_id",
- "unsigned integer EntityId")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "EntityId", "entity_id", "integer")
.build();
}
const std::optional<uint64_t> OptEntityIdInt =
EntityIdIntValue->getAsUINT64();
if (!OptEntityIdInt) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntityDataMap entry")
- .context(ErrorMessages::InvalidUInt64Field, "entity_id")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "EntityId", "entity_id", "integer")
.build();
}
@@ -607,21 +630,20 @@ JSONFormat::entityDataMapEntryFromJSON(
const llvm::json::Object *OptEntitySummaryObject =
EntityDataMapEntryObject.getObject("entity_summary");
if (!OptEntitySummaryObject) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntityDataMap entry")
- .context(ErrorMessages::MissingOrInvalidField, "entity_summary",
- "EntitySummary JSON object")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "EntitySummary", "entity_summary", "object")
.build();
}
auto ExpectedEntitySummary =
entitySummaryFromJSON(SN, *OptEntitySummaryObject, IdTable);
- if (!ExpectedEntitySummary)
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntityDataMap entry")
- .context(ErrorMessages::FailedToReadJSONObjectField, "entity_summary")
- .cause(ExpectedEntitySummary.takeError())
+ if (!ExpectedEntitySummary) {
+ return ErrorBuilder::wrap(ExpectedEntitySummary.takeError())
+ .context(ErrorMessages::ReadingFromField, "EntitySummary",
+ "entity_summary")
.build();
+ }
return std::make_pair(std::move(EI), std::move(*ExpectedEntitySummary));
}
@@ -642,30 +664,27 @@ JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
const llvm::json::Object *OptEntityDataMapEntryObject =
EntityDataMapEntryValue.getAsObject();
if (!OptEntityDataMapEntryObject) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntityDataMap")
- .context(ErrorMessages::ElementNotObject, Index,
- "EntityDataMap entry with 'entity_id' and 'entity_summary' "
- "fields")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtIndex,
+ "EntitySummary entry", Index, "object")
.build();
}
auto ExpectedEntityDataMapEntry =
entityDataMapEntryFromJSON(*OptEntityDataMapEntryObject, SN, IdTable);
if (!ExpectedEntityDataMapEntry)
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntityDataMap")
- .context(ErrorMessages::AtIndex, Index)
- .cause(ExpectedEntityDataMapEntry.takeError())
+ return ErrorBuilder::wrap(ExpectedEntityDataMapEntry.takeError())
+ .context(ErrorMessages::ReadingFromIndex, "EntitySummary entry",
+ Index)
.build();
auto [DataIt, DataInserted] =
EntityDataMap.insert(std::move(*ExpectedEntityDataMapEntry));
if (!DataInserted) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "EntityDataMap")
- .context(ErrorMessages::DuplicateEntityIdAtIndex,
- getEntityIdIndex(DataIt->first), Index)
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedInsertionOnDuplication,
+ "EntitySummary entry", Index, "EntityId",
+ getEntityIdIndex(DataIt->first))
.build();
}
}
@@ -673,17 +692,31 @@ JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
return EntityDataMap;
}
-llvm::json::Array JSONFormat::entityDataMapToJSON(
+llvm::Expected<llvm::json::Array> JSONFormat::entityDataMapToJSON(
const SummaryName &SN,
const std::map<EntityId, std::unique_ptr<EntitySummary>> &EntityDataMap)
const {
llvm::json::Array Result;
Result.reserve(EntityDataMap.size());
+
+ size_t Index = 0;
for (const auto &[EntityId, EntitySummary] : EntityDataMap) {
llvm::json::Object Entry;
+
Entry["entity_id"] = entityIdToJSON(EntityId);
- Entry["entity_summary"] = entitySummaryToJSON(SN, *EntitySummary);
+
+ auto ExpectedEntitySummaryObject = entitySummaryToJSON(SN, *EntitySummary);
+ if (!ExpectedEntitySummaryObject) {
+ return ErrorBuilder::wrap(ExpectedEntitySummaryObject.takeError())
+ .context(ErrorMessages::WritingToIndex, "EntitySummary entry", Index)
+ .build();
+ }
+
+ Entry["entity_summary"] = std::move(*ExpectedEntitySummaryObject);
+
Result.push_back(std::move(Entry));
+
+ ++Index;
}
return Result;
}
@@ -700,12 +733,10 @@ JSONFormat::summaryDataMapEntryFromJSON(
std::optional<llvm::StringRef> OptSummaryNameStr =
SummaryDataMapEntryObject.getString("summary_name");
-
if (!OptSummaryNameStr) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap entry")
- .context(ErrorMessages::MissingOrInvalidField, "summary_name",
- "string (analysis summary identifier)")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "SummaryName", "summary_name", "string")
.build();
}
@@ -714,32 +745,41 @@ JSONFormat::summaryDataMapEntryFromJSON(
const llvm::json::Array *OptEntityDataArray =
SummaryDataMapEntryObject.getArray("summary_data");
if (!OptEntityDataArray) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap entry")
- .context(ErrorMessages::MissingOrInvalidField, "summary_data",
- "JSON array of entity data entries")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "EntitySummary entries", "summary_data",
+ "array")
.build();
}
auto ExpectedEntityDataMap =
entityDataMapFromJSON(SN, *OptEntityDataArray, IdTable);
if (!ExpectedEntityDataMap)
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap entry")
- .context(ErrorMessages::ForSummary, SN.str().data())
- .context(ErrorMessages::FailedToReadJSONArray, "summary_data")
- .cause(ExpectedEntityDataMap.takeError())
+ return ErrorBuilder::wrap(ExpectedEntityDataMap.takeError())
+ .context(ErrorMessages::ReadingFromField, "EntitySummary entries",
+ "summary_data")
.build();
return std::make_pair(std::move(SN), std::move(*ExpectedEntityDataMap));
}
-llvm::json::Object JSONFormat::summaryDataMapEntryToJSON(
+llvm::Expected<llvm::json::Object> JSONFormat::summaryDataMapEntryToJSON(
const SummaryName &SN,
const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const {
llvm::json::Object Result;
+
Result["summary_name"] = summaryNameToJSON(SN);
- Result["summary_data"] = entityDataMapToJSON(SN, SD);
+
+ auto ExpectedSummaryDataArray = entityDataMapToJSON(SN, SD);
+ if (!ExpectedSummaryDataArray) {
+ return ErrorBuilder::wrap(ExpectedSummaryDataArray.takeError())
+ .context(ErrorMessages::WritingToField, "EntitySummary entries",
+ "summary_data")
+ .build();
+ }
+
+ Result["summary_data"] = std::move(*ExpectedSummaryDataArray);
+
return Result;
}
@@ -760,30 +800,27 @@ JSONFormat::summaryDataMapFromJSON(const llvm::json::Array &SummaryDataArray,
const llvm::json::Object *OptSummaryDataMapEntryObject =
SummaryDataMapEntryValue.getAsObject();
if (!OptSummaryDataMapEntryObject) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap")
- .context(ErrorMessages::ElementNotObject, Index,
- "SummaryDataMap entry with 'summary_name' and "
- "'summary_data' fields")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtIndex,
+ "SummaryData entry", Index, "object")
.build();
}
auto ExpectedSummaryDataMapEntry =
summaryDataMapEntryFromJSON(*OptSummaryDataMapEntryObject, IdTable);
- if (!ExpectedSummaryDataMapEntry)
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap")
- .context(ErrorMessages::AtIndex, Index)
- .cause(ExpectedSummaryDataMapEntry.takeError())
+ if (!ExpectedSummaryDataMapEntry) {
+ return ErrorBuilder::wrap(ExpectedSummaryDataMapEntry.takeError())
+ .context(ErrorMessages::ReadingFromIndex, "SummaryData entry", Index)
.build();
+ }
auto [SummaryIt, SummaryInserted] =
SummaryDataMap.emplace(std::move(*ExpectedSummaryDataMapEntry));
if (!SummaryInserted) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::FailedToDeserialize, "SummaryDataMap")
- .context(ErrorMessages::DuplicateAtIndex, "SummaryName",
- SummaryIt->first.str().data(), Index)
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedInsertionOnDuplication,
+ "SummaryData entry", Index, "SummaryName",
+ SummaryIt->first.str())
.build();
}
}
@@ -791,15 +828,29 @@ JSONFormat::summaryDataMapFromJSON(const llvm::json::Array &SummaryDataArray,
return SummaryDataMap;
}
-llvm::json::Array JSONFormat::summaryDataMapToJSON(
+llvm::Expected<llvm::json::Array> JSONFormat::summaryDataMapToJSON(
const std::map<SummaryName,
std::map<EntityId, std::unique_ptr<EntitySummary>>>
&SummaryDataMap) const {
llvm::json::Array Result;
Result.reserve(SummaryDataMap.size());
+
+ size_t Index = 0;
for (const auto &[SummaryName, DataMap] : SummaryDataMap) {
- Result.push_back(summaryDataMapEntryToJSON(SummaryName, DataMap));
+
+ auto ExpectedSummaryDataMapObject =
+ summaryDataMapEntryToJSON(SummaryName, DataMap);
+ if (!ExpectedSummaryDataMapObject) {
+ return ErrorBuilder::wrap(ExpectedSummaryDataMapObject.takeError())
+ .context(ErrorMessages::ReadingFromIndex, "SummaryData entry", Index)
+ .build();
+ }
+
+ Result.push_back(std::move(*ExpectedSummaryDataMapObject));
+
+ ++Index;
}
+
return Result;
}
@@ -808,75 +859,86 @@ llvm::json::Array JSONFormat::summaryDataMapToJSON(
//----------------------------------------------------------------------------
llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
- auto ExpectedRootObject = readJSONObject(Path);
- if (!ExpectedRootObject)
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
- .cause(ExpectedRootObject.takeError())
+
+ auto ExpectedJSON = readJSON(Path);
+ if (!ExpectedJSON) {
+ return ErrorBuilder::wrap(ExpectedJSON.takeError())
+ .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
+ .build();
+ }
+
+ llvm::json::Object *RootObjectPtr = ExpectedJSON->getAsObject();
+ if (!RootObjectPtr) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObject, "TUSummary",
+ "object")
+ .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
.build();
+ }
- const llvm::json::Object &RootObject = *ExpectedRootObject;
+ const llvm::json::Object &RootObject = *RootObjectPtr;
- // Parse TUNamespace field
const llvm::json::Object *TUNamespaceObject =
RootObject.getObject("tu_namespace");
if (!TUNamespaceObject) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
- .context(ErrorMessages::MissingOrInvalidField, "tu_namespace",
- "JSON object")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "BuildNamespace", "tu_namespace", "object")
+ .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
.build();
}
auto ExpectedTUNamespace = buildNamespaceFromJSON(*TUNamespaceObject);
- if (!ExpectedTUNamespace)
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
- .cause(ExpectedTUNamespace.takeError())
+ if (!ExpectedTUNamespace) {
+ return ErrorBuilder::wrap(ExpectedTUNamespace.takeError())
+ .context(ErrorMessages::ReadingFromField, "BuildNamespace",
+ "tu_namespace")
+ .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
.build();
+ }
TUSummary Summary(std::move(*ExpectedTUNamespace));
- // Parse IdTable field
{
const llvm::json::Array *IdTableArray = RootObject.getArray("id_table");
if (!IdTableArray) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
- .context(ErrorMessages::MissingOrInvalidField, "id_table",
- "JSON array")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "IdTable", "id_table", "array")
+ .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
.build();
}
auto ExpectedIdTable = entityIdTableFromJSON(*IdTableArray);
- if (!ExpectedIdTable)
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
- .context(ErrorMessages::FailedToReadJSONArray, "id_table")
- .cause(ExpectedIdTable.takeError())
+ if (!ExpectedIdTable) {
+ return ErrorBuilder::wrap(ExpectedIdTable.takeError())
+ .context(ErrorMessages::ReadingFromField, "IdTable", "id_table")
+ .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
.build();
+ }
getIdTable(Summary) = std::move(*ExpectedIdTable);
}
- // Parse Data field
{
const llvm::json::Array *SummaryDataArray = RootObject.getArray("data");
if (!SummaryDataArray) {
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
- .context(ErrorMessages::MissingOrInvalidField, "data", "JSON array")
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "SummaryData entries", "data", "array")
+ .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
.build();
}
auto ExpectedSummaryDataMap =
summaryDataMapFromJSON(*SummaryDataArray, getIdTable(Summary));
- if (!ExpectedSummaryDataMap)
- return ErrorBuilder(std::errc::invalid_argument)
- .context(ErrorMessages::ReadingTUSummaryFrom, Path.str().c_str())
- .context(ErrorMessages::FailedToReadJSONArray, "data")
- .cause(ExpectedSummaryDataMap.takeError())
+ if (!ExpectedSummaryDataMap) {
+ return ErrorBuilder::wrap(ExpectedSummaryDataMap.takeError())
+ .context(ErrorMessages::ReadingFromField, "SummaryData entries",
+ "data")
+ .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
.build();
+ }
getData(Summary) = std::move(*ExpectedSummaryDataMap);
}
@@ -892,12 +954,18 @@ llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
RootObject["id_table"] = entityIdTableToJSON(getIdTable(S));
- RootObject["data"] = summaryDataMapToJSON(getData(S));
+ auto ExpectedDataObject = summaryDataMapToJSON(getData(S));
+ if (!ExpectedDataObject) {
+ return ErrorBuilder::wrap(ExpectedDataObject.takeError())
+ .context(ErrorMessages::WritingToFile, "TUSummary", Path)
+ .build();
+ }
+
+ RootObject["data"] = std::move(*ExpectedDataObject);
if (auto Error = writeJSON(std::move(RootObject), Path)) {
- return ErrorBuilder(std::errc::io_error)
- .context(ErrorMessages::WritingTUSummaryTo, Path.str().c_str())
- .cause(std::move(Error))
+ return ErrorBuilder::wrap(std::move(Error))
+ .context(ErrorMessages::WritingToFile, "TUSummary", Path)
.build();
}
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
index 567f294700d9e..420ee06c37b7d 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
@@ -10,6 +10,7 @@
//
//===----------------------------------------------------------------------===//
+
#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
@@ -21,6 +22,7 @@
#include "llvm/Support/Registry.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/Support/raw_ostream.h"
+#include "llvm/Testing/Support/Error.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <memory>
@@ -34,68 +36,6 @@ using ::testing::HasSubstr;
namespace {
-// ============================================================================
-// Custom Matchers
-// ============================================================================
-
-// Helper to check if an Error or Expected succeeded
-template <typename T> struct SuccessChecker {
- static bool isSuccess(T &val) {
- // For Expected<U>
- return static_cast<bool>(val);
- }
- static std::string getError(T &val) { return toString(val.takeError()); }
-};
-
-// Specialization for Error type
-template <> struct SuccessChecker<Error> {
- static bool isSuccess(Error &val) {
- // For Error, success means no error (false/empty)
- return !static_cast<bool>(val);
- }
- static std::string getError(Error &val) { return toString(std::move(val)); }
-};
-
-// Matcher for Expected<T> or Error success
-MATCHER(Succeeded, "") {
- // Cast away constness to get mutable access
- auto &mutable_arg =
- const_cast<std::remove_const_t<std::remove_reference_t<decltype(arg)>> &>(
- arg);
-
- using ArgType = std::remove_const_t<std::remove_reference_t<decltype(arg)>>;
-
- if (!SuccessChecker<ArgType>::isSuccess(mutable_arg)) {
- *result_listener << "Operation failed with error: "
- << SuccessChecker<ArgType>::getError(mutable_arg);
- return false;
- }
- return true;
-}
-
-// Matcher for Expected<T> or Error failure with specific error message
-MATCHER_P(FailedWith, SubstrMatcher, "") {
- // Cast away constness to get mutable access
- auto &mutable_arg =
- const_cast<std::remove_const_t<std::remove_reference_t<decltype(arg)>> &>(
- arg);
-
- using ArgType = std::remove_const_t<std::remove_reference_t<decltype(arg)>>;
-
- if (SuccessChecker<ArgType>::isSuccess(mutable_arg)) {
- *result_listener << "Expected operation to fail, but it succeeded";
- return false;
- }
-
- std::string ErrorMsg = SuccessChecker<ArgType>::getError(mutable_arg);
-
- if (!::testing::Matches(SubstrMatcher)(ErrorMsg)) {
- *result_listener << "Error message was: " << ErrorMsg;
- return false;
- }
- return true;
-}
-
// ============================================================================
// Test Analysis - Simple analysis for testing JSON serialization
// ============================================================================
@@ -126,7 +66,7 @@ deserializeTestAnalysis(const json::Object &Obj, EntityIdTable &IdTable,
const json::Array *PairsArray = Obj.getArray("pairs");
if (!PairsArray)
return createStringError(inconvertibleErrorCode(),
- "missing required field 'pairs'");
+ "missing or invalid field 'pairs'");
for (size_t I = 0; I < PairsArray->size(); ++I) {
const json::Object *Pair = (*PairsArray)[I].getAsObject();
if (!Pair)
@@ -136,12 +76,12 @@ deserializeTestAnalysis(const json::Object &Obj, EntityIdTable &IdTable,
auto FirstOpt = Pair->getInteger("first");
if (!FirstOpt)
return createStringError(inconvertibleErrorCode(),
- "missing or invalid 'first' field at index %zu",
+ "missing or invalid 'first' field at index '%zu'",
I);
auto SecondOpt = Pair->getInteger("second");
if (!SecondOpt)
return createStringError(inconvertibleErrorCode(),
- "missing or invalid 'second' field at index %zu",
+ "missing or invalid 'second' field at index '%zu'",
I);
Result->Pairs.emplace_back(Converter.fromJSON(*FirstOpt),
Converter.fromJSON(*SecondOpt));
@@ -175,7 +115,8 @@ class JSONFormatTest : public ::testing::Test {
void TearDown() override { sys::fs::remove_directories(TestDir); }
- auto readJSON(StringRef JSON, StringRef Filename = "test.json") {
+ llvm::Expected<TUSummary> readJSON(StringRef JSON,
+ StringRef Filename = "test.json") {
SmallString<128> FilePath = TestDir;
sys::path::append(FilePath, Filename);
@@ -185,27 +126,53 @@ class JSONFormatTest : public ::testing::Test {
OS << JSON;
OS.close();
- return JSONFormat(vfs::getRealFileSystem()).readTUSummary(FilePath);
+ auto Result = JSONFormat(vfs::getRealFileSystem()).readTUSummary(FilePath);
+ if (!Result) {
+ std::string Message = llvm::toString(Result.takeError());
+ llvm::outs() << Message << "\n\n";
+ return {llvm::createStringError(std::move(Message))};
+ }
+ return Result;
}
- void testRoundTrip(StringRef InputJSON) {
- auto Summary = readJSON(InputJSON, "input.json");
- ASSERT_THAT(Summary, Succeeded());
+ void readWriteJSON(StringRef InputJSON) {
+ // Read the input JSON
+ auto Summary1 = readJSON(InputJSON, "input.json");
+ ASSERT_THAT_EXPECTED(Summary1, Succeeded());
+
+ // Write to first output file
+ SmallString<128> Output1Path = TestDir;
+ sys::path::append(Output1Path, "output1.json");
- SmallString<128> OutputPath = TestDir;
- sys::path::append(OutputPath, "output.json");
+ JSONFormat Format(vfs::getRealFileSystem());
+ auto WriteErr1 = Format.writeTUSummary(*Summary1, Output1Path);
+ ASSERT_THAT_ERROR(std::move(WriteErr1), Succeeded());
- JSONFormat OutputFormat(vfs::getRealFileSystem());
- auto WriteErr = OutputFormat.writeTUSummary(*Summary, OutputPath);
- ASSERT_THAT(WriteErr, Succeeded());
+ // Read back from first output
+ auto Summary2 = Format.readTUSummary(Output1Path);
+ ASSERT_THAT_EXPECTED(Summary2, Succeeded());
- auto RoundTrip = OutputFormat.readTUSummary(OutputPath);
- ASSERT_THAT(RoundTrip, Succeeded());
+ // Write to second output file
+ SmallString<128> Output2Path = TestDir;
+ sys::path::append(Output2Path, "output2.json");
+
+ auto WriteErr2 = Format.writeTUSummary(*Summary2, Output2Path);
+ ASSERT_THAT_ERROR(std::move(WriteErr2), Succeeded());
+
+ // Compare the two output files byte-by-byte
+ auto Buffer1 = MemoryBuffer::getFile(Output1Path);
+ ASSERT_TRUE(Buffer1) << "Failed to read output1.json";
+
+ auto Buffer2 = MemoryBuffer::getFile(Output2Path);
+ ASSERT_TRUE(Buffer2) << "Failed to read output2.json";
+
+ EXPECT_EQ(Buffer1.get()->getBuffer(), Buffer2.get()->getBuffer())
+ << "Serialization is not stable: first write differs from second write";
}
};
// ============================================================================
-// File Access Error Tests
+// readJSON() Error Tests
// ============================================================================
TEST_F(JSONFormatTest, NonexistentFile) {
@@ -215,8 +182,9 @@ TEST_F(JSONFormatTest, NonexistentFile) {
JSONFormat Format(vfs::getRealFileSystem());
auto Result = Format.readTUSummary(NonexistentPath);
- EXPECT_THAT(Result, FailedWith(AllOf(HasSubstr("reading TUSummary from"),
- HasSubstr("file does not exist"))));
+ EXPECT_THAT_EXPECTED(
+ Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("file does not exist"))));
}
TEST_F(JSONFormatTest, PathIsDirectory) {
@@ -229,53 +197,47 @@ TEST_F(JSONFormatTest, PathIsDirectory) {
JSONFormat Format(vfs::getRealFileSystem());
auto Result = Format.readTUSummary(DirPath);
- EXPECT_THAT(Result,
- FailedWith(AllOf(HasSubstr("reading TUSummary from"),
- HasSubstr("path is a directory, not a file"))));
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(HasSubstr("reading TUSummary from"),
+ HasSubstr("path is a directory, not a file"))));
}
-TEST_F(JSONFormatTest, BrokenSymlink) {
-#ifdef _WIN32
- GTEST_SKIP() << "Symlink test skipped on Windows";
-#else
- SmallString<128> SymlinkPath = TestDir;
- sys::path::append(SymlinkPath, "symlink.json");
+TEST_F(JSONFormatTest, NotJsonExtension) {
+ auto Result = readJSON("{}", "test.txt");
- // Create a symlink to a non-existent file
- SmallString<128> NonexistentTarget = TestDir;
- sys::path::append(NonexistentTarget, "does_not_exist.json");
+ EXPECT_THAT_EXPECTED(
+ Result, FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("failed to read file"),
+ HasSubstr("file does not end with '.json' extension"))));
+}
- std::error_code EC = sys::fs::create_link(NonexistentTarget, SymlinkPath);
- if (EC) {
- GTEST_SKIP() << "Failed to create symlink (may need elevated privileges): "
- << EC.message();
- }
+TEST_F(JSONFormatTest, BrokenSymlink) {
+ SmallString<128> TargetPath = TestDir;
+ sys::path::append(TargetPath, "nonexistent_target.json");
- // Verify the symlink points to a non-existent file by checking file status
- sys::fs::file_status Status;
- EC = sys::fs::status(SymlinkPath, Status);
- if (!EC) {
- // If status succeeds, the target exists - skip test
- GTEST_SKIP() << "Symlink unexpectedly points to existing file";
- }
+ SmallString<128> SymlinkPath = TestDir;
+ sys::path::append(SymlinkPath, "broken_symlink.json");
+
+ // Create a symlink pointing to a non-existent file
+ std::error_code EC = sys::fs::create_link(TargetPath, SymlinkPath);
+ ASSERT_FALSE(EC) << "Failed to create symlink: " << EC.message();
JSONFormat Format(vfs::getRealFileSystem());
auto Result = Format.readTUSummary(SymlinkPath);
- EXPECT_THAT(Result, FailedWith(AllOf(HasSubstr("reading TUSummary from"),
- HasSubstr("file does not exist"))));
-#endif
+ EXPECT_THAT_EXPECTED(
+ Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from file"),
+ HasSubstr("failed to read file"))));
}
-TEST_F(JSONFormatTest, FileWithoutReadPermission) {
-#ifdef _WIN32
- GTEST_SKIP() << "Permission test skipped on Windows (uses different ACL "
- "model)";
-#else
+TEST_F(JSONFormatTest, NoReadPermission) {
+#ifndef _WIN32 // Skip on Windows as permission model is different
SmallString<128> FilePath = TestDir;
- sys::path::append(FilePath, "no_read.json");
+ sys::path::append(FilePath, "no_read_permission.json");
- // Create a file with valid JSON content
+ // Create file with valid JSON
std::error_code EC;
raw_fd_ostream OS(FilePath, EC);
ASSERT_FALSE(EC) << "Failed to create file: " << EC.message();
@@ -289,72 +251,69 @@ TEST_F(JSONFormatTest, FileWithoutReadPermission) {
})";
OS.close();
- // Remove read permissions (chmod 000)
- auto Perms = sys::fs::perms::all_all;
+ // Remove read permissions
+ sys::fs::perms Perms =
+ sys::fs::perms::owner_write | sys::fs::perms::owner_exe;
EC = sys::fs::setPermissions(FilePath, Perms);
ASSERT_FALSE(EC) << "Failed to set permissions: " << EC.message();
- // Now remove all permissions
- EC = sys::fs::setPermissions(FilePath, static_cast<sys::fs::perms>(0));
- if (EC) {
- GTEST_SKIP() << "Failed to remove permissions (may be running as root): "
- << EC.message();
- }
-
JSONFormat Format(vfs::getRealFileSystem());
auto Result = Format.readTUSummary(FilePath);
+ EXPECT_THAT_EXPECTED(
+ Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from file"),
+ HasSubstr("failed to read file"))));
+
// Restore permissions for cleanup
sys::fs::setPermissions(FilePath, sys::fs::perms::all_all);
-
- EXPECT_THAT(Result, FailedWith(AllOf(HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read file"))));
#endif
}
-TEST_F(JSONFormatTest, NotJsonExtension) {
- auto Result = readJSON("{}", "test.txt");
-
- EXPECT_THAT(Result, FailedWith(AllOf(HasSubstr("reading TUSummary from"),
- HasSubstr("not a JSON file"))));
-}
-
-// ============================================================================
-// JSON Syntax Error Tests
-// ============================================================================
-
TEST_F(JSONFormatTest, InvalidSyntax) {
auto Result = readJSON("{ invalid json }");
- EXPECT_THAT(Result, FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read JSON object from file"))));
+ EXPECT_THAT_EXPECTED(
+ Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from file"),
+ HasSubstr("Expected object key"))));
}
TEST_F(JSONFormatTest, NotObject) {
auto Result = readJSON("[]");
- EXPECT_THAT(Result, FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read JSON object from file"))));
+ EXPECT_THAT_EXPECTED(
+ Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from file"),
+ HasSubstr("failed to read TUSummary"),
+ HasSubstr("expected JSON object"))));
}
// ============================================================================
-// Root Structure Error Tests
+// JSONFormat::buildNamespaceKindFromJSON() Error Tests
// ============================================================================
-TEST_F(JSONFormatTest, MissingTUNamespace) {
+TEST_F(JSONFormatTest, InvalidKind) {
auto Result = readJSON(R"({
+ "tu_namespace": {
+ "kind": "invalid_kind",
+ "name": "test.cpp"
+ },
"id_table": [],
"data": []
})");
- EXPECT_THAT(
+ EXPECT_THAT_EXPECTED(
Result,
- FailedWith(AllOf(HasSubstr("reading TUSummary from"),
- HasSubstr("missing or invalid field 'tu_namespace'"))));
+ FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading BuildNamespace from field 'tu_namespace'"),
+ HasSubstr("reading BuildNamespaceKind from field 'kind'"),
+ HasSubstr(
+ "invalid 'kind' BuildNamespaceKind value 'invalid_kind'"))));
}
+// ============================================================================
+// JSONFormat::buildNamespaceFromJSON() Error Tests
+// ============================================================================
+
TEST_F(JSONFormatTest, MissingKind) {
auto Result = readJSON(R"({
"tu_namespace": {
@@ -364,11 +323,13 @@ TEST_F(JSONFormatTest, MissingKind) {
"data": []
})");
- EXPECT_THAT(Result, FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to deserialize BuildNamespace"),
- HasSubstr("missing or invalid field 'kind' "
- "(expected BuildNamespaceKind)"))));
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading BuildNamespace from field 'tu_namespace'"),
+ HasSubstr("failed to read BuildNamespaceKind from field 'kind'"),
+ HasSubstr("expected JSON string"))));
}
TEST_F(JSONFormatTest, MissingName) {
@@ -380,98 +341,144 @@ TEST_F(JSONFormatTest, MissingName) {
"data": []
})");
- EXPECT_THAT(Result, FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to deserialize BuildNamespace"),
- HasSubstr("missing or invalid field 'name' "
- "(expected string)"))));
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading BuildNamespace from field 'tu_namespace'"),
+ HasSubstr("failed to read BuildNamespaceName from field 'name'"),
+ HasSubstr("expected JSON string"))));
}
-TEST_F(JSONFormatTest, InvalidKind) {
- auto Result = readJSON(R"({
- "tu_namespace": {
- "kind": "invalid_kind",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": []
- })");
-
- EXPECT_THAT(Result, FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to deserialize BuildNamespace"),
- HasSubstr("while parsing field 'kind'"),
- HasSubstr("invalid 'kind' BuildNamespaceKind "
- "value"))));
-}
+// ============================================================================
+// JSONFormat::nestedBuildNamespaceFromJSON() Error Tests
+// ============================================================================
-TEST_F(JSONFormatTest, MissingIDTable) {
+TEST_F(JSONFormatTest, NamespaceElementNotObject) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": ["invalid"]
+ }
+ }
+ ],
"data": []
})");
- EXPECT_THAT(Result, FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("missing or invalid field 'id_table'"))));
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(
+ AllOf(HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading IdTable from field 'id_table'"),
+ HasSubstr("reading EntityIdTable entry from index '0'"),
+ HasSubstr("reading EntityName from field 'name'"),
+ HasSubstr("reading NesteBuildNamespace from field 'namespace'"),
+ HasSubstr("failed to read BuildNamespace from index '0'"),
+ HasSubstr("expected JSON object"))));
}
-TEST_F(JSONFormatTest, MissingData) {
+// ============================================================================
+// JSONFormat::entityNameFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, EntityNameMissingUSR) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
- "id_table": []
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "namespace": []
+ }
+ }
+ ],
+ "data": []
})");
- EXPECT_THAT(Result,
- FailedWith(AllOf(HasSubstr("reading TUSummary from"),
- HasSubstr("missing or invalid field 'data'"))));
+ EXPECT_THAT_EXPECTED(
+ Result, FailedWithMessage(
+ AllOf(HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading IdTable from field 'id_table'"),
+ HasSubstr("reading EntityIdTable entry from index '0'"),
+ HasSubstr("reading EntityName from field 'name'"),
+ HasSubstr("failed to read USR from field 'usr'"),
+ HasSubstr("expected JSON string"))));
}
-// ============================================================================
-// ID Table Error Tests
-// ============================================================================
-
-TEST_F(JSONFormatTest, IDTableNotArray) {
+TEST_F(JSONFormatTest, EntityNameMissingSuffix) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
- "id_table": {},
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "namespace": []
+ }
+ }
+ ],
"data": []
})");
- EXPECT_THAT(Result, FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("missing or invalid field 'id_table'"))));
+ EXPECT_THAT_EXPECTED(
+ Result, FailedWithMessage(
+ AllOf(HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading IdTable from field 'id_table'"),
+ HasSubstr("reading EntityIdTable entry from index '0'"),
+ HasSubstr("reading EntityName from field 'name'"),
+ HasSubstr("failed to read Suffix from field 'suffix'"),
+ HasSubstr("expected JSON string"))));
}
-TEST_F(JSONFormatTest, IDTableElementNotObject) {
+TEST_F(JSONFormatTest, EntityNameMissingNamespace) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
- "id_table": [123],
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": ""
+ }
+ }
+ ],
"data": []
})");
- EXPECT_THAT(
+ EXPECT_THAT_EXPECTED(
Result,
- FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to deserialize EntityIdTable"),
- HasSubstr("element at index 0 is not a JSON object"),
+ FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading IdTable from field 'id_table'"),
+ HasSubstr("reading EntityIdTable entry from index '0'"),
+ HasSubstr("reading EntityName from field 'name'"),
HasSubstr(
- "(expected EntityIdTable entry with 'id' and 'name' fields)"))));
+ "failed to read NestedBuildNamespace from field 'namespace'"),
+ HasSubstr("expected JSON array"))));
}
+// ============================================================================
+// JSONFormat::entityIdTableEntryFromJSON() Error Tests
+// ============================================================================
+
TEST_F(JSONFormatTest, IDTableEntryMissingID) {
auto Result = readJSON(R"({
"tu_namespace": {
@@ -490,14 +497,13 @@ TEST_F(JSONFormatTest, IDTableEntryMissingID) {
"data": []
})");
- EXPECT_THAT(Result,
- FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read JSON array from field 'id_table'"),
- HasSubstr("failed to deserialize EntityIdTable at index 0"),
- HasSubstr("failed to deserialize EntityIdTable entry"),
- HasSubstr("missing or invalid field 'id' "
- "(expected unsigned integer EntityId)"))));
+ EXPECT_THAT_EXPECTED(
+ Result, FailedWithMessage(
+ AllOf(HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading IdTable from field 'id_table'"),
+ HasSubstr("reading EntityIdTable entry from index '0'"),
+ HasSubstr("failed to read EntityId from field 'id'"),
+ HasSubstr("expected JSON (unsigned 64-bit)"))));
}
TEST_F(JSONFormatTest, IDTableEntryMissingName) {
@@ -514,16 +520,13 @@ TEST_F(JSONFormatTest, IDTableEntryMissingName) {
"data": []
})");
- EXPECT_THAT(
- Result,
- FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read JSON array from field 'id_table'"),
- HasSubstr("failed to deserialize EntityIdTable at index 0"),
- HasSubstr("failed to deserialize EntityIdTable entry"),
- HasSubstr("failed to read JSON object from field 'name'"),
- HasSubstr("missing or invalid field 'name' (expected EntityName JSON "
- "object)"))));
+ EXPECT_THAT_EXPECTED(
+ Result, FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading IdTable from field 'id_table'"),
+ HasSubstr("reading EntityIdTable entry from index '0'"),
+ HasSubstr("failed to read EntityName from field 'name'"),
+ HasSubstr("expected JSON object"))));
}
TEST_F(JSONFormatTest, IDTableEntryIDNotUInt64) {
@@ -545,15 +548,53 @@ TEST_F(JSONFormatTest, IDTableEntryIDNotUInt64) {
"data": []
})");
- EXPECT_THAT(
+ EXPECT_THAT_EXPECTED(
+ Result, FailedWithMessage(
+ AllOf(HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading IdTable from field 'id_table'"),
+ HasSubstr("reading EntityIdTable entry from index '0'"),
+ HasSubstr("failed to read EntityId from field 'id'"),
+ HasSubstr("expected JSON integer (unsigned 64-bit)"))));
+}
+
+// ============================================================================
+// JSONFormat::entityIdTableFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, IDTableNotArray) {
+ auto Result = readJSON(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": {},
+ "data": []
+ })");
+
+ EXPECT_THAT_EXPECTED(
+ Result, FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("failed to read IdTable from field 'id_table'"),
+ HasSubstr("expected JSON array"))));
+}
+
+TEST_F(JSONFormatTest, IDTableElementNotObject) {
+ auto Result = readJSON(R"({
+ "tu_namespace": {
+ "kind": "compilation_unit",
+ "name": "test.cpp"
+ },
+ "id_table": [123],
+ "data": []
+ })");
+
+ EXPECT_THAT_EXPECTED(
Result,
- FailedWith(
- AllOf(HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read JSON array from field 'id_table'"),
- HasSubstr("failed to deserialize EntityIdTable at index 0"),
- HasSubstr("failed to deserialize EntityIdTable entry"),
- HasSubstr("field 'id' is not a valid unsigned 64-bit integer"),
- HasSubstr("(expected non-negative EntityId value)"))));
+ FailedWithMessage(
+ AllOf(HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading IdTable from field 'id_table'"),
+ HasSubstr("failed to read EntityIdTable entry from index '0'"),
+ HasSubstr("expected JSON object"))));
}
TEST_F(JSONFormatTest, DuplicateEntity) {
@@ -593,181 +634,191 @@ TEST_F(JSONFormatTest, DuplicateEntity) {
"data": []
})");
- EXPECT_THAT(Result, FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to deserialize EntityIdTable"),
- HasSubstr("duplicate EntityName found at index"),
- HasSubstr("(EntityId=0 already exists in table)"))));
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(
+ AllOf(HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading IdTable from field 'id_table'"),
+ HasSubstr("failed to insert EntityIdTable entry at index '1'"),
+ HasSubstr("encountered duplicate EntityId '0'"))));
}
// ============================================================================
-// Entity Name Error Tests
+// JSONFormat::entitySummaryFromJSON() Error Tests
// ============================================================================
-TEST_F(JSONFormatTest, EntityNameMissingUSR) {
+TEST_F(JSONFormatTest, EntitySummaryNoFormatInfo) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
- "id_table": [
+ "id_table": [],
+ "data": [
{
- "id": 0,
- "name": {
- "suffix": "",
- "namespace": []
- }
+ "summary_name": "unknown_summary_type",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {}
+ }
+ ]
}
- ],
- "data": []
+ ]
})");
- EXPECT_THAT(Result,
- FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read JSON array from field 'id_table'"),
- HasSubstr("failed to deserialize EntityIdTable at index 0"),
- HasSubstr("failed to deserialize EntityIdTable entry"),
- HasSubstr("failed to read JSON object from field 'name'"),
- HasSubstr("failed to deserialize EntityName"),
- HasSubstr("missing or invalid field 'usr' "
- "(expected string (Unified Symbol Resolution))"))));
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading SummaryData entries from field 'data'"),
+ HasSubstr("reading SummaryData entry from index '0'"),
+ HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+ HasSubstr("reading EntitySummary entry from index '0'"),
+ HasSubstr("reading EntitySummary from field 'entity_summary'"),
+ HasSubstr("failed to deserialize EntitySummary"),
+ HasSubstr(
+ "no FormatInfo registered for summary 'unknown_summary_type'"))));
}
-TEST_F(JSONFormatTest, EntityNameMissingSuffix) {
+TEST_F(JSONFormatTest, TestAnalysisMissingField) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
- "id_table": [
+ "id_table": [],
+ "data": [
{
- "id": 0,
- "name": {
- "usr": "c:@F at foo",
- "namespace": []
- }
+ "summary_name": "test_summary",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {}
+ }
+ ]
}
- ],
- "data": []
+ ]
})");
- EXPECT_THAT(Result,
- FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read JSON array from field 'id_table'"),
- HasSubstr("failed to deserialize EntityIdTable at "
- "index 0"),
- HasSubstr("failed to deserialize EntityIdTable entry"),
- HasSubstr("failed to read JSON object from field 'name'"),
- HasSubstr("failed to deserialize EntityName"),
- HasSubstr("missing or invalid field 'suffix' "
- "(expected string)"))));
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading SummaryData entries from field 'data'"),
+ HasSubstr("reading SummaryData entry from index '0'"),
+ HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+ HasSubstr("reading EntitySummary entry from index '0'"),
+ HasSubstr("reading EntitySummary from field 'entity_summary'"),
+ HasSubstr("missing or invalid field 'pairs'"))));
}
-TEST_F(JSONFormatTest, EntityNameMissingNamespace) {
+TEST_F(JSONFormatTest, TestAnalysisInvalidPair) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
- "id_table": [
+ "id_table": [],
+ "data": [
{
- "id": 0,
- "name": {
- "usr": "c:@F at foo",
- "suffix": ""
- }
+ "summary_name": "test_summary",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "pairs": [
+ {
+ "first": 0,
+ "second": "not_a_number"
+ }
+ ]
+ }
+ }
+ ]
}
- ],
- "data": []
+ ]
})");
- EXPECT_THAT(
+ EXPECT_THAT_EXPECTED(
Result,
- FailedWith(
- AllOf(HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read JSON array from field 'id_table'"),
- HasSubstr("failed to deserialize EntityIdTable at index 0"),
- HasSubstr("failed to deserialize EntityIdTable entry"),
- HasSubstr("failed to read JSON object from field 'name'"),
- HasSubstr("failed to deserialize EntityName"),
- HasSubstr("failed to read JSON array from field 'namespace'"),
- HasSubstr("missing or invalid field 'namespace'"),
- HasSubstr("(expected JSON array of BuildNamespace objects)"))));
+ FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading SummaryData entries from field 'data'"),
+ HasSubstr("reading SummaryData entry from index '0'"),
+ HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+ HasSubstr("reading EntitySummary entry from index '0'"),
+ HasSubstr("reading EntitySummary from field 'entity_summary'"),
+ HasSubstr("missing or invalid 'second' field at index '0'"))));
}
-TEST_F(JSONFormatTest, NamespaceElementNotObject) {
+// ============================================================================
+// JSONFormat::entityDataMapEntryFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, EntityDataMissingEntityID) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
- "id_table": [
+ "id_table": [],
+ "data": [
{
- "id": 0,
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": ["invalid"]
- }
+ "summary_name": "test_summary",
+ "summary_data": [
+ {
+ "entity_summary": {}
+ }
+ ]
}
- ],
- "data": []
- })");
-
- EXPECT_THAT(Result,
- FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read JSON array from field 'id_table'"),
- HasSubstr("failed to deserialize EntityIdTable at index 0"),
- HasSubstr("failed to deserialize EntityIdTable entry"),
- HasSubstr("failed to read JSON object from field 'name'"),
- HasSubstr("failed to deserialize EntityName"),
- HasSubstr("failed to read JSON array from field 'namespace'"),
- HasSubstr("failed to deserialize NestedBuildNamespace"),
- HasSubstr("element at index 0 is not a JSON object"))));
-}
-
-// ============================================================================
-// Data Array Error Tests
-// ============================================================================
-
-TEST_F(JSONFormatTest, DataNotArray) {
- auto Result = readJSON(R"({
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": {}
+ ]
})");
- EXPECT_THAT(Result,
- FailedWith(AllOf(HasSubstr("reading TUSummary from"),
- HasSubstr("missing or invalid field 'data'"))));
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading SummaryData entries from field 'data'"),
+ HasSubstr("reading SummaryData entry from index '0'"),
+ HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+ HasSubstr("reading EntitySummary entry from index '0'"),
+ HasSubstr("failed to read EntityId from field 'entity_id'"),
+ HasSubstr("expected JSON integer"))));
}
-TEST_F(JSONFormatTest, DataElementNotObject) {
+TEST_F(JSONFormatTest, EntityDataMissingEntitySummary) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
"id_table": [],
- "data": ["invalid"]
+ "data": [
+ {
+ "summary_name": "test_summary",
+ "summary_data": [
+ {
+ "entity_id": 0
+ }
+ ]
+ }
+ ]
})");
- EXPECT_THAT(Result, FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to deserialize SummaryDataMap"),
- HasSubstr("element at index 0 is not a JSON object"),
- HasSubstr("(expected SummaryDataMap entry with "
- "'summary_name' and 'summary_data'"),
- HasSubstr("fields)"))));
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading SummaryData entries from field 'data'"),
+ HasSubstr("reading SummaryData entry from index '0'"),
+ HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+ HasSubstr("reading EntitySummary entry from index '0'"),
+ HasSubstr("failed to read EntitySummary from field 'entity_summary'"),
+ HasSubstr("expected JSON object"))));
}
-TEST_F(JSONFormatTest, DataEntryMissingSummaryName) {
+TEST_F(JSONFormatTest, EntityIDNotUInt64) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
@@ -776,24 +827,34 @@ TEST_F(JSONFormatTest, DataEntryMissingSummaryName) {
"id_table": [],
"data": [
{
- "summary_data": []
+ "summary_name": "test_summary",
+ "summary_data": [
+ {
+ "entity_id": "not_a_number",
+ "entity_summary": {}
+ }
+ ]
}
]
})");
- EXPECT_THAT(
+ EXPECT_THAT_EXPECTED(
Result,
- FailedWith(
- AllOf(HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read JSON array from field 'data'"),
- HasSubstr("failed to deserialize SummaryDataMap at "
- "index 0"),
- HasSubstr("failed to deserialize SummaryDataMap entry"),
- HasSubstr("missing or invalid field 'summary_name' "
- "(expected string (analysis summary identifier))"))));
+ FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading SummaryData entries from field 'data'"),
+ HasSubstr("reading SummaryData entry from index '0'"),
+ HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+ HasSubstr("reading EntitySummary entry from index '0'"),
+ HasSubstr("failed to read EntityId from field 'entity_id'"),
+ HasSubstr("expected JSON integer"))));
}
-TEST_F(JSONFormatTest, DataEntryMissingData) {
+// ============================================================================
+// JSONFormat::entityDataMapFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, EntityDataElementNotObject) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
@@ -802,51 +863,76 @@ TEST_F(JSONFormatTest, DataEntryMissingData) {
"id_table": [],
"data": [
{
- "summary_name": "test_summary"
+ "summary_name": "test_summary",
+ "summary_data": ["invalid"]
}
]
})");
- EXPECT_THAT(Result,
- FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read JSON array from field 'data'"),
- HasSubstr("failed to deserialize SummaryDataMap at index 0"),
- HasSubstr("failed to deserialize SummaryDataMap entry"),
- HasSubstr("missing or invalid field 'summary_data'"))));
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading SummaryData entries from field 'data'"),
+ HasSubstr("reading SummaryData entry from index '0'"),
+ HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+ HasSubstr("failed to read EntitySummary entry from index '0'"),
+ HasSubstr("expected JSON object"))));
}
-TEST_F(JSONFormatTest, DuplicateSummaryName) {
+TEST_F(JSONFormatTest, DuplicateEntityIdInDataMap) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
- "id_table": [],
- "data": [
+ "id_table": [
{
- "summary_name": "test_summary",
- "summary_data": []
- },
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": []
+ }
+ }
+ ],
+ "data": [
{
"summary_name": "test_summary",
- "summary_data": []
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "pairs": []
+ }
+ },
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "pairs": []
+ }
+ }
+ ]
}
]
})");
- EXPECT_THAT(Result, FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to deserialize SummaryDataMap"),
- HasSubstr("duplicate SummaryName 'test_summary' "
- "found at index"))));
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading SummaryData entries from field 'data'"),
+ HasSubstr("reading SummaryData entry from index '0'"),
+ HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+ HasSubstr("failed to insert EntitySummary entry at index '1'"),
+ HasSubstr("encountered duplicate EntityId '0'"))));
}
// ============================================================================
-// Entity Data Error Tests
+// JSONFormat::summaryDataMapEntryFromJSON() Error Tests
// ============================================================================
-TEST_F(JSONFormatTest, EntityDataElementNotObject) {
+TEST_F(JSONFormatTest, DataEntryMissingSummaryName) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
@@ -855,29 +941,22 @@ TEST_F(JSONFormatTest, EntityDataElementNotObject) {
"id_table": [],
"data": [
{
- "summary_name": "test_summary",
- "summary_data": ["invalid"]
+ "summary_data": []
}
]
})");
- EXPECT_THAT(
+ EXPECT_THAT_EXPECTED(
Result,
- FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read JSON array from field 'data'"),
- HasSubstr("failed to deserialize SummaryDataMap at index 0"),
- HasSubstr("failed to deserialize SummaryDataMap entry"),
- HasSubstr("for summary 'test_summary'"),
- HasSubstr("failed to read JSON array from field 'summary_data'"),
- HasSubstr("failed to deserialize EntityDataMap"),
- HasSubstr("element at index 0 is not a JSON object"),
- HasSubstr("(expected EntityDataMap entry with 'entity_id' and "
- "'entity_summary'"),
- HasSubstr("fields)"))));
+ FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading SummaryData entries from field 'data'"),
+ HasSubstr("reading SummaryData entry from index '0'"),
+ HasSubstr("failed to read SummaryName from field 'summary_name'"),
+ HasSubstr("expected JSON string"))));
}
-TEST_F(JSONFormatTest, EntityDataMissingEntityID) {
+TEST_F(JSONFormatTest, DataEntryMissingData) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
@@ -886,102 +965,63 @@ TEST_F(JSONFormatTest, EntityDataMissingEntityID) {
"id_table": [],
"data": [
{
- "summary_name": "test_summary",
- "summary_data": [
- {
- "entity_summary": {}
- }
- ]
+ "summary_name": "test_summary"
}
]
})");
- EXPECT_THAT(
+ EXPECT_THAT_EXPECTED(
Result,
- FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read JSON array from field 'data'"),
- HasSubstr("failed to deserialize SummaryDataMap at index 0"),
- HasSubstr("failed to deserialize SummaryDataMap entry"),
- HasSubstr("for summary 'test_summary'"),
- HasSubstr("failed to read JSON array from field 'summary_data'"),
- HasSubstr("failed to deserialize EntityDataMap at index 0"),
- HasSubstr("failed to deserialize EntityDataMap entry"),
- HasSubstr("missing or invalid field 'entity_id' "
- "(expected unsigned integer EntityId)"))));
+ FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading SummaryData entries from field 'data'"),
+ HasSubstr("reading SummaryData entry from index '0'"),
+ HasSubstr(
+ "failed to read EntitySummary entries from field 'summary_data'"),
+ HasSubstr("expected JSON array"))));
}
-TEST_F(JSONFormatTest, EntityDataMissingEntitySummary) {
+// ============================================================================
+// JSONFormat::summaryDataMapFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, DataNotArray) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
"id_table": [],
- "data": [
- {
- "summary_name": "test_summary",
- "summary_data": [
- {
- "entity_id": 0
- }
- ]
- }
- ]
+ "data": {}
})");
- EXPECT_THAT(
+ EXPECT_THAT_EXPECTED(
Result,
- FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read JSON array from field 'data'"),
- HasSubstr("failed to deserialize SummaryDataMap at index 0"),
- HasSubstr("failed to deserialize SummaryDataMap entry"),
- HasSubstr("for summary 'test_summary'"),
- HasSubstr("failed to read JSON array from field 'summary_data'"),
- HasSubstr("failed to deserialize EntityDataMap at index 0"),
- HasSubstr("failed to deserialize EntityDataMap entry"),
- HasSubstr("failed to read JSON object from field 'entity_summary'"),
- HasSubstr("missing or invalid field 'entity_summary'"),
- HasSubstr("(expected EntitySummary JSON object)"))));
+ FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("failed to read SummaryData entries from field 'data'"),
+ HasSubstr("expected JSON array"))));
}
-TEST_F(JSONFormatTest, EntityIDNotUInt64) {
+TEST_F(JSONFormatTest, DataElementNotObject) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
"id_table": [],
- "data": [
- {
- "summary_name": "test_summary",
- "summary_data": [
- {
- "entity_id": "not_a_number",
- "entity_summary": {}
- }
- ]
- }
- ]
+ "data": ["invalid"]
})");
- EXPECT_THAT(
- Result,
- FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read JSON array from field 'data'"),
- HasSubstr("failed to deserialize SummaryDataMap at index 0"),
- HasSubstr("failed to deserialize SummaryDataMap entry"),
- HasSubstr("for summary 'test_summary'"),
- HasSubstr("failed to read JSON array from field 'summary_data'"),
- HasSubstr("failed to deserialize EntityDataMap at index 0"),
- HasSubstr("failed to deserialize EntityDataMap entry"),
- HasSubstr("field 'entity_id' is not a valid unsigned 64-bit integer"),
- HasSubstr("(expected non-negative EntityId value)"))));
+ EXPECT_THAT_EXPECTED(
+ Result, FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading SummaryData entries from field 'data'"),
+ HasSubstr("failed to read SummaryData entry from index '0'"),
+ HasSubstr("expected JSON object"))));
}
-TEST_F(JSONFormatTest, EntitySummaryNoFormatInfo) {
+TEST_F(JSONFormatTest, DuplicateSummaryName) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
@@ -990,126 +1030,174 @@ TEST_F(JSONFormatTest, EntitySummaryNoFormatInfo) {
"id_table": [],
"data": [
{
- "summary_name": "unknown_summary_type",
- "summary_data": [
- {
- "entity_id": 0,
- "entity_summary": {}
- }
- ]
+ "summary_name": "test_summary",
+ "summary_data": []
+ },
+ {
+ "summary_name": "test_summary",
+ "summary_data": []
}
]
})");
- EXPECT_THAT(
+ EXPECT_THAT_EXPECTED(
Result,
- FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read JSON array from field 'data'"),
- HasSubstr("failed to deserialize SummaryDataMap at index 0"),
- HasSubstr("failed to deserialize SummaryDataMap entry"),
- HasSubstr("for summary 'unknown_summary_type'"),
- HasSubstr("failed to read JSON array from field 'summary_data'"),
- HasSubstr("failed to deserialize EntityDataMap at index 0"),
- HasSubstr("failed to deserialize EntityDataMap entry"),
- HasSubstr("failed to read JSON object from field 'entity_summary'"),
- HasSubstr("failed to deserialize EntitySummary"),
- HasSubstr("no FormatInfo was registered for summary name: "
- "unknown_summary_type"))));
+ FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("reading SummaryData entries from field 'data'"),
+ HasSubstr("failed to insert SummaryData entry at index '1'"),
+ HasSubstr("encountered duplicate SummaryName 'test_summary'"))));
}
// ============================================================================
-// Analysis-Specific Error Tests - TestAnalysis
+// JSONFormat::readTUSummary() Error Tests
// ============================================================================
-TEST_F(JSONFormatTest, TestAnalysisMissingField) {
+TEST_F(JSONFormatTest, MissingTUNamespace) {
+ auto Result = readJSON(R"({
+ "id_table": [],
+ "data": []
+ })");
+
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("failed to read BuildNamespace from field 'tu_namespace'"),
+ HasSubstr("expected JSON object"))));
+}
+
+TEST_F(JSONFormatTest, MissingIDTable) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
- "id_table": [],
- "data": [
- {
- "summary_name": "test_summary",
- "summary_data": [
- {
- "entity_id": 0,
- "entity_summary": {}
- }
- ]
- }
- ]
+ "data": []
})");
- EXPECT_THAT(
- Result,
- FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read JSON array from field 'data'"),
- HasSubstr("failed to deserialize SummaryDataMap at "
- "index 0"),
- HasSubstr("failed to deserialize SummaryDataMap entry"),
- HasSubstr("for summary 'test_summary'"),
- HasSubstr("failed to read JSON array from field 'summary_data'"),
- HasSubstr("failed to deserialize EntityDataMap at "
- "index 0"),
- HasSubstr("failed to deserialize EntityDataMap entry"),
- HasSubstr("failed to read JSON object from field 'entity_summary'"),
- HasSubstr("missing required field 'pairs'"))));
+ EXPECT_THAT_EXPECTED(
+ Result, FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("failed to read IdTable from field 'id_table'"),
+ HasSubstr("expected JSON array"))));
}
-TEST_F(JSONFormatTest, TestAnalysisInvalidPair) {
+TEST_F(JSONFormatTest, MissingData) {
auto Result = readJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
},
- "id_table": [],
- "data": [
- {
- "summary_name": "test_summary",
- "summary_data": [
- {
- "entity_id": 0,
- "entity_summary": {
- "pairs": [
- {
- "first": 0,
- "second": "not_a_number"
- }
- ]
- }
- }
- ]
- }
- ]
+ "id_table": []
})");
- EXPECT_THAT(
+ EXPECT_THAT_EXPECTED(
Result,
- FailedWith(AllOf(
- HasSubstr("reading TUSummary from"),
- HasSubstr("failed to read JSON array from field 'data'"),
- HasSubstr("failed to deserialize SummaryDataMap at "
- "index 0"),
- HasSubstr("failed to deserialize SummaryDataMap entry"),
- HasSubstr("for summary 'test_summary'"),
- HasSubstr("failed to read JSON array from field 'summary_data'"),
- HasSubstr("failed to deserialize EntityDataMap at "
- "index 0"),
- HasSubstr("failed to deserialize EntityDataMap entry"),
- HasSubstr("failed to read JSON object from field 'entity_summary'"),
- HasSubstr("missing or invalid 'second' field at "
- "index 0"))));
+ FailedWithMessage(AllOf(
+ HasSubstr("reading TUSummary from file"),
+ HasSubstr("failed to read SummaryData entries from field 'data'"),
+ HasSubstr("expected JSON array"))));
}
// ============================================================================
-// Valid Configuration Tests
+// JSONFormat::writeJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, WriteFileAlreadyExists) {
+ SmallString<128> FilePath = TestDir;
+ sys::path::append(FilePath, "existing.json");
+
+ // Create an existing file
+ std::error_code EC;
+ raw_fd_ostream OS(FilePath, EC);
+ ASSERT_FALSE(EC) << "Failed to create file: " << EC.message();
+ OS << "{}";
+ OS.close();
+
+ // Try to write to the same path
+ TUSummary Summary(
+ BuildNamespace(BuildNamespaceKind::CompilationUnit, "test.cpp"));
+ JSONFormat Format(vfs::getRealFileSystem());
+ auto Result = Format.writeTUSummary(Summary, FilePath);
+
+ EXPECT_THAT_ERROR(
+ std::move(Result),
+ FailedWithMessage(AllOf(HasSubstr("writing TUSummary to file"),
+ HasSubstr("failed to write file"),
+ HasSubstr("file already exists"))));
+}
+
+TEST_F(JSONFormatTest, WriteParentDirectoryNotFound) {
+ SmallString<128> FilePath = TestDir;
+ sys::path::append(FilePath, "nonexistent_dir", "test.json");
+
+ TUSummary Summary(
+ BuildNamespace(BuildNamespaceKind::CompilationUnit, "test.cpp"));
+ JSONFormat Format(vfs::getRealFileSystem());
+ auto Result = Format.writeTUSummary(Summary, FilePath);
+
+ EXPECT_THAT_ERROR(
+ std::move(Result),
+ FailedWithMessage(AllOf(HasSubstr("writing TUSummary to file"),
+ HasSubstr("failed to write file"),
+ HasSubstr("parent directory does not exist"))));
+}
+
+TEST_F(JSONFormatTest, WriteNotJsonExtension) {
+ SmallString<128> FilePath = TestDir;
+ sys::path::append(FilePath, "test.txt");
+
+ TUSummary Summary(
+ BuildNamespace(BuildNamespaceKind::CompilationUnit, "test.cpp"));
+ JSONFormat Format(vfs::getRealFileSystem());
+ auto Result = Format.writeTUSummary(Summary, FilePath);
+
+ EXPECT_THAT_ERROR(
+ std::move(Result),
+ FailedWithMessage(
+ AllOf(HasSubstr("writing TUSummary to file"),
+ HasSubstr("failed to write file"),
+ HasSubstr("file does not end with '.json' extension"))));
+}
+
+TEST_F(JSONFormatTest, WriteStreamOpenFailure) {
+#ifndef _WIN32 // Skip on Windows as permission model is different
+ SmallString<128> DirPath = TestDir;
+ sys::path::append(DirPath, "write_protected_dir");
+
+ // Create a directory without write permissions
+ std::error_code EC = sys::fs::create_directory(DirPath);
+ ASSERT_FALSE(EC) << "Failed to create directory: " << EC.message();
+
+ sys::fs::perms Perms = sys::fs::perms::owner_read | sys::fs::perms::owner_exe;
+ EC = sys::fs::setPermissions(DirPath, Perms);
+ ASSERT_FALSE(EC) << "Failed to set permissions: " << EC.message();
+
+ SmallString<128> FilePath = DirPath;
+ sys::path::append(FilePath, "test.json");
+
+ TUSummary Summary(
+ BuildNamespace(BuildNamespaceKind::CompilationUnit, "test.cpp"));
+ JSONFormat Format(vfs::getRealFileSystem());
+ auto Result = Format.writeTUSummary(Summary, FilePath);
+
+ EXPECT_THAT_ERROR(
+ std::move(Result),
+ FailedWithMessage(AllOf(HasSubstr("writing TUSummary to file"),
+ HasSubstr("failed to write file"))));
+
+ // Restore permissions for cleanup
+ sys::fs::setPermissions(DirPath, sys::fs::perms::all_all);
+#endif
+}
+
+// ============================================================================
+// Round-Trip Tests - Serialization Verification
// ============================================================================
TEST_F(JSONFormatTest, Empty) {
- auto Result = readJSON(R"({
+ readWriteJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -1117,12 +1205,10 @@ TEST_F(JSONFormatTest, Empty) {
"id_table": [],
"data": []
})");
-
- EXPECT_THAT(Result, Succeeded());
}
TEST_F(JSONFormatTest, LinkUnit) {
- auto Result = readJSON(R"({
+ readWriteJSON(R"({
"tu_namespace": {
"kind": "link_unit",
"name": "libtest.so"
@@ -1130,12 +1216,10 @@ TEST_F(JSONFormatTest, LinkUnit) {
"id_table": [],
"data": []
})");
-
- EXPECT_THAT(Result, Succeeded());
}
TEST_F(JSONFormatTest, WithIDTable) {
- auto Result = readJSON(R"({
+ readWriteJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -1174,12 +1258,10 @@ TEST_F(JSONFormatTest, WithIDTable) {
],
"data": []
})");
-
- EXPECT_THAT(Result, Succeeded());
}
TEST_F(JSONFormatTest, WithEmptyDataEntry) {
- auto Result = readJSON(R"({
+ readWriteJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -1192,27 +1274,10 @@ TEST_F(JSONFormatTest, WithEmptyDataEntry) {
}
]
})");
-
- EXPECT_THAT(Result, Succeeded());
-}
-
-// ============================================================================
-// Round-Trip Tests
-// ============================================================================
-
-TEST_F(JSONFormatTest, RoundTripEmpty) {
- testRoundTrip(R"({
- "tu_namespace": {
- "kind": "compilation_unit",
- "name": "test.cpp"
- },
- "id_table": [],
- "data": []
- })");
}
TEST_F(JSONFormatTest, RoundTripWithIDTable) {
- testRoundTrip(R"({
+ readWriteJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
@@ -1236,23 +1301,8 @@ TEST_F(JSONFormatTest, RoundTripWithIDTable) {
})");
}
-TEST_F(JSONFormatTest, RoundTripLinkUnit) {
- testRoundTrip(R"({
- "tu_namespace": {
- "kind": "link_unit",
- "name": "libtest.so"
- },
- "id_table": [],
- "data": []
- })");
-}
-
-// ============================================================================
-// Analysis-Specific Round-Trip Tests
-// ============================================================================
-
TEST_F(JSONFormatTest, RoundTripTestAnalysis) {
- testRoundTrip(R"({
+ readWriteJSON(R"({
"tu_namespace": {
"kind": "compilation_unit",
"name": "test.cpp"
More information about the cfe-commits
mailing list