Author: Aviral Goel
Date: 2026-07-02T20:11:37-07:00
New Revision: e69038f9c2b1c47dd7beebb5531129514fab36e7

URL: 
https://github.com/llvm/llvm-project/commit/e69038f9c2b1c47dd7beebb5531129514fab36e7
DIFF: 
https://github.com/llvm/llvm-project/commit/e69038f9c2b1c47dd7beebb5531129514fab36e7.diff

LOG: [clang][ssaf] Add `MultiArchSharedLibrary` data structure (#206854)

This change introduces `MultiArchSharedLibrary` data structure that wraps 
per-architecture `LUSummaryEncoding` members. This is the SSAF analogue of a 
fat shared library. The overall design mirrors the existing 
`MultiArchStaticLibrary` design: each member identifies the same logical 
library built for a different target triple. Support for constructing and 
consuming this object will be added in a future PR.

rdar://181164537

Added: 
    
clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h
    
clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/MultiArchSharedLibrary.cpp
    
clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-shared-library-empty.json
    
clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-shared-library-nonempty.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/duplicate-target-triple.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/invalid-syntax.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-empty-lu-namespace.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-inner-error.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-mismatched-type.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-missing-type.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-namespace-mismatch.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-not-object.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/members-not-array.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/mismatched-type.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/missing-members.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/missing-namespace.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/missing-type.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/namespace-not-array.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/not-json-extension.txt
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/not-object.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-empty-members.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-multiple-members.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-nonempty-members.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-single-member.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-three-members.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/unsorted-members-input.json
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/unsorted-three-members-input.json
    clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/io.test
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/permissions.test
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/round-trip.test
    
clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/top-level.test

Modified: 
    
clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h
    
clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h
    clang/include/clang/ScalableStaticAnalysis/Core/Model/PrivateFieldNames.def
    clang/include/clang/ScalableStaticAnalysis/Core/Serialization/JSONFormat.h
    
clang/include/clang/ScalableStaticAnalysis/Core/Serialization/SerializationFormat.h
    clang/lib/ScalableStaticAnalysis/Core/CMakeLists.txt
    clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/Artifact.cpp
    
clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.h
    
clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/LUSummaryEncoding.cpp
    clang/test/Analysis/Scalable/ssaf-format/Artifact/round-trip.test
    clang/test/Analysis/Scalable/ssaf-format/Artifact/top-level.test
    clang/tools/clang-ssaf-format/SSAFFormat.cpp
    
clang/unittests/ScalableStaticAnalysis/Frontend/TUSummaryExtractorFrontendActionTest.cpp
    
clang/unittests/ScalableStaticAnalysis/Registries/MockSerializationFormat.cpp
    clang/unittests/ScalableStaticAnalysis/Registries/MockSerializationFormat.h
    clang/unittests/ScalableStaticAnalysis/TestFixture.h

Removed: 
    


################################################################################
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..8c3f4345ee5f8
--- /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 
diff erent
+/// architectures; the wrapper's \c Namespace is the nested build
+/// namespace identifying that shared library, and every member's
+/// \c LUNamespace must equal it exactly.
+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 nested namespace identifying this multi-architecture shared
+  // library. It matches the LUNamespace of every member exactly (same
+  // path, same kinds, same names): the shared library's identity is the
+  // same across all its architecture slices.
+  NestedBuildNamespace 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(NestedBuildNamespace Namespace)
+      : Namespace(std::move(Namespace)) {}
+};
+
+} // namespace clang::ssaf
+
+#endif // 
LLVM_CLANG_SCALABLESTATICANALYSIS_CORE_ENTITYLINKER_MULTIARCHSHAREDLIBRARY_H

diff  --git 
a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h
 
b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h
index 37147c98243c3..6ff7ed347a791 100644
--- 
a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h
+++ 
b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h
@@ -8,8 +8,7 @@
 //
 // 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).
+// instances.
 //
 
//===----------------------------------------------------------------------===//
 
@@ -26,21 +25,15 @@ 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 
diff erent architectures;
-/// the wrapper's \c Namespace identifies that shared library and every
-/// member's namespace must agree on its name.
+/// A MultiArchStaticLibrary bundles per-architecture StaticLibrary members. 
All
+/// members represent the same logical library built for 
diff erent
+/// 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(),

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 f95f3f490da81..aa4d3d6e3a3cd 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/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..b17f296eda1b4 100644
--- 
a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.h
+++ 
b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.h
@@ -82,11 +82,13 @@ inline constexpr const char *FailedToReadObjectAtIndex =
 
 inline constexpr const char *MismatchedSummaryType =
     "expected '{0}' for field '{1}' but got '{2}'";
+inline constexpr const char *MismatchedNestedNamespace =
+    "field '{0}' does not equal the enclosing artifact's namespace";
 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 +160,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..d23eef1ff9fe9
--- /dev/null
+++ 
b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/MultiArchSharedLibrary.cpp
@@ -0,0 +1,163 @@
+//===- 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 Array *NamespaceArray = RootObject.getArray("namespace");
+  if (!NamespaceArray) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "NestedBuildNamespace", "namespace", "array")
+        .build();
+  }
+
+  auto ExpectedNamespace = nestedBuildNamespaceFromJSON(*NamespaceArray);
+  if (!ExpectedNamespace) {
+    return ErrorBuilder::wrap(ExpectedNamespace.takeError())
+        .context(ErrorMessages::ReadingFromField, "NestedBuildNamespace",
+                 "namespace")
+        .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 &WrapperNamespace = 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();
+    }
+
+    // Every per-arch slice must resolve to the same shared-library identity,
+    // so the member's LUNamespace must equal the wrapper's Namespace.
+    if (WrapperNamespace != getLUNamespace(*ExpectedMember)) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::MismatchedNestedNamespace,
+                                  "lu_namespace")
+          .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"] = nestedBuildNamespaceToJSON(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..1cd693b2fc8ce
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-shared-library-empty.json
@@ -0,0 +1,10 @@
+{
+  "members": [],
+  "namespace": [
+    {
+      "kind": "LinkUnit",
+      "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..dc00a24f0eaf0
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/Artifact/Inputs/rt-multi-arch-shared-library-nonempty.json
@@ -0,0 +1,79 @@
+{
+  "members": [
+    {
+      "data": [],
+      "id_table": [
+        {
+          "id": 0,
+          "name": {
+            "namespace": [
+              {
+                "kind": "LinkUnit",
+                "name": "libfoo"
+              }
+            ],
+            "suffix": "",
+            "usr": "c:@F@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@foo"
+          }
+        }
+      ],
+      "linkage_table": [
+        {
+          "id": 0,
+          "linkage": {
+            "type": "External"
+          }
+        }
+      ],
+      "lu_namespace": [
+        {
+          "kind": "LinkUnit",
+          "name": "libfoo"
+        }
+      ],
+      "target_triple": "x86_64-apple-macosx",
+      "type": "LUSummary"
+    }
+  ],
+  "namespace": [
+    {
+      "kind": "LinkUnit",
+      "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..f3142832581fb
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/duplicate-target-triple.json
@@ -0,0 +1,37 @@
+{
+  "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": "LinkUnit",
+      "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-empty-lu-namespace.json
 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-empty-lu-namespace.json
new file mode 100644
index 0000000000000..a8bdc5e79da9b
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-empty-lu-namespace.json
@@ -0,0 +1,19 @@
+{
+  "members": [
+    {
+      "data": [],
+      "id_table": [],
+      "linkage_table": [],
+      "lu_namespace": [],
+      "target_triple": "arm64-apple-macosx",
+      "type": "LUSummary"
+    }
+  ],
+  "namespace": [
+    {
+      "kind": "LinkUnit",
+      "name": "libfoo"
+    }
+  ],
+  "type": "MultiArchSharedLibrary"
+}

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..5b6b7413b1738
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-inner-error.json
@@ -0,0 +1,18 @@
+{
+  "members": [
+    {
+      "data": [],
+      "id_table": [],
+      "linkage_table": [],
+      "target_triple": "arm64-apple-macosx",
+      "type": "LUSummary"
+    }
+  ],
+  "namespace": [
+    {
+      "kind": "LinkUnit",
+      "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..c48d5a5cc3eb4
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-mismatched-type.json
@@ -0,0 +1,24 @@
+{
+  "members": [
+    {
+      "data": [],
+      "id_table": [],
+      "linkage_table": [],
+      "lu_namespace": [
+        {
+          "kind": "LinkUnit",
+          "name": "libfoo"
+        }
+      ],
+      "target_triple": "arm64-apple-macosx",
+      "type": "TUSummary"
+    }
+  ],
+  "namespace": [
+    {
+      "kind": "LinkUnit",
+      "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..3630d1843e243
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-missing-type.json
@@ -0,0 +1,23 @@
+{
+  "members": [
+    {
+      "data": [],
+      "id_table": [],
+      "linkage_table": [],
+      "lu_namespace": [
+        {
+          "kind": "LinkUnit",
+          "name": "libfoo"
+        }
+      ],
+      "target_triple": "arm64-apple-macosx"
+    }
+  ],
+  "namespace": [
+    {
+      "kind": "LinkUnit",
+      "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..ab779950af56d
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-namespace-mismatch.json
@@ -0,0 +1,24 @@
+{
+  "members": [
+    {
+      "data": [],
+      "id_table": [],
+      "linkage_table": [],
+      "lu_namespace": [
+        {
+          "kind": "LinkUnit",
+          "name": "libbar"
+        }
+      ],
+      "target_triple": "arm64-apple-macosx",
+      "type": "LUSummary"
+    }
+  ],
+  "namespace": [
+    {
+      "kind": "LinkUnit",
+      "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..8269b374cb602
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/member-not-object.json
@@ -0,0 +1,12 @@
+{
+  "members": [
+    "not an object"
+  ],
+  "namespace": [
+    {
+      "kind": "LinkUnit",
+      "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..7b84c5635327a
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/members-not-array.json
@@ -0,0 +1,10 @@
+{
+  "members": {},
+  "namespace": [
+    {
+      "kind": "LinkUnit",
+      "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..3d7f01538bee1
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/mismatched-type.json
@@ -0,0 +1,10 @@
+{
+  "members": [],
+  "namespace": [
+    {
+      "kind": "LinkUnit",
+      "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..5191a4f092f2d
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/missing-members.json
@@ -0,0 +1,9 @@
+{
+  "namespace": [
+    {
+      "kind": "LinkUnit",
+      "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..79794ae546e1e
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/missing-type.json
@@ -0,0 +1,9 @@
+{
+  "members": [],
+  "namespace": [
+    {
+      "kind": "LinkUnit",
+      "name": "libfoo"
+    }
+  ]
+}

diff  --git 
a/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/namespace-not-array.json
 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/namespace-not-array.json
new file mode 100644
index 0000000000000..3dc32039cb74a
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/namespace-not-array.json
@@ -0,0 +1,5 @@
+{
+  "members": [],
+  "namespace": "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..1cd693b2fc8ce
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-empty-members.json
@@ -0,0 +1,10 @@
+{
+  "members": [],
+  "namespace": [
+    {
+      "kind": "LinkUnit",
+      "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..d1de17900eb84
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-multiple-members.json
@@ -0,0 +1,37 @@
+{
+  "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": "LinkUnit",
+      "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..dc00a24f0eaf0
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-nonempty-members.json
@@ -0,0 +1,79 @@
+{
+  "members": [
+    {
+      "data": [],
+      "id_table": [
+        {
+          "id": 0,
+          "name": {
+            "namespace": [
+              {
+                "kind": "LinkUnit",
+                "name": "libfoo"
+              }
+            ],
+            "suffix": "",
+            "usr": "c:@F@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@foo"
+          }
+        }
+      ],
+      "linkage_table": [
+        {
+          "id": 0,
+          "linkage": {
+            "type": "External"
+          }
+        }
+      ],
+      "lu_namespace": [
+        {
+          "kind": "LinkUnit",
+          "name": "libfoo"
+        }
+      ],
+      "target_triple": "x86_64-apple-macosx",
+      "type": "LUSummary"
+    }
+  ],
+  "namespace": [
+    {
+      "kind": "LinkUnit",
+      "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..bf7e570e21bf5
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-single-member.json
@@ -0,0 +1,24 @@
+{
+  "members": [
+    {
+      "data": [],
+      "id_table": [],
+      "linkage_table": [],
+      "lu_namespace": [
+        {
+          "kind": "LinkUnit",
+          "name": "libfoo"
+        }
+      ],
+      "target_triple": "arm64-apple-macosx",
+      "type": "LUSummary"
+    }
+  ],
+  "namespace": [
+    {
+      "kind": "LinkUnit",
+      "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..ce123fe904dda
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/rt-three-members.json
@@ -0,0 +1,50 @@
+{
+  "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": "LinkUnit",
+      "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..3c47d9167107e
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/unsorted-members-input.json
@@ -0,0 +1,37 @@
+{
+  "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": "LinkUnit",
+      "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..ff5f5f88e134d
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/Inputs/unsorted-three-members-input.json
@@ -0,0 +1,50 @@
+{
+  "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": "LinkUnit",
+      "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..c0f9aa2bef153
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/ssaf-format/MultiArchSharedLibrary/top-level.test
@@ -0,0 +1,105 @@
+// 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 and must be a JSON array of
+// BuildNamespace steps.
+// 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 NestedBuildNamespace from field 
'namespace': expected JSON array
+
+// The namespace field must be a JSON array, not a string.
+// RUN: not clang-ssaf-format --type multi-arch-shared-library 
%S/Inputs/namespace-not-array.json 2>&1 \
+// RUN:   | FileCheck %s --match-full-lines --check-prefix=NAMESPACE-NOT-ARRAY
+// NAMESPACE-NOT-ARRAY:      clang-ssaf-format: error: reading 
MultiArchSharedLibrary from file '{{.*}}namespace-not-array.json'
+// NAMESPACE-NOT-ARRAY-NEXT: failed to read NestedBuildNamespace from field 
'namespace': expected JSON array
+
+// ============================================================================
+// 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'
+
+// Each member is a per-arch slice of the same shared library, so its
+// lu_namespace must equal the wrapper's namespace exactly. A member
+// whose leaf-step name 
diff ers 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: field 'lu_namespace' does not equal the 
enclosing artifact's namespace
+
+// A member with an empty lu_namespace array cannot equal the wrapper's
+// (non-empty) namespace, so the equality check also fires.
+// RUN: not clang-ssaf-format --type multi-arch-shared-library 
%S/Inputs/member-empty-lu-namespace.json 2>&1 \
+// RUN:   | FileCheck %s --match-full-lines 
--check-prefix=MEMBER-EMPTY-LU-NAMESPACE
+// MEMBER-EMPTY-LU-NAMESPACE:      clang-ssaf-format: error: reading 
MultiArchSharedLibrary from file '{{.*}}member-empty-lu-namespace.json'
+// MEMBER-EMPTY-LU-NAMESPACE-NEXT: reading MultiArchSharedLibrary member from 
index '0'
+// MEMBER-EMPTY-LU-NAMESPACE-NEXT: field 'lu_namespace' does not equal the 
enclosing artifact's namespace
+
+// 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"


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to