[clang] SSAF JSON Format (PR #180021)
Aviral Goel via cfe-commits
cfe-commits at lists.llvm.org
Fri Feb 6 12:53:35 PST 2026
https://github.com/aviralg updated https://github.com/llvm/llvm-project/pull/180021
>From 5ae260fc6191ef087118a619214684fe0d471739 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Wed, 28 Jan 2026 09:21:20 -0800
Subject: [PATCH 1/7] Replace const and non-const static methods with templated
methods
---
.../Serialization/SerializationFormat.h | 30 ++++-----
clang/lib/Analysis/Scalable/CMakeLists.txt | 1 -
.../Serialization/SerializationFormat.cpp | 61 -------------------
3 files changed, 16 insertions(+), 76 deletions(-)
delete mode 100644 clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index a53a315f461df..30d728239e5c3 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -32,20 +32,22 @@ class SerializationFormat {
protected:
// Helpers providing access to implementation details of basic data structures
// for efficient serialization/deserialization.
- static EntityIdTable &getIdTableForDeserialization(TUSummary &S);
- static BuildNamespace &getTUNamespaceForDeserialization(TUSummary &S);
- static const EntityIdTable &getIdTable(const TUSummary &S);
- static const BuildNamespace &getTUNamespace(const TUSummary &S);
-
- static BuildNamespaceKind getBuildNamespaceKind(const BuildNamespace &BN);
- static llvm::StringRef getBuildNamespaceName(const BuildNamespace &BN);
- static const std::vector<BuildNamespace> &
- getNestedBuildNamespaces(const NestedBuildNamespace &NBN);
-
- static llvm::StringRef getEntityNameUSR(const EntityName &EN);
- static const llvm::SmallString<16> &getEntityNameSuffix(const EntityName &EN);
- static const NestedBuildNamespace &
- getEntityNameNamespace(const EntityName &EN);
+
+ // Accessors for TUSummary:
+ template <class T> static auto &IdTableOf(T &X) { return X.IdTable; }
+ template <class T> static auto &TUNamespaceOf(T &X) { return X.TUNamespace; }
+
+ // Accessors for BuildNamespace:
+ template <class T> static auto &KindOf(T &X) { return X.Kind; }
+ template <class T> static auto &NameOf(T &X) { return X.Name; }
+
+ // Accessors for NestedBuildNamespace:
+ template <class T> static auto &NamespacesOf(T &X) { return X.Namespaces; }
+
+ // Accessors for EntityName:
+ template <class T> static auto &USROf(T &X) { return X.USR; }
+ template <class T> static auto &SuffixOf(T &X) { return X.Suffix; }
+ template <class T> static auto &NamespaceOf(T &X) { return X.Namespace; }
public:
virtual ~SerializationFormat() = default;
diff --git a/clang/lib/Analysis/Scalable/CMakeLists.txt b/clang/lib/Analysis/Scalable/CMakeLists.txt
index 36365b1fb87e1..566edca552388 100644
--- a/clang/lib/Analysis/Scalable/CMakeLists.txt
+++ b/clang/lib/Analysis/Scalable/CMakeLists.txt
@@ -7,7 +7,6 @@ add_clang_library(clangAnalysisScalable
Model/BuildNamespace.cpp
Model/EntityIdTable.cpp
Model/EntityName.cpp
- Serialization/SerializationFormat.cpp
TUSummary/ExtractorRegistry.cpp
LINK_LIBS
diff --git a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
deleted file mode 100644
index ee155d22afa9b..0000000000000
--- a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-//===- SerializationFormat.cpp ----------------------------------*- C++ -*-===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-#include "clang/Analysis/Scalable/Serialization/SerializationFormat.h"
-#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
-#include "clang/Analysis/Scalable/Model/EntityId.h"
-#include "clang/Analysis/Scalable/Model/EntityName.h"
-#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
-
-using namespace clang::ssaf;
-
-EntityIdTable &SerializationFormat::getIdTableForDeserialization(TUSummary &S) {
- return S.IdTable;
-}
-
-BuildNamespace &
-SerializationFormat::getTUNamespaceForDeserialization(TUSummary &S) {
- return S.TUNamespace;
-}
-
-const EntityIdTable &SerializationFormat::getIdTable(const TUSummary &S) {
- return S.IdTable;
-}
-
-const BuildNamespace &SerializationFormat::getTUNamespace(const TUSummary &S) {
- return S.TUNamespace;
-}
-
-BuildNamespaceKind
-SerializationFormat::getBuildNamespaceKind(const BuildNamespace &BN) {
- return BN.Kind;
-}
-
-llvm::StringRef
-SerializationFormat::getBuildNamespaceName(const BuildNamespace &BN) {
- return BN.Name;
-}
-
-const std::vector<BuildNamespace> &
-SerializationFormat::getNestedBuildNamespaces(const NestedBuildNamespace &NBN) {
- return NBN.Namespaces;
-}
-
-llvm::StringRef SerializationFormat::getEntityNameUSR(const EntityName &EN) {
- return EN.USR;
-}
-
-const llvm::SmallString<16> &
-SerializationFormat::getEntityNameSuffix(const EntityName &EN) {
- return EN.Suffix;
-}
-
-const NestedBuildNamespace &
-SerializationFormat::getEntityNameNamespace(const EntityName &EN) {
- return EN.Namespace;
-}
>From b5ae6874e8cbcc6d8eeb3737b7bffea7a0506eaa Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Wed, 28 Jan 2026 13:52:12 -0800
Subject: [PATCH 2/7] Revert "Replace const and non-const static methods with
templated methods"
This reverts commit c93430657298a6ac87ac51be6bd310cc652aa52d.
---
.../Serialization/SerializationFormat.h | 30 +++++----
clang/lib/Analysis/Scalable/CMakeLists.txt | 1 +
.../Serialization/SerializationFormat.cpp | 61 +++++++++++++++++++
3 files changed, 76 insertions(+), 16 deletions(-)
create mode 100644 clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index 30d728239e5c3..a53a315f461df 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -32,22 +32,20 @@ class SerializationFormat {
protected:
// Helpers providing access to implementation details of basic data structures
// for efficient serialization/deserialization.
-
- // Accessors for TUSummary:
- template <class T> static auto &IdTableOf(T &X) { return X.IdTable; }
- template <class T> static auto &TUNamespaceOf(T &X) { return X.TUNamespace; }
-
- // Accessors for BuildNamespace:
- template <class T> static auto &KindOf(T &X) { return X.Kind; }
- template <class T> static auto &NameOf(T &X) { return X.Name; }
-
- // Accessors for NestedBuildNamespace:
- template <class T> static auto &NamespacesOf(T &X) { return X.Namespaces; }
-
- // Accessors for EntityName:
- template <class T> static auto &USROf(T &X) { return X.USR; }
- template <class T> static auto &SuffixOf(T &X) { return X.Suffix; }
- template <class T> static auto &NamespaceOf(T &X) { return X.Namespace; }
+ static EntityIdTable &getIdTableForDeserialization(TUSummary &S);
+ static BuildNamespace &getTUNamespaceForDeserialization(TUSummary &S);
+ static const EntityIdTable &getIdTable(const TUSummary &S);
+ static const BuildNamespace &getTUNamespace(const TUSummary &S);
+
+ static BuildNamespaceKind getBuildNamespaceKind(const BuildNamespace &BN);
+ static llvm::StringRef getBuildNamespaceName(const BuildNamespace &BN);
+ static const std::vector<BuildNamespace> &
+ getNestedBuildNamespaces(const NestedBuildNamespace &NBN);
+
+ static llvm::StringRef getEntityNameUSR(const EntityName &EN);
+ static const llvm::SmallString<16> &getEntityNameSuffix(const EntityName &EN);
+ static const NestedBuildNamespace &
+ getEntityNameNamespace(const EntityName &EN);
public:
virtual ~SerializationFormat() = default;
diff --git a/clang/lib/Analysis/Scalable/CMakeLists.txt b/clang/lib/Analysis/Scalable/CMakeLists.txt
index 566edca552388..36365b1fb87e1 100644
--- a/clang/lib/Analysis/Scalable/CMakeLists.txt
+++ b/clang/lib/Analysis/Scalable/CMakeLists.txt
@@ -7,6 +7,7 @@ add_clang_library(clangAnalysisScalable
Model/BuildNamespace.cpp
Model/EntityIdTable.cpp
Model/EntityName.cpp
+ Serialization/SerializationFormat.cpp
TUSummary/ExtractorRegistry.cpp
LINK_LIBS
diff --git a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
new file mode 100644
index 0000000000000..ee155d22afa9b
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
@@ -0,0 +1,61 @@
+//===- SerializationFormat.cpp ----------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/Serialization/SerializationFormat.h"
+#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
+#include "clang/Analysis/Scalable/Model/EntityId.h"
+#include "clang/Analysis/Scalable/Model/EntityName.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+
+using namespace clang::ssaf;
+
+EntityIdTable &SerializationFormat::getIdTableForDeserialization(TUSummary &S) {
+ return S.IdTable;
+}
+
+BuildNamespace &
+SerializationFormat::getTUNamespaceForDeserialization(TUSummary &S) {
+ return S.TUNamespace;
+}
+
+const EntityIdTable &SerializationFormat::getIdTable(const TUSummary &S) {
+ return S.IdTable;
+}
+
+const BuildNamespace &SerializationFormat::getTUNamespace(const TUSummary &S) {
+ return S.TUNamespace;
+}
+
+BuildNamespaceKind
+SerializationFormat::getBuildNamespaceKind(const BuildNamespace &BN) {
+ return BN.Kind;
+}
+
+llvm::StringRef
+SerializationFormat::getBuildNamespaceName(const BuildNamespace &BN) {
+ return BN.Name;
+}
+
+const std::vector<BuildNamespace> &
+SerializationFormat::getNestedBuildNamespaces(const NestedBuildNamespace &NBN) {
+ return NBN.Namespaces;
+}
+
+llvm::StringRef SerializationFormat::getEntityNameUSR(const EntityName &EN) {
+ return EN.USR;
+}
+
+const llvm::SmallString<16> &
+SerializationFormat::getEntityNameSuffix(const EntityName &EN) {
+ return EN.Suffix;
+}
+
+const NestedBuildNamespace &
+SerializationFormat::getEntityNameNamespace(const EntityName &EN) {
+ return EN.Namespace;
+}
>From 1d12d1eebf9a5db3208d8ccf3911d7035e4d3d45 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Wed, 4 Feb 2026 16:12:00 -0800
Subject: [PATCH 3/7] Initial implementation of JSONFormat with tests.
---
.../clang/Analysis/Scalable/Model/EntityId.h | 1 +
.../Analysis/Scalable/Model/EntityIdTable.h | 2 +
.../Analysis/Scalable/Model/SummaryName.h | 2 +
.../Scalable/Serialization/JSONFormat.h | 77 ++
.../Serialization/SerializationFormat.h | 25 +-
clang/lib/Analysis/Scalable/CMakeLists.txt | 1 +
.../Scalable/Serialization/JSONFormat.cpp | 860 +++++++++++++
.../Serialization/SerializationFormat.cpp | 29 +
.../Analysis/Scalable/CMakeLists.txt | 1 +
.../Analysis/Scalable/JSONFormatTest.cpp | 1127 +++++++++++++++++
10 files changed, 2122 insertions(+), 3 deletions(-)
create mode 100644 clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
create mode 100644 clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
create mode 100644 clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityId.h b/clang/include/clang/Analysis/Scalable/Model/EntityId.h
index 6fa059445d853..e348486386cb6 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityId.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityId.h
@@ -29,6 +29,7 @@ class EntityIdTable;
/// \see EntityIdTable
class EntityId {
friend class EntityIdTable;
+ friend class SerializationFormat;
size_t Index;
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h b/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
index cf4bb83efddd0..a1099c4e4d0f8 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
@@ -21,6 +21,8 @@ namespace clang::ssaf {
/// The table maps each unique EntityName to exactly one EntityId.
/// Entities are never removed.
class EntityIdTable {
+ friend class SerializationFormat;
+
std::map<EntityName, EntityId> Entities;
public:
diff --git a/clang/include/clang/Analysis/Scalable/Model/SummaryName.h b/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
index 785fe0eb10372..08ed67fe17bed 100644
--- a/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
+++ b/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
@@ -10,6 +10,8 @@
#define LLVM_CLANG_ANALYSIS_SCALABLE_MODEL_SUMMARYNAME_H
#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Path.h"
+#include <cassert>
#include <string>
namespace clang::ssaf {
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
new file mode 100644
index 0000000000000..476853af0dce4
--- /dev/null
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -0,0 +1,77 @@
+//===- JSONFormat.h ---------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// JSON serialization format implementation
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef CLANG_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_H
+#define CLANG_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_H
+
+#include "clang/Analysis/Scalable/Serialization/SerializationFormat.h"
+#include "llvm/Support/JSON.h"
+
+namespace clang::ssaf {
+
+class EntitySummary;
+class SummaryName;
+
+class JSONFormat : public SerializationFormat {
+public:
+ JSONFormat() = default;
+
+ ~JSONFormat() = default;
+
+ llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) override;
+
+ llvm::Error writeTUSummary(const TUSummary &Summary,
+ llvm::StringRef OutputDir) override;
+
+private:
+ EntityId entityIdFromJSON(const uint64_t EntityIdIndex);
+ uint64_t entityIdToJSON(EntityId EI) const;
+
+ llvm::json::Object buildNamespaceToJSON(const BuildNamespace &BN) const;
+ llvm::json::Array
+ nestedBuildNamespaceToJSON(const NestedBuildNamespace &NBN) const;
+ llvm::json::Object entityNameToJSON(const EntityName &EN) const;
+ llvm::Expected<std::pair<EntityName, EntityId>>
+ entityIdTableEntryFromJSON(const llvm::json::Object &EntityIdTableEntryObject,
+ llvm::StringRef Path);
+ llvm::Expected<EntityIdTable>
+ entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray,
+ llvm::StringRef Path);
+ llvm::json::Array entityIdTableToJSON(const EntityIdTable &IdTable) const;
+
+ llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
+ entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
+ llvm::StringRef Path);
+ llvm::json::Array entityDataMapToJSON(
+ const std::map<EntityId, std::unique_ptr<EntitySummary>> &EntityDataMap)
+ const;
+
+ llvm::Expected<std::pair<SummaryName,
+ std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+ summaryDataMapEntryFromJSON(const llvm::json::Object &SummaryDataObject,
+ llvm::StringRef Path);
+ llvm::json::Object summaryDataMapEntryToJSON(
+ const SummaryName &SN,
+ const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const;
+
+ llvm::Expected<
+ std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+ readTUSummaryData(llvm::StringRef Path);
+ llvm::Error writeTUSummaryData(
+ const std::map<SummaryName,
+ std::map<EntityId, std::unique_ptr<EntitySummary>>> &Data,
+ llvm::StringRef Path);
+};
+
+} // namespace clang::ssaf
+
+#endif // CLANG_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_H
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index a53a315f461df..3d79f49c12dcb 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -17,6 +17,8 @@
#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include <map>
#include <vector>
namespace clang::ssaf {
@@ -26,16 +28,33 @@ class EntityIdTable;
class EntityName;
class TUSummary;
class TUSummaryData;
+class SummaryName;
+class EntitySummary;
/// Abstract base class for serialization formats.
class SerializationFormat {
protected:
// Helpers providing access to implementation details of basic data structures
// for efficient serialization/deserialization.
+
+ static size_t getEntityIdIndex(const EntityId &EI);
+ static EntityId makeEntityId(const size_t Index);
+
+ static const std::map<EntityName, EntityId> &
+ getEntities(const EntityIdTable &EIT);
+ static std::map<EntityName, EntityId> &
+ getEntitiesForDeserialization(EntityIdTable &EIT);
+
static EntityIdTable &getIdTableForDeserialization(TUSummary &S);
static BuildNamespace &getTUNamespaceForDeserialization(TUSummary &S);
+ static std::map<SummaryName,
+ std::map<EntityId, std::unique_ptr<EntitySummary>>> &
+ getDataForDeserialization(TUSummary &S);
static const EntityIdTable &getIdTable(const TUSummary &S);
static const BuildNamespace &getTUNamespace(const TUSummary &S);
+ static const std::map<SummaryName,
+ std::map<EntityId, std::unique_ptr<EntitySummary>>> &
+ getData(const TUSummary &S);
static BuildNamespaceKind getBuildNamespaceKind(const BuildNamespace &BN);
static llvm::StringRef getBuildNamespaceName(const BuildNamespace &BN);
@@ -50,10 +69,10 @@ class SerializationFormat {
public:
virtual ~SerializationFormat() = default;
- virtual TUSummary readTUSummary(llvm::StringRef Path) = 0;
+ virtual llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) = 0;
- virtual void writeTUSummary(const TUSummary &Summary,
- llvm::StringRef OutputDir) = 0;
+ virtual llvm::Error writeTUSummary(const TUSummary &Summary,
+ llvm::StringRef OutputDir) = 0;
};
} // namespace clang::ssaf
diff --git a/clang/lib/Analysis/Scalable/CMakeLists.txt b/clang/lib/Analysis/Scalable/CMakeLists.txt
index 36365b1fb87e1..6592d01bd8f38 100644
--- a/clang/lib/Analysis/Scalable/CMakeLists.txt
+++ b/clang/lib/Analysis/Scalable/CMakeLists.txt
@@ -7,6 +7,7 @@ add_clang_library(clangAnalysisScalable
Model/BuildNamespace.cpp
Model/EntityIdTable.cpp
Model/EntityName.cpp
+ Serialization/JSONFormat.cpp
Serialization/SerializationFormat.cpp
TUSummary/ExtractorRegistry.cpp
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
new file mode 100644
index 0000000000000..cca3ca36eb9ae
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -0,0 +1,860 @@
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+
+using namespace clang::ssaf;
+
+namespace {
+constexpr size_t kPathBufferSize = 128;
+constexpr const char *TUSummaryTUNamespaceFilename = "tu_namespace.json";
+constexpr const char *TUSummaryIdTableFilename = "id_table.json";
+constexpr const char *TUSummaryDataDirname = "data";
+
+// Helper to wrap an error with additional context
+template <typename... Args>
+llvm::Error wrapError(llvm::Error E, std::errc ErrorCode, const char *Fmt,
+ Args &&...Vals) {
+ return llvm::joinErrors(
+ llvm::createStringError(ErrorCode, Fmt, std::forward<Args>(Vals)...),
+ std::move(E));
+}
+
+// Convenience version that defaults to invalid_argument
+template <typename... Args>
+llvm::Error wrapError(llvm::Error E, const char *Fmt, Args &&...Vals) {
+ return wrapError(std::move(E), std::errc::invalid_argument, Fmt,
+ std::forward<Args>(Vals)...);
+}
+
+} // namespace
+
+//----------------------------------------------------------------------------
+// JSON Reader and Writer
+//----------------------------------------------------------------------------
+
+llvm::Error isJSONFile(llvm::StringRef Path) {
+ if (!llvm::sys::fs::exists(Path))
+ return llvm::createStringError(std::errc::no_such_file_or_directory,
+ "file does not exist: '%s'",
+ Path.str().c_str());
+
+ if (!Path.ends_with_insensitive(".json"))
+ return llvm::createStringError(std::errc::invalid_argument,
+ "not a JSON file: '%s'", Path.str().c_str());
+
+ return llvm::Error::success();
+}
+
+llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
+ if (llvm::Error Err = isJSONFile(Path))
+ return wrapError(std::move(Err), "failed to validate JSON file '%s'",
+ Path.str().c_str());
+
+ auto BufferOrError = llvm::MemoryBuffer::getFile(Path);
+ if (!BufferOrError) {
+ return llvm::createStringError(BufferOrError.getError(),
+ "failed to read file '%s'",
+ Path.str().c_str());
+ }
+
+ return llvm::json::parse(BufferOrError.get()->getBuffer());
+}
+
+llvm::Expected<llvm::json::Object> readJSONObject(llvm::StringRef Path) {
+ auto ExpectedJSON = readJSON(Path);
+ if (!ExpectedJSON)
+ return wrapError(ExpectedJSON.takeError(),
+ "failed to read JSON from file '%s'", Path.str().c_str());
+
+ llvm::json::Object *Object = ExpectedJSON->getAsObject();
+ if (!Object) {
+ return llvm::createStringError(std::errc::invalid_argument,
+ "failed to read JSON object from file '%s'",
+ Path.str().c_str());
+ }
+ return std::move(*Object);
+}
+
+llvm::Expected<llvm::json::Array> readJSONArray(llvm::StringRef Path) {
+ auto ExpectedJSON = readJSON(Path);
+ if (!ExpectedJSON)
+ return wrapError(ExpectedJSON.takeError(),
+ "failed to read JSON from file '%s'", Path.str().c_str());
+
+ llvm::json::Array *Array = ExpectedJSON->getAsArray();
+ if (!Array) {
+ return llvm::createStringError(std::errc::invalid_argument,
+ "failed to read JSON array from file '%s'",
+ Path.str().c_str());
+ }
+ return std::move(*Array);
+}
+
+llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
+ std::error_code EC;
+ llvm::raw_fd_ostream OS(Path, EC);
+ if (EC) {
+ return llvm::createStringError(EC, "failed to open '%s'",
+ Path.str().c_str());
+ }
+
+ OS << llvm::formatv("{0:2}\n", Value);
+
+ OS.flush();
+ if (OS.has_error()) {
+ return llvm::createStringError(OS.error(), "write failed");
+ }
+
+ return llvm::Error::success();
+}
+
+//----------------------------------------------------------------------------
+// EntityId
+//----------------------------------------------------------------------------
+
+EntityId JSONFormat::entityIdFromJSON(const uint64_t EntityIdIndex) {
+ return makeEntityId(static_cast<size_t>(EntityIdIndex));
+}
+
+uint64_t JSONFormat::entityIdToJSON(EntityId EI) const {
+ return static_cast<uint64_t>(getEntityIdIndex(EI));
+}
+
+//----------------------------------------------------------------------------
+// BuildNamespaceKind
+//----------------------------------------------------------------------------
+
+llvm::Expected<BuildNamespaceKind>
+buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr,
+ llvm::StringRef Path) {
+ auto OptBuildNamespaceKind = parseBuildNamespaceKind(BuildNamespaceKindStr);
+ if (!OptBuildNamespaceKind) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "invalid 'kind' BuildNamespaceKind value '%s' in file '%s'",
+ BuildNamespaceKindStr.str().c_str(), Path.str().c_str());
+ }
+
+ return *OptBuildNamespaceKind;
+}
+
+llvm::StringRef buildNamespaceKindToJSON(BuildNamespaceKind BNK) {
+ return toString(BNK);
+}
+
+//----------------------------------------------------------------------------
+// BuildNamespace
+//----------------------------------------------------------------------------
+
+llvm::Expected<BuildNamespace>
+buildNamespaceFromJSON(const llvm::json::Object &BuildNamespaceObject,
+ llvm::StringRef Path) {
+ auto OptBuildNamespaceKindStr = BuildNamespaceObject.getString("kind");
+ if (!OptBuildNamespaceKindStr) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize BuildNamespace from file '%s': "
+ "missing required field 'kind' (expected BuildNamespaceKind)",
+ Path.str().c_str());
+ }
+
+ auto ExpectedKind =
+ buildNamespaceKindFromJSON(*OptBuildNamespaceKindStr, Path);
+ if (!ExpectedKind)
+ return wrapError(ExpectedKind.takeError(),
+ "failed to deserialize BuildNamespace from file '%s'",
+ Path.str().c_str());
+
+ auto OptNameStr = BuildNamespaceObject.getString("name");
+ if (!OptNameStr) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize BuildNamespace from file '%s': "
+ "missing required field 'name'",
+ Path.str().c_str());
+ }
+
+ return {BuildNamespace(*ExpectedKind, *OptNameStr)};
+}
+
+llvm::json::Object
+JSONFormat::buildNamespaceToJSON(const BuildNamespace &BN) const {
+ llvm::json::Object Result;
+ Result["kind"] = buildNamespaceKindToJSON(getBuildNamespaceKind(BN));
+ Result["name"] = getBuildNamespaceName(BN);
+ return Result;
+}
+
+//----------------------------------------------------------------------------
+// NestedBuildNamespace
+//----------------------------------------------------------------------------
+
+llvm::Expected<NestedBuildNamespace>
+nestedBuildNamespaceFromJSON(const llvm::json::Array &NestedBuildNamespaceArray,
+ llvm::StringRef Path) {
+ std::vector<BuildNamespace> Namespaces;
+
+ size_t NamespaceCount = NestedBuildNamespaceArray.size();
+ Namespaces.reserve(NamespaceCount);
+ for (size_t Index = 0; Index < NamespaceCount; ++Index) {
+ const llvm::json::Value &BuildNamespaceValue =
+ NestedBuildNamespaceArray[Index];
+
+ const llvm::json::Object *BuildNamespaceObject =
+ BuildNamespaceValue.getAsObject();
+ if (!BuildNamespaceObject) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize NestedBuildNamespace from file '%s': "
+ "element at index %zu is not a JSON object "
+ "(expected BuildNamespace object)",
+ Path.str().c_str(), Index);
+ }
+
+ auto ExpectedBuildNamespace =
+ buildNamespaceFromJSON(*BuildNamespaceObject, Path);
+ if (!ExpectedBuildNamespace)
+ return wrapError(
+ ExpectedBuildNamespace.takeError(),
+ "failed to deserialize NestedBuildNamespace from file '%s' "
+ "at index %zu",
+ Path.str().c_str(), Index);
+
+ Namespaces.push_back(std::move(*ExpectedBuildNamespace));
+ }
+
+ return NestedBuildNamespace(std::move(Namespaces));
+}
+
+llvm::json::Array
+JSONFormat::nestedBuildNamespaceToJSON(const NestedBuildNamespace &NBN) const {
+ llvm::json::Array Result;
+ const auto &Namespaces = getNestedBuildNamespaces(NBN);
+ Result.reserve(Namespaces.size());
+
+ for (const auto &BN : Namespaces) {
+ Result.push_back(buildNamespaceToJSON(BN));
+ }
+
+ return Result;
+}
+
+//----------------------------------------------------------------------------
+// EntityName
+//----------------------------------------------------------------------------
+
+llvm::Expected<EntityName>
+entityNameFromJSON(const llvm::json::Object &EntityNameObject,
+ llvm::StringRef Path) {
+ const auto OptUSR = EntityNameObject.getString("usr");
+ if (!OptUSR) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntityName from file '%s': "
+ "missing required field 'usr' (Unified Symbol Resolution string)",
+ Path.str().c_str());
+ }
+
+ const auto OptSuffix = EntityNameObject.getString("suffix");
+ if (!OptSuffix) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntityName from file '%s': "
+ "missing required field 'suffix'",
+ Path.str().c_str());
+ }
+
+ const llvm::json::Array *OptNamespaceArray =
+ EntityNameObject.getArray("namespace");
+ if (!OptNamespaceArray) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntityName from file '%s': "
+ "missing or invalid field 'namespace' "
+ "(expected JSON array of BuildNamespace objects)",
+ Path.str().c_str());
+ }
+
+ auto ExpectedNamespace =
+ nestedBuildNamespaceFromJSON(*OptNamespaceArray, Path);
+ if (!ExpectedNamespace)
+ return wrapError(ExpectedNamespace.takeError(),
+ "failed to deserialize EntityName from file '%s'",
+ Path.str().c_str());
+
+ return EntityName{*OptUSR, *OptSuffix, std::move(*ExpectedNamespace)};
+}
+
+llvm::json::Object JSONFormat::entityNameToJSON(const EntityName &EN) const {
+ llvm::json::Object Result;
+ Result["usr"] = getEntityNameUSR(EN);
+ Result["suffix"] = getEntityNameSuffix(EN);
+ Result["namespace"] = nestedBuildNamespaceToJSON(getEntityNameNamespace(EN));
+ return Result;
+}
+
+//----------------------------------------------------------------------------
+// SummaryName
+//----------------------------------------------------------------------------
+
+SummaryName summaryNameFromJSON(llvm::StringRef SummaryNameStr) {
+ return SummaryName(SummaryNameStr.str());
+}
+
+llvm::StringRef summaryNameToJSON(const SummaryName &SN) { return SN.str(); }
+
+//----------------------------------------------------------------------------
+// EntityIdTable
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::pair<EntityName, EntityId>>
+JSONFormat::entityIdTableEntryFromJSON(
+ const llvm::json::Object &EntityIdTableEntryObject, llvm::StringRef Path) {
+
+ const llvm::json::Object *OptEntityNameObject =
+ EntityIdTableEntryObject.getObject("name");
+ if (!OptEntityNameObject) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntityIdTable entry from file '%s': "
+ "missing or invalid field 'name' (expected EntityName JSON object)",
+ Path.str().c_str());
+ }
+
+ auto ExpectedEntityName = entityNameFromJSON(*OptEntityNameObject, Path);
+ if (!ExpectedEntityName)
+ return wrapError(ExpectedEntityName.takeError(),
+ "failed to deserialize EntityIdTable entry from file '%s'",
+ Path.str().c_str());
+
+ const llvm::json::Value *EntityIdIntValue =
+ EntityIdTableEntryObject.get("id");
+ if (!EntityIdIntValue) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntityIdTable entry from file '%s': "
+ "missing required field 'id' (expected unsigned integer EntityId)",
+ Path.str().c_str());
+ }
+
+ const std::optional<uint64_t> OptEntityIdInt =
+ EntityIdIntValue->getAsUINT64();
+ if (!OptEntityIdInt) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntityIdTable entry from file '%s': "
+ "field 'id' is not a valid unsigned 64-bit integer "
+ "(expected non-negative EntityId value)",
+ Path.str().c_str());
+ }
+
+ EntityId EI = entityIdFromJSON(*OptEntityIdInt);
+
+ return std::make_pair(std::move(*ExpectedEntityName), std::move(EI));
+}
+
+llvm::Expected<EntityIdTable>
+JSONFormat::entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray,
+ llvm::StringRef Path) {
+ const size_t EntityCount = EntityIdTableArray.size();
+
+ EntityIdTable IdTable;
+ std::map<EntityName, EntityId> &Entities =
+ getEntitiesForDeserialization(IdTable);
+
+ for (size_t Index = 0; Index < EntityCount; ++Index) {
+ const llvm::json::Value &EntityIdTableEntryValue =
+ EntityIdTableArray[Index];
+
+ const llvm::json::Object *OptEntityIdTableEntryObject =
+ EntityIdTableEntryValue.getAsObject();
+
+ if (!OptEntityIdTableEntryObject) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntityIdTable from file '%s': "
+ "element at index %zu is not a JSON object "
+ "(expected EntityIdTable entry with 'id' and 'name' fields)",
+ Path.str().c_str(), Index);
+ }
+
+ auto ExpectedEntityIdTableEntry =
+ entityIdTableEntryFromJSON(*OptEntityIdTableEntryObject, Path);
+ if (!ExpectedEntityIdTableEntry)
+ return wrapError(
+ ExpectedEntityIdTableEntry.takeError(),
+ "failed to deserialize EntityIdTable from file '%s' at index %zu",
+ Path.str().c_str(), Index);
+
+ auto [EntityIt, EntityInserted] =
+ Entities.emplace(std::move(*ExpectedEntityIdTableEntry));
+ if (!EntityInserted) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntityIdTable from file '%s': "
+ "duplicate EntityName found at index %zu "
+ "(EntityId=%zu already exists in table)",
+ Path.str().c_str(), Index, getEntityIdIndex(EntityIt->second));
+ }
+ }
+
+ return IdTable;
+}
+
+llvm::json::Array
+JSONFormat::entityIdTableToJSON(const EntityIdTable &IdTable) const {
+ llvm::json::Array EntityIdTableArray;
+ const auto &Entities = getEntities(IdTable);
+ EntityIdTableArray.reserve(Entities.size());
+
+ for (const auto &[EntityName, EntityId] : Entities) {
+ llvm::json::Object Entry;
+ Entry["id"] = entityIdToJSON(EntityId);
+ Entry["name"] = entityNameToJSON(EntityName);
+ EntityIdTableArray.push_back(std::move(Entry));
+ }
+
+ return EntityIdTableArray;
+}
+
+//----------------------------------------------------------------------------
+// EntitySummary
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::unique_ptr<EntitySummary>>
+entitySummaryFromJSON(const llvm::json::Object &EntitySummaryObject,
+ llvm::StringRef Path) {
+ return llvm::createStringError(
+ std::errc::function_not_supported,
+ "EntitySummary deserialization from file '%s' is not yet implemented",
+ Path.str().c_str());
+}
+
+llvm::json::Object entitySummaryToJSON(const EntitySummary &ES) {
+ // TODO
+ llvm::json::Object Result;
+ return Result;
+}
+
+//----------------------------------------------------------------------------
+// SummaryData
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
+JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
+ llvm::StringRef Path) {
+ std::map<EntityId, std::unique_ptr<EntitySummary>> EntityDataMap;
+
+ size_t Index = 0;
+ for (const llvm::json::Value &EntityDataEntryValue : EntityDataArray) {
+ const llvm::json::Object *OptEntityDataEntryObject =
+ EntityDataEntryValue.getAsObject();
+ if (!OptEntityDataEntryObject) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize entity data map from file '%s': "
+ "element at index %zu is not a JSON object "
+ "(expected object with 'entity_id' and 'entity_summary' fields)",
+ Path.str().c_str(), Index);
+ }
+
+ const llvm::json::Value *EntityIdIntValue =
+ OptEntityDataEntryObject->get("entity_id");
+ if (!EntityIdIntValue) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize entity data map entry from file '%s' "
+ "at index %zu: missing required field 'entity_id' "
+ "(expected unsigned integer EntityId)",
+ Path.str().c_str(), Index);
+ }
+
+ const std::optional<uint64_t> OptEntityIdInt =
+ EntityIdIntValue->getAsUINT64();
+ if (!OptEntityIdInt) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize entity data map entry from file '%s' "
+ "at index %zu: field 'entity_id' is not a valid unsigned 64-bit "
+ "integer",
+ Path.str().c_str(), Index);
+ }
+
+ EntityId EI = entityIdFromJSON(*OptEntityIdInt);
+
+ const llvm::json::Object *OptEntitySummaryObject =
+ OptEntityDataEntryObject->getObject("entity_summary");
+ if (!OptEntitySummaryObject) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize entity data map entry from file '%s' "
+ "at index %zu: missing or invalid field 'entity_summary' "
+ "(expected EntitySummary JSON object)",
+ Path.str().c_str(), Index);
+ }
+
+ auto ExpectedEntitySummary =
+ entitySummaryFromJSON(*OptEntitySummaryObject, Path);
+
+ if (!ExpectedEntitySummary) {
+ return wrapError(
+ ExpectedEntitySummary.takeError(),
+ "failed to deserialize entity data map entry from file '%s' "
+ "at index %zu",
+ Path.str().c_str(), Index);
+ }
+
+ auto [DataIt, DataInserted] = EntityDataMap.insert(
+ {std::move(EI), std::move(*ExpectedEntitySummary)});
+ if (!DataInserted) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize entity data map from file '%s': "
+ "duplicate EntityId (%zu) found at index %zu",
+ Path.str().c_str(), getEntityIdIndex(DataIt->first), Index);
+ }
+
+ ++Index;
+ }
+
+ return EntityDataMap;
+}
+
+llvm::json::Array JSONFormat::entityDataMapToJSON(
+ const std::map<EntityId, std::unique_ptr<EntitySummary>> &EntityDataMap)
+ const {
+ llvm::json::Array Result;
+ Result.reserve(EntityDataMap.size());
+ for (const auto &[EntityId, EntitySummary] : EntityDataMap) {
+ llvm::json::Object Entry;
+ Entry["entity_id"] = entityIdToJSON(EntityId);
+ Entry["entity_summary"] = entitySummaryToJSON(*EntitySummary);
+ Result.push_back(std::move(Entry));
+ }
+ return Result;
+}
+
+llvm::Expected<
+ std::pair<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+JSONFormat::summaryDataMapEntryFromJSON(
+ const llvm::json::Object &SummaryDataObject, llvm::StringRef Path) {
+
+ std::optional<llvm::StringRef> OptSummaryNameStr =
+ SummaryDataObject.getString("summary_name");
+
+ if (!OptSummaryNameStr) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize summary data from file '%s': "
+ "missing required field 'summary_name' "
+ "(expected string identifier for the analysis summary)",
+ Path.str().c_str());
+ }
+
+ SummaryName SN = summaryNameFromJSON(*OptSummaryNameStr);
+
+ const llvm::json::Array *OptEntityDataArray =
+ SummaryDataObject.getArray("summary_data");
+ if (!OptEntityDataArray) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize summary data from file '%s' for summary '%s': "
+ "missing or invalid field 'summary_data' "
+ "(expected JSON array of entity summaries)",
+ Path.str().c_str(), SN.str().data());
+ }
+
+ auto ExpectedEntityDataMap = entityDataMapFromJSON(*OptEntityDataArray, Path);
+ if (!ExpectedEntityDataMap)
+ return wrapError(
+ ExpectedEntityDataMap.takeError(),
+ "failed to deserialize summary data from file '%s' for summary '%s'",
+ Path.str().c_str(), SN.str().data());
+
+ return std::make_pair(std::move(SN), std::move(*ExpectedEntityDataMap));
+}
+
+llvm::json::Object JSONFormat::summaryDataMapEntryToJSON(
+ const SummaryName &SN,
+ const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const {
+ llvm::json::Object Result;
+ Result["summary_name"] = summaryNameToJSON(SN);
+ Result["summary_data"] = entityDataMapToJSON(SD);
+ return Result;
+}
+
+//----------------------------------------------------------------------------
+// SummaryDataMap
+//----------------------------------------------------------------------------
+
+llvm::Expected<
+ std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+JSONFormat::readTUSummaryData(llvm::StringRef Path) {
+ if (!llvm::sys::fs::exists(Path)) {
+ return llvm::createStringError(
+ std::errc::no_such_file_or_directory,
+ "failed to read TUSummary data: directory does not exist: '%s'",
+ Path.str().c_str());
+ }
+
+ if (!llvm::sys::fs::is_directory(Path)) {
+ return llvm::createStringError(
+ std::errc::not_a_directory,
+ "failed to read TUSummary data: path is not a directory: '%s'",
+ Path.str().c_str());
+ }
+
+ std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>
+ Data;
+ std::error_code EC;
+
+ llvm::sys::fs::directory_iterator Dir(Path, EC);
+ if (EC) {
+ return llvm::createStringError(
+ EC, "failed to read TUSummary data: cannot iterate directory '%s'",
+ Path.str().c_str());
+ }
+
+ for (llvm::sys::fs::directory_iterator End; Dir != End && !EC;
+ Dir.increment(EC)) {
+ std::string SummaryPath = Dir->path();
+
+ auto ExpectedObject = readJSONObject(SummaryPath);
+ if (!ExpectedObject)
+ return wrapError(ExpectedObject.takeError(),
+ "failed to read TUSummary data from file '%s'",
+ SummaryPath.c_str());
+
+ auto ExpectedSummaryDataMap =
+ summaryDataMapEntryFromJSON(*ExpectedObject, SummaryPath);
+ if (!ExpectedSummaryDataMap)
+ return wrapError(ExpectedSummaryDataMap.takeError(),
+ "failed to read TUSummary data from file '%s'",
+ SummaryPath.c_str());
+
+ auto [SummaryIt, SummaryInserted] =
+ Data.emplace(std::move(*ExpectedSummaryDataMap));
+ if (!SummaryInserted) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to read TUSummary data from directory '%s': "
+ "duplicate SummaryName '%s' encountered in file '%s'",
+ Path.str().c_str(), SummaryIt->first.str().data(),
+ SummaryPath.c_str());
+ }
+ }
+
+ if (EC) {
+ return llvm::createStringError(EC,
+ "failed to read TUSummary data: "
+ "error during directory iteration of '%s'",
+ Path.str().c_str());
+ }
+
+ return Data;
+}
+
+std::string makeValidFilename(llvm::StringRef Name, size_t Prefix,
+ char Replacement) {
+ std::string Result;
+ llvm::raw_string_ostream OS(Result);
+ OS << llvm::format("%02d-%s", Prefix, Name.str().c_str());
+
+ for (size_t Index = 0; Index < Result.size(); ++Index) {
+ char &Actual = Result[Index];
+ if (llvm::isAlnum(Actual) || Actual == '-' || Actual == '_')
+ continue;
+ Actual = Replacement;
+ }
+
+ return Result;
+}
+
+llvm::Error JSONFormat::writeTUSummaryData(
+ const std::map<SummaryName,
+ std::map<EntityId, std::unique_ptr<EntitySummary>>> &Data,
+ llvm::StringRef Path) {
+ if (!llvm::sys::fs::exists(Path)) {
+ return llvm::createStringError(
+ std::errc::no_such_file_or_directory,
+ "failed to write TUSummary data: directory does not exist: '%s'",
+ Path.str().c_str());
+ }
+
+ if (!llvm::sys::fs::is_directory(Path)) {
+ return llvm::createStringError(
+ std::errc::not_a_directory,
+ "failed to write TUSummary data: path is not a directory: '%s'",
+ Path.str().c_str());
+ }
+
+ size_t Index = 0;
+ for (const auto &[SummaryName, DataMap] : Data) {
+ llvm::SmallString<kPathBufferSize> SummaryPath(Path);
+ llvm::sys::path::append(SummaryPath,
+ makeValidFilename(SummaryName.str(), Index, '_'));
+ llvm::sys::path::replace_extension(SummaryPath, ".json");
+
+ llvm::json::Object Result = summaryDataMapEntryToJSON(SummaryName, DataMap);
+ if (auto Error = writeJSON(std::move(Result), SummaryPath)) {
+ return wrapError(
+ std::move(Error), std::errc::io_error,
+ "failed to write TUSummary data to directory '%s': cannot write "
+ "summary '%s' to file '%s'",
+ Path.str().c_str(), SummaryName.str().data(),
+ SummaryPath.str().data());
+ }
+
+ ++Index;
+ }
+
+ return llvm::Error::success();
+}
+
+//----------------------------------------------------------------------------
+// TUSummary
+//----------------------------------------------------------------------------
+
+llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
+
+ // Populate TUNamespace field.
+ llvm::SmallString<kPathBufferSize> TUNamespacePath(Path);
+ llvm::sys::path::append(TUNamespacePath, TUSummaryTUNamespaceFilename);
+
+ auto ExpectedObject = readJSONObject(TUNamespacePath);
+ if (!ExpectedObject)
+ return wrapError(ExpectedObject.takeError(),
+ "failed to read TUSummary from '%s'", Path.str().c_str());
+
+ auto ExpectedTUNamespace =
+ buildNamespaceFromJSON(*ExpectedObject, TUNamespacePath);
+ if (!ExpectedTUNamespace)
+ return wrapError(ExpectedTUNamespace.takeError(),
+ "failed to read TUSummary from '%s'", Path.str().c_str());
+
+ TUSummary Summary(std::move(*ExpectedTUNamespace));
+
+ // Populate IdTable field.
+ {
+ llvm::SmallString<kPathBufferSize> IdTablePath(Path);
+ llvm::sys::path::append(IdTablePath, TUSummaryIdTableFilename);
+
+ auto ExpectedArray = readJSONArray(IdTablePath);
+ if (!ExpectedArray)
+ return wrapError(ExpectedArray.takeError(),
+ "failed to read TUSummary from '%s'",
+ Path.str().c_str());
+
+ auto ExpectedIdTable = entityIdTableFromJSON(*ExpectedArray, IdTablePath);
+ if (!ExpectedIdTable)
+ return wrapError(ExpectedIdTable.takeError(),
+ "failed to read TUSummary from '%s'",
+ Path.str().c_str());
+
+ getIdTableForDeserialization(Summary) = std::move(*ExpectedIdTable);
+ }
+
+ // Populate Data field.
+ {
+ llvm::SmallString<kPathBufferSize> DataPath(Path);
+ llvm::sys::path::append(DataPath, TUSummaryDataDirname);
+
+ if (!llvm::sys::fs::exists(DataPath)) {
+ return llvm::createStringError(std::errc::no_such_file_or_directory,
+ "failed to read TUSummary from '%s': "
+ "data directory does not exist: '%s'",
+ Path.str().c_str(), DataPath.str().data());
+ }
+
+ if (!llvm::sys::fs::is_directory(DataPath)) {
+ return llvm::createStringError(std::errc::not_a_directory,
+ "failed to read TUSummary from '%s': "
+ "data path is not a directory: '%s'",
+ Path.str().c_str(), DataPath.str().data());
+ }
+
+ auto ExpectedData = readTUSummaryData(DataPath);
+ if (!ExpectedData)
+ return wrapError(ExpectedData.takeError(),
+ "failed to read TUSummary from '%s'",
+ Path.str().c_str());
+
+ getDataForDeserialization(Summary) = std::move(*ExpectedData);
+ }
+
+ return Summary;
+}
+
+llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
+ llvm::StringRef Dir) {
+ // Serialize TUNamespace field.
+ {
+ llvm::SmallString<kPathBufferSize> TUNamespacePath(Dir);
+ llvm::sys::path::append(TUNamespacePath, TUSummaryTUNamespaceFilename);
+
+ llvm::json::Object BuildNamespaceObj =
+ buildNamespaceToJSON(getTUNamespace(S));
+ if (auto Error = writeJSON(std::move(BuildNamespaceObj), TUNamespacePath)) {
+ return wrapError(std::move(Error), std::errc::io_error,
+ "failed to write TUSummary to '%s': "
+ "cannot write TUNamespace file '%s'",
+ Dir.str().c_str(), TUNamespacePath.str().data());
+ }
+ }
+
+ // Serialize IdTable field.
+ {
+ llvm::SmallString<kPathBufferSize> IdTablePath(Dir);
+ llvm::sys::path::append(IdTablePath, TUSummaryIdTableFilename);
+
+ llvm::json::Array IdTableObj = entityIdTableToJSON(getIdTable(S));
+ if (auto Error = writeJSON(std::move(IdTableObj), IdTablePath)) {
+ return wrapError(std::move(Error), std::errc::io_error,
+ "failed to write TUSummary to '%s': "
+ "cannot write IdTable file '%s'",
+ Dir.str().c_str(), IdTablePath.str().data());
+ }
+ }
+
+ // Serialize Data field.
+ {
+ llvm::SmallString<kPathBufferSize> DataPath(Dir);
+ llvm::sys::path::append(DataPath, TUSummaryDataDirname);
+
+ // Create the data directory if it doesn't exist
+ if (std::error_code EC = llvm::sys::fs::create_directory(DataPath)) {
+ // If error is not "already exists", return error
+ if (EC != std::errc::file_exists) {
+ return llvm::createStringError(EC,
+ "failed to write TUSummary to '%s': "
+ "cannot create data directory '%s'",
+ Dir.str().c_str(),
+ DataPath.str().data());
+ }
+ }
+
+ // Verify it's a directory (could be a file with the same name)
+ if (llvm::sys::fs::exists(DataPath) &&
+ !llvm::sys::fs::is_directory(DataPath)) {
+ return llvm::createStringError(
+ std::errc::not_a_directory,
+ "failed to write TUSummary to '%s': data path exists but is not a "
+ "directory: '%s'",
+ Dir.str().c_str(), DataPath.str().data());
+ }
+
+ if (auto Error = writeTUSummaryData(getData(S), DataPath)) {
+ return wrapError(std::move(Error), std::errc::io_error,
+ "failed to write TUSummary to '%s': cannot write data",
+ Dir.str().c_str());
+ }
+ }
+
+ return llvm::Error::success();
+}
diff --git a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
index ee155d22afa9b..4e5dd47b7b17e 100644
--- a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
@@ -14,6 +14,24 @@
using namespace clang::ssaf;
+size_t SerializationFormat::getEntityIdIndex(const EntityId &EI) {
+ return EI.Index;
+}
+
+EntityId SerializationFormat::makeEntityId(const size_t Index) {
+ return EntityId(Index);
+}
+
+const std::map<EntityName, EntityId> &
+SerializationFormat::getEntities(const EntityIdTable &EIT) {
+ return EIT.Entities;
+}
+
+std::map<EntityName, EntityId> &
+SerializationFormat::getEntitiesForDeserialization(EntityIdTable &EIT) {
+ return EIT.Entities;
+}
+
EntityIdTable &SerializationFormat::getIdTableForDeserialization(TUSummary &S) {
return S.IdTable;
}
@@ -23,6 +41,11 @@ SerializationFormat::getTUNamespaceForDeserialization(TUSummary &S) {
return S.TUNamespace;
}
+std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>> &
+SerializationFormat::getDataForDeserialization(TUSummary &S) {
+ return S.Data;
+}
+
const EntityIdTable &SerializationFormat::getIdTable(const TUSummary &S) {
return S.IdTable;
}
@@ -31,6 +54,12 @@ const BuildNamespace &SerializationFormat::getTUNamespace(const TUSummary &S) {
return S.TUNamespace;
}
+const std::map<SummaryName,
+ std::map<EntityId, std::unique_ptr<EntitySummary>>> &
+SerializationFormat::getData(const TUSummary &S) {
+ return S.Data;
+}
+
BuildNamespaceKind
SerializationFormat::getBuildNamespaceKind(const BuildNamespace &BN) {
return BN.Kind;
diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt b/clang/unittests/Analysis/Scalable/CMakeLists.txt
index a21002e313ead..4732f5f600bcf 100644
--- a/clang/unittests/Analysis/Scalable/CMakeLists.txt
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -4,6 +4,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
EntityIdTest.cpp
EntityIdTableTest.cpp
EntityNameTest.cpp
+ JSONFormatTest.cpp
Registries/MockSummaryExtractor1.cpp
Registries/MockSummaryExtractor2.cpp
Registries/SummaryExtractorRegistryTest.cpp
diff --git a/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
new file mode 100644
index 0000000000000..db38ce23565da
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
@@ -0,0 +1,1127 @@
+//===- unittests/Analysis/Scalable/JSONFormatTest.cpp -------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
+#include "clang/Analysis/Scalable/Model/EntityId.h"
+#include "clang/Analysis/Scalable/Model/EntityIdTable.h"
+#include "clang/Analysis/Scalable/Model/EntityName.h"
+#include "clang/Analysis/Scalable/Model/SummaryName.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <fstream>
+
+using namespace clang::ssaf;
+using llvm::Failed;
+using llvm::Succeeded;
+using ::testing::AllOf;
+using ::testing::HasSubstr;
+
+namespace {
+
+// Helper function to check that an error message contains all specified
+// substrings
+::testing::Matcher<std::string>
+ContainsAllSubstrings(std::initializer_list<const char *> substrings) {
+ std::vector<::testing::Matcher<std::string>> matchers;
+ for (const char *substr : substrings) {
+ matchers.push_back(HasSubstr(substr));
+ }
+ return ::testing::AllOfArray(matchers);
+}
+
+//===----------------------------------------------------------------------===//
+// Test Fixtures and Helpers
+//===----------------------------------------------------------------------===//
+
+// Helper class to manage temporary directories for testing
+class TempDir {
+ llvm::SmallString<128> Path;
+ std::error_code EC;
+
+public:
+ TempDir() {
+ EC = llvm::sys::fs::createUniqueDirectory("JSONFormatTest", Path);
+ }
+
+ ~TempDir() {
+ if (!EC && llvm::sys::fs::exists(Path)) {
+ llvm::sys::fs::remove_directories(Path);
+ }
+ }
+
+ llvm::StringRef path() const { return Path; }
+ bool isValid() const { return !EC; }
+};
+
+// Helper function to write a file with content
+void writeFile(llvm::StringRef Path, llvm::StringRef Content) {
+ std::ofstream File(Path.str());
+ File << Content.str();
+ File.close();
+}
+
+// Helper function to create a directory
+void createDir(llvm::StringRef Path) {
+ llvm::sys::fs::create_directories(Path);
+}
+
+// Base test fixture for JSONFormat tests
+class JSONFormatTestBase : public ::testing::Test {
+protected:
+ JSONFormat Format;
+};
+
+//===----------------------------------------------------------------------===//
+// TUSummary Read Tests - TUNamespace
+//===----------------------------------------------------------------------===//
+
+class JSONFormatReadTUNamespaceTest : public JSONFormatTestBase {};
+
+TEST_F(JSONFormatReadTUNamespaceTest, NonexistentDirectory) {
+ auto Result = Format.readTUSummary("/nonexistent/path");
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from '/nonexistent/path'",
+ "failed to read JSON from file", "tu_namespace.json",
+ "failed to validate JSON file", "file does not exist"}));
+}
+
+TEST_F(JSONFormatReadTUNamespaceTest, MissingTUNamespaceFile) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg, ContainsAllSubstrings({"failed to read TUSummary from",
+ "failed to read JSON from file",
+ "tu_namespace.json",
+ "file does not exist"}));
+}
+
+TEST_F(JSONFormatReadTUNamespaceTest, InvalidJSONSyntax) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath, "{ invalid json }");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg, ContainsAllSubstrings({"failed to read TUSummary from",
+ "failed to read JSON from file",
+ "tu_namespace.json"}));
+}
+
+TEST_F(JSONFormatReadTUNamespaceTest, NotJSONObject) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath, "[]");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings({"failed to read TUSummary from",
+ "failed to read JSON object from file",
+ "tu_namespace.json"}));
+}
+
+TEST_F(JSONFormatReadTUNamespaceTest, MissingKindField) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath, R"({"name": "test.cpp"})");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(
+ ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize BuildNamespace from file",
+ "tu_namespace.json",
+ "missing required field 'kind' (expected BuildNamespaceKind)"}));
+}
+
+TEST_F(JSONFormatReadTUNamespaceTest, InvalidKindValue) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath, R"({"kind": "InvalidKind", "name": "test.cpp"})");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize BuildNamespace from file",
+ "tu_namespace.json",
+ "invalid 'kind' BuildNamespaceKind value 'InvalidKind'"}));
+}
+
+TEST_F(JSONFormatReadTUNamespaceTest, MissingNameField) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath, R"({"kind": "compilation_unit"})");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize BuildNamespace from file",
+ "tu_namespace.json", "missing required field 'name'"}));
+}
+
+//===----------------------------------------------------------------------===//
+// TUSummary Read Tests - IdTable
+//===----------------------------------------------------------------------===//
+
+class JSONFormatReadIdTableTest : public JSONFormatTestBase {
+protected:
+ void SetUpValidTUNamespace(TempDir &Dir) {
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath,
+ R"({"kind": "compilation_unit", "name": "test.cpp"})");
+ }
+};
+
+TEST_F(JSONFormatReadIdTableTest, MissingIdTableFile) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespace(Dir);
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings({"failed to read TUSummary from",
+ "failed to read JSON from file",
+ "id_table.json", "file does not exist"}));
+}
+
+TEST_F(JSONFormatReadIdTableTest, NotJSONArray) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespace(Dir);
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, "{}");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings({"failed to read TUSummary from",
+ "failed to read JSON array from file",
+ "id_table.json"}));
+}
+
+TEST_F(JSONFormatReadIdTableTest, ElementNotObject) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespace(Dir);
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, "[\"not an object\"]");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(
+ ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize EntityIdTable from file", "id_table.json",
+ "element at index 0 is not a JSON object",
+ "expected EntityIdTable entry with 'id' and 'name' fields"}));
+}
+
+TEST_F(JSONFormatReadIdTableTest, EntryMissingName) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespace(Dir);
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, R"([{"id": 0}])");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize EntityIdTable entry from file",
+ "id_table.json",
+ "missing or invalid field 'name' (expected EntityName JSON "
+ "object)"}));
+}
+
+TEST_F(JSONFormatReadIdTableTest, EntryMissingId) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespace(Dir);
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, R"([{
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+ }
+ }])");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize EntityIdTable entry from file",
+ "id_table.json",
+ "missing required field 'id' (expected unsigned integer "
+ "EntityId)"}));
+}
+
+TEST_F(JSONFormatReadIdTableTest, EntryIdNotInteger) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespace(Dir);
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, R"([{
+ "id": "not a number",
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+ }
+ }])");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(
+ ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize EntityIdTable entry from file",
+ "id_table.json", "field 'id' is not a valid unsigned 64-bit integer",
+ "expected non-negative EntityId value"}));
+}
+
+TEST_F(JSONFormatReadIdTableTest, DuplicateEntityName) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespace(Dir);
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, R"([
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+ }
+ }
+ ])");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize EntityIdTable from file",
+ "id_table.json", "duplicate EntityName found at index 1",
+ "EntityId=0 already exists in table"}));
+}
+
+//===----------------------------------------------------------------------===//
+// TUSummary Read Tests - EntityName
+//===----------------------------------------------------------------------===//
+
+class JSONFormatReadEntityNameTest : public JSONFormatTestBase {
+protected:
+ void SetUpValidTUNamespaceAndPartialIdTable(TempDir &Dir,
+ llvm::StringRef EntityNameJson) {
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath,
+ R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ std::string JsonContent = "[{\"id\": 0, \"name\": ";
+ JsonContent += EntityNameJson.str();
+ JsonContent += "}]";
+ writeFile(IdTablePath, JsonContent);
+ }
+};
+
+TEST_F(JSONFormatReadEntityNameTest, MissingUSR) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+ "suffix": "",
+ "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+ })");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(
+ ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize EntityName from file", "id_table.json",
+ "missing required field 'usr' (Unified Symbol Resolution string)"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, MissingSuffix) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+ "usr": "c:@F at foo",
+ "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+ })");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize EntityName from file",
+ "id_table.json", "missing required field 'suffix'"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, MissingNamespace) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+ "usr": "c:@F at foo",
+ "suffix": ""
+ })");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize EntityName from file",
+ "id_table.json", "missing or invalid field 'namespace'",
+ "expected JSON array of BuildNamespace objects"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, NamespaceNotArray) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": "not an array"
+ })");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize EntityName from file",
+ "id_table.json", "missing or invalid field 'namespace'",
+ "expected JSON array of BuildNamespace objects"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, NamespaceElementNotObject) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": ["not an object"]
+ })");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize NestedBuildNamespace from file",
+ "id_table.json", "element at index 0 is not a JSON object",
+ "expected BuildNamespace object"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, NamespaceMissingKind) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [{"name": "test.cpp"}]
+ })");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(
+ ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize NestedBuildNamespace from file",
+ "id_table.json", "at index 0",
+ "failed to deserialize BuildNamespace from file",
+ "missing required field 'kind' (expected BuildNamespaceKind)"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, NamespaceInvalidKind) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [{"kind": "InvalidKind", "name": "test.cpp"}]
+ })");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize NestedBuildNamespace from file",
+ "id_table.json", "at index 0",
+ "failed to deserialize BuildNamespace from file",
+ "invalid 'kind' BuildNamespaceKind value 'InvalidKind'"}));
+}
+
+TEST_F(JSONFormatReadEntityNameTest, NamespaceMissingName) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [{"kind": "compilation_unit"}]
+ })");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize NestedBuildNamespace from file",
+ "id_table.json", "at index 0",
+ "failed to deserialize BuildNamespace from file",
+ "missing required field 'name'"}));
+}
+
+//===----------------------------------------------------------------------===//
+// TUSummary Read Tests - Data Directory and Files
+//===----------------------------------------------------------------------===//
+
+class JSONFormatReadDataTest : public JSONFormatTestBase {
+protected:
+ void SetUpValidTUNamespaceAndIdTable(TempDir &Dir) {
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath,
+ R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, "[]");
+ }
+};
+
+TEST_F(JSONFormatReadDataTest, MissingDataDirectory) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndIdTable(Dir);
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings({"failed to read TUSummary from",
+ "data directory does not exist"}));
+}
+
+TEST_F(JSONFormatReadDataTest, DataPathIsFile) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndIdTable(Dir);
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ writeFile(DataPath, "content");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings({"failed to read TUSummary from",
+ "data path is not a directory"}));
+}
+
+TEST_F(JSONFormatReadDataTest, NonJSONFileInDataDirectory) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndIdTable(Dir);
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ llvm::SmallString<128> SummaryPath(DataPath);
+ llvm::sys::path::append(SummaryPath, "summary.txt");
+ writeFile(SummaryPath, "{}");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to read TUSummary data from file", "summary.txt",
+ "failed to validate JSON file", "not a JSON file"}));
+}
+
+TEST_F(JSONFormatReadDataTest, FileNotJSONObject) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndIdTable(Dir);
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ llvm::SmallString<128> SummaryPath(DataPath);
+ llvm::sys::path::append(SummaryPath, "summary.json");
+ writeFile(SummaryPath, "[]");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings({"failed to read TUSummary from",
+ "failed to read TUSummary data from file",
+ "summary.json",
+ "failed to read JSON object from file"}));
+}
+
+TEST_F(JSONFormatReadDataTest, MissingSummaryName) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndIdTable(Dir);
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ llvm::SmallString<128> SummaryPath(DataPath);
+ llvm::sys::path::append(SummaryPath, "summary.json");
+ writeFile(SummaryPath, R"({"summary_data": []})");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize summary data from file",
+ "summary.json", "missing required field 'summary_name'",
+ "expected string identifier for the analysis summary"}));
+}
+
+TEST_F(JSONFormatReadDataTest, MissingSummaryData) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndIdTable(Dir);
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ llvm::SmallString<128> SummaryPath(DataPath);
+ llvm::sys::path::append(SummaryPath, "summary.json");
+ writeFile(SummaryPath, R"({"summary_name": "test"})");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg, ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize summary data from file",
+ "summary.json", "for summary 'test'",
+ "missing or invalid field 'summary_data'",
+ "expected JSON array of entity summaries"}));
+}
+
+TEST_F(JSONFormatReadDataTest, SummaryDataNotArray) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndIdTable(Dir);
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ llvm::SmallString<128> SummaryPath(DataPath);
+ llvm::sys::path::append(SummaryPath, "summary.json");
+ writeFile(SummaryPath,
+ R"({"summary_name": "test", "summary_data": "not an array"})");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg, ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize summary data from file",
+ "summary.json", "for summary 'test'",
+ "missing or invalid field 'summary_data'",
+ "expected JSON array of entity summaries"}));
+}
+
+TEST_F(JSONFormatReadDataTest, DuplicateSummaryName) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceAndIdTable(Dir);
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ llvm::SmallString<128> SummaryPath1(DataPath);
+ llvm::sys::path::append(SummaryPath1, "summary1.json");
+ writeFile(SummaryPath1, R"({"summary_name": "test", "summary_data": []})");
+
+ llvm::SmallString<128> SummaryPath2(DataPath);
+ llvm::sys::path::append(SummaryPath2, "summary2.json");
+ writeFile(SummaryPath2, R"({"summary_name": "test", "summary_data": []})");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(
+ ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from", "failed to read TUSummary data",
+ "duplicate SummaryName 'test' encountered in file"}));
+}
+
+//===----------------------------------------------------------------------===//
+// TUSummary Read Tests - Entity Data
+//===----------------------------------------------------------------------===//
+
+class JSONFormatReadEntityDataTest : public JSONFormatTestBase {
+protected:
+ void SetUpValidTUNamespaceIdTableAndDataDir(TempDir &Dir,
+ llvm::StringRef SummaryDataJson) {
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath,
+ R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, "[]");
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ llvm::SmallString<128> SummaryPath(DataPath);
+ llvm::sys::path::append(SummaryPath, "summary.json");
+ std::string JsonContent = R"({"summary_name": "test", "summary_data": )";
+ JsonContent += SummaryDataJson.str();
+ JsonContent += "}";
+ writeFile(SummaryPath, JsonContent);
+ }
+};
+
+TEST_F(JSONFormatReadEntityDataTest, ElementNotObject) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceIdTableAndDataDir(Dir, "[\"not an object\"]");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(
+ ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize entity data map from file", "summary.json",
+ "element at index 0 is not a JSON object",
+ "expected object with 'entity_id' and 'entity_summary' fields"}));
+}
+
+TEST_F(JSONFormatReadEntityDataTest, MissingEntityId) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceIdTableAndDataDir(Dir, R"([{"entity_summary": {}}])");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(
+ ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize entity data map entry from file",
+ "summary.json", "at index 0", "missing required field 'entity_id'",
+ "expected unsigned integer EntityId"}));
+}
+
+TEST_F(JSONFormatReadEntityDataTest, EntityIdNotInteger) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceIdTableAndDataDir(
+ Dir, R"([{"entity_id": "not a number", "entity_summary": {}}])");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(
+ ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize entity data map entry from file",
+ "summary.json", "at index 0",
+ "field 'entity_id' is not a valid unsigned 64-bit integer"}));
+}
+
+TEST_F(JSONFormatReadEntityDataTest, MissingEntitySummary) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceIdTableAndDataDir(Dir, R"([{"entity_id": 0}])");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize entity data map entry from file",
+ "summary.json", "at index 0",
+ "missing or invalid field 'entity_summary'",
+ "expected EntitySummary JSON object"}));
+}
+
+TEST_F(JSONFormatReadEntityDataTest, EntitySummaryNotObject) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceIdTableAndDataDir(
+ Dir, R"([{"entity_id": 0, "entity_summary": "not an object"}])");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize entity data map entry from file",
+ "summary.json", "at index 0",
+ "missing or invalid field 'entity_summary'",
+ "expected EntitySummary JSON object"}));
+}
+
+TEST_F(JSONFormatReadEntityDataTest,
+ EntitySummaryDeserializationNotImplemented) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+ SetUpValidTUNamespaceIdTableAndDataDir(
+ Dir, R"([{"entity_id": 0, "entity_summary": {}}])");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ ASSERT_FALSE(Result);
+ std::string ErrorMsg = llvm::toString(Result.takeError());
+ EXPECT_THAT(ErrorMsg,
+ ContainsAllSubstrings(
+ {"failed to read TUSummary from",
+ "failed to deserialize entity data map entry from file",
+ "summary.json", "at index 0",
+ "EntitySummary deserialization from file",
+ "is not yet implemented"}));
+}
+
+// Note: DuplicateEntityId test cannot be implemented without EntitySummary
+// deserialization support, as the error occurs during EntitySummary parsing
+// before the duplicate check is reached.
+
+//===----------------------------------------------------------------------===//
+// TUSummary Write Tests
+//===----------------------------------------------------------------------===//
+
+class JSONFormatWriteTest : public JSONFormatTestBase {};
+
+TEST_F(JSONFormatWriteTest, Success) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ BuildNamespace TUNamespace = BuildNamespace::makeCompilationUnit("test.cpp");
+ TUSummary Summary(TUNamespace);
+
+ auto Error = Format.writeTUSummary(Summary, Dir.path());
+ EXPECT_THAT_ERROR(std::move(Error), Succeeded());
+
+ // Verify files were created
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ EXPECT_TRUE(llvm::sys::fs::exists(TUNamespacePath));
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ EXPECT_TRUE(llvm::sys::fs::exists(IdTablePath));
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ EXPECT_TRUE(llvm::sys::fs::exists(DataPath));
+ EXPECT_TRUE(llvm::sys::fs::is_directory(DataPath));
+}
+
+TEST_F(JSONFormatWriteTest, DataDirectoryExistsAsFile) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ // Create 'data' as a file instead of directory
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ writeFile(DataPath, "content");
+
+ BuildNamespace TUNamespace = BuildNamespace::makeCompilationUnit("test.cpp");
+ TUSummary Summary(TUNamespace);
+
+ auto Error = Format.writeTUSummary(Summary, Dir.path());
+ ASSERT_TRUE(!!Error);
+ std::string ErrorMsg = llvm::toString(std::move(Error));
+ EXPECT_THAT(ErrorMsg, ContainsAllSubstrings(
+ {"failed to write TUSummary to",
+ "data path exists but is not a directory"}));
+}
+
+//===----------------------------------------------------------------------===//
+// TUSummary Success Cases
+//===----------------------------------------------------------------------===//
+
+class JSONFormatSuccessTest : public JSONFormatTestBase {};
+
+TEST_F(JSONFormatSuccessTest, ReadWithEmptyIdTable) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath,
+ R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, "[]");
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ auto Result = Format.readTUSummary(Dir.path());
+ EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
+}
+
+TEST_F(JSONFormatSuccessTest, ReadWithNonEmptyIdTable) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath,
+ R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, R"([
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "usr": "c:@F at bar",
+ "suffix": "1",
+ "namespace": [
+ {"kind": "compilation_unit", "name": "test.cpp"},
+ {"kind": "link_unit", "name": "libtest.so"}
+ ]
+ }
+ }
+ ])");
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ auto Result = Format.readTUSummary(Dir.path());
+ EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
+}
+
+TEST_F(JSONFormatSuccessTest, ReadWithEmptyData) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath,
+ R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, "[]");
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ // Add an empty summary data file
+ llvm::SmallString<128> SummaryPath(DataPath);
+ llvm::sys::path::append(SummaryPath, "summary.json");
+ writeFile(SummaryPath, R"({"summary_name": "test", "summary_data": []})");
+
+ auto Result = Format.readTUSummary(Dir.path());
+ EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
+}
+
+TEST_F(JSONFormatSuccessTest, ReadWithLinkUnitNamespace) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath, R"({"kind": "link_unit", "name": "libtest.so"})");
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, "[]");
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ auto Result = Format.readTUSummary(Dir.path());
+ EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
+}
+
+//===----------------------------------------------------------------------===//
+// Round-Trip Tests
+//===----------------------------------------------------------------------===//
+
+class JSONFormatRoundTripTest : public JSONFormatTestBase {};
+
+TEST_F(JSONFormatRoundTripTest, EmptyIdTable) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ BuildNamespace TUNamespace = BuildNamespace::makeCompilationUnit("test.cpp");
+ TUSummary Summary(TUNamespace);
+
+ auto WriteError = Format.writeTUSummary(Summary, Dir.path());
+ EXPECT_THAT_ERROR(std::move(WriteError), Succeeded());
+
+ auto ReadResult = Format.readTUSummary(Dir.path());
+ EXPECT_THAT_ERROR(ReadResult.takeError(), Succeeded());
+}
+
+TEST_F(JSONFormatRoundTripTest, NonEmptyIdTable) {
+ TempDir Dir;
+ ASSERT_TRUE(Dir.isValid());
+
+ // Manually create the files to test roundtrip
+ llvm::SmallString<128> TUNamespacePath(Dir.path());
+ llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
+ writeFile(TUNamespacePath,
+ R"({"kind": "compilation_unit", "name": "test.cpp"})");
+
+ llvm::SmallString<128> IdTablePath(Dir.path());
+ llvm::sys::path::append(IdTablePath, "id_table.json");
+ writeFile(IdTablePath, R"([
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
+ }
+ }
+ ])");
+
+ llvm::SmallString<128> DataPath(Dir.path());
+ llvm::sys::path::append(DataPath, "data");
+ createDir(DataPath);
+
+ auto ReadResult = Format.readTUSummary(Dir.path());
+ ASSERT_THAT_EXPECTED(ReadResult, Succeeded());
+
+ TempDir Dir2;
+ ASSERT_TRUE(Dir2.isValid());
+
+ auto WriteError = Format.writeTUSummary(*ReadResult, Dir2.path());
+ EXPECT_THAT_ERROR(std::move(WriteError), Succeeded());
+
+ // Verify the written files
+ llvm::SmallString<128> TUNamespacePath2(Dir2.path());
+ llvm::sys::path::append(TUNamespacePath2, "tu_namespace.json");
+ EXPECT_TRUE(llvm::sys::fs::exists(TUNamespacePath2));
+}
+
+} // namespace
>From 47ed1daa2b4db92b08656ed509e95410e92d378d Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 5 Feb 2026 13:51:50 -0800
Subject: [PATCH 4/7] Fix return types, incorporate VFS APIs, and fix tests.
---
.../Scalable/Serialization/JSONFormat.h | 3 +-
.../Serialization/SerializationFormat.h | 21 +--
.../Scalable/Serialization/JSONFormat.cpp | 132 ++++++++++++------
.../Serialization/SerializationFormat.cpp | 40 ++----
.../Analysis/Scalable/JSONFormatTest.cpp | 2 +-
.../Registries/MockSerializationFormat.cpp | 46 +++---
.../Registries/MockSerializationFormat.h | 6 +-
.../SerializationFormatRegistryTest.cpp | 7 +-
8 files changed, 151 insertions(+), 106 deletions(-)
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
index 476853af0dce4..23e37138d6926 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -23,7 +23,8 @@ class SummaryName;
class JSONFormat : public SerializationFormat {
public:
- JSONFormat() = default;
+ explicit JSONFormat(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
+ : SerializationFormat(FS) {}
~JSONFormat() = default;
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index 1d3df0dc55e08..54098939cdfe4 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -42,21 +42,16 @@ class SerializationFormat {
static size_t getEntityIdIndex(const EntityId &EI);
static EntityId makeEntityId(const size_t Index);
- static const std::map<EntityName, EntityId> &
+ static const decltype(EntityIdTable::Entities) &
getEntities(const EntityIdTable &EIT);
- static std::map<EntityName, EntityId> &
- getEntitiesForDeserialization(EntityIdTable &EIT);
-
- static EntityIdTable &getIdTableForDeserialization(TUSummary &S);
- static BuildNamespace &getTUNamespaceForDeserialization(TUSummary &S);
- static std::map<SummaryName,
- std::map<EntityId, std::unique_ptr<EntitySummary>>> &
- getDataForDeserialization(TUSummary &S);
+ static decltype(EntityIdTable::Entities) &getEntities(EntityIdTable &EIT);
+
static const EntityIdTable &getIdTable(const TUSummary &S);
+ static EntityIdTable &getIdTable(TUSummary &S);
static const BuildNamespace &getTUNamespace(const TUSummary &S);
- static const std::map<SummaryName,
- std::map<EntityId, std::unique_ptr<EntitySummary>>> &
- getData(const TUSummary &S);
+ static BuildNamespace &getTUNamespace(TUSummary &S);
+ static const decltype(TUSummary::Data) &getData(const TUSummary &S);
+ static decltype(TUSummary::Data) &getData(TUSummary &S);
static BuildNamespaceKind getBuildNamespaceKind(const BuildNamespace &BN);
static llvm::StringRef getBuildNamespaceName(const BuildNamespace &BN);
@@ -67,8 +62,6 @@ class SerializationFormat {
static const llvm::SmallString<16> &getEntityNameSuffix(const EntityName &EN);
static const NestedBuildNamespace &
getEntityNameNamespace(const EntityName &EN);
- static decltype(TUSummary::Data) &getData(TUSummary &S);
- static const decltype(TUSummary::Data) &getData(const TUSummary &S);
public:
explicit SerializationFormat(
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index cca3ca36eb9ae..7a9d301c2791b 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -38,8 +38,8 @@ llvm::Error wrapError(llvm::Error E, const char *Fmt, Args &&...Vals) {
// JSON Reader and Writer
//----------------------------------------------------------------------------
-llvm::Error isJSONFile(llvm::StringRef Path) {
- if (!llvm::sys::fs::exists(Path))
+llvm::Error isJSONFile(llvm::vfs::FileSystem &FS, llvm::StringRef Path) {
+ if (!FS.exists(Path))
return llvm::createStringError(std::errc::no_such_file_or_directory,
"file does not exist: '%s'",
Path.str().c_str());
@@ -51,12 +51,13 @@ llvm::Error isJSONFile(llvm::StringRef Path) {
return llvm::Error::success();
}
-llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
- if (llvm::Error Err = isJSONFile(Path))
+llvm::Expected<llvm::json::Value> readJSON(llvm::vfs::FileSystem &FS,
+ llvm::StringRef Path) {
+ if (llvm::Error Err = isJSONFile(FS, Path))
return wrapError(std::move(Err), "failed to validate JSON file '%s'",
Path.str().c_str());
- auto BufferOrError = llvm::MemoryBuffer::getFile(Path);
+ auto BufferOrError = FS.getBufferForFile(Path);
if (!BufferOrError) {
return llvm::createStringError(BufferOrError.getError(),
"failed to read file '%s'",
@@ -66,8 +67,9 @@ llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
return llvm::json::parse(BufferOrError.get()->getBuffer());
}
-llvm::Expected<llvm::json::Object> readJSONObject(llvm::StringRef Path) {
- auto ExpectedJSON = readJSON(Path);
+llvm::Expected<llvm::json::Object> readJSONObject(llvm::vfs::FileSystem &FS,
+ llvm::StringRef Path) {
+ auto ExpectedJSON = readJSON(FS, Path);
if (!ExpectedJSON)
return wrapError(ExpectedJSON.takeError(),
"failed to read JSON from file '%s'", Path.str().c_str());
@@ -81,8 +83,9 @@ llvm::Expected<llvm::json::Object> readJSONObject(llvm::StringRef Path) {
return std::move(*Object);
}
-llvm::Expected<llvm::json::Array> readJSONArray(llvm::StringRef Path) {
- auto ExpectedJSON = readJSON(Path);
+llvm::Expected<llvm::json::Array> readJSONArray(llvm::vfs::FileSystem &FS,
+ llvm::StringRef Path) {
+ auto ExpectedJSON = readJSON(FS, Path);
if (!ExpectedJSON)
return wrapError(ExpectedJSON.takeError(),
"failed to read JSON from file '%s'", Path.str().c_str());
@@ -96,19 +99,28 @@ llvm::Expected<llvm::json::Array> readJSONArray(llvm::StringRef Path) {
return std::move(*Array);
}
-llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
+llvm::Error writeJSON(llvm::vfs::FileSystem &FS, llvm::json::Value &&Value,
+ llvm::StringRef Path) {
+ std::string Content;
+ llvm::raw_string_ostream OS(Content);
+ OS << llvm::formatv("{0:2}\n", Value);
+ OS.flush();
+
std::error_code EC;
- llvm::raw_fd_ostream OS(Path, EC);
+
+ // For real file system, we can use the atomic file approach
+ // For VFS, we need to write the content directly
+ llvm::raw_fd_ostream OutStream(Path, EC, llvm::sys::fs::OF_Text);
if (EC) {
return llvm::createStringError(EC, "failed to open '%s'",
Path.str().c_str());
}
- OS << llvm::formatv("{0:2}\n", Value);
+ OutStream << Content;
+ OutStream.flush();
- OS.flush();
- if (OS.has_error()) {
- return llvm::createStringError(OS.error(), "write failed");
+ if (OutStream.has_error()) {
+ return llvm::createStringError(OutStream.error(), "write failed");
}
return llvm::Error::success();
@@ -365,8 +377,7 @@ JSONFormat::entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray,
const size_t EntityCount = EntityIdTableArray.size();
EntityIdTable IdTable;
- std::map<EntityName, EntityId> &Entities =
- getEntitiesForDeserialization(IdTable);
+ std::map<EntityName, EntityId> &Entities = getEntities(IdTable);
for (size_t Index = 0; Index < EntityCount; ++Index) {
const llvm::json::Value &EntityIdTableEntryValue =
@@ -596,14 +607,22 @@ llvm::json::Object JSONFormat::summaryDataMapEntryToJSON(
llvm::Expected<
std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
JSONFormat::readTUSummaryData(llvm::StringRef Path) {
- if (!llvm::sys::fs::exists(Path)) {
+ if (!FS->exists(Path)) {
return llvm::createStringError(
std::errc::no_such_file_or_directory,
"failed to read TUSummary data: directory does not exist: '%s'",
Path.str().c_str());
}
- if (!llvm::sys::fs::is_directory(Path)) {
+ auto StatusOrErr = FS->status(Path);
+ if (!StatusOrErr) {
+ return llvm::createStringError(
+ StatusOrErr.getError(),
+ "failed to read TUSummary data: cannot get status of '%s'",
+ Path.str().c_str());
+ }
+
+ if (!StatusOrErr->isDirectory()) {
return llvm::createStringError(
std::errc::not_a_directory,
"failed to read TUSummary data: path is not a directory: '%s'",
@@ -614,18 +633,19 @@ JSONFormat::readTUSummaryData(llvm::StringRef Path) {
Data;
std::error_code EC;
- llvm::sys::fs::directory_iterator Dir(Path, EC);
+ auto DirIter = FS->dir_begin(Path, EC);
if (EC) {
return llvm::createStringError(
EC, "failed to read TUSummary data: cannot iterate directory '%s'",
Path.str().c_str());
}
- for (llvm::sys::fs::directory_iterator End; Dir != End && !EC;
- Dir.increment(EC)) {
- std::string SummaryPath = Dir->path();
+ for (llvm::vfs::directory_iterator End; DirIter != End && !EC;
+ DirIter.increment(EC)) {
+ llvm::StringRef SummaryPathRef = DirIter->path();
+ std::string SummaryPath = SummaryPathRef.str();
- auto ExpectedObject = readJSONObject(SummaryPath);
+ auto ExpectedObject = readJSONObject(*FS, SummaryPath);
if (!ExpectedObject)
return wrapError(ExpectedObject.takeError(),
"failed to read TUSummary data from file '%s'",
@@ -680,14 +700,22 @@ llvm::Error JSONFormat::writeTUSummaryData(
const std::map<SummaryName,
std::map<EntityId, std::unique_ptr<EntitySummary>>> &Data,
llvm::StringRef Path) {
- if (!llvm::sys::fs::exists(Path)) {
+ if (!FS->exists(Path)) {
return llvm::createStringError(
std::errc::no_such_file_or_directory,
"failed to write TUSummary data: directory does not exist: '%s'",
Path.str().c_str());
}
- if (!llvm::sys::fs::is_directory(Path)) {
+ auto StatusOrErr = FS->status(Path);
+ if (!StatusOrErr) {
+ return llvm::createStringError(
+ StatusOrErr.getError(),
+ "failed to write TUSummary data: cannot get status of '%s'",
+ Path.str().c_str());
+ }
+
+ if (!StatusOrErr->isDirectory()) {
return llvm::createStringError(
std::errc::not_a_directory,
"failed to write TUSummary data: path is not a directory: '%s'",
@@ -702,7 +730,7 @@ llvm::Error JSONFormat::writeTUSummaryData(
llvm::sys::path::replace_extension(SummaryPath, ".json");
llvm::json::Object Result = summaryDataMapEntryToJSON(SummaryName, DataMap);
- if (auto Error = writeJSON(std::move(Result), SummaryPath)) {
+ if (auto Error = writeJSON(*FS, std::move(Result), SummaryPath)) {
return wrapError(
std::move(Error), std::errc::io_error,
"failed to write TUSummary data to directory '%s': cannot write "
@@ -727,7 +755,7 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
llvm::SmallString<kPathBufferSize> TUNamespacePath(Path);
llvm::sys::path::append(TUNamespacePath, TUSummaryTUNamespaceFilename);
- auto ExpectedObject = readJSONObject(TUNamespacePath);
+ auto ExpectedObject = readJSONObject(*FS, TUNamespacePath);
if (!ExpectedObject)
return wrapError(ExpectedObject.takeError(),
"failed to read TUSummary from '%s'", Path.str().c_str());
@@ -745,7 +773,7 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
llvm::SmallString<kPathBufferSize> IdTablePath(Path);
llvm::sys::path::append(IdTablePath, TUSummaryIdTableFilename);
- auto ExpectedArray = readJSONArray(IdTablePath);
+ auto ExpectedArray = readJSONArray(*FS, IdTablePath);
if (!ExpectedArray)
return wrapError(ExpectedArray.takeError(),
"failed to read TUSummary from '%s'",
@@ -757,7 +785,7 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
"failed to read TUSummary from '%s'",
Path.str().c_str());
- getIdTableForDeserialization(Summary) = std::move(*ExpectedIdTable);
+ getIdTable(Summary) = std::move(*ExpectedIdTable);
}
// Populate Data field.
@@ -765,14 +793,22 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
llvm::SmallString<kPathBufferSize> DataPath(Path);
llvm::sys::path::append(DataPath, TUSummaryDataDirname);
- if (!llvm::sys::fs::exists(DataPath)) {
+ if (!FS->exists(DataPath)) {
return llvm::createStringError(std::errc::no_such_file_or_directory,
"failed to read TUSummary from '%s': "
"data directory does not exist: '%s'",
Path.str().c_str(), DataPath.str().data());
}
- if (!llvm::sys::fs::is_directory(DataPath)) {
+ auto StatusOrErr = FS->status(DataPath);
+ if (!StatusOrErr) {
+ return llvm::createStringError(StatusOrErr.getError(),
+ "failed to read TUSummary from '%s': "
+ "cannot get status of data path: '%s'",
+ Path.str().c_str(), DataPath.str().data());
+ }
+
+ if (!StatusOrErr->isDirectory()) {
return llvm::createStringError(std::errc::not_a_directory,
"failed to read TUSummary from '%s': "
"data path is not a directory: '%s'",
@@ -785,7 +821,7 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
"failed to read TUSummary from '%s'",
Path.str().c_str());
- getDataForDeserialization(Summary) = std::move(*ExpectedData);
+ getData(Summary) = std::move(*ExpectedData);
}
return Summary;
@@ -800,7 +836,8 @@ llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
llvm::json::Object BuildNamespaceObj =
buildNamespaceToJSON(getTUNamespace(S));
- if (auto Error = writeJSON(std::move(BuildNamespaceObj), TUNamespacePath)) {
+ if (auto Error =
+ writeJSON(*FS, std::move(BuildNamespaceObj), TUNamespacePath)) {
return wrapError(std::move(Error), std::errc::io_error,
"failed to write TUSummary to '%s': "
"cannot write TUNamespace file '%s'",
@@ -814,7 +851,7 @@ llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
llvm::sys::path::append(IdTablePath, TUSummaryIdTableFilename);
llvm::json::Array IdTableObj = entityIdTableToJSON(getIdTable(S));
- if (auto Error = writeJSON(std::move(IdTableObj), IdTablePath)) {
+ if (auto Error = writeJSON(*FS, std::move(IdTableObj), IdTablePath)) {
return wrapError(std::move(Error), std::errc::io_error,
"failed to write TUSummary to '%s': "
"cannot write IdTable file '%s'",
@@ -828,6 +865,8 @@ llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
llvm::sys::path::append(DataPath, TUSummaryDataDirname);
// Create the data directory if it doesn't exist
+ // Use the real filesystem for directory creation as VFS doesn't always
+ // support this
if (std::error_code EC = llvm::sys::fs::create_directory(DataPath)) {
// If error is not "already exists", return error
if (EC != std::errc::file_exists) {
@@ -840,13 +879,22 @@ llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
}
// Verify it's a directory (could be a file with the same name)
- if (llvm::sys::fs::exists(DataPath) &&
- !llvm::sys::fs::is_directory(DataPath)) {
- return llvm::createStringError(
- std::errc::not_a_directory,
- "failed to write TUSummary to '%s': data path exists but is not a "
- "directory: '%s'",
- Dir.str().c_str(), DataPath.str().data());
+ if (FS->exists(DataPath)) {
+ auto StatusOrErr = FS->status(DataPath);
+ if (!StatusOrErr) {
+ return llvm::createStringError(StatusOrErr.getError(),
+ "failed to write TUSummary to '%s': "
+ "cannot get status of data path '%s'",
+ Dir.str().c_str(),
+ DataPath.str().data());
+ }
+ if (!StatusOrErr->isDirectory()) {
+ return llvm::createStringError(
+ std::errc::not_a_directory,
+ "failed to write TUSummary to '%s': data path exists but is not a "
+ "directory: '%s'",
+ Dir.str().c_str(), DataPath.str().data());
+ }
}
if (auto Error = writeTUSummaryData(getData(S), DataPath)) {
diff --git a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
index 1208780510b18..02f64f112761a 100644
--- a/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/SerializationFormat.cpp
@@ -26,31 +26,21 @@ EntityId SerializationFormat::makeEntityId(const size_t Index) {
return EntityId(Index);
}
-const std::map<EntityName, EntityId> &
+const decltype(EntityIdTable::Entities) &
SerializationFormat::getEntities(const EntityIdTable &EIT) {
return EIT.Entities;
}
-std::map<EntityName, EntityId> &
-SerializationFormat::getEntitiesForDeserialization(EntityIdTable &EIT) {
+decltype(EntityIdTable::Entities) &
+SerializationFormat::getEntities(EntityIdTable &EIT) {
return EIT.Entities;
}
-EntityIdTable &SerializationFormat::getIdTableForDeserialization(TUSummary &S) {
+const EntityIdTable &SerializationFormat::getIdTable(const TUSummary &S) {
return S.IdTable;
}
-BuildNamespace &
-SerializationFormat::getTUNamespaceForDeserialization(TUSummary &S) {
- return S.TUNamespace;
-}
-
-std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>> &
-SerializationFormat::getDataForDeserialization(TUSummary &S) {
- return S.Data;
-}
-
-const EntityIdTable &SerializationFormat::getIdTable(const TUSummary &S) {
+EntityIdTable &SerializationFormat::getIdTable(TUSummary &S) {
return S.IdTable;
}
@@ -58,12 +48,19 @@ const BuildNamespace &SerializationFormat::getTUNamespace(const TUSummary &S) {
return S.TUNamespace;
}
-const std::map<SummaryName,
- std::map<EntityId, std::unique_ptr<EntitySummary>>> &
+BuildNamespace &SerializationFormat::getTUNamespace(TUSummary &S) {
+ return S.TUNamespace;
+}
+
+const decltype(TUSummary::Data) &
SerializationFormat::getData(const TUSummary &S) {
return S.Data;
}
+decltype(TUSummary::Data) &SerializationFormat::getData(TUSummary &S) {
+ return S.Data;
+}
+
BuildNamespaceKind
SerializationFormat::getBuildNamespaceKind(const BuildNamespace &BN) {
return BN.Kind;
@@ -92,12 +89,3 @@ const NestedBuildNamespace &
SerializationFormat::getEntityNameNamespace(const EntityName &EN) {
return EN.Namespace;
}
-
-const decltype(TUSummary::Data) &
-SerializationFormat::getData(const TUSummary &S) {
- return S.Data;
-}
-
-decltype(TUSummary::Data) &SerializationFormat::getData(TUSummary &S) {
- return S.Data;
-}
diff --git a/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
index db38ce23565da..eb2ed77976bda 100644
--- a/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
+++ b/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
@@ -78,7 +78,7 @@ void createDir(llvm::StringRef Path) {
// Base test fixture for JSONFormat tests
class JSONFormatTestBase : public ::testing::Test {
protected:
- JSONFormat Format;
+ JSONFormat Format{llvm::vfs::getRealFileSystem()};
};
//===----------------------------------------------------------------------===//
diff --git a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
index 44904d53d2412..2ff75c9c5b1c3 100644
--- a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
@@ -40,12 +40,16 @@ MockSerializationFormat::MockSerializationFormat(
}
}
-TUSummary MockSerializationFormat::readTUSummary(llvm::StringRef Path) {
+llvm::Expected<TUSummary>
+MockSerializationFormat::readTUSummary(llvm::StringRef Path) {
BuildNamespace NS(BuildNamespaceKind::CompilationUnit, "Mock.cpp");
TUSummary Summary(NS);
auto ManifestFile = FS->getBufferForFile(Path + "/analyses.txt");
- assert(ManifestFile); // TODO Handle error.
+ if (!ManifestFile) {
+ return llvm::createStringError(ManifestFile.getError(),
+ "Failed to read manifest file");
+ }
llvm::StringRef ManifestFileContent = (*ManifestFile)->getBuffer();
llvm::SmallVector<llvm::StringRef, 5> Analyses;
@@ -55,20 +59,24 @@ TUSummary MockSerializationFormat::readTUSummary(llvm::StringRef Path) {
for (llvm::StringRef Analysis : Analyses) {
SummaryName Name(Analysis.str());
auto InputFile = FS->getBufferForFile(Path + "/" + Name.str() + ".special");
- assert(InputFile);
+ if (!InputFile) {
+ return llvm::createStringError(InputFile.getError(),
+ "Failed to read analysis file");
+ }
auto InfoIt = FormatInfos.find(Name);
if (InfoIt == FormatInfos.end()) {
- llvm::report_fatal_error(
+ return llvm::createStringError(
+ std::make_error_code(std::errc::invalid_argument),
"No FormatInfo was registered for summary name: " + Name.str());
}
const auto &InfoEntry = InfoIt->second;
assert(InfoEntry.ForSummary == Name);
SpecialFileRepresentation Repr{(*InputFile)->getBuffer().str()};
- auto &Table = getIdTableForDeserialization(Summary);
+ auto &Table = getIdTable(Summary);
std::unique_ptr<EntitySummary> Result = InfoEntry.Deserialize(Repr, Table);
- if (!Result) // TODO: Handle error.
+ if (!Result)
continue;
EntityId FooId = Table.getId(EntityName{"c:@F at foo", "", /*Namespace=*/{}});
@@ -81,16 +89,16 @@ TUSummary MockSerializationFormat::readTUSummary(llvm::StringRef Path) {
return Summary;
}
-void MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
- llvm::StringRef OutputDir) {
+llvm::Error MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
+ llvm::StringRef OutputDir) {
std::error_code EC;
// Check if output directory exists, create if needed
if (!llvm::sys::fs::exists(OutputDir)) {
EC = llvm::sys::fs::create_directories(OutputDir);
if (EC) {
- llvm::report_fatal_error("Failed to create output directory '" +
- OutputDir + "': " + EC.message());
+ return llvm::createStringError(EC, "Failed to create output directory '" +
+ OutputDir + "': " + EC.message());
}
}
@@ -101,9 +109,10 @@ void MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
for (const auto &Data : llvm::make_second_range(EntityMappings)) {
auto InfoIt = FormatInfos.find(SummaryName);
if (InfoIt == FormatInfos.end()) {
- llvm::report_fatal_error(
+ return llvm::createStringError(
+ std::make_error_code(std::errc::invalid_argument),
"There was no FormatInfo registered for summary name '" +
- SummaryName.str() + "'");
+ SummaryName.str() + "'");
}
const auto &InfoEntry = InfoIt->second;
assert(InfoEntry.ForSummary == SummaryName);
@@ -114,8 +123,9 @@ void MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
(OutputDir + "/" + SummaryName.str() + ".special").str();
llvm::raw_fd_ostream AnalysisOutputFile(AnalysisFilePath, EC);
if (EC) {
- llvm::report_fatal_error("Failed to create file '" + AnalysisFilePath +
- "': " + llvm::StringRef(EC.message()));
+ return llvm::createStringError(
+ EC, "Failed to create file '" + AnalysisFilePath +
+ "': " + llvm::StringRef(EC.message()));
}
AnalysisOutputFile << Output.MockRepresentation;
}
@@ -124,14 +134,16 @@ void MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
std::string ManifestFilePath = (OutputDir + "/analyses.txt").str();
llvm::raw_fd_ostream ManifestFile(ManifestFilePath, EC);
if (EC) {
- llvm::report_fatal_error("Failed to create manifest file '" +
- ManifestFilePath +
- "': " + llvm::StringRef(EC.message()));
+ return llvm::createStringError(
+ EC, "Failed to create manifest file '" + ManifestFilePath +
+ "': " + llvm::StringRef(EC.message()));
}
interleave(map_range(Analyses, std::mem_fn(&SummaryName::str)), ManifestFile,
"\n");
ManifestFile << "\n";
+
+ return llvm::Error::success();
}
static SerializationFormatRegistry::Add<MockSerializationFormat>
diff --git a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h
index a106e53fc20ac..c8b146821a91f 100644
--- a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h
+++ b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h
@@ -21,10 +21,10 @@ class MockSerializationFormat final : public SerializationFormat {
explicit MockSerializationFormat(
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS);
- TUSummary readTUSummary(llvm::StringRef Path) override;
+ llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) override;
- void writeTUSummary(const TUSummary &Summary,
- llvm::StringRef OutputDir) override;
+ llvm::Error writeTUSummary(const TUSummary &Summary,
+ llvm::StringRef OutputDir) override;
struct SpecialFileRepresentation {
std::string MockRepresentation;
diff --git a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
index 484c38309e3d8..79a4adf218aac 100644
--- a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
@@ -68,7 +68,9 @@ TEST(SerializationFormatRegistryTest, Roundtrip) {
makeFormat(Inputs, "MockSerializationFormat");
ASSERT_TRUE(Format);
- TUSummary LoadedSummary = Format->readTUSummary("input");
+ auto LoadedSummaryOrErr = Format->readTUSummary("input");
+ ASSERT_TRUE(!!LoadedSummaryOrErr);
+ TUSummary LoadedSummary = std::move(*LoadedSummaryOrErr);
// Create a temporary output directory
SmallString<128> OutputDir;
@@ -77,7 +79,8 @@ TEST(SerializationFormatRegistryTest, Roundtrip) {
llvm::scope_exit CleanupOnExit(
[&] { sys::fs::remove_directories(OutputDir); });
- Format->writeTUSummary(LoadedSummary, OutputDir);
+ auto WriteErr = Format->writeTUSummary(LoadedSummary, OutputDir);
+ ASSERT_FALSE(!!WriteErr);
EXPECT_EQ(readFilesFromDir(OutputDir),
(std::map<std::string, std::string>{
>From b45cf5fc2bcb870c68d582ec731fc481b3b956f1 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 12:22:53 -0800
Subject: [PATCH 5/7] Fix JSONFormat to use a single file and add tests
---
.../Scalable/Serialization/JSONFormat.h | 82 ++-
.../Scalable/Serialization/JSONFormat.cpp | 640 ++++++------------
.../error-callgraph-invalid-caller.test | 8 +
.../error-callgraph-missing-field.test | 8 +
.../error-data-element-not-object.test | 6 +
.../error-data-entry-missing-data.test | 8 +
.../error-data-missing-summary-name.test | 7 +
.../Serialization/error-data-not-array.test | 7 +
.../error-defuse-invalid-use.test | 8 +
.../error-defuse-missing-field.test | 8 +
.../Serialization/error-duplicate-entity.test | 8 +
.../error-duplicate-summary-name.test | 6 +
.../error-entity-data-element-not-object.test | 8 +
.../error-entity-data-missing-entity-id.test | 9 +
...or-entity-data-missing-entity-summary.test | 9 +
.../error-entity-id-not-uint64.test | 8 +
.../error-entity-name-missing-namespace.test | 9 +
.../error-entity-name-missing-suffix.test | 8 +
.../error-entity-name-missing-usr.test | 9 +
.../error-entity-summary-no-format-info.test | 7 +
.../error-id-table-element-not-object.test | 8 +
.../error-id-table-entry-id-not-uint64.test | 8 +
.../error-id-table-entry-missing-id.test | 8 +
.../error-id-table-entry-missing-name.test | 8 +
.../error-id-table-not-array.test | 7 +
.../Serialization/error-invalid-kind.test | 7 +
.../Serialization/error-invalid-syntax.test | 6 +
.../Serialization/error-missing-data.test | 7 +
.../Serialization/error-missing-id-table.test | 7 +
.../Serialization/error-missing-kind.test | 8 +
.../Serialization/error-missing-name.test | 7 +
.../error-missing-tu-namespace.test | 7 +
.../error-namespace-element-not-object.test | 10 +
.../Serialization/error-nonexistent-file.test | 6 +
.../error-not-json-extension.test | 8 +
.../Serialization/error-not-object.test | 6 +
.../Serialization/valid-callgraph.test | 9 +
.../Scalable/Serialization/valid-defuse.test | 10 +
.../Scalable/Serialization/valid-empty.test | 4 +
.../Serialization/valid-link-unit.test | 4 +
.../valid-with-empty-data-entry.test | 4 +
.../Serialization/valid-with-idtable.test | 4 +
clang/test/lit.cfg.py | 1 +
.../Analysis/Scalable/CMakeLists.txt | 3 +
.../Analysis/Scalable/tools/CMakeLists.txt | 1 +
.../CMakeLists.txt | 9 +
.../ExampleAnalyses.cpp | 303 +++++++++
.../SSAFSerializationFormatTest.cpp | 84 +++
48 files changed, 975 insertions(+), 442 deletions(-)
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-data-not-array.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-id-table-not-array.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-data.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-id-table.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-name.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-missing-tu-namespace.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-nonexistent-file.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/error-not-object.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/valid-callgraph.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/valid-defuse.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/valid-empty.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/valid-link-unit.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/valid-with-empty-data-entry.test
create mode 100644 clang/test/Analysis/Scalable/Serialization/valid-with-idtable.test
create mode 100644 clang/unittests/Analysis/Scalable/tools/CMakeLists.txt
create mode 100644 clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/CMakeLists.txt
create mode 100644 clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
create mode 100644 clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
index 23e37138d6926..ac809cd00e0cd 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -14,17 +14,34 @@
#define CLANG_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_H
#include "clang/Analysis/Scalable/Serialization/SerializationFormat.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/Support/JSON.h"
namespace clang::ssaf {
class EntitySummary;
+class EntityIdTable;
class SummaryName;
class JSONFormat : public SerializationFormat {
public:
- explicit JSONFormat(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
- : SerializationFormat(FS) {}
+ // Helper class to provide limited access to EntityId conversion methods
+ // Only exposes EntityId serialization/deserialization to format handlers
+ class EntityIdConverter {
+ public:
+ EntityId fromJSON(uint64_t EntityIdIndex) const {
+ return Format.entityIdFromJSON(EntityIdIndex);
+ }
+
+ uint64_t toJSON(EntityId EI) const { return Format.entityIdToJSON(EI); }
+
+ private:
+ friend class JSONFormat;
+ EntityIdConverter(const JSONFormat &Format) : Format(Format) {}
+ const JSONFormat &Format;
+ };
+
+ explicit JSONFormat(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS);
~JSONFormat() = default;
@@ -33,44 +50,65 @@ class JSONFormat : public SerializationFormat {
llvm::Error writeTUSummary(const TUSummary &Summary,
llvm::StringRef OutputDir) override;
+ using SerializerFn = llvm::function_ref<llvm::json::Object(
+ const EntitySummary &, const EntityIdConverter &)>;
+ using DeserializerFn =
+ llvm::function_ref<llvm::Expected<std::unique_ptr<EntitySummary>>(
+ const llvm::json::Object &, EntityIdTable &,
+ const EntityIdConverter &)>;
+
+ using FormatInfo = FormatInfoEntry<SerializerFn, DeserializerFn>;
+ std::map<SummaryName, FormatInfo> FormatInfos;
+
private:
- EntityId entityIdFromJSON(const uint64_t EntityIdIndex);
+ EntityId entityIdFromJSON(const uint64_t EntityIdIndex) const;
uint64_t entityIdToJSON(EntityId EI) const;
+ llvm::Expected<BuildNamespaceKind>
+ buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr);
+
+ llvm::Expected<BuildNamespace>
+ buildNamespaceFromJSON(const llvm::json::Object &BuildNamespaceObject);
llvm::json::Object buildNamespaceToJSON(const BuildNamespace &BN) const;
+
+ llvm::Expected<NestedBuildNamespace> nestedBuildNamespaceFromJSON(
+ const llvm::json::Array &NestedBuildNamespaceArray);
llvm::json::Array
nestedBuildNamespaceToJSON(const NestedBuildNamespace &NBN) const;
+
+ llvm::Expected<EntityName>
+ entityNameFromJSON(const llvm::json::Object &EntityNameObject);
llvm::json::Object entityNameToJSON(const EntityName &EN) const;
- llvm::Expected<std::pair<EntityName, EntityId>>
- entityIdTableEntryFromJSON(const llvm::json::Object &EntityIdTableEntryObject,
- llvm::StringRef Path);
+
+ llvm::Expected<std::pair<EntityName, EntityId>> entityIdTableEntryFromJSON(
+ const llvm::json::Object &EntityIdTableEntryObject);
llvm::Expected<EntityIdTable>
- entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray,
- llvm::StringRef Path);
+ entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray);
llvm::json::Array entityIdTableToJSON(const EntityIdTable &IdTable) const;
+ llvm::Expected<std::unique_ptr<EntitySummary>>
+ entitySummaryFromJSON(const SummaryName &SN,
+ const llvm::json::Object &EntitySummaryObject,
+ EntityIdTable &IdTable);
+ llvm::json::Object entitySummaryToJSON(const SummaryName &SN,
+ const EntitySummary &ES) const;
+
llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
- entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
- llvm::StringRef Path);
- llvm::json::Array entityDataMapToJSON(
- const std::map<EntityId, std::unique_ptr<EntitySummary>> &EntityDataMap)
- const;
+ entityDataMapFromJSON(const SummaryName &SN,
+ const llvm::json::Array &EntityDataArray,
+ EntityIdTable &IdTable);
+ llvm::json::Array
+ entityDataMapToJSON(const SummaryName &SN,
+ const std::map<EntityId, std::unique_ptr<EntitySummary>>
+ &EntityDataMap) const;
llvm::Expected<std::pair<SummaryName,
std::map<EntityId, std::unique_ptr<EntitySummary>>>>
summaryDataMapEntryFromJSON(const llvm::json::Object &SummaryDataObject,
- llvm::StringRef Path);
+ EntityIdTable &IdTable);
llvm::json::Object summaryDataMapEntryToJSON(
const SummaryName &SN,
const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const;
-
- llvm::Expected<
- std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
- readTUSummaryData(llvm::StringRef Path);
- llvm::Error writeTUSummaryData(
- const std::map<SummaryName,
- std::map<EntityId, std::unique_ptr<EntitySummary>>> &Data,
- llvm::StringRef Path);
};
} // namespace clang::ssaf
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index 7a9d301c2791b..1d3376ef2c9f2 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -7,15 +7,11 @@
#include "llvm/Support/JSON.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
+#include "llvm/Support/Registry.h"
using namespace clang::ssaf;
namespace {
-constexpr size_t kPathBufferSize = 128;
-constexpr const char *TUSummaryTUNamespaceFilename = "tu_namespace.json";
-constexpr const char *TUSummaryIdTableFilename = "id_table.json";
-constexpr const char *TUSummaryDataDirname = "data";
-
// Helper to wrap an error with additional context
template <typename... Args>
llvm::Error wrapError(llvm::Error E, std::errc ErrorCode, const char *Fmt,
@@ -38,8 +34,8 @@ llvm::Error wrapError(llvm::Error E, const char *Fmt, Args &&...Vals) {
// JSON Reader and Writer
//----------------------------------------------------------------------------
-llvm::Error isJSONFile(llvm::vfs::FileSystem &FS, llvm::StringRef Path) {
- if (!FS.exists(Path))
+llvm::Error isJSONFile(llvm::StringRef Path) {
+ if (!llvm::sys::fs::exists(Path))
return llvm::createStringError(std::errc::no_such_file_or_directory,
"file does not exist: '%s'",
Path.str().c_str());
@@ -51,13 +47,12 @@ llvm::Error isJSONFile(llvm::vfs::FileSystem &FS, llvm::StringRef Path) {
return llvm::Error::success();
}
-llvm::Expected<llvm::json::Value> readJSON(llvm::vfs::FileSystem &FS,
- llvm::StringRef Path) {
- if (llvm::Error Err = isJSONFile(FS, Path))
+llvm::Expected<llvm::json::Value> readJSON(llvm::StringRef Path) {
+ if (llvm::Error Err = isJSONFile(Path))
return wrapError(std::move(Err), "failed to validate JSON file '%s'",
Path.str().c_str());
- auto BufferOrError = FS.getBufferForFile(Path);
+ auto BufferOrError = llvm::MemoryBuffer::getFile(Path);
if (!BufferOrError) {
return llvm::createStringError(BufferOrError.getError(),
"failed to read file '%s'",
@@ -67,12 +62,12 @@ llvm::Expected<llvm::json::Value> readJSON(llvm::vfs::FileSystem &FS,
return llvm::json::parse(BufferOrError.get()->getBuffer());
}
-llvm::Expected<llvm::json::Object> readJSONObject(llvm::vfs::FileSystem &FS,
- llvm::StringRef Path) {
- auto ExpectedJSON = readJSON(FS, Path);
+llvm::Expected<llvm::json::Object> readJSONObject(llvm::StringRef Path) {
+ auto ExpectedJSON = readJSON(Path);
if (!ExpectedJSON)
return wrapError(ExpectedJSON.takeError(),
- "failed to read JSON from file '%s'", Path.str().c_str());
+ "failed to read JSON object from file '%s'",
+ Path.str().c_str());
llvm::json::Object *Object = ExpectedJSON->getAsObject();
if (!Object) {
@@ -83,40 +78,15 @@ llvm::Expected<llvm::json::Object> readJSONObject(llvm::vfs::FileSystem &FS,
return std::move(*Object);
}
-llvm::Expected<llvm::json::Array> readJSONArray(llvm::vfs::FileSystem &FS,
- llvm::StringRef Path) {
- auto ExpectedJSON = readJSON(FS, Path);
- if (!ExpectedJSON)
- return wrapError(ExpectedJSON.takeError(),
- "failed to read JSON from file '%s'", Path.str().c_str());
-
- llvm::json::Array *Array = ExpectedJSON->getAsArray();
- if (!Array) {
- return llvm::createStringError(std::errc::invalid_argument,
- "failed to read JSON array from file '%s'",
- Path.str().c_str());
- }
- return std::move(*Array);
-}
-
-llvm::Error writeJSON(llvm::vfs::FileSystem &FS, llvm::json::Value &&Value,
- llvm::StringRef Path) {
- std::string Content;
- llvm::raw_string_ostream OS(Content);
- OS << llvm::formatv("{0:2}\n", Value);
- OS.flush();
-
+llvm::Error writeJSON(llvm::json::Value &&Value, llvm::StringRef Path) {
std::error_code EC;
-
- // For real file system, we can use the atomic file approach
- // For VFS, we need to write the content directly
llvm::raw_fd_ostream OutStream(Path, EC, llvm::sys::fs::OF_Text);
if (EC) {
return llvm::createStringError(EC, "failed to open '%s'",
Path.str().c_str());
}
- OutStream << Content;
+ OutStream << llvm::formatv("{0:2}\n", Value);
OutStream.flush();
if (OutStream.has_error()) {
@@ -126,16 +96,33 @@ llvm::Error writeJSON(llvm::vfs::FileSystem &FS, llvm::json::Value &&Value,
return llvm::Error::success();
}
+//----------------------------------------------------------------------------
+// JSONFormat Constructor
+//----------------------------------------------------------------------------
+
+JSONFormat::JSONFormat(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
+ : SerializationFormat(FS) {
+ for (const auto &FormatInfoEntry : llvm::Registry<FormatInfo>::entries()) {
+ std::unique_ptr<FormatInfo> Info = FormatInfoEntry.instantiate();
+ bool Inserted = FormatInfos.try_emplace(Info->ForSummary, *Info).second;
+ if (!Inserted) {
+ llvm::report_fatal_error(
+ "Format info was already registered for summary name: " +
+ Info->ForSummary.str());
+ }
+ }
+}
+
//----------------------------------------------------------------------------
// EntityId
//----------------------------------------------------------------------------
-EntityId JSONFormat::entityIdFromJSON(const uint64_t EntityIdIndex) {
- return makeEntityId(static_cast<size_t>(EntityIdIndex));
+EntityId JSONFormat::entityIdFromJSON(const uint64_t EntityIdIndex) const {
+ return SerializationFormat::makeEntityId(static_cast<size_t>(EntityIdIndex));
}
uint64_t JSONFormat::entityIdToJSON(EntityId EI) const {
- return static_cast<uint64_t>(getEntityIdIndex(EI));
+ return static_cast<uint64_t>(SerializationFormat::getEntityIdIndex(EI));
}
//----------------------------------------------------------------------------
@@ -143,14 +130,13 @@ uint64_t JSONFormat::entityIdToJSON(EntityId EI) const {
//----------------------------------------------------------------------------
llvm::Expected<BuildNamespaceKind>
-buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr,
- llvm::StringRef Path) {
+JSONFormat::buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr) {
auto OptBuildNamespaceKind = parseBuildNamespaceKind(BuildNamespaceKindStr);
if (!OptBuildNamespaceKind) {
return llvm::createStringError(
std::errc::invalid_argument,
- "invalid 'kind' BuildNamespaceKind value '%s' in file '%s'",
- BuildNamespaceKindStr.str().c_str(), Path.str().c_str());
+ "invalid 'kind' BuildNamespaceKind value '%s'",
+ BuildNamespaceKindStr.str().c_str());
}
return *OptBuildNamespaceKind;
@@ -164,32 +150,26 @@ llvm::StringRef buildNamespaceKindToJSON(BuildNamespaceKind BNK) {
// BuildNamespace
//----------------------------------------------------------------------------
-llvm::Expected<BuildNamespace>
-buildNamespaceFromJSON(const llvm::json::Object &BuildNamespaceObject,
- llvm::StringRef Path) {
+llvm::Expected<BuildNamespace> JSONFormat::buildNamespaceFromJSON(
+ const llvm::json::Object &BuildNamespaceObject) {
auto OptBuildNamespaceKindStr = BuildNamespaceObject.getString("kind");
if (!OptBuildNamespaceKindStr) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize BuildNamespace from file '%s': "
- "missing required field 'kind' (expected BuildNamespaceKind)",
- Path.str().c_str());
+ "failed to deserialize BuildNamespace: "
+ "missing required field 'kind' (expected BuildNamespaceKind)");
}
- auto ExpectedKind =
- buildNamespaceKindFromJSON(*OptBuildNamespaceKindStr, Path);
+ auto ExpectedKind = buildNamespaceKindFromJSON(*OptBuildNamespaceKindStr);
if (!ExpectedKind)
return wrapError(ExpectedKind.takeError(),
- "failed to deserialize BuildNamespace from file '%s'",
- Path.str().c_str());
+ "failed to deserialize BuildNamespace");
auto OptNameStr = BuildNamespaceObject.getString("name");
if (!OptNameStr) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize BuildNamespace from file '%s': "
- "missing required field 'name'",
- Path.str().c_str());
+ return llvm::createStringError(std::errc::invalid_argument,
+ "failed to deserialize BuildNamespace: "
+ "missing required field 'name'");
}
return {BuildNamespace(*ExpectedKind, *OptNameStr)};
@@ -207,9 +187,8 @@ JSONFormat::buildNamespaceToJSON(const BuildNamespace &BN) const {
// NestedBuildNamespace
//----------------------------------------------------------------------------
-llvm::Expected<NestedBuildNamespace>
-nestedBuildNamespaceFromJSON(const llvm::json::Array &NestedBuildNamespaceArray,
- llvm::StringRef Path) {
+llvm::Expected<NestedBuildNamespace> JSONFormat::nestedBuildNamespaceFromJSON(
+ const llvm::json::Array &NestedBuildNamespaceArray) {
std::vector<BuildNamespace> Namespaces;
size_t NamespaceCount = NestedBuildNamespaceArray.size();
@@ -223,20 +202,17 @@ nestedBuildNamespaceFromJSON(const llvm::json::Array &NestedBuildNamespaceArray,
if (!BuildNamespaceObject) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize NestedBuildNamespace from file '%s': "
+ "failed to deserialize NestedBuildNamespace: "
"element at index %zu is not a JSON object "
"(expected BuildNamespace object)",
- Path.str().c_str(), Index);
+ Index);
}
- auto ExpectedBuildNamespace =
- buildNamespaceFromJSON(*BuildNamespaceObject, Path);
+ auto ExpectedBuildNamespace = buildNamespaceFromJSON(*BuildNamespaceObject);
if (!ExpectedBuildNamespace)
return wrapError(
ExpectedBuildNamespace.takeError(),
- "failed to deserialize NestedBuildNamespace from file '%s' "
- "at index %zu",
- Path.str().c_str(), Index);
+ "failed to deserialize NestedBuildNamespace at index %zu", Index);
Namespaces.push_back(std::move(*ExpectedBuildNamespace));
}
@@ -262,24 +238,20 @@ JSONFormat::nestedBuildNamespaceToJSON(const NestedBuildNamespace &NBN) const {
//----------------------------------------------------------------------------
llvm::Expected<EntityName>
-entityNameFromJSON(const llvm::json::Object &EntityNameObject,
- llvm::StringRef Path) {
+JSONFormat::entityNameFromJSON(const llvm::json::Object &EntityNameObject) {
const auto OptUSR = EntityNameObject.getString("usr");
if (!OptUSR) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize EntityName from file '%s': "
- "missing required field 'usr' (Unified Symbol Resolution string)",
- Path.str().c_str());
+ "failed to deserialize EntityName: "
+ "missing required field 'usr' (Unified Symbol Resolution string)");
}
const auto OptSuffix = EntityNameObject.getString("suffix");
if (!OptSuffix) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize EntityName from file '%s': "
- "missing required field 'suffix'",
- Path.str().c_str());
+ return llvm::createStringError(std::errc::invalid_argument,
+ "failed to deserialize EntityName: "
+ "missing required field 'suffix'");
}
const llvm::json::Array *OptNamespaceArray =
@@ -287,18 +259,15 @@ entityNameFromJSON(const llvm::json::Object &EntityNameObject,
if (!OptNamespaceArray) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize EntityName from file '%s': "
+ "failed to deserialize EntityName: "
"missing or invalid field 'namespace' "
- "(expected JSON array of BuildNamespace objects)",
- Path.str().c_str());
+ "(expected JSON array of BuildNamespace objects)");
}
- auto ExpectedNamespace =
- nestedBuildNamespaceFromJSON(*OptNamespaceArray, Path);
+ auto ExpectedNamespace = nestedBuildNamespaceFromJSON(*OptNamespaceArray);
if (!ExpectedNamespace)
return wrapError(ExpectedNamespace.takeError(),
- "failed to deserialize EntityName from file '%s'",
- Path.str().c_str());
+ "failed to deserialize EntityName");
return EntityName{*OptUSR, *OptSuffix, std::move(*ExpectedNamespace)};
}
@@ -327,32 +296,29 @@ llvm::StringRef summaryNameToJSON(const SummaryName &SN) { return SN.str(); }
llvm::Expected<std::pair<EntityName, EntityId>>
JSONFormat::entityIdTableEntryFromJSON(
- const llvm::json::Object &EntityIdTableEntryObject, llvm::StringRef Path) {
+ const llvm::json::Object &EntityIdTableEntryObject) {
const llvm::json::Object *OptEntityNameObject =
EntityIdTableEntryObject.getObject("name");
if (!OptEntityNameObject) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize EntityIdTable entry from file '%s': "
- "missing or invalid field 'name' (expected EntityName JSON object)",
- Path.str().c_str());
+ "failed to deserialize EntityIdTable entry: "
+ "missing or invalid field 'name' (expected EntityName JSON object)");
}
- auto ExpectedEntityName = entityNameFromJSON(*OptEntityNameObject, Path);
+ auto ExpectedEntityName = entityNameFromJSON(*OptEntityNameObject);
if (!ExpectedEntityName)
return wrapError(ExpectedEntityName.takeError(),
- "failed to deserialize EntityIdTable entry from file '%s'",
- Path.str().c_str());
+ "failed to deserialize EntityIdTable entry");
const llvm::json::Value *EntityIdIntValue =
EntityIdTableEntryObject.get("id");
if (!EntityIdIntValue) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize EntityIdTable entry from file '%s': "
- "missing required field 'id' (expected unsigned integer EntityId)",
- Path.str().c_str());
+ "failed to deserialize EntityIdTable entry: "
+ "missing required field 'id' (expected unsigned integer EntityId)");
}
const std::optional<uint64_t> OptEntityIdInt =
@@ -360,10 +326,9 @@ JSONFormat::entityIdTableEntryFromJSON(
if (!OptEntityIdInt) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize EntityIdTable entry from file '%s': "
+ "failed to deserialize EntityIdTable entry: "
"field 'id' is not a valid unsigned 64-bit integer "
- "(expected non-negative EntityId value)",
- Path.str().c_str());
+ "(expected non-negative EntityId value)");
}
EntityId EI = entityIdFromJSON(*OptEntityIdInt);
@@ -372,8 +337,7 @@ JSONFormat::entityIdTableEntryFromJSON(
}
llvm::Expected<EntityIdTable>
-JSONFormat::entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray,
- llvm::StringRef Path) {
+JSONFormat::entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray) {
const size_t EntityCount = EntityIdTableArray.size();
EntityIdTable IdTable;
@@ -389,29 +353,27 @@ JSONFormat::entityIdTableFromJSON(const llvm::json::Array &EntityIdTableArray,
if (!OptEntityIdTableEntryObject) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize EntityIdTable from file '%s': "
+ "failed to deserialize EntityIdTable: "
"element at index %zu is not a JSON object "
"(expected EntityIdTable entry with 'id' and 'name' fields)",
- Path.str().c_str(), Index);
+ Index);
}
auto ExpectedEntityIdTableEntry =
- entityIdTableEntryFromJSON(*OptEntityIdTableEntryObject, Path);
+ entityIdTableEntryFromJSON(*OptEntityIdTableEntryObject);
if (!ExpectedEntityIdTableEntry)
- return wrapError(
- ExpectedEntityIdTableEntry.takeError(),
- "failed to deserialize EntityIdTable from file '%s' at index %zu",
- Path.str().c_str(), Index);
+ return wrapError(ExpectedEntityIdTableEntry.takeError(),
+ "failed to deserialize EntityIdTable at index %zu",
+ Index);
auto [EntityIt, EntityInserted] =
Entities.emplace(std::move(*ExpectedEntityIdTableEntry));
if (!EntityInserted) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to deserialize EntityIdTable from file '%s': "
- "duplicate EntityName found at index %zu "
- "(EntityId=%zu already exists in table)",
- Path.str().c_str(), Index, getEntityIdIndex(EntityIt->second));
+ return llvm::createStringError(std::errc::invalid_argument,
+ "failed to deserialize EntityIdTable: "
+ "duplicate EntityName found at index %zu "
+ "(EntityId=%zu already exists in table)",
+ Index, getEntityIdIndex(EntityIt->second));
}
}
@@ -439,18 +401,39 @@ JSONFormat::entityIdTableToJSON(const EntityIdTable &IdTable) const {
//----------------------------------------------------------------------------
llvm::Expected<std::unique_ptr<EntitySummary>>
-entitySummaryFromJSON(const llvm::json::Object &EntitySummaryObject,
- llvm::StringRef Path) {
- return llvm::createStringError(
- std::errc::function_not_supported,
- "EntitySummary deserialization from file '%s' is not yet implemented",
- Path.str().c_str());
+JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
+ const llvm::json::Object &EntitySummaryObject,
+ EntityIdTable &IdTable) {
+ auto InfoIt = FormatInfos.find(SN);
+ if (InfoIt == FormatInfos.end()) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to deserialize EntitySummary: "
+ "no FormatInfo was registered for summary name: %s",
+ SN.str().data());
+ }
+ const auto &InfoEntry = InfoIt->second;
+ assert(InfoEntry.ForSummary == SN);
+
+ EntityIdConverter Converter(*this);
+ return InfoEntry.Deserialize(EntitySummaryObject, IdTable, Converter);
}
-llvm::json::Object entitySummaryToJSON(const EntitySummary &ES) {
- // TODO
- llvm::json::Object Result;
- return Result;
+llvm::json::Object
+JSONFormat::entitySummaryToJSON(const SummaryName &SN,
+ const EntitySummary &ES) const {
+ auto InfoIt = FormatInfos.find(SN);
+ if (InfoIt == FormatInfos.end()) {
+ llvm::report_fatal_error(
+ "Failed to serialize EntitySummary: no FormatInfo was registered for "
+ "summary name: " +
+ SN.str());
+ }
+ const auto &InfoEntry = InfoIt->second;
+ assert(InfoEntry.ForSummary == SN);
+
+ EntityIdConverter Converter(*this);
+ return InfoEntry.Serialize(ES, Converter);
}
//----------------------------------------------------------------------------
@@ -458,8 +441,9 @@ llvm::json::Object entitySummaryToJSON(const EntitySummary &ES) {
//----------------------------------------------------------------------------
llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
-JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
- llvm::StringRef Path) {
+JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
+ const llvm::json::Array &EntityDataArray,
+ EntityIdTable &IdTable) {
std::map<EntityId, std::unique_ptr<EntitySummary>> EntityDataMap;
size_t Index = 0;
@@ -469,10 +453,10 @@ JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
if (!OptEntityDataEntryObject) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize entity data map from file '%s': "
+ "failed to deserialize entity data map: "
"element at index %zu is not a JSON object "
"(expected object with 'entity_id' and 'entity_summary' fields)",
- Path.str().c_str(), Index);
+ Index);
}
const llvm::json::Value *EntityIdIntValue =
@@ -480,10 +464,10 @@ JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
if (!EntityIdIntValue) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize entity data map entry from file '%s' "
+ "failed to deserialize entity data map entry "
"at index %zu: missing required field 'entity_id' "
"(expected unsigned integer EntityId)",
- Path.str().c_str(), Index);
+ Index);
}
const std::optional<uint64_t> OptEntityIdInt =
@@ -491,10 +475,10 @@ JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
if (!OptEntityIdInt) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize entity data map entry from file '%s' "
+ "failed to deserialize entity data map entry "
"at index %zu: field 'entity_id' is not a valid unsigned 64-bit "
"integer",
- Path.str().c_str(), Index);
+ Index);
}
EntityId EI = entityIdFromJSON(*OptEntityIdInt);
@@ -504,21 +488,19 @@ JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
if (!OptEntitySummaryObject) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize entity data map entry from file '%s' "
+ "failed to deserialize entity data map entry "
"at index %zu: missing or invalid field 'entity_summary' "
"(expected EntitySummary JSON object)",
- Path.str().c_str(), Index);
+ Index);
}
auto ExpectedEntitySummary =
- entitySummaryFromJSON(*OptEntitySummaryObject, Path);
+ entitySummaryFromJSON(SN, *OptEntitySummaryObject, IdTable);
if (!ExpectedEntitySummary) {
return wrapError(
ExpectedEntitySummary.takeError(),
- "failed to deserialize entity data map entry from file '%s' "
- "at index %zu",
- Path.str().c_str(), Index);
+ "failed to deserialize entity data map entry at index %zu", Index);
}
auto [DataIt, DataInserted] = EntityDataMap.insert(
@@ -526,9 +508,9 @@ JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
if (!DataInserted) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize entity data map from file '%s': "
+ "failed to deserialize entity data map: "
"duplicate EntityId (%zu) found at index %zu",
- Path.str().c_str(), getEntityIdIndex(DataIt->first), Index);
+ getEntityIdIndex(DataIt->first), Index);
}
++Index;
@@ -538,6 +520,7 @@ JSONFormat::entityDataMapFromJSON(const llvm::json::Array &EntityDataArray,
}
llvm::json::Array JSONFormat::entityDataMapToJSON(
+ const SummaryName &SN,
const std::map<EntityId, std::unique_ptr<EntitySummary>> &EntityDataMap)
const {
llvm::json::Array Result;
@@ -545,7 +528,7 @@ llvm::json::Array JSONFormat::entityDataMapToJSON(
for (const auto &[EntityId, EntitySummary] : EntityDataMap) {
llvm::json::Object Entry;
Entry["entity_id"] = entityIdToJSON(EntityId);
- Entry["entity_summary"] = entitySummaryToJSON(*EntitySummary);
+ Entry["entity_summary"] = entitySummaryToJSON(SN, *EntitySummary);
Result.push_back(std::move(Entry));
}
return Result;
@@ -554,7 +537,7 @@ llvm::json::Array JSONFormat::entityDataMapToJSON(
llvm::Expected<
std::pair<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
JSONFormat::summaryDataMapEntryFromJSON(
- const llvm::json::Object &SummaryDataObject, llvm::StringRef Path) {
+ const llvm::json::Object &SummaryDataObject, EntityIdTable &IdTable) {
std::optional<llvm::StringRef> OptSummaryNameStr =
SummaryDataObject.getString("summary_name");
@@ -562,31 +545,30 @@ JSONFormat::summaryDataMapEntryFromJSON(
if (!OptSummaryNameStr) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize summary data from file '%s': "
+ "failed to deserialize summary data: "
"missing required field 'summary_name' "
- "(expected string identifier for the analysis summary)",
- Path.str().c_str());
+ "(expected string identifier for the analysis summary)");
}
SummaryName SN = summaryNameFromJSON(*OptSummaryNameStr);
const llvm::json::Array *OptEntityDataArray =
- SummaryDataObject.getArray("summary_data");
+ SummaryDataObject.getArray("data");
if (!OptEntityDataArray) {
return llvm::createStringError(
std::errc::invalid_argument,
- "failed to deserialize summary data from file '%s' for summary '%s': "
- "missing or invalid field 'summary_data' "
+ "failed to deserialize summary data for summary '%s': "
+ "missing or invalid field 'data' "
"(expected JSON array of entity summaries)",
- Path.str().c_str(), SN.str().data());
+ SN.str().data());
}
- auto ExpectedEntityDataMap = entityDataMapFromJSON(*OptEntityDataArray, Path);
+ auto ExpectedEntityDataMap =
+ entityDataMapFromJSON(SN, *OptEntityDataArray, IdTable);
if (!ExpectedEntityDataMap)
- return wrapError(
- ExpectedEntityDataMap.takeError(),
- "failed to deserialize summary data from file '%s' for summary '%s'",
- Path.str().c_str(), SN.str().data());
+ return wrapError(ExpectedEntityDataMap.takeError(),
+ "failed to deserialize summary data for summary '%s'",
+ SN.str().data());
return std::make_pair(std::move(SN), std::move(*ExpectedEntityDataMap));
}
@@ -596,190 +578,53 @@ llvm::json::Object JSONFormat::summaryDataMapEntryToJSON(
const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const {
llvm::json::Object Result;
Result["summary_name"] = summaryNameToJSON(SN);
- Result["summary_data"] = entityDataMapToJSON(SD);
+ Result["data"] = entityDataMapToJSON(SN, SD);
return Result;
}
//----------------------------------------------------------------------------
-// SummaryDataMap
+// TUSummary
//----------------------------------------------------------------------------
-llvm::Expected<
- std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
-JSONFormat::readTUSummaryData(llvm::StringRef Path) {
- if (!FS->exists(Path)) {
- return llvm::createStringError(
- std::errc::no_such_file_or_directory,
- "failed to read TUSummary data: directory does not exist: '%s'",
- Path.str().c_str());
- }
-
- auto StatusOrErr = FS->status(Path);
- if (!StatusOrErr) {
- return llvm::createStringError(
- StatusOrErr.getError(),
- "failed to read TUSummary data: cannot get status of '%s'",
- Path.str().c_str());
- }
-
- if (!StatusOrErr->isDirectory()) {
- return llvm::createStringError(
- std::errc::not_a_directory,
- "failed to read TUSummary data: path is not a directory: '%s'",
- Path.str().c_str());
- }
-
- std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>
- Data;
- std::error_code EC;
-
- auto DirIter = FS->dir_begin(Path, EC);
- if (EC) {
- return llvm::createStringError(
- EC, "failed to read TUSummary data: cannot iterate directory '%s'",
- Path.str().c_str());
- }
-
- for (llvm::vfs::directory_iterator End; DirIter != End && !EC;
- DirIter.increment(EC)) {
- llvm::StringRef SummaryPathRef = DirIter->path();
- std::string SummaryPath = SummaryPathRef.str();
-
- auto ExpectedObject = readJSONObject(*FS, SummaryPath);
- if (!ExpectedObject)
- return wrapError(ExpectedObject.takeError(),
- "failed to read TUSummary data from file '%s'",
- SummaryPath.c_str());
-
- auto ExpectedSummaryDataMap =
- summaryDataMapEntryFromJSON(*ExpectedObject, SummaryPath);
- if (!ExpectedSummaryDataMap)
- return wrapError(ExpectedSummaryDataMap.takeError(),
- "failed to read TUSummary data from file '%s'",
- SummaryPath.c_str());
-
- auto [SummaryIt, SummaryInserted] =
- Data.emplace(std::move(*ExpectedSummaryDataMap));
- if (!SummaryInserted) {
- return llvm::createStringError(
- std::errc::invalid_argument,
- "failed to read TUSummary data from directory '%s': "
- "duplicate SummaryName '%s' encountered in file '%s'",
- Path.str().c_str(), SummaryIt->first.str().data(),
- SummaryPath.c_str());
- }
- }
-
- if (EC) {
- return llvm::createStringError(EC,
- "failed to read TUSummary data: "
- "error during directory iteration of '%s'",
- Path.str().c_str());
- }
-
- return Data;
-}
-
-std::string makeValidFilename(llvm::StringRef Name, size_t Prefix,
- char Replacement) {
- std::string Result;
- llvm::raw_string_ostream OS(Result);
- OS << llvm::format("%02d-%s", Prefix, Name.str().c_str());
-
- for (size_t Index = 0; Index < Result.size(); ++Index) {
- char &Actual = Result[Index];
- if (llvm::isAlnum(Actual) || Actual == '-' || Actual == '_')
- continue;
- Actual = Replacement;
- }
-
- return Result;
-}
-
-llvm::Error JSONFormat::writeTUSummaryData(
- const std::map<SummaryName,
- std::map<EntityId, std::unique_ptr<EntitySummary>>> &Data,
- llvm::StringRef Path) {
- if (!FS->exists(Path)) {
- return llvm::createStringError(
- std::errc::no_such_file_or_directory,
- "failed to write TUSummary data: directory does not exist: '%s'",
- Path.str().c_str());
- }
+llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
+ // Read the JSON object from the file
+ auto ExpectedRootObject = readJSONObject(Path);
+ if (!ExpectedRootObject)
+ return wrapError(ExpectedRootObject.takeError(),
+ "failed to read TUSummary from '%s'", Path.str().c_str());
- auto StatusOrErr = FS->status(Path);
- if (!StatusOrErr) {
- return llvm::createStringError(
- StatusOrErr.getError(),
- "failed to write TUSummary data: cannot get status of '%s'",
- Path.str().c_str());
- }
+ const llvm::json::Object &RootObject = *ExpectedRootObject;
- if (!StatusOrErr->isDirectory()) {
+ // Parse TUNamespace field
+ const llvm::json::Object *TUNamespaceObject =
+ RootObject.getObject("tu_namespace");
+ if (!TUNamespaceObject) {
return llvm::createStringError(
- std::errc::not_a_directory,
- "failed to write TUSummary data: path is not a directory: '%s'",
+ std::errc::invalid_argument,
+ "failed to read TUSummary from '%s': "
+ "missing or invalid field 'tu_namespace' (expected JSON object)",
Path.str().c_str());
}
- size_t Index = 0;
- for (const auto &[SummaryName, DataMap] : Data) {
- llvm::SmallString<kPathBufferSize> SummaryPath(Path);
- llvm::sys::path::append(SummaryPath,
- makeValidFilename(SummaryName.str(), Index, '_'));
- llvm::sys::path::replace_extension(SummaryPath, ".json");
-
- llvm::json::Object Result = summaryDataMapEntryToJSON(SummaryName, DataMap);
- if (auto Error = writeJSON(*FS, std::move(Result), SummaryPath)) {
- return wrapError(
- std::move(Error), std::errc::io_error,
- "failed to write TUSummary data to directory '%s': cannot write "
- "summary '%s' to file '%s'",
- Path.str().c_str(), SummaryName.str().data(),
- SummaryPath.str().data());
- }
-
- ++Index;
- }
-
- return llvm::Error::success();
-}
-
-//----------------------------------------------------------------------------
-// TUSummary
-//----------------------------------------------------------------------------
-
-llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
-
- // Populate TUNamespace field.
- llvm::SmallString<kPathBufferSize> TUNamespacePath(Path);
- llvm::sys::path::append(TUNamespacePath, TUSummaryTUNamespaceFilename);
-
- auto ExpectedObject = readJSONObject(*FS, TUNamespacePath);
- if (!ExpectedObject)
- return wrapError(ExpectedObject.takeError(),
- "failed to read TUSummary from '%s'", Path.str().c_str());
-
- auto ExpectedTUNamespace =
- buildNamespaceFromJSON(*ExpectedObject, TUNamespacePath);
+ auto ExpectedTUNamespace = buildNamespaceFromJSON(*TUNamespaceObject);
if (!ExpectedTUNamespace)
return wrapError(ExpectedTUNamespace.takeError(),
"failed to read TUSummary from '%s'", Path.str().c_str());
TUSummary Summary(std::move(*ExpectedTUNamespace));
- // Populate IdTable field.
+ // Parse IdTable field
{
- llvm::SmallString<kPathBufferSize> IdTablePath(Path);
- llvm::sys::path::append(IdTablePath, TUSummaryIdTableFilename);
-
- auto ExpectedArray = readJSONArray(*FS, IdTablePath);
- if (!ExpectedArray)
- return wrapError(ExpectedArray.takeError(),
- "failed to read TUSummary from '%s'",
- Path.str().c_str());
+ const llvm::json::Array *IdTableArray = RootObject.getArray("id_table");
+ if (!IdTableArray) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to read TUSummary from '%s': "
+ "missing or invalid field 'id_table' (expected JSON array)",
+ Path.str().c_str());
+ }
- auto ExpectedIdTable = entityIdTableFromJSON(*ExpectedArray, IdTablePath);
+ auto ExpectedIdTable = entityIdTableFromJSON(*IdTableArray);
if (!ExpectedIdTable)
return wrapError(ExpectedIdTable.takeError(),
"failed to read TUSummary from '%s'",
@@ -788,120 +633,75 @@ llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
getIdTable(Summary) = std::move(*ExpectedIdTable);
}
- // Populate Data field.
+ // Parse data field
{
- llvm::SmallString<kPathBufferSize> DataPath(Path);
- llvm::sys::path::append(DataPath, TUSummaryDataDirname);
-
- if (!FS->exists(DataPath)) {
- return llvm::createStringError(std::errc::no_such_file_or_directory,
- "failed to read TUSummary from '%s': "
- "data directory does not exist: '%s'",
- Path.str().c_str(), DataPath.str().data());
+ const llvm::json::Array *SummaryDataArray = RootObject.getArray("data");
+ if (!SummaryDataArray) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "failed to read TUSummary from '%s': "
+ "missing or invalid field 'data' (expected JSON array)",
+ Path.str().c_str());
}
- auto StatusOrErr = FS->status(DataPath);
- if (!StatusOrErr) {
- return llvm::createStringError(StatusOrErr.getError(),
- "failed to read TUSummary from '%s': "
- "cannot get status of data path: '%s'",
- Path.str().c_str(), DataPath.str().data());
- }
+ // Parse each summary data entry
+ std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>
+ Data;
+ for (const llvm::json::Value &SummaryDataValue : *SummaryDataArray) {
+ const llvm::json::Object *SummaryDataObject =
+ SummaryDataValue.getAsObject();
+ if (!SummaryDataObject) {
+ return llvm::createStringError(std::errc::invalid_argument,
+ "failed to read TUSummary from '%s': "
+ "data array contains non-object element",
+ Path.str().c_str());
+ }
- if (!StatusOrErr->isDirectory()) {
- return llvm::createStringError(std::errc::not_a_directory,
- "failed to read TUSummary from '%s': "
- "data path is not a directory: '%s'",
- Path.str().c_str(), DataPath.str().data());
+ auto ExpectedSummaryDataEntry =
+ summaryDataMapEntryFromJSON(*SummaryDataObject, getIdTable(Summary));
+ if (!ExpectedSummaryDataEntry)
+ return wrapError(ExpectedSummaryDataEntry.takeError(),
+ "failed to read TUSummary from '%s'",
+ Path.str().c_str());
+
+ auto [SummaryIt, SummaryInserted] =
+ Data.emplace(std::move(*ExpectedSummaryDataEntry));
+ if (!SummaryInserted) {
+ return llvm::createStringError(std::errc::invalid_argument,
+ "failed to read TUSummary from '%s': "
+ "duplicate SummaryName '%s' found",
+ Path.str().c_str(),
+ SummaryIt->first.str().data());
+ }
}
- auto ExpectedData = readTUSummaryData(DataPath);
- if (!ExpectedData)
- return wrapError(ExpectedData.takeError(),
- "failed to read TUSummary from '%s'",
- Path.str().c_str());
-
- getData(Summary) = std::move(*ExpectedData);
+ getData(Summary) = std::move(Data);
}
return Summary;
}
llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
- llvm::StringRef Dir) {
- // Serialize TUNamespace field.
- {
- llvm::SmallString<kPathBufferSize> TUNamespacePath(Dir);
- llvm::sys::path::append(TUNamespacePath, TUSummaryTUNamespaceFilename);
-
- llvm::json::Object BuildNamespaceObj =
- buildNamespaceToJSON(getTUNamespace(S));
- if (auto Error =
- writeJSON(*FS, std::move(BuildNamespaceObj), TUNamespacePath)) {
- return wrapError(std::move(Error), std::errc::io_error,
- "failed to write TUSummary to '%s': "
- "cannot write TUNamespace file '%s'",
- Dir.str().c_str(), TUNamespacePath.str().data());
- }
- }
+ llvm::StringRef Path) {
+ llvm::json::Object RootObject;
- // Serialize IdTable field.
- {
- llvm::SmallString<kPathBufferSize> IdTablePath(Dir);
- llvm::sys::path::append(IdTablePath, TUSummaryIdTableFilename);
-
- llvm::json::Array IdTableObj = entityIdTableToJSON(getIdTable(S));
- if (auto Error = writeJSON(*FS, std::move(IdTableObj), IdTablePath)) {
- return wrapError(std::move(Error), std::errc::io_error,
- "failed to write TUSummary to '%s': "
- "cannot write IdTable file '%s'",
- Dir.str().c_str(), IdTablePath.str().data());
- }
- }
+ RootObject["tu_namespace"] = buildNamespaceToJSON(getTUNamespace(S));
- // Serialize Data field.
- {
- llvm::SmallString<kPathBufferSize> DataPath(Dir);
- llvm::sys::path::append(DataPath, TUSummaryDataDirname);
-
- // Create the data directory if it doesn't exist
- // Use the real filesystem for directory creation as VFS doesn't always
- // support this
- if (std::error_code EC = llvm::sys::fs::create_directory(DataPath)) {
- // If error is not "already exists", return error
- if (EC != std::errc::file_exists) {
- return llvm::createStringError(EC,
- "failed to write TUSummary to '%s': "
- "cannot create data directory '%s'",
- Dir.str().c_str(),
- DataPath.str().data());
- }
- }
+ RootObject["id_table"] = entityIdTableToJSON(getIdTable(S));
- // Verify it's a directory (could be a file with the same name)
- if (FS->exists(DataPath)) {
- auto StatusOrErr = FS->status(DataPath);
- if (!StatusOrErr) {
- return llvm::createStringError(StatusOrErr.getError(),
- "failed to write TUSummary to '%s': "
- "cannot get status of data path '%s'",
- Dir.str().c_str(),
- DataPath.str().data());
- }
- if (!StatusOrErr->isDirectory()) {
- return llvm::createStringError(
- std::errc::not_a_directory,
- "failed to write TUSummary to '%s': data path exists but is not a "
- "directory: '%s'",
- Dir.str().c_str(), DataPath.str().data());
- }
+ {
+ llvm::json::Array SummaryDataArray;
+ for (const auto &[SummaryName, DataMap] : getData(S)) {
+ SummaryDataArray.push_back(
+ summaryDataMapEntryToJSON(SummaryName, DataMap));
}
+ RootObject["data"] = std::move(SummaryDataArray);
+ }
- if (auto Error = writeTUSummaryData(getData(S), DataPath)) {
- return wrapError(std::move(Error), std::errc::io_error,
- "failed to write TUSummary to '%s': cannot write data",
- Dir.str().c_str());
- }
+ // Write the JSON to file
+ if (auto Error = writeJSON(std::move(RootObject), Path)) {
+ return wrapError(std::move(Error), std::errc::io_error,
+ "failed to write TUSummary to '%s'", Path.str().c_str());
}
return llvm::Error::success();
diff --git a/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test b/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
new file mode 100644
index 0000000000000..c051f41af4fc4
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-callgraph-invalid-caller.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/callgraph-invalid-caller.json 2>&1 | FileCheck %s
+
+// Test that CallGraph analysis validates caller field type
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize EntitySummary
+// CHECK: CallGraph: 'caller' at index 0 is not a valid uint64
diff --git a/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test b/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
new file mode 100644
index 0000000000000..0e5e2877d06d2
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-callgraph-missing-field.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/callgraph-missing-field.json 2>&1 | FileCheck %s
+
+// Test that CallGraph analysis requires 'call_graph' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize EntitySummary
+// CHECK: CallGraph: missing or invalid 'call_graph' field
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
new file mode 100644
index 0000000000000..90dea14d683db
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-element-not-object.test
@@ -0,0 +1,6 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/data-element-not-object.json 2>&1 | FileCheck %s
+
+// Test that 'data' array elements must be objects
+
+// CHECK: failed to read TUSummary
+// CHECK: data array contains non-object element
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test b/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
new file mode 100644
index 0000000000000..865db1e8709cd
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-entry-missing-data.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/data-entry-missing-data.json 2>&1 | FileCheck %s
+
+// Test that data array entries must have 'data' field
+
+// CHECK: failed to read TUSummary
+// CHECK: for summary 'test'
+// CHECK: missing or invalid field 'data'
+// CHECK: expected JSON array of entity summaries
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test b/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
new file mode 100644
index 0000000000000..a9bf765424b7e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-missing-summary-name.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/data-missing-summary-name.json 2>&1 | FileCheck %s
+
+// Test that data array entries must have 'summary_name' field
+
+// CHECK: failed to read TUSummary
+// CHECK: missing required field 'summary_name'
+// CHECK: expected string identifier for the analysis summary
diff --git a/clang/test/Analysis/Scalable/Serialization/error-data-not-array.test b/clang/test/Analysis/Scalable/Serialization/error-data-not-array.test
new file mode 100644
index 0000000000000..058be1a4bddbb
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-data-not-array.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/data-not-array.json 2>&1 | FileCheck %s
+
+// Test that root 'data' field must be an array
+
+// CHECK: failed to read TUSummary
+// CHECK: missing or invalid field 'data'
+// CHECK: expected JSON array
diff --git a/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test b/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
new file mode 100644
index 0000000000000..64675a201dd59
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-defuse-invalid-use.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/defuse-invalid-use.json 2>&1 | FileCheck %s
+
+// Test that DefUse analysis validates use value types
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize EntitySummary
+// CHECK: DefUse: use at index 0,0,0 is not a valid uint64
diff --git a/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test b/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
new file mode 100644
index 0000000000000..73409a2d46503
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-defuse-missing-field.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/defuse-missing-field.json 2>&1 | FileCheck %s
+
+// Test that DefUse analysis requires 'def_use_chains' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry at index 0
+// CHECK: failed to deserialize EntitySummary
+// CHECK: DefUse: missing or invalid 'def_use_chains' field
diff --git a/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test b/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
new file mode 100644
index 0000000000000..8e4d594cc75e6
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-duplicate-entity.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/duplicate-entity.json 2>&1 | FileCheck %s
+
+// Test that duplicate EntityName in id_table is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable from file
+// CHECK: duplicate EntityName found at index 1
+// CHECK: EntityId=0 already exists in table
diff --git a/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test b/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
new file mode 100644
index 0000000000000..568db0a339620
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-duplicate-summary-name.test
@@ -0,0 +1,6 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/duplicate-summary-name.json 2>&1 | FileCheck %s
+
+// Test that duplicate summary names are detected
+
+// CHECK: failed to read TUSummary
+// CHECK: duplicate SummaryName 'test' found
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
new file mode 100644
index 0000000000000..72bf04adc267c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-data-element-not-object.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-data-element-not-object.json 2>&1 | FileCheck %s
+
+// Test that entity data elements must be objects
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map from file
+// CHECK: element at index 0 is not a JSON object
+// CHECK: expected object with 'entity_id' and 'entity_summary' fields
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
new file mode 100644
index 0000000000000..e2dd9a34b34d3
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-id.test
@@ -0,0 +1,9 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-data-missing-entity-id.json 2>&1 | FileCheck %s
+
+// Test that entity data entries must have 'entity_id' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry from file
+// CHECK: at index 0
+// CHECK: missing required field 'entity_id'
+// CHECK: expected unsigned integer EntityId
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
new file mode 100644
index 0000000000000..078bc4b068258
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-data-missing-entity-summary.test
@@ -0,0 +1,9 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-data-missing-entity-summary.json 2>&1 | FileCheck %s
+
+// Test that entity data entries must have 'entity_summary' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry from file
+// CHECK: at index 0
+// CHECK: missing or invalid field 'entity_summary'
+// CHECK: expected EntitySummary JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test b/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
new file mode 100644
index 0000000000000..f5903b02d7eeb
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-id-not-uint64.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-id-not-uint64.json 2>&1 | FileCheck %s
+
+// Test that entity_id field must be a valid uint64
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize entity data map entry from file
+// CHECK: at index 0
+// CHECK: field 'entity_id' is not a valid unsigned 64-bit integer
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
new file mode 100644
index 0000000000000..f9230bed9b25f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-namespace.test
@@ -0,0 +1,9 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-name-missing-namespace.json 2>&1 | FileCheck %s
+
+// Test that EntityName must have 'namespace' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: failed to deserialize EntityName from file
+// CHECK: missing or invalid field 'namespace'
+// CHECK: expected JSON array of BuildNamespace objects
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
new file mode 100644
index 0000000000000..70a75f8b63433
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-suffix.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-name-missing-suffix.json 2>&1 | FileCheck %s
+
+// Test that EntityName must have 'suffix' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: failed to deserialize EntityName from file
+// CHECK: missing required field 'suffix'
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
new file mode 100644
index 0000000000000..8531cfa1bd307
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-name-missing-usr.test
@@ -0,0 +1,9 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-name-missing-usr.json 2>&1 | FileCheck %s
+
+// Test that EntityName must have 'usr' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: failed to deserialize EntityName from file
+// CHECK: missing required field 'usr'
+// CHECK: Unified Symbol Resolution string
diff --git a/clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test b/clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test
new file mode 100644
index 0000000000000..9a317f1270c4a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-entity-summary-no-format-info.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/entity-summary-no-format-info.json 2>&1 | FileCheck %s
+
+// Test that EntitySummary deserialization requires registered FormatInfo
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntitySummary from file
+// CHECK: no FormatInfo was registered for summary name
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test
new file mode 100644
index 0000000000000..5e1359a3ce94c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-element-not-object.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-element-not-object.json 2>&1 | FileCheck %s
+
+// Test that id_table elements must be objects
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable from file
+// CHECK: element at index 0 is not a JSON object
+// CHECK: expected EntityIdTable entry with 'id' and 'name' fields
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test
new file mode 100644
index 0000000000000..4e2706d89f52d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-id-not-uint64.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-entry-id-not-uint64.json 2>&1 | FileCheck %s
+
+// Test that id_table entry 'id' field must be a valid uint64
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: field 'id' is not a valid unsigned 64-bit integer
+// CHECK: expected non-negative EntityId value
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test
new file mode 100644
index 0000000000000..423cd54b3c1f6
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-id.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-entry-missing-id.json 2>&1 | FileCheck %s
+
+// Test that id_table entries must have 'id' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: missing required field 'id'
+// CHECK: expected unsigned integer EntityId
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test
new file mode 100644
index 0000000000000..de56b93de491b
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-entry-missing-name.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-entry-missing-name.json 2>&1 | FileCheck %s
+
+// Test that id_table entries must have 'name' field
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: missing or invalid field 'name'
+// CHECK: expected EntityName JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-id-table-not-array.test b/clang/test/Analysis/Scalable/Serialization/error-id-table-not-array.test
new file mode 100644
index 0000000000000..8d7a9bd640247
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-id-table-not-array.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/id-table-not-array.json 2>&1 | FileCheck %s
+
+// Test that id_table must be an array
+
+// CHECK: failed to read TUSummary
+// CHECK: missing or invalid field 'id_table'
+// CHECK: expected JSON array
diff --git a/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test b/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
new file mode 100644
index 0000000000000..cecc51e633f40
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-invalid-kind.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/invalid-kind.json 2>&1 | FileCheck %s
+
+// Test that invalid kind value is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize BuildNamespace from file
+// CHECK: invalid 'kind' BuildNamespaceKind value 'InvalidKind'
diff --git a/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test b/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
new file mode 100644
index 0000000000000..1724b13031c13
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-invalid-syntax.test
@@ -0,0 +1,6 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/invalid-syntax.json 2>&1 | FileCheck %s
+
+// Test that invalid JSON syntax is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to read JSON object from file
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-data.test b/clang/test/Analysis/Scalable/Serialization/error-missing-data.test
new file mode 100644
index 0000000000000..8d4b654a8715d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-data.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/missing-data.json 2>&1 | FileCheck %s
+
+// Test that missing root 'data' field is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: missing or invalid field 'data'
+// CHECK: expected JSON array
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-id-table.test b/clang/test/Analysis/Scalable/Serialization/error-missing-id-table.test
new file mode 100644
index 0000000000000..a71bb4e361fdd
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-id-table.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/missing-id-table.json 2>&1 | FileCheck %s
+
+// Test that missing id_table field is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: missing or invalid field 'id_table'
+// CHECK: expected JSON array
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test b/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
new file mode 100644
index 0000000000000..d26a39d46c4b2
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-kind.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/missing-kind.json 2>&1 | FileCheck %s
+
+// Test that missing kind field in tu_namespace is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize BuildNamespace from file
+// CHECK: missing required field 'kind'
+// CHECK: expected BuildNamespaceKind
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-name.test b/clang/test/Analysis/Scalable/Serialization/error-missing-name.test
new file mode 100644
index 0000000000000..d88a8c46bc85f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-name.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/missing-name.json 2>&1 | FileCheck %s
+
+// Test that missing name field in tu_namespace is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize BuildNamespace from file
+// CHECK: missing required field 'name'
diff --git a/clang/test/Analysis/Scalable/Serialization/error-missing-tu-namespace.test b/clang/test/Analysis/Scalable/Serialization/error-missing-tu-namespace.test
new file mode 100644
index 0000000000000..fb6b58693edf6
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-missing-tu-namespace.test
@@ -0,0 +1,7 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/missing-tu-namespace.json 2>&1 | FileCheck %s
+
+// Test that missing tu_namespace field is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: missing or invalid field 'tu_namespace'
+// CHECK: expected JSON object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
new file mode 100644
index 0000000000000..4723d9d4434d9
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-namespace-element-not-object.test
@@ -0,0 +1,10 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/namespace-element-not-object.json 2>&1 | FileCheck %s
+
+// Test that namespace array elements must be objects
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to deserialize EntityIdTable entry from file
+// CHECK: failed to deserialize EntityName from file
+// CHECK: failed to deserialize NestedBuildNamespace from file
+// CHECK: element at index 0 is not a JSON object
+// CHECK: expected BuildNamespace object
diff --git a/clang/test/Analysis/Scalable/Serialization/error-nonexistent-file.test b/clang/test/Analysis/Scalable/Serialization/error-nonexistent-file.test
new file mode 100644
index 0000000000000..3748d54622e75
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-nonexistent-file.test
@@ -0,0 +1,6 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/nonexistent.json 2>&1 | FileCheck %s
+
+// Test that nonexistent file is detected
+
+// CHECK: failed to read TUSummary
+// CHECK: file does not exist
diff --git a/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test b/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
new file mode 100644
index 0000000000000..57bcab0bf8212
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-not-json-extension.test
@@ -0,0 +1,8 @@
+// RUN: not ssaf-serialization-format-test %S/not_json-extension.txt 2>&1 | FileCheck %s
+
+// Test that files with non-.json extensions are rejected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to read JSON object from file
+// CHECK: failed to validate JSON file
+// CHECK: not a JSON file
diff --git a/clang/test/Analysis/Scalable/Serialization/error-not-object.test b/clang/test/Analysis/Scalable/Serialization/error-not-object.test
new file mode 100644
index 0000000000000..ec34a20c9f641
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/error-not-object.test
@@ -0,0 +1,6 @@
+// RUN: not ssaf-serialization-format-test %S/Inputs/not-object.json 2>&1 | FileCheck %s
+
+// Test that JSON array (not object) is rejected
+
+// CHECK: failed to read TUSummary
+// CHECK: failed to read JSON object from file
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-callgraph.test b/clang/test/Analysis/Scalable/Serialization/valid-callgraph.test
new file mode 100644
index 0000000000000..09722d11a796e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/valid-callgraph.test
@@ -0,0 +1,9 @@
+// RUN: ssaf-serialization-format-test %S/Inputs/valid-callgraph.json -q -o %t.json
+// RUN: diff %t.json %S/Inputs/valid-callgraph-expected.json
+
+// Test CallGraph analysis round-trip serialization
+// This test verifies that:
+// - CallGraph analysis data can be read from JSON
+// - The call graph structure (caller -> callees) is preserved
+// - EntityId references are correctly serialized/deserialized
+// - Output matches expected format with alphabetically sorted fields
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-defuse.test b/clang/test/Analysis/Scalable/Serialization/valid-defuse.test
new file mode 100644
index 0000000000000..311bd02fdde3d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/valid-defuse.test
@@ -0,0 +1,10 @@
+// RUN: ssaf-serialization-format-test %S/Inputs/valid-defuse.json -q -o %t.json
+// RUN: diff %t.json %S/Inputs/valid-defuse-expected.json
+
+// Test DefUse analysis round-trip serialization
+// This test verifies that:
+// - DefUse analysis data can be read from JSON
+// - The def-use chain structure (variable -> definitions -> uses) is preserved
+// - Nested maps and sets are correctly serialized/deserialized
+// - EntityId references are correctly converted using EntityIdConverter
+// - Output matches expected format with alphabetically sorted fields
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-empty.test b/clang/test/Analysis/Scalable/Serialization/valid-empty.test
new file mode 100644
index 0000000000000..30712a3e8d637
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/valid-empty.test
@@ -0,0 +1,4 @@
+// RUN: ssaf-serialization-format-test %S/Inputs/valid-empty.json -q -o %t.json
+// RUN: diff %t.json %S/Inputs/valid-empty-expected.json
+
+// Test that an empty valid TUSummary can be read and written back
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-link-unit.test b/clang/test/Analysis/Scalable/Serialization/valid-link-unit.test
new file mode 100644
index 0000000000000..b9533904c0bd1
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/valid-link-unit.test
@@ -0,0 +1,4 @@
+// RUN: ssaf-serialization-format-test %S/Inputs/valid-link-unit.json -q -o %t.json
+// RUN: diff %t.json %S/Inputs/valid-link-unit-expected.json
+
+// Test that a TUSummary with link_unit namespace can be read and written back
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-with-empty-data-entry.test b/clang/test/Analysis/Scalable/Serialization/valid-with-empty-data-entry.test
new file mode 100644
index 0000000000000..a9be32902a10a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/valid-with-empty-data-entry.test
@@ -0,0 +1,4 @@
+// RUN: ssaf-serialization-format-test %S/Inputs/valid-with-empty-data-entry.json -q -o %t.json
+// RUN: diff %t.json %S/Inputs/valid-with-empty-data-entry-expected.json
+
+// Test that a TUSummary with an empty data entry can be read and written back
diff --git a/clang/test/Analysis/Scalable/Serialization/valid-with-idtable.test b/clang/test/Analysis/Scalable/Serialization/valid-with-idtable.test
new file mode 100644
index 0000000000000..a714aa7dcf2d1
--- /dev/null
+++ b/clang/test/Analysis/Scalable/Serialization/valid-with-idtable.test
@@ -0,0 +1,4 @@
+// RUN: ssaf-serialization-format-test %S/Inputs/valid-with-idtable.json -q -o %t.json
+// RUN: diff %t.json %S/Inputs/valid-with-idtable-expected.json
+
+// Test that a TUSummary with id_table entries can be read and written back
diff --git a/clang/test/lit.cfg.py b/clang/test/lit.cfg.py
index a622f5335354a..bb5c9b331a43f 100644
--- a/clang/test/lit.cfg.py
+++ b/clang/test/lit.cfg.py
@@ -103,6 +103,7 @@
"clang-diff",
"clang-format",
"clang-repl",
+ "ssaf-serialization-format-test",
"llvm-offload-binary",
"clang-tblgen",
"clang-scan-deps",
diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt b/clang/unittests/Analysis/Scalable/CMakeLists.txt
index e9bfca2c8fe7f..993ca3669228c 100644
--- a/clang/unittests/Analysis/Scalable/CMakeLists.txt
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -28,3 +28,6 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
FrontendOpenMP
Support
)
+
+add_subdirectory(tools)
+
diff --git a/clang/unittests/Analysis/Scalable/tools/CMakeLists.txt b/clang/unittests/Analysis/Scalable/tools/CMakeLists.txt
new file mode 100644
index 0000000000000..69e51c6a6aba5
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/tools/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(ssaf-serialization-format-test)
diff --git a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/CMakeLists.txt b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/CMakeLists.txt
new file mode 100644
index 0000000000000..ea008bee8d2db
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/CMakeLists.txt
@@ -0,0 +1,9 @@
+add_clang_tool(ssaf-serialization-format-test
+ SSAFSerializationFormatTest.cpp
+ ExampleAnalyses.cpp
+ )
+
+target_link_libraries(ssaf-serialization-format-test
+ PRIVATE
+ clangAnalysisScalable
+ )
diff --git a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
new file mode 100644
index 0000000000000..f8b4a535f00f3
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/ExampleAnalyses.cpp
@@ -0,0 +1,303 @@
+//===- ExampleAnalyses.cpp - Example analysis data for clang-ssaf-dump ---===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines example analysis data structures and their serialization/
+// deserialization functions for the JSONFormat.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/TUSummary/EntitySummary.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/Registry.h"
+#include <map>
+#include <set>
+
+using namespace clang::ssaf;
+
+//===----------------------------------------------------------------------===//
+// CallGraphAnalysis - Tracks caller-to-callees relationships
+//===----------------------------------------------------------------------===//
+
+namespace {
+struct CallGraphAnalysis : EntitySummary {
+ CallGraphAnalysis() : EntitySummary(SummaryName("CallGraph")) {}
+
+ // Maps each function (EntityId) to the set of functions it calls
+ std::map<EntityId, std::set<EntityId>> CallGraph;
+};
+} // namespace
+
+static llvm::json::Object
+serializeCallGraph(const EntitySummary &Data,
+ const JSONFormat::EntityIdConverter &Converter) {
+ const auto &CG = static_cast<const CallGraphAnalysis &>(Data);
+ llvm::json::Object Result;
+
+ // Serialize the call graph as an array of objects
+ llvm::json::Array CallGraphArray;
+ for (const auto &[Caller, Callees] : CG.CallGraph) {
+ llvm::json::Object Entry;
+ Entry["caller"] = Converter.toJSON(Caller);
+
+ llvm::json::Array CalleesArray;
+ for (const auto &Callee : Callees) {
+ CalleesArray.push_back(Converter.toJSON(Callee));
+ }
+ Entry["callees"] = std::move(CalleesArray);
+
+ CallGraphArray.push_back(std::move(Entry));
+ }
+
+ Result["call_graph"] = std::move(CallGraphArray);
+ return Result;
+}
+
+static llvm::Expected<std::unique_ptr<EntitySummary>>
+deserializeCallGraph(const llvm::json::Object &JSONObj, EntityIdTable &Table,
+ const JSONFormat::EntityIdConverter &Converter) {
+ auto Result = std::make_unique<CallGraphAnalysis>();
+
+ const llvm::json::Array *CallGraphArray = JSONObj.getArray("call_graph");
+ if (!CallGraphArray) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "CallGraph: missing or invalid 'call_graph' field");
+ }
+
+ for (size_t Index = 0; Index < CallGraphArray->size(); ++Index) {
+ const llvm::json::Object *EntryObj = (*CallGraphArray)[Index].getAsObject();
+ if (!EntryObj) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "CallGraph: call_graph entry at index %zu is not an object", Index);
+ }
+
+ // Parse caller
+ const llvm::json::Value *CallerValue = EntryObj->get("caller");
+ if (!CallerValue) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "CallGraph: entry at index %zu missing 'caller' field", Index);
+ }
+ auto CallerInt = CallerValue->getAsUINT64();
+ if (!CallerInt) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "CallGraph: 'caller' at index %zu is not a valid uint64", Index);
+ }
+ EntityId Caller = Converter.fromJSON(*CallerInt);
+
+ // Parse callees array
+ const llvm::json::Array *CalleesArray = EntryObj->getArray("callees");
+ if (!CalleesArray) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "CallGraph: entry at index %zu missing or invalid 'callees' field",
+ Index);
+ }
+
+ std::set<EntityId> Callees;
+ for (size_t CalleeIndex = 0; CalleeIndex < CalleesArray->size();
+ ++CalleeIndex) {
+ auto CalleeInt = (*CalleesArray)[CalleeIndex].getAsUINT64();
+ if (!CalleeInt) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "CallGraph: callee at index %zu,%zu is not a valid uint64", Index,
+ CalleeIndex);
+ }
+ Callees.insert(Converter.fromJSON(*CalleeInt));
+ }
+
+ Result->CallGraph[Caller] = std::move(Callees);
+ }
+
+ return std::move(Result);
+}
+
+namespace {
+using FormatInfo = JSONFormat::FormatInfo;
+struct CallGraphFormatInfo : FormatInfo {
+ CallGraphFormatInfo()
+ : FormatInfo{
+ SummaryName("CallGraph"),
+ serializeCallGraph,
+ deserializeCallGraph,
+ } {}
+};
+} // namespace
+
+static llvm::Registry<JSONFormat::FormatInfo>::Add<CallGraphFormatInfo>
+ RegisterCallGraph("CallGraphAnalysis",
+ "Format info for CallGraph analysis data");
+
+//===----------------------------------------------------------------------===//
+// DefUseAnalysis - Tracks definition-use chains
+//===----------------------------------------------------------------------===//
+
+namespace {
+struct DefUseAnalysis : EntitySummary {
+ DefUseAnalysis() : EntitySummary(SummaryName("DefUse")) {}
+
+ // For each variable (EntityId), maps definitions (EntityId) to their use
+ // sites (set of EntityId)
+ std::map<EntityId, std::map<EntityId, std::set<EntityId>>> DefUseChains;
+};
+} // namespace
+
+static llvm::json::Object
+serializeDefUse(const EntitySummary &Data,
+ const JSONFormat::EntityIdConverter &Converter) {
+ const auto &DU = static_cast<const DefUseAnalysis &>(Data);
+ llvm::json::Object Result;
+
+ // Serialize def-use chains as an array of objects
+ llvm::json::Array ChainsArray;
+ for (const auto &[Variable, DefUseMap] : DU.DefUseChains) {
+ llvm::json::Object VarEntry;
+ VarEntry["variable"] = Converter.toJSON(Variable);
+
+ llvm::json::Array DefsArray;
+ for (const auto &[Definition, Uses] : DefUseMap) {
+ llvm::json::Object DefEntry;
+ DefEntry["definition"] = Converter.toJSON(Definition);
+
+ llvm::json::Array UsesArray;
+ for (const auto &Use : Uses) {
+ UsesArray.push_back(Converter.toJSON(Use));
+ }
+ DefEntry["uses"] = std::move(UsesArray);
+
+ DefsArray.push_back(std::move(DefEntry));
+ }
+ VarEntry["definitions"] = std::move(DefsArray);
+
+ ChainsArray.push_back(std::move(VarEntry));
+ }
+
+ Result["def_use_chains"] = std::move(ChainsArray);
+ return Result;
+}
+
+static llvm::Expected<std::unique_ptr<EntitySummary>>
+deserializeDefUse(const llvm::json::Object &JSONObj, EntityIdTable &Table,
+ const JSONFormat::EntityIdConverter &Converter) {
+ auto Result = std::make_unique<DefUseAnalysis>();
+
+ const llvm::json::Array *ChainsArray = JSONObj.getArray("def_use_chains");
+ if (!ChainsArray) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: missing or invalid 'def_use_chains' field");
+ }
+
+ for (size_t VarIndex = 0; VarIndex < ChainsArray->size(); ++VarIndex) {
+ const llvm::json::Object *VarObj = (*ChainsArray)[VarIndex].getAsObject();
+ if (!VarObj) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: def_use_chains entry at index %zu is not an object",
+ VarIndex);
+ }
+
+ // Parse variable
+ const llvm::json::Value *VarValue = VarObj->get("variable");
+ if (!VarValue) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: entry at index %zu missing 'variable' field", VarIndex);
+ }
+ auto VarInt = VarValue->getAsUINT64();
+ if (!VarInt) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: 'variable' at index %zu is not a valid uint64", VarIndex);
+ }
+ EntityId Variable = Converter.fromJSON(*VarInt);
+
+ // Parse definitions array
+ const llvm::json::Array *DefsArray = VarObj->getArray("definitions");
+ if (!DefsArray) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: entry at index %zu missing or invalid 'definitions' field",
+ VarIndex);
+ }
+
+ std::map<EntityId, std::set<EntityId>> DefUseMap;
+ for (size_t DefIndex = 0; DefIndex < DefsArray->size(); ++DefIndex) {
+ const llvm::json::Object *DefObj = (*DefsArray)[DefIndex].getAsObject();
+ if (!DefObj) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: definition at index %zu,%zu is not an object", VarIndex,
+ DefIndex);
+ }
+
+ // Parse definition
+ const llvm::json::Value *DefValue = DefObj->get("definition");
+ if (!DefValue) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: definition at index %zu,%zu missing 'definition' field",
+ VarIndex, DefIndex);
+ }
+ auto DefInt = DefValue->getAsUINT64();
+ if (!DefInt) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: 'definition' at index %zu,%zu is not a valid uint64",
+ VarIndex, DefIndex);
+ }
+ EntityId Definition = Converter.fromJSON(*DefInt);
+
+ // Parse uses array
+ const llvm::json::Array *UsesArray = DefObj->getArray("uses");
+ if (!UsesArray) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: definition at index %zu,%zu missing or invalid 'uses' "
+ "field",
+ VarIndex, DefIndex);
+ }
+
+ std::set<EntityId> Uses;
+ for (size_t UseIndex = 0; UseIndex < UsesArray->size(); ++UseIndex) {
+ auto UseInt = (*UsesArray)[UseIndex].getAsUINT64();
+ if (!UseInt) {
+ return llvm::createStringError(
+ std::errc::invalid_argument,
+ "DefUse: use at index %zu,%zu,%zu is not a valid uint64",
+ VarIndex, DefIndex, UseIndex);
+ }
+ Uses.insert(Converter.fromJSON(*UseInt));
+ }
+
+ DefUseMap[Definition] = std::move(Uses);
+ }
+
+ Result->DefUseChains[Variable] = std::move(DefUseMap);
+ }
+
+ return std::move(Result);
+}
+
+namespace {
+struct DefUseFormatInfo : FormatInfo {
+ DefUseFormatInfo()
+ : FormatInfo{
+ SummaryName("DefUse"),
+ serializeDefUse,
+ deserializeDefUse,
+ } {}
+};
+} // namespace
+
+static llvm::Registry<JSONFormat::FormatInfo>::Add<DefUseFormatInfo>
+ RegisterDefUse("DefUseAnalysis", "Format info for DefUse analysis data");
diff --git a/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp
new file mode 100644
index 0000000000000..2262b9117e7d5
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/tools/ssaf-serialization-format-test/SSAFSerializationFormatTest.cpp
@@ -0,0 +1,84 @@
+//===- SSAFSerializationFormatTest.cpp - Test SSAF JSON serialization ---===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This tool reads SSAF TUSummary JSON files and can:
+// - Validate the JSON format
+// - Print summary information
+// - Write back to JSON (for round-trip testing)
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/VirtualFileSystem.h"
+#include "llvm/Support/WithColor.h"
+
+using namespace clang::ssaf;
+using namespace llvm;
+
+static cl::opt<std::string>
+ InputFilename(cl::Positional, cl::desc("<input file>"), cl::Required);
+
+static cl::opt<std::string> OutputFilename("o", cl::desc("Output filename"),
+ cl::value_desc("filename"),
+ cl::init("-"));
+
+static cl::opt<bool> PrintSummary("print-summary",
+ cl::desc("Print summary information"),
+ cl::init(false));
+
+static cl::opt<bool> Quiet("q", cl::desc("Suppress diagnostic messages"),
+ cl::init(false));
+
+static void reportError(StringRef Prefix, Error E) {
+ if (Quiet)
+ return;
+ WithColor::error(errs(), "ssaf-serialization-format-test")
+ << Prefix << ": " << toString(std::move(E)) << "\n";
+}
+
+int main(int argc, char **argv) {
+ InitLLVM X(argc, argv);
+ cl::ParseCommandLineOptions(argc, argv,
+ "SSAF JSON format validator and dumper\n");
+
+ // Create JSONFormat instance
+ auto FS = vfs::getRealFileSystem();
+ JSONFormat Format(FS);
+
+ // Read the input file
+ auto SummaryOrErr = Format.readTUSummary(InputFilename);
+ if (!SummaryOrErr) {
+ reportError("failed to read TUSummary", SummaryOrErr.takeError());
+ return 1;
+ }
+
+ TUSummary &Summary = *SummaryOrErr;
+
+ // Print summary information if requested
+ if (PrintSummary) {
+ outs() << "TUSummary successfully read from: " << InputFilename << "\n";
+ // Could add more detailed summary info here
+ }
+
+ // Write output if specified
+ if (OutputFilename != "-") {
+ if (auto Err = Format.writeTUSummary(Summary, OutputFilename)) {
+ reportError("failed to write TUSummary", std::move(Err));
+ return 1;
+ }
+ if (!Quiet) {
+ outs() << "TUSummary written to: " << OutputFilename << "\n";
+ }
+ }
+
+ return 0;
+}
>From cf6dd82d2c785089894daf09d88f1fe85538f9a6 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 12:35:00 -0800
Subject: [PATCH 6/7] Remove JSONFormatTest since we are using lit tests
---
.../Analysis/Scalable/CMakeLists.txt | 1 -
.../Analysis/Scalable/JSONFormatTest.cpp | 1127 -----------------
2 files changed, 1128 deletions(-)
delete mode 100644 clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt b/clang/unittests/Analysis/Scalable/CMakeLists.txt
index 993ca3669228c..3c642dd4b2e67 100644
--- a/clang/unittests/Analysis/Scalable/CMakeLists.txt
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -4,7 +4,6 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
EntityIdTest.cpp
EntityIdTableTest.cpp
EntityNameTest.cpp
- JSONFormatTest.cpp
Registries/FancyAnalysisData.cpp
Registries/MockSerializationFormat.cpp
Registries/MockSummaryExtractor1.cpp
diff --git a/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
deleted file mode 100644
index eb2ed77976bda..0000000000000
--- a/clang/unittests/Analysis/Scalable/JSONFormatTest.cpp
+++ /dev/null
@@ -1,1127 +0,0 @@
-//===- unittests/Analysis/Scalable/JSONFormatTest.cpp -------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
-#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
-#include "clang/Analysis/Scalable/Model/EntityId.h"
-#include "clang/Analysis/Scalable/Model/EntityIdTable.h"
-#include "clang/Analysis/Scalable/Model/EntityName.h"
-#include "clang/Analysis/Scalable/Model/SummaryName.h"
-#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
-#include "llvm/Support/FileSystem.h"
-#include "llvm/Support/Path.h"
-#include "llvm/Testing/Support/Error.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include <fstream>
-
-using namespace clang::ssaf;
-using llvm::Failed;
-using llvm::Succeeded;
-using ::testing::AllOf;
-using ::testing::HasSubstr;
-
-namespace {
-
-// Helper function to check that an error message contains all specified
-// substrings
-::testing::Matcher<std::string>
-ContainsAllSubstrings(std::initializer_list<const char *> substrings) {
- std::vector<::testing::Matcher<std::string>> matchers;
- for (const char *substr : substrings) {
- matchers.push_back(HasSubstr(substr));
- }
- return ::testing::AllOfArray(matchers);
-}
-
-//===----------------------------------------------------------------------===//
-// Test Fixtures and Helpers
-//===----------------------------------------------------------------------===//
-
-// Helper class to manage temporary directories for testing
-class TempDir {
- llvm::SmallString<128> Path;
- std::error_code EC;
-
-public:
- TempDir() {
- EC = llvm::sys::fs::createUniqueDirectory("JSONFormatTest", Path);
- }
-
- ~TempDir() {
- if (!EC && llvm::sys::fs::exists(Path)) {
- llvm::sys::fs::remove_directories(Path);
- }
- }
-
- llvm::StringRef path() const { return Path; }
- bool isValid() const { return !EC; }
-};
-
-// Helper function to write a file with content
-void writeFile(llvm::StringRef Path, llvm::StringRef Content) {
- std::ofstream File(Path.str());
- File << Content.str();
- File.close();
-}
-
-// Helper function to create a directory
-void createDir(llvm::StringRef Path) {
- llvm::sys::fs::create_directories(Path);
-}
-
-// Base test fixture for JSONFormat tests
-class JSONFormatTestBase : public ::testing::Test {
-protected:
- JSONFormat Format{llvm::vfs::getRealFileSystem()};
-};
-
-//===----------------------------------------------------------------------===//
-// TUSummary Read Tests - TUNamespace
-//===----------------------------------------------------------------------===//
-
-class JSONFormatReadTUNamespaceTest : public JSONFormatTestBase {};
-
-TEST_F(JSONFormatReadTUNamespaceTest, NonexistentDirectory) {
- auto Result = Format.readTUSummary("/nonexistent/path");
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from '/nonexistent/path'",
- "failed to read JSON from file", "tu_namespace.json",
- "failed to validate JSON file", "file does not exist"}));
-}
-
-TEST_F(JSONFormatReadTUNamespaceTest, MissingTUNamespaceFile) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg, ContainsAllSubstrings({"failed to read TUSummary from",
- "failed to read JSON from file",
- "tu_namespace.json",
- "file does not exist"}));
-}
-
-TEST_F(JSONFormatReadTUNamespaceTest, InvalidJSONSyntax) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath, "{ invalid json }");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg, ContainsAllSubstrings({"failed to read TUSummary from",
- "failed to read JSON from file",
- "tu_namespace.json"}));
-}
-
-TEST_F(JSONFormatReadTUNamespaceTest, NotJSONObject) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath, "[]");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings({"failed to read TUSummary from",
- "failed to read JSON object from file",
- "tu_namespace.json"}));
-}
-
-TEST_F(JSONFormatReadTUNamespaceTest, MissingKindField) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath, R"({"name": "test.cpp"})");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(
- ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize BuildNamespace from file",
- "tu_namespace.json",
- "missing required field 'kind' (expected BuildNamespaceKind)"}));
-}
-
-TEST_F(JSONFormatReadTUNamespaceTest, InvalidKindValue) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath, R"({"kind": "InvalidKind", "name": "test.cpp"})");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize BuildNamespace from file",
- "tu_namespace.json",
- "invalid 'kind' BuildNamespaceKind value 'InvalidKind'"}));
-}
-
-TEST_F(JSONFormatReadTUNamespaceTest, MissingNameField) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath, R"({"kind": "compilation_unit"})");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize BuildNamespace from file",
- "tu_namespace.json", "missing required field 'name'"}));
-}
-
-//===----------------------------------------------------------------------===//
-// TUSummary Read Tests - IdTable
-//===----------------------------------------------------------------------===//
-
-class JSONFormatReadIdTableTest : public JSONFormatTestBase {
-protected:
- void SetUpValidTUNamespace(TempDir &Dir) {
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath,
- R"({"kind": "compilation_unit", "name": "test.cpp"})");
- }
-};
-
-TEST_F(JSONFormatReadIdTableTest, MissingIdTableFile) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespace(Dir);
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings({"failed to read TUSummary from",
- "failed to read JSON from file",
- "id_table.json", "file does not exist"}));
-}
-
-TEST_F(JSONFormatReadIdTableTest, NotJSONArray) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespace(Dir);
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, "{}");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings({"failed to read TUSummary from",
- "failed to read JSON array from file",
- "id_table.json"}));
-}
-
-TEST_F(JSONFormatReadIdTableTest, ElementNotObject) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespace(Dir);
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, "[\"not an object\"]");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(
- ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize EntityIdTable from file", "id_table.json",
- "element at index 0 is not a JSON object",
- "expected EntityIdTable entry with 'id' and 'name' fields"}));
-}
-
-TEST_F(JSONFormatReadIdTableTest, EntryMissingName) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespace(Dir);
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, R"([{"id": 0}])");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize EntityIdTable entry from file",
- "id_table.json",
- "missing or invalid field 'name' (expected EntityName JSON "
- "object)"}));
-}
-
-TEST_F(JSONFormatReadIdTableTest, EntryMissingId) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespace(Dir);
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, R"([{
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
- }
- }])");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize EntityIdTable entry from file",
- "id_table.json",
- "missing required field 'id' (expected unsigned integer "
- "EntityId)"}));
-}
-
-TEST_F(JSONFormatReadIdTableTest, EntryIdNotInteger) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespace(Dir);
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, R"([{
- "id": "not a number",
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
- }
- }])");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(
- ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize EntityIdTable entry from file",
- "id_table.json", "field 'id' is not a valid unsigned 64-bit integer",
- "expected non-negative EntityId value"}));
-}
-
-TEST_F(JSONFormatReadIdTableTest, DuplicateEntityName) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespace(Dir);
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, R"([
- {
- "id": 0,
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
- }
- },
- {
- "id": 1,
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
- }
- }
- ])");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize EntityIdTable from file",
- "id_table.json", "duplicate EntityName found at index 1",
- "EntityId=0 already exists in table"}));
-}
-
-//===----------------------------------------------------------------------===//
-// TUSummary Read Tests - EntityName
-//===----------------------------------------------------------------------===//
-
-class JSONFormatReadEntityNameTest : public JSONFormatTestBase {
-protected:
- void SetUpValidTUNamespaceAndPartialIdTable(TempDir &Dir,
- llvm::StringRef EntityNameJson) {
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath,
- R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- std::string JsonContent = "[{\"id\": 0, \"name\": ";
- JsonContent += EntityNameJson.str();
- JsonContent += "}]";
- writeFile(IdTablePath, JsonContent);
- }
-};
-
-TEST_F(JSONFormatReadEntityNameTest, MissingUSR) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
- "suffix": "",
- "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
- })");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(
- ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize EntityName from file", "id_table.json",
- "missing required field 'usr' (Unified Symbol Resolution string)"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, MissingSuffix) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
- "usr": "c:@F at foo",
- "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
- })");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize EntityName from file",
- "id_table.json", "missing required field 'suffix'"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, MissingNamespace) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
- "usr": "c:@F at foo",
- "suffix": ""
- })");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize EntityName from file",
- "id_table.json", "missing or invalid field 'namespace'",
- "expected JSON array of BuildNamespace objects"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, NamespaceNotArray) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": "not an array"
- })");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize EntityName from file",
- "id_table.json", "missing or invalid field 'namespace'",
- "expected JSON array of BuildNamespace objects"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, NamespaceElementNotObject) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": ["not an object"]
- })");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize NestedBuildNamespace from file",
- "id_table.json", "element at index 0 is not a JSON object",
- "expected BuildNamespace object"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, NamespaceMissingKind) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [{"name": "test.cpp"}]
- })");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(
- ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize NestedBuildNamespace from file",
- "id_table.json", "at index 0",
- "failed to deserialize BuildNamespace from file",
- "missing required field 'kind' (expected BuildNamespaceKind)"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, NamespaceInvalidKind) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [{"kind": "InvalidKind", "name": "test.cpp"}]
- })");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize NestedBuildNamespace from file",
- "id_table.json", "at index 0",
- "failed to deserialize BuildNamespace from file",
- "invalid 'kind' BuildNamespaceKind value 'InvalidKind'"}));
-}
-
-TEST_F(JSONFormatReadEntityNameTest, NamespaceMissingName) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndPartialIdTable(Dir, R"({
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [{"kind": "compilation_unit"}]
- })");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize NestedBuildNamespace from file",
- "id_table.json", "at index 0",
- "failed to deserialize BuildNamespace from file",
- "missing required field 'name'"}));
-}
-
-//===----------------------------------------------------------------------===//
-// TUSummary Read Tests - Data Directory and Files
-//===----------------------------------------------------------------------===//
-
-class JSONFormatReadDataTest : public JSONFormatTestBase {
-protected:
- void SetUpValidTUNamespaceAndIdTable(TempDir &Dir) {
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath,
- R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, "[]");
- }
-};
-
-TEST_F(JSONFormatReadDataTest, MissingDataDirectory) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndIdTable(Dir);
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings({"failed to read TUSummary from",
- "data directory does not exist"}));
-}
-
-TEST_F(JSONFormatReadDataTest, DataPathIsFile) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndIdTable(Dir);
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- writeFile(DataPath, "content");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings({"failed to read TUSummary from",
- "data path is not a directory"}));
-}
-
-TEST_F(JSONFormatReadDataTest, NonJSONFileInDataDirectory) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndIdTable(Dir);
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- llvm::SmallString<128> SummaryPath(DataPath);
- llvm::sys::path::append(SummaryPath, "summary.txt");
- writeFile(SummaryPath, "{}");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to read TUSummary data from file", "summary.txt",
- "failed to validate JSON file", "not a JSON file"}));
-}
-
-TEST_F(JSONFormatReadDataTest, FileNotJSONObject) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndIdTable(Dir);
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- llvm::SmallString<128> SummaryPath(DataPath);
- llvm::sys::path::append(SummaryPath, "summary.json");
- writeFile(SummaryPath, "[]");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings({"failed to read TUSummary from",
- "failed to read TUSummary data from file",
- "summary.json",
- "failed to read JSON object from file"}));
-}
-
-TEST_F(JSONFormatReadDataTest, MissingSummaryName) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndIdTable(Dir);
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- llvm::SmallString<128> SummaryPath(DataPath);
- llvm::sys::path::append(SummaryPath, "summary.json");
- writeFile(SummaryPath, R"({"summary_data": []})");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize summary data from file",
- "summary.json", "missing required field 'summary_name'",
- "expected string identifier for the analysis summary"}));
-}
-
-TEST_F(JSONFormatReadDataTest, MissingSummaryData) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndIdTable(Dir);
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- llvm::SmallString<128> SummaryPath(DataPath);
- llvm::sys::path::append(SummaryPath, "summary.json");
- writeFile(SummaryPath, R"({"summary_name": "test"})");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg, ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize summary data from file",
- "summary.json", "for summary 'test'",
- "missing or invalid field 'summary_data'",
- "expected JSON array of entity summaries"}));
-}
-
-TEST_F(JSONFormatReadDataTest, SummaryDataNotArray) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndIdTable(Dir);
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- llvm::SmallString<128> SummaryPath(DataPath);
- llvm::sys::path::append(SummaryPath, "summary.json");
- writeFile(SummaryPath,
- R"({"summary_name": "test", "summary_data": "not an array"})");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg, ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize summary data from file",
- "summary.json", "for summary 'test'",
- "missing or invalid field 'summary_data'",
- "expected JSON array of entity summaries"}));
-}
-
-TEST_F(JSONFormatReadDataTest, DuplicateSummaryName) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceAndIdTable(Dir);
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- llvm::SmallString<128> SummaryPath1(DataPath);
- llvm::sys::path::append(SummaryPath1, "summary1.json");
- writeFile(SummaryPath1, R"({"summary_name": "test", "summary_data": []})");
-
- llvm::SmallString<128> SummaryPath2(DataPath);
- llvm::sys::path::append(SummaryPath2, "summary2.json");
- writeFile(SummaryPath2, R"({"summary_name": "test", "summary_data": []})");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(
- ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from", "failed to read TUSummary data",
- "duplicate SummaryName 'test' encountered in file"}));
-}
-
-//===----------------------------------------------------------------------===//
-// TUSummary Read Tests - Entity Data
-//===----------------------------------------------------------------------===//
-
-class JSONFormatReadEntityDataTest : public JSONFormatTestBase {
-protected:
- void SetUpValidTUNamespaceIdTableAndDataDir(TempDir &Dir,
- llvm::StringRef SummaryDataJson) {
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath,
- R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, "[]");
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- llvm::SmallString<128> SummaryPath(DataPath);
- llvm::sys::path::append(SummaryPath, "summary.json");
- std::string JsonContent = R"({"summary_name": "test", "summary_data": )";
- JsonContent += SummaryDataJson.str();
- JsonContent += "}";
- writeFile(SummaryPath, JsonContent);
- }
-};
-
-TEST_F(JSONFormatReadEntityDataTest, ElementNotObject) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceIdTableAndDataDir(Dir, "[\"not an object\"]");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(
- ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize entity data map from file", "summary.json",
- "element at index 0 is not a JSON object",
- "expected object with 'entity_id' and 'entity_summary' fields"}));
-}
-
-TEST_F(JSONFormatReadEntityDataTest, MissingEntityId) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceIdTableAndDataDir(Dir, R"([{"entity_summary": {}}])");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(
- ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize entity data map entry from file",
- "summary.json", "at index 0", "missing required field 'entity_id'",
- "expected unsigned integer EntityId"}));
-}
-
-TEST_F(JSONFormatReadEntityDataTest, EntityIdNotInteger) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceIdTableAndDataDir(
- Dir, R"([{"entity_id": "not a number", "entity_summary": {}}])");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(
- ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize entity data map entry from file",
- "summary.json", "at index 0",
- "field 'entity_id' is not a valid unsigned 64-bit integer"}));
-}
-
-TEST_F(JSONFormatReadEntityDataTest, MissingEntitySummary) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceIdTableAndDataDir(Dir, R"([{"entity_id": 0}])");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize entity data map entry from file",
- "summary.json", "at index 0",
- "missing or invalid field 'entity_summary'",
- "expected EntitySummary JSON object"}));
-}
-
-TEST_F(JSONFormatReadEntityDataTest, EntitySummaryNotObject) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceIdTableAndDataDir(
- Dir, R"([{"entity_id": 0, "entity_summary": "not an object"}])");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize entity data map entry from file",
- "summary.json", "at index 0",
- "missing or invalid field 'entity_summary'",
- "expected EntitySummary JSON object"}));
-}
-
-TEST_F(JSONFormatReadEntityDataTest,
- EntitySummaryDeserializationNotImplemented) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
- SetUpValidTUNamespaceIdTableAndDataDir(
- Dir, R"([{"entity_id": 0, "entity_summary": {}}])");
-
- auto Result = Format.readTUSummary(Dir.path());
- ASSERT_FALSE(Result);
- std::string ErrorMsg = llvm::toString(Result.takeError());
- EXPECT_THAT(ErrorMsg,
- ContainsAllSubstrings(
- {"failed to read TUSummary from",
- "failed to deserialize entity data map entry from file",
- "summary.json", "at index 0",
- "EntitySummary deserialization from file",
- "is not yet implemented"}));
-}
-
-// Note: DuplicateEntityId test cannot be implemented without EntitySummary
-// deserialization support, as the error occurs during EntitySummary parsing
-// before the duplicate check is reached.
-
-//===----------------------------------------------------------------------===//
-// TUSummary Write Tests
-//===----------------------------------------------------------------------===//
-
-class JSONFormatWriteTest : public JSONFormatTestBase {};
-
-TEST_F(JSONFormatWriteTest, Success) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- BuildNamespace TUNamespace = BuildNamespace::makeCompilationUnit("test.cpp");
- TUSummary Summary(TUNamespace);
-
- auto Error = Format.writeTUSummary(Summary, Dir.path());
- EXPECT_THAT_ERROR(std::move(Error), Succeeded());
-
- // Verify files were created
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- EXPECT_TRUE(llvm::sys::fs::exists(TUNamespacePath));
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- EXPECT_TRUE(llvm::sys::fs::exists(IdTablePath));
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- EXPECT_TRUE(llvm::sys::fs::exists(DataPath));
- EXPECT_TRUE(llvm::sys::fs::is_directory(DataPath));
-}
-
-TEST_F(JSONFormatWriteTest, DataDirectoryExistsAsFile) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- // Create 'data' as a file instead of directory
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- writeFile(DataPath, "content");
-
- BuildNamespace TUNamespace = BuildNamespace::makeCompilationUnit("test.cpp");
- TUSummary Summary(TUNamespace);
-
- auto Error = Format.writeTUSummary(Summary, Dir.path());
- ASSERT_TRUE(!!Error);
- std::string ErrorMsg = llvm::toString(std::move(Error));
- EXPECT_THAT(ErrorMsg, ContainsAllSubstrings(
- {"failed to write TUSummary to",
- "data path exists but is not a directory"}));
-}
-
-//===----------------------------------------------------------------------===//
-// TUSummary Success Cases
-//===----------------------------------------------------------------------===//
-
-class JSONFormatSuccessTest : public JSONFormatTestBase {};
-
-TEST_F(JSONFormatSuccessTest, ReadWithEmptyIdTable) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath,
- R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, "[]");
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- auto Result = Format.readTUSummary(Dir.path());
- EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
-}
-
-TEST_F(JSONFormatSuccessTest, ReadWithNonEmptyIdTable) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath,
- R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, R"([
- {
- "id": 0,
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
- }
- },
- {
- "id": 1,
- "name": {
- "usr": "c:@F at bar",
- "suffix": "1",
- "namespace": [
- {"kind": "compilation_unit", "name": "test.cpp"},
- {"kind": "link_unit", "name": "libtest.so"}
- ]
- }
- }
- ])");
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- auto Result = Format.readTUSummary(Dir.path());
- EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
-}
-
-TEST_F(JSONFormatSuccessTest, ReadWithEmptyData) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath,
- R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, "[]");
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- // Add an empty summary data file
- llvm::SmallString<128> SummaryPath(DataPath);
- llvm::sys::path::append(SummaryPath, "summary.json");
- writeFile(SummaryPath, R"({"summary_name": "test", "summary_data": []})");
-
- auto Result = Format.readTUSummary(Dir.path());
- EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
-}
-
-TEST_F(JSONFormatSuccessTest, ReadWithLinkUnitNamespace) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath, R"({"kind": "link_unit", "name": "libtest.so"})");
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, "[]");
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- auto Result = Format.readTUSummary(Dir.path());
- EXPECT_THAT_ERROR(Result.takeError(), Succeeded());
-}
-
-//===----------------------------------------------------------------------===//
-// Round-Trip Tests
-//===----------------------------------------------------------------------===//
-
-class JSONFormatRoundTripTest : public JSONFormatTestBase {};
-
-TEST_F(JSONFormatRoundTripTest, EmptyIdTable) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- BuildNamespace TUNamespace = BuildNamespace::makeCompilationUnit("test.cpp");
- TUSummary Summary(TUNamespace);
-
- auto WriteError = Format.writeTUSummary(Summary, Dir.path());
- EXPECT_THAT_ERROR(std::move(WriteError), Succeeded());
-
- auto ReadResult = Format.readTUSummary(Dir.path());
- EXPECT_THAT_ERROR(ReadResult.takeError(), Succeeded());
-}
-
-TEST_F(JSONFormatRoundTripTest, NonEmptyIdTable) {
- TempDir Dir;
- ASSERT_TRUE(Dir.isValid());
-
- // Manually create the files to test roundtrip
- llvm::SmallString<128> TUNamespacePath(Dir.path());
- llvm::sys::path::append(TUNamespacePath, "tu_namespace.json");
- writeFile(TUNamespacePath,
- R"({"kind": "compilation_unit", "name": "test.cpp"})");
-
- llvm::SmallString<128> IdTablePath(Dir.path());
- llvm::sys::path::append(IdTablePath, "id_table.json");
- writeFile(IdTablePath, R"([
- {
- "id": 0,
- "name": {
- "usr": "c:@F at foo",
- "suffix": "",
- "namespace": [{"kind": "compilation_unit", "name": "test.cpp"}]
- }
- }
- ])");
-
- llvm::SmallString<128> DataPath(Dir.path());
- llvm::sys::path::append(DataPath, "data");
- createDir(DataPath);
-
- auto ReadResult = Format.readTUSummary(Dir.path());
- ASSERT_THAT_EXPECTED(ReadResult, Succeeded());
-
- TempDir Dir2;
- ASSERT_TRUE(Dir2.isValid());
-
- auto WriteError = Format.writeTUSummary(*ReadResult, Dir2.path());
- EXPECT_THAT_ERROR(std::move(WriteError), Succeeded());
-
- // Verify the written files
- llvm::SmallString<128> TUNamespacePath2(Dir2.path());
- llvm::sys::path::append(TUNamespacePath2, "tu_namespace.json");
- EXPECT_TRUE(llvm::sys::fs::exists(TUNamespacePath2));
-}
-
-} // namespace
>From 7296235a0a53762ba7a25fe88eb1a94ff5e7ab6d Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Feb 2026 12:53:02 -0800
Subject: [PATCH 7/7] Cleanup changes to SummaryName.h
---
clang/include/clang/Analysis/Scalable/Model/SummaryName.h | 2 --
1 file changed, 2 deletions(-)
diff --git a/clang/include/clang/Analysis/Scalable/Model/SummaryName.h b/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
index 08ed67fe17bed..785fe0eb10372 100644
--- a/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
+++ b/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
@@ -10,8 +10,6 @@
#define LLVM_CLANG_ANALYSIS_SCALABLE_MODEL_SUMMARYNAME_H
#include "llvm/ADT/StringRef.h"
-#include "llvm/Support/Path.h"
-#include <cassert>
#include <string>
namespace clang::ssaf {
More information about the cfe-commits
mailing list