[clang] [clang][ssaf] Add `MultiArchSharedLibrary` data structure (PR #206854)
Aviral Goel via cfe-commits
cfe-commits at lists.llvm.org
Tue Jun 30 15:51:47 PDT 2026
https://github.com/aviralg created https://github.com/llvm/llvm-project/pull/206854
This change introduces `MultiArchSharedLibrary` data structure that wraps per-architecture `LUSummaryEncoding` members. This is the SSAF analogue of a Mach-O fat shared library (`lipo -create` over per-arch `.dylib` files). The overall design mirrors the existing `MultiArchStaticLibrary` design: each member identifies the same logical library built for a different target triple. Support for constructing this object will be added in a future PR.
>From 3dfdb2146410abf2a56a47877d9a01a7686f39cd Mon Sep 17 00:00:00 2001
From: Aviral Goel <goel.aviral at gmail.com>
Date: Tue, 30 Jun 2026 09:12:38 -0700
Subject: [PATCH 1/2] Add MultiArchStaticLibrary
---
.../EntityLinker/MultiArchStaticLibrary.h | 71 +++++++
.../Core/EntityLinker/StaticLibrary.h | 1 +
.../Core/Model/BuildNamespace.h | 3 +-
.../Core/Model/PrivateFieldNames.def | 2 +
.../Core/Serialization/JSONFormat.h | 16 ++
.../Core/Serialization/SerializationFormat.h | 19 +-
.../Core/CMakeLists.txt | 1 +
.../Core/ModelStringConversions.h | 4 +
.../Serialization/JSONFormat/Artifact.cpp | 26 ++-
.../Serialization/JSONFormat/JSONFormatImpl.h | 8 +-
.../JSONFormat/MultiArchStaticLibrary.cpp | 174 ++++++++++++++++++
.../JSONFormat/StaticLibrary.cpp | 18 +-
.../rt-multi-arch-static-library-empty.json | 18 ++
...rt-multi-arch-static-library-nonempty.json | 39 ++++
.../ssaf-format/Artifact/round-trip.test | 13 ++
.../ssaf-format/Artifact/top-level.test | 2 +-
.../Inputs/duplicate-target-triple.json | 27 +++
.../Inputs/invalid-syntax.json | 1 +
.../Inputs/member-inner-error.json | 14 ++
.../Inputs/member-mismatched-type.json | 18 ++
.../Inputs/member-missing-type.json | 17 ++
.../Inputs/member-namespace-mismatch.json | 18 ++
.../Inputs/member-not-object.json | 10 +
.../Inputs/members-not-array.json | 8 +
.../Inputs/mismatched-type.json | 14 ++
.../Inputs/missing-members.json | 7 +
.../Inputs/missing-namespace.json | 4 +
.../Inputs/missing-type.json | 13 ++
.../Inputs/namespace-not-object.json | 5 +
.../Inputs/namespace-wrong-kind.json | 8 +
.../Inputs/not-json-extension.txt | 1 +
.../Inputs/not-object.json | 1 +
.../Inputs/rt-empty-members.json | 8 +
.../Inputs/rt-multiple-members.json | 27 +++
.../Inputs/rt-nonempty-members.json | 51 +++++
.../Inputs/rt-single-member.json | 18 ++
.../Inputs/rt-three-members.json | 36 ++++
.../Inputs/unsorted-members-input.json | 27 +++
.../Inputs/unsorted-three-members-input.json | 36 ++++
.../MultiArchStaticLibrary/io.test | 49 +++++
.../MultiArchStaticLibrary/permissions.test | 40 ++++
.../MultiArchStaticLibrary/round-trip.test | 55 ++++++
.../MultiArchStaticLibrary/top-level.test | 102 ++++++++++
clang/tools/clang-ssaf-format/SSAFFormat.cpp | 20 +-
.../TUSummaryExtractorFrontendActionTest.cpp | 18 ++
.../Registries/MockSerializationFormat.cpp | 13 ++
.../Registries/MockSerializationFormat.h | 6 +
.../ScalableStaticAnalysis/TestFixture.h | 1 +
48 files changed, 1065 insertions(+), 23 deletions(-)
create mode 100644 clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h
create mode 100644 clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/MultiArchStaticLibrary.cpp
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-static-library-empty.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-static-library-nonempty.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/duplicate-target-triple.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/invalid-syntax.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-inner-error.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-mismatched-type.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-missing-type.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-namespace-mismatch.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-not-object.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/members-not-array.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/mismatched-type.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/missing-members.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/missing-namespace.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/missing-type.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/namespace-not-object.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/namespace-wrong-kind.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/not-json-extension.txt
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/not-object.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-empty-members.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-multiple-members.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-nonempty-members.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-single-member.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-three-members.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/unsorted-members-input.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/unsorted-three-members-input.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/io.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/permissions.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/round-trip.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/top-level.test
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h
new file mode 100644
index 0000000000000..37147c98243c3
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h
@@ -0,0 +1,71 @@
+//===- MultiArchStaticLibrary.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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines the MultiArchStaticLibrary class, which represents a
+// multi-architecture wrapper around per-architecture StaticLibrary
+// instances (the SSAF analogue of a Mach-O fat static library, e.g. one
+// produced by `lipo -create` over per-arch `.a` files).
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSIS_CORE_ENTITYLINKER_MULTIARCHSTATICLIBRARY_H
+#define LLVM_CLANG_SCALABLESTATICANALYSIS_CORE_ENTITYLINKER_MULTIARCHSTATICLIBRARY_H
+
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h"
+#include "clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h"
+#include <memory>
+#include <set>
+#include <tuple>
+
+namespace clang::ssaf {
+
+/// Represents a multi-architecture static library.
+///
+/// A MultiArchStaticLibrary bundles per-architecture StaticLibrary
+/// members, mirroring the role of a Mach-O fat static library (the result
+/// of `lipo -create` over per-architecture `.a` files). All members
+/// represent the same logical library built for different architectures;
+/// the wrapper's \c Namespace identifies that shared library and every
+/// member's namespace must agree on its name.
+class MultiArchStaticLibrary {
+ friend class SerializationFormat;
+ friend class TestFixture;
+
+ /// Orders members by their TargetTriple's canonical enum components.
+ /// llvm::Triple's parser maps alias spellings to the same enum values
+ /// (e.g. "aarch64" and "arm64" both become Triple::aarch64), so a
+ /// tuple-of-enums compare is equivalent to comparing normalized triples
+ /// for identity, but without the per-call string allocation.
+ struct MemberByTargetTriple {
+ static auto key(const llvm::Triple &T) {
+ return std::make_tuple(T.getArch(), T.getSubArch(), T.getVendor(),
+ T.getOS(), T.getEnvironment(),
+ T.getObjectFormat());
+ }
+ bool operator()(const std::unique_ptr<StaticLibrary> &A,
+ const std::unique_ptr<StaticLibrary> &B) const {
+ return key(A->TargetTriple) < key(B->TargetTriple);
+ }
+ };
+
+ // The namespace identifying this multi-architecture library. Every member's
+ // Namespace must equal this Namespace.
+ BuildNamespace Namespace;
+
+ // StaticLibrary objects ordered by TargetTriple enum components. Two
+ // members with the same TargetTriple are not permitted.
+ std::set<std::unique_ptr<StaticLibrary>, MemberByTargetTriple> Members;
+
+public:
+ explicit MultiArchStaticLibrary(BuildNamespace Namespace)
+ : Namespace(std::move(Namespace)) {}
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSIS_CORE_ENTITYLINKER_MULTIARCHSTATICLIBRARY_H
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h
index ae8cfa4c7bacb..4ed34c9ec68cf 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h
@@ -39,6 +39,7 @@ namespace clang::ssaf {
/// tool never decodes per-entity payloads, and the linker consumes them
/// as-is during its selective inclusion pass.
class StaticLibrary {
+ friend class MultiArchStaticLibrary;
friend class SerializationFormat;
friend class TestFixture;
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h b/clang/include/clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h
index 1549b357ea50d..d503b16bf294c 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h
@@ -30,7 +30,8 @@ namespace clang::ssaf {
enum class BuildNamespaceKind : unsigned short {
CompilationUnit,
LinkUnit,
- StaticLibrary
+ StaticLibrary,
+ MultiArchStaticLibrary
};
/// Represents a single namespace in the build process.
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/Model/PrivateFieldNames.def b/clang/include/clang/ScalableStaticAnalysis/Core/Model/PrivateFieldNames.def
index b1c37823dc3fe..e43ff93140674 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/Model/PrivateFieldNames.def
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/Model/PrivateFieldNames.def
@@ -35,6 +35,8 @@ FIELD(LUSummaryEncoding, Data)
FIELD(LUSummaryEncoding, IdTable)
FIELD(LUSummaryEncoding, LinkageTable)
FIELD(LUSummaryEncoding, LUNamespace)
+FIELD(MultiArchStaticLibrary, Members)
+FIELD(MultiArchStaticLibrary, Namespace)
FIELD(NestedBuildNamespace, Namespaces)
FIELD(StaticLibrary, Members)
FIELD(StaticLibrary, Namespace)
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/Serialization/JSONFormat.h b/clang/include/clang/ScalableStaticAnalysis/Core/Serialization/JSONFormat.h
index d4b6cfc0e1690..094628ab7fa04 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/Serialization/JSONFormat.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/Serialization/JSONFormat.h
@@ -74,6 +74,12 @@ class JSONFormat final : public SerializationFormat {
llvm::Error writeStaticLibrary(const StaticLibrary &S,
llvm::StringRef Path) override;
+ llvm::Expected<MultiArchStaticLibrary>
+ readMultiArchStaticLibrary(llvm::StringRef Path) override;
+
+ llvm::Error writeMultiArchStaticLibrary(const MultiArchStaticLibrary &M,
+ llvm::StringRef Path) override;
+
llvm::Expected<WPASuite> readWPASuite(llvm::StringRef Path) override;
llvm::Error writeWPASuite(const WPASuite &Suite,
@@ -132,11 +138,21 @@ class JSONFormat final : public SerializationFormat {
/// See \c readTUSummaryFromObject for caller responsibilities.
llvm::Expected<StaticLibrary> readStaticLibraryFromObject(const Object &Root);
+ /// Parses a MultiArchStaticLibrary from an already-validated root JSON
+ /// object. See \c readTUSummaryFromObject for caller responsibilities.
+ llvm::Expected<MultiArchStaticLibrary>
+ readMultiArchStaticLibraryFromObject(const Object &Root);
+
/// Serializes a TUSummaryEncoding to a JSON object including its
/// self-describing \c type field. Used both by \c writeTUSummaryEncoding
/// and by the StaticLibrary writer to emit member entries.
Object tuSummaryEncodingToJSON(const TUSummaryEncoding &SE) const;
+ /// Serializes a StaticLibrary to a JSON object including its
+ /// self-describing \c type field. Used both by \c writeStaticLibrary
+ /// and by the MultiArchStaticLibrary writer to emit per-arch slices.
+ Object staticLibraryToJSON(const StaticLibrary &S) const;
+
/// Parses a WPASuite from an already-validated root JSON object. See
/// \c readTUSummaryFromObject for caller responsibilities.
llvm::Expected<WPASuite> readWPASuiteFromObject(const Object &Root);
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/Serialization/SerializationFormat.h b/clang/include/clang/ScalableStaticAnalysis/Core/Serialization/SerializationFormat.h
index 29b662bc0e380..a9c25de95a598 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/Serialization/SerializationFormat.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/Serialization/SerializationFormat.h
@@ -16,6 +16,7 @@
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h"
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h"
#include "clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h"
@@ -42,11 +43,12 @@ using Artifact = std::variant<TUSummary, LUSummary, WPASuite>;
/// artifacts but with their per-entity summary payloads left as opaque
/// format-specific encodings rather than fully resolved analysis results.
///
-/// \c StaticLibrary appears only in this variant: the archiver tool and
-/// the linker pass member payloads through without decoding them, so a
-/// fully decoded static-library shape would have no consumer.
-using ArtifactEncoding =
- std::variant<TUSummaryEncoding, LUSummaryEncoding, StaticLibrary>;
+/// \c StaticLibrary and \c MultiArchStaticLibrary appear only in this
+/// variant: the archiver/arch tools and the linker pass member payloads
+/// through without decoding them, so fully decoded shapes would have no
+/// consumer.
+using ArtifactEncoding = std::variant<TUSummaryEncoding, LUSummaryEncoding,
+ StaticLibrary, MultiArchStaticLibrary>;
/// Abstract base class for serialization formats.
class SerializationFormat {
@@ -106,6 +108,13 @@ class SerializationFormat {
virtual llvm::Error writeStaticLibrary(const StaticLibrary &S,
llvm::StringRef Path) = 0;
+ virtual llvm::Expected<MultiArchStaticLibrary>
+ readMultiArchStaticLibrary(llvm::StringRef Path) = 0;
+
+ virtual llvm::Error
+ writeMultiArchStaticLibrary(const MultiArchStaticLibrary &M,
+ llvm::StringRef Path) = 0;
+
virtual llvm::Expected<WPASuite> readWPASuite(llvm::StringRef Path) = 0;
virtual llvm::Error writeWPASuite(const WPASuite &Suite,
diff --git a/clang/lib/ScalableStaticAnalysis/Core/CMakeLists.txt b/clang/lib/ScalableStaticAnalysis/Core/CMakeLists.txt
index 0a0ce19d63732..47f018f61becf 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysis/Core/CMakeLists.txt
@@ -17,6 +17,7 @@ add_clang_library(clangScalableStaticAnalysisCore
Serialization/JSONFormat/JSONFormatImpl.cpp
Serialization/JSONFormat/LUSummary.cpp
Serialization/JSONFormat/LUSummaryEncoding.cpp
+ Serialization/JSONFormat/MultiArchStaticLibrary.cpp
Serialization/JSONFormat/StaticLibrary.cpp
Serialization/JSONFormat/TUSummary.cpp
Serialization/JSONFormat/TUSummaryEncoding.cpp
diff --git a/clang/lib/ScalableStaticAnalysis/Core/ModelStringConversions.h b/clang/lib/ScalableStaticAnalysis/Core/ModelStringConversions.h
index 8d3c932205740..9a4392bd72990 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/ModelStringConversions.h
+++ b/clang/lib/ScalableStaticAnalysis/Core/ModelStringConversions.h
@@ -38,6 +38,8 @@ inline llvm::StringRef buildNamespaceKindToString(BuildNamespaceKind BNK) {
return "LinkUnit";
case BuildNamespaceKind::StaticLibrary:
return "StaticLibrary";
+ case BuildNamespaceKind::MultiArchStaticLibrary:
+ return "MultiArchStaticLibrary";
}
llvm_unreachable("Unhandled BuildNamespaceKind variant");
}
@@ -52,6 +54,8 @@ buildNamespaceKindFromString(llvm::StringRef Str) {
return BuildNamespaceKind::LinkUnit;
if (Str == "StaticLibrary")
return BuildNamespaceKind::StaticLibrary;
+ if (Str == "MultiArchStaticLibrary")
+ return BuildNamespaceKind::MultiArchStaticLibrary;
return std::nullopt;
}
diff --git a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/Artifact.cpp b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/Artifact.cpp
index e5fc46a8ea952..a34f5cb718ab2 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/Artifact.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/Artifact.cpp
@@ -155,11 +155,21 @@ JSONFormat::readArtifactEncoding(llvm::StringRef Path) {
return ArtifactEncoding{std::move(*ExpectedStaticLibrary)};
}
- return ErrorBuilder::create(std::errc::invalid_argument,
- ErrorMessages::UnknownArtifactEncodingType,
- *ExpectedType, JSONTypeKey,
- JSONTypeValueTUSummary, JSONTypeValueLUSummary,
- JSONTypeValueStaticLibrary)
+ if (*ExpectedType == JSONTypeValueMultiArchStaticLibrary) {
+ auto ExpectedM = readMultiArchStaticLibraryFromObject(*RootObjectPtr);
+ if (!ExpectedM) {
+ return ErrorBuilder::wrap(ExpectedM.takeError())
+ .context(ErrorMessages::ReadingFromFile, "ArtifactEncoding", Path)
+ .build();
+ }
+ return ArtifactEncoding{std::move(*ExpectedM)};
+ }
+
+ return ErrorBuilder::create(
+ std::errc::invalid_argument,
+ ErrorMessages::UnknownArtifactEncodingType, *ExpectedType,
+ JSONTypeKey, JSONTypeValueTUSummary, JSONTypeValueLUSummary,
+ JSONTypeValueStaticLibrary, JSONTypeValueMultiArchStaticLibrary)
.context(ErrorMessages::ReadingFromFile, "ArtifactEncoding", Path)
.build();
}
@@ -173,11 +183,13 @@ llvm::Error JSONFormat::writeArtifactEncoding(const ArtifactEncoding &E,
return writeTUSummaryEncoding(Enc, Path);
} else if constexpr (std::is_same_v<T, LUSummaryEncoding>) {
return writeLUSummaryEncoding(Enc, Path);
+ } else if constexpr (std::is_same_v<T, StaticLibrary>) {
+ return writeStaticLibrary(Enc, Path);
} else {
static_assert(
- std::is_same_v<T, StaticLibrary>,
+ std::is_same_v<T, MultiArchStaticLibrary>,
"ArtifactEncoding visitor must cover all variant alternatives");
- return writeStaticLibrary(Enc, Path);
+ return writeMultiArchStaticLibrary(Enc, Path);
}
},
E);
diff --git a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.h b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.h
index e83a8dabac113..9cfe67e20d1d7 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.h
+++ b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.h
@@ -85,7 +85,8 @@ inline constexpr const char *MismatchedSummaryType =
inline constexpr const char *UnknownArtifactType =
"unknown value '{0}' for field '{1}': expected '{2}', '{3}', or '{4}'";
inline constexpr const char *UnknownArtifactEncodingType =
- "unknown value '{0}' for field '{1}': expected '{2}', '{3}', or '{4}'";
+ "unknown value '{0}' for field '{1}': expected '{2}', '{3}', '{4}', or "
+ "'{5}'";
inline constexpr const char *FailedToDeserializeEntitySummaryNoFormatInfo =
"failed to deserialize EntitySummary: no FormatInfo registered for '{0}'";
@@ -152,6 +153,11 @@ inline constexpr const char *JSONTypeValueLUSummary = "LUSummary";
/// Value written to \c JSONTypeKey for serialized \c StaticLibrary files.
inline constexpr const char *JSONTypeValueStaticLibrary = "StaticLibrary";
+/// Value written to \c JSONTypeKey for serialized \c MultiArchStaticLibrary
+/// files.
+inline constexpr const char *JSONTypeValueMultiArchStaticLibrary =
+ "MultiArchStaticLibrary";
+
/// Value written to \c JSONTypeKey for serialized \c WPASuite files.
inline constexpr const char *JSONTypeValueWPASuite = "WPASuite";
diff --git a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/MultiArchStaticLibrary.cpp b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/MultiArchStaticLibrary.cpp
new file mode 100644
index 0000000000000..e8971a2f34be3
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/MultiArchStaticLibrary.cpp
@@ -0,0 +1,174 @@
+//===- MultiArchStaticLibrary.cpp -----------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "JSONFormatImpl.h"
+
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h"
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h"
+#include "llvm/TargetParser/Triple.h"
+
+namespace clang::ssaf {
+
+//----------------------------------------------------------------------------
+// MultiArchStaticLibrary
+//----------------------------------------------------------------------------
+
+llvm::Expected<MultiArchStaticLibrary>
+JSONFormat::readMultiArchStaticLibrary(llvm::StringRef Path) {
+ auto ExpectedJSON = readJSON(Path);
+ if (!ExpectedJSON) {
+ return ErrorBuilder::wrap(ExpectedJSON.takeError())
+ .context(ErrorMessages::ReadingFromFile, "MultiArchStaticLibrary", Path)
+ .build();
+ }
+
+ Object *RootObjectPtr = ExpectedJSON->getAsObject();
+ if (!RootObjectPtr) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObject,
+ "MultiArchStaticLibrary", "object")
+ .context(ErrorMessages::ReadingFromFile, "MultiArchStaticLibrary", Path)
+ .build();
+ }
+
+ if (auto Err = checkSummaryType(*RootObjectPtr,
+ JSONTypeValueMultiArchStaticLibrary)) {
+ return ErrorBuilder::wrap(std::move(Err))
+ .context(ErrorMessages::ReadingFromFile, "MultiArchStaticLibrary", Path)
+ .build();
+ }
+
+ auto ExpectedM = readMultiArchStaticLibraryFromObject(*RootObjectPtr);
+ if (!ExpectedM) {
+ return ErrorBuilder::wrap(ExpectedM.takeError())
+ .context(ErrorMessages::ReadingFromFile, "MultiArchStaticLibrary", Path)
+ .build();
+ }
+
+ return std::move(*ExpectedM);
+}
+
+llvm::Expected<MultiArchStaticLibrary>
+JSONFormat::readMultiArchStaticLibraryFromObject(const Object &RootObject) {
+ const Object *NamespaceObject = RootObject.getObject("namespace");
+ if (!NamespaceObject) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "BuildNamespace", "namespace", "object")
+ .build();
+ }
+
+ auto ExpectedNamespace = buildNamespaceFromJSON(*NamespaceObject);
+ if (!ExpectedNamespace) {
+ return ErrorBuilder::wrap(ExpectedNamespace.takeError())
+ .context(ErrorMessages::ReadingFromField, "BuildNamespace", "namespace")
+ .build();
+ }
+
+ if (getKind(*ExpectedNamespace) !=
+ BuildNamespaceKind::MultiArchStaticLibrary) {
+ return ErrorBuilder::create(
+ std::errc::invalid_argument,
+ ErrorMessages::MismatchedSummaryType,
+ buildNamespaceKindToJSON(
+ BuildNamespaceKind::MultiArchStaticLibrary),
+ "namespace.kind",
+ buildNamespaceKindToJSON(getKind(*ExpectedNamespace)))
+ .build();
+ }
+
+ const Array *MembersArray = RootObject.getArray("members");
+ if (!MembersArray) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "MultiArchStaticLibrary members", "members",
+ "array")
+ .build();
+ }
+
+ MultiArchStaticLibrary M(std::move(*ExpectedNamespace));
+ auto &Members = getMembers(M);
+ const auto &ExpectedName = getName(getNamespace(M));
+
+ for (const auto &[Index, MemberValue] : llvm::enumerate(*MembersArray)) {
+ const Object *MemberObject = MemberValue.getAsObject();
+ if (!MemberObject) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtIndex,
+ "MultiArchStaticLibrary member", Index,
+ "object")
+ .build();
+ }
+
+ if (auto Err =
+ checkSummaryType(*MemberObject, JSONTypeValueStaticLibrary)) {
+ return ErrorBuilder::wrap(std::move(Err))
+ .context(ErrorMessages::ReadingFromIndex,
+ "MultiArchStaticLibrary member", Index)
+ .build();
+ }
+
+ auto ExpectedMember = readStaticLibraryFromObject(*MemberObject);
+ if (!ExpectedMember) {
+ return ErrorBuilder::wrap(ExpectedMember.takeError())
+ .context(ErrorMessages::ReadingFromIndex,
+ "MultiArchStaticLibrary member", Index)
+ .build();
+ }
+
+ const auto &MemberName = getName(getNamespace(*ExpectedMember));
+ if (MemberName != ExpectedName) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::MismatchedSummaryType,
+ ExpectedName, "namespace.name", MemberName)
+ .context(ErrorMessages::ReadingFromIndex,
+ "MultiArchStaticLibrary member", Index)
+ .build();
+ }
+
+ auto [It, Inserted] = Members.insert(
+ std::make_unique<StaticLibrary>(std::move(*ExpectedMember)));
+ if (!Inserted) {
+ auto MemberTriple = llvm::Triple::normalize(getTargetTriple(**It).str());
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedInsertionOnDuplication,
+ "MultiArchStaticLibrary member", Index,
+ MemberTriple)
+ .build();
+ }
+ }
+
+ return std::move(M);
+}
+
+llvm::Error
+JSONFormat::writeMultiArchStaticLibrary(const MultiArchStaticLibrary &M,
+ llvm::StringRef Path) {
+ Object RootObject;
+
+ RootObject[JSONTypeKey] = JSONTypeValueMultiArchStaticLibrary;
+
+ RootObject["namespace"] = buildNamespaceToJSON(getNamespace(M));
+
+ Array MembersArray;
+ MembersArray.reserve(getMembers(M).size());
+ for (const auto &Member : getMembers(M)) {
+ MembersArray.push_back(staticLibraryToJSON(*Member));
+ }
+ RootObject["members"] = std::move(MembersArray);
+
+ if (auto Error = writeJSON(std::move(RootObject), Path)) {
+ return ErrorBuilder::wrap(std::move(Error))
+ .context(ErrorMessages::WritingToFile, "MultiArchStaticLibrary", Path)
+ .build();
+ }
+
+ return llvm::Error::success();
+}
+
+} // namespace clang::ssaf
diff --git a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/StaticLibrary.cpp b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/StaticLibrary.cpp
index b34c63b185b0e..d4538d19aed24 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/StaticLibrary.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/StaticLibrary.cpp
@@ -164,6 +164,16 @@ JSONFormat::readStaticLibraryFromObject(const Object &RootObject) {
llvm::Error JSONFormat::writeStaticLibrary(const StaticLibrary &S,
llvm::StringRef Path) {
+ if (auto Error = writeJSON(staticLibraryToJSON(S), Path)) {
+ return ErrorBuilder::wrap(std::move(Error))
+ .context(ErrorMessages::WritingToFile, "StaticLibrary", Path)
+ .build();
+ }
+
+ return llvm::Error::success();
+}
+
+Object JSONFormat::staticLibraryToJSON(const StaticLibrary &S) const {
Object RootObject;
RootObject[JSONTypeKey] = JSONTypeValueStaticLibrary;
@@ -180,13 +190,7 @@ llvm::Error JSONFormat::writeStaticLibrary(const StaticLibrary &S,
}
RootObject["members"] = std::move(MembersArray);
- if (auto Error = writeJSON(std::move(RootObject), Path)) {
- return ErrorBuilder::wrap(std::move(Error))
- .context(ErrorMessages::WritingToFile, "StaticLibrary", Path)
- .build();
- }
-
- return llvm::Error::success();
+ return RootObject;
}
} // namespace clang::ssaf
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-static-library-empty.json b/clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-static-library-empty.json
new file mode 100644
index 0000000000000..dfe68cef81677
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-static-library-empty.json
@@ -0,0 +1,18 @@
+{
+ "members": [
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-static-library-nonempty.json b/clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-static-library-nonempty.json
new file mode 100644
index 0000000000000..ea877c68be3fb
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-static-library-nonempty.json
@@ -0,0 +1,39 @@
+{
+ "members": [
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+ },
+ {
+ "members": [
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "target_triple": "x86_64-apple-macosx",
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "a.cpp"
+ },
+ "type": "TUSummary"
+ }
+ ],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "x86_64-apple-macosx",
+ "type": "StaticLibrary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Artifact/round-trip.test b/clang/test/Analysis/Scalable/ssaf-format/Artifact/round-trip.test
index d6d67826e4847..32214cabbc36b 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/Artifact/round-trip.test
+++ b/clang/test/Analysis/Scalable/ssaf-format/Artifact/round-trip.test
@@ -47,3 +47,16 @@
// RUN: diff %S/Inputs/rt-static-library-empty.json %t/rt-static-library-empty-enc.json
// RUN: clang-ssaf-format --type auto --encoding %S/Inputs/rt-static-library-empty.json -o %t/rt-static-library-empty-enc-explicit.json
// RUN: diff %S/Inputs/rt-static-library-empty.json %t/rt-static-library-empty-enc-explicit.json
+
+// RUN: clang-ssaf-format --encoding %S/Inputs/rt-multi-arch-static-library-empty.json -o %t/rt-multi-arch-static-library-empty-enc.json
+// RUN: diff %S/Inputs/rt-multi-arch-static-library-empty.json %t/rt-multi-arch-static-library-empty-enc.json
+// RUN: clang-ssaf-format --type auto --encoding %S/Inputs/rt-multi-arch-static-library-empty.json -o %t/rt-multi-arch-static-library-empty-enc-explicit.json
+// RUN: diff %S/Inputs/rt-multi-arch-static-library-empty.json %t/rt-multi-arch-static-library-empty-enc-explicit.json
+
+// Non-empty MultiArchStaticLibrary routed through the auto-mode --encoding
+// dispatcher exercises both arch-slice writers and the inner TUSummaryEncoding
+// path inside the StaticLibrary member.
+// RUN: clang-ssaf-format --encoding %S/Inputs/rt-multi-arch-static-library-nonempty.json -o %t/rt-multi-arch-static-library-nonempty-enc.json
+// RUN: diff %S/Inputs/rt-multi-arch-static-library-nonempty.json %t/rt-multi-arch-static-library-nonempty-enc.json
+// RUN: clang-ssaf-format --type auto --encoding %S/Inputs/rt-multi-arch-static-library-nonempty.json -o %t/rt-multi-arch-static-library-nonempty-enc-explicit.json
+// RUN: diff %S/Inputs/rt-multi-arch-static-library-nonempty.json %t/rt-multi-arch-static-library-nonempty-enc-explicit.json
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Artifact/top-level.test b/clang/test/Analysis/Scalable/ssaf-format/Artifact/top-level.test
index 7838f3a90534c..5f8333ca52b37 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/Artifact/top-level.test
+++ b/clang/test/Analysis/Scalable/ssaf-format/Artifact/top-level.test
@@ -40,4 +40,4 @@
// RUN: not clang-ssaf-format --type auto --encoding %S/Inputs/unknown-type.json 2>&1 \
// RUN: | FileCheck %s --match-full-lines --check-prefix=ENCODING-UNKNOWN-TYPE
// ENCODING-UNKNOWN-TYPE: clang-ssaf-format: error: reading ArtifactEncoding from file '{{.*}}unknown-type.json'
-// ENCODING-UNKNOWN-TYPE-NEXT: unknown value 'Mystery' for field 'type': expected 'TUSummary', 'LUSummary', or 'StaticLibrary'
+// ENCODING-UNKNOWN-TYPE-NEXT: unknown value 'Mystery' for field 'type': expected 'TUSummary', 'LUSummary', 'StaticLibrary', or 'MultiArchStaticLibrary'
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/duplicate-target-triple.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/duplicate-target-triple.json
new file mode 100644
index 0000000000000..6c07b51e9823c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/duplicate-target-triple.json
@@ -0,0 +1,27 @@
+{
+ "members": [
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+ },
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/invalid-syntax.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/invalid-syntax.json
new file mode 100644
index 0000000000000..b0e13f61aa06e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/invalid-syntax.json
@@ -0,0 +1 @@
+{ invalid json }
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-inner-error.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-inner-error.json
new file mode 100644
index 0000000000000..e71e2a42cf8cb
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-inner-error.json
@@ -0,0 +1,14 @@
+{
+ "members": [
+ {
+ "members": [],
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-mismatched-type.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-mismatched-type.json
new file mode 100644
index 0000000000000..5e92521f02260
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-mismatched-type.json
@@ -0,0 +1,18 @@
+{
+ "members": [
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "TUSummary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-missing-type.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-missing-type.json
new file mode 100644
index 0000000000000..6422b9ce326e5
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-missing-type.json
@@ -0,0 +1,17 @@
+{
+ "members": [
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "arm64-apple-macosx"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-namespace-mismatch.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-namespace-mismatch.json
new file mode 100644
index 0000000000000..8b7f6cacd5937
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-namespace-mismatch.json
@@ -0,0 +1,18 @@
+{
+ "members": [
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libbar"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-not-object.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-not-object.json
new file mode 100644
index 0000000000000..ccaa32242d329
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/member-not-object.json
@@ -0,0 +1,10 @@
+{
+ "members": [
+ "not an object"
+ ],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/members-not-array.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/members-not-array.json
new file mode 100644
index 0000000000000..cb68c661a1d3a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/members-not-array.json
@@ -0,0 +1,8 @@
+{
+ "members": {},
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/mismatched-type.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/mismatched-type.json
new file mode 100644
index 0000000000000..390eb2a4cb0a9
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/mismatched-type.json
@@ -0,0 +1,14 @@
+{
+ "members": {
+ "arm64": {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+ }
+ },
+ "type": "StaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/missing-members.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/missing-members.json
new file mode 100644
index 0000000000000..d3e3ac408164e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/missing-members.json
@@ -0,0 +1,7 @@
+{
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/missing-namespace.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/missing-namespace.json
new file mode 100644
index 0000000000000..a04e97923e36d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/missing-namespace.json
@@ -0,0 +1,4 @@
+{
+ "members": [],
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/missing-type.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/missing-type.json
new file mode 100644
index 0000000000000..ea946be0839aa
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/missing-type.json
@@ -0,0 +1,13 @@
+{
+ "members": {
+ "arm64": {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+ }
+ }
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/namespace-not-object.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/namespace-not-object.json
new file mode 100644
index 0000000000000..2703072e852af
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/namespace-not-object.json
@@ -0,0 +1,5 @@
+{
+ "members": [],
+ "namespace": "libfoo",
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/namespace-wrong-kind.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/namespace-wrong-kind.json
new file mode 100644
index 0000000000000..e5495e2551700
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/namespace-wrong-kind.json
@@ -0,0 +1,8 @@
+{
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/not-json-extension.txt b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/not-json-extension.txt
new file mode 100644
index 0000000000000..0967ef424bce6
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/not-json-extension.txt
@@ -0,0 +1 @@
+{}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/not-object.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/not-object.json
new file mode 100644
index 0000000000000..fe51488c7066f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/not-object.json
@@ -0,0 +1 @@
+[]
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-empty-members.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-empty-members.json
new file mode 100644
index 0000000000000..13a106614f1e0
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-empty-members.json
@@ -0,0 +1,8 @@
+{
+ "members": [],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-multiple-members.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-multiple-members.json
new file mode 100644
index 0000000000000..ab9f2744b8e78
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-multiple-members.json
@@ -0,0 +1,27 @@
+{
+ "members": [
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+ },
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "x86_64-apple-macosx",
+ "type": "StaticLibrary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-nonempty-members.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-nonempty-members.json
new file mode 100644
index 0000000000000..5a76aef859774
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-nonempty-members.json
@@ -0,0 +1,51 @@
+{
+ "members": [
+ {
+ "members": [
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "target_triple": "arm64-apple-macosx",
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "a.cpp"
+ },
+ "type": "TUSummary"
+ }
+ ],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+ },
+ {
+ "members": [
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "target_triple": "x86_64-apple-macosx",
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "a.cpp"
+ },
+ "type": "TUSummary"
+ }
+ ],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "x86_64-apple-macosx",
+ "type": "StaticLibrary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-single-member.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-single-member.json
new file mode 100644
index 0000000000000..dfe68cef81677
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-single-member.json
@@ -0,0 +1,18 @@
+{
+ "members": [
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-three-members.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-three-members.json
new file mode 100644
index 0000000000000..2b85071edaecb
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/rt-three-members.json
@@ -0,0 +1,36 @@
+{
+ "members": [
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+ },
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "arm64_32-apple-macosx",
+ "type": "StaticLibrary"
+ },
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "x86_64-apple-macosx",
+ "type": "StaticLibrary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/unsorted-members-input.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/unsorted-members-input.json
new file mode 100644
index 0000000000000..b7a23875432f2
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/unsorted-members-input.json
@@ -0,0 +1,27 @@
+{
+ "members": [
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "x86_64-apple-macosx",
+ "type": "StaticLibrary"
+ },
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/unsorted-three-members-input.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/unsorted-three-members-input.json
new file mode 100644
index 0000000000000..74ee6fe49e82c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/Inputs/unsorted-three-members-input.json
@@ -0,0 +1,36 @@
+{
+ "members": [
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "x86_64-apple-macosx",
+ "type": "StaticLibrary"
+ },
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+ },
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libfoo"
+ },
+ "target_triple": "arm64_32-apple-macosx",
+ "type": "StaticLibrary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/io.test b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/io.test
new file mode 100644
index 0000000000000..56f5bdd2021c0
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/io.test
@@ -0,0 +1,49 @@
+// File-I/O error tests: readJSON() input validation and writeJSON() output
+// validation for clang-ssaf-format --type multi-arch-static-library.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// ============================================================================
+// readJSON() errors
+// ============================================================================
+
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/nonexistent.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NONEXISTENT-FILE
+// NONEXISTENT-FILE: clang-ssaf-format: error: failed to validate path '{{.*}}nonexistent.json': Path does not exist
+
+// RUN: mkdir -p %t/test-directory.json
+// RUN: not clang-ssaf-format --type multi-arch-static-library %t/test-directory.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=PATH-IS-DIRECTORY
+// PATH-IS-DIRECTORY: clang-ssaf-format: error: failed to validate path '{{.*}}test-directory.json': Path is not a file
+
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/not-json-extension.txt 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NOT-JSON-EXTENSION
+// NOT-JSON-EXTENSION: clang-ssaf-format: error: failed to validate path '{{.*}}not-json-extension.txt': No format registered for extension 'txt'
+
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/invalid-syntax.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=INVALID-SYNTAX
+// INVALID-SYNTAX: clang-ssaf-format: error: reading MultiArchStaticLibrary from file '{{.*}}invalid-syntax.json'
+// INVALID-SYNTAX-NEXT: {{.*}}Expected object key
+
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/not-object.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NOT-OBJECT
+// NOT-OBJECT: clang-ssaf-format: error: reading MultiArchStaticLibrary from file '{{.*}}not-object.json'
+// NOT-OBJECT-NEXT: failed to read MultiArchStaticLibrary: expected JSON object
+
+// ============================================================================
+// writeJSON() errors
+// ============================================================================
+
+// RUN: echo '{}' > %t/existing.json
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/rt-single-member.json -o %t/existing.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=WRITE-FILE-ALREADY-EXISTS
+// WRITE-FILE-ALREADY-EXISTS: clang-ssaf-format: error: failed to validate path '{{.*}}existing.json': File already exists
+
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/rt-single-member.json -o %t/nonexistent-dir/test.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=WRITE-PARENT-DIRECTORY-NOT-FOUND
+// WRITE-PARENT-DIRECTORY-NOT-FOUND: clang-ssaf-format: error: failed to validate path '{{.*}}nonexistent-dir{{.}}test.json': Parent directory does not exist
+
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/rt-single-member.json -o %t/test.txt 2>&1 \
+// RUN: | FileCheck %s --check-prefix=WRITE-NOT-JSON-EXTENSION
+// WRITE-NOT-JSON-EXTENSION: clang-ssaf-format: error: failed to validate path '{{.*}}test.txt': No format registered for extension 'txt'
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/permissions.test b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/permissions.test
new file mode 100644
index 0000000000000..821099842135b
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/permissions.test
@@ -0,0 +1,40 @@
+// Tests for clang-ssaf-format --type multi-arch-static-library that
+// require filesystem permission support (symlinks, chmod).
+
+// UNSUPPORTED: system-windows
+// REQUIRES: non-root-user
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// ============================================================================
+// Broken symlink
+// ============================================================================
+
+// RUN: ln -sf %t/nonexistent-target.json %t/broken-symlink.json
+// RUN: not clang-ssaf-format --type multi-arch-static-library %t/broken-symlink.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=BROKEN-SYMLINK
+// BROKEN-SYMLINK: clang-ssaf-format: error: failed to validate path '{{.*}}broken-symlink.json': Path does not exist
+
+// ============================================================================
+// No read permission
+// ============================================================================
+
+// RUN: cp %S/Inputs/rt-single-member.json %t/no-read-permission.json
+// RUN: chmod -r %t/no-read-permission.json
+// RUN: not clang-ssaf-format --type multi-arch-static-library %t/no-read-permission.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-READ-PERMISSION
+// RUN: chmod +r %t/no-read-permission.json
+// NO-READ-PERMISSION: clang-ssaf-format: error: reading MultiArchStaticLibrary from file '{{.*}}no-read-permission.json'
+// NO-READ-PERMISSION-NEXT: failed to read file '{{.*}}no-read-permission.json': {{.*}}
+
+// ============================================================================
+// Write to directory without write permission
+// ============================================================================
+
+// RUN: mkdir -p %t/write-protected-dir
+// RUN: chmod -w %t/write-protected-dir
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/rt-single-member.json -o %t/write-protected-dir/test.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=WRITE-STREAM-OPEN-FAILURE
+// RUN: chmod +w %t/write-protected-dir
+// WRITE-STREAM-OPEN-FAILURE: clang-ssaf-format: error: failed to validate path '{{.*}}write-protected-dir{{.}}test.json': Parent directory is not writable
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/round-trip.test b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/round-trip.test
new file mode 100644
index 0000000000000..502ed54d8f1f1
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/round-trip.test
@@ -0,0 +1,55 @@
+// Round-trip tests: read a MultiArchStaticLibrary JSON input, write it
+// back out, and diff the result against the original.
+//
+// MultiArchStaticLibrary has only an encoding representation, so
+// --encoding is a no-op for --type multi-arch-static-library and both
+// invocations must produce the same output.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// An empty members array is a valid (if degenerate) MultiArchStaticLibrary.
+// RUN: clang-ssaf-format --type multi-arch-static-library %S/Inputs/rt-empty-members.json -o %t/rt-empty-members.json
+// RUN: diff %S/Inputs/rt-empty-members.json %t/rt-empty-members.json
+// RUN: clang-ssaf-format --type multi-arch-static-library --encoding %S/Inputs/rt-empty-members.json -o %t/rt-empty-members-enc.json
+// RUN: diff %S/Inputs/rt-empty-members.json %t/rt-empty-members-enc.json
+
+// RUN: clang-ssaf-format --type multi-arch-static-library %S/Inputs/rt-single-member.json -o %t/rt-single-member.json
+// RUN: diff %S/Inputs/rt-single-member.json %t/rt-single-member.json
+// RUN: clang-ssaf-format --type multi-arch-static-library --encoding %S/Inputs/rt-single-member.json -o %t/rt-single-member-enc.json
+// RUN: diff %S/Inputs/rt-single-member.json %t/rt-single-member-enc.json
+
+// RUN: clang-ssaf-format --type multi-arch-static-library %S/Inputs/rt-multiple-members.json -o %t/rt-multiple-members.json
+// RUN: diff %S/Inputs/rt-multiple-members.json %t/rt-multiple-members.json
+// RUN: clang-ssaf-format --type multi-arch-static-library --encoding %S/Inputs/rt-multiple-members.json -o %t/rt-multiple-members-enc.json
+// RUN: diff %S/Inputs/rt-multiple-members.json %t/rt-multiple-members-enc.json
+
+// RUN: clang-ssaf-format --type multi-arch-static-library %S/Inputs/rt-nonempty-members.json -o %t/rt-nonempty-members.json
+// RUN: diff %S/Inputs/rt-nonempty-members.json %t/rt-nonempty-members.json
+// RUN: clang-ssaf-format --type multi-arch-static-library --encoding %S/Inputs/rt-nonempty-members.json -o %t/rt-nonempty-members-enc.json
+// RUN: diff %S/Inputs/rt-nonempty-members.json %t/rt-nonempty-members-enc.json
+
+// ============================================================================
+// Member sort order
+// ============================================================================
+//
+// Members are stored in a std::set keyed by TargetTriple enum components,
+// so the writer emits them in enum-tuple order regardless of input order.
+// unsorted-members-input.json lists the members as x86_64, then arm64;
+// the writer must produce the canonical (arm64, x86_64) ordering, byte
+// identical to rt-multiple-members.json.
+
+// RUN: clang-ssaf-format --type multi-arch-static-library %S/Inputs/unsorted-members-input.json -o %t/unsorted-members-output.json
+// RUN: diff %S/Inputs/rt-multiple-members.json %t/unsorted-members-output.json
+// RUN: clang-ssaf-format --type multi-arch-static-library --encoding %S/Inputs/unsorted-members-input.json -o %t/unsorted-members-output-enc.json
+// RUN: diff %S/Inputs/rt-multiple-members.json %t/unsorted-members-output-enc.json
+
+// 3-member ordering: confirms the sort isn't an accidental two-element
+// swap. The unsorted input lists x86_64, arm64, arm64_32; the writer
+// must produce (arm64, arm64_32, x86_64) — byte identical to
+// rt-three-members.json.
+
+// RUN: clang-ssaf-format --type multi-arch-static-library %S/Inputs/rt-three-members.json -o %t/rt-three-members.json
+// RUN: diff %S/Inputs/rt-three-members.json %t/rt-three-members.json
+// RUN: clang-ssaf-format --type multi-arch-static-library %S/Inputs/unsorted-three-members-input.json -o %t/unsorted-three-members-output.json
+// RUN: diff %S/Inputs/rt-three-members.json %t/unsorted-three-members-output.json
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/top-level.test b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/top-level.test
new file mode 100644
index 0000000000000..d233465f9fd62
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchStaticLibrary/top-level.test
@@ -0,0 +1,102 @@
+// Top-level MultiArchStaticLibrary structure tests: type discriminator,
+// namespace identity, members array, and per-member validation including
+// duplicate-target-triple and namespace-mismatch rejection.
+
+// ============================================================================
+// readMultiArchStaticLibrary() / writeMultiArchStaticLibrary() type-field
+// errors
+// ============================================================================
+
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/missing-type.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MISSING-TYPE
+// MISSING-TYPE: clang-ssaf-format: error: reading MultiArchStaticLibrary from file '{{.*}}missing-type.json'
+// MISSING-TYPE-NEXT: failed to read summary type from field 'type': expected JSON string
+
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/mismatched-type.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MISMATCHED-TYPE
+// MISMATCHED-TYPE: clang-ssaf-format: error: reading MultiArchStaticLibrary from file '{{.*}}mismatched-type.json'
+// MISMATCHED-TYPE-NEXT: expected 'MultiArchStaticLibrary' for field 'type' but got 'StaticLibrary'
+
+// ============================================================================
+// namespace field errors
+// ============================================================================
+
+// The top-level namespace is required.
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/missing-namespace.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MISSING-NAMESPACE
+// MISSING-NAMESPACE: clang-ssaf-format: error: reading MultiArchStaticLibrary from file '{{.*}}missing-namespace.json'
+// MISSING-NAMESPACE-NEXT: failed to read BuildNamespace from field 'namespace': expected JSON object
+
+// The namespace field must be a JSON object, not a string.
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/namespace-not-object.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NAMESPACE-NOT-OBJECT
+// NAMESPACE-NOT-OBJECT: clang-ssaf-format: error: reading MultiArchStaticLibrary from file '{{.*}}namespace-not-object.json'
+// NAMESPACE-NOT-OBJECT-NEXT: failed to read BuildNamespace from field 'namespace': expected JSON object
+
+// The namespace kind must be MultiArchStaticLibrary.
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/namespace-wrong-kind.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NAMESPACE-WRONG-KIND
+// NAMESPACE-WRONG-KIND: clang-ssaf-format: error: reading MultiArchStaticLibrary from file '{{.*}}namespace-wrong-kind.json'
+// NAMESPACE-WRONG-KIND-NEXT: expected 'MultiArchStaticLibrary' for field 'namespace.kind' but got 'StaticLibrary'
+
+// ============================================================================
+// members field errors
+// ============================================================================
+
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/missing-members.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MISSING-MEMBERS
+// MISSING-MEMBERS: clang-ssaf-format: error: reading MultiArchStaticLibrary from file '{{.*}}missing-members.json'
+// MISSING-MEMBERS-NEXT: failed to read MultiArchStaticLibrary members from field 'members': expected JSON array
+
+// The members field must be a JSON array, not an object.
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/members-not-array.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MEMBERS-NOT-ARRAY
+// MEMBERS-NOT-ARRAY: clang-ssaf-format: error: reading MultiArchStaticLibrary from file '{{.*}}members-not-array.json'
+// MEMBERS-NOT-ARRAY-NEXT: failed to read MultiArchStaticLibrary members from field 'members': expected JSON array
+
+// ============================================================================
+// per-member errors
+// ============================================================================
+
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/member-not-object.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MEMBER-NOT-OBJECT
+// MEMBER-NOT-OBJECT: clang-ssaf-format: error: reading MultiArchStaticLibrary from file '{{.*}}member-not-object.json'
+// MEMBER-NOT-OBJECT-NEXT: failed to read MultiArchStaticLibrary member from index '0': expected JSON object
+
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/member-missing-type.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MEMBER-MISSING-TYPE
+// MEMBER-MISSING-TYPE: clang-ssaf-format: error: reading MultiArchStaticLibrary from file '{{.*}}member-missing-type.json'
+// MEMBER-MISSING-TYPE-NEXT: reading MultiArchStaticLibrary member from index '0'
+// MEMBER-MISSING-TYPE-NEXT: failed to read summary type from field 'type': expected JSON string
+
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/member-mismatched-type.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MEMBER-MISMATCHED-TYPE
+// MEMBER-MISMATCHED-TYPE: clang-ssaf-format: error: reading MultiArchStaticLibrary from file '{{.*}}member-mismatched-type.json'
+// MEMBER-MISMATCHED-TYPE-NEXT: reading MultiArchStaticLibrary member from index '0'
+// MEMBER-MISMATCHED-TYPE-NEXT: expected 'StaticLibrary' for field 'type' but got 'TUSummary'
+
+// All members must share the wrapper's namespace name (same logical
+// library, different architectures). A member whose Namespace.name differs
+// from the wrapper's is rejected.
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/member-namespace-mismatch.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MEMBER-NAMESPACE-MISMATCH
+// MEMBER-NAMESPACE-MISMATCH: clang-ssaf-format: error: reading MultiArchStaticLibrary from file '{{.*}}member-namespace-mismatch.json'
+// MEMBER-NAMESPACE-MISMATCH-NEXT: reading MultiArchStaticLibrary member from index '0'
+// MEMBER-NAMESPACE-MISMATCH-NEXT: expected 'libfoo' for field 'namespace.name' but got 'libbar'
+
+// Members are identified by their full target_triple. Two members sharing
+// the same target_triple are rejected.
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/duplicate-target-triple.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=DUPLICATE-TARGET-TRIPLE
+// DUPLICATE-TARGET-TRIPLE: clang-ssaf-format: error: reading MultiArchStaticLibrary from file '{{.*}}duplicate-target-triple.json'
+// DUPLICATE-TARGET-TRIPLE-NEXT: failed to insert MultiArchStaticLibrary member at index '1': encountered duplicate 'arm64-apple-macosx'
+
+// Verifies that errors raised inside readStaticLibraryFromObject (here, a
+// missing namespace on the member) are properly wrapped with the
+// "reading MultiArchStaticLibrary member from index 'N'" context, then
+// with the outer "reading MultiArchStaticLibrary from file '...'" context.
+// RUN: not clang-ssaf-format --type multi-arch-static-library %S/Inputs/member-inner-error.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MEMBER-INNER-ERROR
+// MEMBER-INNER-ERROR: clang-ssaf-format: error: reading MultiArchStaticLibrary from file '{{.*}}member-inner-error.json'
+// MEMBER-INNER-ERROR-NEXT: reading MultiArchStaticLibrary member from index '0'
+// MEMBER-INNER-ERROR-NEXT: failed to read BuildNamespace from field 'namespace': expected JSON object
diff --git a/clang/tools/clang-ssaf-format/SSAFFormat.cpp b/clang/tools/clang-ssaf-format/SSAFFormat.cpp
index 5b94da384cb7d..b0f123f75be2a 100644
--- a/clang/tools/clang-ssaf-format/SSAFFormat.cpp
+++ b/clang/tools/clang-ssaf-format/SSAFFormat.cpp
@@ -12,6 +12,7 @@
//===----------------------------------------------------------------------===//
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h"
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h"
#include "clang/ScalableStaticAnalysis/Core/Serialization/JSONFormat.h"
@@ -40,7 +41,14 @@ namespace {
// Summary Type
//===----------------------------------------------------------------------===//
-enum class SummaryType { Auto, TU, LU, StaticLibrary, WPA };
+enum class SummaryType {
+ Auto,
+ TU,
+ LU,
+ StaticLibrary,
+ MultiArchStaticLibrary,
+ WPA
+};
//===----------------------------------------------------------------------===//
// Command-Line Options
@@ -66,6 +74,9 @@ cl::opt<SummaryType> Type(
clEnumValN(SummaryType::LU, "lu", "Link unit summary"),
clEnumValN(SummaryType::StaticLibrary, "static-library",
"Static library of translation unit summaries"),
+ clEnumValN(SummaryType::MultiArchStaticLibrary,
+ "multi-arch-static-library",
+ "Multi-architecture static library"),
clEnumValN(SummaryType::WPA, "wpa",
"Whole-program analysis suite")),
cl::init(SummaryType::Auto), cl::cat(SsafFormatCategory));
@@ -304,6 +315,13 @@ void convert(const FormatInput &FI) {
run(FI, &SerializationFormat::readStaticLibrary,
&SerializationFormat::writeStaticLibrary);
return;
+ case SummaryType::MultiArchStaticLibrary:
+ // MultiArchStaticLibrary has only an encoded representation, so
+ // --encoding is a no-op here: both paths route to
+ // readMultiArchStaticLibrary / writeMultiArchStaticLibrary.
+ run(FI, &SerializationFormat::readMultiArchStaticLibrary,
+ &SerializationFormat::writeMultiArchStaticLibrary);
+ return;
case SummaryType::WPA:
run(FI, &SerializationFormat::readWPASuite,
&SerializationFormat::writeWPASuite);
diff --git a/clang/unittests/ScalableStaticAnalysis/Frontend/TUSummaryExtractorFrontendActionTest.cpp b/clang/unittests/ScalableStaticAnalysis/Frontend/TUSummaryExtractorFrontendActionTest.cpp
index 4baf593f57eef..79504a4f9754f 100644
--- a/clang/unittests/ScalableStaticAnalysis/Frontend/TUSummaryExtractorFrontendActionTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysis/Frontend/TUSummaryExtractorFrontendActionTest.cpp
@@ -108,6 +108,16 @@ class FailingSerializationFormat final : public SerializationFormat {
return failing("writeStaticLibrary");
}
+ llvm::Expected<MultiArchStaticLibrary>
+ readMultiArchStaticLibrary(llvm::StringRef Path) override {
+ return failing("readMultiArchStaticLibrary");
+ }
+
+ llvm::Error writeMultiArchStaticLibrary(const MultiArchStaticLibrary &M,
+ llvm::StringRef Path) override {
+ return failing("writeMultiArchStaticLibrary");
+ }
+
llvm::Expected<WPASuite> readWPASuite(llvm::StringRef Path) override {
return failing("readWPASuite");
}
@@ -196,6 +206,14 @@ class CapturingSerializationFormat final : public SerializationFormat {
llvm::StringRef) override {
return llvm::Error::success();
}
+ llvm::Expected<MultiArchStaticLibrary>
+ readMultiArchStaticLibrary(llvm::StringRef) override {
+ return llvm::createStringError("not implemented");
+ }
+ llvm::Error writeMultiArchStaticLibrary(const MultiArchStaticLibrary &,
+ llvm::StringRef) override {
+ return llvm::Error::success();
+ }
llvm::Expected<WPASuite> readWPASuite(llvm::StringRef) override {
return llvm::createStringError("not implemented");
}
diff --git a/clang/unittests/ScalableStaticAnalysis/Registries/MockSerializationFormat.cpp b/clang/unittests/ScalableStaticAnalysis/Registries/MockSerializationFormat.cpp
index 4a01248b53ac7..92d6d0abc8936 100644
--- a/clang/unittests/ScalableStaticAnalysis/Registries/MockSerializationFormat.cpp
+++ b/clang/unittests/ScalableStaticAnalysis/Registries/MockSerializationFormat.cpp
@@ -9,6 +9,7 @@
#include "Registries/MockSerializationFormat.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h"
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h"
#include "clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h"
@@ -209,6 +210,18 @@ llvm::Error MockSerializationFormat::writeStaticLibrary(const StaticLibrary &S,
llvm_unreachable("MockSerializationFormat does not support StaticLibrary");
}
+llvm::Expected<MultiArchStaticLibrary>
+MockSerializationFormat::readMultiArchStaticLibrary(llvm::StringRef Path) {
+ llvm_unreachable(
+ "MockSerializationFormat does not support MultiArchStaticLibrary");
+}
+
+llvm::Error MockSerializationFormat::writeMultiArchStaticLibrary(
+ const MultiArchStaticLibrary &M, llvm::StringRef Path) {
+ llvm_unreachable(
+ "MockSerializationFormat does not support MultiArchStaticLibrary");
+}
+
llvm::Expected<WPASuite>
MockSerializationFormat::readWPASuite(llvm::StringRef Path) {
llvm_unreachable("MockSerializationFormat does not support WPASuite");
diff --git a/clang/unittests/ScalableStaticAnalysis/Registries/MockSerializationFormat.h b/clang/unittests/ScalableStaticAnalysis/Registries/MockSerializationFormat.h
index 0cadd0b98fe3a..e03e5092589fe 100644
--- a/clang/unittests/ScalableStaticAnalysis/Registries/MockSerializationFormat.h
+++ b/clang/unittests/ScalableStaticAnalysis/Registries/MockSerializationFormat.h
@@ -50,6 +50,12 @@ class MockSerializationFormat final : public SerializationFormat {
llvm::Error writeStaticLibrary(const StaticLibrary &S,
llvm::StringRef Path) override;
+ llvm::Expected<MultiArchStaticLibrary>
+ readMultiArchStaticLibrary(llvm::StringRef Path) override;
+
+ llvm::Error writeMultiArchStaticLibrary(const MultiArchStaticLibrary &M,
+ llvm::StringRef Path) override;
+
llvm::Expected<Artifact> readArtifact(llvm::StringRef Path) override;
llvm::Error writeArtifact(const Artifact &A, llvm::StringRef Path) override;
diff --git a/clang/unittests/ScalableStaticAnalysis/TestFixture.h b/clang/unittests/ScalableStaticAnalysis/TestFixture.h
index e7eed940938ce..c2b7005bd4b0c 100644
--- a/clang/unittests/ScalableStaticAnalysis/TestFixture.h
+++ b/clang/unittests/ScalableStaticAnalysis/TestFixture.h
@@ -11,6 +11,7 @@
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h"
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h"
#include "clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h"
>From 3ef131d9d5d99a9ff2e9aa62944d3bb389329708 Mon Sep 17 00:00:00 2001
From: Aviral Goel <goel.aviral at gmail.com>
Date: Tue, 30 Jun 2026 15:48:17 -0700
Subject: [PATCH 2/2] [clang][ssaf] Add `MultiArchSharedLibrary` data structure
---
.../Core/EntityLinker/LUSummaryEncoding.h | 1 +
.../EntityLinker/MultiArchSharedLibrary.h | 74 +++++++
.../Core/Model/BuildNamespace.h | 3 +-
.../Core/Model/PrivateFieldNames.def | 2 +
.../Core/Serialization/JSONFormat.h | 16 ++
.../Core/Serialization/SerializationFormat.h | 21 +-
.../Core/CMakeLists.txt | 1 +
.../Core/ModelStringConversions.h | 4 +
.../Serialization/JSONFormat/Artifact.cpp | 19 +-
.../Serialization/JSONFormat/JSONFormatImpl.h | 9 +-
.../JSONFormat/LUSummaryEncoding.cpp | 19 +-
.../JSONFormat/MultiArchSharedLibrary.cpp | 186 ++++++++++++++++++
.../rt-multi-arch-shared-library-empty.json | 8 +
...rt-multi-arch-shared-library-nonempty.json | 77 ++++++++
.../ssaf-format/Artifact/round-trip.test | 13 ++
.../ssaf-format/Artifact/top-level.test | 2 +-
.../Inputs/duplicate-target-triple.json | 35 ++++
.../Inputs/invalid-syntax.json | 1 +
.../Inputs/member-inner-error.json | 16 ++
.../Inputs/member-mismatched-type.json | 22 +++
.../Inputs/member-missing-type.json | 21 ++
.../Inputs/member-namespace-mismatch.json | 22 +++
.../Inputs/member-not-object.json | 10 +
.../Inputs/members-not-array.json | 8 +
.../Inputs/mismatched-type.json | 8 +
.../Inputs/missing-members.json | 7 +
.../Inputs/missing-namespace.json | 4 +
.../Inputs/missing-type.json | 7 +
.../Inputs/namespace-not-object.json | 5 +
.../Inputs/namespace-wrong-kind.json | 8 +
.../Inputs/not-json-extension.txt | 1 +
.../Inputs/not-object.json | 1 +
.../Inputs/rt-empty-members.json | 8 +
.../Inputs/rt-multiple-members.json | 35 ++++
.../Inputs/rt-nonempty-members.json | 77 ++++++++
.../Inputs/rt-single-member.json | 22 +++
.../Inputs/rt-three-members.json | 48 +++++
.../Inputs/unsorted-members-input.json | 35 ++++
.../Inputs/unsorted-three-members-input.json | 48 +++++
.../MultiArchSharedLibrary/io.test | 49 +++++
.../MultiArchSharedLibrary/permissions.test | 40 ++++
.../MultiArchSharedLibrary/round-trip.test | 55 ++++++
.../MultiArchSharedLibrary/top-level.test | 102 ++++++++++
clang/tools/clang-ssaf-format/SSAFFormat.cpp | 12 ++
.../TUSummaryExtractorFrontendActionTest.cpp | 18 ++
.../Registries/MockSerializationFormat.cpp | 13 ++
.../Registries/MockSerializationFormat.h | 6 +
.../ScalableStaticAnalysis/TestFixture.h | 1 +
48 files changed, 1180 insertions(+), 20 deletions(-)
create mode 100644 clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h
create mode 100644 clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/MultiArchSharedLibrary.cpp
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-shared-library-empty.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-shared-library-nonempty.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/duplicate-target-triple.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/invalid-syntax.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-inner-error.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-mismatched-type.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-missing-type.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-namespace-mismatch.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-not-object.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/members-not-array.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/mismatched-type.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/missing-members.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/missing-namespace.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/missing-type.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/namespace-not-object.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/namespace-wrong-kind.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/not-json-extension.txt
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/not-object.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-empty-members.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-multiple-members.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-nonempty-members.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-single-member.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-three-members.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/unsorted-members-input.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/unsorted-three-members-input.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/io.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/permissions.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/round-trip.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/top-level.test
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h
index 663697cdb88b3..308196d38a84a 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h
@@ -33,6 +33,7 @@ namespace clang::ssaf {
/// entity linker and contains deduplicated and patched entity summaries.
class LUSummaryEncoding {
friend class EntityLinker;
+ friend class MultiArchSharedLibrary;
friend class SerializationFormat;
friend class TestFixture;
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h
new file mode 100644
index 0000000000000..cedbdccef130c
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h
@@ -0,0 +1,74 @@
+//===- MultiArchSharedLibrary.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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines the MultiArchSharedLibrary class, which represents a
+// multi-architecture wrapper around per-architecture LUSummaryEncoding
+// instances (the SSAF analogue of a Mach-O fat shared library/dylib, e.g.
+// one produced by `lipo -create` over per-arch `.dylib` files).
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSIS_CORE_ENTITYLINKER_MULTIARCHSHAREDLIBRARY_H
+#define LLVM_CLANG_SCALABLESTATICANALYSIS_CORE_ENTITYLINKER_MULTIARCHSHAREDLIBRARY_H
+
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h"
+#include "clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h"
+#include <memory>
+#include <set>
+#include <tuple>
+
+namespace clang::ssaf {
+
+/// Represents a multi-architecture shared library.
+///
+/// A MultiArchSharedLibrary bundles per-architecture LUSummaryEncoding
+/// members, mirroring the role of a Mach-O fat shared library (the result
+/// of `lipo -create` over per-architecture `.dylib` files). All members
+/// represent the same logical shared library built for different
+/// architectures; the wrapper's \c Namespace identifies that shared
+/// library and every member's LU-namespace leaf step must agree on its
+/// name.
+class MultiArchSharedLibrary {
+ friend class SerializationFormat;
+ friend class TestFixture;
+
+ /// Orders members by their TargetTriple's canonical enum components.
+ /// llvm::Triple's parser maps alias spellings to the same enum values
+ /// (e.g. "aarch64" and "arm64" both become Triple::aarch64), so a
+ /// tuple-of-enums compare is equivalent to comparing normalized triples
+ /// for identity, but without the per-call string allocation.
+ struct MemberByTargetTriple {
+ static auto key(const llvm::Triple &T) {
+ return std::make_tuple(T.getArch(), T.getSubArch(), T.getVendor(),
+ T.getOS(), T.getEnvironment(),
+ T.getObjectFormat());
+ }
+ bool operator()(const std::unique_ptr<LUSummaryEncoding> &A,
+ const std::unique_ptr<LUSummaryEncoding> &B) const {
+ return key(A->TargetTriple) < key(B->TargetTriple);
+ }
+ };
+
+ // The namespace identifying this multi-architecture shared library.
+ // Every member's LU-namespace leaf step must share this Namespace's
+ // name (the leaf step's kind on the member is LinkUnit, while the
+ // wrapper's kind is MultiArchSharedLibrary).
+ BuildNamespace Namespace;
+
+ // LUSummaryEncoding objects ordered by TargetTriple enum components.
+ // Two members with the same TargetTriple are not permitted.
+ std::set<std::unique_ptr<LUSummaryEncoding>, MemberByTargetTriple> Members;
+
+public:
+ explicit MultiArchSharedLibrary(BuildNamespace Namespace)
+ : Namespace(std::move(Namespace)) {}
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSIS_CORE_ENTITYLINKER_MULTIARCHSHAREDLIBRARY_H
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h b/clang/include/clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h
index d503b16bf294c..df5b333741e9d 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h
@@ -31,7 +31,8 @@ enum class BuildNamespaceKind : unsigned short {
CompilationUnit,
LinkUnit,
StaticLibrary,
- MultiArchStaticLibrary
+ MultiArchStaticLibrary,
+ MultiArchSharedLibrary
};
/// Represents a single namespace in the build process.
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/Model/PrivateFieldNames.def b/clang/include/clang/ScalableStaticAnalysis/Core/Model/PrivateFieldNames.def
index e43ff93140674..33f47f8f9151c 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/Model/PrivateFieldNames.def
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/Model/PrivateFieldNames.def
@@ -37,6 +37,8 @@ FIELD(LUSummaryEncoding, LinkageTable)
FIELD(LUSummaryEncoding, LUNamespace)
FIELD(MultiArchStaticLibrary, Members)
FIELD(MultiArchStaticLibrary, Namespace)
+FIELD(MultiArchSharedLibrary, Members)
+FIELD(MultiArchSharedLibrary, Namespace)
FIELD(NestedBuildNamespace, Namespaces)
FIELD(StaticLibrary, Members)
FIELD(StaticLibrary, Namespace)
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/Serialization/JSONFormat.h b/clang/include/clang/ScalableStaticAnalysis/Core/Serialization/JSONFormat.h
index 094628ab7fa04..548f6ed74c34e 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/Serialization/JSONFormat.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/Serialization/JSONFormat.h
@@ -80,6 +80,12 @@ class JSONFormat final : public SerializationFormat {
llvm::Error writeMultiArchStaticLibrary(const MultiArchStaticLibrary &M,
llvm::StringRef Path) override;
+ llvm::Expected<MultiArchSharedLibrary>
+ readMultiArchSharedLibrary(llvm::StringRef Path) override;
+
+ llvm::Error writeMultiArchSharedLibrary(const MultiArchSharedLibrary &M,
+ llvm::StringRef Path) override;
+
llvm::Expected<WPASuite> readWPASuite(llvm::StringRef Path) override;
llvm::Error writeWPASuite(const WPASuite &Suite,
@@ -143,6 +149,11 @@ class JSONFormat final : public SerializationFormat {
llvm::Expected<MultiArchStaticLibrary>
readMultiArchStaticLibraryFromObject(const Object &Root);
+ /// Parses a MultiArchSharedLibrary from an already-validated root JSON
+ /// object. See \c readTUSummaryFromObject for caller responsibilities.
+ llvm::Expected<MultiArchSharedLibrary>
+ readMultiArchSharedLibraryFromObject(const Object &Root);
+
/// Serializes a TUSummaryEncoding to a JSON object including its
/// self-describing \c type field. Used both by \c writeTUSummaryEncoding
/// and by the StaticLibrary writer to emit member entries.
@@ -153,6 +164,11 @@ class JSONFormat final : public SerializationFormat {
/// and by the MultiArchStaticLibrary writer to emit per-arch slices.
Object staticLibraryToJSON(const StaticLibrary &S) const;
+ /// Serializes an LUSummaryEncoding to a JSON object including its
+ /// self-describing \c type field. Used both by \c writeLUSummaryEncoding
+ /// and by the MultiArchSharedLibrary writer to emit per-arch slices.
+ Object luSummaryEncodingToJSON(const LUSummaryEncoding &E) const;
+
/// Parses a WPASuite from an already-validated root JSON object. See
/// \c readTUSummaryFromObject for caller responsibilities.
llvm::Expected<WPASuite> readWPASuiteFromObject(const Object &Root);
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/Serialization/SerializationFormat.h b/clang/include/clang/ScalableStaticAnalysis/Core/Serialization/SerializationFormat.h
index a9c25de95a598..ddd52718ab4ee 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/Serialization/SerializationFormat.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/Serialization/SerializationFormat.h
@@ -16,6 +16,7 @@
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h"
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h"
@@ -43,12 +44,13 @@ using Artifact = std::variant<TUSummary, LUSummary, WPASuite>;
/// artifacts but with their per-entity summary payloads left as opaque
/// format-specific encodings rather than fully resolved analysis results.
///
-/// \c StaticLibrary and \c MultiArchStaticLibrary appear only in this
-/// variant: the archiver/arch tools and the linker pass member payloads
-/// through without decoding them, so fully decoded shapes would have no
-/// consumer.
-using ArtifactEncoding = std::variant<TUSummaryEncoding, LUSummaryEncoding,
- StaticLibrary, MultiArchStaticLibrary>;
+/// \c StaticLibrary, \c MultiArchStaticLibrary, and
+/// \c MultiArchSharedLibrary appear only in this variant: the
+/// archiver/arch tools and the linker pass member payloads through
+/// without decoding them, so fully decoded shapes would have no consumer.
+using ArtifactEncoding =
+ std::variant<TUSummaryEncoding, LUSummaryEncoding, StaticLibrary,
+ MultiArchStaticLibrary, MultiArchSharedLibrary>;
/// Abstract base class for serialization formats.
class SerializationFormat {
@@ -115,6 +117,13 @@ class SerializationFormat {
writeMultiArchStaticLibrary(const MultiArchStaticLibrary &M,
llvm::StringRef Path) = 0;
+ virtual llvm::Expected<MultiArchSharedLibrary>
+ readMultiArchSharedLibrary(llvm::StringRef Path) = 0;
+
+ virtual llvm::Error
+ writeMultiArchSharedLibrary(const MultiArchSharedLibrary &M,
+ llvm::StringRef Path) = 0;
+
virtual llvm::Expected<WPASuite> readWPASuite(llvm::StringRef Path) = 0;
virtual llvm::Error writeWPASuite(const WPASuite &Suite,
diff --git a/clang/lib/ScalableStaticAnalysis/Core/CMakeLists.txt b/clang/lib/ScalableStaticAnalysis/Core/CMakeLists.txt
index 47f018f61becf..28c4ac9f6911b 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysis/Core/CMakeLists.txt
@@ -18,6 +18,7 @@ add_clang_library(clangScalableStaticAnalysisCore
Serialization/JSONFormat/LUSummary.cpp
Serialization/JSONFormat/LUSummaryEncoding.cpp
Serialization/JSONFormat/MultiArchStaticLibrary.cpp
+ Serialization/JSONFormat/MultiArchSharedLibrary.cpp
Serialization/JSONFormat/StaticLibrary.cpp
Serialization/JSONFormat/TUSummary.cpp
Serialization/JSONFormat/TUSummaryEncoding.cpp
diff --git a/clang/lib/ScalableStaticAnalysis/Core/ModelStringConversions.h b/clang/lib/ScalableStaticAnalysis/Core/ModelStringConversions.h
index 9a4392bd72990..10ee5fb8c1d12 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/ModelStringConversions.h
+++ b/clang/lib/ScalableStaticAnalysis/Core/ModelStringConversions.h
@@ -40,6 +40,8 @@ inline llvm::StringRef buildNamespaceKindToString(BuildNamespaceKind BNK) {
return "StaticLibrary";
case BuildNamespaceKind::MultiArchStaticLibrary:
return "MultiArchStaticLibrary";
+ case BuildNamespaceKind::MultiArchSharedLibrary:
+ return "MultiArchSharedLibrary";
}
llvm_unreachable("Unhandled BuildNamespaceKind variant");
}
@@ -56,6 +58,8 @@ buildNamespaceKindFromString(llvm::StringRef Str) {
return BuildNamespaceKind::StaticLibrary;
if (Str == "MultiArchStaticLibrary")
return BuildNamespaceKind::MultiArchStaticLibrary;
+ if (Str == "MultiArchSharedLibrary")
+ return BuildNamespaceKind::MultiArchSharedLibrary;
return std::nullopt;
}
diff --git a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/Artifact.cpp b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/Artifact.cpp
index a34f5cb718ab2..15248ce92ae88 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/Artifact.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/Artifact.cpp
@@ -165,11 +165,22 @@ JSONFormat::readArtifactEncoding(llvm::StringRef Path) {
return ArtifactEncoding{std::move(*ExpectedM)};
}
+ if (*ExpectedType == JSONTypeValueMultiArchSharedLibrary) {
+ auto ExpectedM = readMultiArchSharedLibraryFromObject(*RootObjectPtr);
+ if (!ExpectedM) {
+ return ErrorBuilder::wrap(ExpectedM.takeError())
+ .context(ErrorMessages::ReadingFromFile, "ArtifactEncoding", Path)
+ .build();
+ }
+ return ArtifactEncoding{std::move(*ExpectedM)};
+ }
+
return ErrorBuilder::create(
std::errc::invalid_argument,
ErrorMessages::UnknownArtifactEncodingType, *ExpectedType,
JSONTypeKey, JSONTypeValueTUSummary, JSONTypeValueLUSummary,
- JSONTypeValueStaticLibrary, JSONTypeValueMultiArchStaticLibrary)
+ JSONTypeValueStaticLibrary, JSONTypeValueMultiArchStaticLibrary,
+ JSONTypeValueMultiArchSharedLibrary)
.context(ErrorMessages::ReadingFromFile, "ArtifactEncoding", Path)
.build();
}
@@ -185,11 +196,13 @@ llvm::Error JSONFormat::writeArtifactEncoding(const ArtifactEncoding &E,
return writeLUSummaryEncoding(Enc, Path);
} else if constexpr (std::is_same_v<T, StaticLibrary>) {
return writeStaticLibrary(Enc, Path);
+ } else if constexpr (std::is_same_v<T, MultiArchStaticLibrary>) {
+ return writeMultiArchStaticLibrary(Enc, Path);
} else {
static_assert(
- std::is_same_v<T, MultiArchStaticLibrary>,
+ std::is_same_v<T, MultiArchSharedLibrary>,
"ArtifactEncoding visitor must cover all variant alternatives");
- return writeMultiArchStaticLibrary(Enc, Path);
+ return writeMultiArchSharedLibrary(Enc, Path);
}
},
E);
diff --git a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.h b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.h
index 9cfe67e20d1d7..ad1e6630078dc 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.h
+++ b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.h
@@ -85,8 +85,8 @@ inline constexpr const char *MismatchedSummaryType =
inline constexpr const char *UnknownArtifactType =
"unknown value '{0}' for field '{1}': expected '{2}', '{3}', or '{4}'";
inline constexpr const char *UnknownArtifactEncodingType =
- "unknown value '{0}' for field '{1}': expected '{2}', '{3}', '{4}', or "
- "'{5}'";
+ "unknown value '{0}' for field '{1}': expected '{2}', '{3}', '{4}', '{5}', "
+ "or '{6}'";
inline constexpr const char *FailedToDeserializeEntitySummaryNoFormatInfo =
"failed to deserialize EntitySummary: no FormatInfo registered for '{0}'";
@@ -158,6 +158,11 @@ inline constexpr const char *JSONTypeValueStaticLibrary = "StaticLibrary";
inline constexpr const char *JSONTypeValueMultiArchStaticLibrary =
"MultiArchStaticLibrary";
+/// Value written to \c JSONTypeKey for serialized \c MultiArchSharedLibrary
+/// files.
+inline constexpr const char *JSONTypeValueMultiArchSharedLibrary =
+ "MultiArchSharedLibrary";
+
/// Value written to \c JSONTypeKey for serialized \c WPASuite files.
inline constexpr const char *JSONTypeValueWPASuite = "WPASuite";
diff --git a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/LUSummaryEncoding.cpp b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/LUSummaryEncoding.cpp
index b1f78c5309dfb..8961ce95e059b 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/LUSummaryEncoding.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/LUSummaryEncoding.cpp
@@ -164,6 +164,17 @@ JSONFormat::readLUSummaryEncodingFromObject(const Object &RootObject) {
llvm::Error
JSONFormat::writeLUSummaryEncoding(const LUSummaryEncoding &SummaryEncoding,
llvm::StringRef Path) {
+ if (auto Error = writeJSON(luSummaryEncodingToJSON(SummaryEncoding), Path)) {
+ return ErrorBuilder::wrap(std::move(Error))
+ .context(ErrorMessages::WritingToFile, "LUSummary", Path)
+ .build();
+ }
+
+ return llvm::Error::success();
+}
+
+Object JSONFormat::luSummaryEncodingToJSON(
+ const LUSummaryEncoding &SummaryEncoding) const {
Object RootObject;
RootObject[JSONTypeKey] = JSONTypeValueLUSummary;
@@ -181,13 +192,7 @@ JSONFormat::writeLUSummaryEncoding(const LUSummaryEncoding &SummaryEncoding,
RootObject["data"] = encodingSummaryDataMapToJSON(getData(SummaryEncoding));
- if (auto Error = writeJSON(std::move(RootObject), Path)) {
- return ErrorBuilder::wrap(std::move(Error))
- .context(ErrorMessages::WritingToFile, "LUSummary", Path)
- .build();
- }
-
- return llvm::Error::success();
+ return RootObject;
}
} // namespace clang::ssaf
diff --git a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/MultiArchSharedLibrary.cpp b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/MultiArchSharedLibrary.cpp
new file mode 100644
index 0000000000000..bce8da8b2496c
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/MultiArchSharedLibrary.cpp
@@ -0,0 +1,186 @@
+//===- MultiArchSharedLibrary.cpp -----------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "JSONFormatImpl.h"
+
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h"
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h"
+#include "llvm/TargetParser/Triple.h"
+
+namespace clang::ssaf {
+
+//----------------------------------------------------------------------------
+// MultiArchSharedLibrary
+//----------------------------------------------------------------------------
+
+llvm::Expected<MultiArchSharedLibrary>
+JSONFormat::readMultiArchSharedLibrary(llvm::StringRef Path) {
+ auto ExpectedJSON = readJSON(Path);
+ if (!ExpectedJSON) {
+ return ErrorBuilder::wrap(ExpectedJSON.takeError())
+ .context(ErrorMessages::ReadingFromFile, "MultiArchSharedLibrary", Path)
+ .build();
+ }
+
+ Object *RootObjectPtr = ExpectedJSON->getAsObject();
+ if (!RootObjectPtr) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObject,
+ "MultiArchSharedLibrary", "object")
+ .context(ErrorMessages::ReadingFromFile, "MultiArchSharedLibrary", Path)
+ .build();
+ }
+
+ if (auto Err = checkSummaryType(*RootObjectPtr,
+ JSONTypeValueMultiArchSharedLibrary)) {
+ return ErrorBuilder::wrap(std::move(Err))
+ .context(ErrorMessages::ReadingFromFile, "MultiArchSharedLibrary", Path)
+ .build();
+ }
+
+ auto ExpectedM = readMultiArchSharedLibraryFromObject(*RootObjectPtr);
+ if (!ExpectedM) {
+ return ErrorBuilder::wrap(ExpectedM.takeError())
+ .context(ErrorMessages::ReadingFromFile, "MultiArchSharedLibrary", Path)
+ .build();
+ }
+
+ return std::move(*ExpectedM);
+}
+
+llvm::Expected<MultiArchSharedLibrary>
+JSONFormat::readMultiArchSharedLibraryFromObject(const Object &RootObject) {
+ const Object *NamespaceObject = RootObject.getObject("namespace");
+ if (!NamespaceObject) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "BuildNamespace", "namespace", "object")
+ .build();
+ }
+
+ auto ExpectedNamespace = buildNamespaceFromJSON(*NamespaceObject);
+ if (!ExpectedNamespace) {
+ return ErrorBuilder::wrap(ExpectedNamespace.takeError())
+ .context(ErrorMessages::ReadingFromField, "BuildNamespace", "namespace")
+ .build();
+ }
+
+ if (getKind(*ExpectedNamespace) !=
+ BuildNamespaceKind::MultiArchSharedLibrary) {
+ return ErrorBuilder::create(
+ std::errc::invalid_argument,
+ ErrorMessages::MismatchedSummaryType,
+ buildNamespaceKindToJSON(
+ BuildNamespaceKind::MultiArchSharedLibrary),
+ "namespace.kind",
+ buildNamespaceKindToJSON(getKind(*ExpectedNamespace)))
+ .build();
+ }
+
+ const Array *MembersArray = RootObject.getArray("members");
+ if (!MembersArray) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "MultiArchSharedLibrary members", "members",
+ "array")
+ .build();
+ }
+
+ MultiArchSharedLibrary M(std::move(*ExpectedNamespace));
+ auto &Members = getMembers(M);
+ const auto &ExpectedName = getName(getNamespace(M));
+
+ for (const auto &[Index, MemberValue] : llvm::enumerate(*MembersArray)) {
+ const Object *MemberObject = MemberValue.getAsObject();
+ if (!MemberObject) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtIndex,
+ "MultiArchSharedLibrary member", Index,
+ "object")
+ .build();
+ }
+
+ if (auto Err = checkSummaryType(*MemberObject, JSONTypeValueLUSummary)) {
+ return ErrorBuilder::wrap(std::move(Err))
+ .context(ErrorMessages::ReadingFromIndex,
+ "MultiArchSharedLibrary member", Index)
+ .build();
+ }
+
+ auto ExpectedMember = readLUSummaryEncodingFromObject(*MemberObject);
+ if (!ExpectedMember) {
+ return ErrorBuilder::wrap(ExpectedMember.takeError())
+ .context(ErrorMessages::ReadingFromIndex,
+ "MultiArchSharedLibrary member", Index)
+ .build();
+ }
+
+ // The member identifies itself via its LU-namespace path; the leaf
+ // step's name must match the wrapper's namespace name. (Members'
+ // leaf-step kind is LinkUnit; the wrapper's is MultiArchSharedLibrary.)
+ const auto &MemberNamespaceSteps =
+ getNamespaces(getLUNamespace(*ExpectedMember));
+ if (MemberNamespaceSteps.empty()) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::MismatchedSummaryType,
+ ExpectedName, "lu_namespace.name", "")
+ .context(ErrorMessages::ReadingFromIndex,
+ "MultiArchSharedLibrary member", Index)
+ .build();
+ }
+ const auto &MemberName = getName(MemberNamespaceSteps.back());
+ if (MemberName != ExpectedName) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::MismatchedSummaryType,
+ ExpectedName, "lu_namespace.name", MemberName)
+ .context(ErrorMessages::ReadingFromIndex,
+ "MultiArchSharedLibrary member", Index)
+ .build();
+ }
+
+ auto [It, Inserted] = Members.insert(
+ std::make_unique<LUSummaryEncoding>(std::move(*ExpectedMember)));
+ if (!Inserted) {
+ auto MemberTriple = llvm::Triple::normalize(getTargetTriple(**It).str());
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedInsertionOnDuplication,
+ "MultiArchSharedLibrary member", Index,
+ MemberTriple)
+ .build();
+ }
+ }
+
+ return std::move(M);
+}
+
+llvm::Error
+JSONFormat::writeMultiArchSharedLibrary(const MultiArchSharedLibrary &M,
+ llvm::StringRef Path) {
+ Object RootObject;
+
+ RootObject[JSONTypeKey] = JSONTypeValueMultiArchSharedLibrary;
+
+ RootObject["namespace"] = buildNamespaceToJSON(getNamespace(M));
+
+ Array MembersArray;
+ MembersArray.reserve(getMembers(M).size());
+ for (const auto &Member : getMembers(M)) {
+ MembersArray.push_back(luSummaryEncodingToJSON(*Member));
+ }
+ RootObject["members"] = std::move(MembersArray);
+
+ if (auto Error = writeJSON(std::move(RootObject), Path)) {
+ return ErrorBuilder::wrap(std::move(Error))
+ .context(ErrorMessages::WritingToFile, "MultiArchSharedLibrary", Path)
+ .build();
+ }
+
+ return llvm::Error::success();
+}
+
+} // namespace clang::ssaf
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-shared-library-empty.json b/clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-shared-library-empty.json
new file mode 100644
index 0000000000000..158a4cbcacb16
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-shared-library-empty.json
@@ -0,0 +1,8 @@
+{
+ "members": [],
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-shared-library-nonempty.json b/clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-shared-library-nonempty.json
new file mode 100644
index 0000000000000..e5a71c27ddb15
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-shared-library-nonempty.json
@@ -0,0 +1,77 @@
+{
+ "members": [
+ {
+ "data": [],
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at foo"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "type": "External"
+ }
+ }
+ ],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "LUSummary"
+ },
+ {
+ "data": [],
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at foo"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "type": "External"
+ }
+ }
+ ],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "x86_64-apple-macosx",
+ "type": "LUSummary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Artifact/round-trip.test b/clang/test/Analysis/Scalable/ssaf-format/Artifact/round-trip.test
index 32214cabbc36b..19d162f46261b 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/Artifact/round-trip.test
+++ b/clang/test/Analysis/Scalable/ssaf-format/Artifact/round-trip.test
@@ -60,3 +60,16 @@
// RUN: diff %S/Inputs/rt-multi-arch-static-library-nonempty.json %t/rt-multi-arch-static-library-nonempty-enc.json
// RUN: clang-ssaf-format --type auto --encoding %S/Inputs/rt-multi-arch-static-library-nonempty.json -o %t/rt-multi-arch-static-library-nonempty-enc-explicit.json
// RUN: diff %S/Inputs/rt-multi-arch-static-library-nonempty.json %t/rt-multi-arch-static-library-nonempty-enc-explicit.json
+
+// MultiArchSharedLibrary, like its sibling, appears only in the
+// ArtifactEncoding variant; round-trip both empty and non-empty fixtures.
+
+// RUN: clang-ssaf-format --encoding %S/Inputs/rt-multi-arch-shared-library-empty.json -o %t/rt-multi-arch-shared-library-empty-enc.json
+// RUN: diff %S/Inputs/rt-multi-arch-shared-library-empty.json %t/rt-multi-arch-shared-library-empty-enc.json
+// RUN: clang-ssaf-format --type auto --encoding %S/Inputs/rt-multi-arch-shared-library-empty.json -o %t/rt-multi-arch-shared-library-empty-enc-explicit.json
+// RUN: diff %S/Inputs/rt-multi-arch-shared-library-empty.json %t/rt-multi-arch-shared-library-empty-enc-explicit.json
+
+// RUN: clang-ssaf-format --encoding %S/Inputs/rt-multi-arch-shared-library-nonempty.json -o %t/rt-multi-arch-shared-library-nonempty-enc.json
+// RUN: diff %S/Inputs/rt-multi-arch-shared-library-nonempty.json %t/rt-multi-arch-shared-library-nonempty-enc.json
+// RUN: clang-ssaf-format --type auto --encoding %S/Inputs/rt-multi-arch-shared-library-nonempty.json -o %t/rt-multi-arch-shared-library-nonempty-enc-explicit.json
+// RUN: diff %S/Inputs/rt-multi-arch-shared-library-nonempty.json %t/rt-multi-arch-shared-library-nonempty-enc-explicit.json
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Artifact/top-level.test b/clang/test/Analysis/Scalable/ssaf-format/Artifact/top-level.test
index 5f8333ca52b37..f5b4f3106d7f9 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/Artifact/top-level.test
+++ b/clang/test/Analysis/Scalable/ssaf-format/Artifact/top-level.test
@@ -40,4 +40,4 @@
// RUN: not clang-ssaf-format --type auto --encoding %S/Inputs/unknown-type.json 2>&1 \
// RUN: | FileCheck %s --match-full-lines --check-prefix=ENCODING-UNKNOWN-TYPE
// ENCODING-UNKNOWN-TYPE: clang-ssaf-format: error: reading ArtifactEncoding from file '{{.*}}unknown-type.json'
-// ENCODING-UNKNOWN-TYPE-NEXT: unknown value 'Mystery' for field 'type': expected 'TUSummary', 'LUSummary', 'StaticLibrary', or 'MultiArchStaticLibrary'
+// ENCODING-UNKNOWN-TYPE-NEXT: unknown value 'Mystery' for field 'type': expected 'TUSummary', 'LUSummary', 'StaticLibrary', 'MultiArchStaticLibrary', or 'MultiArchSharedLibrary'
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/duplicate-target-triple.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/duplicate-target-triple.json
new file mode 100644
index 0000000000000..96f94a25c6f61
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/duplicate-target-triple.json
@@ -0,0 +1,35 @@
+{
+ "members": [
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "LUSummary"
+ },
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "LUSummary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/invalid-syntax.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/invalid-syntax.json
new file mode 100644
index 0000000000000..20d082b967157
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/invalid-syntax.json
@@ -0,0 +1 @@
+{ broken
\ No newline at end of file
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-inner-error.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-inner-error.json
new file mode 100644
index 0000000000000..b8d47c15ee4f4
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-inner-error.json
@@ -0,0 +1,16 @@
+{
+ "members": [
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "target_triple": "arm64-apple-macosx",
+ "type": "LUSummary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-mismatched-type.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-mismatched-type.json
new file mode 100644
index 0000000000000..eec5d86480076
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-mismatched-type.json
@@ -0,0 +1,22 @@
+{
+ "members": [
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "TUSummary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-missing-type.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-missing-type.json
new file mode 100644
index 0000000000000..601e08fb21013
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-missing-type.json
@@ -0,0 +1,21 @@
+{
+ "members": [
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "arm64-apple-macosx"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-namespace-mismatch.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-namespace-mismatch.json
new file mode 100644
index 0000000000000..11ec4374bc776
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-namespace-mismatch.json
@@ -0,0 +1,22 @@
+{
+ "members": [
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libbar"
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "LUSummary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-not-object.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-not-object.json
new file mode 100644
index 0000000000000..a02ec5353668d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-not-object.json
@@ -0,0 +1,10 @@
+{
+ "members": [
+ "not an object"
+ ],
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/members-not-array.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/members-not-array.json
new file mode 100644
index 0000000000000..48be754c75367
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/members-not-array.json
@@ -0,0 +1,8 @@
+{
+ "members": {},
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/mismatched-type.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/mismatched-type.json
new file mode 100644
index 0000000000000..e8e07e3eeb5c1
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/mismatched-type.json
@@ -0,0 +1,8 @@
+{
+ "members": [],
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/missing-members.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/missing-members.json
new file mode 100644
index 0000000000000..2b1800f9b054f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/missing-members.json
@@ -0,0 +1,7 @@
+{
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/missing-namespace.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/missing-namespace.json
new file mode 100644
index 0000000000000..66fb6bc008515
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/missing-namespace.json
@@ -0,0 +1,4 @@
+{
+ "members": [],
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/missing-type.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/missing-type.json
new file mode 100644
index 0000000000000..c33cdbaaf9112
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/missing-type.json
@@ -0,0 +1,7 @@
+{
+ "members": [],
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ }
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/namespace-not-object.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/namespace-not-object.json
new file mode 100644
index 0000000000000..3dc32039cb74a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/namespace-not-object.json
@@ -0,0 +1,5 @@
+{
+ "members": [],
+ "namespace": "libfoo",
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/namespace-wrong-kind.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/namespace-wrong-kind.json
new file mode 100644
index 0000000000000..a9c33028dcd6b
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/namespace-wrong-kind.json
@@ -0,0 +1,8 @@
+{
+ "members": [],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/not-json-extension.txt b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/not-json-extension.txt
new file mode 100644
index 0000000000000..0967ef424bce6
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/not-json-extension.txt
@@ -0,0 +1 @@
+{}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/not-object.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/not-object.json
new file mode 100644
index 0000000000000..fe51488c7066f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/not-object.json
@@ -0,0 +1 @@
+[]
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-empty-members.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-empty-members.json
new file mode 100644
index 0000000000000..158a4cbcacb16
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-empty-members.json
@@ -0,0 +1,8 @@
+{
+ "members": [],
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-multiple-members.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-multiple-members.json
new file mode 100644
index 0000000000000..a05b07dae21cc
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-multiple-members.json
@@ -0,0 +1,35 @@
+{
+ "members": [
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "LUSummary"
+ },
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "x86_64-apple-macosx",
+ "type": "LUSummary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-nonempty-members.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-nonempty-members.json
new file mode 100644
index 0000000000000..e5a71c27ddb15
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-nonempty-members.json
@@ -0,0 +1,77 @@
+{
+ "members": [
+ {
+ "data": [],
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at foo"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "type": "External"
+ }
+ }
+ ],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "LUSummary"
+ },
+ {
+ "data": [],
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "suffix": "",
+ "usr": "c:@F at foo"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "type": "External"
+ }
+ }
+ ],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "x86_64-apple-macosx",
+ "type": "LUSummary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-single-member.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-single-member.json
new file mode 100644
index 0000000000000..2035270159378
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-single-member.json
@@ -0,0 +1,22 @@
+{
+ "members": [
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "LUSummary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-three-members.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-three-members.json
new file mode 100644
index 0000000000000..0e6fd58a8a0f2
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-three-members.json
@@ -0,0 +1,48 @@
+{
+ "members": [
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "LUSummary"
+ },
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "arm64_32-apple-macosx",
+ "type": "LUSummary"
+ },
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "x86_64-apple-macosx",
+ "type": "LUSummary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/unsorted-members-input.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/unsorted-members-input.json
new file mode 100644
index 0000000000000..952e77bde17f9
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/unsorted-members-input.json
@@ -0,0 +1,35 @@
+{
+ "members": [
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "x86_64-apple-macosx",
+ "type": "LUSummary"
+ },
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "LUSummary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/unsorted-three-members-input.json b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/unsorted-three-members-input.json
new file mode 100644
index 0000000000000..8747736e530d8
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/unsorted-three-members-input.json
@@ -0,0 +1,48 @@
+{
+ "members": [
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "x86_64-apple-macosx",
+ "type": "LUSummary"
+ },
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "LUSummary"
+ },
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "arm64_32-apple-macosx",
+ "type": "LUSummary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchSharedLibrary",
+ "name": "libfoo"
+ },
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/io.test b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/io.test
new file mode 100644
index 0000000000000..351149a6c8145
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/io.test
@@ -0,0 +1,49 @@
+// File-I/O error tests: readJSON() input validation and writeJSON() output
+// validation for clang-ssaf-format --type multi-arch-shared-library.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// ============================================================================
+// readJSON() errors
+// ============================================================================
+
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/nonexistent.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NONEXISTENT-FILE
+// NONEXISTENT-FILE: clang-ssaf-format: error: failed to validate path '{{.*}}nonexistent.json': Path does not exist
+
+// RUN: mkdir -p %t/test-directory.json
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %t/test-directory.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=PATH-IS-DIRECTORY
+// PATH-IS-DIRECTORY: clang-ssaf-format: error: failed to validate path '{{.*}}test-directory.json': Path is not a file
+
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/not-json-extension.txt 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NOT-JSON-EXTENSION
+// NOT-JSON-EXTENSION: clang-ssaf-format: error: failed to validate path '{{.*}}not-json-extension.txt': No format registered for extension 'txt'
+
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/invalid-syntax.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=INVALID-SYNTAX
+// INVALID-SYNTAX: clang-ssaf-format: error: reading MultiArchSharedLibrary from file '{{.*}}invalid-syntax.json'
+// INVALID-SYNTAX-NEXT: {{.*}}Expected object key
+
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/not-object.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NOT-OBJECT
+// NOT-OBJECT: clang-ssaf-format: error: reading MultiArchSharedLibrary from file '{{.*}}not-object.json'
+// NOT-OBJECT-NEXT: failed to read MultiArchSharedLibrary: expected JSON object
+
+// ============================================================================
+// writeJSON() errors
+// ============================================================================
+
+// RUN: echo '{}' > %t/existing.json
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/rt-single-member.json -o %t/existing.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=WRITE-FILE-ALREADY-EXISTS
+// WRITE-FILE-ALREADY-EXISTS: clang-ssaf-format: error: failed to validate path '{{.*}}existing.json': File already exists
+
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/rt-single-member.json -o %t/nonexistent-dir/test.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=WRITE-PARENT-DIRECTORY-NOT-FOUND
+// WRITE-PARENT-DIRECTORY-NOT-FOUND: clang-ssaf-format: error: failed to validate path '{{.*}}nonexistent-dir{{.}}test.json': Parent directory does not exist
+
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/rt-single-member.json -o %t/test.txt 2>&1 \
+// RUN: | FileCheck %s --check-prefix=WRITE-NOT-JSON-EXTENSION
+// WRITE-NOT-JSON-EXTENSION: clang-ssaf-format: error: failed to validate path '{{.*}}test.txt': No format registered for extension 'txt'
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/permissions.test b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/permissions.test
new file mode 100644
index 0000000000000..bf873b5916440
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/permissions.test
@@ -0,0 +1,40 @@
+// Tests for clang-ssaf-format --type multi-arch-shared-library that
+// require filesystem permission support (symlinks, chmod).
+
+// UNSUPPORTED: system-windows
+// REQUIRES: non-root-user
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// ============================================================================
+// Broken symlink
+// ============================================================================
+
+// RUN: ln -sf %t/nonexistent-target.json %t/broken-symlink.json
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %t/broken-symlink.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=BROKEN-SYMLINK
+// BROKEN-SYMLINK: clang-ssaf-format: error: failed to validate path '{{.*}}broken-symlink.json': Path does not exist
+
+// ============================================================================
+// No read permission
+// ============================================================================
+
+// RUN: cp %S/Inputs/rt-single-member.json %t/no-read-permission.json
+// RUN: chmod -r %t/no-read-permission.json
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %t/no-read-permission.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-READ-PERMISSION
+// RUN: chmod +r %t/no-read-permission.json
+// NO-READ-PERMISSION: clang-ssaf-format: error: reading MultiArchSharedLibrary from file '{{.*}}no-read-permission.json'
+// NO-READ-PERMISSION-NEXT: failed to read file '{{.*}}no-read-permission.json': {{.*}}
+
+// ============================================================================
+// Write to directory without write permission
+// ============================================================================
+
+// RUN: mkdir -p %t/write-protected-dir
+// RUN: chmod -w %t/write-protected-dir
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/rt-single-member.json -o %t/write-protected-dir/test.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=WRITE-STREAM-OPEN-FAILURE
+// RUN: chmod +w %t/write-protected-dir
+// WRITE-STREAM-OPEN-FAILURE: clang-ssaf-format: error: failed to validate path '{{.*}}write-protected-dir{{.}}test.json': Parent directory is not writable
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/round-trip.test b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/round-trip.test
new file mode 100644
index 0000000000000..9beb9068d4302
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/round-trip.test
@@ -0,0 +1,55 @@
+// Round-trip tests: read a MultiArchSharedLibrary JSON input, write it
+// back out, and diff the result against the original.
+//
+// MultiArchSharedLibrary has only an encoding representation, so
+// --encoding is a no-op for --type multi-arch-shared-library and both
+// invocations must produce the same output.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// An empty members array is a valid (if degenerate) MultiArchSharedLibrary.
+// RUN: clang-ssaf-format --type multi-arch-shared-library %S/Inputs/rt-empty-members.json -o %t/rt-empty-members.json
+// RUN: diff %S/Inputs/rt-empty-members.json %t/rt-empty-members.json
+// RUN: clang-ssaf-format --type multi-arch-shared-library --encoding %S/Inputs/rt-empty-members.json -o %t/rt-empty-members-enc.json
+// RUN: diff %S/Inputs/rt-empty-members.json %t/rt-empty-members-enc.json
+
+// RUN: clang-ssaf-format --type multi-arch-shared-library %S/Inputs/rt-single-member.json -o %t/rt-single-member.json
+// RUN: diff %S/Inputs/rt-single-member.json %t/rt-single-member.json
+// RUN: clang-ssaf-format --type multi-arch-shared-library --encoding %S/Inputs/rt-single-member.json -o %t/rt-single-member-enc.json
+// RUN: diff %S/Inputs/rt-single-member.json %t/rt-single-member-enc.json
+
+// RUN: clang-ssaf-format --type multi-arch-shared-library %S/Inputs/rt-multiple-members.json -o %t/rt-multiple-members.json
+// RUN: diff %S/Inputs/rt-multiple-members.json %t/rt-multiple-members.json
+// RUN: clang-ssaf-format --type multi-arch-shared-library --encoding %S/Inputs/rt-multiple-members.json -o %t/rt-multiple-members-enc.json
+// RUN: diff %S/Inputs/rt-multiple-members.json %t/rt-multiple-members-enc.json
+
+// RUN: clang-ssaf-format --type multi-arch-shared-library %S/Inputs/rt-nonempty-members.json -o %t/rt-nonempty-members.json
+// RUN: diff %S/Inputs/rt-nonempty-members.json %t/rt-nonempty-members.json
+// RUN: clang-ssaf-format --type multi-arch-shared-library --encoding %S/Inputs/rt-nonempty-members.json -o %t/rt-nonempty-members-enc.json
+// RUN: diff %S/Inputs/rt-nonempty-members.json %t/rt-nonempty-members-enc.json
+
+// ============================================================================
+// Member sort order
+// ============================================================================
+//
+// Members are stored in a std::set keyed by TargetTriple enum components,
+// so the writer emits them in enum-tuple order regardless of input order.
+// unsorted-members-input.json lists the members as x86_64, then arm64;
+// the writer must produce the canonical (arm64, x86_64) ordering, byte
+// identical to rt-multiple-members.json.
+
+// RUN: clang-ssaf-format --type multi-arch-shared-library %S/Inputs/unsorted-members-input.json -o %t/unsorted-members-output.json
+// RUN: diff %S/Inputs/rt-multiple-members.json %t/unsorted-members-output.json
+// RUN: clang-ssaf-format --type multi-arch-shared-library --encoding %S/Inputs/unsorted-members-input.json -o %t/unsorted-members-output-enc.json
+// RUN: diff %S/Inputs/rt-multiple-members.json %t/unsorted-members-output-enc.json
+
+// 3-member ordering: confirms the sort isn't an accidental two-element
+// swap. The unsorted input lists x86_64, arm64, arm64_32; the writer
+// must produce (arm64, arm64_32, x86_64) — byte identical to
+// rt-three-members.json.
+
+// RUN: clang-ssaf-format --type multi-arch-shared-library %S/Inputs/rt-three-members.json -o %t/rt-three-members.json
+// RUN: diff %S/Inputs/rt-three-members.json %t/rt-three-members.json
+// RUN: clang-ssaf-format --type multi-arch-shared-library %S/Inputs/unsorted-three-members-input.json -o %t/unsorted-three-members-output.json
+// RUN: diff %S/Inputs/rt-three-members.json %t/unsorted-three-members-output.json
diff --git a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/top-level.test b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/top-level.test
new file mode 100644
index 0000000000000..6cfebaccb80c2
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/top-level.test
@@ -0,0 +1,102 @@
+// Top-level MultiArchSharedLibrary structure tests: type discriminator,
+// namespace identity, members array, and per-member validation including
+// duplicate-target-triple and namespace-mismatch rejection.
+
+// ============================================================================
+// readMultiArchSharedLibrary() / writeMultiArchSharedLibrary() type-field
+// errors
+// ============================================================================
+
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/missing-type.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MISSING-TYPE
+// MISSING-TYPE: clang-ssaf-format: error: reading MultiArchSharedLibrary from file '{{.*}}missing-type.json'
+// MISSING-TYPE-NEXT: failed to read summary type from field 'type': expected JSON string
+
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/mismatched-type.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MISMATCHED-TYPE
+// MISMATCHED-TYPE: clang-ssaf-format: error: reading MultiArchSharedLibrary from file '{{.*}}mismatched-type.json'
+// MISMATCHED-TYPE-NEXT: expected 'MultiArchSharedLibrary' for field 'type' but got 'MultiArchStaticLibrary'
+
+// ============================================================================
+// namespace field errors
+// ============================================================================
+
+// The top-level namespace is required.
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/missing-namespace.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MISSING-NAMESPACE
+// MISSING-NAMESPACE: clang-ssaf-format: error: reading MultiArchSharedLibrary from file '{{.*}}missing-namespace.json'
+// MISSING-NAMESPACE-NEXT: failed to read BuildNamespace from field 'namespace': expected JSON object
+
+// The namespace field must be a JSON object, not a string.
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/namespace-not-object.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NAMESPACE-NOT-OBJECT
+// NAMESPACE-NOT-OBJECT: clang-ssaf-format: error: reading MultiArchSharedLibrary from file '{{.*}}namespace-not-object.json'
+// NAMESPACE-NOT-OBJECT-NEXT: failed to read BuildNamespace from field 'namespace': expected JSON object
+
+// The namespace kind must be MultiArchSharedLibrary.
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/namespace-wrong-kind.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NAMESPACE-WRONG-KIND
+// NAMESPACE-WRONG-KIND: clang-ssaf-format: error: reading MultiArchSharedLibrary from file '{{.*}}namespace-wrong-kind.json'
+// NAMESPACE-WRONG-KIND-NEXT: expected 'MultiArchSharedLibrary' for field 'namespace.kind' but got 'MultiArchStaticLibrary'
+
+// ============================================================================
+// members field errors
+// ============================================================================
+
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/missing-members.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MISSING-MEMBERS
+// MISSING-MEMBERS: clang-ssaf-format: error: reading MultiArchSharedLibrary from file '{{.*}}missing-members.json'
+// MISSING-MEMBERS-NEXT: failed to read MultiArchSharedLibrary members from field 'members': expected JSON array
+
+// The members field must be a JSON array, not an object.
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/members-not-array.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MEMBERS-NOT-ARRAY
+// MEMBERS-NOT-ARRAY: clang-ssaf-format: error: reading MultiArchSharedLibrary from file '{{.*}}members-not-array.json'
+// MEMBERS-NOT-ARRAY-NEXT: failed to read MultiArchSharedLibrary members from field 'members': expected JSON array
+
+// ============================================================================
+// per-member errors
+// ============================================================================
+
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/member-not-object.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MEMBER-NOT-OBJECT
+// MEMBER-NOT-OBJECT: clang-ssaf-format: error: reading MultiArchSharedLibrary from file '{{.*}}member-not-object.json'
+// MEMBER-NOT-OBJECT-NEXT: failed to read MultiArchSharedLibrary member from index '0': expected JSON object
+
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/member-missing-type.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MEMBER-MISSING-TYPE
+// MEMBER-MISSING-TYPE: clang-ssaf-format: error: reading MultiArchSharedLibrary from file '{{.*}}member-missing-type.json'
+// MEMBER-MISSING-TYPE-NEXT: reading MultiArchSharedLibrary member from index '0'
+// MEMBER-MISSING-TYPE-NEXT: failed to read summary type from field 'type': expected JSON string
+
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/member-mismatched-type.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MEMBER-MISMATCHED-TYPE
+// MEMBER-MISMATCHED-TYPE: clang-ssaf-format: error: reading MultiArchSharedLibrary from file '{{.*}}member-mismatched-type.json'
+// MEMBER-MISMATCHED-TYPE-NEXT: reading MultiArchSharedLibrary member from index '0'
+// MEMBER-MISMATCHED-TYPE-NEXT: expected 'LUSummary' for field 'type' but got 'TUSummary'
+
+// All members must share the wrapper's namespace name (same logical
+// library, different architectures). A member whose LU-namespace leaf
+// step name differs from the wrapper's is rejected.
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/member-namespace-mismatch.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MEMBER-NAMESPACE-MISMATCH
+// MEMBER-NAMESPACE-MISMATCH: clang-ssaf-format: error: reading MultiArchSharedLibrary from file '{{.*}}member-namespace-mismatch.json'
+// MEMBER-NAMESPACE-MISMATCH-NEXT: reading MultiArchSharedLibrary member from index '0'
+// MEMBER-NAMESPACE-MISMATCH-NEXT: expected 'libfoo' for field 'lu_namespace.name' but got 'libbar'
+
+// Members are identified by their full target_triple. Two members sharing
+// the same target_triple are rejected.
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/duplicate-target-triple.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=DUPLICATE-TARGET-TRIPLE
+// DUPLICATE-TARGET-TRIPLE: clang-ssaf-format: error: reading MultiArchSharedLibrary from file '{{.*}}duplicate-target-triple.json'
+// DUPLICATE-TARGET-TRIPLE-NEXT: failed to insert MultiArchSharedLibrary member at index '1': encountered duplicate 'arm64-apple-macosx'
+
+// Verifies that errors raised inside readLUSummaryEncodingFromObject (here, a
+// missing lu_namespace on the member) are properly wrapped with the
+// "reading MultiArchSharedLibrary member from index 'N'" context, then
+// with the outer "reading MultiArchSharedLibrary from file '...'" context.
+// RUN: not clang-ssaf-format --type multi-arch-shared-library %S/Inputs/member-inner-error.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MEMBER-INNER-ERROR
+// MEMBER-INNER-ERROR: clang-ssaf-format: error: reading MultiArchSharedLibrary from file '{{.*}}member-inner-error.json'
+// MEMBER-INNER-ERROR-NEXT: reading MultiArchSharedLibrary member from index '0'
+// MEMBER-INNER-ERROR-NEXT: failed to read NestedBuildNamespace from field 'lu_namespace': expected JSON array
diff --git a/clang/tools/clang-ssaf-format/SSAFFormat.cpp b/clang/tools/clang-ssaf-format/SSAFFormat.cpp
index b0f123f75be2a..7317424d2ca42 100644
--- a/clang/tools/clang-ssaf-format/SSAFFormat.cpp
+++ b/clang/tools/clang-ssaf-format/SSAFFormat.cpp
@@ -12,6 +12,7 @@
//===----------------------------------------------------------------------===//
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h"
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h"
@@ -47,6 +48,7 @@ enum class SummaryType {
LU,
StaticLibrary,
MultiArchStaticLibrary,
+ MultiArchSharedLibrary,
WPA
};
@@ -77,6 +79,9 @@ cl::opt<SummaryType> Type(
clEnumValN(SummaryType::MultiArchStaticLibrary,
"multi-arch-static-library",
"Multi-architecture static library"),
+ clEnumValN(SummaryType::MultiArchSharedLibrary,
+ "multi-arch-shared-library",
+ "Multi-architecture shared library"),
clEnumValN(SummaryType::WPA, "wpa",
"Whole-program analysis suite")),
cl::init(SummaryType::Auto), cl::cat(SsafFormatCategory));
@@ -322,6 +327,13 @@ void convert(const FormatInput &FI) {
run(FI, &SerializationFormat::readMultiArchStaticLibrary,
&SerializationFormat::writeMultiArchStaticLibrary);
return;
+ case SummaryType::MultiArchSharedLibrary:
+ // MultiArchSharedLibrary has only an encoded representation, so
+ // --encoding is a no-op here: both paths route to
+ // readMultiArchSharedLibrary / writeMultiArchSharedLibrary.
+ run(FI, &SerializationFormat::readMultiArchSharedLibrary,
+ &SerializationFormat::writeMultiArchSharedLibrary);
+ return;
case SummaryType::WPA:
run(FI, &SerializationFormat::readWPASuite,
&SerializationFormat::writeWPASuite);
diff --git a/clang/unittests/ScalableStaticAnalysis/Frontend/TUSummaryExtractorFrontendActionTest.cpp b/clang/unittests/ScalableStaticAnalysis/Frontend/TUSummaryExtractorFrontendActionTest.cpp
index 79504a4f9754f..ab2b406850489 100644
--- a/clang/unittests/ScalableStaticAnalysis/Frontend/TUSummaryExtractorFrontendActionTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysis/Frontend/TUSummaryExtractorFrontendActionTest.cpp
@@ -118,6 +118,16 @@ class FailingSerializationFormat final : public SerializationFormat {
return failing("writeMultiArchStaticLibrary");
}
+ llvm::Expected<MultiArchSharedLibrary>
+ readMultiArchSharedLibrary(llvm::StringRef Path) override {
+ return failing("readMultiArchSharedLibrary");
+ }
+
+ llvm::Error writeMultiArchSharedLibrary(const MultiArchSharedLibrary &M,
+ llvm::StringRef Path) override {
+ return failing("writeMultiArchSharedLibrary");
+ }
+
llvm::Expected<WPASuite> readWPASuite(llvm::StringRef Path) override {
return failing("readWPASuite");
}
@@ -214,6 +224,14 @@ class CapturingSerializationFormat final : public SerializationFormat {
llvm::StringRef) override {
return llvm::Error::success();
}
+ llvm::Expected<MultiArchSharedLibrary>
+ readMultiArchSharedLibrary(llvm::StringRef) override {
+ return llvm::createStringError("not implemented");
+ }
+ llvm::Error writeMultiArchSharedLibrary(const MultiArchSharedLibrary &,
+ llvm::StringRef) override {
+ return llvm::Error::success();
+ }
llvm::Expected<WPASuite> readWPASuite(llvm::StringRef) override {
return llvm::createStringError("not implemented");
}
diff --git a/clang/unittests/ScalableStaticAnalysis/Registries/MockSerializationFormat.cpp b/clang/unittests/ScalableStaticAnalysis/Registries/MockSerializationFormat.cpp
index 92d6d0abc8936..236e81a16b56e 100644
--- a/clang/unittests/ScalableStaticAnalysis/Registries/MockSerializationFormat.cpp
+++ b/clang/unittests/ScalableStaticAnalysis/Registries/MockSerializationFormat.cpp
@@ -9,6 +9,7 @@
#include "Registries/MockSerializationFormat.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h"
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h"
@@ -222,6 +223,18 @@ llvm::Error MockSerializationFormat::writeMultiArchStaticLibrary(
"MockSerializationFormat does not support MultiArchStaticLibrary");
}
+llvm::Expected<MultiArchSharedLibrary>
+MockSerializationFormat::readMultiArchSharedLibrary(llvm::StringRef Path) {
+ llvm_unreachable(
+ "MockSerializationFormat does not support MultiArchSharedLibrary");
+}
+
+llvm::Error MockSerializationFormat::writeMultiArchSharedLibrary(
+ const MultiArchSharedLibrary &M, llvm::StringRef Path) {
+ llvm_unreachable(
+ "MockSerializationFormat does not support MultiArchSharedLibrary");
+}
+
llvm::Expected<WPASuite>
MockSerializationFormat::readWPASuite(llvm::StringRef Path) {
llvm_unreachable("MockSerializationFormat does not support WPASuite");
diff --git a/clang/unittests/ScalableStaticAnalysis/Registries/MockSerializationFormat.h b/clang/unittests/ScalableStaticAnalysis/Registries/MockSerializationFormat.h
index e03e5092589fe..f5f44c51d3e97 100644
--- a/clang/unittests/ScalableStaticAnalysis/Registries/MockSerializationFormat.h
+++ b/clang/unittests/ScalableStaticAnalysis/Registries/MockSerializationFormat.h
@@ -56,6 +56,12 @@ class MockSerializationFormat final : public SerializationFormat {
llvm::Error writeMultiArchStaticLibrary(const MultiArchStaticLibrary &M,
llvm::StringRef Path) override;
+ llvm::Expected<MultiArchSharedLibrary>
+ readMultiArchSharedLibrary(llvm::StringRef Path) override;
+
+ llvm::Error writeMultiArchSharedLibrary(const MultiArchSharedLibrary &M,
+ llvm::StringRef Path) override;
+
llvm::Expected<Artifact> readArtifact(llvm::StringRef Path) override;
llvm::Error writeArtifact(const Artifact &A, llvm::StringRef Path) override;
diff --git a/clang/unittests/ScalableStaticAnalysis/TestFixture.h b/clang/unittests/ScalableStaticAnalysis/TestFixture.h
index c2b7005bd4b0c..cb33dd57a31a9 100644
--- a/clang/unittests/ScalableStaticAnalysis/TestFixture.h
+++ b/clang/unittests/ScalableStaticAnalysis/TestFixture.h
@@ -11,6 +11,7 @@
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h"
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h"
More information about the cfe-commits
mailing list