[clang] [clang][ssaf] Implement Entity Linker CLI and patching for JSON Format (PR #184713)
Aviral Goel via cfe-commits
cfe-commits at lists.llvm.org
Sat Mar 7 22:29:20 PST 2026
https://github.com/aviralg updated https://github.com/llvm/llvm-project/pull/184713
>From 99acae41e7ea2e46aa44fd55470acb6969cc888d Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Wed, 4 Mar 2026 16:38:03 -0800
Subject: [PATCH 01/13] Patching + EntityLinker CLI
---
.../EntityLinker/EntitySummaryEncoding.h | 3 +-
.../Scalable/Serialization/JSONFormat.h | 38 +-
.../Serialization/SerializationFormat.h | 2 +-
clang/lib/Analysis/Scalable/CMakeLists.txt | 1 +
.../Scalable/EntityLinker/EntityLinker.cpp | 8 +-
.../JSONFormat/JSONEntitySummaryEncoding.cpp | 68 ++
.../JSONFormat/JSONEntitySummaryEncoding.h | 55 ++
.../JSONFormat/JSONFormatImpl.cpp | 62 +-
.../Serialization/JSONFormat/JSONFormatImpl.h | 31 +-
.../Scalable/ssaf-linker/Inputs/tu-1.json | 159 +++++
.../Scalable/ssaf-linker/Inputs/tu-2.json | 160 +++++
.../Scalable/ssaf-linker/Inputs/tu-badext.txt | 1 +
.../ssaf-linker/Inputs/tu-dup-id.json | 28 +
.../ssaf-linker/Inputs/tu-dup-namespace.json | 9 +
.../Scalable/ssaf-linker/Inputs/tu-empty.json | 9 +
.../ssaf-linker/Inputs/tu-malformed.json | 1 +
.../Scalable/ssaf-linker/Inputs/tu-noext | 1 +
.../ssaf-linker/Inputs/tu-orphan-data.json | 30 +
.../Scalable/ssaf-linker/Outputs/lu-1+2.json | 652 ++++++++++++++++++
.../Scalable/ssaf-linker/Outputs/lu-1.json | 364 ++++++++++
.../Scalable/ssaf-linker/Outputs/lu-2.json | 370 ++++++++++
.../ssaf-linker/Outputs/lu-empty.json | 11 +
.../Analysis/Scalable/ssaf-linker/cli.test | 13 +
.../Analysis/Scalable/ssaf-linker/help.test | 23 +
.../Analysis/Scalable/ssaf-linker/io.test | 26 +
.../Scalable/ssaf-linker/linking-errors.test | 9 +
.../Scalable/ssaf-linker/linking.test | 30 +
.../Analysis/Scalable/ssaf-linker/time.test | 32 +
.../ssaf-linker/validation-errors.test | 49 ++
.../Scalable/ssaf-linker/verbose.test | 38 +
clang/tools/CMakeLists.txt | 1 +
clang/tools/ssaf-linker/CMakeLists.txt | 14 +
clang/tools/ssaf-linker/SSAFLinker.cpp | 335 +++++++++
.../Analysis/Scalable/EntityLinkerTest.cpp | 3 +-
.../SerializationFormatRegistryTest.cpp | 2 +-
.../JSONFormatTest/JSONFormatTest.cpp | 61 +-
.../JSONFormatTest/LUSummaryTest.cpp | 20 +-
.../JSONFormatTest/TUSummaryTest.cpp | 20 +-
38 files changed, 2649 insertions(+), 90 deletions(-)
create mode 100644 clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.cpp
create mode 100644 clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.h
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-1.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-2.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-badext.txt
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-dup-id.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-dup-namespace.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-empty.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-malformed.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-noext
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-orphan-data.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1+2.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-2.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-empty.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/cli.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/help.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/io.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/linking.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/time.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/verbose.test
create mode 100644 clang/tools/ssaf-linker/CMakeLists.txt
create mode 100644 clang/tools/ssaf-linker/SSAFLinker.cpp
diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/EntitySummaryEncoding.h b/clang/include/clang/Analysis/Scalable/EntityLinker/EntitySummaryEncoding.h
index a38dd0c895452..0a18bd6d1d0f9 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/EntitySummaryEncoding.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/EntitySummaryEncoding.h
@@ -15,6 +15,7 @@
#define LLVM_CLANG_ANALYSIS_SCALABLE_ENTITYLINKER_ENTITYSUMMARYENCODING_H
#include "clang/Analysis/Scalable/Model/EntityId.h"
+#include "llvm/Support/Error.h"
#include <map>
namespace clang::ssaf {
@@ -32,7 +33,7 @@ class EntitySummaryEncoding {
/// Updates EntityId references in the encoded data.
///
/// \param EntityResolutionTable Mapping from old EntityIds to new EntityIds.
- virtual void
+ virtual llvm::Error
patch(const std::map<EntityId, EntityId> &EntityResolutionTable) = 0;
};
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
index d93fe41f67972..60e4eeb97edb6 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -28,27 +28,18 @@ class EntityIdTable;
class EntitySummary;
class SummaryName;
+/// Call this from main() to prevent the linker from dead-stripping the
+/// JSONFormat library and its static registration objects.
+void initializeJSONFormat();
+
class JSONFormat final : public SerializationFormat {
using Array = llvm::json::Array;
using Object = llvm::json::Object;
+ using Value = llvm::json::Value;
-public:
- // 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;
- };
+ friend class JSONEntitySummaryEncoding;
+public:
llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) override;
llvm::Error writeTUSummary(const TUSummary &Summary,
@@ -71,11 +62,15 @@ class JSONFormat final : public SerializationFormat {
llvm::Error writeLUSummaryEncoding(const LUSummaryEncoding &SummaryEncoding,
llvm::StringRef Path) override;
- using SerializerFn = llvm::function_ref<Object(const EntitySummary &,
- const EntityIdConverter &)>;
+ using EntityIdToJSONFn = llvm::function_ref<Object(EntityId)>;
+ using EntityIdFromJSONFn =
+ llvm::function_ref<llvm::Expected<EntityId>(const Object &)>;
+
+ using SerializerFn =
+ llvm::function_ref<Object(const EntitySummary &, EntityIdToJSONFn)>;
using DeserializerFn =
llvm::function_ref<llvm::Expected<std::unique_ptr<EntitySummary>>(
- const Object &, EntityIdTable &, const EntityIdConverter &)>;
+ const Object &, EntityIdTable &, EntityIdFromJSONFn)>;
using FormatInfo = FormatInfoEntry<SerializerFn, DeserializerFn>;
@@ -86,6 +81,11 @@ class JSONFormat final : public SerializationFormat {
EntityId entityIdFromJSON(const uint64_t EntityIdIndex) const;
uint64_t entityIdToJSON(EntityId EI) const;
+ llvm::Expected<EntityId>
+ entityIdFromJSONObject(const Object &EntityIdObject) const;
+ static Value *entityIdReferenceFromJSONObject(Object &EntityIdObject);
+ Object entityIdToJSONObject(EntityId EI) const;
+
llvm::Expected<BuildNamespace>
buildNamespaceFromJSON(const Object &BuildNamespaceObject) const;
Object buildNamespaceToJSON(const BuildNamespace &BN) const;
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index c86ca7ef960e9..55e1ec7addc6c 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -58,7 +58,7 @@ class SerializationFormat {
// Helpers providing access to implementation details of basic data structures
// for efficient serialization/deserialization.
- EntityId makeEntityId(const size_t Index) const { return EntityId(Index); }
+ static EntityId makeEntityId(const size_t Index) { return EntityId(Index); }
#define FIELD(CLASS, FIELD_NAME) \
static const auto &get##FIELD_NAME(const CLASS &X) { return X.FIELD_NAME; } \
diff --git a/clang/lib/Analysis/Scalable/CMakeLists.txt b/clang/lib/Analysis/Scalable/CMakeLists.txt
index 81550df4565cb..52f56fa6261ed 100644
--- a/clang/lib/Analysis/Scalable/CMakeLists.txt
+++ b/clang/lib/Analysis/Scalable/CMakeLists.txt
@@ -11,6 +11,7 @@ add_clang_library(clangAnalysisScalable
Model/EntityLinkage.cpp
Model/EntityName.cpp
Model/SummaryName.cpp
+ Serialization/JSONFormat/JSONEntitySummaryEncoding.cpp
Serialization/JSONFormat/JSONFormatImpl.cpp
Serialization/JSONFormat/LUSummary.cpp
Serialization/JSONFormat/LUSummaryEncoding.cpp
diff --git a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
index cd0c83a38a377..5449dada64c68 100644
--- a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
+++ b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
@@ -166,7 +166,13 @@ void EntityLinker::patch(
const std::map<EntityId, EntityId> &EntityResolutionTable) {
for (auto *PatchTarget : PatchTargets) {
assert(PatchTarget && "EntityLinker::patch: Patch target cannot be null");
- PatchTarget->patch(EntityResolutionTable);
+
+ if (auto Err = PatchTarget->patch(EntityResolutionTable)) {
+ std::string PatchingErrorMessage = llvm::toString(std::move(Err));
+ ErrorBuilder::fatal("{0} - {1}",
+ ErrorMessages::EntityLinkerFatalErrorPrefix,
+ PatchingErrorMessage);
+ }
}
}
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.cpp
new file mode 100644
index 0000000000000..13a20ed1800a4
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.cpp
@@ -0,0 +1,68 @@
+//===- JSONEntitySummaryEncoding.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 "JSONEntitySummaryEncoding.h"
+#include "JSONFormatImpl.h"
+
+namespace clang::ssaf {
+
+llvm::Error JSONEntitySummaryEncoding::patchObject(
+ llvm::json::Object &Obj, const std::map<EntityId, EntityId> &Table) {
+
+ if (auto AtVal = JSONFormat::entityIdReferenceFromJSONObject(Obj)) {
+ std::optional<uint64_t> OptEntityIdIndex = AtVal->getAsUINT64();
+ if (!OptEntityIdIndex) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadEntityIdObject,
+ JSONEntityIdKey)
+ .build();
+ }
+
+ auto OldId = JSONFormat::makeEntityId(*OptEntityIdIndex);
+ auto It = Table.find(OldId);
+ if (It == Table.end()) {
+ return ErrorBuilder::create(
+ std::errc::invalid_argument,
+ ErrorMessages::FailedToPatchEntityIdNotInTable, OldId)
+ .build();
+ }
+
+ *AtVal = static_cast<uint64_t>(JSONFormat::getIndex(It->second));
+ } else {
+ for (auto &[Key, Val] : Obj) {
+ if (auto Err = patchValue(Val, Table)) {
+ return Err;
+ }
+ }
+ }
+
+ return llvm::Error::success();
+}
+
+llvm::Error JSONEntitySummaryEncoding::patchValue(
+ llvm::json::Value &V, const std::map<EntityId, EntityId> &Table) {
+ if (llvm::json::Object *Obj = V.getAsObject()) {
+ if (auto Err = patchObject(*Obj, Table)) {
+ return Err;
+ }
+ } else if (llvm::json::Array *Arr = V.getAsArray()) {
+ for (auto &Val : *Arr) {
+ if (auto Err = patchValue(Val, Table)) {
+ return Err;
+ }
+ }
+ }
+ return llvm::Error::success();
+}
+
+llvm::Error JSONEntitySummaryEncoding::patch(
+ const std::map<EntityId, EntityId> &EntityResolutionTable) {
+ return patchValue(Data, EntityResolutionTable);
+}
+
+} // namespace clang::ssaf
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.h b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.h
new file mode 100644
index 0000000000000..92fec92650c4c
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.h
@@ -0,0 +1,55 @@
+//===- JSONEntitySummaryEncoding.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Opaque JSON-based entity summary encoding used by JSONFormat. Stores raw
+// EntitySummary JSON blobs and patches embedded entity ID references without
+// requiring knowledge of the analysis schema.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef CLANG_LIB_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_JSONENTITYSUMMARYENCODING_H
+#define CLANG_LIB_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_JSONENTITYSUMMARYENCODING_H
+
+#include "clang/Analysis/Scalable/EntityLinker/EntitySummaryEncoding.h"
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "llvm/Support/JSON.h"
+
+#include <map>
+
+namespace clang::ssaf {
+
+//----------------------------------------------------------------------------
+// JSONEntitySummaryEncoding
+//
+// Concrete EntitySummaryEncoding used by JSONFormat for both TUSummaryEncoding
+// and LUSummaryEncoding. Stores the raw EntitySummary JSON value opaquely so
+// the linker can patch and emit it without knowing the analysis schema.
+//----------------------------------------------------------------------------
+
+class JSONEntitySummaryEncoding final : public EntitySummaryEncoding {
+ friend JSONFormat;
+
+public:
+ llvm::Error
+ patch(const std::map<EntityId, EntityId> &EntityResolutionTable) override;
+
+private:
+ explicit JSONEntitySummaryEncoding(llvm::json::Value Data)
+ : Data(std::move(Data)) {}
+
+ static llvm::Error patchObject(llvm::json::Object &Obj,
+ const std::map<EntityId, EntityId> &Table);
+ static llvm::Error patchValue(llvm::json::Value &V,
+ const std::map<EntityId, EntityId> &Table);
+
+ llvm::json::Value Data;
+};
+
+} // namespace clang::ssaf
+
+#endif // CLANG_LIB_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_JSONENTITYSUMMARYENCODING_H
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
index 0cf35fdae6927..5f49ee5c10898 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
@@ -8,13 +8,19 @@
#include "JSONFormatImpl.h"
+#include "clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h"
#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
#include "llvm/Support/Registry.h"
LLVM_INSTANTIATE_REGISTRY(llvm::Registry<clang::ssaf::JSONFormat::FormatInfo>)
+static clang::ssaf::SerializationFormatRegistry::Add<clang::ssaf::JSONFormat>
+ RegisterJSONFormat("JSON", "JSON serialization format");
+
namespace clang::ssaf {
+void initializeJSONFormat() {}
+
//----------------------------------------------------------------------------
// JSON Reader and Writer
//----------------------------------------------------------------------------
@@ -142,6 +148,53 @@ uint64_t JSONFormat::entityIdToJSON(EntityId EI) const {
return static_cast<uint64_t>(getIndex(EI));
}
+llvm::Expected<EntityId>
+JSONFormat::entityIdFromJSONObject(const Object &EntityIdObject) const {
+ if (EntityIdObject.size() != 1) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadEntityIdObject,
+ JSONEntityIdKey)
+ .build();
+ }
+
+ const llvm::json::Value *AtVal = EntityIdObject.get(JSONEntityIdKey);
+ if (!AtVal) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadEntityIdObject,
+ JSONEntityIdKey)
+ .build();
+ }
+
+ std::optional<uint64_t> OptEntityIdIndex = AtVal->getAsUINT64();
+ if (!OptEntityIdIndex) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadEntityIdObject,
+ JSONEntityIdKey)
+ .build();
+ }
+
+ return makeEntityId(static_cast<size_t>(*OptEntityIdIndex));
+}
+
+Value *JSONFormat::entityIdReferenceFromJSONObject(Object &EntityIdObject) {
+ if (EntityIdObject.size() != 1) {
+ return nullptr;
+ }
+
+ llvm::json::Value *AtVal = EntityIdObject.get(JSONEntityIdKey);
+ if (!AtVal) {
+ return nullptr;
+ }
+
+ return AtVal;
+}
+
+Object JSONFormat::entityIdToJSONObject(EntityId EI) const {
+ Object Result;
+ Result["@"] = static_cast<uint64_t>(getIndex(EI));
+ return Result;
+}
+
//----------------------------------------------------------------------------
// BuildNamespaceKind
//----------------------------------------------------------------------------
@@ -611,8 +664,9 @@ JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
const auto &InfoEntry = InfoIt->second;
assert(InfoEntry.ForSummary == SN);
- EntityIdConverter Converter(*this);
- return InfoEntry.Deserialize(EntitySummaryObject, IdTable, Converter);
+ return InfoEntry.Deserialize(
+ EntitySummaryObject, IdTable,
+ [this](const Object &Obj) { return entityIdFromJSONObject(Obj); });
}
llvm::Expected<Object>
@@ -629,8 +683,8 @@ JSONFormat::entitySummaryToJSON(const SummaryName &SN,
const auto &InfoEntry = InfoIt->second;
assert(InfoEntry.ForSummary == SN);
- EntityIdConverter Converter(*this);
- return InfoEntry.Serialize(ES, Converter);
+ return InfoEntry.Serialize(
+ ES, [this](EntityId EI) { return entityIdToJSONObject(EI); });
}
//----------------------------------------------------------------------------
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.h b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.h
index b51313c5e6877..74837d845b8f1 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.h
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.h
@@ -15,6 +15,7 @@
#define CLANG_LIB_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_JSONFORMATIMPL_H
#include "../../ModelStringConversions.h"
+#include "JSONEntitySummaryEncoding.h"
#include "clang/Analysis/Scalable/EntityLinker/EntitySummaryEncoding.h"
#include "clang/Analysis/Scalable/Model/EntityLinkage.h"
#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
@@ -108,31 +109,25 @@ inline constexpr const char *FailedToDeserializeLinkageTableExtraId =
inline constexpr const char *FailedToDeserializeLinkageTableMissingId =
"failed to deserialize LinkageTable: missing '{0}' present in IdTable";
+inline constexpr const char *FailedToReadEntityIdObject =
+ "failed to read EntityId: expected JSON object with a single '{0}' key "
+ "mapped to a number (unsigned 64-bit integer)";
+
+inline constexpr const char *FailedToPatchEntityIdNotInTable =
+ "failed to patch EntityId: '{0}' not found in entity resolution table";
+
} // namespace ErrorMessages
//----------------------------------------------------------------------------
-// JSONEntitySummaryEncoding
-//
-// Concrete EntitySummaryEncoding used by JSONFormat for both TUSummaryEncoding
-// and LUSummaryEncoding. Stores the raw EntitySummary JSON value opaquely so
-// the linker can patch and emit it without knowing the analysis schema.
+// Entity Id JSON Representation
//----------------------------------------------------------------------------
-class JSONEntitySummaryEncoding final : public EntitySummaryEncoding {
- friend JSONFormat;
-
-public:
- void
- patch(const std::map<EntityId, EntityId> &EntityResolutionTable) override {
- ErrorBuilder::fatal("will be implemented in the future");
- }
+namespace {
-private:
- explicit JSONEntitySummaryEncoding(llvm::json::Value Data)
- : Data(std::move(Data)) {}
+/// An entity ID is encoded as the single-key object {"@": <index>}.
+inline constexpr const char *JSONEntityIdKey = "@";
- llvm::json::Value Data;
-};
+} // namespace
//----------------------------------------------------------------------------
// JSON Reader and Writer
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-1.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-1.json
new file mode 100644
index 0000000000000..b67f9aad690c3
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-1.json
@@ -0,0 +1,159 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "tu1.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "namespace": [{ "kind": "CompilationUnit", "name": "tu1.cpp" }],
+ "suffix": "",
+ "usr": "c:@F at shared_ext#"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "namespace": [{ "kind": "CompilationUnit", "name": "tu1.cpp" }],
+ "suffix": "",
+ "usr": "c:@F at shared_int#"
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "namespace": [{ "kind": "CompilationUnit", "name": "tu1.cpp" }],
+ "suffix": "",
+ "usr": "c:@F at shared_none#"
+ }
+ },
+ {
+ "id": 3,
+ "name": {
+ "namespace": [{ "kind": "CompilationUnit", "name": "tu1.cpp" }],
+ "suffix": "",
+ "usr": "c:@F at unique_ext_tu1#"
+ }
+ },
+ {
+ "id": 4,
+ "name": {
+ "namespace": [{ "kind": "CompilationUnit", "name": "tu1.cpp" }],
+ "suffix": "",
+ "usr": "c:@F at unique_int_tu1#"
+ }
+ },
+ {
+ "id": 5,
+ "name": {
+ "namespace": [{ "kind": "CompilationUnit", "name": "tu1.cpp" }],
+ "suffix": "",
+ "usr": "c:@F at unique_none_tu1#"
+ }
+ }
+ ],
+ "linkage_table": [
+ { "id": 0, "linkage": { "type": "External" } },
+ { "id": 1, "linkage": { "type": "Internal" } },
+ { "id": 2, "linkage": { "type": "None" } },
+ { "id": 3, "linkage": { "type": "External" } },
+ { "id": 4, "linkage": { "type": "Internal" } },
+ { "id": 5, "linkage": { "type": "None" } }
+ ],
+ "data": [
+ {
+ "summary_name": "CallGraph",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": { "call_count": 3, "callees": [{ "@": 1 }, { "@": 2 }, { "@": 3 }] }
+ },
+ {
+ "entity_id": 1,
+ "entity_summary": { "call_count": 2, "callees": [{ "@": 0 }, { "@": 4 }] }
+ },
+ {
+ "entity_id": 2,
+ "entity_summary": { "call_count": 1, "callees": [{ "@": 5 }] }
+ },
+ {
+ "entity_id": 3,
+ "entity_summary": { "call_count": 2, "callees": [{ "@": 0 }, { "@": 1 }] }
+ },
+ {
+ "entity_id": 4,
+ "entity_summary": { "call_count": 1, "callees": [{ "@": 3 }] }
+ },
+ {
+ "entity_id": 5,
+ "entity_summary": { "call_count": 3, "callees": [{ "@": 0 }, { "@": 3 }, { "@": 4 }] }
+ }
+ ]
+ },
+ {
+ "summary_name": "TypeInfo",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "direct": { "@": 3 },
+ "indirect": [
+ { "entity": { "@": 1 }, "level": 1 },
+ { "entity": { "@": 4 }, "level": 2 }
+ ]
+ }
+ },
+ {
+ "entity_id": 1,
+ "entity_summary": {
+ "direct": { "@": 0 },
+ "indirect": [
+ { "entity": { "@": 2 }, "level": 1 },
+ { "entity": { "@": 5 }, "level": 2 }
+ ]
+ }
+ },
+ {
+ "entity_id": 2,
+ "entity_summary": {
+ "direct": { "@": 1 },
+ "indirect": [
+ { "entity": { "@": 3 }, "level": 1 }
+ ]
+ }
+ },
+ {
+ "entity_id": 3,
+ "entity_summary": {
+ "direct": { "@": 4 },
+ "indirect": [
+ { "entity": { "@": 0 }, "level": 1 },
+ { "entity": { "@": 2 }, "level": 2 }
+ ]
+ }
+ },
+ {
+ "entity_id": 4,
+ "entity_summary": {
+ "direct": { "@": 5 },
+ "indirect": [
+ { "entity": { "@": 1 }, "level": 1 }
+ ]
+ }
+ },
+ {
+ "entity_id": 5,
+ "entity_summary": {
+ "direct": { "@": 2 },
+ "indirect": [
+ { "entity": { "@": 0 }, "level": 1 },
+ { "entity": { "@": 3 }, "level": 2 },
+ { "entity": { "@": 4 }, "level": 3 }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-2.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-2.json
new file mode 100644
index 0000000000000..ac6e385e6b49d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-2.json
@@ -0,0 +1,160 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "tu2.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "namespace": [{ "kind": "CompilationUnit", "name": "tu2.cpp" }],
+ "suffix": "",
+ "usr": "c:@F at shared_ext#"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "namespace": [{ "kind": "CompilationUnit", "name": "tu2.cpp" }],
+ "suffix": "",
+ "usr": "c:@F at shared_int#"
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "namespace": [{ "kind": "CompilationUnit", "name": "tu2.cpp" }],
+ "suffix": "",
+ "usr": "c:@F at shared_none#"
+ }
+ },
+ {
+ "id": 3,
+ "name": {
+ "namespace": [{ "kind": "CompilationUnit", "name": "tu2.cpp" }],
+ "suffix": "",
+ "usr": "c:@F at unique_ext_tu2#"
+ }
+ },
+ {
+ "id": 4,
+ "name": {
+ "namespace": [{ "kind": "CompilationUnit", "name": "tu2.cpp" }],
+ "suffix": "",
+ "usr": "c:@F at unique_int_tu2#"
+ }
+ },
+ {
+ "id": 5,
+ "name": {
+ "namespace": [{ "kind": "CompilationUnit", "name": "tu2.cpp" }],
+ "suffix": "",
+ "usr": "c:@F at unique_none_tu2#"
+ }
+ }
+ ],
+ "linkage_table": [
+ { "id": 0, "linkage": { "type": "External" } },
+ { "id": 1, "linkage": { "type": "Internal" } },
+ { "id": 2, "linkage": { "type": "None" } },
+ { "id": 3, "linkage": { "type": "External" } },
+ { "id": 4, "linkage": { "type": "Internal" } },
+ { "id": 5, "linkage": { "type": "None" } }
+ ],
+ "data": [
+ {
+ "summary_name": "CallGraph",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": { "call_count": 3, "callees": [{ "@": 1 }, { "@": 2 }, { "@": 5 }] }
+ },
+ {
+ "entity_id": 1,
+ "entity_summary": { "call_count": 2, "callees": [{ "@": 0 }, { "@": 3 }] }
+ },
+ {
+ "entity_id": 2,
+ "entity_summary": { "call_count": 1, "callees": [{ "@": 4 }] }
+ },
+ {
+ "entity_id": 3,
+ "entity_summary": { "call_count": 2, "callees": [{ "@": 0 }, { "@": 2 }] }
+ },
+ {
+ "entity_id": 4,
+ "entity_summary": { "call_count": 1, "callees": [{ "@": 3 }] }
+ },
+ {
+ "entity_id": 5,
+ "entity_summary": { "call_count": 3, "callees": [{ "@": 1 }, { "@": 2 }, { "@": 3 }] }
+ }
+ ]
+ },
+ {
+ "summary_name": "TypeInfo",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "direct": { "@": 3 },
+ "indirect": [
+ { "entity": { "@": 2 }, "level": 1 },
+ { "entity": { "@": 5 }, "level": 2 }
+ ]
+ }
+ },
+ {
+ "entity_id": 1,
+ "entity_summary": {
+ "direct": { "@": 0 },
+ "indirect": [
+ { "entity": { "@": 4 }, "level": 1 }
+ ]
+ }
+ },
+ {
+ "entity_id": 2,
+ "entity_summary": {
+ "direct": { "@": 5 },
+ "indirect": [
+ { "entity": { "@": 0 }, "level": 1 },
+ { "entity": { "@": 3 }, "level": 2 }
+ ]
+ }
+ },
+ {
+ "entity_id": 3,
+ "entity_summary": {
+ "direct": { "@": 1 },
+ "indirect": [
+ { "entity": { "@": 2 }, "level": 1 },
+ { "entity": { "@": 0 }, "level": 2 }
+ ]
+ }
+ },
+ {
+ "entity_id": 4,
+ "entity_summary": {
+ "direct": { "@": 2 },
+ "indirect": [
+ { "entity": { "@": 5 }, "level": 1 },
+ { "entity": { "@": 3 }, "level": 2 }
+ ]
+ }
+ },
+ {
+ "entity_id": 5,
+ "entity_summary": {
+ "direct": { "@": 4 },
+ "indirect": [
+ { "entity": { "@": 1 }, "level": 1 },
+ { "entity": { "@": 2 }, "level": 2 },
+ { "entity": { "@": 0 }, "level": 3 }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-badext.txt b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-badext.txt
new file mode 100644
index 0000000000000..a2466bf0a9eb9
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-badext.txt
@@ -0,0 +1 @@
+This file exists to exercise the unsupported-extension error path in cli.test.
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-dup-id.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-dup-id.json
new file mode 100644
index 0000000000000..85e884b96590b
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-dup-id.json
@@ -0,0 +1,28 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "tu-dup-id.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "namespace": [{ "kind": "CompilationUnit", "name": "tu-dup-id.cpp" }],
+ "suffix": "",
+ "usr": "c:@F at foo#"
+ }
+ },
+ {
+ "id": 0,
+ "name": {
+ "namespace": [{ "kind": "CompilationUnit", "name": "tu-dup-id.cpp" }],
+ "suffix": "",
+ "usr": "c:@F at bar#"
+ }
+ }
+ ],
+ "linkage_table": [
+ { "id": 0, "linkage": { "type": "External" } }
+ ],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-dup-namespace.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-dup-namespace.json
new file mode 100644
index 0000000000000..4550e814bceab
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-dup-namespace.json
@@ -0,0 +1,9 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "tu1.cpp"
+ },
+ "id_table": [],
+ "linkage_table": [],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-empty.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-empty.json
new file mode 100644
index 0000000000000..ec1d262f42653
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-empty.json
@@ -0,0 +1,9 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "empty.cpp"
+ },
+ "id_table": [],
+ "linkage_table": [],
+ "data": []
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-malformed.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-malformed.json
new file mode 100644
index 0000000000000..88b85f4dd1a1b
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-malformed.json
@@ -0,0 +1 @@
+this is not valid json {{{
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-noext b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-noext
new file mode 100644
index 0000000000000..6b1b7f06742ea
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-noext
@@ -0,0 +1 @@
+This file exists to exercise the no-extension error path in cli.test.
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-orphan-data.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-orphan-data.json
new file mode 100644
index 0000000000000..f0aa5507ec19a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-orphan-data.json
@@ -0,0 +1,30 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "tu-orphan-data.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "namespace": [{ "kind": "CompilationUnit", "name": "tu-orphan-data.cpp" }],
+ "suffix": "",
+ "usr": "c:@F at foo#"
+ }
+ }
+ ],
+ "linkage_table": [
+ { "id": 0, "linkage": { "type": "External" } }
+ ],
+ "data": [
+ {
+ "summary_name": "TestAnalysis",
+ "summary_data": [
+ {
+ "entity_id": 99,
+ "entity_summary": {}
+ }
+ ]
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1+2.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1+2.json
new file mode 100644
index 0000000000000..6a74d1e341c2c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1+2.json
@@ -0,0 +1,652 @@
+{
+ "data": [
+ {
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "call_count": 3,
+ "callees": [
+ {
+ "@": 1
+ },
+ {
+ "@": 2
+ },
+ {
+ "@": 3
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 1,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": [
+ {
+ "@": 0
+ },
+ {
+ "@": 4
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 2,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": [
+ {
+ "@": 5
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 3,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": [
+ {
+ "@": 0
+ },
+ {
+ "@": 1
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 4,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": [
+ {
+ "@": 3
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 5,
+ "entity_summary": {
+ "call_count": 3,
+ "callees": [
+ {
+ "@": 0
+ },
+ {
+ "@": 3
+ },
+ {
+ "@": 4
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 6,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": [
+ {
+ "@": 0
+ },
+ {
+ "@": 8
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 7,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": [
+ {
+ "@": 9
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 8,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": [
+ {
+ "@": 0
+ },
+ {
+ "@": 7
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 9,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": [
+ {
+ "@": 8
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 10,
+ "entity_summary": {
+ "call_count": 3,
+ "callees": [
+ {
+ "@": 6
+ },
+ {
+ "@": 7
+ },
+ {
+ "@": 8
+ }
+ ]
+ }
+ }
+ ],
+ "summary_name": "CallGraph"
+ },
+ {
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "direct": {
+ "@": 3
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 1
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 4
+ },
+ "level": 2
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 1,
+ "entity_summary": {
+ "direct": {
+ "@": 0
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 2
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 5
+ },
+ "level": 2
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 2,
+ "entity_summary": {
+ "direct": {
+ "@": 1
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 3
+ },
+ "level": 1
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 3,
+ "entity_summary": {
+ "direct": {
+ "@": 4
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 0
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 2
+ },
+ "level": 2
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 4,
+ "entity_summary": {
+ "direct": {
+ "@": 5
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 1
+ },
+ "level": 1
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 5,
+ "entity_summary": {
+ "direct": {
+ "@": 2
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 0
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 3
+ },
+ "level": 2
+ },
+ {
+ "entity": {
+ "@": 4
+ },
+ "level": 3
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 6,
+ "entity_summary": {
+ "direct": {
+ "@": 0
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 9
+ },
+ "level": 1
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 7,
+ "entity_summary": {
+ "direct": {
+ "@": 10
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 0
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 8
+ },
+ "level": 2
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 8,
+ "entity_summary": {
+ "direct": {
+ "@": 6
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 7
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 0
+ },
+ "level": 2
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 9,
+ "entity_summary": {
+ "direct": {
+ "@": 7
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 10
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 8
+ },
+ "level": 2
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 10,
+ "entity_summary": {
+ "direct": {
+ "@": 9
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 6
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 7
+ },
+ "level": 2
+ },
+ {
+ "entity": {
+ "@": 0
+ },
+ "level": 3
+ }
+ ]
+ }
+ }
+ ],
+ "summary_name": "TypeInfo"
+ }
+ ],
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1+2"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at shared_ext#"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu1.cpp"
+ },
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1+2"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at shared_int#"
+ }
+ },
+ {
+ "id": 6,
+ "name": {
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu2.cpp"
+ },
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1+2"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at shared_int#"
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu1.cpp"
+ },
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1+2"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at shared_none#"
+ }
+ },
+ {
+ "id": 7,
+ "name": {
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu2.cpp"
+ },
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1+2"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at shared_none#"
+ }
+ },
+ {
+ "id": 3,
+ "name": {
+ "namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1+2"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at unique_ext_tu1#"
+ }
+ },
+ {
+ "id": 8,
+ "name": {
+ "namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1+2"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at unique_ext_tu2#"
+ }
+ },
+ {
+ "id": 4,
+ "name": {
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu1.cpp"
+ },
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1+2"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at unique_int_tu1#"
+ }
+ },
+ {
+ "id": 9,
+ "name": {
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu2.cpp"
+ },
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1+2"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at unique_int_tu2#"
+ }
+ },
+ {
+ "id": 5,
+ "name": {
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu1.cpp"
+ },
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1+2"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at unique_none_tu1#"
+ }
+ },
+ {
+ "id": 10,
+ "name": {
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu2.cpp"
+ },
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1+2"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at unique_none_tu2#"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "type": "External"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "type": "Internal"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "type": "None"
+ }
+ },
+ {
+ "id": 3,
+ "linkage": {
+ "type": "External"
+ }
+ },
+ {
+ "id": 4,
+ "linkage": {
+ "type": "Internal"
+ }
+ },
+ {
+ "id": 5,
+ "linkage": {
+ "type": "None"
+ }
+ },
+ {
+ "id": 6,
+ "linkage": {
+ "type": "Internal"
+ }
+ },
+ {
+ "id": 7,
+ "linkage": {
+ "type": "None"
+ }
+ },
+ {
+ "id": 8,
+ "linkage": {
+ "type": "External"
+ }
+ },
+ {
+ "id": 9,
+ "linkage": {
+ "type": "Internal"
+ }
+ },
+ {
+ "id": 10,
+ "linkage": {
+ "type": "None"
+ }
+ }
+ ],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1+2"
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1.json
new file mode 100644
index 0000000000000..3e5e17ce3f0c0
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1.json
@@ -0,0 +1,364 @@
+{
+ "data": [
+ {
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "call_count": 3,
+ "callees": [
+ {
+ "@": 1
+ },
+ {
+ "@": 2
+ },
+ {
+ "@": 3
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 1,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": [
+ {
+ "@": 0
+ },
+ {
+ "@": 4
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 2,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": [
+ {
+ "@": 5
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 3,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": [
+ {
+ "@": 0
+ },
+ {
+ "@": 1
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 4,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": [
+ {
+ "@": 3
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 5,
+ "entity_summary": {
+ "call_count": 3,
+ "callees": [
+ {
+ "@": 0
+ },
+ {
+ "@": 3
+ },
+ {
+ "@": 4
+ }
+ ]
+ }
+ }
+ ],
+ "summary_name": "CallGraph"
+ },
+ {
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "direct": {
+ "@": 3
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 1
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 4
+ },
+ "level": 2
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 1,
+ "entity_summary": {
+ "direct": {
+ "@": 0
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 2
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 5
+ },
+ "level": 2
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 2,
+ "entity_summary": {
+ "direct": {
+ "@": 1
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 3
+ },
+ "level": 1
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 3,
+ "entity_summary": {
+ "direct": {
+ "@": 4
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 0
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 2
+ },
+ "level": 2
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 4,
+ "entity_summary": {
+ "direct": {
+ "@": 5
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 1
+ },
+ "level": 1
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 5,
+ "entity_summary": {
+ "direct": {
+ "@": 2
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 0
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 3
+ },
+ "level": 2
+ },
+ {
+ "entity": {
+ "@": 4
+ },
+ "level": 3
+ }
+ ]
+ }
+ }
+ ],
+ "summary_name": "TypeInfo"
+ }
+ ],
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at shared_ext#"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu1.cpp"
+ },
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at shared_int#"
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu1.cpp"
+ },
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at shared_none#"
+ }
+ },
+ {
+ "id": 3,
+ "name": {
+ "namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at unique_ext_tu1#"
+ }
+ },
+ {
+ "id": 4,
+ "name": {
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu1.cpp"
+ },
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at unique_int_tu1#"
+ }
+ },
+ {
+ "id": 5,
+ "name": {
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu1.cpp"
+ },
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at unique_none_tu1#"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "type": "External"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "type": "Internal"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "type": "None"
+ }
+ },
+ {
+ "id": 3,
+ "linkage": {
+ "type": "External"
+ }
+ },
+ {
+ "id": 4,
+ "linkage": {
+ "type": "Internal"
+ }
+ },
+ {
+ "id": 5,
+ "linkage": {
+ "type": "None"
+ }
+ }
+ ],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "lu-1"
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-2.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-2.json
new file mode 100644
index 0000000000000..a60887a53cb83
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-2.json
@@ -0,0 +1,370 @@
+{
+ "data": [
+ {
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "call_count": 3,
+ "callees": [
+ {
+ "@": 1
+ },
+ {
+ "@": 2
+ },
+ {
+ "@": 5
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 1,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": [
+ {
+ "@": 0
+ },
+ {
+ "@": 3
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 2,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": [
+ {
+ "@": 4
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 3,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": [
+ {
+ "@": 0
+ },
+ {
+ "@": 2
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 4,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": [
+ {
+ "@": 3
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 5,
+ "entity_summary": {
+ "call_count": 3,
+ "callees": [
+ {
+ "@": 1
+ },
+ {
+ "@": 2
+ },
+ {
+ "@": 3
+ }
+ ]
+ }
+ }
+ ],
+ "summary_name": "CallGraph"
+ },
+ {
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "direct": {
+ "@": 3
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 2
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 5
+ },
+ "level": 2
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 1,
+ "entity_summary": {
+ "direct": {
+ "@": 0
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 4
+ },
+ "level": 1
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 2,
+ "entity_summary": {
+ "direct": {
+ "@": 5
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 0
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 3
+ },
+ "level": 2
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 3,
+ "entity_summary": {
+ "direct": {
+ "@": 1
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 2
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 0
+ },
+ "level": 2
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 4,
+ "entity_summary": {
+ "direct": {
+ "@": 2
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 5
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 3
+ },
+ "level": 2
+ }
+ ]
+ }
+ },
+ {
+ "entity_id": 5,
+ "entity_summary": {
+ "direct": {
+ "@": 4
+ },
+ "indirect": [
+ {
+ "entity": {
+ "@": 1
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 2
+ },
+ "level": 2
+ },
+ {
+ "entity": {
+ "@": 0
+ },
+ "level": 3
+ }
+ ]
+ }
+ }
+ ],
+ "summary_name": "TypeInfo"
+ }
+ ],
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "lu-2"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at shared_ext#"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu2.cpp"
+ },
+ {
+ "kind": "LinkUnit",
+ "name": "lu-2"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at shared_int#"
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu2.cpp"
+ },
+ {
+ "kind": "LinkUnit",
+ "name": "lu-2"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at shared_none#"
+ }
+ },
+ {
+ "id": 3,
+ "name": {
+ "namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "lu-2"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at unique_ext_tu2#"
+ }
+ },
+ {
+ "id": 4,
+ "name": {
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu2.cpp"
+ },
+ {
+ "kind": "LinkUnit",
+ "name": "lu-2"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at unique_int_tu2#"
+ }
+ },
+ {
+ "id": 5,
+ "name": {
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu2.cpp"
+ },
+ {
+ "kind": "LinkUnit",
+ "name": "lu-2"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at unique_none_tu2#"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "type": "External"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "type": "Internal"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "type": "None"
+ }
+ },
+ {
+ "id": 3,
+ "linkage": {
+ "type": "External"
+ }
+ },
+ {
+ "id": 4,
+ "linkage": {
+ "type": "Internal"
+ }
+ },
+ {
+ "id": 5,
+ "linkage": {
+ "type": "None"
+ }
+ }
+ ],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "lu-2"
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-empty.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-empty.json
new file mode 100644
index 0000000000000..e63b1f0a1337d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-empty.json
@@ -0,0 +1,11 @@
+{
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "lu-empty"
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/cli.test b/clang/test/Analysis/Scalable/ssaf-linker/cli.test
new file mode 100644
index 0000000000000..87302e78187b8
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/cli.test
@@ -0,0 +1,13 @@
+// Tests for ssaf-linker command-line option validation.
+
+// RUN: not ssaf-linker 2>&1 | FileCheck %s --check-prefix=NO-ARGS
+// NO-ARGS: ssaf-linker: Not enough positional command line arguments specified!
+// NO-ARGS: Must specify at least 1 positional argument: See: ssaf-linker --help
+
+// RUN: not ssaf-linker %S/Inputs/tu-empty.json 2>&1 | FileCheck %s --check-prefix=NO-OUTPUT
+// NO-OUTPUT: ssaf-linker: for the --output option: must be specified at least once!
+
+// RUN: not ssaf-linker -o %t/output.json 2>&1 | FileCheck %s --check-prefix=NO-INPUT
+// NO-INPUT: ssaf-linker: Not enough positional command line arguments specified!
+// NO-INPUT: Must specify at least 1 positional argument: See: ssaf-linker --help
+
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/help.test b/clang/test/Analysis/Scalable/ssaf-linker/help.test
new file mode 100644
index 0000000000000..bc90d79435bc7
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/help.test
@@ -0,0 +1,23 @@
+// Test ssaf-linker help option
+
+// RUN: ssaf-linker --help-list-hidden | FileCheck %s
+
+// CHECK: OVERVIEW: SSAF Linker
+// CHECK-EMPTY:
+// CHECK: USAGE: ssaf-linker [options] input...
+// CHECK-EMPTY:
+// CHECK: OPTIONS:
+// CHECK: -h - Alias for --help
+// CHECK: --help - Display available options (--help-hidden for more)
+// CHECK: --help-hidden - Display all available options
+// CHECK: --help-list - Display list of available options (--help-list-hidden for more)
+// CHECK: --help-list-hidden - Display list of all available options
+// CHECK: -o -
+// CHECK: --output=<path> - Output summary path
+// CHECK: --print-all-options - Print all option values after command line parsing
+// CHECK: --print-options - Print non-default options after command line parsing
+// CHECK: -t -
+// CHECK: --time - Enable timing
+// CHECK: -v -
+// CHECK: --verbose - Enable verbose output
+// CHECK: --version - Display the version of this program
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/io.test b/clang/test/Analysis/Scalable/ssaf-linker/io.test
new file mode 100644
index 0000000000000..386e64602c251
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/io.test
@@ -0,0 +1,26 @@
+// Tests for ssaf-linker file I/O error handling.
+
+// Input file is a directory.
+// RUN: not ssaf-linker %S/Inputs -o %t.json 2>&1 | FileCheck %s --check-prefix=INPUT-IS-DIR
+// INPUT-IS-DIR: error: Failed to load input summary
+// INPUT-IS-DIR: path is a directory, not a file
+
+// Input file has wrong extension — caught at validation before read.
+// (Extension check happens in SummaryFile::FromPath before real_path.)
+// RUN: not ssaf-linker %S/Inputs/empty.json %S/Inputs/malformed.json -o %t.json 2>&1
+
+// Malformed JSON input.
+// RUN: not ssaf-linker %S/Inputs/malformed.json -o %t.json 2>&1 | FileCheck %s --check-prefix=BAD-JSON
+// BAD-JSON: error: Failed to load input summary
+// BAD-JSON: failed to read file
+
+// Missing required fields in otherwise valid JSON.
+// RUN: not ssaf-linker %S/Inputs/missing-fields.json -o %t.json 2>&1 | FileCheck %s --check-prefix=MISSING-FIELDS
+// MISSING-FIELDS: error: Failed to load input summary
+// MISSING-FIELDS: failed to read
+
+// Output file already exists.
+// RUN: ssaf-linker %S/Inputs/empty.json -o %t-exists.json
+// RUN: not ssaf-linker %S/Inputs/empty.json -o %t-exists.json 2>&1 | FileCheck %s --check-prefix=OUTPUT-EXISTS
+// OUTPUT-EXISTS: error: Failed to write output summary
+// OUTPUT-EXISTS: file already exists
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test b/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test
new file mode 100644
index 0000000000000..19b08936cef3d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test
@@ -0,0 +1,9 @@
+// Tests for EntityLinker error conditions.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// Linking the same TU namespace twice produces an error.
+// RUN: not ssaf-linker %S/Inputs/tu-empty.json %S/Inputs/tu-empty.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=DUP-NS -DPATH=%S/Inputs/tu-empty.json
+// DUP-NS: ssaf-linker: error: Failed to link input summary '[[PATH]]': failed to link TU summary: duplicate BuildNamespace(CompilationUnit, empty.cpp).
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/linking.test b/clang/test/Analysis/Scalable/ssaf-linker/linking.test
new file mode 100644
index 0000000000000..63e501811334d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/linking.test
@@ -0,0 +1,30 @@
+// Tests for successful ssaf-linker linking behaviour.
+// See linking-errors.test for error cases.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// Single empty TU produces valid output.
+// RUN: ssaf-linker %S/Inputs/tu-empty.json -o %t/lu-empty.json
+// RUN: diff %S/Outputs/lu-empty.json %t/lu-empty.json
+// RUN: rm %t/lu-empty.json
+
+// Single non-empty TU produces valid output.
+// RUN: ssaf-linker %S/Inputs/tu-1.json -o %t/lu-1.json
+// RUN: diff %S/Outputs/lu-1.json %t/lu-1.json
+// RUN: rm %t/lu-1.json
+
+// Linking empty and non-empty TU is equivalent to just linking non-empty TU
+// RUN: ssaf-linker %S/Inputs/tu-empty.json %S/Inputs/tu-1.json -o %t/lu-1.json
+// RUN: diff %S/Outputs/lu-1.json %t/lu-1.json
+// RUN: rm %t/lu-1.json
+
+// Linking non-empty and empty TU is equivalent to just linking non-empty TU
+// RUN: ssaf-linker %S/Inputs/tu-2.json %S/Inputs/tu-empty.json -o %t/lu-2.json
+// RUN: diff %S/Outputs/lu-2.json %t/lu-2.json
+// RUN: rm %t/lu-2.json
+
+// Linking two TUs correctly resolves and patches data.
+// RUN: ssaf-linker %S/Inputs/tu-1.json %S/Inputs/tu-2.json -o %t/lu-1+2.json
+// RUN: diff %S/Outputs/lu-1+2.json %t/lu-1+2.json
+// RUN: rm %t/lu-1+2.json
\ No newline at end of file
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/time.test b/clang/test/Analysis/Scalable/ssaf-linker/time.test
new file mode 100644
index 0000000000000..0f7033876313f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/time.test
@@ -0,0 +1,32 @@
+// Test the --time/-t flag of ssaf-linker.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// RUN: ssaf-linker --time %S/Inputs/tu-1.json %S/Inputs/tu-2.json -o %t/lu-1+2.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=CHECK-LONG
+// CHECK-LONG: ===-------------------------------------------------------------------------===
+// CHECK-LONG: SSAF Linker
+// CHECK-LONG: ===-------------------------------------------------------------------------===
+// CHECK-LONG: Total Execution Time: {{[0-9.]+}} seconds ({{[0-9.]+}} wall clock)
+// CHECK-LONG: ---User Time--- --System Time-- --User+System-- ---Wall Time---
+// CHECK-LONG-DAG: {{.*}}Write Summary
+// CHECK-LONG-DAG: {{.*}}Read Summaries
+// CHECK-LONG-DAG: {{.*}}Link Summaries
+// CHECK-LONG-DAG: {{.*}}Validate Input
+// CHECK-LONG: {{.*}}Total
+
+// RUN: rm %t/lu-1+2.json
+
+// RUN: ssaf-linker -t %S/Inputs/tu-1.json %S/Inputs/tu-2.json -o %t/lu-1+2.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=CHECK-SHORT
+// CHECK-SHORT: ===-------------------------------------------------------------------------===
+// CHECK-SHORT: SSAF Linker
+// CHECK-SHORT: ===-------------------------------------------------------------------------===
+// CHECK-SHORT: Total Execution Time: {{[0-9.]+}} seconds ({{[0-9.]+}} wall clock)
+// CHECK-SHORT: ---User Time--- --System Time-- --User+System-- ---Wall Time---
+// CHECK-SHORT-DAG: {{.*}}Write Summary
+// CHECK-SHORT-DAG: {{.*}}Read Summaries
+// CHECK-SHORT-DAG: {{.*}}Link Summaries
+// CHECK-SHORT-DAG: {{.*}}Validate Input
+// CHECK-SHORT: {{.*}}Total
\ No newline at end of file
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test b/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
new file mode 100644
index 0000000000000..1fc99a18e8041
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
@@ -0,0 +1,49 @@
+// Tests for EntityLinker error cases.
+
+// RUN: mkdir -p %t
+
+// No extension on output.
+// RUN: not ssaf-linker %S/Inputs/tu-empty.json -o lu 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-EXT-OUTPUT
+// NO-EXT-OUTPUT: ssaf-linker: error: Failed to validate summary 'lu': Extension not supplied.
+
+// No extension on input.
+// RUN: not ssaf-linker %S/Inputs/tu-noext -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-EXT-INPUT -DPATH=%S/Inputs/tu-noext
+// NO-EXT-INPUT: ssaf-linker: error: Failed to validate summary '[[PATH]]': Extension not supplied.
+
+// Invalid extension on output.
+// RUN: not ssaf-linker %S/Inputs/tu-empty.json -o lu.txt 2>&1 \
+// RUN: | FileCheck %s --check-prefix=BAD-EXT-OUTPUT
+// BAD-EXT-OUTPUT: ssaf-linker: error: Failed to validate summary 'lu.txt': Format not registered for extension 'txt'.
+
+// Invalid extension on input.
+// RUN: not ssaf-linker %S/Inputs/tu-badext.txt -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=BAD-EXT-INPUT -DPATH=%S/Inputs/tu-badext.txt
+// BAD-EXT-INPUT: ssaf-linker: error: Failed to validate summary '[[PATH]]': Format not registered for extension 'txt'.
+
+// Output directory does not exist.
+// RUN: not ssaf-linker %S/Inputs/tu-empty.json -o %S/Outputs/NonExistentDirectory/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=OUTPUT-PARENT-DIR-MISSING -DPATH=%S/Outputs/NonExistentDirectory/lu.json
+// OUTPUT-PARENT-DIR-MISSING: ssaf-linker: error: Failed to validate summary '[[PATH]]': Parent directory does not exist.
+
+// Output parent directory exists but is not writable.
+// UNSUPPORTED: system-windows
+// RUN: mkdir -p %t/output-dir
+// RUN: chmod -w %t/output-dir
+// RUN: not ssaf-linker %S/Inputs/tu-empty.json -o %t/output-dir/output.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-WRITE-PERM -DPATH=%t/output-dir/output.json
+// RUN: chmod +w %t/output-dir
+// NO-WRITE-PERM: ssaf-linker: error: Failed to validate summary '[[PATH]]': Parent directory is not writable.
+
+// Input summary does not exist.
+// RUN: not ssaf-linker %S/Inputs/tu-nonexistent.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-INPUT-FILE -DPATH=%S/Inputs/tu-nonexistent.json
+// NO-INPUT-FILE: ssaf-linker: error: Failed to validate summary '[[PATH]]': No such file or directory.
+
+// Input summary is a broken symlink.
+// RUN: ln -sf %t/tu-nonexistent %t/tu-dangling.json
+// RUN: not ssaf-linker %t/tu-dangling.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=BROKEN-SYMLINK -DPATH=%t/tu-dangling.json
+// BROKEN-SYMLINK: ssaf-linker: error: Failed to validate summary '[[PATH]]': No such file or directory.
+
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/verbose.test b/clang/test/Analysis/Scalable/ssaf-linker/verbose.test
new file mode 100644
index 0000000000000..81c8c922e5324
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/verbose.test
@@ -0,0 +1,38 @@
+// Test the --verbose/-v flag of ssaf-linker.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// RUN: ssaf-linker --verbose %S/Inputs/tu-1.json %S/Inputs/tu-2.json -o %t/lu-1+2.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=CHECK-LONG -DINPUT=%S/Inputs -DOUTPUT=%t
+// CHECK-LONG: note: - Linking started.
+// CHECK-LONG: note: - Validating input.
+// CHECK-LONG: note: - Validated output summary path '[[OUTPUT]]/lu-1+2.json'.
+// CHECK-LONG: note: - Validated 2 input summary paths.
+// CHECK-LONG: note: - Linking input.
+// CHECK-LONG: note: - Constructing linker.
+// CHECK-LONG: note: - Linking summaries.
+// CHECK-LONG: note: - [1/2] Reading '[[INPUT]]/tu-1.json'.
+// CHECK-LONG: note: - [1/2] Linking '[[INPUT]]/tu-1.json'.
+// CHECK-LONG: note: - [2/2] Reading '[[INPUT]]/tu-2.json'.
+// CHECK-LONG: note: - [2/2] Linking '[[INPUT]]/tu-2.json'.
+// CHECK-LONG: note: - Writing output summary to '[[OUTPUT]]/lu-1+2.json'.
+// CHECK-LONG: note: - Linking finished.
+
+// RUN: rm %t/lu-1+2.json
+
+// RUN: ssaf-linker -v %S/Inputs/tu-1.json %S/Inputs/tu-2.json -o %t/lu-1+2.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=CHECK-SHORT -DINPUT=%S/Inputs -DOUTPUT=%t
+// CHECK-SHORT: note: - Linking started.
+// CHECK-SHORT: note: - Validating input.
+// CHECK-SHORT: note: - Validated output summary path '[[OUTPUT]]/lu-1+2.json'.
+// CHECK-SHORT: note: - Validated 2 input summary paths.
+// CHECK-SHORT: note: - Linking input.
+// CHECK-SHORT: note: - Constructing linker.
+// CHECK-SHORT: note: - Linking summaries.
+// CHECK-SHORT: note: - [1/2] Reading '[[INPUT]]/tu-1.json'.
+// CHECK-SHORT: note: - [1/2] Linking '[[INPUT]]/tu-1.json'.
+// CHECK-SHORT: note: - [2/2] Reading '[[INPUT]]/tu-2.json'.
+// CHECK-SHORT: note: - [2/2] Linking '[[INPUT]]/tu-2.json'.
+// CHECK-SHORT: note: - Writing output summary to '[[OUTPUT]]/lu-1+2.json'.
+// CHECK-SHORT: note: - Linking finished.
\ No newline at end of file
diff --git a/clang/tools/CMakeLists.txt b/clang/tools/CMakeLists.txt
index afdd613b4ee99..aa4c4c226db57 100644
--- a/clang/tools/CMakeLists.txt
+++ b/clang/tools/CMakeLists.txt
@@ -53,3 +53,4 @@ add_llvm_external_project(clang-tools-extra extra)
add_clang_subdirectory(libclang)
add_clang_subdirectory(offload-arch)
+add_clang_subdirectory(ssaf-linker)
diff --git a/clang/tools/ssaf-linker/CMakeLists.txt b/clang/tools/ssaf-linker/CMakeLists.txt
new file mode 100644
index 0000000000000..db5750fb1f758
--- /dev/null
+++ b/clang/tools/ssaf-linker/CMakeLists.txt
@@ -0,0 +1,14 @@
+set(LLVM_LINK_COMPONENTS
+ Option
+ Support
+ )
+
+add_clang_tool(ssaf-linker
+ SSAFLinker.cpp
+ )
+
+clang_target_link_libraries(ssaf-linker
+ PRIVATE
+ clangAnalysisScalable
+ clangBasic
+ )
diff --git a/clang/tools/ssaf-linker/SSAFLinker.cpp b/clang/tools/ssaf-linker/SSAFLinker.cpp
new file mode 100644
index 0000000000000..ad9ae56646260
--- /dev/null
+++ b/clang/tools/ssaf-linker/SSAFLinker.cpp
@@ -0,0 +1,335 @@
+//===--- tools/ssaf-linker/SSAFLinker.cpp - SSAF Linker -------------------===//
+//
+// 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 implements the SSAF entity linker tool that performs entity
+// linking across multiple TU summaries using the EntityLinker framework.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/EntityLinker/EntityLinker.h"
+#include "clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h"
+#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Signals.h"
+#include "llvm/Support/Timer.h"
+#include "llvm/Support/WithColor.h"
+#include "llvm/Support/raw_ostream.h"
+#include <map>
+#include <memory>
+#include <string>
+
+using namespace llvm;
+using namespace clang::ssaf;
+
+namespace fs = llvm::sys::fs;
+namespace path = llvm::sys::path;
+
+namespace {
+
+//===----------------------------------------------------------------------===//
+// Command-Line Options
+//===----------------------------------------------------------------------===//
+
+cl::OptionCategory SsafLinkerCategory("ssaf-linker options");
+
+cl::list<std::string> InputPaths(cl::Positional, cl::desc("input..."),
+ cl::value_desc("path"), cl::OneOrMore,
+ cl::cat(SsafLinkerCategory));
+
+cl::opt<std::string> OutputPath("output", cl::desc("Output summary path"),
+ cl::value_desc("path"), cl::Required,
+ cl::cat(SsafLinkerCategory));
+
+cl::alias OutputFileShort("o", cl::aliasopt(OutputPath));
+
+cl::opt<bool> Verbose("verbose", cl::desc("Enable verbose output"),
+ cl::init(false), cl::cat(SsafLinkerCategory));
+
+cl::alias VerboseShort("v", cl::aliasopt(Verbose));
+
+cl::opt<bool> Time("time", cl::desc("Enable timing"), cl::init(false),
+ cl::cat(SsafLinkerCategory));
+
+cl::alias TimeShort("t", cl::aliasopt(Time));
+
+//===----------------------------------------------------------------------===//
+// Error Messages
+//===----------------------------------------------------------------------===//
+
+constexpr const char *ErrorCannotValidateSummary =
+ "Failed to validate summary '{0}': {1}";
+
+constexpr const char *ErrorOutputDirectoryMissing =
+ "Parent directory does not exist.";
+
+constexpr const char *ErrorOutputDirectoryNotWritable =
+ "Parent directory is not writable.";
+
+constexpr const char *ErrorExtensionNotSupplied = "Extension not supplied.";
+
+constexpr const char *ErrorNoFormatForExtension =
+ "Format not registered for extension '{0}'.";
+
+constexpr const char *ErrorCannotResolvePath =
+ "Failed to validate summary '{0}': {1}.";
+
+constexpr const char *ErrorLoadFailed =
+ "Failed to load input summary '{0}': {1}.";
+
+constexpr const char *ErrorLinkFailed =
+ "Failed to link input summary '{0}': {1}.";
+
+constexpr const char *ErrorWriteFailed =
+ "Failed to write output summary '{0}': {1}.";
+
+//===----------------------------------------------------------------------===//
+// Diagnostic Utilities
+//===----------------------------------------------------------------------===//
+
+constexpr unsigned IndentationWidth = 2;
+
+static llvm::StringRef ToolName;
+
+template <typename... Ts>
+[[noreturn]] void Fail(const char *Fmt, Ts &&...Args) {
+ llvm::WithColor::error(llvm::errs(), ToolName)
+ << llvm::formatv(Fmt, std::forward<Ts>(Args)...) << "\n";
+ std::exit(1);
+}
+
+template <typename... Ts>
+void Info(unsigned IndentationLevel, const char *Fmt, Ts &&...Args) {
+ if (Verbose) {
+ llvm::WithColor::note()
+ << std::string(IndentationLevel * IndentationWidth, ' ') << "- "
+ << llvm::formatv(Fmt, std::forward<Ts>(Args)...) << "\n";
+ }
+}
+
+struct ScopedTimer {
+ explicit ScopedTimer(llvm::Timer &T) : T(T) {
+ if (Time) {
+ T.startTimer();
+ }
+ }
+
+ ~ScopedTimer() {
+ if (Time) {
+ T.stopTimer();
+ }
+ }
+ llvm::Timer &T;
+};
+
+//===----------------------------------------------------------------------===//
+// Format Registry
+//===----------------------------------------------------------------------===//
+
+// TODO - will be replaced by an equivalent method from the framework
+std::unique_ptr<SerializationFormat>
+MakeFormatForExtension(llvm::StringRef Extension) {
+ if (Extension == "json") {
+ return std::make_unique<JSONFormat>();
+ }
+ return nullptr;
+}
+
+SerializationFormat *GetFormatForExtension(llvm::StringRef Extension) {
+ static std::map<std::string, std::unique_ptr<SerializationFormat>>
+ ExtensionFormatMap;
+
+ auto It = ExtensionFormatMap.find(Extension.str());
+ if (It != ExtensionFormatMap.end()) {
+ return It->second.get();
+ }
+
+ auto Format = MakeFormatForExtension(Extension);
+ SerializationFormat *Result = Format.get();
+
+ if (Result) {
+ ExtensionFormatMap.emplace(Extension, std::move(Format));
+ }
+
+ return Result;
+}
+
+//===----------------------------------------------------------------------===//
+// Data Structures
+//===----------------------------------------------------------------------===//
+
+struct SummaryFile {
+ std::string Path;
+ SerializationFormat *Format = nullptr;
+
+ static SummaryFile FromPath(llvm::StringRef Path) {
+ llvm::StringRef Extension = path::extension(Path);
+ if (Extension.empty()) {
+ Fail(ErrorCannotValidateSummary, Path, ErrorExtensionNotSupplied);
+ }
+ Extension = Extension.drop_front();
+ SerializationFormat *Format = GetFormatForExtension(Extension);
+ if (!Format) {
+ std::string Suffix = llvm::formatv(ErrorNoFormatForExtension, Extension);
+ Fail(ErrorCannotValidateSummary, Path, Suffix);
+ }
+ return {Path.str(), Format};
+ }
+};
+
+struct LinkerInput {
+ std::vector<SummaryFile> InputFiles;
+ SummaryFile OutputFile;
+ std::string LinkUnitName;
+};
+
+//===----------------------------------------------------------------------===//
+// Pipeline
+//===----------------------------------------------------------------------===//
+
+LinkerInput Validate(llvm::TimerGroup &TG) {
+ llvm::Timer TValidate("validate", "Validate Input", TG);
+ LinkerInput LI;
+
+ {
+ ScopedTimer _(TValidate);
+ llvm::StringRef ParentDir = path::parent_path(OutputPath);
+ llvm::StringRef DirToCheck = ParentDir.empty() ? "." : ParentDir;
+
+ if (!fs::exists(DirToCheck)) {
+ Fail(ErrorCannotValidateSummary, OutputPath, ErrorOutputDirectoryMissing);
+ }
+
+ if (fs::access(DirToCheck, fs::AccessMode::Write)) {
+ Fail(ErrorCannotValidateSummary, OutputPath,
+ ErrorOutputDirectoryNotWritable);
+ }
+
+ LI.OutputFile = SummaryFile::FromPath(OutputPath);
+ LI.LinkUnitName = path::stem(LI.OutputFile.Path).str();
+ }
+
+ Info(2, "Validated output summary path '{0}'.", LI.OutputFile.Path);
+
+ {
+ ScopedTimer _(TValidate);
+ for (const auto &InputPath : InputPaths) {
+ llvm::SmallString<256> RealPath;
+ std::error_code EC = fs::real_path(InputPath, RealPath, true);
+ if (EC) {
+ Fail(ErrorCannotResolvePath, InputPath, EC.message());
+ }
+ LI.InputFiles.push_back(SummaryFile::FromPath(RealPath));
+ }
+ }
+
+ Info(2, "Validated {0} input summary paths.", LI.InputFiles.size());
+
+ return LI;
+}
+
+void Link(const LinkerInput &LI, llvm::TimerGroup &TG) {
+ Info(2, "Constructing linker.");
+
+ EntityLinker EL(NestedBuildNamespace(
+ BuildNamespace(BuildNamespaceKind::LinkUnit, LI.LinkUnitName)));
+
+ llvm::Timer TRead("read", "Read Summaries", TG);
+ llvm::Timer TLink("link", "Link Summaries", TG);
+ llvm::Timer TWrite("write", "Write Summary", TG);
+
+ Info(2, "Linking summaries.");
+
+ for (auto [Index, InputFile] : llvm::enumerate(LI.InputFiles)) {
+ std::unique_ptr<TUSummaryEncoding> Summary;
+
+ {
+ Info(3, "[{0}/{1}] Reading '{2}'.", (Index + 1), LI.InputFiles.size(),
+ InputFile.Path);
+
+ ScopedTimer _(TRead);
+
+ auto ExpectedSummaryEncoding =
+ InputFile.Format->readTUSummaryEncoding(InputFile.Path);
+ if (!ExpectedSummaryEncoding) {
+ Fail(ErrorLoadFailed, InputFile.Path,
+ toString(ExpectedSummaryEncoding.takeError()));
+ }
+
+ Summary = std::make_unique<TUSummaryEncoding>(
+ std::move(*ExpectedSummaryEncoding));
+ }
+
+ {
+ Info(3, "[{0}/{1}] Linking '{2}'.", (Index + 1), LI.InputFiles.size(),
+ InputFile.Path);
+
+ ScopedTimer _(TLink);
+
+ if (auto Err = EL.link(std::move(Summary))) {
+ Fail(ErrorLinkFailed, InputFile.Path, toString(std::move(Err)));
+ }
+ }
+ }
+
+ {
+ Info(2, "Writing output summary to '{0}'.", LI.OutputFile.Path);
+
+ ScopedTimer _(TWrite);
+
+ auto Output = std::move(EL).getOutput();
+ if (auto Err = LI.OutputFile.Format->writeLUSummaryEncoding(
+ Output, LI.OutputFile.Path)) {
+ Fail(ErrorWriteFailed, LI.OutputFile.Path, toString(std::move(Err)));
+ }
+ }
+}
+
+} // namespace
+
+//===----------------------------------------------------------------------===//
+// Driver
+//===----------------------------------------------------------------------===//
+
+int main(int argc, const char **argv) {
+ ToolName = argv[0];
+ initializeJSONFormat();
+ sys::PrintStackTraceOnErrorSignal(argv[0]);
+
+ cl::HideUnrelatedOptions(SsafLinkerCategory);
+ cl::ParseCommandLineOptions(argc, argv, "SSAF Linker\n");
+
+ llvm::TimerGroup LinkerTimers("ssaf-linker", "SSAF Linker");
+ LinkerInput LI;
+
+ {
+ Info(0, "Linking started.");
+
+ {
+ Info(1, "Validating input.");
+ LI = Validate(LinkerTimers);
+ }
+
+ {
+ Info(1, "Linking input.");
+ Link(LI, LinkerTimers);
+ }
+
+ Info(0, "Linking finished.");
+ }
+
+ if (Time) {
+ LinkerTimers.print(llvm::errs());
+ }
+
+ return 0;
+}
diff --git a/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp b/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp
index b6d28f701fb6f..93e276f0c0237 100644
--- a/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp
+++ b/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp
@@ -36,9 +36,10 @@ class MockEntitySummaryEncoding : public EntitySummaryEncoding {
size_t getId() const { return Id; }
- void
+ llvm::Error
patch(const std::map<EntityId, EntityId> &EntityResolutionTable) override {
PatchedIds = EntityResolutionTable;
+ return llvm::Error::success();
}
const std::map<EntityId, EntityId> &getPatchedIds() const {
diff --git a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
index 8b6d1a5ae15cf..b8f1ea0a866e4 100644
--- a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
@@ -54,7 +54,7 @@ TEST(SerializationFormatRegistryTest, isFormatRegistered) {
TEST(SerializationFormatRegistryTest, EnumeratingRegistryEntries) {
auto Formats = SerializationFormatRegistry::entries();
- ASSERT_EQ(std::distance(Formats.begin(), Formats.end()), 1U);
+ ASSERT_EQ(std::distance(Formats.begin(), Formats.end()), 2U);
EXPECT_EQ(Formats.begin()->getName(), "MockSerializationFormat");
}
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.cpp
index 92966f22c5971..66d112580d748 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/JSONFormatTest.cpp
@@ -207,7 +207,7 @@ llvm::Error normalizeIDTable(json::Array &IDTable,
return createStringError(
inconvertibleErrorCode(),
"Cannot normalize %s JSON: id_table entry at index %zu "
- "does not contain a valid 'id' uint64_t field",
+ "'id' field is not a valid entity id integer",
SummaryClassName.data(), Index);
}
}
@@ -246,7 +246,7 @@ llvm::Error normalizeLinkageTable(json::Array &LinkageTable,
return createStringError(
inconvertibleErrorCode(),
"Cannot normalize %s JSON: linkage_table entry at index "
- "%zu does not contain a valid 'id' uint64_t field",
+ "%zu 'id' field is not a valid entity id integer",
SummaryClassName.data(), Index);
}
}
@@ -287,8 +287,8 @@ llvm::Error normalizeSummaryData(json::Array &SummaryData, size_t DataIndex,
return createStringError(
inconvertibleErrorCode(),
"Cannot normalize %s JSON: data entry at index %zu, "
- "summary_data entry at index %zu does not contain a valid "
- "'entity_id' uint64_t field",
+ "summary_data entry at index %zu 'entity_id' field is not "
+ "a valid entity id integer",
SummaryClassName.data(), DataIndex, SummaryIndex);
}
}
@@ -458,15 +458,14 @@ namespace {
// ============================================================================
json::Object serializePairsEntitySummaryForJSONFormatTest(
- const EntitySummary &Summary,
- const JSONFormat::EntityIdConverter &Converter) {
+ const EntitySummary &Summary, JSONFormat::EntityIdToJSONFn ToJSON) {
const auto &TA =
static_cast<const PairsEntitySummaryForJSONFormatTest &>(Summary);
json::Array PairsArray;
for (const auto &[First, Second] : TA.Pairs) {
PairsArray.push_back(json::Object{
- {"first", Converter.toJSON(First)},
- {"second", Converter.toJSON(Second)},
+ {"first", ToJSON(First)},
+ {"second", ToJSON(Second)},
});
}
return json::Object{{"pairs", std::move(PairsArray)}};
@@ -475,7 +474,7 @@ json::Object serializePairsEntitySummaryForJSONFormatTest(
Expected<std::unique_ptr<EntitySummary>>
deserializePairsEntitySummaryForJSONFormatTest(
const json::Object &Obj, EntityIdTable &IdTable,
- const JSONFormat::EntityIdConverter &Converter) {
+ JSONFormat::EntityIdFromJSONFn FromJSON) {
auto Result = std::make_unique<PairsEntitySummaryForJSONFormatTest>();
const json::Array *PairsArray = Obj.getArray("pairs");
if (!PairsArray) {
@@ -489,20 +488,33 @@ deserializePairsEntitySummaryForJSONFormatTest(
inconvertibleErrorCode(),
"pairs element at index %zu is not a JSON object", Index);
}
- auto FirstOpt = Pair->getInteger("first");
- if (!FirstOpt) {
+ const json::Object *FirstObj = Pair->getObject("first");
+ if (!FirstObj) {
return createStringError(
inconvertibleErrorCode(),
"missing or invalid 'first' field at index '%zu'", Index);
}
- auto SecondOpt = Pair->getInteger("second");
- if (!SecondOpt) {
+ const json::Object *SecondObj = Pair->getObject("second");
+ if (!SecondObj) {
return createStringError(
inconvertibleErrorCode(),
"missing or invalid 'second' field at index '%zu'", Index);
}
- Result->Pairs.emplace_back(Converter.fromJSON(*FirstOpt),
- Converter.fromJSON(*SecondOpt));
+ auto ExpectedFirst = FromJSON(*FirstObj);
+ if (!ExpectedFirst) {
+ return createStringError(inconvertibleErrorCode(),
+ "invalid 'first' entity id at index '%zu': %s",
+ Index,
+ toString(ExpectedFirst.takeError()).c_str());
+ }
+ auto ExpectedSecond = FromJSON(*SecondObj);
+ if (!ExpectedSecond) {
+ return createStringError(inconvertibleErrorCode(),
+ "invalid 'second' entity id at index '%zu': %s",
+ Index,
+ toString(ExpectedSecond.takeError()).c_str());
+ }
+ Result->Pairs.emplace_back(*ExpectedFirst, *ExpectedSecond);
}
return std::move(Result);
}
@@ -526,8 +538,9 @@ llvm::Registry<JSONFormat::FormatInfo>::Add<
// Second Test Analysis - Simple analysis for multi-summary round-trip tests.
// ============================================================================
-json::Object serializeTagsEntitySummaryForJSONFormatTest(
- const EntitySummary &Summary, const JSONFormat::EntityIdConverter &) {
+json::Object
+serializeTagsEntitySummaryForJSONFormatTest(const EntitySummary &Summary,
+ JSONFormat::EntityIdToJSONFn) {
const auto &TA =
static_cast<const TagsEntitySummaryForJSONFormatTest &>(Summary);
json::Array TagsArray;
@@ -538,9 +551,9 @@ json::Object serializeTagsEntitySummaryForJSONFormatTest(
}
Expected<std::unique_ptr<EntitySummary>>
-deserializeTagsEntitySummaryForJSONFormatTest(
- const json::Object &Obj, EntityIdTable &,
- const JSONFormat::EntityIdConverter &) {
+deserializeTagsEntitySummaryForJSONFormatTest(const json::Object &Obj,
+ EntityIdTable &,
+ JSONFormat::EntityIdFromJSONFn) {
auto Result = std::make_unique<TagsEntitySummaryForJSONFormatTest>();
const json::Array *TagsArray = Obj.getArray("tags");
if (!TagsArray) {
@@ -583,10 +596,10 @@ struct NullEntitySummaryForJSONFormatTestFormatInfo final
NullEntitySummaryForJSONFormatTestFormatInfo()
: JSONFormat::FormatInfo(
SummaryName("NullEntitySummaryForJSONFormatTest"),
- [](const EntitySummary &, const JSONFormat::EntityIdConverter &)
+ [](const EntitySummary &, JSONFormat::EntityIdToJSONFn)
-> json::Object { return json::Object{}; },
[](const json::Object &, EntityIdTable &,
- const JSONFormat::EntityIdConverter &)
+ JSONFormat::EntityIdFromJSONFn)
-> llvm::Expected<std::unique_ptr<EntitySummary>> {
return nullptr;
}) {}
@@ -607,10 +620,10 @@ struct MismatchedEntitySummaryForJSONFormatTestFormatInfo final
MismatchedEntitySummaryForJSONFormatTestFormatInfo()
: JSONFormat::FormatInfo(
SummaryName("MismatchedEntitySummaryForJSONFormatTest"),
- [](const EntitySummary &, const JSONFormat::EntityIdConverter &)
+ [](const EntitySummary &, JSONFormat::EntityIdToJSONFn)
-> json::Object { return json::Object{}; },
[](const json::Object &, EntityIdTable &,
- const JSONFormat::EntityIdConverter &)
+ JSONFormat::EntityIdFromJSONFn)
-> llvm::Expected<std::unique_ptr<EntitySummary>> {
return std::make_unique<
MismatchedEntitySummaryForJSONFormatTest>();
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp
index 5c6f32192b2c5..062bdcde86336 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/LUSummaryTest.cpp
@@ -1340,7 +1340,7 @@ TEST_F(JSONFormatLUSummaryTest,
"entity_summary": {
"pairs": [
{
- "second": 1
+ "second": {"@": 1}
}
]
}
@@ -1383,7 +1383,7 @@ TEST_F(JSONFormatLUSummaryTest,
"pairs": [
{
"first": "not_a_number",
- "second": 1
+ "second": {"@": 1}
}
]
}
@@ -1425,7 +1425,7 @@ TEST_F(JSONFormatLUSummaryTest,
"entity_summary": {
"pairs": [
{
- "first": 0
+ "first": {"@": 0}
}
]
}
@@ -1467,7 +1467,7 @@ TEST_F(JSONFormatLUSummaryTest,
"entity_summary": {
"pairs": [
{
- "first": 0,
+ "first": {"@": 0},
"second": "not_a_number"
}
]
@@ -2309,7 +2309,7 @@ TEST_P(LUSummaryTest, RoundTripWithTwoSummaryTypes) {
"entity_id": 1,
"entity_summary": {
"pairs": [
- { "first": 1, "second": 3 }
+ { "first": {"@": 1}, "second": {"@": 3} }
]
}
},
@@ -2317,8 +2317,8 @@ TEST_P(LUSummaryTest, RoundTripWithTwoSummaryTypes) {
"entity_id": 4,
"entity_summary": {
"pairs": [
- { "first": 4, "second": 0 },
- { "first": 4, "second": 2 }
+ { "first": {"@": 4}, "second": {"@": 0} },
+ { "first": {"@": 4}, "second": {"@": 2} }
]
}
},
@@ -2332,7 +2332,7 @@ TEST_P(LUSummaryTest, RoundTripWithTwoSummaryTypes) {
"entity_id": 3,
"entity_summary": {
"pairs": [
- { "first": 3, "second": 1 }
+ { "first": {"@": 3}, "second": {"@": 1} }
]
}
},
@@ -2340,8 +2340,8 @@ TEST_P(LUSummaryTest, RoundTripWithTwoSummaryTypes) {
"entity_id": 2,
"entity_summary": {
"pairs": [
- { "first": 2, "second": 4 },
- { "first": 2, "second": 3 }
+ { "first": {"@": 2}, "second": {"@": 4} },
+ { "first": {"@": 2}, "second": {"@": 3} }
]
}
}
diff --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp
index a8cf82806aecc..98d749d8fa67e 100644
--- a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest/TUSummaryTest.cpp
@@ -1216,7 +1216,7 @@ TEST_F(JSONFormatTUSummaryTest,
"entity_summary": {
"pairs": [
{
- "second": 1
+ "second": {"@": 1}
}
]
}
@@ -1257,7 +1257,7 @@ TEST_F(JSONFormatTUSummaryTest,
"pairs": [
{
"first": "not_a_number",
- "second": 1
+ "second": {"@": 1}
}
]
}
@@ -1297,7 +1297,7 @@ TEST_F(JSONFormatTUSummaryTest,
"entity_summary": {
"pairs": [
{
- "first": 0
+ "first": {"@": 0}
}
]
}
@@ -1337,7 +1337,7 @@ TEST_F(JSONFormatTUSummaryTest,
"entity_summary": {
"pairs": [
{
- "first": 0,
+ "first": {"@": 0},
"second": "not_a_number"
}
]
@@ -2112,7 +2112,7 @@ TEST_P(TUSummaryTest, RoundTripWithTwoSummaryTypes) {
"entity_id": 1,
"entity_summary": {
"pairs": [
- { "first": 1, "second": 3 }
+ { "first": {"@": 1}, "second": {"@": 3} }
]
}
},
@@ -2120,8 +2120,8 @@ TEST_P(TUSummaryTest, RoundTripWithTwoSummaryTypes) {
"entity_id": 4,
"entity_summary": {
"pairs": [
- { "first": 4, "second": 0 },
- { "first": 4, "second": 2 }
+ { "first": {"@": 4}, "second": {"@": 0} },
+ { "first": {"@": 4}, "second": {"@": 2} }
]
}
},
@@ -2135,7 +2135,7 @@ TEST_P(TUSummaryTest, RoundTripWithTwoSummaryTypes) {
"entity_id": 3,
"entity_summary": {
"pairs": [
- { "first": 3, "second": 1 }
+ { "first": {"@": 3}, "second": {"@": 1} }
]
}
},
@@ -2143,8 +2143,8 @@ TEST_P(TUSummaryTest, RoundTripWithTwoSummaryTypes) {
"entity_id": 2,
"entity_summary": {
"pairs": [
- { "first": 2, "second": 4 },
- { "first": 2, "second": 3 }
+ { "first": {"@": 2}, "second": {"@": 4} },
+ { "first": {"@": 2}, "second": {"@": 3} }
]
}
}
>From 73ac1e1255de9669452206a3d5ead84d97f41907 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 5 Mar 2026 10:08:45 -0800
Subject: [PATCH 02/13] Fixes
---
.../Scalable/Serialization/JSONFormat.h | 6 +-
.../JSONFormat/JSONEntitySummaryEncoding.h | 10 +-
.../JSONFormat/JSONFormatImpl.cpp | 10 +-
.../Serialization/JSONFormat/JSONFormatImpl.h | 4 -
clang/tools/ssaf-linker/SSAFLinker.cpp | 100 +++++++-----------
.../SerializationFormatRegistryTest.cpp | 7 +-
6 files changed, 52 insertions(+), 85 deletions(-)
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
index 60e4eeb97edb6..c918e438e426f 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -81,10 +81,10 @@ class JSONFormat final : public SerializationFormat {
EntityId entityIdFromJSON(const uint64_t EntityIdIndex) const;
uint64_t entityIdToJSON(EntityId EI) const;
- llvm::Expected<EntityId>
- entityIdFromJSONObject(const Object &EntityIdObject) const;
+ static llvm::Expected<EntityId>
+ entityIdFromJSONObject(const Object &EntityIdObject);
static Value *entityIdReferenceFromJSONObject(Object &EntityIdObject);
- Object entityIdToJSONObject(EntityId EI) const;
+ static Object entityIdToJSONObject(EntityId EI);
llvm::Expected<BuildNamespace>
buildNamespaceFromJSON(const Object &BuildNamespaceObject) const;
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.h b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.h
index 92fec92650c4c..aa0c6ab61f658 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.h
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.h
@@ -1,4 +1,4 @@
-//===- JSONEntitySummaryEncoding.h ------------------------------*- C++ -*-===//
+//===- JSONEntitySummaryEncoding.h ----------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -23,14 +23,6 @@
namespace clang::ssaf {
-//----------------------------------------------------------------------------
-// JSONEntitySummaryEncoding
-//
-// Concrete EntitySummaryEncoding used by JSONFormat for both TUSummaryEncoding
-// and LUSummaryEncoding. Stores the raw EntitySummary JSON value opaquely so
-// the linker can patch and emit it without knowing the analysis schema.
-//----------------------------------------------------------------------------
-
class JSONEntitySummaryEncoding final : public EntitySummaryEncoding {
friend JSONFormat;
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
index 5f49ee5c10898..e4f9c3dc99452 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
@@ -149,7 +149,7 @@ uint64_t JSONFormat::entityIdToJSON(EntityId EI) const {
}
llvm::Expected<EntityId>
-JSONFormat::entityIdFromJSONObject(const Object &EntityIdObject) const {
+JSONFormat::entityIdFromJSONObject(const Object &EntityIdObject) {
if (EntityIdObject.size() != 1) {
return ErrorBuilder::create(std::errc::invalid_argument,
ErrorMessages::FailedToReadEntityIdObject,
@@ -189,9 +189,9 @@ Value *JSONFormat::entityIdReferenceFromJSONObject(Object &EntityIdObject) {
return AtVal;
}
-Object JSONFormat::entityIdToJSONObject(EntityId EI) const {
+Object JSONFormat::entityIdToJSONObject(EntityId EI) {
Object Result;
- Result["@"] = static_cast<uint64_t>(getIndex(EI));
+ Result[JSONEntityIdKey] = static_cast<uint64_t>(getIndex(EI));
return Result;
}
@@ -666,7 +666,7 @@ JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
return InfoEntry.Deserialize(
EntitySummaryObject, IdTable,
- [this](const Object &Obj) { return entityIdFromJSONObject(Obj); });
+ [](const Object &Obj) { return entityIdFromJSONObject(Obj); });
}
llvm::Expected<Object>
@@ -684,7 +684,7 @@ JSONFormat::entitySummaryToJSON(const SummaryName &SN,
assert(InfoEntry.ForSummary == SN);
return InfoEntry.Serialize(
- ES, [this](EntityId EI) { return entityIdToJSONObject(EI); });
+ ES, [](EntityId EI) { return entityIdToJSONObject(EI); });
}
//----------------------------------------------------------------------------
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.h b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.h
index 74837d845b8f1..a95d4c3d37db8 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.h
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.h
@@ -122,13 +122,9 @@ inline constexpr const char *FailedToPatchEntityIdNotInTable =
// Entity Id JSON Representation
//----------------------------------------------------------------------------
-namespace {
-
/// An entity ID is encoded as the single-key object {"@": <index>}.
inline constexpr const char *JSONEntityIdKey = "@";
-} // namespace
-
//----------------------------------------------------------------------------
// JSON Reader and Writer
//----------------------------------------------------------------------------
diff --git a/clang/tools/ssaf-linker/SSAFLinker.cpp b/clang/tools/ssaf-linker/SSAFLinker.cpp
index ad9ae56646260..d2864e543cc51 100644
--- a/clang/tools/ssaf-linker/SSAFLinker.cpp
+++ b/clang/tools/ssaf-linker/SSAFLinker.cpp
@@ -15,16 +15,18 @@
#include "clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h"
#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/Path.h"
-#include "llvm/Support/Signals.h"
+#include "llvm/Support/Process.h"
#include "llvm/Support/Timer.h"
#include "llvm/Support/WithColor.h"
#include "llvm/Support/raw_ostream.h"
-#include <map>
#include <memory>
#include <string>
@@ -42,32 +44,27 @@ namespace {
cl::OptionCategory SsafLinkerCategory("ssaf-linker options");
-cl::list<std::string> InputPaths(cl::Positional, cl::desc("input..."),
- cl::value_desc("path"), cl::OneOrMore,
- cl::cat(SsafLinkerCategory));
+cl::list<std::string> InputPaths(cl::Positional, cl::desc("<input files>"),
+ cl::OneOrMore, cl::cat(SsafLinkerCategory));
-cl::opt<std::string> OutputPath("output", cl::desc("Output summary path"),
+cl::opt<std::string> OutputPath("o", cl::desc("Output summary path"),
cl::value_desc("path"), cl::Required,
cl::cat(SsafLinkerCategory));
-cl::alias OutputFileShort("o", cl::aliasopt(OutputPath));
+cl::alias OutputPathLong("output", cl::aliasopt(OutputPath));
cl::opt<bool> Verbose("verbose", cl::desc("Enable verbose output"),
cl::init(false), cl::cat(SsafLinkerCategory));
-cl::alias VerboseShort("v", cl::aliasopt(Verbose));
-
cl::opt<bool> Time("time", cl::desc("Enable timing"), cl::init(false),
cl::cat(SsafLinkerCategory));
-cl::alias TimeShort("t", cl::aliasopt(Time));
-
//===----------------------------------------------------------------------===//
// Error Messages
//===----------------------------------------------------------------------===//
constexpr const char *ErrorCannotValidateSummary =
- "Failed to validate summary '{0}': {1}";
+ "failed to validate summary '{0}': {1}.";
constexpr const char *ErrorOutputDirectoryMissing =
"Parent directory does not exist.";
@@ -80,17 +77,14 @@ constexpr const char *ErrorExtensionNotSupplied = "Extension not supplied.";
constexpr const char *ErrorNoFormatForExtension =
"Format not registered for extension '{0}'.";
-constexpr const char *ErrorCannotResolvePath =
- "Failed to validate summary '{0}': {1}.";
-
constexpr const char *ErrorLoadFailed =
- "Failed to load input summary '{0}': {1}.";
+ "failed to load input summary '{0}': {1}.";
constexpr const char *ErrorLinkFailed =
- "Failed to link input summary '{0}': {1}.";
+ "failed to link input summary '{0}': {1}.";
constexpr const char *ErrorWriteFailed =
- "Failed to write output summary '{0}': {1}.";
+ "failed to write output summary '{0}': {1}.";
//===----------------------------------------------------------------------===//
// Diagnostic Utilities
@@ -100,11 +94,13 @@ constexpr unsigned IndentationWidth = 2;
static llvm::StringRef ToolName;
+static void PrintVersion(llvm::raw_ostream &OS) { OS << "ssaf-linker 0.1\n"; }
+
template <typename... Ts>
[[noreturn]] void Fail(const char *Fmt, Ts &&...Args) {
llvm::WithColor::error(llvm::errs(), ToolName)
<< llvm::formatv(Fmt, std::forward<Ts>(Args)...) << "\n";
- std::exit(1);
+ llvm::sys::Process::Exit(1);
}
template <typename... Ts>
@@ -116,48 +112,31 @@ void Info(unsigned IndentationLevel, const char *Fmt, Ts &&...Args) {
}
}
-struct ScopedTimer {
- explicit ScopedTimer(llvm::Timer &T) : T(T) {
- if (Time) {
- T.startTimer();
- }
- }
-
- ~ScopedTimer() {
- if (Time) {
- T.stopTimer();
- }
- }
- llvm::Timer &T;
-};
-
//===----------------------------------------------------------------------===//
// Format Registry
//===----------------------------------------------------------------------===//
-// TODO - will be replaced by an equivalent method from the framework
-std::unique_ptr<SerializationFormat>
-MakeFormatForExtension(llvm::StringRef Extension) {
- if (Extension == "json") {
- return std::make_unique<JSONFormat>();
- }
- return nullptr;
-}
-
SerializationFormat *GetFormatForExtension(llvm::StringRef Extension) {
- static std::map<std::string, std::unique_ptr<SerializationFormat>>
- ExtensionFormatMap;
-
- auto It = ExtensionFormatMap.find(Extension.str());
- if (It != ExtensionFormatMap.end()) {
+ static llvm::SmallVector<
+ std::pair<std::string, std::unique_ptr<SerializationFormat>>, 4>
+ ExtensionFormatList;
+
+ // Most recently used format is most likely to be reused again.
+ auto ReversedList = llvm::reverse(ExtensionFormatList);
+ auto It = llvm::find_if(ReversedList, [&](const auto &Entry) {
+ return Entry.first == Extension;
+ });
+ if (It != ReversedList.end()) {
return It->second.get();
}
- auto Format = MakeFormatForExtension(Extension);
+ // SerializationFormats are uppercase while file extensions are lowercase.
+ std::string CapitalizedExtension = Extension.upper();
+ auto Format = makeFormat(CapitalizedExtension);
SerializationFormat *Result = Format.get();
if (Result) {
- ExtensionFormatMap.emplace(Extension, std::move(Format));
+ ExtensionFormatList.emplace_back(Extension, std::move(Format));
}
return Result;
@@ -201,7 +180,7 @@ LinkerInput Validate(llvm::TimerGroup &TG) {
LinkerInput LI;
{
- ScopedTimer _(TValidate);
+ llvm::TimeRegion _(Time ? &TValidate : nullptr);
llvm::StringRef ParentDir = path::parent_path(OutputPath);
llvm::StringRef DirToCheck = ParentDir.empty() ? "." : ParentDir;
@@ -221,12 +200,12 @@ LinkerInput Validate(llvm::TimerGroup &TG) {
Info(2, "Validated output summary path '{0}'.", LI.OutputFile.Path);
{
- ScopedTimer _(TValidate);
+ llvm::TimeRegion _(Time ? &TValidate : nullptr);
for (const auto &InputPath : InputPaths) {
llvm::SmallString<256> RealPath;
std::error_code EC = fs::real_path(InputPath, RealPath, true);
if (EC) {
- Fail(ErrorCannotResolvePath, InputPath, EC.message());
+ Fail(ErrorCannotValidateSummary, InputPath, EC.message());
}
LI.InputFiles.push_back(SummaryFile::FromPath(RealPath));
}
@@ -256,7 +235,7 @@ void Link(const LinkerInput &LI, llvm::TimerGroup &TG) {
Info(3, "[{0}/{1}] Reading '{2}'.", (Index + 1), LI.InputFiles.size(),
InputFile.Path);
- ScopedTimer _(TRead);
+ llvm::TimeRegion _(Time ? &TRead : nullptr);
auto ExpectedSummaryEncoding =
InputFile.Format->readTUSummaryEncoding(InputFile.Path);
@@ -273,7 +252,7 @@ void Link(const LinkerInput &LI, llvm::TimerGroup &TG) {
Info(3, "[{0}/{1}] Linking '{2}'.", (Index + 1), LI.InputFiles.size(),
InputFile.Path);
- ScopedTimer _(TLink);
+ llvm::TimeRegion _(Time ? &TLink : nullptr);
if (auto Err = EL.link(std::move(Summary))) {
Fail(ErrorLinkFailed, InputFile.Path, toString(std::move(Err)));
@@ -284,7 +263,7 @@ void Link(const LinkerInput &LI, llvm::TimerGroup &TG) {
{
Info(2, "Writing output summary to '{0}'.", LI.OutputFile.Path);
- ScopedTimer _(TWrite);
+ llvm::TimeRegion _(Time ? &TWrite : nullptr);
auto Output = std::move(EL).getOutput();
if (auto Err = LI.OutputFile.Format->writeLUSummaryEncoding(
@@ -301,11 +280,12 @@ void Link(const LinkerInput &LI, llvm::TimerGroup &TG) {
//===----------------------------------------------------------------------===//
int main(int argc, const char **argv) {
- ToolName = argv[0];
+ InitLLVM X(argc, argv);
+ ToolName = llvm::sys::path::filename(argv[0]);
initializeJSONFormat();
- sys::PrintStackTraceOnErrorSignal(argv[0]);
cl::HideUnrelatedOptions(SsafLinkerCategory);
+ cl::SetVersionPrinter(PrintVersion);
cl::ParseCommandLineOptions(argc, argv, "SSAF Linker\n");
llvm::TimerGroup LinkerTimers("ssaf-linker", "SSAF Linker");
@@ -327,9 +307,5 @@ int main(int argc, const char **argv) {
Info(0, "Linking finished.");
}
- if (Time) {
- LinkerTimers.print(llvm::errs());
- }
-
return 0;
}
diff --git a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
index b8f1ea0a866e4..65e01cca4ce97 100644
--- a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
@@ -8,6 +8,7 @@
#include "clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h"
#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/FileSystem.h"
@@ -54,8 +55,10 @@ TEST(SerializationFormatRegistryTest, isFormatRegistered) {
TEST(SerializationFormatRegistryTest, EnumeratingRegistryEntries) {
auto Formats = SerializationFormatRegistry::entries();
- ASSERT_EQ(std::distance(Formats.begin(), Formats.end()), 2U);
- EXPECT_EQ(Formats.begin()->getName(), "MockSerializationFormat");
+ ASSERT_GE(std::distance(Formats.begin(), Formats.end()), 1U);
+ EXPECT_TRUE(llvm::any_of(Formats, [](const auto &Entry) {
+ return StringRef(Entry.getName()) == "MockSerializationFormat";
+ }));
}
TEST(SerializationFormatRegistryTest, Roundtrip) {
>From 45ccc0b80dc71eb922fe526496e8d4111a0a0a34 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 5 Mar 2026 12:01:26 -0800
Subject: [PATCH 03/13] Fix basic tests
---
.../Analysis/Scalable/ssaf-linker/help.test | 10 ++--
.../Analysis/Scalable/ssaf-linker/time.test | 37 +++++----------
.../ssaf-linker/validation-errors.test | 16 +++----
.../Scalable/ssaf-linker/verbose.test | 46 ++++++-------------
.../Scalable/ssaf-linker/version.test | 5 ++
clang/tools/ssaf-linker/SSAFLinker.cpp | 10 ++--
6 files changed, 49 insertions(+), 75 deletions(-)
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/version.test
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/help.test b/clang/test/Analysis/Scalable/ssaf-linker/help.test
index bc90d79435bc7..900f2285c5d5d 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/help.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/help.test
@@ -4,7 +4,7 @@
// CHECK: OVERVIEW: SSAF Linker
// CHECK-EMPTY:
-// CHECK: USAGE: ssaf-linker [options] input...
+// CHECK: USAGE: ssaf-linker [options] <input files>
// CHECK-EMPTY:
// CHECK: OPTIONS:
// CHECK: -h - Alias for --help
@@ -12,12 +12,10 @@
// CHECK: --help-hidden - Display all available options
// CHECK: --help-list - Display list of available options (--help-list-hidden for more)
// CHECK: --help-list-hidden - Display list of all available options
-// CHECK: -o -
-// CHECK: --output=<path> - Output summary path
+// CHECK: -o <path> - Output summary path
+// CHECK: --output -
// CHECK: --print-all-options - Print all option values after command line parsing
// CHECK: --print-options - Print non-default options after command line parsing
-// CHECK: -t -
// CHECK: --time - Enable timing
-// CHECK: -v -
// CHECK: --verbose - Enable verbose output
-// CHECK: --version - Display the version of this program
+// CHECK: --version - Display the version of this program
\ No newline at end of file
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/time.test b/clang/test/Analysis/Scalable/ssaf-linker/time.test
index 0f7033876313f..8737c6d9e0eef 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/time.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/time.test
@@ -4,29 +4,14 @@
// RUN: mkdir -p %t
// RUN: ssaf-linker --time %S/Inputs/tu-1.json %S/Inputs/tu-2.json -o %t/lu-1+2.json 2>&1 \
-// RUN: | FileCheck %s --check-prefix=CHECK-LONG
-// CHECK-LONG: ===-------------------------------------------------------------------------===
-// CHECK-LONG: SSAF Linker
-// CHECK-LONG: ===-------------------------------------------------------------------------===
-// CHECK-LONG: Total Execution Time: {{[0-9.]+}} seconds ({{[0-9.]+}} wall clock)
-// CHECK-LONG: ---User Time--- --System Time-- --User+System-- ---Wall Time---
-// CHECK-LONG-DAG: {{.*}}Write Summary
-// CHECK-LONG-DAG: {{.*}}Read Summaries
-// CHECK-LONG-DAG: {{.*}}Link Summaries
-// CHECK-LONG-DAG: {{.*}}Validate Input
-// CHECK-LONG: {{.*}}Total
-
-// RUN: rm %t/lu-1+2.json
-
-// RUN: ssaf-linker -t %S/Inputs/tu-1.json %S/Inputs/tu-2.json -o %t/lu-1+2.json 2>&1 \
-// RUN: | FileCheck %s --check-prefix=CHECK-SHORT
-// CHECK-SHORT: ===-------------------------------------------------------------------------===
-// CHECK-SHORT: SSAF Linker
-// CHECK-SHORT: ===-------------------------------------------------------------------------===
-// CHECK-SHORT: Total Execution Time: {{[0-9.]+}} seconds ({{[0-9.]+}} wall clock)
-// CHECK-SHORT: ---User Time--- --System Time-- --User+System-- ---Wall Time---
-// CHECK-SHORT-DAG: {{.*}}Write Summary
-// CHECK-SHORT-DAG: {{.*}}Read Summaries
-// CHECK-SHORT-DAG: {{.*}}Link Summaries
-// CHECK-SHORT-DAG: {{.*}}Validate Input
-// CHECK-SHORT: {{.*}}Total
\ No newline at end of file
+// RUN: | FileCheck %s
+// CHECK: ===-------------------------------------------------------------------------===
+// CHECK: SSAF Linker
+// CHECK: ===-------------------------------------------------------------------------===
+// CHECK: Total Execution Time: {{[0-9.]+}} seconds ({{[0-9.]+}} wall clock)
+// CHECK: ---User Time--- --System Time-- --User+System-- ---Wall Time---
+// CHECK-DAG: {{.*}}Write Summary
+// CHECK-DAG: {{.*}}Read Summaries
+// CHECK-DAG: {{.*}}Link Summaries
+// CHECK-DAG: {{.*}}Validate Input
+// CHECK: {{.*}}Total
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test b/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
index 1fc99a18e8041..2a8476f8be48b 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
@@ -5,27 +5,27 @@
// No extension on output.
// RUN: not ssaf-linker %S/Inputs/tu-empty.json -o lu 2>&1 \
// RUN: | FileCheck %s --check-prefix=NO-EXT-OUTPUT
-// NO-EXT-OUTPUT: ssaf-linker: error: Failed to validate summary 'lu': Extension not supplied.
+// NO-EXT-OUTPUT: ssaf-linker: error: failed to validate summary 'lu': Extension not supplied.
// No extension on input.
// RUN: not ssaf-linker %S/Inputs/tu-noext -o %t/lu.json 2>&1 \
// RUN: | FileCheck %s --check-prefix=NO-EXT-INPUT -DPATH=%S/Inputs/tu-noext
-// NO-EXT-INPUT: ssaf-linker: error: Failed to validate summary '[[PATH]]': Extension not supplied.
+// NO-EXT-INPUT: ssaf-linker: error: failed to validate summary '[[PATH]]': Extension not supplied.
// Invalid extension on output.
// RUN: not ssaf-linker %S/Inputs/tu-empty.json -o lu.txt 2>&1 \
// RUN: | FileCheck %s --check-prefix=BAD-EXT-OUTPUT
-// BAD-EXT-OUTPUT: ssaf-linker: error: Failed to validate summary 'lu.txt': Format not registered for extension 'txt'.
+// BAD-EXT-OUTPUT: ssaf-linker: error: failed to validate summary 'lu.txt': Format not registered for extension 'txt'.
// Invalid extension on input.
// RUN: not ssaf-linker %S/Inputs/tu-badext.txt -o %t/lu.json 2>&1 \
// RUN: | FileCheck %s --check-prefix=BAD-EXT-INPUT -DPATH=%S/Inputs/tu-badext.txt
-// BAD-EXT-INPUT: ssaf-linker: error: Failed to validate summary '[[PATH]]': Format not registered for extension 'txt'.
+// BAD-EXT-INPUT: ssaf-linker: error: failed to validate summary '[[PATH]]': Format not registered for extension 'txt'.
// Output directory does not exist.
// RUN: not ssaf-linker %S/Inputs/tu-empty.json -o %S/Outputs/NonExistentDirectory/lu.json 2>&1 \
// RUN: | FileCheck %s --check-prefix=OUTPUT-PARENT-DIR-MISSING -DPATH=%S/Outputs/NonExistentDirectory/lu.json
-// OUTPUT-PARENT-DIR-MISSING: ssaf-linker: error: Failed to validate summary '[[PATH]]': Parent directory does not exist.
+// OUTPUT-PARENT-DIR-MISSING: ssaf-linker: error: failed to validate summary '[[PATH]]': Parent directory does not exist.
// Output parent directory exists but is not writable.
// UNSUPPORTED: system-windows
@@ -34,16 +34,16 @@
// RUN: not ssaf-linker %S/Inputs/tu-empty.json -o %t/output-dir/output.json 2>&1 \
// RUN: | FileCheck %s --check-prefix=NO-WRITE-PERM -DPATH=%t/output-dir/output.json
// RUN: chmod +w %t/output-dir
-// NO-WRITE-PERM: ssaf-linker: error: Failed to validate summary '[[PATH]]': Parent directory is not writable.
+// NO-WRITE-PERM: ssaf-linker: error: failed to validate summary '[[PATH]]': Parent directory is not writable.
// Input summary does not exist.
// RUN: not ssaf-linker %S/Inputs/tu-nonexistent.json -o %t/lu.json 2>&1 \
// RUN: | FileCheck %s --check-prefix=NO-INPUT-FILE -DPATH=%S/Inputs/tu-nonexistent.json
-// NO-INPUT-FILE: ssaf-linker: error: Failed to validate summary '[[PATH]]': No such file or directory.
+// NO-INPUT-FILE: ssaf-linker: error: failed to validate summary '[[PATH]]': No such file or directory.
// Input summary is a broken symlink.
// RUN: ln -sf %t/tu-nonexistent %t/tu-dangling.json
// RUN: not ssaf-linker %t/tu-dangling.json -o %t/lu.json 2>&1 \
// RUN: | FileCheck %s --check-prefix=BROKEN-SYMLINK -DPATH=%t/tu-dangling.json
-// BROKEN-SYMLINK: ssaf-linker: error: Failed to validate summary '[[PATH]]': No such file or directory.
+// BROKEN-SYMLINK: ssaf-linker: error: failed to validate summary '[[PATH]]': No such file or directory.
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/verbose.test b/clang/test/Analysis/Scalable/ssaf-linker/verbose.test
index 81c8c922e5324..0f7777043d1d9 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/verbose.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/verbose.test
@@ -4,35 +4,17 @@
// RUN: mkdir -p %t
// RUN: ssaf-linker --verbose %S/Inputs/tu-1.json %S/Inputs/tu-2.json -o %t/lu-1+2.json 2>&1 \
-// RUN: | FileCheck %s --check-prefix=CHECK-LONG -DINPUT=%S/Inputs -DOUTPUT=%t
-// CHECK-LONG: note: - Linking started.
-// CHECK-LONG: note: - Validating input.
-// CHECK-LONG: note: - Validated output summary path '[[OUTPUT]]/lu-1+2.json'.
-// CHECK-LONG: note: - Validated 2 input summary paths.
-// CHECK-LONG: note: - Linking input.
-// CHECK-LONG: note: - Constructing linker.
-// CHECK-LONG: note: - Linking summaries.
-// CHECK-LONG: note: - [1/2] Reading '[[INPUT]]/tu-1.json'.
-// CHECK-LONG: note: - [1/2] Linking '[[INPUT]]/tu-1.json'.
-// CHECK-LONG: note: - [2/2] Reading '[[INPUT]]/tu-2.json'.
-// CHECK-LONG: note: - [2/2] Linking '[[INPUT]]/tu-2.json'.
-// CHECK-LONG: note: - Writing output summary to '[[OUTPUT]]/lu-1+2.json'.
-// CHECK-LONG: note: - Linking finished.
-
-// RUN: rm %t/lu-1+2.json
-
-// RUN: ssaf-linker -v %S/Inputs/tu-1.json %S/Inputs/tu-2.json -o %t/lu-1+2.json 2>&1 \
-// RUN: | FileCheck %s --check-prefix=CHECK-SHORT -DINPUT=%S/Inputs -DOUTPUT=%t
-// CHECK-SHORT: note: - Linking started.
-// CHECK-SHORT: note: - Validating input.
-// CHECK-SHORT: note: - Validated output summary path '[[OUTPUT]]/lu-1+2.json'.
-// CHECK-SHORT: note: - Validated 2 input summary paths.
-// CHECK-SHORT: note: - Linking input.
-// CHECK-SHORT: note: - Constructing linker.
-// CHECK-SHORT: note: - Linking summaries.
-// CHECK-SHORT: note: - [1/2] Reading '[[INPUT]]/tu-1.json'.
-// CHECK-SHORT: note: - [1/2] Linking '[[INPUT]]/tu-1.json'.
-// CHECK-SHORT: note: - [2/2] Reading '[[INPUT]]/tu-2.json'.
-// CHECK-SHORT: note: - [2/2] Linking '[[INPUT]]/tu-2.json'.
-// CHECK-SHORT: note: - Writing output summary to '[[OUTPUT]]/lu-1+2.json'.
-// CHECK-SHORT: note: - Linking finished.
\ No newline at end of file
+// RUN: | FileCheck %s -DINPUT=%S/Inputs -DOUTPUT=%t
+// CHECK: note: - Linking started.
+// CHECK: note: - Validating input.
+// CHECK: note: - Validated output summary path '[[OUTPUT]]/lu-1+2.json'.
+// CHECK: note: - Validated 2 input summary paths.
+// CHECK: note: - Linking input.
+// CHECK: note: - Constructing linker.
+// CHECK: note: - Linking summaries.
+// CHECK: note: - [1/2] Reading '[[INPUT]]/tu-1.json'.
+// CHECK: note: - [1/2] Linking '[[INPUT]]/tu-1.json'.
+// CHECK: note: - [2/2] Reading '[[INPUT]]/tu-2.json'.
+// CHECK: note: - [2/2] Linking '[[INPUT]]/tu-2.json'.
+// CHECK: note: - Writing output summary to '[[OUTPUT]]/lu-1+2.json'.
+// CHECK: note: - Linking finished.
\ No newline at end of file
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/version.test b/clang/test/Analysis/Scalable/ssaf-linker/version.test
new file mode 100644
index 0000000000000..e68e4eeb47152
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/version.test
@@ -0,0 +1,5 @@
+// Test ssaf-linker version
+
+// RUN: ssaf-linker --version | FileCheck %s
+
+// CHECK: ssaf-linker 0.1
diff --git a/clang/tools/ssaf-linker/SSAFLinker.cpp b/clang/tools/ssaf-linker/SSAFLinker.cpp
index d2864e543cc51..d3485508c1f74 100644
--- a/clang/tools/ssaf-linker/SSAFLinker.cpp
+++ b/clang/tools/ssaf-linker/SSAFLinker.cpp
@@ -132,12 +132,16 @@ SerializationFormat *GetFormatForExtension(llvm::StringRef Extension) {
// SerializationFormats are uppercase while file extensions are lowercase.
std::string CapitalizedExtension = Extension.upper();
+
+ if (!isFormatRegistered(CapitalizedExtension)) {
+ return nullptr;
+ }
+
auto Format = makeFormat(CapitalizedExtension);
SerializationFormat *Result = Format.get();
+ assert(Result);
- if (Result) {
- ExtensionFormatList.emplace_back(Extension, std::move(Format));
- }
+ ExtensionFormatList.emplace_back(Extension, std::move(Format));
return Result;
}
>From d78ef5cdf1d59057165715626d3fec0b9c16b22a Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 5 Mar 2026 15:31:11 -0800
Subject: [PATCH 04/13] More fixes
---
.../Scalable/EntityLinker/EntityLinker.h | 11 +--
.../Scalable/Serialization/JSONFormat.h | 1 -
.../Analysis/Scalable/Support/ErrorBuilder.h | 5 +-
.../Scalable/EntityLinker/EntityLinker.cpp | 16 ++---
.../JSONFormat/JSONEntitySummaryEncoding.cpp | 62 +++++++++++-----
.../JSONFormat/JSONEntitySummaryEncoding.h | 11 +--
.../JSONFormat/JSONFormatImpl.cpp | 13 ----
.../Inputs/tu-invalid-entity-id-multikey.json | 32 +++++++++
.../Inputs/tu-invalid-entity-id-ref.json | 32 +++++++++
.../Inputs/tu-invalid-entity-id-value.json | 32 +++++++++
.../ssaf-linker/Inputs/tu-missing-fields.json | 6 ++
.../Analysis/Scalable/ssaf-linker/cli.test | 2 +-
.../Analysis/Scalable/ssaf-linker/help.test | 1 -
.../Analysis/Scalable/ssaf-linker/io.test | 34 +++++----
.../Scalable/ssaf-linker/linking-errors.test | 21 +++++-
.../ssaf-linker/validation-errors.test | 3 +-
clang/tools/ssaf-linker/SSAFLinker.cpp | 70 +++++++++++--------
17 files changed, 249 insertions(+), 103 deletions(-)
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-multikey.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-ref.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-value.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-missing-fields.json
diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h b/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
index c17e6cdc2955d..28d70e9e5f1fd 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
@@ -42,9 +42,9 @@ class EntityLinker {
/// and merges them into a single data store.
///
/// \param Summary The TU summary to link. Ownership is transferred.
- /// \returns Error if the TU namespace has already been linked, success
- /// otherwise. Corrupted summary data (missing linkage information,
- /// duplicate entity IDs, etc.) triggers a fatal error.
+ /// \returns Error if the TU namespace has already been linked or if patching
+ /// fails, success otherwise. Corrupted summary data (missing linkage
+ /// information, duplicate entity IDs, etc.) triggers a fatal error.
llvm::Error link(std::unique_ptr<TUSummaryEncoding> Summary);
/// Returns the accumulated LU summary.
@@ -81,8 +81,9 @@ class EntityLinker {
///
/// \param PatchTargets Vector of summary encodings that need patching.
/// \param EntityResolutionTable Map from TU EntityIds to LU EntityIds.
- void patch(const std::vector<EntitySummaryEncoding *> &PatchTargets,
- const std::map<EntityId, EntityId> &EntityResolutionTable);
+ /// \returns Error if patching any encoding fails, success otherwise.
+ llvm::Error patch(const std::vector<EntitySummaryEncoding *> &PatchTargets,
+ const std::map<EntityId, EntityId> &EntityResolutionTable);
};
} // namespace clang::ssaf
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
index c918e438e426f..8c7cbd39e7099 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -83,7 +83,6 @@ class JSONFormat final : public SerializationFormat {
static llvm::Expected<EntityId>
entityIdFromJSONObject(const Object &EntityIdObject);
- static Value *entityIdReferenceFromJSONObject(Object &EntityIdObject);
static Object entityIdToJSONObject(EntityId EI);
llvm::Expected<BuildNamespace>
diff --git a/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h b/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
index 81c3991c315e8..a22936663fa39 100644
--- a/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
+++ b/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
@@ -199,8 +199,9 @@ class ErrorBuilder {
/// \endcode
template <typename... Args>
[[noreturn]] static void fatal(const char *Fmt, Args &&...ArgVals) {
- llvm::report_fatal_error(llvm::StringRef(
- formatErrorMessage(Fmt, std::forward<Args>(ArgVals)...)));
+ llvm::report_fatal_error(llvm::StringRef(formatErrorMessage(
+ Fmt, std::forward<Args>(ArgVals)...)),
+ false);
}
};
diff --git a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
index 5449dada64c68..2cb23718d0fe8 100644
--- a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
+++ b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
@@ -161,19 +161,17 @@ EntityLinker::merge(TUSummaryEncoding &Summary,
return PatchTargets;
}
-void EntityLinker::patch(
- const std::vector<EntitySummaryEncoding *> &PatchTargets,
- const std::map<EntityId, EntityId> &EntityResolutionTable) {
+llvm::Error
+EntityLinker::patch(const std::vector<EntitySummaryEncoding *> &PatchTargets,
+ const std::map<EntityId, EntityId> &EntityResolutionTable) {
for (auto *PatchTarget : PatchTargets) {
assert(PatchTarget && "EntityLinker::patch: Patch target cannot be null");
if (auto Err = PatchTarget->patch(EntityResolutionTable)) {
- std::string PatchingErrorMessage = llvm::toString(std::move(Err));
- ErrorBuilder::fatal("{0} - {1}",
- ErrorMessages::EntityLinkerFatalErrorPrefix,
- PatchingErrorMessage);
+ return Err;
}
}
+ return llvm::Error::success();
}
llvm::Error EntityLinker::link(std::unique_ptr<TUSummaryEncoding> Summary) {
@@ -189,7 +187,9 @@ llvm::Error EntityLinker::link(std::unique_ptr<TUSummaryEncoding> Summary) {
auto EntityResolutionTable = resolve(SummaryRef);
auto PatchTargets = merge(SummaryRef, EntityResolutionTable);
- patch(PatchTargets, EntityResolutionTable);
+ if (auto Err = patch(PatchTargets, EntityResolutionTable)) {
+ return Err;
+ }
return llvm::Error::success();
}
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.cpp
index 13a20ed1800a4..6cab6885db051 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.cpp
@@ -11,29 +11,55 @@
namespace clang::ssaf {
+llvm::Expected<bool> JSONEntitySummaryEncoding::patchEntityIdObject(
+ llvm::json::Object &Obj, const std::map<EntityId, EntityId> &Table) {
+
+ llvm::json::Value *AtVal = Obj.get(JSONEntityIdKey);
+ if (!AtVal) {
+ return false;
+ }
+
+ if (Obj.size() != 1) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadEntityIdObject,
+ JSONEntityIdKey)
+ .build();
+ }
+
+ std::optional<uint64_t> OptEntityIdIndex = AtVal->getAsUINT64();
+ if (!OptEntityIdIndex) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadEntityIdObject,
+ JSONEntityIdKey)
+ .build();
+ }
+
+ auto OldId = JSONFormat::makeEntityId(*OptEntityIdIndex);
+ auto It = Table.find(OldId);
+ if (It == Table.end()) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToPatchEntityIdNotInTable,
+ OldId)
+ .build();
+ }
+
+ *AtVal = static_cast<uint64_t>(JSONFormat::getIndex(It->second));
+
+ return true;
+}
+
llvm::Error JSONEntitySummaryEncoding::patchObject(
llvm::json::Object &Obj, const std::map<EntityId, EntityId> &Table) {
- if (auto AtVal = JSONFormat::entityIdReferenceFromJSONObject(Obj)) {
- std::optional<uint64_t> OptEntityIdIndex = AtVal->getAsUINT64();
- if (!OptEntityIdIndex) {
- return ErrorBuilder::create(std::errc::invalid_argument,
- ErrorMessages::FailedToReadEntityIdObject,
- JSONEntityIdKey)
- .build();
- }
+ auto ExpectedIsEntityId = patchEntityIdObject(Obj, Table);
- auto OldId = JSONFormat::makeEntityId(*OptEntityIdIndex);
- auto It = Table.find(OldId);
- if (It == Table.end()) {
- return ErrorBuilder::create(
- std::errc::invalid_argument,
- ErrorMessages::FailedToPatchEntityIdNotInTable, OldId)
- .build();
- }
+ if (!ExpectedIsEntityId) {
+ return ExpectedIsEntityId.takeError();
+ }
+
+ bool IsEntityId = *ExpectedIsEntityId;
- *AtVal = static_cast<uint64_t>(JSONFormat::getIndex(It->second));
- } else {
+ if (!IsEntityId) {
for (auto &[Key, Val] : Obj) {
if (auto Err = patchValue(Val, Table)) {
return Err;
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.h b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.h
index aa0c6ab61f658..aa826671e2706 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.h
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.h
@@ -34,10 +34,13 @@ class JSONEntitySummaryEncoding final : public EntitySummaryEncoding {
explicit JSONEntitySummaryEncoding(llvm::json::Value Data)
: Data(std::move(Data)) {}
- static llvm::Error patchObject(llvm::json::Object &Obj,
- const std::map<EntityId, EntityId> &Table);
- static llvm::Error patchValue(llvm::json::Value &V,
- const std::map<EntityId, EntityId> &Table);
+ llvm::Expected<bool>
+ patchEntityIdObject(llvm::json::Object &Obj,
+ const std::map<EntityId, EntityId> &Table);
+ llvm::Error patchObject(llvm::json::Object &Obj,
+ const std::map<EntityId, EntityId> &Table);
+ llvm::Error patchValue(llvm::json::Value &V,
+ const std::map<EntityId, EntityId> &Table);
llvm::json::Value Data;
};
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
index e4f9c3dc99452..dc5831d89a147 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
@@ -176,19 +176,6 @@ JSONFormat::entityIdFromJSONObject(const Object &EntityIdObject) {
return makeEntityId(static_cast<size_t>(*OptEntityIdIndex));
}
-Value *JSONFormat::entityIdReferenceFromJSONObject(Object &EntityIdObject) {
- if (EntityIdObject.size() != 1) {
- return nullptr;
- }
-
- llvm::json::Value *AtVal = EntityIdObject.get(JSONEntityIdKey);
- if (!AtVal) {
- return nullptr;
- }
-
- return AtVal;
-}
-
Object JSONFormat::entityIdToJSONObject(EntityId EI) {
Object Result;
Result[JSONEntityIdKey] = static_cast<uint64_t>(getIndex(EI));
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-multikey.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-multikey.json
new file mode 100644
index 0000000000000..ef05419fb2b0b
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-multikey.json
@@ -0,0 +1,32 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "invalid-entity-id-multikey.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "namespace": [{"kind": "CompilationUnit", "name": "invalid-entity-id-multikey.cpp"}],
+ "suffix": "",
+ "usr": "c:@F at foo#"
+ }
+ }
+ ],
+ "linkage_table": [
+ {"id": 0, "linkage": {"type": "External"}}
+ ],
+ "data": [
+ {
+ "summary_name": "TestAnalysis",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "ref": {"@": 0, "extra": "field"}
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-ref.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-ref.json
new file mode 100644
index 0000000000000..02b8315bd1679
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-ref.json
@@ -0,0 +1,32 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "invalid-entity-id-ref.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "namespace": [{"kind": "CompilationUnit", "name": "invalid-entity-id-ref.cpp"}],
+ "suffix": "",
+ "usr": "c:@F at foo#"
+ }
+ }
+ ],
+ "linkage_table": [
+ {"id": 0, "linkage": {"type": "External"}}
+ ],
+ "data": [
+ {
+ "summary_name": "TestAnalysis",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "ref": {"@": 99}
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-value.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-value.json
new file mode 100644
index 0000000000000..bf89df75671b3
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-value.json
@@ -0,0 +1,32 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "invalid-entity-id-value.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "namespace": [{"kind": "CompilationUnit", "name": "invalid-entity-id-value.cpp"}],
+ "suffix": "",
+ "usr": "c:@F at foo#"
+ }
+ }
+ ],
+ "linkage_table": [
+ {"id": 0, "linkage": {"type": "External"}}
+ ],
+ "data": [
+ {
+ "summary_name": "TestAnalysis",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "ref": {"@": "not-a-number"}
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-missing-fields.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-missing-fields.json
new file mode 100644
index 0000000000000..80256d6fd47a8
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-missing-fields.json
@@ -0,0 +1,6 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "missing-fields.cpp"
+ }
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/cli.test b/clang/test/Analysis/Scalable/ssaf-linker/cli.test
index 87302e78187b8..337843562e74b 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/cli.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/cli.test
@@ -5,7 +5,7 @@
// NO-ARGS: Must specify at least 1 positional argument: See: ssaf-linker --help
// RUN: not ssaf-linker %S/Inputs/tu-empty.json 2>&1 | FileCheck %s --check-prefix=NO-OUTPUT
-// NO-OUTPUT: ssaf-linker: for the --output option: must be specified at least once!
+// NO-OUTPUT: ssaf-linker: for the -o option: must be specified at least once!
// RUN: not ssaf-linker -o %t/output.json 2>&1 | FileCheck %s --check-prefix=NO-INPUT
// NO-INPUT: ssaf-linker: Not enough positional command line arguments specified!
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/help.test b/clang/test/Analysis/Scalable/ssaf-linker/help.test
index 900f2285c5d5d..d239fba48092a 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/help.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/help.test
@@ -13,7 +13,6 @@
// CHECK: --help-list - Display list of available options (--help-list-hidden for more)
// CHECK: --help-list-hidden - Display list of all available options
// CHECK: -o <path> - Output summary path
-// CHECK: --output -
// CHECK: --print-all-options - Print all option values after command line parsing
// CHECK: --print-options - Print non-default options after command line parsing
// CHECK: --time - Enable timing
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/io.test b/clang/test/Analysis/Scalable/ssaf-linker/io.test
index 386e64602c251..96ff839c362e7 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/io.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/io.test
@@ -1,26 +1,24 @@
// Tests for ssaf-linker file I/O error handling.
-// Input file is a directory.
-// RUN: not ssaf-linker %S/Inputs -o %t.json 2>&1 | FileCheck %s --check-prefix=INPUT-IS-DIR
-// INPUT-IS-DIR: error: Failed to load input summary
-// INPUT-IS-DIR: path is a directory, not a file
-
-// Input file has wrong extension — caught at validation before read.
-// (Extension check happens in SummaryFile::FromPath before real_path.)
-// RUN: not ssaf-linker %S/Inputs/empty.json %S/Inputs/malformed.json -o %t.json 2>&1
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
// Malformed JSON input.
-// RUN: not ssaf-linker %S/Inputs/malformed.json -o %t.json 2>&1 | FileCheck %s --check-prefix=BAD-JSON
-// BAD-JSON: error: Failed to load input summary
-// BAD-JSON: failed to read file
+// RUN: not ssaf-linker %S/Inputs/tu-malformed.json -o %t/out.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=BAD-JSON -DPATH=%S/Inputs/tu-malformed.json
+// BAD-JSON: ssaf-linker: error: reading TUSummary from file '[[PATH]]'
+// BAD-JSON: Invalid JSON value
// Missing required fields in otherwise valid JSON.
-// RUN: not ssaf-linker %S/Inputs/missing-fields.json -o %t.json 2>&1 | FileCheck %s --check-prefix=MISSING-FIELDS
-// MISSING-FIELDS: error: Failed to load input summary
-// MISSING-FIELDS: failed to read
+// RUN: not ssaf-linker %S/Inputs/tu-missing-fields.json -o %t/out.json 2>&1 \
+// | FileCheck %s --check-prefix=MISSING-FIELDS -DPATH=%S/Inputs/tu-missing-fields.json
+// MISSING-FIELDS: ssaf-linker: error: reading TUSummary from file '[[PATH]]'
+// MISSING-FIELDS: failed to read IdTable from field 'id_table': expected JSON array
// Output file already exists.
-// RUN: ssaf-linker %S/Inputs/empty.json -o %t-exists.json
-// RUN: not ssaf-linker %S/Inputs/empty.json -o %t-exists.json 2>&1 | FileCheck %s --check-prefix=OUTPUT-EXISTS
-// OUTPUT-EXISTS: error: Failed to write output summary
-// OUTPUT-EXISTS: file already exists
+// RUN: touch %t/out.json
+// RUN: ssaf-linker %S/Inputs/tu-empty.json -o %t/out.json
+// RUN: not ssaf-linker %S/Inputs/tu-empty.json -o %t/out.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=OUTPUT-EXISTS -DPATH=%t/out.json
+// OUTPUT-EXISTS: ssaf-linker: error: writing LUSummary to file '[[PATH]]'
+// OUTPUT-EXISTS: failed to write file '[[PATH]]': file already exists
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test b/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test
index 19b08936cef3d..13b9f471df7f8 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test
@@ -6,4 +6,23 @@
// Linking the same TU namespace twice produces an error.
// RUN: not ssaf-linker %S/Inputs/tu-empty.json %S/Inputs/tu-empty.json -o %t/lu.json 2>&1 \
// RUN: | FileCheck %s --check-prefix=DUP-NS -DPATH=%S/Inputs/tu-empty.json
-// DUP-NS: ssaf-linker: error: Failed to link input summary '[[PATH]]': failed to link TU summary: duplicate BuildNamespace(CompilationUnit, empty.cpp).
+// DUP-NS: ssaf-linker: error: Linking summary '[[PATH]]'
+// DUP-NS: failed to link TU summary: duplicate BuildNamespace(CompilationUnit, empty.cpp)
+
+// Entity ID object in summary data blob with '@' key alongside extra keys is a fatal error.
+// RUN: not ssaf-linker %S/Inputs/tu-invalid-entity-id-multikey.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=INVALID-ID-MULTIKEY -DPATH=%S/Inputs/tu-invalid-entity-id-multikey.json
+// INVALID-ID-MULTIKEY: ssaf-linker: error: Linking summary '[[PATH]]'
+// INVALID-ID-MULTIKEY: failed to read EntityId: expected JSON object with a single '@' key mapped to a number (unsigned 64-bit integer)
+
+// Entity ID object in summary data blob with a non-uint64 '@' value is a fatal error.
+// RUN: not ssaf-linker %S/Inputs/tu-invalid-entity-id-value.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=INVALID-ID-VALUE -DPATH=%S/Inputs/tu-invalid-entity-id-value.json
+// INVALID-ID-VALUE: ssaf-linker: error: Linking summary '[[PATH]]'
+// INVALID-ID-VALUE: failed to read EntityId: expected JSON object with a single '@' key mapped to a number (unsigned 64-bit integer)
+
+// Entity ID reference in summary data blob pointing to an ID absent from the resolution table
+// RUN: not ssaf-linker %S/Inputs/tu-invalid-entity-id-ref.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=INVALID-ID-REF -DPATH=%S/Inputs/tu-invalid-entity-id-ref.json
+// INVALID-ID-REF: ssaf-linker: error: Linking summary '[[PATH]]'
+// INVALID-ID-REF: failed to patch EntityId: 'EntityId(99)' not found in entity resolution table
\ No newline at end of file
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test b/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
index 2a8476f8be48b..50709846aebe9 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
@@ -1,5 +1,6 @@
-// Tests for EntityLinker error cases.
+// Tests for ssaf-linker input validation.
+// RUN: rm -rf %t
// RUN: mkdir -p %t
// No extension on output.
diff --git a/clang/tools/ssaf-linker/SSAFLinker.cpp b/clang/tools/ssaf-linker/SSAFLinker.cpp
index d3485508c1f74..2be482859a3b1 100644
--- a/clang/tools/ssaf-linker/SSAFLinker.cpp
+++ b/clang/tools/ssaf-linker/SSAFLinker.cpp
@@ -16,6 +16,7 @@
#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
#include "clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h"
+#include "clang/Analysis/Scalable/Support/ErrorBuilder.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/CommandLine.h"
@@ -29,6 +30,7 @@
#include "llvm/Support/raw_ostream.h"
#include <memory>
#include <string>
+#include <system_error>
using namespace llvm;
using namespace clang::ssaf;
@@ -51,8 +53,6 @@ cl::opt<std::string> OutputPath("o", cl::desc("Output summary path"),
cl::value_desc("path"), cl::Required,
cl::cat(SsafLinkerCategory));
-cl::alias OutputPathLong("output", cl::aliasopt(OutputPath));
-
cl::opt<bool> Verbose("verbose", cl::desc("Enable verbose output"),
cl::init(false), cl::cat(SsafLinkerCategory));
@@ -63,28 +63,25 @@ cl::opt<bool> Time("time", cl::desc("Enable timing"), cl::init(false),
// Error Messages
//===----------------------------------------------------------------------===//
-constexpr const char *ErrorCannotValidateSummary =
+namespace ErrorMessages {
+
+constexpr const char *CannotValidateSummary =
"failed to validate summary '{0}': {1}.";
-constexpr const char *ErrorOutputDirectoryMissing =
+constexpr const char *OutputDirectoryMissing =
"Parent directory does not exist.";
-constexpr const char *ErrorOutputDirectoryNotWritable =
+constexpr const char *OutputDirectoryNotWritable =
"Parent directory is not writable.";
-constexpr const char *ErrorExtensionNotSupplied = "Extension not supplied.";
+constexpr const char *ExtensionNotSupplied = "Extension not supplied.";
-constexpr const char *ErrorNoFormatForExtension =
+constexpr const char *NoFormatForExtension =
"Format not registered for extension '{0}'.";
-constexpr const char *ErrorLoadFailed =
- "failed to load input summary '{0}': {1}.";
-
-constexpr const char *ErrorLinkFailed =
- "failed to link input summary '{0}': {1}.";
+constexpr const char *LinkingSummary = "Linking summary '{0}'";
-constexpr const char *ErrorWriteFailed =
- "failed to write output summary '{0}': {1}.";
+} // namespace ErrorMessages
//===----------------------------------------------------------------------===//
// Diagnostic Utilities
@@ -92,15 +89,22 @@ constexpr const char *ErrorWriteFailed =
constexpr unsigned IndentationWidth = 2;
-static llvm::StringRef ToolName;
+llvm::StringRef ToolName;
-static void PrintVersion(llvm::raw_ostream &OS) { OS << "ssaf-linker 0.1\n"; }
+template <typename... Ts> [[noreturn]] void Fail(const char *Msg) {
+ llvm::WithColor::error(llvm::errs(), ToolName) << Msg << "\n";
+ llvm::sys::Process::Exit(1);
+}
template <typename... Ts>
[[noreturn]] void Fail(const char *Fmt, Ts &&...Args) {
- llvm::WithColor::error(llvm::errs(), ToolName)
- << llvm::formatv(Fmt, std::forward<Ts>(Args)...) << "\n";
- llvm::sys::Process::Exit(1);
+ std::string Message = llvm::formatv(Fmt, std::forward<Ts>(Args)...);
+ Fail(Message.data());
+}
+
+template <typename... Ts> [[noreturn]] void Fail(llvm::Error Err) {
+ std::string Message = toString(std::move(Err));
+ Fail(Message.data());
}
template <typename... Ts>
@@ -157,13 +161,15 @@ struct SummaryFile {
static SummaryFile FromPath(llvm::StringRef Path) {
llvm::StringRef Extension = path::extension(Path);
if (Extension.empty()) {
- Fail(ErrorCannotValidateSummary, Path, ErrorExtensionNotSupplied);
+ Fail(ErrorMessages::CannotValidateSummary, Path,
+ ErrorMessages::ExtensionNotSupplied);
}
Extension = Extension.drop_front();
SerializationFormat *Format = GetFormatForExtension(Extension);
if (!Format) {
- std::string Suffix = llvm::formatv(ErrorNoFormatForExtension, Extension);
- Fail(ErrorCannotValidateSummary, Path, Suffix);
+ std::string BadExtension =
+ llvm::formatv(ErrorMessages::NoFormatForExtension, Extension);
+ Fail(ErrorMessages::CannotValidateSummary, Path, BadExtension);
}
return {Path.str(), Format};
}
@@ -175,6 +181,8 @@ struct LinkerInput {
std::string LinkUnitName;
};
+static void PrintVersion(llvm::raw_ostream &OS) { OS << ToolName << " 0.1\n"; }
+
//===----------------------------------------------------------------------===//
// Pipeline
//===----------------------------------------------------------------------===//
@@ -189,12 +197,13 @@ LinkerInput Validate(llvm::TimerGroup &TG) {
llvm::StringRef DirToCheck = ParentDir.empty() ? "." : ParentDir;
if (!fs::exists(DirToCheck)) {
- Fail(ErrorCannotValidateSummary, OutputPath, ErrorOutputDirectoryMissing);
+ Fail(ErrorMessages::CannotValidateSummary, OutputPath,
+ ErrorMessages::OutputDirectoryMissing);
}
if (fs::access(DirToCheck, fs::AccessMode::Write)) {
- Fail(ErrorCannotValidateSummary, OutputPath,
- ErrorOutputDirectoryNotWritable);
+ Fail(ErrorMessages::CannotValidateSummary, OutputPath,
+ ErrorMessages::OutputDirectoryNotWritable);
}
LI.OutputFile = SummaryFile::FromPath(OutputPath);
@@ -209,7 +218,7 @@ LinkerInput Validate(llvm::TimerGroup &TG) {
llvm::SmallString<256> RealPath;
std::error_code EC = fs::real_path(InputPath, RealPath, true);
if (EC) {
- Fail(ErrorCannotValidateSummary, InputPath, EC.message());
+ Fail(ErrorMessages::CannotValidateSummary, InputPath, EC.message());
}
LI.InputFiles.push_back(SummaryFile::FromPath(RealPath));
}
@@ -244,8 +253,7 @@ void Link(const LinkerInput &LI, llvm::TimerGroup &TG) {
auto ExpectedSummaryEncoding =
InputFile.Format->readTUSummaryEncoding(InputFile.Path);
if (!ExpectedSummaryEncoding) {
- Fail(ErrorLoadFailed, InputFile.Path,
- toString(ExpectedSummaryEncoding.takeError()));
+ Fail(ExpectedSummaryEncoding.takeError());
}
Summary = std::make_unique<TUSummaryEncoding>(
@@ -259,7 +267,9 @@ void Link(const LinkerInput &LI, llvm::TimerGroup &TG) {
llvm::TimeRegion _(Time ? &TLink : nullptr);
if (auto Err = EL.link(std::move(Summary))) {
- Fail(ErrorLinkFailed, InputFile.Path, toString(std::move(Err)));
+ Fail(ErrorBuilder::wrap(std::move(Err))
+ .context(ErrorMessages::LinkingSummary, InputFile.Path)
+ .build());
}
}
}
@@ -272,7 +282,7 @@ void Link(const LinkerInput &LI, llvm::TimerGroup &TG) {
auto Output = std::move(EL).getOutput();
if (auto Err = LI.OutputFile.Format->writeLUSummaryEncoding(
Output, LI.OutputFile.Path)) {
- Fail(ErrorWriteFailed, LI.OutputFile.Path, toString(std::move(Err)));
+ Fail(std::move(Err));
}
}
}
>From 15d5ac9de83d4bd485900ef0164b4ccabd902bd9 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 5 Mar 2026 15:44:58 -0800
Subject: [PATCH 05/13] LIT needs to know about the tool
---
clang/test/lit.cfg.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/test/lit.cfg.py b/clang/test/lit.cfg.py
index 6796c64fc4778..68f460fe5178f 100644
--- a/clang/test/lit.cfg.py
+++ b/clang/test/lit.cfg.py
@@ -123,6 +123,7 @@
command=FindTool("clang-extdef-mapping"),
unresolved="ignore",
),
+ "ssaf-linker",
]
if config.clang_examples:
>From 733e7c83b5070ae1266f2204def2902973facc40 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 5 Mar 2026 15:54:06 -0800
Subject: [PATCH 06/13] Remove unreferenced file
---
.../ssaf-linker/Inputs/tu-dup-id.json | 28 -----------------
.../ssaf-linker/Inputs/tu-dup-namespace.json | 9 ------
.../ssaf-linker/Inputs/tu-orphan-data.json | 30 -------------------
3 files changed, 67 deletions(-)
delete mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-dup-id.json
delete mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-dup-namespace.json
delete mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-orphan-data.json
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-dup-id.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-dup-id.json
deleted file mode 100644
index 85e884b96590b..0000000000000
--- a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-dup-id.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "tu_namespace": {
- "kind": "CompilationUnit",
- "name": "tu-dup-id.cpp"
- },
- "id_table": [
- {
- "id": 0,
- "name": {
- "namespace": [{ "kind": "CompilationUnit", "name": "tu-dup-id.cpp" }],
- "suffix": "",
- "usr": "c:@F at foo#"
- }
- },
- {
- "id": 0,
- "name": {
- "namespace": [{ "kind": "CompilationUnit", "name": "tu-dup-id.cpp" }],
- "suffix": "",
- "usr": "c:@F at bar#"
- }
- }
- ],
- "linkage_table": [
- { "id": 0, "linkage": { "type": "External" } }
- ],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-dup-namespace.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-dup-namespace.json
deleted file mode 100644
index 4550e814bceab..0000000000000
--- a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-dup-namespace.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "tu_namespace": {
- "kind": "CompilationUnit",
- "name": "tu1.cpp"
- },
- "id_table": [],
- "linkage_table": [],
- "data": []
-}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-orphan-data.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-orphan-data.json
deleted file mode 100644
index f0aa5507ec19a..0000000000000
--- a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-orphan-data.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "tu_namespace": {
- "kind": "CompilationUnit",
- "name": "tu-orphan-data.cpp"
- },
- "id_table": [
- {
- "id": 0,
- "name": {
- "namespace": [{ "kind": "CompilationUnit", "name": "tu-orphan-data.cpp" }],
- "suffix": "",
- "usr": "c:@F at foo#"
- }
- }
- ],
- "linkage_table": [
- { "id": 0, "linkage": { "type": "External" } }
- ],
- "data": [
- {
- "summary_name": "TestAnalysis",
- "summary_data": [
- {
- "entity_id": 99,
- "entity_summary": {}
- }
- ]
- }
- ]
-}
>From 34c4f125ea10ec1cca0daf3b23ca8c61fffb1cc4 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 5 Mar 2026 16:16:11 -0800
Subject: [PATCH 07/13] More fix
---
clang/test/Analysis/Scalable/ssaf-linker/cli.test | 5 +++--
clang/tools/ssaf-linker/SSAFLinker.cpp | 6 +++++-
2 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/cli.test b/clang/test/Analysis/Scalable/ssaf-linker/cli.test
index 337843562e74b..2ba0edae63fe0 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/cli.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/cli.test
@@ -2,12 +2,13 @@
// RUN: not ssaf-linker 2>&1 | FileCheck %s --check-prefix=NO-ARGS
// NO-ARGS: ssaf-linker: Not enough positional command line arguments specified!
-// NO-ARGS: Must specify at least 1 positional argument: See: ssaf-linker --help
+// NO-ARGS: Must specify at least 1 positional argument: See: {{.*}}/ssaf-linker --help
+// NO-ARGS: ssaf-linker: for the -o option: must be specified at least once!
// RUN: not ssaf-linker %S/Inputs/tu-empty.json 2>&1 | FileCheck %s --check-prefix=NO-OUTPUT
// NO-OUTPUT: ssaf-linker: for the -o option: must be specified at least once!
// RUN: not ssaf-linker -o %t/output.json 2>&1 | FileCheck %s --check-prefix=NO-INPUT
// NO-INPUT: ssaf-linker: Not enough positional command line arguments specified!
-// NO-INPUT: Must specify at least 1 positional argument: See: ssaf-linker --help
+// NO-INPUT: Must specify at least 1 positional argument: See: {{.*}}/ssaf-linker --help
diff --git a/clang/tools/ssaf-linker/SSAFLinker.cpp b/clang/tools/ssaf-linker/SSAFLinker.cpp
index 2be482859a3b1..318a9a5d99795 100644
--- a/clang/tools/ssaf-linker/SSAFLinker.cpp
+++ b/clang/tools/ssaf-linker/SSAFLinker.cpp
@@ -296,12 +296,16 @@ void Link(const LinkerInput &LI, llvm::TimerGroup &TG) {
int main(int argc, const char **argv) {
InitLLVM X(argc, argv);
ToolName = llvm::sys::path::filename(argv[0]);
- initializeJSONFormat();
+ // Hide options unrelated to ssaf-linker from --help output.
cl::HideUnrelatedOptions(SsafLinkerCategory);
+ // Register a custom version printer for the --version flag.
cl::SetVersionPrinter(PrintVersion);
+ // Parse command-line arguments and exit with an error if they are invalid.
cl::ParseCommandLineOptions(argc, argv, "SSAF Linker\n");
+ initializeJSONFormat();
+
llvm::TimerGroup LinkerTimers("ssaf-linker", "SSAF Linker");
LinkerInput LI;
>From 8174c5135ae7321ededb98dbb0f41082e5977f0d Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 5 Mar 2026 16:16:24 -0800
Subject: [PATCH 08/13] Format
---
.../Scalable/ssaf-linker/Inputs/tu-1.json | 257 ++++++++++++++---
.../Scalable/ssaf-linker/Inputs/tu-2.json | 264 +++++++++++++++---
.../Inputs/tu-invalid-entity-id-multikey.json | 19 +-
.../Inputs/tu-invalid-entity-id-ref.json | 18 +-
.../Inputs/tu-invalid-entity-id-value.json | 18 +-
5 files changed, 496 insertions(+), 80 deletions(-)
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-1.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-1.json
index b67f9aad690c3..0936ae654069c 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-1.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-1.json
@@ -7,7 +7,12 @@
{
"id": 0,
"name": {
- "namespace": [{ "kind": "CompilationUnit", "name": "tu1.cpp" }],
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu1.cpp"
+ }
+ ],
"suffix": "",
"usr": "c:@F at shared_ext#"
}
@@ -15,7 +20,12 @@
{
"id": 1,
"name": {
- "namespace": [{ "kind": "CompilationUnit", "name": "tu1.cpp" }],
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu1.cpp"
+ }
+ ],
"suffix": "",
"usr": "c:@F at shared_int#"
}
@@ -23,7 +33,12 @@
{
"id": 2,
"name": {
- "namespace": [{ "kind": "CompilationUnit", "name": "tu1.cpp" }],
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu1.cpp"
+ }
+ ],
"suffix": "",
"usr": "c:@F at shared_none#"
}
@@ -31,7 +46,12 @@
{
"id": 3,
"name": {
- "namespace": [{ "kind": "CompilationUnit", "name": "tu1.cpp" }],
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu1.cpp"
+ }
+ ],
"suffix": "",
"usr": "c:@F at unique_ext_tu1#"
}
@@ -39,7 +59,12 @@
{
"id": 4,
"name": {
- "namespace": [{ "kind": "CompilationUnit", "name": "tu1.cpp" }],
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu1.cpp"
+ }
+ ],
"suffix": "",
"usr": "c:@F at unique_int_tu1#"
}
@@ -47,19 +72,54 @@
{
"id": 5,
"name": {
- "namespace": [{ "kind": "CompilationUnit", "name": "tu1.cpp" }],
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu1.cpp"
+ }
+ ],
"suffix": "",
"usr": "c:@F at unique_none_tu1#"
}
}
],
"linkage_table": [
- { "id": 0, "linkage": { "type": "External" } },
- { "id": 1, "linkage": { "type": "Internal" } },
- { "id": 2, "linkage": { "type": "None" } },
- { "id": 3, "linkage": { "type": "External" } },
- { "id": 4, "linkage": { "type": "Internal" } },
- { "id": 5, "linkage": { "type": "None" } }
+ {
+ "id": 0,
+ "linkage": {
+ "type": "External"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "type": "Internal"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "type": "None"
+ }
+ },
+ {
+ "id": 3,
+ "linkage": {
+ "type": "External"
+ }
+ },
+ {
+ "id": 4,
+ "linkage": {
+ "type": "Internal"
+ }
+ },
+ {
+ "id": 5,
+ "linkage": {
+ "type": "None"
+ }
+ }
],
"data": [
{
@@ -67,27 +127,87 @@
"summary_data": [
{
"entity_id": 0,
- "entity_summary": { "call_count": 3, "callees": [{ "@": 1 }, { "@": 2 }, { "@": 3 }] }
+ "entity_summary": {
+ "call_count": 3,
+ "callees": [
+ {
+ "@": 1
+ },
+ {
+ "@": 2
+ },
+ {
+ "@": 3
+ }
+ ]
+ }
},
{
"entity_id": 1,
- "entity_summary": { "call_count": 2, "callees": [{ "@": 0 }, { "@": 4 }] }
+ "entity_summary": {
+ "call_count": 2,
+ "callees": [
+ {
+ "@": 0
+ },
+ {
+ "@": 4
+ }
+ ]
+ }
},
{
"entity_id": 2,
- "entity_summary": { "call_count": 1, "callees": [{ "@": 5 }] }
+ "entity_summary": {
+ "call_count": 1,
+ "callees": [
+ {
+ "@": 5
+ }
+ ]
+ }
},
{
"entity_id": 3,
- "entity_summary": { "call_count": 2, "callees": [{ "@": 0 }, { "@": 1 }] }
+ "entity_summary": {
+ "call_count": 2,
+ "callees": [
+ {
+ "@": 0
+ },
+ {
+ "@": 1
+ }
+ ]
+ }
},
{
"entity_id": 4,
- "entity_summary": { "call_count": 1, "callees": [{ "@": 3 }] }
+ "entity_summary": {
+ "call_count": 1,
+ "callees": [
+ {
+ "@": 3
+ }
+ ]
+ }
},
{
"entity_id": 5,
- "entity_summary": { "call_count": 3, "callees": [{ "@": 0 }, { "@": 3 }, { "@": 4 }] }
+ "entity_summary": {
+ "call_count": 3,
+ "callees": [
+ {
+ "@": 0
+ },
+ {
+ "@": 3
+ },
+ {
+ "@": 4
+ }
+ ]
+ }
}
]
},
@@ -97,59 +217,126 @@
{
"entity_id": 0,
"entity_summary": {
- "direct": { "@": 3 },
+ "direct": {
+ "@": 3
+ },
"indirect": [
- { "entity": { "@": 1 }, "level": 1 },
- { "entity": { "@": 4 }, "level": 2 }
+ {
+ "entity": {
+ "@": 1
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 4
+ },
+ "level": 2
+ }
]
}
},
{
"entity_id": 1,
"entity_summary": {
- "direct": { "@": 0 },
+ "direct": {
+ "@": 0
+ },
"indirect": [
- { "entity": { "@": 2 }, "level": 1 },
- { "entity": { "@": 5 }, "level": 2 }
+ {
+ "entity": {
+ "@": 2
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 5
+ },
+ "level": 2
+ }
]
}
},
{
"entity_id": 2,
"entity_summary": {
- "direct": { "@": 1 },
+ "direct": {
+ "@": 1
+ },
"indirect": [
- { "entity": { "@": 3 }, "level": 1 }
+ {
+ "entity": {
+ "@": 3
+ },
+ "level": 1
+ }
]
}
},
{
"entity_id": 3,
"entity_summary": {
- "direct": { "@": 4 },
+ "direct": {
+ "@": 4
+ },
"indirect": [
- { "entity": { "@": 0 }, "level": 1 },
- { "entity": { "@": 2 }, "level": 2 }
+ {
+ "entity": {
+ "@": 0
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 2
+ },
+ "level": 2
+ }
]
}
},
{
"entity_id": 4,
"entity_summary": {
- "direct": { "@": 5 },
+ "direct": {
+ "@": 5
+ },
"indirect": [
- { "entity": { "@": 1 }, "level": 1 }
+ {
+ "entity": {
+ "@": 1
+ },
+ "level": 1
+ }
]
}
},
{
"entity_id": 5,
"entity_summary": {
- "direct": { "@": 2 },
+ "direct": {
+ "@": 2
+ },
"indirect": [
- { "entity": { "@": 0 }, "level": 1 },
- { "entity": { "@": 3 }, "level": 2 },
- { "entity": { "@": 4 }, "level": 3 }
+ {
+ "entity": {
+ "@": 0
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 3
+ },
+ "level": 2
+ },
+ {
+ "entity": {
+ "@": 4
+ },
+ "level": 3
+ }
]
}
}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-2.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-2.json
index ac6e385e6b49d..2dedd002c9536 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-2.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-2.json
@@ -7,7 +7,12 @@
{
"id": 0,
"name": {
- "namespace": [{ "kind": "CompilationUnit", "name": "tu2.cpp" }],
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu2.cpp"
+ }
+ ],
"suffix": "",
"usr": "c:@F at shared_ext#"
}
@@ -15,7 +20,12 @@
{
"id": 1,
"name": {
- "namespace": [{ "kind": "CompilationUnit", "name": "tu2.cpp" }],
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu2.cpp"
+ }
+ ],
"suffix": "",
"usr": "c:@F at shared_int#"
}
@@ -23,7 +33,12 @@
{
"id": 2,
"name": {
- "namespace": [{ "kind": "CompilationUnit", "name": "tu2.cpp" }],
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu2.cpp"
+ }
+ ],
"suffix": "",
"usr": "c:@F at shared_none#"
}
@@ -31,7 +46,12 @@
{
"id": 3,
"name": {
- "namespace": [{ "kind": "CompilationUnit", "name": "tu2.cpp" }],
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu2.cpp"
+ }
+ ],
"suffix": "",
"usr": "c:@F at unique_ext_tu2#"
}
@@ -39,7 +59,12 @@
{
"id": 4,
"name": {
- "namespace": [{ "kind": "CompilationUnit", "name": "tu2.cpp" }],
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu2.cpp"
+ }
+ ],
"suffix": "",
"usr": "c:@F at unique_int_tu2#"
}
@@ -47,19 +72,54 @@
{
"id": 5,
"name": {
- "namespace": [{ "kind": "CompilationUnit", "name": "tu2.cpp" }],
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "tu2.cpp"
+ }
+ ],
"suffix": "",
"usr": "c:@F at unique_none_tu2#"
}
}
],
"linkage_table": [
- { "id": 0, "linkage": { "type": "External" } },
- { "id": 1, "linkage": { "type": "Internal" } },
- { "id": 2, "linkage": { "type": "None" } },
- { "id": 3, "linkage": { "type": "External" } },
- { "id": 4, "linkage": { "type": "Internal" } },
- { "id": 5, "linkage": { "type": "None" } }
+ {
+ "id": 0,
+ "linkage": {
+ "type": "External"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "type": "Internal"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "type": "None"
+ }
+ },
+ {
+ "id": 3,
+ "linkage": {
+ "type": "External"
+ }
+ },
+ {
+ "id": 4,
+ "linkage": {
+ "type": "Internal"
+ }
+ },
+ {
+ "id": 5,
+ "linkage": {
+ "type": "None"
+ }
+ }
],
"data": [
{
@@ -67,27 +127,87 @@
"summary_data": [
{
"entity_id": 0,
- "entity_summary": { "call_count": 3, "callees": [{ "@": 1 }, { "@": 2 }, { "@": 5 }] }
+ "entity_summary": {
+ "call_count": 3,
+ "callees": [
+ {
+ "@": 1
+ },
+ {
+ "@": 2
+ },
+ {
+ "@": 5
+ }
+ ]
+ }
},
{
"entity_id": 1,
- "entity_summary": { "call_count": 2, "callees": [{ "@": 0 }, { "@": 3 }] }
+ "entity_summary": {
+ "call_count": 2,
+ "callees": [
+ {
+ "@": 0
+ },
+ {
+ "@": 3
+ }
+ ]
+ }
},
{
"entity_id": 2,
- "entity_summary": { "call_count": 1, "callees": [{ "@": 4 }] }
+ "entity_summary": {
+ "call_count": 1,
+ "callees": [
+ {
+ "@": 4
+ }
+ ]
+ }
},
{
"entity_id": 3,
- "entity_summary": { "call_count": 2, "callees": [{ "@": 0 }, { "@": 2 }] }
+ "entity_summary": {
+ "call_count": 2,
+ "callees": [
+ {
+ "@": 0
+ },
+ {
+ "@": 2
+ }
+ ]
+ }
},
{
"entity_id": 4,
- "entity_summary": { "call_count": 1, "callees": [{ "@": 3 }] }
+ "entity_summary": {
+ "call_count": 1,
+ "callees": [
+ {
+ "@": 3
+ }
+ ]
+ }
},
{
"entity_id": 5,
- "entity_summary": { "call_count": 3, "callees": [{ "@": 1 }, { "@": 2 }, { "@": 3 }] }
+ "entity_summary": {
+ "call_count": 3,
+ "callees": [
+ {
+ "@": 1
+ },
+ {
+ "@": 2
+ },
+ {
+ "@": 3
+ }
+ ]
+ }
}
]
},
@@ -97,60 +217,132 @@
{
"entity_id": 0,
"entity_summary": {
- "direct": { "@": 3 },
+ "direct": {
+ "@": 3
+ },
"indirect": [
- { "entity": { "@": 2 }, "level": 1 },
- { "entity": { "@": 5 }, "level": 2 }
+ {
+ "entity": {
+ "@": 2
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 5
+ },
+ "level": 2
+ }
]
}
},
{
"entity_id": 1,
"entity_summary": {
- "direct": { "@": 0 },
+ "direct": {
+ "@": 0
+ },
"indirect": [
- { "entity": { "@": 4 }, "level": 1 }
+ {
+ "entity": {
+ "@": 4
+ },
+ "level": 1
+ }
]
}
},
{
"entity_id": 2,
"entity_summary": {
- "direct": { "@": 5 },
+ "direct": {
+ "@": 5
+ },
"indirect": [
- { "entity": { "@": 0 }, "level": 1 },
- { "entity": { "@": 3 }, "level": 2 }
+ {
+ "entity": {
+ "@": 0
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 3
+ },
+ "level": 2
+ }
]
}
},
{
"entity_id": 3,
"entity_summary": {
- "direct": { "@": 1 },
+ "direct": {
+ "@": 1
+ },
"indirect": [
- { "entity": { "@": 2 }, "level": 1 },
- { "entity": { "@": 0 }, "level": 2 }
+ {
+ "entity": {
+ "@": 2
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 0
+ },
+ "level": 2
+ }
]
}
},
{
"entity_id": 4,
"entity_summary": {
- "direct": { "@": 2 },
+ "direct": {
+ "@": 2
+ },
"indirect": [
- { "entity": { "@": 5 }, "level": 1 },
- { "entity": { "@": 3 }, "level": 2 }
+ {
+ "entity": {
+ "@": 5
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 3
+ },
+ "level": 2
+ }
]
}
},
{
"entity_id": 5,
"entity_summary": {
- "direct": { "@": 4 },
+ "direct": {
+ "@": 4
+ },
"indirect": [
- { "entity": { "@": 1 }, "level": 1 },
- { "entity": { "@": 2 }, "level": 2 },
- { "entity": { "@": 0 }, "level": 3 }
+ {
+ "entity": {
+ "@": 1
+ },
+ "level": 1
+ },
+ {
+ "entity": {
+ "@": 2
+ },
+ "level": 2
+ },
+ {
+ "entity": {
+ "@": 0
+ },
+ "level": 3
+ }
]
}
}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-multikey.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-multikey.json
index ef05419fb2b0b..a975213c4e61e 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-multikey.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-multikey.json
@@ -7,14 +7,24 @@
{
"id": 0,
"name": {
- "namespace": [{"kind": "CompilationUnit", "name": "invalid-entity-id-multikey.cpp"}],
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "invalid-entity-id-multikey.cpp"
+ }
+ ],
"suffix": "",
"usr": "c:@F at foo#"
}
}
],
"linkage_table": [
- {"id": 0, "linkage": {"type": "External"}}
+ {
+ "id": 0,
+ "linkage": {
+ "type": "External"
+ }
+ }
],
"data": [
{
@@ -23,7 +33,10 @@
{
"entity_id": 0,
"entity_summary": {
- "ref": {"@": 0, "extra": "field"}
+ "ref": {
+ "@": 0,
+ "extra": "field"
+ }
}
}
]
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-ref.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-ref.json
index 02b8315bd1679..089a38dc4e7d9 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-ref.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-ref.json
@@ -7,14 +7,24 @@
{
"id": 0,
"name": {
- "namespace": [{"kind": "CompilationUnit", "name": "invalid-entity-id-ref.cpp"}],
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "invalid-entity-id-ref.cpp"
+ }
+ ],
"suffix": "",
"usr": "c:@F at foo#"
}
}
],
"linkage_table": [
- {"id": 0, "linkage": {"type": "External"}}
+ {
+ "id": 0,
+ "linkage": {
+ "type": "External"
+ }
+ }
],
"data": [
{
@@ -23,7 +33,9 @@
{
"entity_id": 0,
"entity_summary": {
- "ref": {"@": 99}
+ "ref": {
+ "@": 99
+ }
}
}
]
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-value.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-value.json
index bf89df75671b3..eedf0aa35b953 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-value.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-value.json
@@ -7,14 +7,24 @@
{
"id": 0,
"name": {
- "namespace": [{"kind": "CompilationUnit", "name": "invalid-entity-id-value.cpp"}],
+ "namespace": [
+ {
+ "kind": "CompilationUnit",
+ "name": "invalid-entity-id-value.cpp"
+ }
+ ],
"suffix": "",
"usr": "c:@F at foo#"
}
}
],
"linkage_table": [
- {"id": 0, "linkage": {"type": "External"}}
+ {
+ "id": 0,
+ "linkage": {
+ "type": "External"
+ }
+ }
],
"data": [
{
@@ -23,7 +33,9 @@
{
"entity_id": 0,
"entity_summary": {
- "ref": {"@": "not-a-number"}
+ "ref": {
+ "@": "not-a-number"
+ }
}
}
]
>From f14d2dfe782b0bbbc62ff9a6deae103c9c0dfe2a Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 5 Mar 2026 18:09:11 -0800
Subject: [PATCH 09/13] More
---
clang/test/CMakeLists.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/test/CMakeLists.txt b/clang/test/CMakeLists.txt
index bee19fd375d3e..24136256883f3 100644
--- a/clang/test/CMakeLists.txt
+++ b/clang/test/CMakeLists.txt
@@ -106,6 +106,7 @@ list(APPEND CLANG_TEST_DEPS
clang-sycl-linker
diagtool
hmaptool
+ ssaf-linker
)
if(CLANG_ENABLE_CIR)
>From f93bc8a4e2bf65199ca848c9a5792b3ab0ca3935 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 6 Mar 2026 13:46:53 -0800
Subject: [PATCH 10/13] Balazs
---
.../Scalable/EntityLinker/EntityLinker.cpp | 6 +-
.../JSONFormat/JSONEntitySummaryEncoding.cpp | 40 ++++-----
.../JSONFormat/JSONEntitySummaryEncoding.h | 8 +-
.../JSONFormat/JSONFormatImpl.cpp | 8 +-
.../Scalable/ssaf-linker/Inputs/tu-1.json | 4 +-
.../Scalable/ssaf-linker/Inputs/tu-2.json | 4 +-
.../Inputs/tu-invalid-entity-id-multikey.json | 2 +-
.../Inputs/tu-invalid-entity-id-ref.json | 2 +-
.../Inputs/tu-invalid-entity-id-value.json | 2 +-
.../Scalable/ssaf-linker/Outputs/lu-1+2.json | 4 +-
.../Scalable/ssaf-linker/Outputs/lu-1.json | 4 +-
.../Scalable/ssaf-linker/Outputs/lu-2.json | 4 +-
.../Analysis/Scalable/ssaf-linker/cli.test | 19 ++--
.../Analysis/Scalable/ssaf-linker/help.test | 31 +++----
.../Analysis/Scalable/ssaf-linker/io.test | 19 ++--
.../Scalable/ssaf-linker/linking-errors.test | 24 +++---
.../Analysis/Scalable/ssaf-linker/time.test | 22 ++---
.../ssaf-linker/validation-errors.test | 33 ++++---
.../Scalable/ssaf-linker/verbose.test | 28 +++---
.../Scalable/ssaf-linker/version.test | 2 +-
clang/tools/ssaf-linker/SSAFLinker.cpp | 86 +++++++++----------
21 files changed, 171 insertions(+), 181 deletions(-)
diff --git a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
index 2cb23718d0fe8..25cad1105e303 100644
--- a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
+++ b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
@@ -187,9 +187,5 @@ llvm::Error EntityLinker::link(std::unique_ptr<TUSummaryEncoding> Summary) {
auto EntityResolutionTable = resolve(SummaryRef);
auto PatchTargets = merge(SummaryRef, EntityResolutionTable);
- if (auto Err = patch(PatchTargets, EntityResolutionTable)) {
- return Err;
- }
-
- return llvm::Error::success();
+ return patch(PatchTargets, EntityResolutionTable);
}
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.cpp
index 6cab6885db051..35c45ac108e07 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.cpp
@@ -11,13 +11,9 @@
namespace clang::ssaf {
-llvm::Expected<bool> JSONEntitySummaryEncoding::patchEntityIdObject(
- llvm::json::Object &Obj, const std::map<EntityId, EntityId> &Table) {
-
- llvm::json::Value *AtVal = Obj.get(JSONEntityIdKey);
- if (!AtVal) {
- return false;
- }
+llvm::Error JSONEntitySummaryEncoding::patchEntityIdObject(
+ llvm::json::Object &Obj, const std::map<EntityId, EntityId> &Table,
+ llvm::json::Value *AtVal) {
if (Obj.size() != 1) {
return ErrorBuilder::create(std::errc::invalid_argument,
@@ -45,31 +41,27 @@ llvm::Expected<bool> JSONEntitySummaryEncoding::patchEntityIdObject(
*AtVal = static_cast<uint64_t>(JSONFormat::getIndex(It->second));
- return true;
+ return llvm::Error::success();
}
-llvm::Error JSONEntitySummaryEncoding::patchObject(
+llvm::Error JSONEntitySummaryEncoding::patchRegularObject(
llvm::json::Object &Obj, const std::map<EntityId, EntityId> &Table) {
-
- auto ExpectedIsEntityId = patchEntityIdObject(Obj, Table);
-
- if (!ExpectedIsEntityId) {
- return ExpectedIsEntityId.takeError();
- }
-
- bool IsEntityId = *ExpectedIsEntityId;
-
- if (!IsEntityId) {
- for (auto &[Key, Val] : Obj) {
- if (auto Err = patchValue(Val, Table)) {
- return Err;
- }
+ for (auto &[Key, Val] : Obj) {
+ if (auto Err = patchValue(Val, Table)) {
+ return Err;
}
}
-
return llvm::Error::success();
}
+llvm::Error JSONEntitySummaryEncoding::patchObject(
+ llvm::json::Object &Obj, const std::map<EntityId, EntityId> &Table) {
+
+ llvm::json::Value *AtVal = Obj.get(JSONEntityIdKey);
+ return AtVal ? patchEntityIdObject(Obj, Table, AtVal)
+ : patchRegularObject(Obj, Table);
+}
+
llvm::Error JSONEntitySummaryEncoding::patchValue(
llvm::json::Value &V, const std::map<EntityId, EntityId> &Table) {
if (llvm::json::Object *Obj = V.getAsObject()) {
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.h b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.h
index aa826671e2706..8475fd9c02851 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.h
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONEntitySummaryEncoding.h
@@ -34,9 +34,11 @@ class JSONEntitySummaryEncoding final : public EntitySummaryEncoding {
explicit JSONEntitySummaryEncoding(llvm::json::Value Data)
: Data(std::move(Data)) {}
- llvm::Expected<bool>
- patchEntityIdObject(llvm::json::Object &Obj,
- const std::map<EntityId, EntityId> &Table);
+ llvm::Error patchEntityIdObject(llvm::json::Object &Obj,
+ const std::map<EntityId, EntityId> &Table,
+ llvm::json::Value *AtVal);
+ llvm::Error patchRegularObject(llvm::json::Object &Obj,
+ const std::map<EntityId, EntityId> &Table);
llvm::Error patchObject(llvm::json::Object &Obj,
const std::map<EntityId, EntityId> &Table);
llvm::Error patchValue(llvm::json::Value &V,
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
index dc5831d89a147..fa7cd8b7a346a 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
@@ -651,9 +651,8 @@ JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
const auto &InfoEntry = InfoIt->second;
assert(InfoEntry.ForSummary == SN);
- return InfoEntry.Deserialize(
- EntitySummaryObject, IdTable,
- [](const Object &Obj) { return entityIdFromJSONObject(Obj); });
+ return InfoEntry.Deserialize(EntitySummaryObject, IdTable,
+ entityIdFromJSONObject);
}
llvm::Expected<Object>
@@ -670,8 +669,7 @@ JSONFormat::entitySummaryToJSON(const SummaryName &SN,
const auto &InfoEntry = InfoIt->second;
assert(InfoEntry.ForSummary == SN);
- return InfoEntry.Serialize(
- ES, [](EntityId EI) { return entityIdToJSONObject(EI); });
+ return InfoEntry.Serialize(ES, entityIdToJSONObject);
}
//----------------------------------------------------------------------------
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-1.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-1.json
index 0936ae654069c..4606f2532df5a 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-1.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-1.json
@@ -123,7 +123,7 @@
],
"data": [
{
- "summary_name": "CallGraph",
+ "summary_name": "Analysis1",
"summary_data": [
{
"entity_id": 0,
@@ -212,7 +212,7 @@
]
},
{
- "summary_name": "TypeInfo",
+ "summary_name": "Analysis2",
"summary_data": [
{
"entity_id": 0,
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-2.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-2.json
index 2dedd002c9536..ce4bf3e0307aa 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-2.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-2.json
@@ -123,7 +123,7 @@
],
"data": [
{
- "summary_name": "CallGraph",
+ "summary_name": "Analysis1",
"summary_data": [
{
"entity_id": 0,
@@ -212,7 +212,7 @@
]
},
{
- "summary_name": "TypeInfo",
+ "summary_name": "Analysis2",
"summary_data": [
{
"entity_id": 0,
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-multikey.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-multikey.json
index a975213c4e61e..30798eaa1786e 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-multikey.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-multikey.json
@@ -28,7 +28,7 @@
],
"data": [
{
- "summary_name": "TestAnalysis",
+ "summary_name": "Analysis1",
"summary_data": [
{
"entity_id": 0,
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-ref.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-ref.json
index 089a38dc4e7d9..2db2c6ea9b3c1 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-ref.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-ref.json
@@ -28,7 +28,7 @@
],
"data": [
{
- "summary_name": "TestAnalysis",
+ "summary_name": "Analysis1",
"summary_data": [
{
"entity_id": 0,
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-value.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-value.json
index eedf0aa35b953..7a66281f7ac41 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-value.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-value.json
@@ -28,7 +28,7 @@
],
"data": [
{
- "summary_name": "TestAnalysis",
+ "summary_name": "Analysis1",
"summary_data": [
{
"entity_id": 0,
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1+2.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1+2.json
index 6a74d1e341c2c..568a175730ce6 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1+2.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1+2.json
@@ -154,7 +154,7 @@
}
}
],
- "summary_name": "CallGraph"
+ "summary_name": "Analysis1"
},
{
"summary_data": [
@@ -395,7 +395,7 @@
}
}
],
- "summary_name": "TypeInfo"
+ "summary_name": "Analysis2"
}
],
"id_table": [
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1.json
index 3e5e17ce3f0c0..004e5490bba26 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1.json
@@ -87,7 +87,7 @@
}
}
],
- "summary_name": "CallGraph"
+ "summary_name": "Analysis1"
},
{
"summary_data": [
@@ -218,7 +218,7 @@
}
}
],
- "summary_name": "TypeInfo"
+ "summary_name": "Analysis2"
}
],
"id_table": [
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-2.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-2.json
index a60887a53cb83..1ed0e7fd38f6c 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-2.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-2.json
@@ -87,7 +87,7 @@
}
}
],
- "summary_name": "CallGraph"
+ "summary_name": "Analysis1"
},
{
"summary_data": [
@@ -224,7 +224,7 @@
}
}
],
- "summary_name": "TypeInfo"
+ "summary_name": "Analysis2"
}
],
"id_table": [
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/cli.test b/clang/test/Analysis/Scalable/ssaf-linker/cli.test
index 2ba0edae63fe0..79775c3f9c6ba 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/cli.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/cli.test
@@ -1,14 +1,17 @@
// Tests for ssaf-linker command-line option validation.
-// RUN: not ssaf-linker 2>&1 | FileCheck %s --check-prefix=NO-ARGS
-// NO-ARGS: ssaf-linker: Not enough positional command line arguments specified!
-// NO-ARGS: Must specify at least 1 positional argument: See: {{.*}}/ssaf-linker --help
-// NO-ARGS: ssaf-linker: for the -o option: must be specified at least once!
+// RUN: not ssaf-linker 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-ARGS
+// NO-ARGS: ssaf-linker: Not enough positional command line arguments specified!
+// NO-ARGS-NEXT: Must specify at least 1 positional argument: See: {{.*}}ssaf-linker{{(\.exe)?}} --help
+// NO-ARGS-NEXT: ssaf-linker: for the -o option: must be specified at least once!
-// RUN: not ssaf-linker %S/Inputs/tu-empty.json 2>&1 | FileCheck %s --check-prefix=NO-OUTPUT
+// RUN: not ssaf-linker %S/Inputs/tu-empty.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-OUTPUT
// NO-OUTPUT: ssaf-linker: for the -o option: must be specified at least once!
-// RUN: not ssaf-linker -o %t/output.json 2>&1 | FileCheck %s --check-prefix=NO-INPUT
-// NO-INPUT: ssaf-linker: Not enough positional command line arguments specified!
-// NO-INPUT: Must specify at least 1 positional argument: See: {{.*}}/ssaf-linker --help
+// RUN: not ssaf-linker -o %t/output.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-INPUT
+// NO-INPUT: ssaf-linker: Not enough positional command line arguments specified!
+// NO-INPUT-NEXT: Must specify at least 1 positional argument: See: {{.*}}ssaf-linker{{(\.exe)?}} --help
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/help.test b/clang/test/Analysis/Scalable/ssaf-linker/help.test
index d239fba48092a..71445a8bd91bb 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/help.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/help.test
@@ -1,20 +1,21 @@
// Test ssaf-linker help option
-// RUN: ssaf-linker --help-list-hidden | FileCheck %s
+// RUN: ssaf-linker --help-list-hidden \
+// RUN: | FileCheck %s --match-full-lines
-// CHECK: OVERVIEW: SSAF Linker
+// CHECK: OVERVIEW: SSAF Linker
// CHECK-EMPTY:
-// CHECK: USAGE: ssaf-linker [options] <input files>
+// CHECK-NEXT: USAGE: ssaf-linker{{(\.exe)?}} [options] <input files>
// CHECK-EMPTY:
-// CHECK: OPTIONS:
-// CHECK: -h - Alias for --help
-// CHECK: --help - Display available options (--help-hidden for more)
-// CHECK: --help-hidden - Display all available options
-// CHECK: --help-list - Display list of available options (--help-list-hidden for more)
-// CHECK: --help-list-hidden - Display list of all available options
-// CHECK: -o <path> - Output summary path
-// CHECK: --print-all-options - Print all option values after command line parsing
-// CHECK: --print-options - Print non-default options after command line parsing
-// CHECK: --time - Enable timing
-// CHECK: --verbose - Enable verbose output
-// CHECK: --version - Display the version of this program
\ No newline at end of file
+// CHECK-NEXT: OPTIONS:
+// CHECK-NEXT: -h - Alias for --help
+// CHECK-NEXT: --help - Display available options (--help-hidden for more)
+// CHECK-NEXT: --help-hidden - Display all available options
+// CHECK-NEXT: --help-list - Display list of available options (--help-list-hidden for more)
+// CHECK-NEXT: --help-list-hidden - Display list of all available options
+// CHECK-NEXT: -o <path> - Output summary path
+// CHECK-NEXT: --print-all-options - Print all option values after command line parsing
+// CHECK-NEXT: --print-options - Print non-default options after command line parsing
+// CHECK-NEXT: --time - Enable timing
+// CHECK-NEXT: --verbose - Enable verbose output
+// CHECK-NEXT: --version - Display the version of this program
\ No newline at end of file
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/io.test b/clang/test/Analysis/Scalable/ssaf-linker/io.test
index 96ff839c362e7..213cf3c23c511 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/io.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/io.test
@@ -5,20 +5,19 @@
// Malformed JSON input.
// RUN: not ssaf-linker %S/Inputs/tu-malformed.json -o %t/out.json 2>&1 \
-// RUN: | FileCheck %s --check-prefix=BAD-JSON -DPATH=%S/Inputs/tu-malformed.json
-// BAD-JSON: ssaf-linker: error: reading TUSummary from file '[[PATH]]'
-// BAD-JSON: Invalid JSON value
+// RUN: | FileCheck %s --match-full-lines --check-prefix=BAD-JSON
+// BAD-JSON: ssaf-linker: error: reading TUSummary from file '{{.*}}tu-malformed.json'
+// BAD-JSON-NEXT: {{.*}}: Invalid JSON value{{.*}}
// Missing required fields in otherwise valid JSON.
// RUN: not ssaf-linker %S/Inputs/tu-missing-fields.json -o %t/out.json 2>&1 \
-// | FileCheck %s --check-prefix=MISSING-FIELDS -DPATH=%S/Inputs/tu-missing-fields.json
-// MISSING-FIELDS: ssaf-linker: error: reading TUSummary from file '[[PATH]]'
-// MISSING-FIELDS: failed to read IdTable from field 'id_table': expected JSON array
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MISSING-FIELDS
+// MISSING-FIELDS: ssaf-linker: error: reading TUSummary from file '{{.*}}tu-missing-fields.json'
+// MISSING-FIELDS-NEXT: failed to read IdTable from field 'id_table': expected JSON array
// Output file already exists.
// RUN: touch %t/out.json
-// RUN: ssaf-linker %S/Inputs/tu-empty.json -o %t/out.json
// RUN: not ssaf-linker %S/Inputs/tu-empty.json -o %t/out.json 2>&1 \
-// RUN: | FileCheck %s --check-prefix=OUTPUT-EXISTS -DPATH=%t/out.json
-// OUTPUT-EXISTS: ssaf-linker: error: writing LUSummary to file '[[PATH]]'
-// OUTPUT-EXISTS: failed to write file '[[PATH]]': file already exists
+// RUN: | FileCheck %s --match-full-lines --check-prefix=OUTPUT-EXISTS
+// OUTPUT-EXISTS: ssaf-linker: error: writing LUSummary to file '{{.*}}out.json'
+// OUTPUT-EXISTS-NEXT: failed to write file '{{.*}}out.json': file already exists
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test b/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test
index 13b9f471df7f8..87f1f08a6b2ef 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test
@@ -5,24 +5,24 @@
// Linking the same TU namespace twice produces an error.
// RUN: not ssaf-linker %S/Inputs/tu-empty.json %S/Inputs/tu-empty.json -o %t/lu.json 2>&1 \
-// RUN: | FileCheck %s --check-prefix=DUP-NS -DPATH=%S/Inputs/tu-empty.json
-// DUP-NS: ssaf-linker: error: Linking summary '[[PATH]]'
-// DUP-NS: failed to link TU summary: duplicate BuildNamespace(CompilationUnit, empty.cpp)
+// RUN: | FileCheck %s --match-full-lines --check-prefix=DUP-NS
+// DUP-NS: ssaf-linker: error: Linking summary '{{.*}}tu-empty.json'
+// DUP-NS-NEXT: failed to link TU summary: duplicate BuildNamespace(CompilationUnit, empty.cpp)
// Entity ID object in summary data blob with '@' key alongside extra keys is a fatal error.
// RUN: not ssaf-linker %S/Inputs/tu-invalid-entity-id-multikey.json -o %t/lu.json 2>&1 \
-// RUN: | FileCheck %s --check-prefix=INVALID-ID-MULTIKEY -DPATH=%S/Inputs/tu-invalid-entity-id-multikey.json
-// INVALID-ID-MULTIKEY: ssaf-linker: error: Linking summary '[[PATH]]'
-// INVALID-ID-MULTIKEY: failed to read EntityId: expected JSON object with a single '@' key mapped to a number (unsigned 64-bit integer)
+// RUN: | FileCheck %s --match-full-lines --check-prefix=INVALID-ID-MULTIKEY
+// INVALID-ID-MULTIKEY: ssaf-linker: error: Linking summary '{{.*}}tu-invalid-entity-id-multikey.json'
+// INVALID-ID-MULTIKEY-NEXT: failed to read EntityId: expected JSON object with a single '@' key mapped to a number (unsigned 64-bit integer)
// Entity ID object in summary data blob with a non-uint64 '@' value is a fatal error.
// RUN: not ssaf-linker %S/Inputs/tu-invalid-entity-id-value.json -o %t/lu.json 2>&1 \
-// RUN: | FileCheck %s --check-prefix=INVALID-ID-VALUE -DPATH=%S/Inputs/tu-invalid-entity-id-value.json
-// INVALID-ID-VALUE: ssaf-linker: error: Linking summary '[[PATH]]'
-// INVALID-ID-VALUE: failed to read EntityId: expected JSON object with a single '@' key mapped to a number (unsigned 64-bit integer)
+// RUN: | FileCheck %s --match-full-lines --check-prefix=INVALID-ID-VALUE
+// INVALID-ID-VALUE: ssaf-linker: error: Linking summary '{{.*}}tu-invalid-entity-id-value.json'
+// INVALID-ID-VALUE-NEXT: failed to read EntityId: expected JSON object with a single '@' key mapped to a number (unsigned 64-bit integer)
// Entity ID reference in summary data blob pointing to an ID absent from the resolution table
// RUN: not ssaf-linker %S/Inputs/tu-invalid-entity-id-ref.json -o %t/lu.json 2>&1 \
-// RUN: | FileCheck %s --check-prefix=INVALID-ID-REF -DPATH=%S/Inputs/tu-invalid-entity-id-ref.json
-// INVALID-ID-REF: ssaf-linker: error: Linking summary '[[PATH]]'
-// INVALID-ID-REF: failed to patch EntityId: 'EntityId(99)' not found in entity resolution table
\ No newline at end of file
+// RUN: | FileCheck %s --match-full-lines --check-prefix=INVALID-ID-REF
+// INVALID-ID-REF: ssaf-linker: error: Linking summary '{{.*}}tu-invalid-entity-id-ref.json'
+// INVALID-ID-REF-NEXT: failed to patch EntityId: 'EntityId(99)' not found in entity resolution table
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/time.test b/clang/test/Analysis/Scalable/ssaf-linker/time.test
index 8737c6d9e0eef..1c59a19fe35f0 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/time.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/time.test
@@ -4,14 +4,14 @@
// RUN: mkdir -p %t
// RUN: ssaf-linker --time %S/Inputs/tu-1.json %S/Inputs/tu-2.json -o %t/lu-1+2.json 2>&1 \
-// RUN: | FileCheck %s
-// CHECK: ===-------------------------------------------------------------------------===
-// CHECK: SSAF Linker
-// CHECK: ===-------------------------------------------------------------------------===
-// CHECK: Total Execution Time: {{[0-9.]+}} seconds ({{[0-9.]+}} wall clock)
-// CHECK: ---User Time--- --System Time-- --User+System-- ---Wall Time---
-// CHECK-DAG: {{.*}}Write Summary
-// CHECK-DAG: {{.*}}Read Summaries
-// CHECK-DAG: {{.*}}Link Summaries
-// CHECK-DAG: {{.*}}Validate Input
-// CHECK: {{.*}}Total
+// RUN: | FileCheck %s --match-full-lines
+// CHECK: ===-------------------------------------------------------------------------===
+// CHECK-NEXT: {{[ ]+}}SSAF Linker
+// CHECK-NEXT: ===-------------------------------------------------------------------------===
+// CHECK-NEXT: Total Execution Time: {{[0-9.]+}} seconds ({{[0-9.]+}} wall clock)
+// CHECK: {{.*}}---Wall Time---{{.*}}
+// CHECK-DAG: {{.*}}Write Summary
+// CHECK-DAG: {{.*}}Read Summaries
+// CHECK-DAG: {{.*}}Link Summaries
+// CHECK-DAG: {{.*}}Validate Input
+// CHECK: {{.*}}Total
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test b/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
index 50709846aebe9..efdd9756ff8af 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
@@ -5,46 +5,45 @@
// No extension on output.
// RUN: not ssaf-linker %S/Inputs/tu-empty.json -o lu 2>&1 \
-// RUN: | FileCheck %s --check-prefix=NO-EXT-OUTPUT
-// NO-EXT-OUTPUT: ssaf-linker: error: failed to validate summary 'lu': Extension not supplied.
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-EXT-OUTPUT
+// NO-EXT-OUTPUT: ssaf-linker: error: failed to validate summary 'lu': Extension not supplied
// No extension on input.
// RUN: not ssaf-linker %S/Inputs/tu-noext -o %t/lu.json 2>&1 \
-// RUN: | FileCheck %s --check-prefix=NO-EXT-INPUT -DPATH=%S/Inputs/tu-noext
-// NO-EXT-INPUT: ssaf-linker: error: failed to validate summary '[[PATH]]': Extension not supplied.
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-EXT-INPUT
+// NO-EXT-INPUT: ssaf-linker: error: failed to validate summary '{{.*}}tu-noext': Extension not supplied
// Invalid extension on output.
// RUN: not ssaf-linker %S/Inputs/tu-empty.json -o lu.txt 2>&1 \
-// RUN: | FileCheck %s --check-prefix=BAD-EXT-OUTPUT
-// BAD-EXT-OUTPUT: ssaf-linker: error: failed to validate summary 'lu.txt': Format not registered for extension 'txt'.
+// RUN: | FileCheck %s --match-full-lines --check-prefix=BAD-EXT-OUTPUT
+// BAD-EXT-OUTPUT: ssaf-linker: error: failed to validate summary 'lu.txt': Format not registered for extension 'txt'
// Invalid extension on input.
// RUN: not ssaf-linker %S/Inputs/tu-badext.txt -o %t/lu.json 2>&1 \
-// RUN: | FileCheck %s --check-prefix=BAD-EXT-INPUT -DPATH=%S/Inputs/tu-badext.txt
-// BAD-EXT-INPUT: ssaf-linker: error: failed to validate summary '[[PATH]]': Format not registered for extension 'txt'.
+// RUN: | FileCheck %s --match-full-lines --check-prefix=BAD-EXT-INPUT
+// BAD-EXT-INPUT: ssaf-linker: error: failed to validate summary '{{.*}}tu-badext.txt': Format not registered for extension 'txt'
// Output directory does not exist.
// RUN: not ssaf-linker %S/Inputs/tu-empty.json -o %S/Outputs/NonExistentDirectory/lu.json 2>&1 \
-// RUN: | FileCheck %s --check-prefix=OUTPUT-PARENT-DIR-MISSING -DPATH=%S/Outputs/NonExistentDirectory/lu.json
-// OUTPUT-PARENT-DIR-MISSING: ssaf-linker: error: failed to validate summary '[[PATH]]': Parent directory does not exist.
+// RUN: | FileCheck %s --match-full-lines --check-prefix=OUTPUT-PARENT-DIR-MISSING
+// OUTPUT-PARENT-DIR-MISSING: ssaf-linker: error: failed to validate summary '{{.*}}lu.json': Parent directory does not exist
// Output parent directory exists but is not writable.
// UNSUPPORTED: system-windows
// RUN: mkdir -p %t/output-dir
// RUN: chmod -w %t/output-dir
// RUN: not ssaf-linker %S/Inputs/tu-empty.json -o %t/output-dir/output.json 2>&1 \
-// RUN: | FileCheck %s --check-prefix=NO-WRITE-PERM -DPATH=%t/output-dir/output.json
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-WRITE-PERM
// RUN: chmod +w %t/output-dir
-// NO-WRITE-PERM: ssaf-linker: error: failed to validate summary '[[PATH]]': Parent directory is not writable.
+// NO-WRITE-PERM: ssaf-linker: error: failed to validate summary '{{.*}}output.json': Parent directory is not writable
// Input summary does not exist.
// RUN: not ssaf-linker %S/Inputs/tu-nonexistent.json -o %t/lu.json 2>&1 \
-// RUN: | FileCheck %s --check-prefix=NO-INPUT-FILE -DPATH=%S/Inputs/tu-nonexistent.json
-// NO-INPUT-FILE: ssaf-linker: error: failed to validate summary '[[PATH]]': No such file or directory.
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-INPUT-FILE
+// NO-INPUT-FILE: ssaf-linker: error: failed to validate summary '{{.*}}tu-nonexistent.json': No such file or directory
// Input summary is a broken symlink.
// RUN: ln -sf %t/tu-nonexistent %t/tu-dangling.json
// RUN: not ssaf-linker %t/tu-dangling.json -o %t/lu.json 2>&1 \
-// RUN: | FileCheck %s --check-prefix=BROKEN-SYMLINK -DPATH=%t/tu-dangling.json
-// BROKEN-SYMLINK: ssaf-linker: error: failed to validate summary '[[PATH]]': No such file or directory.
-
+// RUN: | FileCheck %s --match-full-lines --check-prefix=BROKEN-SYMLINK
+// BROKEN-SYMLINK: ssaf-linker: error: failed to validate summary '{{.*}}tu-dangling.json': No such file or directory
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/verbose.test b/clang/test/Analysis/Scalable/ssaf-linker/verbose.test
index 0f7777043d1d9..8e246efd71f2a 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/verbose.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/verbose.test
@@ -4,17 +4,17 @@
// RUN: mkdir -p %t
// RUN: ssaf-linker --verbose %S/Inputs/tu-1.json %S/Inputs/tu-2.json -o %t/lu-1+2.json 2>&1 \
-// RUN: | FileCheck %s -DINPUT=%S/Inputs -DOUTPUT=%t
-// CHECK: note: - Linking started.
-// CHECK: note: - Validating input.
-// CHECK: note: - Validated output summary path '[[OUTPUT]]/lu-1+2.json'.
-// CHECK: note: - Validated 2 input summary paths.
-// CHECK: note: - Linking input.
-// CHECK: note: - Constructing linker.
-// CHECK: note: - Linking summaries.
-// CHECK: note: - [1/2] Reading '[[INPUT]]/tu-1.json'.
-// CHECK: note: - [1/2] Linking '[[INPUT]]/tu-1.json'.
-// CHECK: note: - [2/2] Reading '[[INPUT]]/tu-2.json'.
-// CHECK: note: - [2/2] Linking '[[INPUT]]/tu-2.json'.
-// CHECK: note: - Writing output summary to '[[OUTPUT]]/lu-1+2.json'.
-// CHECK: note: - Linking finished.
\ No newline at end of file
+// RUN: | FileCheck %s --match-full-lines
+// CHECK: note: - Linking started.
+// CHECK-NEXT: note: - Validating input.
+// CHECK-NEXT: note: - Validated output summary path '{{.*}}lu-1+2.json'.
+// CHECK-NEXT: note: - Validated 2 input summary paths.
+// CHECK-NEXT: note: - Linking input.
+// CHECK-NEXT: note: - Constructing linker.
+// CHECK-NEXT: note: - Linking summaries.
+// CHECK-NEXT: note: - [1/2] Reading '{{.*}}tu-1.json'.
+// CHECK-NEXT: note: - [1/2] Linking '{{.*}}tu-1.json'.
+// CHECK-NEXT: note: - [2/2] Reading '{{.*}}tu-2.json'.
+// CHECK-NEXT: note: - [2/2] Linking '{{.*}}tu-2.json'.
+// CHECK-NEXT: note: - Writing output summary to '{{.*}}lu-1+2.json'.
+// CHECK-NEXT: note: - Linking finished.
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/version.test b/clang/test/Analysis/Scalable/ssaf-linker/version.test
index e68e4eeb47152..ed0b4c2ab56bc 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/version.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/version.test
@@ -1,5 +1,5 @@
// Test ssaf-linker version
-// RUN: ssaf-linker --version | FileCheck %s
+// RUN: ssaf-linker --version | FileCheck %s --match-full-lines
// CHECK: ssaf-linker 0.1
diff --git a/clang/tools/ssaf-linker/SSAFLinker.cpp b/clang/tools/ssaf-linker/SSAFLinker.cpp
index 318a9a5d99795..66cf8b368487c 100644
--- a/clang/tools/ssaf-linker/SSAFLinker.cpp
+++ b/clang/tools/ssaf-linker/SSAFLinker.cpp
@@ -66,18 +66,18 @@ cl::opt<bool> Time("time", cl::desc("Enable timing"), cl::init(false),
namespace ErrorMessages {
constexpr const char *CannotValidateSummary =
- "failed to validate summary '{0}': {1}.";
+ "failed to validate summary '{0}': {1}";
constexpr const char *OutputDirectoryMissing =
- "Parent directory does not exist.";
+ "Parent directory does not exist";
constexpr const char *OutputDirectoryNotWritable =
- "Parent directory is not writable.";
+ "Parent directory is not writable";
-constexpr const char *ExtensionNotSupplied = "Extension not supplied.";
+constexpr const char *ExtensionNotSupplied = "Extension not supplied";
constexpr const char *NoFormatForExtension =
- "Format not registered for extension '{0}'.";
+ "Format not registered for extension '{0}'";
constexpr const char *LinkingSummary = "Linking summary '{0}'";
@@ -91,24 +91,23 @@ constexpr unsigned IndentationWidth = 2;
llvm::StringRef ToolName;
-template <typename... Ts> [[noreturn]] void Fail(const char *Msg) {
+template <typename... Ts> [[noreturn]] void fail(const char *Msg) {
llvm::WithColor::error(llvm::errs(), ToolName) << Msg << "\n";
llvm::sys::Process::Exit(1);
}
template <typename... Ts>
-[[noreturn]] void Fail(const char *Fmt, Ts &&...Args) {
+[[noreturn]] void fail(const char *Fmt, Ts &&...Args) {
std::string Message = llvm::formatv(Fmt, std::forward<Ts>(Args)...);
- Fail(Message.data());
+ fail(Message.data());
}
-template <typename... Ts> [[noreturn]] void Fail(llvm::Error Err) {
- std::string Message = toString(std::move(Err));
- Fail(Message.data());
+template <typename... Ts> [[noreturn]] void fail(llvm::Error Err) {
+ fail(toString(std::move(Err)).data());
}
template <typename... Ts>
-void Info(unsigned IndentationLevel, const char *Fmt, Ts &&...Args) {
+void info(unsigned IndentationLevel, const char *Fmt, Ts &&...Args) {
if (Verbose) {
llvm::WithColor::note()
<< std::string(IndentationLevel * IndentationWidth, ' ') << "- "
@@ -120,7 +119,7 @@ void Info(unsigned IndentationLevel, const char *Fmt, Ts &&...Args) {
// Format Registry
//===----------------------------------------------------------------------===//
-SerializationFormat *GetFormatForExtension(llvm::StringRef Extension) {
+SerializationFormat *getFormatForExtension(llvm::StringRef Extension) {
static llvm::SmallVector<
std::pair<std::string, std::unique_ptr<SerializationFormat>>, 4>
ExtensionFormatList;
@@ -158,18 +157,18 @@ struct SummaryFile {
std::string Path;
SerializationFormat *Format = nullptr;
- static SummaryFile FromPath(llvm::StringRef Path) {
+ static SummaryFile fromPath(llvm::StringRef Path) {
llvm::StringRef Extension = path::extension(Path);
if (Extension.empty()) {
- Fail(ErrorMessages::CannotValidateSummary, Path,
+ fail(ErrorMessages::CannotValidateSummary, Path,
ErrorMessages::ExtensionNotSupplied);
}
Extension = Extension.drop_front();
- SerializationFormat *Format = GetFormatForExtension(Extension);
+ SerializationFormat *Format = getFormatForExtension(Extension);
if (!Format) {
std::string BadExtension =
llvm::formatv(ErrorMessages::NoFormatForExtension, Extension);
- Fail(ErrorMessages::CannotValidateSummary, Path, BadExtension);
+ fail(ErrorMessages::CannotValidateSummary, Path, BadExtension);
}
return {Path.str(), Format};
}
@@ -181,13 +180,13 @@ struct LinkerInput {
std::string LinkUnitName;
};
-static void PrintVersion(llvm::raw_ostream &OS) { OS << ToolName << " 0.1\n"; }
+static void printVersion(llvm::raw_ostream &OS) { OS << ToolName << " 0.1\n"; }
//===----------------------------------------------------------------------===//
// Pipeline
//===----------------------------------------------------------------------===//
-LinkerInput Validate(llvm::TimerGroup &TG) {
+LinkerInput validate(llvm::TimerGroup &TG) {
llvm::Timer TValidate("validate", "Validate Input", TG);
LinkerInput LI;
@@ -197,20 +196,20 @@ LinkerInput Validate(llvm::TimerGroup &TG) {
llvm::StringRef DirToCheck = ParentDir.empty() ? "." : ParentDir;
if (!fs::exists(DirToCheck)) {
- Fail(ErrorMessages::CannotValidateSummary, OutputPath,
+ fail(ErrorMessages::CannotValidateSummary, OutputPath,
ErrorMessages::OutputDirectoryMissing);
}
if (fs::access(DirToCheck, fs::AccessMode::Write)) {
- Fail(ErrorMessages::CannotValidateSummary, OutputPath,
+ fail(ErrorMessages::CannotValidateSummary, OutputPath,
ErrorMessages::OutputDirectoryNotWritable);
}
- LI.OutputFile = SummaryFile::FromPath(OutputPath);
+ LI.OutputFile = SummaryFile::fromPath(OutputPath);
LI.LinkUnitName = path::stem(LI.OutputFile.Path).str();
}
- Info(2, "Validated output summary path '{0}'.", LI.OutputFile.Path);
+ info(2, "Validated output summary path '{0}'.", LI.OutputFile.Path);
{
llvm::TimeRegion _(Time ? &TValidate : nullptr);
@@ -218,19 +217,19 @@ LinkerInput Validate(llvm::TimerGroup &TG) {
llvm::SmallString<256> RealPath;
std::error_code EC = fs::real_path(InputPath, RealPath, true);
if (EC) {
- Fail(ErrorMessages::CannotValidateSummary, InputPath, EC.message());
+ fail(ErrorMessages::CannotValidateSummary, InputPath, EC.message());
}
- LI.InputFiles.push_back(SummaryFile::FromPath(RealPath));
+ LI.InputFiles.push_back(SummaryFile::fromPath(RealPath));
}
}
- Info(2, "Validated {0} input summary paths.", LI.InputFiles.size());
+ info(2, "Validated {0} input summary paths.", LI.InputFiles.size());
return LI;
}
-void Link(const LinkerInput &LI, llvm::TimerGroup &TG) {
- Info(2, "Constructing linker.");
+void link(const LinkerInput &LI, llvm::TimerGroup &TG) {
+ info(2, "Constructing linker.");
EntityLinker EL(NestedBuildNamespace(
BuildNamespace(BuildNamespaceKind::LinkUnit, LI.LinkUnitName)));
@@ -239,13 +238,13 @@ void Link(const LinkerInput &LI, llvm::TimerGroup &TG) {
llvm::Timer TLink("link", "Link Summaries", TG);
llvm::Timer TWrite("write", "Write Summary", TG);
- Info(2, "Linking summaries.");
+ info(2, "Linking summaries.");
for (auto [Index, InputFile] : llvm::enumerate(LI.InputFiles)) {
std::unique_ptr<TUSummaryEncoding> Summary;
{
- Info(3, "[{0}/{1}] Reading '{2}'.", (Index + 1), LI.InputFiles.size(),
+ info(3, "[{0}/{1}] Reading '{2}'.", (Index + 1), LI.InputFiles.size(),
InputFile.Path);
llvm::TimeRegion _(Time ? &TRead : nullptr);
@@ -253,7 +252,7 @@ void Link(const LinkerInput &LI, llvm::TimerGroup &TG) {
auto ExpectedSummaryEncoding =
InputFile.Format->readTUSummaryEncoding(InputFile.Path);
if (!ExpectedSummaryEncoding) {
- Fail(ExpectedSummaryEncoding.takeError());
+ fail(ExpectedSummaryEncoding.takeError());
}
Summary = std::make_unique<TUSummaryEncoding>(
@@ -261,13 +260,13 @@ void Link(const LinkerInput &LI, llvm::TimerGroup &TG) {
}
{
- Info(3, "[{0}/{1}] Linking '{2}'.", (Index + 1), LI.InputFiles.size(),
+ info(3, "[{0}/{1}] Linking '{2}'.", (Index + 1), LI.InputFiles.size(),
InputFile.Path);
llvm::TimeRegion _(Time ? &TLink : nullptr);
if (auto Err = EL.link(std::move(Summary))) {
- Fail(ErrorBuilder::wrap(std::move(Err))
+ fail(ErrorBuilder::wrap(std::move(Err))
.context(ErrorMessages::LinkingSummary, InputFile.Path)
.build());
}
@@ -275,14 +274,14 @@ void Link(const LinkerInput &LI, llvm::TimerGroup &TG) {
}
{
- Info(2, "Writing output summary to '{0}'.", LI.OutputFile.Path);
+ info(2, "Writing output summary to '{0}'.", LI.OutputFile.Path);
llvm::TimeRegion _(Time ? &TWrite : nullptr);
auto Output = std::move(EL).getOutput();
if (auto Err = LI.OutputFile.Format->writeLUSummaryEncoding(
Output, LI.OutputFile.Path)) {
- Fail(std::move(Err));
+ fail(std::move(Err));
}
}
}
@@ -295,12 +294,13 @@ void Link(const LinkerInput &LI, llvm::TimerGroup &TG) {
int main(int argc, const char **argv) {
InitLLVM X(argc, argv);
- ToolName = llvm::sys::path::filename(argv[0]);
+ // path::stem strips the .exe extension on Windows so ToolName is consistent.
+ ToolName = llvm::sys::path::stem(argv[0]);
// Hide options unrelated to ssaf-linker from --help output.
cl::HideUnrelatedOptions(SsafLinkerCategory);
// Register a custom version printer for the --version flag.
- cl::SetVersionPrinter(PrintVersion);
+ cl::SetVersionPrinter(printVersion);
// Parse command-line arguments and exit with an error if they are invalid.
cl::ParseCommandLineOptions(argc, argv, "SSAF Linker\n");
@@ -310,19 +310,19 @@ int main(int argc, const char **argv) {
LinkerInput LI;
{
- Info(0, "Linking started.");
+ info(0, "Linking started.");
{
- Info(1, "Validating input.");
- LI = Validate(LinkerTimers);
+ info(1, "Validating input.");
+ LI = validate(LinkerTimers);
}
{
- Info(1, "Linking input.");
- Link(LI, LinkerTimers);
+ info(1, "Linking input.");
+ link(LI, LinkerTimers);
}
- Info(0, "Linking finished.");
+ info(0, "Linking finished.");
}
return 0;
>From 2f47726836af6e6339d0ba4099cb8b3f0fe49121 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Sat, 7 Mar 2026 11:37:06 -0800
Subject: [PATCH 11/13] More fixes
---
clang/test/Analysis/Scalable/ssaf-linker/cli.test | 8 ++++----
.../Analysis/Scalable/ssaf-linker/linking.test | 14 +++++++++++++-
.../ssaf-linker/validation-errors-permissions.test | 13 +++++++++++++
.../Scalable/ssaf-linker/validation-errors.test | 9 ---------
.../Analysis/Scalable/ssaf-linker/version.test | 4 ++--
5 files changed, 32 insertions(+), 16 deletions(-)
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/validation-errors-permissions.test
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/cli.test b/clang/test/Analysis/Scalable/ssaf-linker/cli.test
index 79775c3f9c6ba..3a4bb7d6e1faf 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/cli.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/cli.test
@@ -2,16 +2,16 @@
// RUN: not ssaf-linker 2>&1 \
// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-ARGS
-// NO-ARGS: ssaf-linker: Not enough positional command line arguments specified!
+// NO-ARGS: ssaf-linker{{(\.exe)?}}: Not enough positional command line arguments specified!
// NO-ARGS-NEXT: Must specify at least 1 positional argument: See: {{.*}}ssaf-linker{{(\.exe)?}} --help
-// NO-ARGS-NEXT: ssaf-linker: for the -o option: must be specified at least once!
+// NO-ARGS-NEXT: ssaf-linker{{(\.exe)?}}: for the -o option: must be specified at least once!
// RUN: not ssaf-linker %S/Inputs/tu-empty.json 2>&1 \
// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-OUTPUT
-// NO-OUTPUT: ssaf-linker: for the -o option: must be specified at least once!
+// NO-OUTPUT: ssaf-linker{{(\.exe)?}}: for the -o option: must be specified at least once!
// RUN: not ssaf-linker -o %t/output.json 2>&1 \
// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-INPUT
-// NO-INPUT: ssaf-linker: Not enough positional command line arguments specified!
+// NO-INPUT: ssaf-linker{{(\.exe)?}}: Not enough positional command line arguments specified!
// NO-INPUT-NEXT: Must specify at least 1 positional argument: See: {{.*}}ssaf-linker{{(\.exe)?}} --help
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/linking.test b/clang/test/Analysis/Scalable/ssaf-linker/linking.test
index 63e501811334d..2ed6d37b558ac 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/linking.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/linking.test
@@ -27,4 +27,16 @@
// Linking two TUs correctly resolves and patches data.
// RUN: ssaf-linker %S/Inputs/tu-1.json %S/Inputs/tu-2.json -o %t/lu-1+2.json
// RUN: diff %S/Outputs/lu-1+2.json %t/lu-1+2.json
-// RUN: rm %t/lu-1+2.json
\ No newline at end of file
+// RUN: rm %t/lu-1+2.json
+
+// Relative input path: cd to the test source directory so that
+// Inputs/tu-1.json is a valid relative path.
+// RUN: cd %S && ssaf-linker Inputs/tu-1.json -o %t/lu-1.json
+// RUN: diff %S/Outputs/lu-1.json %t/lu-1.json
+// RUN: rm %t/lu-1.json
+
+// Relative output path: cd to the temp directory so that lu-1.json
+// (no directory component) resolves relative to the current directory.
+// RUN: cd %t && ssaf-linker %S/Inputs/tu-1.json -o lu-1.json
+// RUN: diff %S/Outputs/lu-1.json %t/lu-1.json
+// RUN: rm %t/lu-1.json
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/validation-errors-permissions.test b/clang/test/Analysis/Scalable/ssaf-linker/validation-errors-permissions.test
new file mode 100644
index 0000000000000..801b67b7ba777
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/validation-errors-permissions.test
@@ -0,0 +1,13 @@
+// Tests for ssaf-linker input validation requiring file permission support.
+// UNSUPPORTED: system-windows
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// Output parent directory exists but is not writable.
+// RUN: mkdir -p %t/output-dir
+// RUN: chmod -w %t/output-dir
+// RUN: not ssaf-linker %S/Inputs/tu-empty.json -o %t/output-dir/output.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-WRITE-PERM
+// RUN: chmod +w %t/output-dir
+// NO-WRITE-PERM: ssaf-linker: error: failed to validate summary '{{.*}}output.json': Parent directory is not writable
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test b/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
index efdd9756ff8af..563edde592b97 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
@@ -28,15 +28,6 @@
// RUN: | FileCheck %s --match-full-lines --check-prefix=OUTPUT-PARENT-DIR-MISSING
// OUTPUT-PARENT-DIR-MISSING: ssaf-linker: error: failed to validate summary '{{.*}}lu.json': Parent directory does not exist
-// Output parent directory exists but is not writable.
-// UNSUPPORTED: system-windows
-// RUN: mkdir -p %t/output-dir
-// RUN: chmod -w %t/output-dir
-// RUN: not ssaf-linker %S/Inputs/tu-empty.json -o %t/output-dir/output.json 2>&1 \
-// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-WRITE-PERM
-// RUN: chmod +w %t/output-dir
-// NO-WRITE-PERM: ssaf-linker: error: failed to validate summary '{{.*}}output.json': Parent directory is not writable
-
// Input summary does not exist.
// RUN: not ssaf-linker %S/Inputs/tu-nonexistent.json -o %t/lu.json 2>&1 \
// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-INPUT-FILE
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/version.test b/clang/test/Analysis/Scalable/ssaf-linker/version.test
index ed0b4c2ab56bc..e6e9e41663199 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/version.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/version.test
@@ -1,5 +1,5 @@
// Test ssaf-linker version
-// RUN: ssaf-linker --version | FileCheck %s --match-full-lines
+// RUN: ssaf-linker --version | FileCheck %s
-// CHECK: ssaf-linker 0.1
+// CHECK: ssaf-linker
>From c499de3aa5120edd0578117dbea27c26b92d43ca Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Sat, 7 Mar 2026 14:32:40 -0800
Subject: [PATCH 12/13] Fix
---
.../test/Analysis/Scalable/ssaf-linker/validation-errors.test | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test b/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
index 563edde592b97..5a1c37f87f5e2 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
@@ -31,10 +31,10 @@
// Input summary does not exist.
// RUN: not ssaf-linker %S/Inputs/tu-nonexistent.json -o %t/lu.json 2>&1 \
// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-INPUT-FILE
-// NO-INPUT-FILE: ssaf-linker: error: failed to validate summary '{{.*}}tu-nonexistent.json': No such file or directory
+// NO-INPUT-FILE: ssaf-linker: error: failed to validate summary '{{.*}}tu-nonexistent.json': {{[Nn]}}o such file or directory
// Input summary is a broken symlink.
// RUN: ln -sf %t/tu-nonexistent %t/tu-dangling.json
// RUN: not ssaf-linker %t/tu-dangling.json -o %t/lu.json 2>&1 \
// RUN: | FileCheck %s --match-full-lines --check-prefix=BROKEN-SYMLINK
-// BROKEN-SYMLINK: ssaf-linker: error: failed to validate summary '{{.*}}tu-dangling.json': No such file or directory
+// BROKEN-SYMLINK: ssaf-linker: error: failed to validate summary '{{.*}}tu-dangling.json': {{[Nn]}}o such file or directory
>From 0ff9185d516689364eda8c64847c5f0d70b14c3d Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Sat, 7 Mar 2026 22:20:09 -0800
Subject: [PATCH 13/13] Even more fix. Will they ever end?
---
.../Scalable/ssaf-linker/validation-errors-permissions.test | 6 ++++++
.../Analysis/Scalable/ssaf-linker/validation-errors.test | 5 -----
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/validation-errors-permissions.test b/clang/test/Analysis/Scalable/ssaf-linker/validation-errors-permissions.test
index 801b67b7ba777..adf15243180e6 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/validation-errors-permissions.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/validation-errors-permissions.test
@@ -11,3 +11,9 @@
// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-WRITE-PERM
// RUN: chmod +w %t/output-dir
// NO-WRITE-PERM: ssaf-linker: error: failed to validate summary '{{.*}}output.json': Parent directory is not writable
+
+// Input summary is a broken symlink.
+// RUN: ln -sf %t/tu-nonexistent %t/tu-dangling.json
+// RUN: not ssaf-linker %t/tu-dangling.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=BROKEN-SYMLINK
+// BROKEN-SYMLINK: ssaf-linker: error: failed to validate summary '{{.*}}tu-dangling.json': {{[Nn]}}o such file or directory
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test b/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
index 5a1c37f87f5e2..336d4f45925a5 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/validation-errors.test
@@ -33,8 +33,3 @@
// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-INPUT-FILE
// NO-INPUT-FILE: ssaf-linker: error: failed to validate summary '{{.*}}tu-nonexistent.json': {{[Nn]}}o such file or directory
-// Input summary is a broken symlink.
-// RUN: ln -sf %t/tu-nonexistent %t/tu-dangling.json
-// RUN: not ssaf-linker %t/tu-dangling.json -o %t/lu.json 2>&1 \
-// RUN: | FileCheck %s --match-full-lines --check-prefix=BROKEN-SYMLINK
-// BROKEN-SYMLINK: ssaf-linker: error: failed to validate summary '{{.*}}tu-dangling.json': {{[Nn]}}o such file or directory
More information about the cfe-commits
mailing list