[clang] [llvm] [clang][ssaf] Add entity metadata for linking (PR #214369)
Aviral Goel via cfe-commits
cfe-commits at lists.llvm.org
Sun Aug 9 18:20:19 PDT 2026
https://github.com/aviralg updated https://github.com/llvm/llvm-project/pull/214369
>From e105187ed37e710cd0b7a00f03c306b6a1c93cc1 Mon Sep 17 00:00:00 2001
From: Aviral Goel <goel.aviral at gmail.com>
Date: Sun, 9 Aug 2026 18:17:46 -0700
Subject: [PATCH] Add linkage fields with EntityLinker support
---
.../Core/EntityLinker/EntityLinker.h | 307 ++++++-
.../Core/EntityLinker/EntitySummaryEncoding.h | 34 +-
.../Core/EntityLinker/LinkageRules.h | 124 +++
.../Core/Model/EntityLinkage.h | 89 +-
.../Core/Model/PrivateFieldNames.def | 4 +
.../Core/TUSummary/TUSummaryBuilder.h | 2 +-
.../Core/CMakeLists.txt | 1 +
.../Core/EntityLinker/EntityLinker.cpp | 591 ++++++++++--
.../Core/EntityLinker/LinkageRules.cpp | 450 +++++++++
.../Core/Model/EntityLinkage.cpp | 28 +-
.../Core/ModelStringConversions.h | 119 +++
.../JSONFormat/JSONEntitySummaryEncoding.cpp | 46 +-
.../JSONFormat/JSONEntitySummaryEncoding.h | 19 +-
.../JSONFormat/JSONFormatImpl.cpp | 140 ++-
.../Serialization/JSONFormat/JSONFormatImpl.h | 29 +
.../Core/TUSummary/TUSummaryBuilder.cpp | 14 +-
.../Core/TUSummary/TUSummaryExtractor.cpp | 160 +++-
.../Inputs/tu-summary-bad-array.json | 18 +-
.../Inputs/tu-summary-bad-summary.json | 18 +-
.../PointerFlow/Inputs/tu-summary-no-key.json | 18 +-
.../PointerFlow/Inputs/tu-summary.json | 18 +-
.../Inputs/tu-summary-bad-id.json | 6 +-
.../Inputs/tu-summary-no-key.json | 6 +-
.../Inputs/tu-summary.json | 18 +-
.../Inputs/tu-summary-bad-element.json | 12 +-
.../Inputs/tu-summary-bad-ptr-level.json | 12 +-
.../Inputs/tu-summary-no-key.json | 12 +-
.../UnsafeBufferUsage/Inputs/tu-summary.json | 18 +-
.../Analysis/Scalable/linkage-extraction.cpp | 73 ++
.../ssaf-analyzer/Inputs/lu-tags-only.json | 6 +-
.../Scalable/ssaf-analyzer/Inputs/lu.json | 18 +-
...rt-multi-arch-shared-library-nonempty.json | 12 +-
.../invalid-direct-callee-element.json | 6 +-
.../CallGraph/invalid-direct-callee-id.json | 6 +-
.../Inputs/CallGraph/missing-def-col.json | 6 +-
.../Inputs/CallGraph/missing-def-file.json | 6 +-
.../Inputs/CallGraph/missing-def-line.json | 6 +-
.../Inputs/CallGraph/missing-def.json | 6 +-
.../CallGraph/missing-direct-callees.json | 6 +-
.../Inputs/CallGraph/missing-pretty-name.json | 6 +-
.../CallGraph/missing-virtual-callees.json | 6 +-
.../duplicate-entity-id-in-data-map.json | 6 +-
.../LUSummary/Inputs/duplicate-entity.json | 12 +-
.../Inputs/entity-name-missing-namespace.json | 6 +-
.../Inputs/entity-name-missing-suffix.json | 6 +-
.../Inputs/entity-name-missing-usr.json | 6 +-
.../Inputs/linkage-table-duplicate-id.json | 12 +-
.../linkage-table-entry-id-not-uint64.json | 6 +-
...kage-table-entry-linkage-invalid-type.json | 6 +-
.../linkage-table-entry-missing-id.json | 6 +-
.../Inputs/linkage-table-extra-id.json | 6 +-
.../namespace-element-invalid-kind.json | 6 +-
.../namespace-element-missing-kind.json | 6 +-
.../namespace-element-missing-name.json | 6 +-
.../Inputs/namespace-element-not-object.json | 6 +-
.../LUSummary/Inputs/rt-linkage-external.json | 6 +-
.../LUSummary/Inputs/rt-linkage-internal.json | 6 +-
.../LUSummary/Inputs/rt-linkage-multiple.json | 18 +-
.../LUSummary/Inputs/rt-linkage-none.json | 6 +-
.../Inputs/rt-two-summary-types.json | 30 +-
.../Inputs/rt-nonempty-members.json | 12 +-
.../duplicate-entity-id-in-data-map.json | 6 +-
.../TUSummary/Inputs/duplicate-entity.json | 4 +-
.../Inputs/entity-name-missing-suffix.json | 2 +-
.../Inputs/entity-name-missing-usr.json | 2 +-
.../Inputs/id-table-entry-missing-name.json | 2 +-
.../Inputs/linkage-table-duplicate-id.json | 4 +-
.../linkage-table-entry-id-not-uint64.json | 2 +-
...able-entry-linkage-invalid-coalescing.json | 16 +
...able-entry-linkage-missing-coalescing.json | 16 +
.../linkage-table-entry-missing-id.json | 2 +-
.../Inputs/linkage-table-extra-id.json | 2 +-
.../TUSummary/Inputs/rt-linkage-external.json | 6 +-
.../TUSummary/Inputs/rt-linkage-internal.json | 6 +-
.../TUSummary/Inputs/rt-linkage-multiple.json | 18 +-
.../TUSummary/Inputs/rt-linkage-none.json | 6 +-
.../Inputs/rt-two-summary-types.json | 30 +-
.../ssaf-format/TUSummary/linkage.test | 22 +
.../ssaf-linker/Inputs/carry-coff.json | 115 +++
.../ssaf-linker/Inputs/carry-elf.json | 115 +++
.../ssaf-linker/Inputs/carry-macho.json | 115 +++
.../ssaf-linker/Inputs/join-lhs-coff.json | 139 +++
.../ssaf-linker/Inputs/join-lhs-elf.json | 139 +++
.../ssaf-linker/Inputs/join-lhs-macho.json | 139 +++
.../ssaf-linker/Inputs/join-rhs-coff.json | 139 +++
.../ssaf-linker/Inputs/join-rhs-elf.json | 139 +++
.../ssaf-linker/Inputs/join-rhs-macho.json | 139 +++
.../Scalable/ssaf-linker/Inputs/tu-1.json | 36 +-
.../Scalable/ssaf-linker/Inputs/tu-2.json | 36 +-
.../Inputs/tu-invalid-entity-id-multikey.json | 6 +-
.../Inputs/tu-invalid-entity-id-ref.json | 6 +-
.../Inputs/tu-invalid-entity-id-value.json | 6 +-
.../Inputs/tu-redefines-shared-ext.json | 43 +
.../ssaf-linker/Inputs/vis-lhs-elf.json | 64 ++
.../ssaf-linker/Inputs/vis-lhs-macho.json | 64 ++
.../ssaf-linker/Inputs/vis-rhs-elf.json | 64 ++
.../ssaf-linker/Inputs/vis-rhs-macho.json | 64 ++
.../Scalable/ssaf-linker/Outputs/libtwo.json | 72 +-
.../Scalable/ssaf-linker/Outputs/lu-1+2.json | 66 +-
.../Scalable/ssaf-linker/Outputs/lu-1.json | 36 +-
.../Scalable/ssaf-linker/Outputs/lu-2.json | 36 +-
.../Analysis/Scalable/ssaf-linker/help.test | 9 +
.../ssaf-linker/linkage-carry-through.test | 70 ++
.../ssaf-linker/linkage-join-visibility.test | 62 ++
.../ssaf-linker/linkage-join-winner.test | 74 ++
.../Scalable/ssaf-linker/linking-errors.test | 5 +
.../Scalable/ssaf-linker/linking.test | 10 +
.../Scalable/ssaf-linker/verbose.test | 7 +-
clang/tools/clang-ssaf-linker/SSAFLinker.cpp | 103 ++-
.../PointerFlow/PointerFlowWPATest.cpp | 7 +-
.../UnsafeBufferUsageWPATest.cpp | 7 +-
.../EntityLinkageTest.cpp | 96 +-
.../EntityLinkerTest.cpp | 869 +++++++++++++++++-
.../SharedLexicalRepresentationFormatTest.cpp | 7 +-
.../SummaryData/SummaryDataTest.cpp | 7 +-
.../ScalableStaticAnalysis/TestFixture.h | 30 +
.../AnalysisDriverTest.cpp | 7 +-
.../UnsafeBufferReachableAnalysisTest.cpp | 7 +-
docs/cmake_install.cmake | 45 +
docs/ssaf-linker-coff-behavior.md | 505 ++++++++++
docs/ssaf-linker-elf-behavior.md | 742 +++++++++++++++
docs/ssaf-linker-implementation-plan.md | 607 ++++++++++++
docs/ssaf-linker-macho-behavior.md | 662 +++++++++++++
123 files changed, 8102 insertions(+), 410 deletions(-)
create mode 100644 clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/LinkageRules.h
create mode 100644 clang/lib/ScalableStaticAnalysis/Core/EntityLinker/LinkageRules.cpp
create mode 100644 clang/test/Analysis/Scalable/linkage-extraction.cpp
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-entry-linkage-invalid-coalescing.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-entry-linkage-missing-coalescing.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/carry-coff.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/carry-elf.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/carry-macho.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-lhs-coff.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-lhs-elf.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-lhs-macho.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-rhs-coff.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-rhs-elf.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-rhs-macho.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-redefines-shared-ext.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/vis-lhs-elf.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/vis-lhs-macho.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/vis-rhs-elf.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/vis-rhs-macho.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/linkage-carry-through.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/linkage-join-visibility.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/linkage-join-winner.test
create mode 100644 docs/cmake_install.cmake
create mode 100644 docs/ssaf-linker-coff-behavior.md
create mode 100644 docs/ssaf-linker-elf-behavior.md
create mode 100644 docs/ssaf-linker-implementation-plan.md
create mode 100644 docs/ssaf-linker-macho-behavior.md
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.h b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.h
index f07def1a9b344..365d293dcbec6 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.h
@@ -15,29 +15,99 @@
#define LLVM_CLANG_SCALABLESTATICANALYSIS_CORE_ENTITYLINKER_ENTITYLINKER_H
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h"
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LinkageRules.h"
#include "llvm/Support/Error.h"
#include "llvm/TargetParser/Triple.h"
#include <map>
#include <memory>
#include <set>
+#include <utility>
#include <vector>
namespace clang::ssaf {
class TUSummaryEncoding;
+/// How the linker reports entities that no linked TU defines.
+enum class UnresolvedPolicy : uint8_t {
+ Ignore, ///< do not check
+ Warn, ///< report on stderr and keep linking
+ Error ///< fail the link
+};
+
+/// How the linker reports summary data that disagrees between two definitions
+/// required to be identical.
+enum class ODRMismatchPolicy : uint8_t {
+ Ignore, ///< do not check
+ Warn, ///< report on stderr and keep linking
+ Error ///< fail the link
+};
+
class EntityLinker {
+ friend class TestFixture;
+
+ /// Records, for each LU EntityId a TU resolved to, whether that TU's summary
+ /// data for the entity is the copy to keep. It is false when an earlier TU's
+ /// occurrence won the linkage reconciliation, in which case the data the TU
+ /// carries for that entity is dropped.
+ using DataSelectionMap = std::map<EntityId, bool>;
+
LUSummaryEncoding Output;
std::set<BuildNamespace> ProcessedTUNamespaces;
+ /// The resolution rules for the link unit's object format. Owned statically
+ /// by LinkageRules::forTarget().
+ const LinkageRules *Rules;
+
+ /// Whether a multiple definition is reported as a warning rather than
+ /// failing the link.
+ bool WarnOnMultipleDefinitions;
+
+ /// How entities that no linked TU defines are reported.
+ UnresolvedPolicy UnresolvedSymbols;
+
+ /// How summary data that disagrees between ODR definitions is reported.
+ ODRMismatchPolicy ODRMismatch;
+
+ /// Summary data displaced by a duplicate ODR definition, kept only when
+ /// \c ODRMismatch asks for the comparison. Per-link scratch, compared once
+ /// the whole TU has been patched into the LU EntityId space.
+ struct DisplacedData {
+ SummaryName Name; ///< the summary it belongs to
+ EntityId Id; ///< the LU entity it describes
+ std::unique_ptr<EntitySummaryEncoding> Data; ///< the displaced encoding
+ };
+ std::vector<DisplacedData> Displaced;
+
+ /// Names of the LU entities, kept so that finalization can name them in
+ /// diagnostics. EntityIdTable offers no reverse lookup.
+ std::map<EntityId, EntityName> EntityNames;
+
public:
/// Constructs an EntityLinker to link TU summaries into a LU summary.
///
- /// \param TargetTriple The target triple of the link unit. Every linked TU
- /// must report the same triple.
+ /// \param TargetTriple The target triple of the link unit. It selects the
+ /// resolution rules to emulate, and every linked TU must report a
+ /// compatible triple.
/// \param LUNamespace The namespace identifying this link unit.
- EntityLinker(llvm::Triple TargetTriple, NestedBuildNamespace LUNamespace)
- : Output(std::move(TargetTriple), std::move(LUNamespace)) {}
+ /// \param WarnOnMultipleDefinitions Report an external entity defined by more
+ /// than one TU as a warning on stderr and keep linking. By default
+ /// such an entity fails the link, matching a linker's
+ /// multiple-definition rule.
+ /// \param UnresolvedSymbols How to report entities that no linked TU defines.
+ /// Defaults to \c Ignore because a link unit is usually an
+ /// intermediate artifact, where unresolved references are expected.
+ /// \param ODRMismatch How to report two definitions that were required to be
+ /// identical but whose summary data differs. Defaults to \c Ignore
+ /// because the check costs an extra patch of every displaced encoding.
+ EntityLinker(llvm::Triple TargetTriple, NestedBuildNamespace LUNamespace,
+ bool WarnOnMultipleDefinitions = false,
+ UnresolvedPolicy UnresolvedSymbols = UnresolvedPolicy::Ignore,
+ ODRMismatchPolicy ODRMismatch = ODRMismatchPolicy::Ignore)
+ : Output(std::move(TargetTriple), std::move(LUNamespace)),
+ Rules(&LinkageRules::forTarget(Output.TargetTriple)),
+ WarnOnMultipleDefinitions(WarnOnMultipleDefinitions),
+ UnresolvedSymbols(UnresolvedSymbols), ODRMismatch(ODRMismatch) {}
/// Links a TU summary into a LU summary.
///
@@ -47,47 +117,250 @@ class EntityLinker {
/// \param Summary The TU summary to link. Ownership is transferred.
/// \returns Error if the TU namespace has already been linked or if patching
/// fails, success otherwise. Corrupted summary data (missing linkage
- /// information, duplicate entity IDs, etc.) triggers a fatal error.
+ /// information, duplicate entity IDs, etc.) triggers a fatal error,
+ /// as does an external entity defined by more than one TU unless
+ /// warnings were requested.
llvm::Error link(std::unique_ptr<TUSummaryEncoding> Summary);
/// Returns the accumulated LU summary.
///
+ /// Runs finalization first: rules that need the complete link unit, such as
+ /// reporting entities no TU defined and demoting hidden entities, cannot run
+ /// per-TU because a later TU can change the answer.
+ ///
/// \returns LU summary containing all the deduplicated and patched entity
/// summaries.
- LUSummaryEncoding takeOutput() && { return std::move(Output); }
+ LUSummaryEncoding takeOutput() && {
+ finalize();
+ return std::move(Output);
+ }
private:
+ /// Applies the rules that need the whole link unit. See takeOutput().
+ void finalize();
+
+ /// Reports entities that no linked TU defined, under \c UnresolvedSymbols.
+ ///
+ /// An undefined weak reference is legal by design and is never reported:
+ ///
+ /// __attribute__((weak)) int maybe_missing(void);
+ /// $ ld.lld -o out undef_weak_main.o -> links
+ ///
+ /// whereas a strong one fails when linking an executable. We default to
+ /// Ignore rather than emulating a specific platform because a link unit is
+ /// typically intermediate; see the plan's §3.1.
+ void reportUnresolvedEntities() const;
+
+ /// Demotes entities the target treats as hidden from `External` to
+ /// `Internal`, so a later link stage cannot resolve against them.
+ ///
+ /// This is the link-unit analogue of a hidden ELF symbol becoming LOCAL and
+ /// dropping out of .dynsym:
+ ///
+ /// // tu1.c
+ /// int v(void) { return 1; }
+ /// // tu2.c
+ /// __attribute__((visibility("hidden"))) int v(void);
+ /// $ ld.lld -shared -o out.so tu1.o tu2.o
+ /// v: FUNC LOCAL HIDDEN, absent from .dynsym
+ ///
+ /// Only the linkage type changes: the entity keeps its name, because every
+ /// EntityId already patched into the summary data refers to it, and external
+ /// names are LU-qualified so they cannot collide across link units.
+ /// (ELF doc §4 V1.)
+ void demoteHiddenEntities();
+
+ /// The binding the target emits for \p Linkage, per the active rules.
+ EntityBinding effectiveBinding(const EntityLinkage &Linkage) const;
+
+ /// Compares each displaced encoding against the one kept in its place.
+ ///
+ /// Only meaningful for entities whose definitions were required to be
+ /// identical: for a plain weak definition, differing bodies are legal and
+ /// the same check would be noise. Runs after patch(), because the two
+ /// encodings are only comparable once both are in the LU EntityId space.
+ ///
+ /// \param TUNamespace The namespace of the TU whose data was displaced.
+ void reportODRMismatches(const NestedBuildNamespace &TUNamespace);
+
+ /// Returns true if \p Current and \p Incoming are two definitions of the
+ /// same external entity that the target cannot both accept.
+ ///
+ /// \param Current The linkage already recorded for the entity.
+ /// \param Incoming The linkage of the occurrence being linked.
+ bool isConflictingDefinition(const EntityLinkage &Current,
+ const EntityLinkage &Incoming) const;
+
+ /// Decides whether the summary data of the occurrence being linked replaces
+ /// the data already linked for the same external entity.
+ ///
+ /// A definition beats a declaration, and among definitions the stronger
+ /// binding wins. Every other case keeps the data already linked, so ties
+ /// resolve in favour of whichever occurrence was linked first. This is the
+ /// one reconciliation rule that is deliberately order-sensitive, and every
+ /// platform agrees on it:
+ ///
+ /// __attribute__((weak)) int f(void) { return 1; } // weak_a.c
+ /// __attribute__((weak)) int f(void) { return 2; } // weak_b.c
+ /// $ ld.lld -o out main.o weak_a.o weak_b.o
+ /// f -> returns 1, the first one linked
+ ///
+ /// (ELF doc §1 P4, Mach-O doc §1 M4.)
+ ///
+ /// \param Current The linkage already recorded for the entity.
+ /// \param Incoming The linkage of the occurrence being linked.
+ bool incomingDataWins(const EntityLinkage &Current,
+ const EntityLinkage &Incoming) const;
+
+ /// Reconciles two occurrences of the same external entity into the linkage
+ /// recorded for it.
+ ///
+ /// Binding and coalescing describe definitions, so a declaration contributes
+ /// neither when the other occurrence defines the entity — every platform
+ /// agrees that a weak declaration leaves no trace on a common definition
+ /// (ELF doc §6.1, Mach-O doc §6.1, COFF doc §6.2). Visibility merges across
+ /// all occurrences, including ones that lose resolution.
+ ///
+ /// The result is commutative except where the target itself is
+ /// order-dependent; see LinkageRules::isOrderDependentMerge().
+ ///
+ /// \param Current The linkage already recorded for the entity.
+ /// \param Incoming The linkage of the occurrence being linked.
+ /// \pre Both occurrences have the same linkage type. Namespace resolution
+ /// guarantees this for every collision the linker reconciles.
+ EntityLinkage mergeLinkage(const EntityLinkage &Current,
+ const EntityLinkage &Incoming) const;
+
+ /// Records \p TUNamespace as linked into this link unit.
+ ///
+ /// \param TUNamespace The namespace of the TU being linked.
+ /// \returns Error if a TU with this namespace has already been linked,
+ /// success otherwise.
+ llvm::Error checkTUNotAlreadyLinked(const BuildNamespace &TUNamespace);
+
+ /// Verifies that \p TUTriple selects the same resolution rules as the link
+ /// unit's own triple.
+ ///
+ /// Rules differ enough between formats that applying the wrong ones is
+ /// silently incorrect: Mach-O ranks a weak definition above a common while
+ /// ELF and COFF do the reverse, and COFF rejects two weak definitions the
+ /// others accept. The OS version is ignored, since it does not affect
+ /// resolution.
+ ///
+ /// \returns Error if the TU targets a different platform, success otherwise.
+ llvm::Error checkTUTargetMatches(const llvm::Triple &TUTriple,
+ const BuildNamespace &TUNamespace) const;
+
+ /// Fails the link if \p Linkage holds a value the target cannot express.
+ ///
+ /// \param Name The entity the linkage belongs to, for diagnostics.
+ void reportIfNotRepresentable(const EntityLinkage &Linkage,
+ const EntityName &Name) const;
+
+ /// Warns when reconciling these two occurrences gives an order-dependent
+ /// result, which means the program itself is ambiguous.
+ void reportIfOrderDependent(const EntityLinkage &Current,
+ const EntityLinkage &Incoming,
+ const EntityName &Name) const;
+
+ /// Fails the link if a collision happened on a non-`External` entity.
+ ///
+ /// `None` and `Internal` entities are qualified with their TU namespace, so
+ /// they can never collide across TUs. A collision means the TU summary is
+ /// corrupted or namespace resolution is buggy.
+ ///
+ /// \param NewId The LU EntityId that collided.
+ /// \param Linkage The linkage of the colliding occurrence.
+ static void reportIfLinkageIsNotExternal(EntityId NewId,
+ const EntityLinkage &Linkage);
+
+ /// Reports an external entity that more than one TU defines.
+ ///
+ /// Does nothing unless \p Current and \p Incoming both strongly define the
+ /// entity. Otherwise it terminates with a fatal error, or emits a warning on
+ /// stderr and returns if \c WarnOnMultipleDefinitions is set.
+ ///
+ /// \param Current The linkage already recorded for the entity.
+ /// \param Incoming The linkage of the occurrence being linked.
+ /// \param Name The LU entity name the two occurrences resolved to.
+ /// \param TUNamespace The namespace of the TU carrying the extra definition.
+ /// \returns True if the two occurrences conflict, in which case the caller
+ /// keeps the definition already linked and ignores \p Incoming
+ /// entirely rather than reconciling the two.
+ bool reportIfDefinitionsConflict(
+ const EntityLinkage &Current, const EntityLinkage &Incoming,
+ const EntityName &Name, const NestedBuildNamespace &TUNamespace) const;
+
/// Resolves a TU entity name to an LU entity name and ID.
///
/// \param OldName The entity name in the TU namespace.
/// \param Linkage The linkage determining namespace resolution strategy.
- /// \returns The resolved LU EntityId.
- EntityId resolveEntity(const EntityName &OldName,
- const EntityLinkage &Linkage,
- const NestedBuildNamespace &TUNamespace);
+ /// \param TUNamespace The namespace of the TU being linked.
+ /// \returns The resolved LU EntityId, paired with whether this TU's summary
+ /// data for it is the copy to keep.
+ std::pair<EntityId, bool>
+ resolveEntity(const EntityName &OldName, const EntityLinkage &Linkage,
+ const NestedBuildNamespace &TUNamespace);
/// Resolves each TU EntityId to its corresponding LU EntityId.
///
/// \param Summary The TU summary whose entities are being resolved.
- /// \returns A map from TU EntityIds to their corresponding LU EntityIds.
- std::map<EntityId, EntityId> resolve(const TUSummaryEncoding &Summary);
+ /// \returns A map from TU EntityIds to their corresponding LU EntityIds,
+ /// paired with the data selection for each LU EntityId resolved.
+ std::pair<EntityResolutionMap, DataSelectionMap>
+ resolve(const TUSummaryEncoding &Summary);
/// Merges all summary data from a TU summary into the LU Summary.
///
/// \param Summary The TU summary whose data is being merged.
- /// \param EntityResolutionTable Map from TU EntityIds to LU EntityIds.
+ /// \param Resolution Map from TU EntityIds to LU EntityIds.
+ /// \param DataSelection Which LU EntityIds this TU owns the data for.
/// \returns Pointers to each EntitySummaryEncoding successfully merged.
std::vector<EntitySummaryEncoding *>
- merge(TUSummaryEncoding &Summary,
- const std::map<EntityId, EntityId> &EntityResolutionTable);
+ merge(TUSummaryEncoding &Summary, const EntityResolutionMap &Resolution,
+ const DataSelectionMap &DataSelection);
+
+ /// Merges one summary's data from a TU summary into the LU summary.
+ ///
+ /// \param SN The summary the data belongs to.
+ /// \param DataMap The TU's encodings for \p SN, keyed by TU EntityId.
+ /// \param Summary The TU summary being merged.
+ /// \param Resolution Map from TU EntityIds to LU EntityIds.
+ /// \param DataSelection Which LU EntityIds this TU owns the data for.
+ /// \returns Pointers to each EntitySummaryEncoding successfully merged.
+ std::vector<EntitySummaryEncoding *>
+ mergeSummaryData(const SummaryName &SN, EntityDataMap &DataMap,
+ const TUSummaryEncoding &Summary,
+ const EntityResolutionMap &Resolution,
+ const DataSelectionMap &DataSelection);
+
+ /// Merges one entity's encoding into the LU summary data for a summary.
+ ///
+ /// The encoding is dropped when an earlier TU already contributed the copy
+ /// to keep for \p NewId.
+ ///
+ /// \param OutputData The LU summary data to merge into.
+ /// \param SN The summary the data belongs to, for diagnostics.
+ /// \param NewId The LU EntityId the data describes.
+ /// \param Encoding The TU's encoding for \p NewId. Ownership is transferred
+ /// only when the encoding is kept.
+ /// \param Linkage The TU's linkage for the entity.
+ /// \param DataFromIncoming Whether \p Encoding is the copy to keep.
+ /// \returns The merged encoding to patch, or nullptr if it was dropped
+ /// without being kept for comparison.
+ EntitySummaryEncoding *
+ mergeEntityData(EntityDataMap &OutputData, const SummaryName &SN,
+ EntityId NewId,
+ std::unique_ptr<EntitySummaryEncoding> &Encoding,
+ const EntityLinkage &Linkage, bool DataFromIncoming);
/// Patches EntityId references in merged summary data.
///
/// \param PatchTargets Vector of summary encodings that need patching.
- /// \param EntityResolutionTable Map from TU EntityIds to LU EntityIds.
+ /// \param Resolution Map from TU EntityIds to LU EntityIds.
/// \returns Error if patching any encoding fails, success otherwise.
llvm::Error patch(const std::vector<EntitySummaryEncoding *> &PatchTargets,
- const std::map<EntityId, EntityId> &EntityResolutionTable);
+ const EntityResolutionMap &Resolution);
};
} // namespace clang::ssaf
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/EntitySummaryEncoding.h b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/EntitySummaryEncoding.h
index 9442ecbbe24cc..76cf0de5fc462 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/EntitySummaryEncoding.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/EntitySummaryEncoding.h
@@ -17,9 +17,20 @@
#include "clang/ScalableStaticAnalysis/Core/Model/EntityId.h"
#include "llvm/Support/Error.h"
#include <map>
+#include <memory>
namespace clang::ssaf {
+class EntitySummaryEncoding;
+
+/// Maps each EntityId in a TU summary to the EntityId it resolved to in the
+/// link unit.
+using EntityResolutionMap = std::map<EntityId, EntityId>;
+
+/// Maps each entity of one summary to its encoded summary data.
+using EntityDataMap =
+ std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>;
+
/// Represents EntitySummary data in its serialized, format-specific encoding.
///
/// This abstract base class allows the entity linker to manipulate serialized
@@ -32,9 +43,26 @@ class EntitySummaryEncoding {
/// Updates EntityId references in the encoded data.
///
- /// \param EntityResolutionTable Mapping from old EntityIds to new EntityIds.
- virtual llvm::Error
- patch(const std::map<EntityId, EntityId> &EntityResolutionTable) = 0;
+ /// \param Resolution Mapping from old EntityIds to new EntityIds.
+ virtual llvm::Error patch(const EntityResolutionMap &Resolution) = 0;
+
+ /// Returns an identifier unique to this encoding's concrete class.
+ ///
+ /// SSAF is built without RTTI, so \c equals implementations use this to
+ /// establish that another encoding has the same type before casting it.
+ /// Implementations return the address of a static object scoped to the
+ /// implementing class.
+ virtual const void *getEncodingKind() const = 0;
+
+ /// Returns true if \p Other encodes equivalent data.
+ ///
+ /// Only encodings of the same concrete class can compare equal;
+ /// implementations return false for any other \c getEncodingKind().
+ ///
+ /// Used to detect definitions that were required to be identical but are
+ /// not. \pre Both operands have been patched into the same EntityId space;
+ /// comparing encodings that reference different spaces is meaningless.
+ virtual bool equals(const EntitySummaryEncoding &Other) const = 0;
};
} // namespace clang::ssaf
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/LinkageRules.h b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/LinkageRules.h
new file mode 100644
index 0000000000000..37c9c1e9fb689
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/LinkageRules.h
@@ -0,0 +1,124 @@
+//===- LinkageRules.h - Per-platform symbol resolution ----------*- 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 LinkageRules, which supplies the symbol resolution rules
+// the entity linker emulates for a given object format. Every rule here is
+// grounded in an observed linker behaviour; see
+// docs/ssaf-linker-{elf,macho,coff}-behavior.md.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSIS_CORE_ENTITYLINKER_LINKAGERULES_H
+#define LLVM_CLANG_SCALABLESTATICANALYSIS_CORE_ENTITYLINKER_LINKAGERULES_H
+
+#include "clang/ScalableStaticAnalysis/Core/Model/EntityLinkage.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/TargetParser/Triple.h"
+
+namespace clang::ssaf {
+
+/// The platform-specific symbol resolution rules the linker emulates.
+///
+/// The EntityLinkage enums carry no ordering of their own, because precedence
+/// is platform-specific: Mach-O ranks a weak definition above a common one
+/// while ELF and COFF do the reverse, and Mach-O merges visibility to the
+/// least restrictive while ELF merges to the most. The ranks below answer
+/// "which value wins the join", not "which value is semantically greater", and
+/// are deliberately inverted for some fields on some targets.
+class LinkageRules {
+public:
+ virtual ~LinkageRules() = default;
+
+ /// Returns the rules for \p TargetTriple, dispatching on its object format.
+ ///
+ /// The returned reference has static storage duration. An unsupported object
+ /// format is a fatal error naming the triple.
+ static const LinkageRules &forTarget(const llvm::Triple &TargetTriple);
+
+ /// The object format these rules emulate, for diagnostics.
+ virtual llvm::StringRef getName() const = 0;
+
+ /// Lowers a source-level linkage to the binding this platform actually
+ /// emits.
+ ///
+ /// A summary records what the source declared, so an inline function is
+ /// Strong + ODR regardless of target. The object formats disagree about how
+ /// to express that, and the disagreement changes resolution, so every
+ /// comparison below is made on the lowered value rather than the raw one.
+ virtual EntityBinding
+ effectiveBinding(const EntityLinkage &Linkage) const = 0;
+
+ /// Precedence among bindings: the greater rank prevails when two definitions
+ /// of the same entity collide.
+ ///
+ /// \param B A binding already lowered by effectiveBinding().
+ virtual unsigned strengthRank(EntityBinding B) const = 0;
+
+ /// Precedence among visibilities: the greater rank prevails when two
+ /// occurrences of the same entity disagree.
+ virtual unsigned visibilityRank(EntityVisibility V) const = 0;
+
+ /// Returns true if two definitions of the same external entity cannot
+ /// coexist, which is the multiple-definition error a real linker reports.
+ ///
+ /// This is not derivable from the ranks: COFF licenses duplicate definitions
+ /// by COMDAT rather than by weakness, and rejects two weak definitions that
+ /// ELF and Mach-O both accept.
+ ///
+ /// \pre Both occurrences are definitions.
+ virtual bool isConflictingDefinition(const EntityLinkage &Current,
+ const EntityLinkage &Incoming) const = 0;
+
+ /// Merges the visibility of two occurrences of the same entity.
+ ///
+ /// Defaults to the higher visibilityRank(). Mach-O overrides this because
+ /// its rule for common symbols depends on the bindings, not just the
+ /// visibilities.
+ virtual EntityVisibility mergeVisibility(const EntityLinkage &Current,
+ const EntityLinkage &Incoming) const;
+
+ /// Returns true if merging these two occurrences gives a result that depends
+ /// on which was linked first, so the caller can warn that the program is
+ /// ambiguous.
+ virtual bool isOrderDependentMerge(const EntityLinkage &Current,
+ const EntityLinkage &Incoming) const;
+
+ /// Normalizes \p Linkage to what this platform can represent.
+ ///
+ /// Values the platform's toolchain silently drops are coerced to their
+ /// platform equivalent. Values the toolchain could never have produced are
+ /// left for isRepresentable() to reject, since they indicate a corrupted or
+ /// hand-edited summary rather than ordinary portable source.
+ virtual EntityLinkage normalize(const EntityLinkage &Linkage) const;
+
+ /// Returns false if \p Linkage holds a value this platform's toolchain could
+ /// not have produced, which the caller reports as a fatal error.
+ ///
+ /// Distinct from normalize(): a value that is merely dropped at emission is
+ /// coerced, whereas a value clang refuses to emit at all is rejected.
+ virtual bool isRepresentable(const EntityLinkage &Linkage) const;
+
+protected:
+ // Friendship with EntityLinkage does not extend to subclasses, so the base
+ // reads its private fields on their behalf.
+ static EntityLinkageType linkageOf(const EntityLinkage &L);
+ static EntityBinding bindingOf(const EntityLinkage &L);
+ static EntityCoalescing coalescingOf(const EntityLinkage &L);
+ static EntityVisibility visibilityOf(const EntityLinkage &L);
+
+ /// True when \p Linkage defines the entity rather than declaring it.
+ static bool defines(const EntityLinkage &Linkage);
+
+ /// Builds a copy of \p Linkage with its visibility replaced.
+ static EntityLinkage withVisibility(const EntityLinkage &Linkage,
+ EntityVisibility V);
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSIS_CORE_ENTITYLINKER_LINKAGERULES_H
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/Model/EntityLinkage.h b/clang/include/clang/ScalableStaticAnalysis/Core/Model/EntityLinkage.h
index f90571c672452..d4df0510ce63d 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/Model/EntityLinkage.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/Model/EntityLinkage.h
@@ -11,27 +11,93 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/raw_ostream.h"
+#include <cstdint>
namespace clang::ssaf {
-enum class EntityLinkageType {
+/// Symbol scope.
+///
+/// Enumerator order is insignificant: the linker never compares these values,
+/// it switches on them by name.
+enum class EntityLinkageType : uint8_t {
None, ///< local variables
Internal, ///< static functions/variables, anonymous namespace
External ///< globally visible across translation units (including parameters
///< of functions with external linkage)
};
-/// Represents the linkage properties of an entity in the program model.
+/// Symbol strength: which definition prevails when two of them collide.
+///
+/// Enumerator order is insignificant. Precedence is platform-specific — on
+/// Mach-O a weak definition displaces a common, while on ELF and COFF the
+/// common wins — so comparisons must go through LinkageRules::strengthRank()
+/// rather than comparing these values directly.
+enum class EntityBinding : uint8_t {
+ Undefined, ///< a reference with no definition in this unit
+ Weak, ///< __attribute__((weak)); overridden by a strong definition
+ Common, ///< C tentative definition; merged, size = max
+ Strong ///< ordinary definition
+};
+
+/// How far a symbol escapes its link unit.
+///
+/// Enumerator order is insignificant. ELF merges to the most restrictive
+/// visibility, Mach-O to the least, and COFF has no visibility at all; see
+/// LinkageRules::mergeVisibility().
+enum class EntityVisibility : uint8_t {
+ Default, ///< participates in cross-LU resolution; exported/interposable
+ Hidden, ///< visible within the LU, not exported past its boundary
+ Protected ///< exported but bound to the defining LU (not interposable)
+};
+
+/// Whether every definition of the entity is required to be identical, as a
+/// COMDAT group (ELF and COFF) or a .weak_definition (Mach-O) guarantees.
+///
+/// This is independent of EntityBinding, because the object formats encode the
+/// two properties separately: an inline function is emitted weak on ELF and
+/// Mach-O but strong on COFF, and in every case the ODR guarantee is carried by
+/// a mechanism other than the binding. See LinkageRules::effectiveBinding().
+///
+/// Modelled as an enum rather than a bool so that COFF's other
+/// IMAGE_COMDAT_SELECT_* kinds can be added without a schema change.
+enum class EntityCoalescing : uint8_t {
+ None, ///< definitions are unrelated; at most one may exist
+ ODR ///< all definitions are required to be identical and are coalesced
+};
+
+/// Whether this occurrence defines the entity or merely declares/references it.
+///
+/// Enumerator order is insignificant; see
+/// LinkageRules::definitionKindRank().
+enum class EntityDefinitionKind : uint8_t {
+ Declaration, ///< declares or references the entity
+ Definition ///< defines the entity
+};
+
+/// Represents the linker-relevant properties of an entity in the program model.
///
-/// EntityLinkage captures whether an entity has no linkage, internal linkage,
-/// or external linkage, which determines its visibility and accessibility
-/// across translation units.
+/// EntityLinkage captures the scope, strength, coalescing, visibility, and
+/// definition state of an entity, which together determine how it is resolved
+/// and exported across translation units and link units.
+///
+/// The values are recorded as the source declares them; translating them into
+/// what a particular object format would emit is the linker's job, via
+/// LinkageRules.
class EntityLinkage {
+ friend class EntityLinker;
+ friend class LinkageRules;
friend class SerializationFormat;
friend class TestFixture;
+ friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+ const EntityLinkage &Linkage);
public:
- constexpr explicit EntityLinkage(EntityLinkageType L) : Linkage(L) {}
+ constexpr EntityLinkage(EntityLinkageType Linkage, EntityBinding Binding,
+ EntityCoalescing Coalescing,
+ EntityVisibility Visibility,
+ EntityDefinitionKind DefinitionKind)
+ : Linkage(Linkage), Binding(Binding), Coalescing(Coalescing),
+ Visibility(Visibility), DefinitionKind(DefinitionKind) {}
EntityLinkageType getLinkage() const { return Linkage; }
@@ -40,10 +106,21 @@ class EntityLinkage {
private:
EntityLinkageType Linkage;
+ EntityBinding Binding;
+ EntityCoalescing Coalescing;
+ EntityVisibility Visibility;
+ EntityDefinitionKind DefinitionKind;
};
/// Writes a string representation of the linkage type to the stream.
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, EntityLinkageType Linkage);
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, EntityBinding Binding);
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+ EntityCoalescing Coalescing);
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+ EntityVisibility Visibility);
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+ EntityDefinitionKind DefinitionKind);
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
const EntityLinkage &Linkage);
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/Model/PrivateFieldNames.def b/clang/include/clang/ScalableStaticAnalysis/Core/Model/PrivateFieldNames.def
index 33f47f8f9151c..1ffda11ed3634 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/Model/PrivateFieldNames.def
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/Model/PrivateFieldNames.def
@@ -22,6 +22,10 @@ FIELD(BuildNamespace, Name)
FIELD(EntityId, Index)
FIELD(EntityIdTable, Entities)
FIELD(EntityLinkage, Linkage)
+FIELD(EntityLinkage, Binding)
+FIELD(EntityLinkage, Coalescing)
+FIELD(EntityLinkage, Visibility)
+FIELD(EntityLinkage, DefinitionKind)
FIELD(EntityName, Namespace)
FIELD(EntityName, Suffix)
FIELD(EntityName, USR)
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/TUSummary/TUSummaryBuilder.h b/clang/include/clang/ScalableStaticAnalysis/Core/TUSummary/TUSummaryBuilder.h
index 5132761ed6bb5..3f78754978056 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/TUSummary/TUSummaryBuilder.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/TUSummary/TUSummaryBuilder.h
@@ -26,7 +26,7 @@ class TUSummaryBuilder {
TUSummaryBuilder(TUSummary &Summary, const SSAFOptions &Options)
: Summary(Summary), Options(Options) {}
- EntityId addEntity(const EntityName &EN, EntityLinkageType Linkage);
+ EntityId addEntity(const EntityName &EN, EntityLinkage Linkage);
/// Associate the \p Data \c EntitySummary with the \p Entity.
/// This consumes the \p Data only if \p Entity wasn't associated yet with the
diff --git a/clang/lib/ScalableStaticAnalysis/Core/CMakeLists.txt b/clang/lib/ScalableStaticAnalysis/Core/CMakeLists.txt
index 28c4ac9f6911b..3143cc230db8a 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysis/Core/CMakeLists.txt
@@ -6,6 +6,7 @@ set(LLVM_LINK_COMPONENTS
add_clang_library(clangScalableStaticAnalysisCore
ASTEntityMapping.cpp
EntityLinker/EntityLinker.cpp
+ EntityLinker/LinkageRules.cpp
Model/BuildNamespace.cpp
Model/EntityId.cpp
Model/EntityIdTable.cpp
diff --git a/clang/lib/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.cpp b/clang/lib/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.cpp
index 462978932a53d..60ccacb4a9875 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.cpp
@@ -13,7 +13,13 @@
#include "clang/ScalableStaticAnalysis/Core/Model/EntityName.h"
#include "clang/ScalableStaticAnalysis/Core/Support/ErrorBuilder.h"
#include "clang/ScalableStaticAnalysis/Core/Support/FormatProviders.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/WithColor.h"
+#include <algorithm>
#include <cassert>
+#include <string>
+#include <utility>
using namespace clang::ssaf;
@@ -38,14 +44,91 @@ static constexpr const char *DuplicateEntityIdInTUSummary =
static constexpr const char *EntityNotFoundInResolutionTable =
"{0} - {1} not found in EntityResolutionTable";
+static constexpr const char *MultipleEntitiesResolveToSameId =
+ "{0} - Multiple entities in the TU summary resolve to {1}";
+
static constexpr const char *FailedToInsertEntityIntoOutputSummary =
"{0} - Failed to insert data for {1} with {2} against {3} to LUSummary";
static constexpr const char *DuplicateTUNamespace =
"failed to link TU summary: duplicate {0}";
+static constexpr const char *MultipleDefinition =
+ "multiple definition of {0} in {1}";
+
+static constexpr const char *TargetTripleMismatch =
+ "failed to link TU summary: {0} targets '{1}', which resolves symbols as "
+ "{2}, but the link unit targets '{3}', which resolves them as {4}";
+
+static constexpr const char *UnrepresentableLinkage =
+ "{0} has {1}, which a {2} target cannot represent";
+
+static constexpr const char *OrderDependentMerge =
+ "visibility of common symbol {0} differs between translation units; the "
+ "linked result depends on link order";
+
+static constexpr const char *UnresolvedEntity = "undefined symbol: {0}";
+
+static constexpr const char *ODRMismatch =
+ "{0} summary data for {1} differs between {2} and an earlier translation "
+ "unit, but its definitions are required to be identical";
+
} // namespace ErrorMessages
+//===----------------------------------------------------------------------===//
+// Fatal Error Helpers
+//===----------------------------------------------------------------------===//
+
+namespace {
+
+/// Reports a corrupted TU summary or logic bug and terminates execution.
+///
+/// Supplies \c EntityLinkerFatalErrorPrefix as the leading `{0}` of every
+/// message in \c ErrorMessages, so callers pass only the remaining arguments.
+template <typename... Args>
+[[noreturn]] void fatal(const char *Fmt, Args &&...ArgVals) {
+ ErrorBuilder::fatal(Fmt, ErrorMessages::EntityLinkerFatalErrorPrefix,
+ std::forward<Args>(ArgVals)...);
+}
+
+/// Looks \p Key up in \p Map, reporting a fatal error if it is absent.
+///
+/// \returns A reference to the mapped value, which is owned by \p Map.
+template <typename MapT, typename... Args>
+const typename MapT::mapped_type &
+lookupOrFatal(const MapT &Map, const typename MapT::key_type &Key,
+ const char *Fmt, Args &&...ArgVals) {
+ auto Iter = Map.find(Key);
+ if (Iter == Map.end()) {
+ fatal(Fmt, std::forward<Args>(ArgVals)...);
+ }
+ return Iter->second;
+}
+
+/// Inserts \p Key into \p Map, reporting a fatal error if it is already
+/// present. Used for the write-once records built while resolving a TU.
+template <typename MapT, typename... Args>
+void insertOrFatal(MapT &Map, const typename MapT::key_type &Key,
+ typename MapT::mapped_type Value, const char *Fmt,
+ Args &&...ArgVals) {
+ auto [Iter, Inserted] = Map.insert({Key, std::move(Value)});
+ if (!Inserted) {
+ fatal(Fmt, std::forward<Args>(ArgVals)...);
+ }
+}
+
+/// Returns true if \p Linkage makes the entity visible across translation
+/// units, and so able to legitimately collide with an occurrence in another TU.
+bool isExternal(const EntityLinkage &Linkage) {
+ return Linkage.getLinkage() == EntityLinkageType::External;
+}
+
+} // namespace
+
+//===----------------------------------------------------------------------===//
+// Namespace Resolution
+//===----------------------------------------------------------------------===//
+
static NestedBuildNamespace
resolveNamespace(const NestedBuildNamespace &LUNamespace,
const NestedBuildNamespace &TUNamespace,
@@ -65,133 +148,487 @@ resolveNamespace(const NestedBuildNamespace &LUNamespace,
llvm_unreachable("Unhandled EntityLinkageType variant");
}
-EntityId EntityLinker::resolveEntity(const EntityName &OldName,
- const EntityLinkage &Linkage,
- const NestedBuildNamespace &TUNamespace) {
- NestedBuildNamespace NewNamespace = resolveNamespace(
- Output.LUNamespace, TUNamespace, OldName.Namespace, Linkage.getLinkage());
+//===----------------------------------------------------------------------===//
+// Linkage Reconciliation
+//===----------------------------------------------------------------------===//
+
+EntityBinding
+EntityLinker::effectiveBinding(const EntityLinkage &Linkage) const {
+ return Rules->effectiveBinding(Linkage);
+}
+
+bool EntityLinker::isConflictingDefinition(
+ const EntityLinkage &Current, const EntityLinkage &Incoming) const {
+ // Only two definitions can conflict; what makes them incompatible is the
+ // target's business.
+ if (Current.DefinitionKind != EntityDefinitionKind::Definition ||
+ Incoming.DefinitionKind != EntityDefinitionKind::Definition) {
+ return false;
+ }
+ return Rules->isConflictingDefinition(Current, Incoming);
+}
+
+bool EntityLinker::incomingDataWins(const EntityLinkage &Current,
+ const EntityLinkage &Incoming) const {
+ const bool CurrentDefines =
+ Current.DefinitionKind == EntityDefinitionKind::Definition;
+ const bool IncomingDefines =
+ Incoming.DefinitionKind == EntityDefinitionKind::Definition;
+
+ // A definition beats a declaration.
+ if (CurrentDefines != IncomingDefines) {
+ return IncomingDefines;
+ }
+
+ // Among definitions the stronger binding wins. Two declarations carry no
+ // data worth preferring, and equally strong definitions keep the data
+ // already linked.
+ return IncomingDefines && Rules->strengthRank(effectiveBinding(Incoming)) >
+ Rules->strengthRank(effectiveBinding(Current));
+}
+
+EntityLinkage EntityLinker::mergeLinkage(const EntityLinkage &Current,
+ const EntityLinkage &Incoming) const {
+ // Entities collide only when their names, namespaces included, are identical,
+ // and namespace resolution qualifies `None` and `Internal` entities with
+ // their TU namespace. Occurrences of different linkage therefore never
+ // reconcile, and the linkage type carries through unchanged rather than
+ // joining.
+ assert(Current.Linkage == Incoming.Linkage &&
+ "EntityLinker::mergeLinkage: only same-linkage occurrences of an "
+ "entity can be reconciled");
+
+ const bool CurrentDefines =
+ Current.DefinitionKind == EntityDefinitionKind::Definition;
+ const bool IncomingDefines =
+ Incoming.DefinitionKind == EntityDefinitionKind::Definition;
+
+ // The binding is the stronger of the two, and the coalescing guarantee holds
+ // only if every copy carries it. Both describe definitions, so a declaration
+ // contributes neither when the other occurrence defines the entity: on
+ // Mach-O, where a weak binding outranks a common one, joining a weak
+ // declaration with a common definition would otherwise describe the common
+ // definition as weak.
+ EntityBinding MergedBinding = Current.Binding;
+ EntityCoalescing MergedCoalescing = Current.Coalescing;
+ if (CurrentDefines == IncomingDefines) {
+ const bool IncomingStronger =
+ Rules->strengthRank(effectiveBinding(Incoming)) >
+ Rules->strengthRank(effectiveBinding(Current));
+ const EntityLinkage &Winner = IncomingStronger ? Incoming : Current;
+
+ MergedCoalescing =
+ (CurrentDefines && Current.Coalescing == EntityCoalescing::ODR &&
+ Incoming.Coalescing == EntityCoalescing::ODR)
+ ? EntityCoalescing::ODR
+ : EntityCoalescing::None;
+
+ // A strong binding that only behaved weakly because of its ODR guarantee
+ // must degrade along with it. Recording Strong once the guarantee is gone
+ // would claim a strong definition the program does not have, and a later
+ // genuinely strong definition would then be reported as a duplicate.
+ // Doing this also makes the merge commutative when an ODR definition meets
+ // an explicitly weak one, since the two tie on effective binding.
+ MergedBinding = MergedCoalescing == Winner.Coalescing
+ ? Winner.Binding
+ : effectiveBinding(Winner);
+ } else if (IncomingDefines) {
+ MergedBinding = Incoming.Binding;
+ MergedCoalescing = Incoming.Coalescing;
+ }
+
+ return EntityLinkage(Current.Linkage, MergedBinding, MergedCoalescing,
+ Rules->mergeVisibility(Current, Incoming),
+ (CurrentDefines || IncomingDefines)
+ ? EntityDefinitionKind::Definition
+ : EntityDefinitionKind::Declaration);
+}
+
+void EntityLinker::reportIfLinkageIsNotExternal(EntityId NewId,
+ const EntityLinkage &Linkage) {
+ if (!isExternal(Linkage)) {
+ fatal(ErrorMessages::EntityAlreadyExistsInLinkageTable, NewId, Linkage);
+ }
+}
+
+bool EntityLinker::reportIfDefinitionsConflict(
+ const EntityLinkage &Current, const EntityLinkage &Incoming,
+ const EntityName &Name, const NestedBuildNamespace &TUNamespace) const {
+ if (!isConflictingDefinition(Current, Incoming)) {
+ return false;
+ }
+
+ if (!WarnOnMultipleDefinitions) {
+ ErrorBuilder::fatal(ErrorMessages::MultipleDefinition, Name, TUNamespace);
+ }
+
+ llvm::WithColor::warning()
+ << llvm::formatv(ErrorMessages::MultipleDefinition, Name, TUNamespace)
+ << "\n";
+
+ return true;
+}
+
+void EntityLinker::reportIfNotRepresentable(const EntityLinkage &Linkage,
+ const EntityName &Name) const {
+ if (!Rules->isRepresentable(Linkage)) {
+ ErrorBuilder::fatal(ErrorMessages::UnrepresentableLinkage, Name, Linkage,
+ Rules->getName());
+ }
+}
+
+void EntityLinker::reportIfOrderDependent(const EntityLinkage &Current,
+ const EntityLinkage &Incoming,
+ const EntityName &Name) const {
+ if (Rules->isOrderDependentMerge(Current, Incoming)) {
+ llvm::WithColor::warning()
+ << llvm::formatv(ErrorMessages::OrderDependentMerge, Name) << "\n";
+ }
+}
+
+//===----------------------------------------------------------------------===//
+// Resolve
+//===----------------------------------------------------------------------===//
+
+std::pair<EntityId, bool>
+EntityLinker::resolveEntity(const EntityName &OldName,
+ const EntityLinkage &RawLinkage,
+ const NestedBuildNamespace &TUNamespace) {
+ NestedBuildNamespace NewNamespace =
+ resolveNamespace(Output.LUNamespace, TUNamespace, OldName.Namespace,
+ RawLinkage.getLinkage());
EntityName NewName(OldName.USR, OldName.Suffix, NewNamespace);
+ // The summary records what the source declared; coerce it to what this
+ // target can express before any rule looks at it, and reject values the
+ // target's toolchain could never have produced.
+ reportIfNotRepresentable(RawLinkage, NewName);
+ const EntityLinkage Linkage = Rules->normalize(RawLinkage);
+
// NewId construction will always return a fresh id for `None` and `Internal`
// linkage entities since their namespaces will be different even if their
// names clash. For `External` linkage entities with identical names this
// function will return the id assigned at the first insertion.
EntityId NewId = Output.IdTable.getId(NewName);
- auto [_, Inserted] = Output.LinkageTable.try_emplace(NewId, Linkage);
- if (!Inserted) {
- // Insertion failure for `None` and `Internal` linkage is a fatal error
- // because these entities have unique namespaces and should never collide.
- // `External` linkage entities may collide.
- if (Linkage.getLinkage() == EntityLinkageType::None ||
- Linkage.getLinkage() == EntityLinkageType::Internal) {
- ErrorBuilder::fatal(ErrorMessages::EntityAlreadyExistsInLinkageTable,
- ErrorMessages::EntityLinkerFatalErrorPrefix, NewId,
- Linkage);
- }
+ auto [It, Inserted] = Output.LinkageTable.try_emplace(NewId, Linkage);
+ if (Inserted) {
+ // Keep the name so finalization can name the entity in diagnostics.
+ EntityNames.insert({NewId, NewName});
+ // First occurrence of this entity: its summary data is the copy to keep.
+ return {NewId, true};
+ }
+
+ reportIfLinkageIsNotExternal(NewId, Linkage);
+
+ // `External` entities collide legitimately. A conflicting duplicate
+ // definition is ignored outright, leaving the definition already linked in
+ // place; anything else is reconciled, and the data selection tells merge()
+ // whose encoding to keep.
+ if (reportIfDefinitionsConflict(It->second, Linkage, NewName, TUNamespace)) {
+ return {NewId, false};
}
- return NewId;
+ reportIfOrderDependent(It->second, Linkage, NewName);
+
+ const bool DataFromIncoming = incomingDataWins(It->second, Linkage);
+ It->second = mergeLinkage(It->second, Linkage);
+
+ return {NewId, DataFromIncoming};
}
-std::map<EntityId, EntityId>
+std::pair<EntityResolutionMap, EntityLinker::DataSelectionMap>
EntityLinker::resolve(const TUSummaryEncoding &Summary) {
- std::map<EntityId, EntityId> EntityResolutionTable;
+ EntityResolutionMap Resolution;
+ DataSelectionMap DataSelection;
Summary.IdTable.forEach([&](const EntityName &OldName, const EntityId OldId) {
- auto Iter = Summary.LinkageTable.find(OldId);
- if (Iter == Summary.LinkageTable.end()) {
- ErrorBuilder::fatal(ErrorMessages::MissingLinkageInformation,
- ErrorMessages::EntityLinkerFatalErrorPrefix, OldId);
- }
+ const EntityLinkage &Linkage =
+ lookupOrFatal(Summary.LinkageTable, OldId,
+ ErrorMessages::MissingLinkageInformation, OldId);
+
+ auto [NewId, DataFromIncoming] = resolveEntity(
+ OldName, Linkage, NestedBuildNamespace(Summary.TUNamespace));
+
+ insertOrFatal(Resolution, OldId, NewId,
+ ErrorMessages::DuplicateEntityIdInTUSummary, OldId);
+ insertOrFatal(DataSelection, NewId, DataFromIncoming,
+ ErrorMessages::MultipleEntitiesResolveToSameId, NewId);
+ });
+
+ return {std::move(Resolution), std::move(DataSelection)};
+}
+
+//===----------------------------------------------------------------------===//
+// Merge
+//===----------------------------------------------------------------------===//
+
+EntitySummaryEncoding *EntityLinker::mergeEntityData(
+ EntityDataMap &OutputData, const SummaryName &SN, EntityId NewId,
+ std::unique_ptr<EntitySummaryEncoding> &Encoding,
+ const EntityLinkage &Linkage, bool DataFromIncoming) {
+ auto [It, Inserted] = OutputData.try_emplace(NewId, std::move(Encoding));
- const EntityLinkage &Linkage = Iter->second;
+ // No earlier TU contributed data for this entity, so this encoding is it.
+ if (Inserted) {
+ return It->second.get();
+ }
- EntityId NewId = resolveEntity(OldName, Linkage,
- NestedBuildNamespace(Summary.TUNamespace));
+ // Insertion should never fail for `None` and `Internal` linkage entities
+ // because these entities will have different namespaces across TUs even if
+ // their names match.
+ if (!isExternal(Linkage)) {
+ fatal(ErrorMessages::FailedToInsertEntityIntoOutputSummary, NewId, Linkage,
+ SN);
+ }
- auto [_, Inserted] = EntityResolutionTable.insert({OldId, NewId});
- if (!Inserted) {
- ErrorBuilder::fatal(ErrorMessages::DuplicateEntityIdInTUSummary,
- ErrorMessages::EntityLinkerFatalErrorPrefix, OldId);
+ // Two definitions required to be identical should carry identical summaries.
+ // Keep the loser so reportODRMismatches() can compare them once both are in
+ // the LU EntityId space; a displaced encoding from this TU still needs
+ // patching, so it is returned as a patch target either way.
+ const bool CompareDisplaced = ODRMismatch != ODRMismatchPolicy::Ignore &&
+ Linkage.Coalescing == EntityCoalescing::ODR;
+
+ // `External` collision: the reconciliation in resolve() decided which
+ // occurrence's data is the copy to keep. If this TU won, its encoding
+ // replaces the incumbent; otherwise the incumbent is kept and this encoding
+ // is dropped.
+ if (!DataFromIncoming) {
+ if (!CompareDisplaced) {
+ return nullptr;
}
- });
+ Displaced.push_back({SN, NewId, std::move(Encoding)});
+ return Displaced.back().Data.get();
+ }
- return EntityResolutionTable;
+ if (CompareDisplaced) {
+ // The incumbent is already patched, so it needs no further work.
+ Displaced.push_back({SN, NewId, std::move(It->second)});
+ }
+ It->second = std::move(Encoding);
+ return It->second.get();
+}
+
+std::vector<EntitySummaryEncoding *>
+EntityLinker::mergeSummaryData(const SummaryName &SN, EntityDataMap &DataMap,
+ const TUSummaryEncoding &Summary,
+ const EntityResolutionMap &Resolution,
+ const DataSelectionMap &DataSelection) {
+ std::vector<EntitySummaryEncoding *> PatchTargets;
+ auto &OutputSummaryData = Output.Data[SN];
+
+ for (auto &[OldId, ES] : DataMap) {
+ const EntityId NewId =
+ lookupOrFatal(Resolution, OldId,
+ ErrorMessages::EntityNotFoundInResolutionTable, OldId);
+
+ // Safe to retrieve linkage and data selection using .at since the resolve
+ // step records both for every OldId in the TU summary.
+ assert(Summary.LinkageTable.count(OldId) &&
+ "EntityLinker::mergeSummaryData: resolve() records a linkage for "
+ "every entity");
+ assert(DataSelection.count(NewId) &&
+ "EntityLinker::mergeSummaryData: resolve() records a data selection "
+ "for every resolved entity");
+
+ if (auto *PatchTarget = mergeEntityData(OutputSummaryData, SN, NewId, ES,
+ Summary.LinkageTable.at(OldId),
+ DataSelection.at(NewId))) {
+ PatchTargets.push_back(PatchTarget);
+ }
+ }
+
+ return PatchTargets;
}
std::vector<EntitySummaryEncoding *>
EntityLinker::merge(TUSummaryEncoding &Summary,
- const std::map<EntityId, EntityId> &EntityResolutionTable) {
+ const EntityResolutionMap &Resolution,
+ const DataSelectionMap &DataSelection) {
std::vector<EntitySummaryEncoding *> PatchTargets;
for (auto &[SN, DataMap] : Summary.Data) {
- auto &OutputSummaryData = Output.Data[SN];
-
- for (auto &[OldId, ES] : DataMap) {
- auto Iter = EntityResolutionTable.find(OldId);
- if (Iter == EntityResolutionTable.end()) {
- ErrorBuilder::fatal(ErrorMessages::EntityNotFoundInResolutionTable,
- ErrorMessages::EntityLinkerFatalErrorPrefix, OldId);
- }
-
- const auto NewId = Iter->second;
-
- auto [It, Inserted] = OutputSummaryData.try_emplace(NewId, std::move(ES));
-
- if (Inserted) {
- PatchTargets.push_back(It->second.get());
- } else {
- // Safe to retrieve linkage using .at since the resolve step ensures
- // linkage information is always present for every OldId.
- auto Linkage = Summary.LinkageTable.at(OldId);
-
- // Insertion should never fail for `None` and `Internal` linkage
- // entities because these entities will have different namespaces across
- // TUs even if their names match.
- if (Linkage.getLinkage() == EntityLinkageType::None ||
- Linkage.getLinkage() == EntityLinkageType::Internal) {
- ErrorBuilder::fatal(
- ErrorMessages::FailedToInsertEntityIntoOutputSummary,
- ErrorMessages::EntityLinkerFatalErrorPrefix, NewId, Linkage, SN);
- }
-
- // TODO: Insertion is expected to fail for duplicate occurrences of
- // `External` linkage entities. Report these cases in a "debug" mode to
- // help debug potential ODR violations.
- }
- }
+ std::vector<EntitySummaryEncoding *> SummaryTargets =
+ mergeSummaryData(SN, DataMap, Summary, Resolution, DataSelection);
+ PatchTargets.insert(PatchTargets.end(), SummaryTargets.begin(),
+ SummaryTargets.end());
}
return PatchTargets;
}
+//===----------------------------------------------------------------------===//
+// Patch
+//===----------------------------------------------------------------------===//
+
llvm::Error
EntityLinker::patch(const std::vector<EntitySummaryEncoding *> &PatchTargets,
- const std::map<EntityId, EntityId> &EntityResolutionTable) {
+ const EntityResolutionMap &Resolution) {
for (auto *PatchTarget : PatchTargets) {
assert(PatchTarget && "EntityLinker::patch: Patch target cannot be null");
- if (auto Err = PatchTarget->patch(EntityResolutionTable)) {
+ if (auto Err = PatchTarget->patch(Resolution)) {
return Err;
}
}
return llvm::Error::success();
}
-llvm::Error EntityLinker::link(std::unique_ptr<TUSummaryEncoding> Summary) {
- auto [_, Inserted] = ProcessedTUNamespaces.insert(Summary->TUNamespace);
+//===----------------------------------------------------------------------===//
+// Link
+//===----------------------------------------------------------------------===//
+
+llvm::Error
+EntityLinker::checkTUNotAlreadyLinked(const BuildNamespace &TUNamespace) {
+ auto [_, Inserted] = ProcessedTUNamespaces.insert(TUNamespace);
if (!Inserted) {
return ErrorBuilder::create(std::errc::invalid_argument,
ErrorMessages::DuplicateTUNamespace,
- Summary->TUNamespace)
+ TUNamespace)
.build();
}
+ return llvm::Error::success();
+}
+
+llvm::Error
+EntityLinker::checkTUTargetMatches(const llvm::Triple &TUTriple,
+ const BuildNamespace &TUNamespace) const {
+ // Compare only what selects resolution rules and affects the outcome. The OS
+ // version does not, so macosx14.0 and macosx15.0 are interchangeable here,
+ // matching the key MultiArch* uses to order its members.
+ const llvm::Triple &LUTriple = Output.TargetTriple;
+ const bool Matches = TUTriple.getArch() == LUTriple.getArch() &&
+ TUTriple.getSubArch() == LUTriple.getSubArch() &&
+ TUTriple.getVendor() == LUTriple.getVendor() &&
+ TUTriple.getOS() == LUTriple.getOS() &&
+ TUTriple.getEnvironment() == LUTriple.getEnvironment() &&
+ TUTriple.getObjectFormat() == LUTriple.getObjectFormat();
+ if (Matches) {
+ return llvm::Error::success();
+ }
+
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::TargetTripleMismatch, TUNamespace,
+ TUTriple.str(),
+ LinkageRules::forTarget(TUTriple).getName(),
+ LUTriple.str(), Rules->getName())
+ .build();
+}
+
+llvm::Error EntityLinker::link(std::unique_ptr<TUSummaryEncoding> Summary) {
+ if (auto Err = checkTUNotAlreadyLinked(Summary->TUNamespace)) {
+ return Err;
+ }
+
+ if (auto Err = checkTUTargetMatches(Summary->getTargetTriple(),
+ Summary->TUNamespace)) {
+ return Err;
+ }
TUSummaryEncoding &SummaryRef = *Summary;
+ Displaced.clear();
+
+ auto [Resolution, DataSelection] = resolve(SummaryRef);
+ auto PatchTargets = merge(SummaryRef, Resolution, DataSelection);
+ if (auto Err = patch(PatchTargets, Resolution)) {
+ return Err;
+ }
+
+ reportODRMismatches(NestedBuildNamespace(SummaryRef.TUNamespace));
+ return llvm::Error::success();
+}
+
+void EntityLinker::reportODRMismatches(
+ const NestedBuildNamespace &TUNamespace) {
+ std::vector<std::string> Messages;
+ for (const DisplacedData &D : Displaced) {
+ const auto &Kept = Output.Data.at(D.Name).at(D.Id);
+ if (Kept->equals(*D.Data)) {
+ continue;
+ }
+ auto NameIt = EntityNames.find(D.Id);
+ assert(NameIt != EntityNames.end() &&
+ "EntityLinker: every resolved entity records its name");
+ Messages.push_back(llvm::formatv(ErrorMessages::ODRMismatch, D.Name,
+ NameIt->second, TUNamespace)
+ .str());
+ }
+ Displaced.clear();
+
+ if (Messages.empty()) {
+ return;
+ }
+
+ if (ODRMismatch == ODRMismatchPolicy::Warn) {
+ for (const std::string &Message : Messages) {
+ llvm::WithColor::warning() << Message << "\n";
+ }
+ return;
+ }
+
+ ErrorBuilder::fatal("{0}", llvm::join(Messages, "\n"));
+}
+
+//===----------------------------------------------------------------------===//
+// Finalization
+//===----------------------------------------------------------------------===//
+
+void EntityLinker::reportUnresolvedEntities() const {
+ if (UnresolvedSymbols == UnresolvedPolicy::Ignore) {
+ return;
+ }
+
+ std::vector<std::string> Messages;
+ for (const auto &[Id, Linkage] : Output.LinkageTable) {
+ // Only an external entity can be satisfied by another translation unit.
+ if (Linkage.Linkage != EntityLinkageType::External ||
+ Linkage.DefinitionKind == EntityDefinitionKind::Definition) {
+ continue;
+ }
+ // An undefined weak reference resolves to nothing by design.
+ if (Rules->effectiveBinding(Linkage) == EntityBinding::Weak) {
+ continue;
+ }
+ auto NameIt = EntityNames.find(Id);
+ assert(NameIt != EntityNames.end() &&
+ "EntityLinker: every resolved entity records its name");
+ Messages.push_back(
+ llvm::formatv(ErrorMessages::UnresolvedEntity, NameIt->second).str());
+ }
+
+ if (Messages.empty()) {
+ return;
+ }
+
+ if (UnresolvedSymbols == UnresolvedPolicy::Warn) {
+ for (const std::string &Message : Messages) {
+ llvm::WithColor::warning() << Message << "\n";
+ }
+ return;
+ }
+
+ ErrorBuilder::fatal("{0}", llvm::join(Messages, "\n"));
+}
+
+void EntityLinker::demoteHiddenEntities() {
+ for (auto &[Id, Linkage] : Output.LinkageTable) {
+ if (Linkage.Linkage != EntityLinkageType::External) {
+ continue;
+ }
+ // "Hidden" is whatever the target treats as most restrictive; on COFF,
+ // where visibility is meaningless, normalize() has already flattened
+ // everything to Default so nothing is demoted.
+ if (Linkage.Visibility != EntityVisibility::Hidden) {
+ continue;
+ }
+ Linkage = EntityLinkage(EntityLinkageType::Internal, Linkage.Binding,
+ Linkage.Coalescing, Linkage.Visibility,
+ Linkage.DefinitionKind);
+ }
+}
- auto EntityResolutionTable = resolve(SummaryRef);
- auto PatchTargets = merge(SummaryRef, EntityResolutionTable);
- return patch(PatchTargets, EntityResolutionTable);
+void EntityLinker::finalize() {
+ reportUnresolvedEntities();
+ demoteHiddenEntities();
}
diff --git a/clang/lib/ScalableStaticAnalysis/Core/EntityLinker/LinkageRules.cpp b/clang/lib/ScalableStaticAnalysis/Core/EntityLinker/LinkageRules.cpp
new file mode 100644
index 0000000000000..c71904d7266c3
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysis/Core/EntityLinker/LinkageRules.cpp
@@ -0,0 +1,450 @@
+//===- LinkageRules.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Each rule below cites the probe that establishes it. The probes, including
+// the full programs and linker output, are in
+// docs/ssaf-linker-{elf,macho,coff}-behavior.md.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LinkageRules.h"
+#include "clang/ScalableStaticAnalysis/Core/Support/ErrorBuilder.h"
+#include "llvm/Support/ErrorHandling.h"
+
+namespace clang::ssaf {
+
+namespace {
+
+constexpr const char *UnsupportedObjectFormat =
+ "unsupported object format for target triple '{0}': the entity linker "
+ "emulates ELF, Mach-O and COFF";
+
+} // namespace
+
+EntityLinkageType LinkageRules::linkageOf(const EntityLinkage &L) {
+ return L.Linkage;
+}
+
+EntityBinding LinkageRules::bindingOf(const EntityLinkage &L) {
+ return L.Binding;
+}
+
+EntityCoalescing LinkageRules::coalescingOf(const EntityLinkage &L) {
+ return L.Coalescing;
+}
+
+EntityVisibility LinkageRules::visibilityOf(const EntityLinkage &L) {
+ return L.Visibility;
+}
+
+bool LinkageRules::defines(const EntityLinkage &Linkage) {
+ return Linkage.DefinitionKind == EntityDefinitionKind::Definition;
+}
+
+EntityLinkage LinkageRules::withVisibility(const EntityLinkage &Linkage,
+ EntityVisibility V) {
+ return EntityLinkage(Linkage.Linkage, Linkage.Binding, Linkage.Coalescing, V,
+ Linkage.DefinitionKind);
+}
+
+EntityVisibility
+LinkageRules::mergeVisibility(const EntityLinkage &Current,
+ const EntityLinkage &Incoming) const {
+ return visibilityRank(visibilityOf(Incoming)) >
+ visibilityRank(visibilityOf(Current))
+ ? visibilityOf(Incoming)
+ : visibilityOf(Current);
+}
+
+bool LinkageRules::isOrderDependentMerge(const EntityLinkage &,
+ const EntityLinkage &) const {
+ return false;
+}
+
+EntityLinkage LinkageRules::normalize(const EntityLinkage &Linkage) const {
+ return Linkage;
+}
+
+bool LinkageRules::isRepresentable(const EntityLinkage &) const { return true; }
+
+//===----------------------------------------------------------------------===//
+// ELF
+//===----------------------------------------------------------------------===//
+
+namespace {
+
+class ELFLinkageRules : public LinkageRules {
+public:
+ llvm::StringRef getName() const override { return "ELF"; }
+
+ /// ELF has no general coalescing mechanism, so clang lowers an ODR
+ /// definition to a weak symbol plus a COMDAT group:
+ ///
+ /// inline int inl(void) { return 42; }
+ /// $ llvm-nm inl_a.o -> W _Z3inlv
+ /// $ llvm-readelf --section-groups inl_a.o
+ /// COMDAT group section [4] `.group' [_Z3inlv]
+ ///
+ /// The weak binding is what lets two copies coexist, and what makes an
+ /// ordinary definition of the same symbol win rather than conflict.
+ /// (ELF doc §3, §8.)
+ EntityBinding effectiveBinding(const EntityLinkage &Linkage) const override {
+ if (bindingOf(Linkage) == EntityBinding::Strong &&
+ coalescingOf(Linkage) == EntityCoalescing::ODR) {
+ return EntityBinding::Weak;
+ }
+ return bindingOf(Linkage);
+ }
+
+ /// Undefined < Weak < Common < Strong.
+ ///
+ /// int g; // common, 4 bytes
+ /// __attribute__((weak)) int g = 9; // weak definition
+ /// $ ld.lld -o out main.o weakdef_g.o common_big.o
+ /// g -> the common (size 32 survives) => Common > Weak
+ ///
+ /// int g = 7; // strong definition
+ /// $ ld.lld -o out main.o common_big.o def_g.o
+ /// g -> def_g.o (size 4) => Strong > Common
+ ///
+ /// (ELF doc §2, probes C2b and C3.)
+ unsigned strengthRank(EntityBinding B) const override {
+ switch (B) {
+ case EntityBinding::Undefined:
+ return 0;
+ case EntityBinding::Weak:
+ return 1;
+ case EntityBinding::Common:
+ return 2;
+ case EntityBinding::Strong:
+ return 3;
+ }
+ llvm_unreachable("Unhandled EntityBinding variant");
+ }
+
+ /// Most restrictive wins: Default < Protected < Hidden.
+ ///
+ /// // tu1.c
+ /// int v(void) { return 1; } // default
+ /// // tu2.c
+ /// __attribute__((visibility("hidden"))) int v(void); // hidden decl
+ /// $ ld.lld -shared -o out.so tu1.o tu2.o
+ /// v: FUNC LOCAL HIDDEN, absent from .dynsym
+ ///
+ /// Note a hidden *declaration* demotes a default-visibility definition, and
+ /// that hidden beats protected in either order. (ELF doc §4 V1, §6.2.)
+ unsigned visibilityRank(EntityVisibility V) const override {
+ switch (V) {
+ case EntityVisibility::Default:
+ return 0;
+ case EntityVisibility::Protected:
+ return 1;
+ case EntityVisibility::Hidden:
+ return 2;
+ }
+ llvm_unreachable("Unhandled EntityVisibility variant");
+ }
+
+ /// Two non-weak definitions collide; anything involving a weak one does not.
+ ///
+ /// int f(void) { return 1; } // strong_a.c
+ /// int f(void) { return 2; } // strong_b.c
+ /// $ ld.lld -o out main.o strong_a.o strong_b.o
+ /// ld.lld: error: duplicate symbol: f
+ ///
+ /// Replacing either with __attribute__((weak)) links cleanly, as does an
+ /// inline definition, which lowers to weak. Commons never conflict.
+ /// (ELF doc §1, probes P1-P4; §2 C1.)
+ bool isConflictingDefinition(const EntityLinkage &Current,
+ const EntityLinkage &Incoming) const override {
+ return effectiveBinding(Current) != EntityBinding::Weak &&
+ effectiveBinding(Incoming) != EntityBinding::Weak &&
+ effectiveBinding(Current) != EntityBinding::Common &&
+ effectiveBinding(Incoming) != EntityBinding::Common;
+ }
+};
+
+//===----------------------------------------------------------------------===//
+// Mach-O
+//===----------------------------------------------------------------------===//
+
+class MachOLinkageRules : public LinkageRules {
+public:
+ llvm::StringRef getName() const override { return "Mach-O"; }
+
+ /// Like ELF, Mach-O expresses an ODR definition as a weak one — the symbol
+ /// table entry is byte-identical to __attribute__((weak)):
+ ///
+ /// inline int q(void) { return 42; }
+ /// $ llvm-nm -m inl.o -> (__TEXT,__text) weak external __Z1qv
+ /// $ llvm-nm -m weak.o -> (__TEXT,__text) weak external __Z1qv
+ ///
+ /// and it resolves the same way: first-wins against another weak definition,
+ /// loses to a strong one. (Mach-O doc §4, §7.1.)
+ EntityBinding effectiveBinding(const EntityLinkage &Linkage) const override {
+ if (bindingOf(Linkage) == EntityBinding::Strong &&
+ coalescingOf(Linkage) == EntityCoalescing::ODR) {
+ return EntityBinding::Weak;
+ }
+ return bindingOf(Linkage);
+ }
+
+ /// Undefined < Common < Weak < Strong.
+ ///
+ /// Mach-O inverts Weak and Common relative to ELF and COFF: a weak
+ /// definition displaces a common one, and ld-prime says so out loud.
+ ///
+ /// int g; // common, 32 bytes
+ /// __attribute__((weak)) int g = 9; // weak definition, 4 bytes
+ /// $ ld -o out main.o weakdef_g.o common_big.o
+ /// ld: warning: tentative definition of '_g' with size 32 ... is being
+ /// replaced by real definition of smaller size 4
+ /// _g -> (__DATA,__data) weak external
+ ///
+ /// (Mach-O doc §3, probe MC3.)
+ unsigned strengthRank(EntityBinding B) const override {
+ switch (B) {
+ case EntityBinding::Undefined:
+ return 0;
+ case EntityBinding::Common:
+ return 1;
+ case EntityBinding::Weak:
+ return 2;
+ case EntityBinding::Strong:
+ return 3;
+ }
+ llvm_unreachable("Unhandled EntityBinding variant");
+ }
+
+ /// LEAST restrictive wins — the opposite of ELF.
+ ///
+ /// // tu1.c
+ /// __attribute__((weak, visibility("hidden"))) int w(void) { return 1; }
+ /// // tu2.c
+ /// __attribute__((weak)) int w(void) { return 2; }
+ /// $ ld -o out main.o tu1.o tu2.o (either order)
+ /// _w -> weak external i.e. exported, not private
+ ///
+ /// Only when every copy is hidden does the merged symbol stay private, which
+ /// is `privateExtern &= isPrivateExtern` in lld/MachO/SymbolTable.cpp:118.
+ /// Mach-O has no protected visibility at all: clang warns and downgrades it
+ /// to default, so it is ranked alongside default here.
+ /// (Mach-O doc §2, probes W1-W3.)
+ unsigned visibilityRank(EntityVisibility V) const override {
+ switch (V) {
+ case EntityVisibility::Hidden:
+ return 0;
+ case EntityVisibility::Default:
+ case EntityVisibility::Protected:
+ return 1;
+ }
+ llvm_unreachable("Unhandled EntityVisibility variant");
+ }
+
+ /// Same predicate as ELF: two non-weak definitions collide.
+ ///
+ /// int f(void) { return 1; } // strong_a.c
+ /// int f(void) { return 2; } // strong_b.c
+ /// $ ld -o out main.o strong_a.o strong_b.o
+ /// duplicate symbol '_f' in: strong_b.o, strong_a.o
+ ///
+ /// (Mach-O doc §1, probes M1-M4.)
+ bool isConflictingDefinition(const EntityLinkage &Current,
+ const EntityLinkage &Incoming) const override {
+ return effectiveBinding(Current) != EntityBinding::Weak &&
+ effectiveBinding(Incoming) != EntityBinding::Weak &&
+ effectiveBinding(Current) != EntityBinding::Common &&
+ effectiveBinding(Incoming) != EntityBinding::Common;
+ }
+
+ /// Common symbols do not merge their visibility: ld-prime keeps whichever
+ /// common it saw first, so the result depends on link order.
+ ///
+ /// // tu1.c
+ /// __attribute__((visibility("hidden"))) int g;
+ /// // tu2.c
+ /// int g;
+ /// $ ld -o out main.o tu1.o tu2.o -> _g non-external (private)
+ /// $ ld -o out main.o tu2.o tu1.o -> _g external
+ ///
+ /// addCommon() carries the isPrivateExtern of the common it keeps, with no
+ /// merge step; only addDefined()'s weak-def path performs the `&=`. We
+ /// reproduce that faithfully and warn, rather than approximating it
+ /// commutatively. (Mach-O doc §7.2.)
+ EntityVisibility
+ mergeVisibility(const EntityLinkage &Current,
+ const EntityLinkage &Incoming) const override {
+ if (isCommonPair(Current, Incoming)) {
+ return visibilityOf(Current);
+ }
+ return LinkageRules::mergeVisibility(Current, Incoming);
+ }
+
+ bool isOrderDependentMerge(const EntityLinkage &Current,
+ const EntityLinkage &Incoming) const override {
+ return isCommonPair(Current, Incoming) &&
+ visibilityOf(Current) != visibilityOf(Incoming);
+ }
+
+ /// Protected is not representable, but clang never emits it for a Mach-O
+ /// target: it warns and downgrades to default at compile time.
+ ///
+ /// $ clang --target=arm64-apple-macosx -c v.c
+ /// warning: target does not support 'protected' visibility;
+ /// using 'default' [-Wunsupported-visibility]
+ ///
+ /// A summary that carries it therefore did not come from the compiler, which
+ /// is worth failing over rather than silently coercing. (Mach-O doc §2.)
+ bool isRepresentable(const EntityLinkage &Linkage) const override {
+ return visibilityOf(Linkage) != EntityVisibility::Protected;
+ }
+
+private:
+ static bool isCommonPair(const EntityLinkage &Current,
+ const EntityLinkage &Incoming) {
+ return defines(Current) && defines(Incoming) &&
+ bindingOf(Current) == EntityBinding::Common &&
+ bindingOf(Incoming) == EntityBinding::Common;
+ }
+};
+
+//===----------------------------------------------------------------------===//
+// COFF
+//===----------------------------------------------------------------------===//
+
+class COFFLinkageRules : public LinkageRules {
+public:
+ llvm::StringRef getName() const override { return "COFF"; }
+
+ /// COFF is the one format that expresses ODR directly: the binding stays
+ /// strong and a COMDAT licenses the duplicates.
+ ///
+ /// inline int inl(void) { return 42; }
+ /// $ llvm-nm inl_a.obj -> T ?inl@@YAHXZ (strong, not weak)
+ /// $ llvm-readobj --sections inl_a.obj
+ /// IMAGE_SCN_LNK_COMDAT (0x1000)
+ /// Selection: Any (0x2)
+ ///
+ /// So no lowering is needed. (COFF doc §2.)
+ EntityBinding effectiveBinding(const EntityLinkage &Linkage) const override {
+ return bindingOf(Linkage);
+ }
+
+ /// Undefined < Weak < Common < Strong, as on ELF.
+ ///
+ /// $ lld-link ... /map:k2.map read.obj common_a.obj def_g.obj
+ /// g -> def_g.obj => Strong > Common
+ /// $ lld-link ... /map:k3.map read.obj weakdef_g.obj common_big.obj
+ /// g -> <common> => Common > Weak
+ ///
+ /// (COFF doc §6.1.)
+ unsigned strengthRank(EntityBinding B) const override {
+ switch (B) {
+ case EntityBinding::Undefined:
+ return 0;
+ case EntityBinding::Weak:
+ return 1;
+ case EntityBinding::Common:
+ return 2;
+ case EntityBinding::Strong:
+ return 3;
+ }
+ llvm_unreachable("Unhandled EntityBinding variant");
+ }
+
+ /// COFF has no symbol visibility. Export is controlled per-DLL by
+ /// __declspec(dllexport) and .def files, which is not a per-symbol attribute
+ /// merged during resolution, so every value ranks equally here and
+ /// normalize() coerces them all to Default. (COFF doc §5, §7.)
+ unsigned visibilityRank(EntityVisibility) const override { return 0; }
+
+ /// Always Default, matching normalize(). Ranking every value equally would
+ /// otherwise make the merge keep whichever occurrence was linked first,
+ /// which is an order dependence on a field the platform does not have.
+ EntityVisibility mergeVisibility(const EntityLinkage &,
+ const EntityLinkage &) const override {
+ return EntityVisibility::Default;
+ }
+
+ /// COMDAT, not weakness, is what licenses duplicate definitions.
+ ///
+ /// inline int inl(void) { return 42; } // COMDAT, in two TUs
+ /// $ lld-link ... main.obj inl_a.obj inl_b.obj -> links
+ ///
+ /// int inl(void) { return 99; } // regular definition
+ /// $ lld-link ... main.obj inl_comdat.obj inl_regular.obj
+ /// lld-link: error: duplicate symbol: int __cdecl inl(void)
+ ///
+ /// __attribute__((weak)) int f(void); // in two TUs
+ /// $ lld-link ... main.obj weak_a.obj weak_b.obj
+ /// lld-link: error: duplicate symbol: .weak.f.default
+ ///
+ /// That last case is the one ELF and Mach-O accept: COFF emulates weak
+ /// symbols with an alias, so two of them collide on the alias while one weak
+ /// against one strong does not. (COFF doc §1 C1/C4, §2 CI1/K4, §6.)
+ bool isConflictingDefinition(const EntityLinkage &Current,
+ const EntityLinkage &Incoming) const override {
+ // Commons merge rather than conflict.
+ if (bindingOf(Current) == EntityBinding::Common ||
+ bindingOf(Incoming) == EntityBinding::Common) {
+ return false;
+ }
+ // Two COMDATs coalesce.
+ if (coalescingOf(Current) == EntityCoalescing::ODR &&
+ coalescingOf(Incoming) == EntityCoalescing::ODR) {
+ return false;
+ }
+ // Exactly one weak definition yields to the other; two collide on the
+ // alias COFF uses to emulate them.
+ const bool CurrentWeak = bindingOf(Current) == EntityBinding::Weak;
+ const bool IncomingWeak = bindingOf(Incoming) == EntityBinding::Weak;
+ if (CurrentWeak != IncomingWeak) {
+ return false;
+ }
+ return true;
+ }
+
+ /// Visibility is dropped at emission, so a summary carrying Hidden or
+ /// Protected is coerced rather than rejected: clang accepts both silently on
+ /// Windows, and portable code applies them unconditionally.
+ ///
+ /// __attribute__((visibility("hidden"))) int g;
+ /// $ clang --target=x86_64-pc-windows-msvc -c g.c (no diagnostic)
+ /// $ llvm-nm g.obj -> 00000004 C g (indistinguishable)
+ ///
+ /// (COFF doc §7.)
+ EntityLinkage normalize(const EntityLinkage &Linkage) const override {
+ if (visibilityOf(Linkage) == EntityVisibility::Default) {
+ return Linkage;
+ }
+ return withVisibility(Linkage, EntityVisibility::Default);
+ }
+};
+
+} // namespace
+
+const LinkageRules &LinkageRules::forTarget(const llvm::Triple &TargetTriple) {
+ static const ELFLinkageRules ELF;
+ static const MachOLinkageRules MachO;
+ static const COFFLinkageRules COFF;
+
+ switch (TargetTriple.getObjectFormat()) {
+ case llvm::Triple::ELF:
+ return ELF;
+ case llvm::Triple::MachO:
+ return MachO;
+ case llvm::Triple::COFF:
+ return COFF;
+ default:
+ break;
+ }
+
+ ErrorBuilder::fatal(UnsupportedObjectFormat, TargetTriple.str());
+}
+
+} // namespace clang::ssaf
diff --git a/clang/lib/ScalableStaticAnalysis/Core/Model/EntityLinkage.cpp b/clang/lib/ScalableStaticAnalysis/Core/Model/EntityLinkage.cpp
index 0bae90cd66a22..0833ea006f260 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/Model/EntityLinkage.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Core/Model/EntityLinkage.cpp
@@ -13,7 +13,9 @@
namespace clang::ssaf {
bool EntityLinkage::operator==(const EntityLinkage &Other) const {
- return Linkage == Other.Linkage;
+ return Linkage == Other.Linkage && Binding == Other.Binding &&
+ Coalescing == Other.Coalescing && Visibility == Other.Visibility &&
+ DefinitionKind == Other.DefinitionKind;
}
bool EntityLinkage::operator!=(const EntityLinkage &Other) const {
@@ -25,9 +27,29 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
return OS << entityLinkageTypeToString(Linkage);
}
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, EntityBinding Binding) {
+ return OS << entityBindingToString(Binding);
+}
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+ EntityCoalescing Coalescing) {
+ return OS << entityCoalescingToString(Coalescing);
+}
+
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
- const EntityLinkage &Linkage) {
- return OS << "EntityLinkage(" << Linkage.getLinkage() << ")";
+ EntityVisibility Visibility) {
+ return OS << entityVisibilityToString(Visibility);
+}
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+ EntityDefinitionKind DefinitionKind) {
+ return OS << entityDefinitionKindToString(DefinitionKind);
+}
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const EntityLinkage &EL) {
+ return OS << "EntityLinkage(" << EL.Linkage << ", " << EL.Binding << ", "
+ << EL.Coalescing << ", " << EL.Visibility << ", "
+ << EL.DefinitionKind << ")";
}
} // namespace clang::ssaf
diff --git a/clang/lib/ScalableStaticAnalysis/Core/ModelStringConversions.h b/clang/lib/ScalableStaticAnalysis/Core/ModelStringConversions.h
index 9a4392bd72990..55a9e8bc460f8 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/ModelStringConversions.h
+++ b/clang/lib/ScalableStaticAnalysis/Core/ModelStringConversions.h
@@ -90,6 +90,125 @@ entityLinkageTypeFromString(llvm::StringRef Str) {
return std::nullopt;
}
+//===----------------------------------------------------------------------===//
+// EntityBinding
+//===----------------------------------------------------------------------===//
+
+/// Returns the canonical string representation of \p B (e.g. "Strong",
+/// "Weak").
+inline llvm::StringRef entityBindingToString(EntityBinding B) {
+ switch (B) {
+ case EntityBinding::Strong:
+ return "Strong";
+ case EntityBinding::Weak:
+ return "Weak";
+ case EntityBinding::Common:
+ return "Common";
+ case EntityBinding::Undefined:
+ return "Undefined";
+ }
+ llvm_unreachable("Unhandled EntityBinding variant");
+}
+
+/// Parses a string produced by entityBindingToString(). Returns std::nullopt
+/// if \p Str does not match any known EntityBinding value.
+inline std::optional<EntityBinding>
+entityBindingFromString(llvm::StringRef Str) {
+ if (Str == "Strong")
+ return EntityBinding::Strong;
+ if (Str == "Weak")
+ return EntityBinding::Weak;
+ if (Str == "Common")
+ return EntityBinding::Common;
+ if (Str == "Undefined")
+ return EntityBinding::Undefined;
+ return std::nullopt;
+}
+
+//===----------------------------------------------------------------------===//
+// EntityCoalescing
+//===----------------------------------------------------------------------===//
+
+/// Returns the canonical string representation of \p C (e.g. "None", "ODR").
+inline llvm::StringRef entityCoalescingToString(EntityCoalescing C) {
+ switch (C) {
+ case EntityCoalescing::None:
+ return "None";
+ case EntityCoalescing::ODR:
+ return "ODR";
+ }
+ llvm_unreachable("Unhandled EntityCoalescing variant");
+}
+
+/// Parses a string produced by entityCoalescingToString(). Returns std::nullopt
+/// if \p Str does not match any known EntityCoalescing value.
+inline std::optional<EntityCoalescing>
+entityCoalescingFromString(llvm::StringRef Str) {
+ if (Str == "None")
+ return EntityCoalescing::None;
+ if (Str == "ODR")
+ return EntityCoalescing::ODR;
+ return std::nullopt;
+}
+
+//===----------------------------------------------------------------------===//
+// EntityVisibility
+//===----------------------------------------------------------------------===//
+
+/// Returns the canonical string representation of \p V (e.g. "Default",
+/// "Hidden", "Protected").
+inline llvm::StringRef entityVisibilityToString(EntityVisibility V) {
+ switch (V) {
+ case EntityVisibility::Default:
+ return "Default";
+ case EntityVisibility::Hidden:
+ return "Hidden";
+ case EntityVisibility::Protected:
+ return "Protected";
+ }
+ llvm_unreachable("Unhandled EntityVisibility variant");
+}
+
+/// Parses a string produced by entityVisibilityToString(). Returns std::nullopt
+/// if \p Str does not match any known EntityVisibility value.
+inline std::optional<EntityVisibility>
+entityVisibilityFromString(llvm::StringRef Str) {
+ if (Str == "Default")
+ return EntityVisibility::Default;
+ if (Str == "Hidden")
+ return EntityVisibility::Hidden;
+ if (Str == "Protected")
+ return EntityVisibility::Protected;
+ return std::nullopt;
+}
+
+//===----------------------------------------------------------------------===//
+// EntityDefinitionKind
+//===----------------------------------------------------------------------===//
+
+/// Returns the canonical string representation of \p DK (e.g. "Definition",
+/// "Declaration").
+inline llvm::StringRef entityDefinitionKindToString(EntityDefinitionKind DK) {
+ switch (DK) {
+ case EntityDefinitionKind::Definition:
+ return "Definition";
+ case EntityDefinitionKind::Declaration:
+ return "Declaration";
+ }
+ llvm_unreachable("Unhandled EntityDefinitionKind variant");
+}
+
+/// Parses a string produced by entityDefinitionKindToString(). Returns
+/// std::nullopt if \p Str does not match any known EntityDefinitionKind value.
+inline std::optional<EntityDefinitionKind>
+entityDefinitionKindFromString(llvm::StringRef Str) {
+ if (Str == "Definition")
+ return EntityDefinitionKind::Definition;
+ if (Str == "Declaration")
+ return EntityDefinitionKind::Declaration;
+ return std::nullopt;
+}
+
} // namespace clang::ssaf
#endif // LLVM_CLANG_LIB_ScalableStaticAnalysis_CORE_MODELSTRINGCONVERSIONS_H
diff --git a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONEntitySummaryEncoding.cpp b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONEntitySummaryEncoding.cpp
index 35c45ac108e07..f7a6ba9d1cf89 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONEntitySummaryEncoding.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONEntitySummaryEncoding.cpp
@@ -11,8 +11,20 @@
namespace clang::ssaf {
+const char JSONEntitySummaryEncoding::Kind = 0;
+
+bool JSONEntitySummaryEncoding::equals(
+ const EntitySummaryEncoding &Other) const {
+ if (Other.getEncodingKind() != getEncodingKind()) {
+ return false;
+ }
+ // json::Value's operator== compares objects as maps, so key order does not
+ // affect the result.
+ return Data == static_cast<const JSONEntitySummaryEncoding &>(Other).Data;
+}
+
llvm::Error JSONEntitySummaryEncoding::patchEntityIdObject(
- llvm::json::Object &Obj, const std::map<EntityId, EntityId> &Table,
+ llvm::json::Object &Obj, const EntityResolutionMap &Resolution,
llvm::json::Value *AtVal) {
if (Obj.size() != 1) {
@@ -31,8 +43,8 @@ llvm::Error JSONEntitySummaryEncoding::patchEntityIdObject(
}
auto OldId = JSONFormat::makeEntityId(*OptEntityIdIndex);
- auto It = Table.find(OldId);
- if (It == Table.end()) {
+ auto It = Resolution.find(OldId);
+ if (It == Resolution.end()) {
return ErrorBuilder::create(std::errc::invalid_argument,
ErrorMessages::FailedToPatchEntityIdNotInTable,
OldId)
@@ -45,32 +57,34 @@ llvm::Error JSONEntitySummaryEncoding::patchEntityIdObject(
}
llvm::Error JSONEntitySummaryEncoding::patchRegularObject(
- llvm::json::Object &Obj, const std::map<EntityId, EntityId> &Table) {
+ llvm::json::Object &Obj, const EntityResolutionMap &Resolution) {
for (auto &[Key, Val] : Obj) {
- if (auto Err = patchValue(Val, Table)) {
+ if (auto Err = patchValue(Val, Resolution)) {
return Err;
}
}
return llvm::Error::success();
}
-llvm::Error JSONEntitySummaryEncoding::patchObject(
- llvm::json::Object &Obj, const std::map<EntityId, EntityId> &Table) {
+llvm::Error
+JSONEntitySummaryEncoding::patchObject(llvm::json::Object &Obj,
+ const EntityResolutionMap &Resolution) {
llvm::json::Value *AtVal = Obj.get(JSONEntityIdKey);
- return AtVal ? patchEntityIdObject(Obj, Table, AtVal)
- : patchRegularObject(Obj, Table);
+ return AtVal ? patchEntityIdObject(Obj, Resolution, AtVal)
+ : patchRegularObject(Obj, Resolution);
}
-llvm::Error JSONEntitySummaryEncoding::patchValue(
- llvm::json::Value &V, const std::map<EntityId, EntityId> &Table) {
+llvm::Error
+JSONEntitySummaryEncoding::patchValue(llvm::json::Value &V,
+ const EntityResolutionMap &Resolution) {
if (llvm::json::Object *Obj = V.getAsObject()) {
- if (auto Err = patchObject(*Obj, Table)) {
+ if (auto Err = patchObject(*Obj, Resolution)) {
return Err;
}
} else if (llvm::json::Array *Arr = V.getAsArray()) {
for (auto &Val : *Arr) {
- if (auto Err = patchValue(Val, Table)) {
+ if (auto Err = patchValue(Val, Resolution)) {
return Err;
}
}
@@ -78,9 +92,9 @@ llvm::Error JSONEntitySummaryEncoding::patchValue(
return llvm::Error::success();
}
-llvm::Error JSONEntitySummaryEncoding::patch(
- const std::map<EntityId, EntityId> &EntityResolutionTable) {
- return patchValue(Data, EntityResolutionTable);
+llvm::Error
+JSONEntitySummaryEncoding::patch(const EntityResolutionMap &Resolution) {
+ return patchValue(Data, Resolution);
}
} // namespace clang::ssaf
diff --git a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONEntitySummaryEncoding.h b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONEntitySummaryEncoding.h
index 5203e9d8ffc6f..1f7c700ceff84 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONEntitySummaryEncoding.h
+++ b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONEntitySummaryEncoding.h
@@ -27,22 +27,29 @@ class JSONEntitySummaryEncoding final : public EntitySummaryEncoding {
friend JSONFormat;
public:
- llvm::Error
- patch(const std::map<EntityId, EntityId> &EntityResolutionTable) override;
+ llvm::Error patch(const EntityResolutionMap &Resolution) override;
+
+ const void *getEncodingKind() const override { return &Kind; }
+
+ bool equals(const EntitySummaryEncoding &Other) const override;
private:
+ /// Distinguishes this encoding from other formats' without RTTI. Its address
+ /// is the identity; the value is irrelevant.
+ static const char Kind;
+
explicit JSONEntitySummaryEncoding(llvm::json::Value Data)
: Data(std::move(Data)) {}
llvm::Error patchEntityIdObject(llvm::json::Object &Obj,
- const std::map<EntityId, EntityId> &Table,
+ const EntityResolutionMap &Resolution,
llvm::json::Value *AtVal);
llvm::Error patchRegularObject(llvm::json::Object &Obj,
- const std::map<EntityId, EntityId> &Table);
+ const EntityResolutionMap &Resolution);
llvm::Error patchObject(llvm::json::Object &Obj,
- const std::map<EntityId, EntityId> &Table);
+ const EntityResolutionMap &Resolution);
llvm::Error patchValue(llvm::json::Value &V,
- const std::map<EntityId, EntityId> &Table);
+ const EntityResolutionMap &Resolution);
llvm::json::Value Data;
};
diff --git a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.cpp b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.cpp
index b19849233f0f8..e6b9bffcdff46 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.cpp
@@ -443,6 +443,67 @@ llvm::StringRef entityLinkageTypeToJSON(EntityLinkageType LT) {
return entityLinkageTypeToString(LT);
}
+//----------------------------------------------------------------------------
+// EntityBinding / EntityVisibility / EntityDefinitionKind
+//----------------------------------------------------------------------------
+
+llvm::Expected<EntityBinding> entityBindingFromJSON(llvm::StringRef Str) {
+ auto Opt = entityBindingFromString(Str);
+ if (!Opt) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::InvalidEntityBinding, Str)
+ .build();
+ }
+ return *Opt;
+}
+
+llvm::StringRef entityBindingToJSON(EntityBinding B) {
+ return entityBindingToString(B);
+}
+
+llvm::Expected<EntityCoalescing> entityCoalescingFromJSON(llvm::StringRef Str) {
+ auto Opt = entityCoalescingFromString(Str);
+ if (!Opt) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::InvalidEntityCoalescing, Str)
+ .build();
+ }
+ return *Opt;
+}
+
+llvm::StringRef entityCoalescingToJSON(EntityCoalescing C) {
+ return entityCoalescingToString(C);
+}
+
+llvm::Expected<EntityVisibility> entityVisibilityFromJSON(llvm::StringRef Str) {
+ auto Opt = entityVisibilityFromString(Str);
+ if (!Opt) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::InvalidEntityVisibility, Str)
+ .build();
+ }
+ return *Opt;
+}
+
+llvm::StringRef entityVisibilityToJSON(EntityVisibility V) {
+ return entityVisibilityToString(V);
+}
+
+llvm::Expected<EntityDefinitionKind>
+entityDefinitionKindFromJSON(llvm::StringRef Str) {
+ auto Opt = entityDefinitionKindFromString(Str);
+ if (!Opt) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::InvalidEntityDefinitionKind, Str)
+ .build();
+ }
+ return *Opt;
+}
+
+llvm::StringRef entityDefinitionKindToJSON(EntityDefinitionKind DK) {
+ return entityDefinitionKindToString(DK);
+}
+
//----------------------------------------------------------------------------
// TargetTriple
//----------------------------------------------------------------------------
@@ -464,27 +525,88 @@ llvm::Error validateNormalizedTargetTriple(llvm::StringRef Triple) {
llvm::Expected<EntityLinkage>
JSONFormat::entityLinkageFromJSON(const Object &EntityLinkageObject) const {
- auto OptLinkageStr = EntityLinkageObject.getString("type");
- if (!OptLinkageStr) {
- return ErrorBuilder::create(std::errc::invalid_argument,
- ErrorMessages::FailedToReadObjectAtField,
- "EntityLinkageType", "type", "string")
- .build();
- }
+ auto ReadStr = [&](llvm::StringRef ObjLabel,
+ llvm::StringRef Field) -> llvm::Expected<llvm::StringRef> {
+ auto OptStr = EntityLinkageObject.getString(Field);
+ if (!OptStr) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ ObjLabel, Field, "string")
+ .build();
+ }
+ return *OptStr;
+ };
- auto ExpectedLinkageType = entityLinkageTypeFromJSON(*OptLinkageStr);
+ auto TypeStr = ReadStr("EntityLinkageType", "type");
+ if (!TypeStr) {
+ return TypeStr.takeError();
+ }
+ auto ExpectedLinkageType = entityLinkageTypeFromJSON(*TypeStr);
if (!ExpectedLinkageType) {
return ErrorBuilder::wrap(ExpectedLinkageType.takeError())
.context(ErrorMessages::ReadingFromField, "EntityLinkageType", "type")
.build();
}
- return EntityLinkage(*ExpectedLinkageType);
+ auto BindingStr = ReadStr("EntityBinding", "binding");
+ if (!BindingStr) {
+ return BindingStr.takeError();
+ }
+ auto ExpectedBinding = entityBindingFromJSON(*BindingStr);
+ if (!ExpectedBinding) {
+ return ErrorBuilder::wrap(ExpectedBinding.takeError())
+ .context(ErrorMessages::ReadingFromField, "EntityBinding", "binding")
+ .build();
+ }
+
+ auto CoalescingStr = ReadStr("EntityCoalescing", "coalescing");
+ if (!CoalescingStr) {
+ return CoalescingStr.takeError();
+ }
+ auto ExpectedCoalescing = entityCoalescingFromJSON(*CoalescingStr);
+ if (!ExpectedCoalescing) {
+ return ErrorBuilder::wrap(ExpectedCoalescing.takeError())
+ .context(ErrorMessages::ReadingFromField, "EntityCoalescing",
+ "coalescing")
+ .build();
+ }
+
+ auto VisibilityStr = ReadStr("EntityVisibility", "visibility");
+ if (!VisibilityStr) {
+ return VisibilityStr.takeError();
+ }
+ auto ExpectedVisibility = entityVisibilityFromJSON(*VisibilityStr);
+ if (!ExpectedVisibility) {
+ return ErrorBuilder::wrap(ExpectedVisibility.takeError())
+ .context(ErrorMessages::ReadingFromField, "EntityVisibility",
+ "visibility")
+ .build();
+ }
+
+ auto DefinitionStr = ReadStr("EntityDefinitionKind", "definition");
+ if (!DefinitionStr) {
+ return DefinitionStr.takeError();
+ }
+ auto ExpectedDefinitionKind = entityDefinitionKindFromJSON(*DefinitionStr);
+ if (!ExpectedDefinitionKind) {
+ return ErrorBuilder::wrap(ExpectedDefinitionKind.takeError())
+ .context(ErrorMessages::ReadingFromField, "EntityDefinitionKind",
+ "definition")
+ .build();
+ }
+
+ return EntityLinkage(*ExpectedLinkageType, *ExpectedBinding,
+ *ExpectedCoalescing, *ExpectedVisibility,
+ *ExpectedDefinitionKind);
}
Object JSONFormat::entityLinkageToJSON(const EntityLinkage &EL) const {
Object Result;
Result["type"] = entityLinkageTypeToJSON(getLinkage(EL));
+ Result["binding"] = entityBindingToJSON(getBinding(EL));
+ Result["coalescing"] = entityCoalescingToJSON(getCoalescing(EL));
+ Result["visibility"] = entityVisibilityToJSON(getVisibility(EL));
+ Result["definition"] = entityDefinitionKindToJSON(getDefinitionKind(EL));
return Result;
}
diff --git a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.h b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.h
index b17f296eda1b4..ebcda8634ba84 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.h
+++ b/clang/lib/ScalableStaticAnalysis/Core/Serialization/JSONFormat/JSONFormatImpl.h
@@ -114,6 +114,18 @@ inline constexpr const char *InvalidBuildNamespaceKind =
inline constexpr const char *InvalidEntityLinkageType =
"invalid EntityLinkageType value '{0}' for field 'type'";
+inline constexpr const char *InvalidEntityBinding =
+ "invalid EntityBinding value '{0}' for field 'binding'";
+
+inline constexpr const char *InvalidEntityCoalescing =
+ "invalid EntityCoalescing value '{0}' for field 'coalescing'";
+
+inline constexpr const char *InvalidEntityVisibility =
+ "invalid EntityVisibility value '{0}' for field 'visibility'";
+
+inline constexpr const char *InvalidEntityDefinitionKind =
+ "invalid EntityDefinitionKind value '{0}' for field 'definition'";
+
inline constexpr const char *FailedToDeserializeLinkageTableExtraId =
"failed to deserialize LinkageTable: extra '{0}' not present in IdTable";
@@ -220,6 +232,23 @@ entityLinkageTypeFromJSON(llvm::StringRef EntityLinkageTypeStr);
// Provided for consistency with respect to rest of the codebase.
llvm::StringRef entityLinkageTypeToJSON(EntityLinkageType LT);
+//----------------------------------------------------------------------------
+// EntityBinding / EntityCoalescing / EntityVisibility / EntityDefinitionKind
+//----------------------------------------------------------------------------
+
+llvm::Expected<EntityBinding> entityBindingFromJSON(llvm::StringRef Str);
+llvm::StringRef entityBindingToJSON(EntityBinding B);
+
+llvm::Expected<EntityCoalescing> entityCoalescingFromJSON(llvm::StringRef Str);
+llvm::StringRef entityCoalescingToJSON(EntityCoalescing C);
+
+llvm::Expected<EntityVisibility> entityVisibilityFromJSON(llvm::StringRef Str);
+llvm::StringRef entityVisibilityToJSON(EntityVisibility V);
+
+llvm::Expected<EntityDefinitionKind>
+entityDefinitionKindFromJSON(llvm::StringRef Str);
+llvm::StringRef entityDefinitionKindToJSON(EntityDefinitionKind DK);
+
//----------------------------------------------------------------------------
// TargetTriple helpers
//----------------------------------------------------------------------------
diff --git a/clang/lib/ScalableStaticAnalysis/Core/TUSummary/TUSummaryBuilder.cpp b/clang/lib/ScalableStaticAnalysis/Core/TUSummary/TUSummaryBuilder.cpp
index ba2def365dddc..a0fb5ad7c9c29 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/TUSummary/TUSummaryBuilder.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Core/TUSummary/TUSummaryBuilder.cpp
@@ -18,12 +18,16 @@ using namespace clang;
using namespace ssaf;
EntityId TUSummaryBuilder::addEntity(const EntityName &EN,
- EntityLinkageType Linkage) {
+ EntityLinkage Linkage) {
EntityId Id = Summary.IdTable.getId(EN);
- [[maybe_unused]] EntityLinkageType Link =
- Summary.LinkageTable.try_emplace(Id, Linkage).first->second.getLinkage();
- // Even if we had in the past a linkage, that must bee the same as we set now.
- assert(Link == Linkage);
+ [[maybe_unused]] const EntityLinkage &Existing =
+ Summary.LinkageTable.try_emplace(Id, Linkage).first->second;
+ // An entity's linkage type must be stable across its redeclarations within a
+ // TU. The other symbol properties (binding, visibility, definition kind) may
+ // legitimately differ between a declaration and a definition of the same
+ // entity; reconciling those is the linker's responsibility, so we keep the
+ // first occurrence here and only assert linkage-type stability.
+ assert(Existing.getLinkage() == Linkage.getLinkage());
return Id;
}
diff --git a/clang/lib/ScalableStaticAnalysis/Core/TUSummary/TUSummaryExtractor.cpp b/clang/lib/ScalableStaticAnalysis/Core/TUSummary/TUSummaryExtractor.cpp
index 40205b90a4eb6..6cd8250d66b9a 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/TUSummary/TUSummaryExtractor.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Core/TUSummary/TUSummaryExtractor.cpp
@@ -7,6 +7,8 @@
//===----------------------------------------------------------------------===//
#include "clang/ScalableStaticAnalysis/Core/TUSummary/TUSummaryExtractor.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/ScalableStaticAnalysis/Core/ASTEntityMapping.h"
@@ -19,13 +21,135 @@
using namespace clang;
using namespace ssaf;
-static EntityLinkageType getLinkageForDecl(const Decl *D) {
- const auto *ND = dyn_cast<NamedDecl>(D);
- if (!ND)
+static EntityLinkageType mapLinkage(const NamedDecl *ND) {
+ switch (ND->getFormalLinkage()) {
+ case Linkage::Invalid: {
+ llvm_unreachable("Shouldn't be invalid");
+ }
+ case Linkage::None:
return EntityLinkageType::None;
+ case Linkage::Internal:
+ case Linkage::UniqueExternal:
+ case Linkage::VisibleNone:
+ return EntityLinkageType::Internal;
+ case Linkage::Module:
+ case Linkage::External:
+ return EntityLinkageType::External;
+ }
+ llvm_unreachable("Unhandled clang::Linkage kind");
+}
+
+static EntityVisibility mapVisibility(const NamedDecl *ND) {
+ switch (ND->getVisibility()) {
+ case HiddenVisibility:
+ return EntityVisibility::Hidden;
+ case ProtectedVisibility:
+ return EntityVisibility::Protected;
+ case DefaultVisibility:
+ return EntityVisibility::Default;
+ }
+ llvm_unreachable("Unhandled clang::Visibility kind");
+}
+
+static EntityDefinitionKind mapDefinitionKind(const NamedDecl *ND,
+ ASTContext &Ctx) {
+ if (const auto *FD = dyn_cast<FunctionDecl>(ND)) {
+ return FD->isThisDeclarationADefinition()
+ ? EntityDefinitionKind::Definition
+ : EntityDefinitionKind::Declaration;
+ }
+ if (const auto *VD = dyn_cast<VarDecl>(ND)) {
+ return VD->isThisDeclarationADefinition(Ctx) == VarDecl::DeclarationOnly
+ ? EntityDefinitionKind::Declaration
+ : EntityDefinitionKind::Definition;
+ }
+ return EntityDefinitionKind::Definition;
+}
+
+static EntityBinding mapBinding(const NamedDecl *ND, ASTContext &Ctx) {
+ if (ND->hasAttr<WeakAttr>() || ND->hasAttr<WeakRefAttr>()) {
+ return EntityBinding::Weak;
+ }
+ if (const auto *FD = dyn_cast<FunctionDecl>(ND)) {
+ if (!FD->isThisDeclarationADefinition()) {
+ return EntityBinding::Undefined;
+ }
+ switch (Ctx.GetGVALinkageForFunction(FD)) {
+ case GVA_AvailableExternally:
+ return EntityBinding::Undefined;
+ case GVA_DiscardableODR:
+ case GVA_StrongODR:
+ // An ODR definition binds strongly; that its duplicates are permitted is
+ // recorded separately by mapCoalescing(). Object formats express this
+ // differently — ELF and Mach-O lower it to a weak symbol, COFF keeps it
+ // strong and uses a COMDAT — which is LinkageRules' concern, not ours.
+ return EntityBinding::Strong;
+ case GVA_Internal:
+ case GVA_StrongExternal:
+ return EntityBinding::Strong;
+ }
+ return EntityBinding::Strong;
+ }
+ if (const auto *VD = dyn_cast<VarDecl>(ND)) {
+ switch (VD->isThisDeclarationADefinition(Ctx)) {
+ case VarDecl::DeclarationOnly:
+ return EntityBinding::Undefined;
+ case VarDecl::TentativeDefinition:
+ return EntityBinding::Common;
+ case VarDecl::Definition:
+ break;
+ }
+ return EntityBinding::Strong;
+ }
+ return EntityBinding::Strong;
+}
+
+/// Returns whether every definition of \p ND is required to be identical, as
+/// the One Definition Rule guarantees for inline functions, templates and
+/// vtables.
+static EntityCoalescing mapCoalescing(const NamedDecl *ND, ASTContext &Ctx) {
+ // An explicitly weak symbol may be replaced by an unrelated definition, so
+ // its copies carry no identity guarantee even if it is also inline.
+ if (ND->hasAttr<WeakAttr>() || ND->hasAttr<WeakRefAttr>()) {
+ return EntityCoalescing::None;
+ }
+ if (const auto *FD = dyn_cast<FunctionDecl>(ND)) {
+ if (!FD->isThisDeclarationADefinition()) {
+ return EntityCoalescing::None;
+ }
+ switch (Ctx.GetGVALinkageForFunction(FD)) {
+ case GVA_DiscardableODR:
+ case GVA_StrongODR:
+ return EntityCoalescing::ODR;
+ default:
+ return EntityCoalescing::None;
+ }
+ }
+ if (const auto *VD = dyn_cast<VarDecl>(ND)) {
+ if (VD->isThisDeclarationADefinition(Ctx) != VarDecl::Definition) {
+ return EntityCoalescing::None;
+ }
+ switch (Ctx.GetGVALinkageForVariable(VD)) {
+ case GVA_DiscardableODR:
+ case GVA_StrongODR:
+ return EntityCoalescing::ODR;
+ default:
+ return EntityCoalescing::None;
+ }
+ }
+ return EntityCoalescing::None;
+}
+
+static EntityLinkage getEntityLinkageForDecl(const Decl *D) {
+ const auto *ND = dyn_cast<NamedDecl>(D);
+ if (!ND) {
+ return EntityLinkage(EntityLinkageType::None, EntityBinding::Undefined,
+ EntityCoalescing::None, EntityVisibility::Default,
+ EntityDefinitionKind::Declaration);
+ }
// Parameters have no linkage in C++, but SSAF needs them to inherit
- // the external linkage from their parent functions.
+ // the linker properties from their parent functions.
// Here is why:
// SSAF treats parameters as entities and may not always associate them back
// to their parent functions. Therefore, it needs to identify parameters of
@@ -36,35 +160,21 @@ static EntityLinkageType getLinkageForDecl(const Decl *D) {
if (const auto *PVD = dyn_cast<ParmVarDecl>(D)) {
if (const auto *FD = llvm::dyn_cast_or_null<FunctionDecl>(
PVD->getParentFunctionOrMethod())) {
- return getLinkageForDecl(FD);
+ return getEntityLinkageForDecl(FD);
}
}
- switch (ND->getFormalLinkage()) {
- case Linkage::Invalid: {
- llvm_unreachable("Shouldn't be invalid");
- }
- case Linkage::None:
- return EntityLinkageType::None;
- case Linkage::Internal:
- return EntityLinkageType::Internal;
- case Linkage::UniqueExternal:
- return EntityLinkageType::Internal;
- case Linkage::VisibleNone:
- return EntityLinkageType::Internal;
- case Linkage::Module:
- return EntityLinkageType::External;
- case Linkage::External:
- return EntityLinkageType::External;
- }
- llvm_unreachable("Unhandled clang::Linkage kind");
+ ASTContext &Ctx = ND->getASTContext();
+ return EntityLinkage(mapLinkage(ND), mapBinding(ND, Ctx),
+ mapCoalescing(ND, Ctx), mapVisibility(ND),
+ mapDefinitionKind(ND, Ctx));
}
std::optional<EntityId> TUSummaryExtractor::addEntity(const NamedDecl *D) {
auto Name = getEntityName(D);
if (!Name)
return std::nullopt;
- return SummaryBuilder.addEntity(*Name, getLinkageForDecl(D));
+ return SummaryBuilder.addEntity(*Name, getEntityLinkageForDecl(D));
}
std::optional<EntityId>
@@ -72,7 +182,7 @@ TUSummaryExtractor::addEntityForReturn(const FunctionDecl *FD) {
auto Name = getEntityNameForReturn(FD);
if (!Name)
return std::nullopt;
- return SummaryBuilder.addEntity(*Name, getLinkageForDecl(FD));
+ return SummaryBuilder.addEntity(*Name, getEntityLinkageForDecl(FD));
}
const SSAFOptions &TUSummaryExtractor::getOptions() const {
diff --git a/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-array.json b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-array.json
index 282917559e5b5..7dcc9e7f559f3 100644
--- a/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-array.json
+++ b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-array.json
@@ -65,19 +65,31 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-summary.json b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-summary.json
index c2c5e119ca330..b40bb2162519a 100644
--- a/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-summary.json
+++ b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-summary.json
@@ -42,19 +42,31 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-no-key.json b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-no-key.json
index 14c654f8b7c14..cad429c9c515e 100644
--- a/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-no-key.json
+++ b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-no-key.json
@@ -71,19 +71,31 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary.json b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary.json
index 9b0b4575e2f61..c05d6f5f60fdd 100644
--- a/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary.json
+++ b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary.json
@@ -68,19 +68,31 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/TypeConstrainedPointers/Inputs/tu-summary-bad-id.json b/clang/test/Analysis/Scalable/TypeConstrainedPointers/Inputs/tu-summary-bad-id.json
index 286826682b050..3f01aaa9a24a7 100644
--- a/clang/test/Analysis/Scalable/TypeConstrainedPointers/Inputs/tu-summary-bad-id.json
+++ b/clang/test/Analysis/Scalable/TypeConstrainedPointers/Inputs/tu-summary-bad-id.json
@@ -27,7 +27,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/TypeConstrainedPointers/Inputs/tu-summary-no-key.json b/clang/test/Analysis/Scalable/TypeConstrainedPointers/Inputs/tu-summary-no-key.json
index 3608117904567..da1a7e82e719b 100644
--- a/clang/test/Analysis/Scalable/TypeConstrainedPointers/Inputs/tu-summary-no-key.json
+++ b/clang/test/Analysis/Scalable/TypeConstrainedPointers/Inputs/tu-summary-no-key.json
@@ -25,7 +25,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/TypeConstrainedPointers/Inputs/tu-summary.json b/clang/test/Analysis/Scalable/TypeConstrainedPointers/Inputs/tu-summary.json
index a6231439888c5..f2fe359e62abd 100644
--- a/clang/test/Analysis/Scalable/TypeConstrainedPointers/Inputs/tu-summary.json
+++ b/clang/test/Analysis/Scalable/TypeConstrainedPointers/Inputs/tu-summary.json
@@ -46,19 +46,31 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/tu-summary-bad-element.json b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/tu-summary-bad-element.json
index 8e73183ce3b29..70fc713f88ce5 100644
--- a/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/tu-summary-bad-element.json
+++ b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/tu-summary-bad-element.json
@@ -34,13 +34,21 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/tu-summary-bad-ptr-level.json b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/tu-summary-bad-ptr-level.json
index 9a4ef04219f0a..e4daa766a5cce 100644
--- a/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/tu-summary-bad-ptr-level.json
+++ b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/tu-summary-bad-ptr-level.json
@@ -39,13 +39,21 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/tu-summary-no-key.json b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/tu-summary-no-key.json
index 0ab350fa56182..d1a6cde4be93d 100644
--- a/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/tu-summary-no-key.json
+++ b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/tu-summary-no-key.json
@@ -39,13 +39,21 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/tu-summary.json b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/tu-summary.json
index e4df108408472..6af968d8cb0df 100644
--- a/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/tu-summary.json
+++ b/clang/test/Analysis/Scalable/UnsafeBufferUsage/Inputs/tu-summary.json
@@ -82,19 +82,31 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/linkage-extraction.cpp b/clang/test/Analysis/Scalable/linkage-extraction.cpp
new file mode 100644
index 0000000000000..45a001ed9b888
--- /dev/null
+++ b/clang/test/Analysis/Scalable/linkage-extraction.cpp
@@ -0,0 +1,73 @@
+// Verifies the linkage the extractor records for each source construct.
+//
+// Binding and coalescing are independent axes: an inline function binds
+// strongly and additionally guarantees its definitions are identical, whereas
+// __attribute__((weak)) binds weakly with no such guarantee. Object formats
+// encode this differently — ELF and Mach-O lower an ODR definition to a weak
+// symbol while COFF keeps it strong and uses a COMDAT — so the summary records
+// the source-level fact and leaves the lowering to the linker. See
+// docs/ssaf-linker-elf-behavior.md §3 and docs/ssaf-linker-coff-behavior.md §2.
+//
+// Each construct lives in its own file so that the summary holds exactly one
+// entity: ids are assigned in traversal order, so a shared file would make the
+// checks depend on declaration order rather than on the linkage itself.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+
+// An ordinary definition: strong, with no coalescing guarantee.
+// RUN: %clang_cc1 -fsyntax-only %t/ordinary.cpp \
+// RUN: --ssaf-extract-summaries=CallGraph --ssaf-compilation-unit-id=cu \
+// RUN: --ssaf-tu-summary-file=%t/ordinary.json
+// RUN: cat %t/ordinary.json | FileCheck %s --check-prefix=ORDINARY
+// ORDINARY: "usr": "c:@F at ordinary#"
+// ORDINARY: "binding": "Strong",
+// ORDINARY-NEXT: "coalescing": "None",
+// ORDINARY-NEXT: "definition": "Definition",
+
+// An inline definition: still strong, but its copies must be identical.
+// RUN: %clang_cc1 -fsyntax-only %t/inlined.cpp \
+// RUN: --ssaf-extract-summaries=CallGraph --ssaf-compilation-unit-id=cu \
+// RUN: --ssaf-tu-summary-file=%t/inlined.json
+// RUN: cat %t/inlined.json | FileCheck %s --check-prefix=INLINED
+// INLINED: "usr": "c:@F at inlined#"
+// INLINED: "binding": "Strong",
+// INLINED-NEXT: "coalescing": "ODR",
+// INLINED-NEXT: "definition": "Definition",
+
+// An explicitly weak definition may be replaced by an unrelated one, so it
+// carries no ODR guarantee even though it is also inline.
+// RUN: %clang_cc1 -fsyntax-only %t/weak_inlined.cpp \
+// RUN: --ssaf-extract-summaries=CallGraph --ssaf-compilation-unit-id=cu \
+// RUN: --ssaf-tu-summary-file=%t/weak_inlined.json
+// RUN: cat %t/weak_inlined.json | FileCheck %s --check-prefix=WEAK
+// WEAK: "usr": "c:@F at weak_inlined#"
+// WEAK: "binding": "Weak",
+// WEAK-NEXT: "coalescing": "None",
+// WEAK-NEXT: "definition": "Definition",
+
+// A hidden definition keeps its visibility; the extractor records what the
+// source said and leaves target-specific coercion to the linker.
+// RUN: %clang_cc1 -fsyntax-only %t/hidden.cpp \
+// RUN: --ssaf-extract-summaries=CallGraph --ssaf-compilation-unit-id=cu \
+// RUN: --ssaf-tu-summary-file=%t/hidden.json
+// RUN: cat %t/hidden.json | FileCheck %s --check-prefix=HIDDEN
+// HIDDEN: "usr": "c:@F at hidden_fn#"
+// HIDDEN: "binding": "Strong",
+// HIDDEN-NEXT: "coalescing": "None",
+// HIDDEN-NEXT: "definition": "Definition",
+// HIDDEN-NEXT: "type": "External",
+// HIDDEN-NEXT: "visibility": "Hidden"
+
+//--- ordinary.cpp
+int ordinary(void) { return 1; }
+
+//--- inlined.cpp
+inline int inlined(void) { return 2; }
+
+//--- weak_inlined.cpp
+__attribute__((weak)) inline int weak_inlined(void) { return 3; }
+
+//--- hidden.cpp
+__attribute__((visibility("hidden"))) int hidden_fn(void) { return 4; }
diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-tags-only.json b/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-tags-only.json
index ddff7a789fffb..658cba233d42b 100644
--- a/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-tags-only.json
+++ b/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-tags-only.json
@@ -31,7 +31,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu.json b/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu.json
index 4654ada97a341..bf70122afacad 100644
--- a/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu.json
+++ b/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu.json
@@ -101,19 +101,31 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
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
index dc00a24f0eaf0..01715bf3b50ca 100644
--- 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
@@ -21,7 +21,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
@@ -55,7 +59,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/invalid-direct-callee-element.json b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/invalid-direct-callee-element.json
index b726ec997917f..dfaafc5e35454 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/invalid-direct-callee-element.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/invalid-direct-callee-element.json
@@ -16,7 +16,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/invalid-direct-callee-id.json b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/invalid-direct-callee-id.json
index c4d8af0dbfcfc..7045b0be2726e 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/invalid-direct-callee-id.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/invalid-direct-callee-id.json
@@ -16,7 +16,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-col.json b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-col.json
index fb928fe397acc..ca97c24b636e7 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-col.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-col.json
@@ -16,7 +16,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-file.json b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-file.json
index e43509f1f8ca4..ec2c3267ad849 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-file.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-file.json
@@ -16,7 +16,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-line.json b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-line.json
index dd14915d37aba..970b0f8b8a4ca 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-line.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-line.json
@@ -16,7 +16,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def.json b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def.json
index c0da9cd05f807..697ebadd3829d 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def.json
@@ -16,7 +16,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-direct-callees.json b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-direct-callees.json
index 45262d5813200..3748c6cd6720b 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-direct-callees.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-direct-callees.json
@@ -16,7 +16,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-pretty-name.json b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-pretty-name.json
index b1e7de381a36a..6ce0a132e0057 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-pretty-name.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-pretty-name.json
@@ -16,7 +16,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-virtual-callees.json b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-virtual-callees.json
index 02e313bdc67b0..c5ddbc9648a65 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-virtual-callees.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-virtual-callees.json
@@ -16,7 +16,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/duplicate-entity-id-in-data-map.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/duplicate-entity-id-in-data-map.json
index 08bda0ffa6c73..4398c91ac2dd3 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/duplicate-entity-id-in-data-map.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/duplicate-entity-id-in-data-map.json
@@ -28,7 +28,11 @@
{
"id": 0,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/duplicate-entity.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/duplicate-entity.json
index 0d5e7d5b1eeec..2af19bf7296e1 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/duplicate-entity.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/duplicate-entity.json
@@ -45,13 +45,21 @@
{
"id": 0,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/entity-name-missing-namespace.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/entity-name-missing-namespace.json
index 174423ce72c33..a805239d836f9 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/entity-name-missing-namespace.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/entity-name-missing-namespace.json
@@ -18,7 +18,11 @@
{
"id": 0,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/entity-name-missing-suffix.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/entity-name-missing-suffix.json
index 92457c7f75d44..3400959cdc3f8 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/entity-name-missing-suffix.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/entity-name-missing-suffix.json
@@ -23,7 +23,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/entity-name-missing-usr.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/entity-name-missing-usr.json
index a3af210d887b1..968367686b9a0 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/entity-name-missing-usr.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/entity-name-missing-usr.json
@@ -27,7 +27,11 @@
{
"id": 0,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-duplicate-id.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-duplicate-id.json
index e03fa76057044..7b83fbd790591 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-duplicate-id.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-duplicate-id.json
@@ -28,13 +28,21 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 0,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-entry-id-not-uint64.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-entry-id-not-uint64.json
index ae30fe240fb20..95a001f1f320f 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-entry-id-not-uint64.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-entry-id-not-uint64.json
@@ -10,7 +10,11 @@
{
"id": "not_a_number",
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-entry-linkage-invalid-type.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-entry-linkage-invalid-type.json
index 7f53e39f1d942..c724a25393ab0 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-entry-linkage-invalid-type.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-entry-linkage-invalid-type.json
@@ -10,7 +10,11 @@
{
"id": 0,
"linkage": {
- "type": "invalid_type"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "invalid_type",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-entry-missing-id.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-entry-missing-id.json
index 73738e0a42255..1ab2daf8b32a3 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-entry-missing-id.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-entry-missing-id.json
@@ -9,7 +9,11 @@
"linkage_table": [
{
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-extra-id.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-extra-id.json
index af1436f97df13..749ca4fe440e1 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-extra-id.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/linkage-table-extra-id.json
@@ -10,7 +10,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/namespace-element-invalid-kind.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/namespace-element-invalid-kind.json
index 6c648ca818656..28fde31072946 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/namespace-element-invalid-kind.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/namespace-element-invalid-kind.json
@@ -24,7 +24,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/namespace-element-missing-kind.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/namespace-element-missing-kind.json
index 004ff880ab480..17c148eded9ec 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/namespace-element-missing-kind.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/namespace-element-missing-kind.json
@@ -23,7 +23,11 @@
{
"id": 0,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/namespace-element-missing-name.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/namespace-element-missing-name.json
index b35b6bb00da4b..1af7f2e3a16f2 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/namespace-element-missing-name.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/namespace-element-missing-name.json
@@ -23,7 +23,11 @@
{
"id": 0,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/namespace-element-not-object.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/namespace-element-not-object.json
index 00f88a4651d90..b5e299b720d28 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/namespace-element-not-object.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/namespace-element-not-object.json
@@ -21,7 +21,11 @@
{
"id": 0,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-linkage-external.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-linkage-external.json
index 1024caba971c7..cd096c47213de 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-linkage-external.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-linkage-external.json
@@ -19,7 +19,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-linkage-internal.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-linkage-internal.json
index f4e9603d9bd0e..b29840e401f70 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-linkage-internal.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-linkage-internal.json
@@ -19,7 +19,11 @@
{
"id": 0,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-linkage-multiple.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-linkage-multiple.json
index e2c333ffbc07a..e52bd82dbfdd0 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-linkage-multiple.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-linkage-multiple.json
@@ -45,19 +45,31 @@
{
"id": 0,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-linkage-none.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-linkage-none.json
index 8330210158484..14b8703376ce3 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-linkage-none.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-linkage-none.json
@@ -19,7 +19,11 @@
{
"id": 0,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-two-summary-types.json b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-two-summary-types.json
index 3297419191ef8..1f67eec48f0b6 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-two-summary-types.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/LUSummary/Inputs/rt-two-summary-types.json
@@ -203,31 +203,51 @@
{
"id": 0,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 3,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 4,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
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
index dc00a24f0eaf0..01715bf3b50ca 100644
--- 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
@@ -21,7 +21,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
@@ -55,7 +59,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/duplicate-entity-id-in-data-map.json b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/duplicate-entity-id-in-data-map.json
index a4d6723f0c595..94421c4a97665 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/duplicate-entity-id-in-data-map.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/duplicate-entity-id-in-data-map.json
@@ -16,7 +16,11 @@
{
"id": 0,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/duplicate-entity.json b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/duplicate-entity.json
index 16e47f09d2a00..c3cb7ea136301 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/duplicate-entity.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/duplicate-entity.json
@@ -22,11 +22,11 @@
"linkage_table": [
{
"id": 0,
- "linkage": { "type": "Internal" }
+ "linkage": { "binding": "Strong", "coalescing": "None", "definition": "Definition", "type": "Internal", "visibility": "Default" }
},
{
"id": 1,
- "linkage": { "type": "External" }
+ "linkage": { "binding": "Strong", "coalescing": "None", "definition": "Definition", "type": "External", "visibility": "Default" }
}
],
"data": [],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/entity-name-missing-suffix.json b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/entity-name-missing-suffix.json
index a9b793c706697..e0690cafc8e64 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/entity-name-missing-suffix.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/entity-name-missing-suffix.json
@@ -14,7 +14,7 @@
"linkage_table": [
{
"id": 0,
- "linkage": { "type": "External" }
+ "linkage": { "binding": "Strong", "coalescing": "None", "definition": "Definition", "type": "External", "visibility": "Default" }
}
],
"data": [],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/entity-name-missing-usr.json b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/entity-name-missing-usr.json
index d32cf5c7ff188..29678063f0c55 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/entity-name-missing-usr.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/entity-name-missing-usr.json
@@ -14,7 +14,7 @@
"linkage_table": [
{
"id": 0,
- "linkage": { "type": "Internal" }
+ "linkage": { "binding": "Strong", "coalescing": "None", "definition": "Definition", "type": "Internal", "visibility": "Default" }
}
],
"data": [],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/id-table-entry-missing-name.json b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/id-table-entry-missing-name.json
index 12b52496f3c04..e0655c130b483 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/id-table-entry-missing-name.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/id-table-entry-missing-name.json
@@ -11,7 +11,7 @@
"linkage_table": [
{
"id": 0,
- "linkage": { "type": "None" }
+ "linkage": { "binding": "Strong", "coalescing": "None", "definition": "Definition", "type": "None", "visibility": "Default" }
}
],
"data": [],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-duplicate-id.json b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-duplicate-id.json
index 2e3cd065e82d9..64f12f33238f6 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-duplicate-id.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-duplicate-id.json
@@ -15,11 +15,11 @@
"linkage_table": [
{
"id": 0,
- "linkage": { "type": "External" }
+ "linkage": { "binding": "Strong", "coalescing": "None", "definition": "Definition", "type": "External", "visibility": "Default" }
},
{
"id": 0,
- "linkage": { "type": "Internal" }
+ "linkage": { "binding": "Strong", "coalescing": "None", "definition": "Definition", "type": "Internal", "visibility": "Default" }
}
],
"data": [],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-entry-id-not-uint64.json b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-entry-id-not-uint64.json
index fde6f8b169cda..1544bb6fbfdd4 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-entry-id-not-uint64.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-entry-id-not-uint64.json
@@ -7,7 +7,7 @@
"linkage_table": [
{
"id": "not_a_number",
- "linkage": { "type": "External" }
+ "linkage": { "binding": "Strong", "coalescing": "None", "definition": "Definition", "type": "External", "visibility": "Default" }
}
],
"data": [],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-entry-linkage-invalid-coalescing.json b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-entry-linkage-invalid-coalescing.json
new file mode 100644
index 0000000000000..82a3f88ec952d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-entry-linkage-invalid-coalescing.json
@@ -0,0 +1,16 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": { "type": "External", "binding": "Strong", "coalescing": "invalid_coalescing" }
+ }
+ ],
+ "data": [],
+ "target_triple": "arm64-apple-macosx",
+ "type": "TUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-entry-linkage-missing-coalescing.json b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-entry-linkage-missing-coalescing.json
new file mode 100644
index 0000000000000..53ee3391e6e84
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-entry-linkage-missing-coalescing.json
@@ -0,0 +1,16 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "test.cpp"
+ },
+ "id_table": [],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": { "type": "External", "binding": "Strong" }
+ }
+ ],
+ "data": [],
+ "target_triple": "arm64-apple-macosx",
+ "type": "TUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-entry-missing-id.json b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-entry-missing-id.json
index 597db631bfd60..bde81305ab03c 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-entry-missing-id.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-entry-missing-id.json
@@ -6,7 +6,7 @@
"id_table": [],
"linkage_table": [
{
- "linkage": { "type": "External" }
+ "linkage": { "binding": "Strong", "coalescing": "None", "definition": "Definition", "type": "External", "visibility": "Default" }
}
],
"data": [],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-extra-id.json b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-extra-id.json
index e0d15d64a0d26..c766e66af2653 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-extra-id.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/linkage-table-extra-id.json
@@ -7,7 +7,7 @@
"linkage_table": [
{
"id": 0,
- "linkage": { "type": "External" }
+ "linkage": { "binding": "Strong", "coalescing": "None", "definition": "Definition", "type": "External", "visibility": "Default" }
}
],
"data": [],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-linkage-external.json b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-linkage-external.json
index bb26bc0de603a..7d9e7a3bed850 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-linkage-external.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-linkage-external.json
@@ -13,7 +13,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-linkage-internal.json b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-linkage-internal.json
index f3aa427a9e239..d17cf2b3b1cb0 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-linkage-internal.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-linkage-internal.json
@@ -13,7 +13,11 @@
{
"id": 0,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-linkage-multiple.json b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-linkage-multiple.json
index c22c932ad2882..7cf69c780eb61 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-linkage-multiple.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-linkage-multiple.json
@@ -27,19 +27,31 @@
{
"id": 0,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-linkage-none.json b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-linkage-none.json
index 495890dc3cc44..677314dfc77ea 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-linkage-none.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-linkage-none.json
@@ -13,7 +13,11 @@
{
"id": 0,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-two-summary-types.json b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-two-summary-types.json
index 080afccbdf482..83d57da97567c 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-two-summary-types.json
+++ b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/Inputs/rt-two-summary-types.json
@@ -173,31 +173,51 @@
{
"id": 0,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 3,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 4,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/linkage.test b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/linkage.test
index a4ec862ff5f86..8d2a707bbdbda 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/TUSummary/linkage.test
+++ b/clang/test/Analysis/Scalable/ssaf-format/TUSummary/linkage.test
@@ -28,6 +28,28 @@
// LINKAGE-TABLE-ENTRY-LINKAGE-INVALID-TYPE-NEXT: reading EntityLinkageType from field 'type'
// LINKAGE-TABLE-ENTRY-LINKAGE-INVALID-TYPE-NEXT: invalid EntityLinkageType value 'invalid_type' for field 'type'
+// 'coalescing' is required, like every other EntityLinkage field.
+// RUN: not clang-ssaf-format --type tu %S/Inputs/linkage-table-entry-linkage-missing-coalescing.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=LINKAGE-MISSING-COALESCING
+// RUN: not clang-ssaf-format --type tu --encoding %S/Inputs/linkage-table-entry-linkage-missing-coalescing.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=LINKAGE-MISSING-COALESCING
+// LINKAGE-MISSING-COALESCING: clang-ssaf-format: error: reading TUSummary from file '{{.*}}linkage-table-entry-linkage-missing-coalescing.json'
+// LINKAGE-MISSING-COALESCING-NEXT: reading LinkageTable from field 'linkage_table'
+// LINKAGE-MISSING-COALESCING-NEXT: reading LinkageTable entry from index '0'
+// LINKAGE-MISSING-COALESCING-NEXT: reading EntityLinkage from field 'linkage'
+// LINKAGE-MISSING-COALESCING-NEXT: failed to read EntityCoalescing from field 'coalescing': expected JSON string
+
+// RUN: not clang-ssaf-format --type tu %S/Inputs/linkage-table-entry-linkage-invalid-coalescing.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=LINKAGE-INVALID-COALESCING
+// RUN: not clang-ssaf-format --type tu --encoding %S/Inputs/linkage-table-entry-linkage-invalid-coalescing.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=LINKAGE-INVALID-COALESCING
+// LINKAGE-INVALID-COALESCING: clang-ssaf-format: error: reading TUSummary from file '{{.*}}linkage-table-entry-linkage-invalid-coalescing.json'
+// LINKAGE-INVALID-COALESCING-NEXT: reading LinkageTable from field 'linkage_table'
+// LINKAGE-INVALID-COALESCING-NEXT: reading LinkageTable entry from index '0'
+// LINKAGE-INVALID-COALESCING-NEXT: reading EntityLinkage from field 'linkage'
+// LINKAGE-INVALID-COALESCING-NEXT: reading EntityCoalescing from field 'coalescing'
+// LINKAGE-INVALID-COALESCING-NEXT: invalid EntityCoalescing value 'invalid_coalescing' for field 'coalescing'
+
// ============================================================================
// linkageTableEntryFromJSON() errors
// ============================================================================
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/carry-coff.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/carry-coff.json
new file mode 100644
index 0000000000000..eaf5c1020ddcd
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/carry-coff.json
@@ -0,0 +1,115 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "carry.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at local_fn"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at static_fn"
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at inline_fn"
+ }
+ },
+ {
+ "id": 3,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at weak_fn"
+ }
+ },
+ {
+ "id": 4,
+ "name": {
+ "suffix": "",
+ "usr": "c:@V at tentative_var"
+ }
+ },
+ {
+ "id": 5,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at extern_decl"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "binding": "Undefined",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Hidden"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "ODR",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 3,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 4,
+ "linkage": {
+ "binding": "Common",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 5,
+ "linkage": {
+ "binding": "Undefined",
+ "coalescing": "None",
+ "definition": "Declaration",
+ "type": "External",
+ "visibility": "Default"
+ }
+ }
+ ],
+ "data": [],
+ "target_triple": "x86_64-pc-windows-msvc",
+ "type": "TUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/carry-elf.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/carry-elf.json
new file mode 100644
index 0000000000000..579009a10a966
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/carry-elf.json
@@ -0,0 +1,115 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "carry.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at local_fn"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at static_fn"
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at inline_fn"
+ }
+ },
+ {
+ "id": 3,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at weak_fn"
+ }
+ },
+ {
+ "id": 4,
+ "name": {
+ "suffix": "",
+ "usr": "c:@V at tentative_var"
+ }
+ },
+ {
+ "id": 5,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at extern_decl"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "binding": "Undefined",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Hidden"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "ODR",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 3,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 4,
+ "linkage": {
+ "binding": "Common",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 5,
+ "linkage": {
+ "binding": "Undefined",
+ "coalescing": "None",
+ "definition": "Declaration",
+ "type": "External",
+ "visibility": "Default"
+ }
+ }
+ ],
+ "data": [],
+ "target_triple": "x86_64-unknown-linux-gnu",
+ "type": "TUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/carry-macho.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/carry-macho.json
new file mode 100644
index 0000000000000..a9a7eedcdf7e2
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/carry-macho.json
@@ -0,0 +1,115 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "carry.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at local_fn"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at static_fn"
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at inline_fn"
+ }
+ },
+ {
+ "id": 3,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at weak_fn"
+ }
+ },
+ {
+ "id": 4,
+ "name": {
+ "suffix": "",
+ "usr": "c:@V at tentative_var"
+ }
+ },
+ {
+ "id": 5,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at extern_decl"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "binding": "Undefined",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Hidden"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "ODR",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 3,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 4,
+ "linkage": {
+ "binding": "Common",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 5,
+ "linkage": {
+ "binding": "Undefined",
+ "coalescing": "None",
+ "definition": "Declaration",
+ "type": "External",
+ "visibility": "Default"
+ }
+ }
+ ],
+ "data": [],
+ "target_triple": "arm64-apple-macosx",
+ "type": "TUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-lhs-coff.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-lhs-coff.json
new file mode 100644
index 0000000000000..24bbf21df44b9
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-lhs-coff.json
@@ -0,0 +1,139 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "join_lhs.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at def_vs_decl"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at decl_vs_def"
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at weaker_incumbent"
+ }
+ },
+ {
+ "id": 3,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at stronger_incumbent"
+ }
+ },
+ {
+ "id": 4,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at equal_weak"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "binding": "Undefined",
+ "coalescing": "None",
+ "definition": "Declaration",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 3,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 4,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ }
+ ],
+ "data": [
+ {
+ "summary_name": "Analysis1",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 1,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 2,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 3,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 4,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": []
+ }
+ }
+ ]
+ }
+ ],
+ "target_triple": "x86_64-pc-windows-msvc",
+ "type": "TUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-lhs-elf.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-lhs-elf.json
new file mode 100644
index 0000000000000..c014546d5ece4
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-lhs-elf.json
@@ -0,0 +1,139 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "join_lhs.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at def_vs_decl"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at decl_vs_def"
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at weaker_incumbent"
+ }
+ },
+ {
+ "id": 3,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at stronger_incumbent"
+ }
+ },
+ {
+ "id": 4,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at equal_weak"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "binding": "Undefined",
+ "coalescing": "None",
+ "definition": "Declaration",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 3,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 4,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ }
+ ],
+ "data": [
+ {
+ "summary_name": "Analysis1",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 1,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 2,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 3,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 4,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": []
+ }
+ }
+ ]
+ }
+ ],
+ "target_triple": "x86_64-unknown-linux-gnu",
+ "type": "TUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-lhs-macho.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-lhs-macho.json
new file mode 100644
index 0000000000000..d30ce015bd919
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-lhs-macho.json
@@ -0,0 +1,139 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "join_lhs.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at def_vs_decl"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at decl_vs_def"
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at weaker_incumbent"
+ }
+ },
+ {
+ "id": 3,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at stronger_incumbent"
+ }
+ },
+ {
+ "id": 4,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at equal_weak"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "binding": "Undefined",
+ "coalescing": "None",
+ "definition": "Declaration",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 3,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 4,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ }
+ ],
+ "data": [
+ {
+ "summary_name": "Analysis1",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 1,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 2,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 3,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 4,
+ "entity_summary": {
+ "call_count": 1,
+ "callees": []
+ }
+ }
+ ]
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "TUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-rhs-coff.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-rhs-coff.json
new file mode 100644
index 0000000000000..4f0a23fe2ac94
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-rhs-coff.json
@@ -0,0 +1,139 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "join_rhs.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at def_vs_decl"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at decl_vs_def"
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at weaker_incumbent"
+ }
+ },
+ {
+ "id": 3,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at stronger_incumbent"
+ }
+ },
+ {
+ "id": 4,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at equal_weak"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "binding": "Undefined",
+ "coalescing": "None",
+ "definition": "Declaration",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 3,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 4,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ }
+ ],
+ "data": [
+ {
+ "summary_name": "Analysis1",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 1,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 2,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 3,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 4,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": []
+ }
+ }
+ ]
+ }
+ ],
+ "target_triple": "x86_64-pc-windows-msvc",
+ "type": "TUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-rhs-elf.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-rhs-elf.json
new file mode 100644
index 0000000000000..6b426e06dd99c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-rhs-elf.json
@@ -0,0 +1,139 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "join_rhs.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at def_vs_decl"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at decl_vs_def"
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at weaker_incumbent"
+ }
+ },
+ {
+ "id": 3,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at stronger_incumbent"
+ }
+ },
+ {
+ "id": 4,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at equal_weak"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "binding": "Undefined",
+ "coalescing": "None",
+ "definition": "Declaration",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 3,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 4,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ }
+ ],
+ "data": [
+ {
+ "summary_name": "Analysis1",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 1,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 2,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 3,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 4,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": []
+ }
+ }
+ ]
+ }
+ ],
+ "target_triple": "x86_64-unknown-linux-gnu",
+ "type": "TUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-rhs-macho.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-rhs-macho.json
new file mode 100644
index 0000000000000..442e1dd56ee16
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/join-rhs-macho.json
@@ -0,0 +1,139 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "join_rhs.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at def_vs_decl"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at decl_vs_def"
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at weaker_incumbent"
+ }
+ },
+ {
+ "id": 3,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at stronger_incumbent"
+ }
+ },
+ {
+ "id": 4,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at equal_weak"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "binding": "Undefined",
+ "coalescing": "None",
+ "definition": "Declaration",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 3,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 4,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ }
+ ],
+ "data": [
+ {
+ "summary_name": "Analysis1",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 1,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 2,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 3,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": []
+ }
+ },
+ {
+ "entity_id": 4,
+ "entity_summary": {
+ "call_count": 2,
+ "callees": []
+ }
+ }
+ ]
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "TUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-1.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-1.json
index 0fe78af8c28bb..6d789111b5e3a 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-1.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-1.json
@@ -51,37 +51,61 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
},
{
"id": 3,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 4,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 5,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-2.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-2.json
index 48a7fbef570e6..35597bd43dc55 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-2.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-2.json
@@ -51,37 +51,61 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Undefined",
+ "coalescing": "None",
+ "definition": "Declaration",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
},
{
"id": 3,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 4,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 5,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-multikey.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-multikey.json
index a66be3036e31f..d0e9b72ba0603 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-multikey.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-multikey.json
@@ -16,7 +16,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-ref.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-ref.json
index 535cc18dc10d5..e41e73e7f7e29 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-ref.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-ref.json
@@ -16,7 +16,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-value.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-value.json
index 1fd3d1aeb0ac9..2c236824219cc 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-value.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-invalid-entity-id-value.json
@@ -16,7 +16,11 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-redefines-shared-ext.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-redefines-shared-ext.json
new file mode 100644
index 0000000000000..71c76bc0ac0f7
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-redefines-shared-ext.json
@@ -0,0 +1,43 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "tu3.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at shared_ext#"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ }
+ ],
+ "data": [
+ {
+ "summary_name": "Analysis1",
+ "summary_data": [
+ {
+ "entity_id": 0,
+ "entity_summary": {
+ "call_count": 0,
+ "callees": []
+ }
+ }
+ ]
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "TUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/vis-lhs-elf.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/vis-lhs-elf.json
new file mode 100644
index 0000000000000..21e968734f513
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/vis-lhs-elf.json
@@ -0,0 +1,64 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "vis_lhs.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at hidden_vs_default"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at default_vs_hidden"
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at both_hidden"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Hidden"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Hidden"
+ }
+ }
+ ],
+ "data": [],
+ "target_triple": "x86_64-unknown-linux-gnu",
+ "type": "TUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/vis-lhs-macho.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/vis-lhs-macho.json
new file mode 100644
index 0000000000000..c163192018c63
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/vis-lhs-macho.json
@@ -0,0 +1,64 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "vis_lhs.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at hidden_vs_default"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at default_vs_hidden"
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at both_hidden"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Hidden"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Hidden"
+ }
+ }
+ ],
+ "data": [],
+ "target_triple": "arm64-apple-macosx",
+ "type": "TUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/vis-rhs-elf.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/vis-rhs-elf.json
new file mode 100644
index 0000000000000..550532d8a24e3
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/vis-rhs-elf.json
@@ -0,0 +1,64 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "vis_rhs.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at hidden_vs_default"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at default_vs_hidden"
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at both_hidden"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Hidden"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Hidden"
+ }
+ }
+ ],
+ "data": [],
+ "target_triple": "x86_64-unknown-linux-gnu",
+ "type": "TUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/vis-rhs-macho.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/vis-rhs-macho.json
new file mode 100644
index 0000000000000..055d416f01f87
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/vis-rhs-macho.json
@@ -0,0 +1,64 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "vis_rhs.cpp"
+ },
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at hidden_vs_default"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at default_vs_hidden"
+ }
+ },
+ {
+ "id": 2,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at both_hidden"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Hidden"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "binding": "Weak",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Hidden"
+ }
+ }
+ ],
+ "data": [],
+ "target_triple": "arm64-apple-macosx",
+ "type": "TUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libtwo.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libtwo.json
index 7c4d80720ec57..41bfda8079603 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libtwo.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libtwo.json
@@ -271,37 +271,61 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
},
{
"id": 3,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 4,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 5,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
}
],
@@ -589,37 +613,61 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Undefined",
+ "coalescing": "None",
+ "definition": "Declaration",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
},
{
"id": 3,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 4,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 5,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1+2.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1+2.json
index 663f5dd814534..1ffa8c39d1ed7 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1+2.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1+2.json
@@ -579,67 +579,111 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
},
{
"id": 3,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 4,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 5,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
},
{
"id": 6,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 7,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
},
{
"id": 8,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 9,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 10,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1.json
index 4fd99f978148b..b39558d8ab167 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-1.json
@@ -321,37 +321,61 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
},
{
"id": 3,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 4,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 5,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-2.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-2.json
index e7fd3f39da0c1..40d4eb1fd389e 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-2.json
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lu-2.json
@@ -327,37 +327,61 @@
{
"id": 0,
"linkage": {
- "type": "External"
+ "binding": "Undefined",
+ "coalescing": "None",
+ "definition": "Declaration",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 1,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 2,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
},
{
"id": 3,
"linkage": {
- "type": "External"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "External",
+ "visibility": "Default"
}
},
{
"id": 4,
"linkage": {
- "type": "Internal"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "Internal",
+ "visibility": "Default"
}
},
{
"id": 5,
"linkage": {
- "type": "None"
+ "binding": "Strong",
+ "coalescing": "None",
+ "definition": "Definition",
+ "type": "None",
+ "visibility": "Default"
}
}
],
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/help.test b/clang/test/Analysis/Scalable/ssaf-linker/help.test
index 7b0c21f066016..d4d1c4957c824 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/help.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/help.test
@@ -21,8 +21,17 @@
// CHECK-NEXT: --help-list - Display list of available options (--help-list-hidden for more)
// CHECK-NEXT: --help-list-hidden - Display list of all available options
// CHECK-NEXT: -o <path> - Output file path
+// CHECK-NEXT: --odr-mismatch=<value> - How to report summary data that differs between two definitions required to be identical, such as an inline function
+// CHECK-NEXT: =ignore - Do not check (default: comparing costs an extra patch of every displaced encoding)
+// CHECK-NEXT: =warn - Report each mismatch and keep linking
+// CHECK-NEXT: =error - Fail the link on the first mismatch
// CHECK-NEXT: --print-all-options - Print all option values after command line parsing
// CHECK-NEXT: --print-options - Print non-default options after command line parsing
// CHECK-NEXT: --time - Enable timing
+// CHECK-NEXT: --unresolved-symbols=<value> - How to report entities that no linked translation unit defines
+// CHECK-NEXT: =ignore - Do not check (default: a link unit is usually an intermediate artifact, where references to other link units are expected)
+// CHECK-NEXT: =warn - Report each undefined entity and keep linking
+// CHECK-NEXT: =error - Fail the link if any entity is left undefined
// CHECK-NEXT: --verbose - Enable verbose output
// CHECK-NEXT: --version - Display the version of this program
+// CHECK-NEXT: --warn-on-multiple-definitions - Warn instead of failing when an external entity is defined by more than one translation unit
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/linkage-carry-through.test b/clang/test/Analysis/Scalable/ssaf-linker/linkage-carry-through.test
new file mode 100644
index 0000000000000..293cc8025aa32
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/linkage-carry-through.test
@@ -0,0 +1,70 @@
+// Verifies that every linkage value survives read -> link -> write, and that
+// values a target cannot express are coerced.
+//
+// Fixtures are per-target because the link unit's triple selects the
+// resolution rules; see docs/ssaf-linker-{elf,macho,coff}-behavior.md.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// The id_table is emitted first and sorted by USR, which fixes the ids the
+// linkage_table then uses:
+//
+// 0 extern_decl 1 inline_fn 2 local_fn
+// 3 static_fn 4 weak_fn 5 tentative_var
+
+// ELF and Mach-O carry every value through untouched.
+// RUN: clang-ssaf-linker %S/Inputs/carry-elf.json -o %t/carry-elf.json
+// RUN: cat %t/carry-elf.json | FileCheck %s --check-prefix=KEEPS-HIDDEN
+// RUN: clang-ssaf-linker %S/Inputs/carry-macho.json -o %t/carry-macho.json
+// RUN: cat %t/carry-macho.json | FileCheck %s --check-prefix=KEEPS-HIDDEN
+
+// KEEPS-HIDDEN: "linkage_table": [
+
+// extern_decl: a declaration defines nothing and coalesces nothing.
+// KEEPS-HIDDEN: "id": 0,
+// KEEPS-HIDDEN-NEXT: "linkage": {
+// KEEPS-HIDDEN-NEXT: "binding": "Undefined",
+// KEEPS-HIDDEN-NEXT: "coalescing": "None",
+// KEEPS-HIDDEN-NEXT: "definition": "Declaration",
+
+// inline_fn: strong at the source level, plus the ODR guarantee. The two are
+// independent axes, which is why the binding is not "Weak" here even though
+// ELF and Mach-O both emit a weak symbol for it.
+// KEEPS-HIDDEN: "id": 1,
+// KEEPS-HIDDEN-NEXT: "linkage": {
+// KEEPS-HIDDEN-NEXT: "binding": "Strong",
+// KEEPS-HIDDEN-NEXT: "coalescing": "ODR",
+// KEEPS-HIDDEN-NEXT: "definition": "Definition",
+// KEEPS-HIDDEN-NEXT: "type": "External",
+// KEEPS-HIDDEN-NEXT: "visibility": "Default"
+
+// static_fn: internal linkage keeps its Hidden visibility.
+// KEEPS-HIDDEN: "id": 3,
+// KEEPS-HIDDEN-NEXT: "linkage": {
+// KEEPS-HIDDEN-NEXT: "binding": "Strong",
+// KEEPS-HIDDEN-NEXT: "coalescing": "None",
+// KEEPS-HIDDEN-NEXT: "definition": "Definition",
+// KEEPS-HIDDEN-NEXT: "type": "Internal",
+// KEEPS-HIDDEN-NEXT: "visibility": "Hidden"
+
+// tentative_var: a C tentative definition is a common symbol.
+// KEEPS-HIDDEN: "id": 5,
+// KEEPS-HIDDEN-NEXT: "linkage": {
+// KEEPS-HIDDEN-NEXT: "binding": "Common",
+// KEEPS-HIDDEN-NEXT: "coalescing": "None",
+// KEEPS-HIDDEN-NEXT: "definition": "Definition",
+
+// COFF has no visibility at all: clang accepts the attribute silently and
+// drops it at emission, so the linker coerces it rather than rejecting a
+// summary that portable source legitimately produces (COFF doc §7).
+// RUN: clang-ssaf-linker %S/Inputs/carry-coff.json -o %t/carry-coff.json
+// RUN: cat %t/carry-coff.json | FileCheck %s --check-prefix=COERCES-HIDDEN
+// COERCES-HIDDEN: "linkage_table": [
+// COERCES-HIDDEN: "id": 3,
+// COERCES-HIDDEN-NEXT: "linkage": {
+// COERCES-HIDDEN-NEXT: "binding": "Strong",
+// COERCES-HIDDEN-NEXT: "coalescing": "None",
+// COERCES-HIDDEN-NEXT: "definition": "Definition",
+// COERCES-HIDDEN-NEXT: "type": "Internal",
+// COERCES-HIDDEN-NEXT: "visibility": "Default"
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/linkage-join-visibility.test b/clang/test/Analysis/Scalable/ssaf-linker/linkage-join-visibility.test
new file mode 100644
index 0000000000000..5e5aa1c1ed1fb
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/linkage-join-visibility.test
@@ -0,0 +1,62 @@
+// Verifies the visibility merge, which is the rule where ELF and Mach-O take
+// opposite directions, and the hidden-blocks-cross-link-unit-resolution
+// behaviour that follows from it.
+//
+// Each fixture pairs two weak definitions of the same entity so that the merge
+// is exercised without triggering a multiple-definition error. Both bindings
+// are weak, so neither displaces the other and only the visibilities differ.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// ELF merges to the MOST restrictive visibility: a hidden occurrence makes the
+// merged entity hidden even when the other is default, and even when the
+// hidden one is only a declaration (ELF doc §4 probe V1).
+// RUN: clang-ssaf-linker %S/Inputs/vis-lhs-elf.json %S/Inputs/vis-rhs-elf.json \
+// RUN: -o %t/vis-elf.json
+// RUN: cat %t/vis-elf.json | FileCheck %s --check-prefix=ELF
+
+// The id_table is sorted by USR, fixing the ids:
+// 0 both_hidden 1 default_vs_hidden 2 hidden_vs_default
+//
+// All three end up hidden, and are therefore demoted to Internal.
+// ELF: "linkage_table": [
+// ELF: "id": 0,
+// ELF: "linkage": {
+// ELF: "type": "Internal",
+// ELF-NEXT: "visibility": "Hidden"
+// ELF: "id": 1,
+// ELF: "linkage": {
+// ELF: "type": "Internal",
+// ELF-NEXT: "visibility": "Hidden"
+// ELF: "id": 2,
+// ELF: "linkage": {
+// ELF: "type": "Internal",
+// ELF-NEXT: "visibility": "Hidden"
+
+// Mach-O merges to the LEAST restrictive: a symbol stays private only when
+// every copy is private (Mach-O doc §2 probes W1-W3).
+// RUN: clang-ssaf-linker %S/Inputs/vis-lhs-macho.json \
+// RUN: %S/Inputs/vis-rhs-macho.json -o %t/vis-macho.json
+// RUN: cat %t/vis-macho.json | FileCheck %s --check-prefix=MACHO
+
+// The mixed pairs stay exported; only both_hidden (id 0) is demoted.
+// MACHO: "linkage_table": [
+// MACHO: "id": 0,
+// MACHO: "linkage": {
+// MACHO: "type": "Internal",
+// MACHO-NEXT: "visibility": "Hidden"
+// MACHO: "id": 1,
+// MACHO: "linkage": {
+// MACHO: "type": "External",
+// MACHO-NEXT: "visibility": "Default"
+// MACHO: "id": 2,
+// MACHO: "linkage": {
+// MACHO: "type": "External",
+// MACHO-NEXT: "visibility": "Default"
+
+// A hidden entity is demoted to Internal so that a later link stage cannot
+// resolve against it, but it keeps its name: every EntityId already patched
+// into the summary data refers to it.
+// RUN: cat %t/vis-elf.json | FileCheck %s --check-prefix=KEEPS-NAME
+// KEEPS-NAME: "usr": "c:@F at both_hidden"
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/linkage-join-winner.test b/clang/test/Analysis/Scalable/ssaf-linker/linkage-join-winner.test
new file mode 100644
index 0000000000000..1adf65168e778
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/linkage-join-winner.test
@@ -0,0 +1,74 @@
+// Verifies which occurrence's summary data survives when two TUs define the
+// same external entity, end to end through the linker.
+//
+// The `call_count` in each entity's summary is 1 in the left-hand TU and 2 in
+// the right-hand one, so the linked value names the winner. The unit tests in
+// EntityLinkerReconcileTest cover the rules themselves; this checks that the
+// decision actually reaches the output file.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// ELF and Mach-O agree on every case here. --warn-on-multiple-definitions is
+// needed only because COFF rejects the two-weak-definitions case (COFF doc §1
+// probe C4), and the same command line is used for all three targets.
+
+// RUN: clang-ssaf-linker --warn-on-multiple-definitions \
+// RUN: %S/Inputs/join-lhs-elf.json %S/Inputs/join-rhs-elf.json \
+// RUN: -o %t/join-elf.json 2>&1 | FileCheck %s --check-prefix=NO-CONFLICT \
+// RUN: --allow-empty
+// RUN: cat %t/join-elf.json | FileCheck %s
+
+// RUN: clang-ssaf-linker --warn-on-multiple-definitions \
+// RUN: %S/Inputs/join-lhs-macho.json %S/Inputs/join-rhs-macho.json \
+// RUN: -o %t/join-macho.json 2>&1 | FileCheck %s --check-prefix=NO-CONFLICT \
+// RUN: --allow-empty
+// RUN: cat %t/join-macho.json | FileCheck %s
+
+// ELF and Mach-O accept two weak definitions silently.
+// NO-CONFLICT-NOT: multiple definition
+
+// The id_table is sorted by USR, which fixes the entity_ids the summary data
+// then uses:
+//
+// 0 decl_vs_def 1 def_vs_decl 2 equal_weak
+// 3 stronger_incumbent 4 weaker_incumbent
+
+// A definition beats a declaration, whichever side it is on. decl_vs_def is a
+// declaration on the left and a definition on the right, so the right wins;
+// def_vs_decl is the mirror image.
+// CHECK: "entity_id": 0,
+// CHECK-NEXT: "entity_summary": {
+// CHECK-NEXT: "call_count": 2
+// CHECK: "entity_id": 1,
+// CHECK-NEXT: "entity_summary": {
+// CHECK-NEXT: "call_count": 1
+
+// A tie keeps the data already linked (ELF probe P4, Mach-O probe M4).
+// CHECK: "entity_id": 2,
+// CHECK-NEXT: "entity_summary": {
+// CHECK-NEXT: "call_count": 1
+
+// Among definitions the stronger binding wins, regardless of link order:
+// stronger_incumbent is strong on the left, weaker_incumbent on the right.
+// CHECK: "entity_id": 3,
+// CHECK-NEXT: "entity_summary": {
+// CHECK-NEXT: "call_count": 1
+// CHECK: "entity_id": 4,
+// CHECK-NEXT: "entity_summary": {
+// CHECK-NEXT: "call_count": 2
+
+// COFF reaches the same data selection, but rejects the two weak definitions
+// unless the multiple-definition error is downgraded, because it emulates weak
+// symbols with an alias that collides (COFF doc §1 probe C4).
+// RUN: clang-ssaf-linker --warn-on-multiple-definitions \
+// RUN: %S/Inputs/join-lhs-coff.json %S/Inputs/join-rhs-coff.json \
+// RUN: -o %t/join-coff.json 2>&1 | FileCheck %s --check-prefix=COFF-WEAK
+// RUN: cat %t/join-coff.json | FileCheck %s
+// COFF-WEAK: warning: multiple definition of EntityName(c:@F at equal_weak,
+
+// RUN: not clang-ssaf-linker \
+// RUN: %S/Inputs/join-lhs-coff.json %S/Inputs/join-rhs-coff.json \
+// RUN: -o %t/join-coff-strict.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=COFF-WEAK-FATAL
+// COFF-WEAK-FATAL: multiple definition of EntityName(c:@F at equal_weak,
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test b/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test
index 5bda8128ac0e1..144e891106ae5 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test
@@ -26,3 +26,8 @@
// RUN: | FileCheck %s --match-full-lines --check-prefix=INVALID-ID-REF
// INVALID-ID-REF: clang-ssaf-linker: error: Linking summary '{{.*}}tu-invalid-entity-id-ref.json'
// INVALID-ID-REF-NEXT: failed to patch EntityId: 'EntityId(99)' not found in entity resolution table
+
+// Two TUs defining the same external entity is a multiple definition error.
+// RUN: not clang-ssaf-linker %S/Inputs/tu-1.json %S/Inputs/tu-redefines-shared-ext.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=MULTIPLE-DEF
+// MULTIPLE-DEF: LLVM ERROR: multiple definition of EntityName(c:@F at shared_ext#, , NestedBuildNamespace([BuildNamespace(LinkUnit, lu)])) in NestedBuildNamespace([BuildNamespace(CompilationUnit, tu3.cpp)])
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/linking.test b/clang/test/Analysis/Scalable/ssaf-linker/linking.test
index a6bd0c8df22c8..ed5b4d71b8fbb 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/linking.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/linking.test
@@ -29,6 +29,16 @@
// RUN: diff %S/Outputs/lu-1+2.json %t/lu-1+2.json
// RUN: rm %t/lu-1+2.json
+// --warn-on-multiple-definitions downgrades a multiple definition (rejected by
+// default, see linking-errors.test) to a warning and keeps the first
+// definition's data.
+// RUN: clang-ssaf-linker --warn-on-multiple-definitions %S/Inputs/tu-1.json \
+// RUN: %S/Inputs/tu-redefines-shared-ext.json -o %t/lu-1.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=MULTIPLE-DEF
+// MULTIPLE-DEF: warning: multiple definition of EntityName(c:@F at shared_ext#, , NestedBuildNamespace([BuildNamespace(LinkUnit, lu-1)])) in NestedBuildNamespace([BuildNamespace(CompilationUnit, tu3.cpp)])
+// RUN: diff %S/Outputs/lu-1.json %t/lu-1.json
+// RUN: rm %t/lu-1.json
+
// Relative input path: cd to the test source directory so that
// Inputs/tu-1.json is a valid relative path.
// RUN: cd %S && clang-ssaf-linker Inputs/tu-1.json -o %t/lu-1.json
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/verbose.test b/clang/test/Analysis/Scalable/ssaf-linker/verbose.test
index d731748ad7547..500300f607b8d 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/verbose.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/verbose.test
@@ -5,16 +5,19 @@
// RUN: clang-ssaf-linker --verbose %S/Inputs/tu-1.json %S/Inputs/tu-2.json -o %t/lu-1+2.json 2>&1 \
// RUN: | FileCheck %s --match-full-lines
+// Every summary is read before the linker is constructed, because the link
+// unit's target triple comes from its inputs and selects the resolution rules.
// CHECK: note: - Linking started.
// CHECK-NEXT: note: - Validating input.
// CHECK-NEXT: note: - Validated output summary path '{{.*}}lu-1+2.json'.
// CHECK-NEXT: note: - Validated 2 input summary paths.
// CHECK-NEXT: note: - Linking input.
+// CHECK-NEXT: note: - [1/2] Reading '{{.*}}tu-1.json'.
+// CHECK-NEXT: note: - [2/2] Reading '{{.*}}tu-2.json'.
// CHECK-NEXT: note: - Constructing linker.
+// CHECK-NEXT: note: - Link unit targets 'arm64-apple-macosx'.
// CHECK-NEXT: note: - Linking summaries.
-// CHECK-NEXT: note: - [1/2] Reading '{{.*}}tu-1.json'.
// CHECK-NEXT: note: - [1/2] Linking '{{.*}}tu-1.json'.
-// CHECK-NEXT: note: - [2/2] Reading '{{.*}}tu-2.json'.
// CHECK-NEXT: note: - [2/2] Linking '{{.*}}tu-2.json'.
// CHECK-NEXT: note: - Writing output summary to '{{.*}}lu-1+2.json'.
// CHECK-NEXT: note: - Linking finished.
diff --git a/clang/tools/clang-ssaf-linker/SSAFLinker.cpp b/clang/tools/clang-ssaf-linker/SSAFLinker.cpp
index 9d72e9f338328..0914ba20b6ef6 100644
--- a/clang/tools/clang-ssaf-linker/SSAFLinker.cpp
+++ b/clang/tools/clang-ssaf-linker/SSAFLinker.cpp
@@ -77,6 +77,42 @@ cl::opt<bool> Time("time", cl::desc("Enable timing"), cl::init(false),
cl::sub(cl::SubCommand::getTopLevel()),
cl::sub(StaticLibraryCmd), cl::sub(MultiArchCmd));
+cl::opt<bool> WarnOnMultipleDefinitions(
+ "warn-on-multiple-definitions",
+ cl::desc("Warn instead of failing when an external entity is defined by "
+ "more than one translation unit"),
+ cl::init(false), cl::cat(SsafLinkerCategory),
+ cl::sub(cl::SubCommand::getTopLevel()));
+
+cl::opt<UnresolvedPolicy> UnresolvedSymbols(
+ "unresolved-symbols",
+ cl::desc("How to report entities that no linked translation unit defines"),
+ cl::init(UnresolvedPolicy::Ignore), cl::cat(SsafLinkerCategory),
+ cl::sub(cl::SubCommand::getTopLevel()),
+ cl::values(
+ clEnumValN(UnresolvedPolicy::Ignore, "ignore",
+ "Do not check (default: a link unit is usually an "
+ "intermediate artifact, where references to other link "
+ "units are expected)"),
+ clEnumValN(UnresolvedPolicy::Warn, "warn",
+ "Report each undefined entity and keep linking"),
+ clEnumValN(UnresolvedPolicy::Error, "error",
+ "Fail the link if any entity is left undefined")));
+
+cl::opt<ODRMismatchPolicy> ODRMismatch(
+ "odr-mismatch",
+ cl::desc("How to report summary data that differs between two definitions "
+ "required to be identical, such as an inline function"),
+ cl::init(ODRMismatchPolicy::Ignore), cl::cat(SsafLinkerCategory),
+ cl::sub(cl::SubCommand::getTopLevel()),
+ cl::values(clEnumValN(ODRMismatchPolicy::Ignore, "ignore",
+ "Do not check (default: comparing costs an extra "
+ "patch of every displaced encoding)"),
+ clEnumValN(ODRMismatchPolicy::Warn, "warn",
+ "Report each mismatch and keep linking"),
+ clEnumValN(ODRMismatchPolicy::Error, "error",
+ "Fail the link on the first mismatch")));
+
// The `static-library` subcommand's verb positional. Declared BEFORE
// StaticLibraryInputs so cl-lib binds argv[0] under the subcommand to the
// verb rather than to the greedy input list.
@@ -207,50 +243,55 @@ void runLink(llvm::TimerGroup &TG) {
}
info(Verbose, 1, "Linking input.");
- info(Verbose, 2, "Constructing linker.");
-
- // TODO: The linker currently uses a hardcoded target triple. Architecture
- // tracking in the linker will be handled properly in a separate PR.
- EntityLinker EL(llvm::Triple("arm64-apple-macosx"),
- NestedBuildNamespace(BuildNamespace(
- BuildNamespaceKind::LinkUnit, LI.LinkUnitName)));
llvm::Timer TRead("read", "Read Summaries", TG);
llvm::Timer TLink("link", "Link Summaries", TG);
llvm::Timer TWrite("write", "Write Summary", TG);
- info(Verbose, 2, "Linking summaries.");
-
+ // Read every summary before constructing the linker: the link unit's target
+ // triple comes from its inputs, and it selects the resolution rules to
+ // emulate. EntityLinker rejects any later input that disagrees.
+ std::vector<std::unique_ptr<TUSummaryEncoding>> Summaries;
for (auto [Index, InputFile] : llvm::enumerate(LI.InputFiles)) {
- std::unique_ptr<TUSummaryEncoding> Summary;
+ info(Verbose, 3, "[{0}/{1}] Reading '{2}'.", (Index + 1),
+ LI.InputFiles.size(), InputFile.Path);
- {
- info(Verbose, 3, "[{0}/{1}] Reading '{2}'.", (Index + 1),
- LI.InputFiles.size(), InputFile.Path);
+ llvm::TimeRegion _(Time ? &TRead : nullptr);
- llvm::TimeRegion _(Time ? &TRead : nullptr);
+ auto ExpectedSummaryEncoding =
+ InputFile.Format->readTUSummaryEncoding(InputFile.Path);
+ if (!ExpectedSummaryEncoding) {
+ fail(ExpectedSummaryEncoding.takeError());
+ }
- auto ExpectedSummaryEncoding =
- InputFile.Format->readTUSummaryEncoding(InputFile.Path);
- if (!ExpectedSummaryEncoding) {
- fail(ExpectedSummaryEncoding.takeError());
- }
+ Summaries.push_back(std::make_unique<TUSummaryEncoding>(
+ std::move(*ExpectedSummaryEncoding)));
+ }
- Summary = std::make_unique<TUSummaryEncoding>(
- std::move(*ExpectedSummaryEncoding));
- }
+ info(Verbose, 2, "Constructing linker.");
+
+ // cl::OneOrMore on the input list guarantees at least one summary.
+ const llvm::Triple &TargetTriple = Summaries.front()->getTargetTriple();
+ info(Verbose, 3, "Link unit targets '{0}'.", TargetTriple.str());
+
+ EntityLinker EL(TargetTriple,
+ NestedBuildNamespace(BuildNamespace(
+ BuildNamespaceKind::LinkUnit, LI.LinkUnitName)),
+ WarnOnMultipleDefinitions, UnresolvedSymbols, ODRMismatch);
+
+ info(Verbose, 2, "Linking summaries.");
- {
- info(Verbose, 3, "[{0}/{1}] Linking '{2}'.", (Index + 1),
- LI.InputFiles.size(), InputFile.Path);
+ for (auto [Index, Summary] : llvm::enumerate(Summaries)) {
+ info(Verbose, 3, "[{0}/{1}] Linking '{2}'.", (Index + 1),
+ LI.InputFiles.size(), LI.InputFiles[Index].Path);
- llvm::TimeRegion _(Time ? &TLink : nullptr);
+ llvm::TimeRegion _(Time ? &TLink : nullptr);
- if (auto Err = EL.link(std::move(Summary))) {
- fail(ErrorBuilder::wrap(std::move(Err))
- .context(LocalErrorMessages::LinkingSummary, InputFile.Path)
- .build());
- }
+ if (auto Err = EL.link(std::move(Summary))) {
+ fail(ErrorBuilder::wrap(std::move(Err))
+ .context(LocalErrorMessages::LinkingSummary,
+ LI.InputFiles[Index].Path)
+ .build());
}
}
diff --git a/clang/unittests/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowWPATest.cpp b/clang/unittests/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowWPATest.cpp
index 8c43ead9750ca..ca37e8a9ce423 100644
--- a/clang/unittests/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowWPATest.cpp
+++ b/clang/unittests/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowWPATest.cpp
@@ -33,8 +33,11 @@ namespace {
class PointerFlowWPATest : public TestFixture {
protected:
- static constexpr EntityLinkage ExternalLinkage =
- EntityLinkage(EntityLinkageType::External);
+ static constexpr EntityLinkage ExternalLinkage = EntityLinkage(
+ EntityLinkageType::External, clang::ssaf::EntityBinding::Strong,
+ clang::ssaf::EntityCoalescing::None,
+ clang::ssaf::EntityVisibility::Default,
+ clang::ssaf::EntityDefinitionKind::Definition);
std::unique_ptr<LUSummary> makeLUSummary() {
NestedBuildNamespace NS(
diff --git a/clang/unittests/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageWPATest.cpp b/clang/unittests/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageWPATest.cpp
index 448f838297e8f..24ac2ac3fc51d 100644
--- a/clang/unittests/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageWPATest.cpp
+++ b/clang/unittests/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageWPATest.cpp
@@ -34,8 +34,11 @@ namespace {
class UnsafeBufferUsageWPATest : public TestFixture {
protected:
- static constexpr EntityLinkage ExternalLinkage =
- EntityLinkage(EntityLinkageType::External);
+ static constexpr EntityLinkage ExternalLinkage = EntityLinkage(
+ EntityLinkageType::External, clang::ssaf::EntityBinding::Strong,
+ clang::ssaf::EntityCoalescing::None,
+ clang::ssaf::EntityVisibility::Default,
+ clang::ssaf::EntityDefinitionKind::Definition);
std::unique_ptr<LUSummary> makeLUSummary() {
NestedBuildNamespace NS(
diff --git a/clang/unittests/ScalableStaticAnalysis/EntityLinkageTest.cpp b/clang/unittests/ScalableStaticAnalysis/EntityLinkageTest.cpp
index ccf38ec8d93fb..ed477fa6e6498 100644
--- a/clang/unittests/ScalableStaticAnalysis/EntityLinkageTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysis/EntityLinkageTest.cpp
@@ -12,8 +12,12 @@
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"
+using clang::ssaf::EntityBinding;
+using clang::ssaf::EntityCoalescing;
+using clang::ssaf::EntityDefinitionKind;
using clang::ssaf::EntityLinkage;
using clang::ssaf::EntityLinkageType;
+using clang::ssaf::EntityVisibility;
namespace {
@@ -21,44 +25,68 @@ constexpr inline auto None = EntityLinkageType::None;
constexpr inline auto Internal = EntityLinkageType::Internal;
constexpr inline auto External = EntityLinkageType::External;
-TEST(EntityLinkageTest, Constructor) {
- EntityLinkage NoneLinkage(None);
- EntityLinkage InternalLinkage(Internal);
- EntityLinkage ExternalLinkage(External);
+// Builds an EntityLinkage with the given linkage type and fixed defaults for
+// the remaining properties (an ordinary strong external definition).
+constexpr EntityLinkage mk(EntityLinkageType LT) {
+ return EntityLinkage(LT, EntityBinding::Strong, EntityCoalescing::None,
+ EntityVisibility::Default,
+ EntityDefinitionKind::Definition);
+}
- EXPECT_EQ(NoneLinkage.getLinkage(), None);
- EXPECT_EQ(InternalLinkage.getLinkage(), Internal);
- EXPECT_EQ(ExternalLinkage.getLinkage(), External);
+TEST(EntityLinkageTest, Constructor) {
+ EntityLinkage L(External, EntityBinding::Weak, EntityCoalescing::ODR,
+ EntityVisibility::Hidden, EntityDefinitionKind::Declaration);
+ EXPECT_EQ(L.getLinkage(), External);
}
TEST(EntityLinkageTest, CopyConstructor) {
- EntityLinkage Original(External);
+ EntityLinkage Original = mk(External);
EntityLinkage Copy = Original;
- EXPECT_EQ(Copy.getLinkage(), External);
- EXPECT_EQ(Copy.getLinkage(), Original.getLinkage());
+ EXPECT_EQ(Copy, Original);
}
TEST(EntityLinkageTest, AssignmentOperator) {
- EntityLinkage Linkage1(None);
- EntityLinkage Linkage2(External);
+ EntityLinkage Linkage1 = mk(None);
+ EntityLinkage Linkage2 = mk(External);
Linkage1 = Linkage2;
- EXPECT_EQ(Linkage1.getLinkage(), External);
- EXPECT_EQ(Linkage1.getLinkage(), Linkage2.getLinkage());
+ EXPECT_EQ(Linkage1, Linkage2);
}
TEST(EntityLinkageTest, EqualityOperatorReflexive) {
- EXPECT_EQ(EntityLinkage(None), EntityLinkage(None));
- EXPECT_EQ(EntityLinkage(Internal), EntityLinkage(Internal));
- EXPECT_EQ(EntityLinkage(External), EntityLinkage(External));
+ EXPECT_EQ(mk(None), mk(None));
+ EXPECT_EQ(mk(Internal), mk(Internal));
+ EXPECT_EQ(mk(External), mk(External));
}
TEST(EntityLinkageTest, EqualityOperatorDistinct) {
- EXPECT_NE(EntityLinkage(None), EntityLinkage(Internal));
- EXPECT_NE(EntityLinkage(None), EntityLinkage(External));
- EXPECT_NE(EntityLinkage(Internal), EntityLinkage(External));
+ EXPECT_NE(mk(None), mk(Internal));
+ EXPECT_NE(mk(None), mk(External));
+ EXPECT_NE(mk(Internal), mk(External));
+}
+
+TEST(EntityLinkageTest, EqualityConsidersAllProperties) {
+ const EntityLinkage Base(External, EntityBinding::Strong,
+ EntityCoalescing::None, EntityVisibility::Default,
+ EntityDefinitionKind::Definition);
+ EXPECT_NE(Base,
+ EntityLinkage(External, EntityBinding::Weak, EntityCoalescing::None,
+ EntityVisibility::Default,
+ EntityDefinitionKind::Definition));
+ EXPECT_NE(Base,
+ EntityLinkage(External, EntityBinding::Strong,
+ EntityCoalescing::ODR, EntityVisibility::Default,
+ EntityDefinitionKind::Definition));
+ EXPECT_NE(Base,
+ EntityLinkage(External, EntityBinding::Strong,
+ EntityCoalescing::None, EntityVisibility::Hidden,
+ EntityDefinitionKind::Definition));
+ EXPECT_NE(Base,
+ EntityLinkage(External, EntityBinding::Strong,
+ EntityCoalescing::None, EntityVisibility::Default,
+ EntityDefinitionKind::Declaration));
}
TEST(EntityLinkageTypeTest, FormatProvider) {
@@ -88,30 +116,16 @@ TEST(EntityLinkageTypeTest, StreamOutputExternal) {
}
TEST(EntityLinkageTest, FormatProvider) {
- EXPECT_EQ(llvm::formatv("{0}", EntityLinkage(None)).str(),
- "EntityLinkage(None)");
- EXPECT_EQ(llvm::formatv("{0}", EntityLinkage(Internal)).str(),
- "EntityLinkage(Internal)");
- EXPECT_EQ(llvm::formatv("{0}", EntityLinkage(External)).str(),
- "EntityLinkage(External)");
-}
-
-TEST(EntityLinkageTest, StreamOutputNone) {
- std::string S;
- llvm::raw_string_ostream(S) << EntityLinkage(None);
- EXPECT_EQ(S, "EntityLinkage(None)");
-}
-
-TEST(EntityLinkageTest, StreamOutputInternal) {
- std::string S;
- llvm::raw_string_ostream(S) << EntityLinkage(Internal);
- EXPECT_EQ(S, "EntityLinkage(Internal)");
+ EXPECT_EQ(llvm::formatv("{0}", mk(None)).str(),
+ "EntityLinkage(None, Strong, None, Default, Definition)");
}
-TEST(EntityLinkageTest, StreamOutputExternal) {
+TEST(EntityLinkageTest, StreamOutput) {
std::string S;
- llvm::raw_string_ostream(S) << EntityLinkage(External);
- EXPECT_EQ(S, "EntityLinkage(External)");
+ llvm::raw_string_ostream(S) << EntityLinkage(
+ External, EntityBinding::Weak, EntityCoalescing::ODR,
+ EntityVisibility::Hidden, EntityDefinitionKind::Declaration);
+ EXPECT_EQ(S, "EntityLinkage(External, Weak, ODR, Hidden, Declaration)");
}
} // namespace
diff --git a/clang/unittests/ScalableStaticAnalysis/EntityLinkerTest.cpp b/clang/unittests/ScalableStaticAnalysis/EntityLinkerTest.cpp
index cd579e16991ef..a77887785e1f9 100644
--- a/clang/unittests/ScalableStaticAnalysis/EntityLinkerTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysis/EntityLinkerTest.cpp
@@ -32,37 +32,56 @@ namespace {
class MockEntitySummaryEncoding : public EntitySummaryEncoding {
public:
- MockEntitySummaryEncoding() : Id(++Index) {}
+ /// \param Payload Distinguishes encodings for the ODR mismatch check; two
+ /// mocks compare equal iff their payloads match.
+ explicit MockEntitySummaryEncoding(int Payload = 0)
+ : Id(++Index), Payload(Payload) {}
size_t getId() const { return Id; }
- llvm::Error
- patch(const std::map<EntityId, EntityId> &EntityResolutionTable) override {
- PatchedIds = EntityResolutionTable;
+ llvm::Error patch(const EntityResolutionMap &Resolution) override {
+ PatchedIds = Resolution;
return llvm::Error::success();
}
- const std::map<EntityId, EntityId> &getPatchedIds() const {
- return PatchedIds;
+ const void *getEncodingKind() const override { return &Kind; }
+
+ bool equals(const EntitySummaryEncoding &Other) const override {
+ if (Other.getEncodingKind() != getEncodingKind()) {
+ return false;
+ }
+ return Payload ==
+ static_cast<const MockEntitySummaryEncoding &>(Other).Payload;
}
+ const EntityResolutionMap &getPatchedIds() const { return PatchedIds; }
+
static size_t Index;
private:
+ static const char Kind;
+
size_t Id;
- std::map<EntityId, EntityId> PatchedIds;
+ int Payload;
+ EntityResolutionMap PatchedIds;
};
size_t MockEntitySummaryEncoding::Index = 0;
+const char MockEntitySummaryEncoding::Kind = 0;
class EntityLinkerTest : public TestFixture {
protected:
- static constexpr EntityLinkage NoneLinkage =
- EntityLinkage(EntityLinkageType::None);
+ static constexpr EntityLinkage NoneLinkage = EntityLinkage(
+ EntityLinkageType::None, EntityBinding::Strong, EntityCoalescing::None,
+ EntityVisibility::Default, EntityDefinitionKind::Definition);
static constexpr EntityLinkage InternalLinkage =
- EntityLinkage(EntityLinkageType::Internal);
+ EntityLinkage(EntityLinkageType::Internal, EntityBinding::Strong,
+ EntityCoalescing::None, EntityVisibility::Default,
+ EntityDefinitionKind::Definition);
static constexpr EntityLinkage ExternalLinkage =
- EntityLinkage(EntityLinkageType::External);
+ EntityLinkage(EntityLinkageType::External, EntityBinding::Strong,
+ EntityCoalescing::None, EntityVisibility::Default,
+ EntityDefinitionKind::Definition);
void SetUp() override {
// This ensures that the MockEntitySummary id assignment does not
@@ -78,9 +97,9 @@ class EntityLinkerTest : public TestFixture {
}
size_t addSummaryData(TUSummaryEncoding &TU, EntityId EId,
- llvm::StringRef SummaryNameStr) {
+ llvm::StringRef SummaryNameStr, int Payload = 0) {
SummaryName SN(SummaryNameStr.str());
- auto Summary = std::make_unique<MockEntitySummaryEncoding>();
+ auto Summary = std::make_unique<MockEntitySummaryEncoding>(Payload);
const size_t ESId = Summary->getId();
getData(TU)[SN][EId] = std::move(Summary);
return ESId;
@@ -326,7 +345,11 @@ TEST_F(EntityLinkerTest, LinksTwoTranslationUnits) {
NestedBuildNamespace LUNamespace(
{BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
- EntityLinker Linker(llvm::Triple("arm64-apple-macosx"), LUNamespace);
+ // The two TUs below both define the external entities P, Q and R, so the
+ // linker is asked to warn about the multiple definitions rather than reject
+ // them, leaving the duplicate summary data to be dropped as usual.
+ EntityLinker Linker(llvm::Triple("arm64-apple-macosx"), LUNamespace,
+ /*WarnOnMultipleDefinitions=*/true);
auto TU1 =
createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU1");
@@ -664,4 +687,822 @@ TEST_F(EntityLinkerTest, InternalLinkageWithEmptyNamespaceAcrossTUs) {
EXPECT_THAT(IdTable, ContainsEntity(ExpectedTU2Name));
}
+// ============================================================================
+// MULTIPLE DEFINITION TESTS
+// ============================================================================
+
+TEST_F(EntityLinkerTest, FailsOnMultipleDefinitions) {
+ constexpr auto LinkUnit = BuildNamespaceKind::LinkUnit;
+ constexpr auto CompilationUnit = BuildNamespaceKind::CompilationUnit;
+
+ EXPECT_DEATH(
+ {
+ EntityLinker Linker(
+ llvm::Triple("arm64-apple-macosx"),
+ NestedBuildNamespace{BuildNamespace(LinkUnit, "LU")});
+
+ auto TU1 = createTUSummaryEncoding(CompilationUnit, "TU1");
+ addEntity(*TU1, "P", ExternalLinkage);
+ cantFail(Linker.link(std::move(TU1)));
+
+ auto TU2 = createTUSummaryEncoding(CompilationUnit, "TU2");
+ addEntity(*TU2, "P", ExternalLinkage);
+ cantFail(Linker.link(std::move(TU2)));
+ },
+ "multiple definition of");
+}
+
+TEST_F(EntityLinkerTest, WarnsOnMultipleDefinitions) {
+ constexpr auto LinkUnit = BuildNamespaceKind::LinkUnit;
+ constexpr auto CompilationUnit = BuildNamespaceKind::CompilationUnit;
+
+ EntityLinker Linker(llvm::Triple("arm64-apple-macosx"),
+ NestedBuildNamespace{BuildNamespace(LinkUnit, "LU")},
+ /*WarnOnMultipleDefinitions=*/true);
+
+ auto TU1 = createTUSummaryEncoding(CompilationUnit, "TU1");
+ const auto TU1_P_Id = addEntity(*TU1, "P", ExternalLinkage);
+ const auto TU1_P_S1_Data = addSummaryData(*TU1, TU1_P_Id, "S1");
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU1)), llvm::Succeeded());
+
+ auto TU2 = createTUSummaryEncoding(CompilationUnit, "TU2");
+ const auto TU2_P_Id = addEntity(*TU2, "P", ExternalLinkage);
+ addSummaryData(*TU2, TU2_P_Id, "S1");
+
+ testing::internal::CaptureStderr();
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU2)), llvm::Succeeded());
+ const std::string Stderr = testing::internal::GetCapturedStderr();
+
+ EXPECT_THAT(Stderr, HasSubstr("warning: multiple definition of "
+ "EntityName(P, , NestedBuildNamespace(["
+ "BuildNamespace(LinkUnit, LU)]))"));
+ EXPECT_THAT(Stderr, HasSubstr("in NestedBuildNamespace(["
+ "BuildNamespace(CompilationUnit, TU2)])"));
+
+ // The extra definition is reported but still dropped: TU1 defined P first,
+ // so its summary data is the data that survives.
+ const auto Output = std::move(Linker).takeOutput();
+ const auto &IdTable = getIdTable(Output);
+
+ NestedBuildNamespace LUNamespace{{{LinkUnit, "LU"}}};
+ EntityName LU_P_Name("P", "", LUNamespace);
+ ASSERT_THAT(IdTable, ContainsEntity(LU_P_Name));
+
+ const auto LU_P_Id = getEntities(IdTable).at(LU_P_Name);
+ const std::map<EntityId, EntityId> TU1Resolution = {{TU1_P_Id, LU_P_Id}};
+ EXPECT_THAT(getData(Output).at(SummaryName("S1")),
+ HasSummaryData(LU_P_Id, TU1_P_S1_Data, TU1Resolution));
+}
+
+TEST_F(EntityLinkerTest, AcceptsDeclarationAlongsideDefinition) {
+ constexpr auto LinkUnit = BuildNamespaceKind::LinkUnit;
+ constexpr auto CompilationUnit = BuildNamespaceKind::CompilationUnit;
+ constexpr EntityLinkage ExternalDeclaration =
+ EntityLinkage(EntityLinkageType::External, EntityBinding::Undefined,
+ EntityCoalescing::None, EntityVisibility::Default,
+ EntityDefinitionKind::Declaration);
+
+ EntityLinker Linker(llvm::Triple("arm64-apple-macosx"),
+ NestedBuildNamespace{BuildNamespace(LinkUnit, "LU")});
+
+ auto TU1 = createTUSummaryEncoding(CompilationUnit, "TU1");
+ addEntity(*TU1, "P", ExternalLinkage);
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU1)), llvm::Succeeded());
+
+ // A TU that only references P must not be mistaken for a second definition.
+ auto TU2 = createTUSummaryEncoding(CompilationUnit, "TU2");
+ addEntity(*TU2, "P", ExternalDeclaration);
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU2)), llvm::Succeeded());
+
+ const auto Output = std::move(Linker).takeOutput();
+ NestedBuildNamespace LUNamespace{{{LinkUnit, "LU"}}};
+ EntityName LU_P_Name("P", "", LUNamespace);
+
+ const auto &IdTable = getIdTable(Output);
+ ASSERT_THAT(IdTable, IdTableHasSize(1u));
+ ASSERT_THAT(IdTable, ContainsEntity(LU_P_Name));
+
+ // The definition wins the reconciliation.
+ EXPECT_THAT(
+ getLinkageTable(Output),
+ EntityHasLinkage(getEntities(IdTable).at(LU_P_Name), ExternalLinkage));
+}
+
+// ============================================================================
+// TARGET VALIDATION TESTS
+// ============================================================================
+
+TEST_F(EntityLinkerTest, RejectsTUTargetingADifferentPlatform) {
+ constexpr auto LinkUnit = BuildNamespaceKind::LinkUnit;
+ constexpr auto CompilationUnit = BuildNamespaceKind::CompilationUnit;
+
+ EntityLinker Linker(llvm::Triple("arm64-apple-macosx"),
+ NestedBuildNamespace{BuildNamespace(LinkUnit, "LU")});
+
+ auto TU = createTUSummaryEncoding(CompilationUnit, "TU",
+ llvm::Triple("x86_64-unknown-linux-gnu"));
+
+ // Applying Mach-O rules to an ELF summary is silently wrong, since the two
+ // disagree about whether a weak definition outranks a common one.
+ EXPECT_THAT_ERROR(
+ Linker.link(std::move(TU)),
+ llvm::FailedWithMessage(AllOf(
+ HasSubstr("targets 'x86_64-unknown-linux-gnu', which resolves "
+ "symbols as ELF"),
+ HasSubstr("link unit targets 'arm64-apple-macosx', which resolves "
+ "them as Mach-O"))));
+}
+
+TEST_F(EntityLinkerTest, AcceptsTUDifferingOnlyInOSVersion) {
+ constexpr auto LinkUnit = BuildNamespaceKind::LinkUnit;
+ constexpr auto CompilationUnit = BuildNamespaceKind::CompilationUnit;
+
+ // The OS version does not affect symbol resolution, so it is not compared.
+ EntityLinker Linker(llvm::Triple("arm64-apple-macosx14.0"),
+ NestedBuildNamespace{BuildNamespace(LinkUnit, "LU")});
+
+ auto TU = createTUSummaryEncoding(CompilationUnit, "TU",
+ llvm::Triple("arm64-apple-macosx15.0"));
+ EXPECT_THAT_ERROR(Linker.link(std::move(TU)), llvm::Succeeded());
+}
+
+TEST_F(EntityLinkerTest, RejectsProtectedVisibilityOnMachO) {
+ constexpr auto LinkUnit = BuildNamespaceKind::LinkUnit;
+ constexpr auto CompilationUnit = BuildNamespaceKind::CompilationUnit;
+ constexpr EntityLinkage ProtectedDef =
+ EntityLinkage(EntityLinkageType::External, EntityBinding::Strong,
+ EntityCoalescing::None, EntityVisibility::Protected,
+ EntityDefinitionKind::Definition);
+
+ // clang warns and downgrades protected visibility on a Mach-O target, so a
+ // summary carrying it did not come from the compiler.
+ EXPECT_DEATH(
+ {
+ EntityLinker Linker(
+ llvm::Triple("arm64-apple-macosx"),
+ NestedBuildNamespace{BuildNamespace(LinkUnit, "LU")});
+ auto TU = createTUSummaryEncoding(CompilationUnit, "TU");
+ addEntity(*TU, "P", ProtectedDef);
+ cantFail(Linker.link(std::move(TU)));
+ },
+ "which a Mach-O target cannot represent");
+}
+
+TEST_F(EntityLinkerTest, CoercesHiddenVisibilityOnCOFF) {
+ constexpr auto LinkUnit = BuildNamespaceKind::LinkUnit;
+ constexpr auto CompilationUnit = BuildNamespaceKind::CompilationUnit;
+ constexpr EntityLinkage HiddenDef =
+ EntityLinkage(EntityLinkageType::External, EntityBinding::Strong,
+ EntityCoalescing::None, EntityVisibility::Hidden,
+ EntityDefinitionKind::Definition);
+
+ // COFF drops visibility at emission and clang accepts the attribute
+ // silently, so portable source legitimately produces this. Coerce rather
+ // than reject.
+ EntityLinker Linker(llvm::Triple("x86_64-pc-windows-msvc"),
+ NestedBuildNamespace{BuildNamespace(LinkUnit, "LU")});
+
+ auto TU = createTUSummaryEncoding(CompilationUnit, "TU",
+ llvm::Triple("x86_64-pc-windows-msvc"));
+ addEntity(*TU, "P", HiddenDef);
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU)), llvm::Succeeded());
+
+ const auto Output = std::move(Linker).takeOutput();
+ NestedBuildNamespace LUNamespace{{{LinkUnit, "LU"}}};
+ EntityName LU_P_Name("P", "", LUNamespace);
+ const auto &IdTable = getIdTable(Output);
+ ASSERT_THAT(IdTable, ContainsEntity(LU_P_Name));
+
+ constexpr EntityLinkage CoercedDef =
+ EntityLinkage(EntityLinkageType::External, EntityBinding::Strong,
+ EntityCoalescing::None, EntityVisibility::Default,
+ EntityDefinitionKind::Definition);
+ EXPECT_THAT(getLinkageTable(Output),
+ EntityHasLinkage(getEntities(IdTable).at(LU_P_Name), CoercedDef));
+}
+
+// ============================================================================
+// FINALIZATION TESTS
+// ============================================================================
+
+TEST_F(EntityLinkerTest, DemotesHiddenEntitiesToInternal) {
+ constexpr auto LinkUnit = BuildNamespaceKind::LinkUnit;
+ constexpr auto CompilationUnit = BuildNamespaceKind::CompilationUnit;
+ constexpr EntityLinkage HiddenDef =
+ EntityLinkage(EntityLinkageType::External, EntityBinding::Strong,
+ EntityCoalescing::None, EntityVisibility::Hidden,
+ EntityDefinitionKind::Definition);
+
+ EntityLinker Linker(llvm::Triple("x86_64-unknown-linux-gnu"),
+ NestedBuildNamespace{BuildNamespace(LinkUnit, "LU")});
+
+ auto TU = createTUSummaryEncoding(CompilationUnit, "TU",
+ llvm::Triple("x86_64-unknown-linux-gnu"));
+ addEntity(*TU, "hidden_fn", HiddenDef);
+ addEntity(*TU, "exported_fn", ExternalLinkage);
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU)), llvm::Succeeded());
+
+ const auto Output = std::move(Linker).takeOutput();
+ NestedBuildNamespace LUNamespace{{{LinkUnit, "LU"}}};
+ const auto &IdTable = getIdTable(Output);
+ const auto &Entities = getEntities(IdTable);
+
+ // A hidden entity is not resolvable past the link unit boundary, which is
+ // Internal linkage in our model.
+ constexpr EntityLinkage DemotedDef =
+ EntityLinkage(EntityLinkageType::Internal, EntityBinding::Strong,
+ EntityCoalescing::None, EntityVisibility::Hidden,
+ EntityDefinitionKind::Definition);
+ EXPECT_THAT(
+ getLinkageTable(Output),
+ EntityHasLinkage(Entities.at(EntityName("hidden_fn", "", LUNamespace)),
+ DemotedDef));
+
+ // The default-visibility entity is untouched.
+ EXPECT_THAT(
+ getLinkageTable(Output),
+ EntityHasLinkage(Entities.at(EntityName("exported_fn", "", LUNamespace)),
+ ExternalLinkage));
+}
+
+TEST_F(EntityLinkerTest, KeepsHiddenEntityNamesWhenDemoting) {
+ constexpr auto LinkUnit = BuildNamespaceKind::LinkUnit;
+ constexpr auto CompilationUnit = BuildNamespaceKind::CompilationUnit;
+ constexpr EntityLinkage HiddenDef =
+ EntityLinkage(EntityLinkageType::External, EntityBinding::Strong,
+ EntityCoalescing::None, EntityVisibility::Hidden,
+ EntityDefinitionKind::Definition);
+
+ EntityLinker Linker(llvm::Triple("x86_64-unknown-linux-gnu"),
+ NestedBuildNamespace{BuildNamespace(LinkUnit, "LU")});
+
+ auto TU = createTUSummaryEncoding(CompilationUnit, "TU",
+ llvm::Triple("x86_64-unknown-linux-gnu"));
+ addEntity(*TU, "hidden_fn", HiddenDef);
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU)), llvm::Succeeded());
+
+ // Demotion changes the linkage type only. Renaming would invalidate every
+ // EntityId already patched into the summary data, and is unnecessary because
+ // external names are already LU-qualified.
+ const auto Output = std::move(Linker).takeOutput();
+ NestedBuildNamespace LUNamespace{{{LinkUnit, "LU"}}};
+ EXPECT_THAT(getIdTable(Output),
+ ContainsEntity(EntityName("hidden_fn", "", LUNamespace)));
+}
+
+TEST_F(EntityLinkerTest, IgnoresUnresolvedEntitiesByDefault) {
+ constexpr auto LinkUnit = BuildNamespaceKind::LinkUnit;
+ constexpr auto CompilationUnit = BuildNamespaceKind::CompilationUnit;
+ constexpr EntityLinkage ExternalDeclaration =
+ EntityLinkage(EntityLinkageType::External, EntityBinding::Undefined,
+ EntityCoalescing::None, EntityVisibility::Default,
+ EntityDefinitionKind::Declaration);
+
+ // A link unit is usually intermediate, so an unresolved reference is not by
+ // itself an error.
+ EntityLinker Linker(llvm::Triple("x86_64-unknown-linux-gnu"),
+ NestedBuildNamespace{BuildNamespace(LinkUnit, "LU")});
+
+ auto TU = createTUSummaryEncoding(CompilationUnit, "TU",
+ llvm::Triple("x86_64-unknown-linux-gnu"));
+ addEntity(*TU, "missing", ExternalDeclaration);
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU)), llvm::Succeeded());
+
+ testing::internal::CaptureStderr();
+ const auto Output = std::move(Linker).takeOutput();
+ EXPECT_THAT(testing::internal::GetCapturedStderr(),
+ Not(HasSubstr("undefined symbol")));
+}
+
+TEST_F(EntityLinkerTest, WarnsOnUnresolvedEntities) {
+ constexpr auto LinkUnit = BuildNamespaceKind::LinkUnit;
+ constexpr auto CompilationUnit = BuildNamespaceKind::CompilationUnit;
+ constexpr EntityLinkage ExternalDeclaration =
+ EntityLinkage(EntityLinkageType::External, EntityBinding::Undefined,
+ EntityCoalescing::None, EntityVisibility::Default,
+ EntityDefinitionKind::Declaration);
+ constexpr EntityLinkage WeakDeclaration = EntityLinkage(
+ EntityLinkageType::External, EntityBinding::Weak, EntityCoalescing::None,
+ EntityVisibility::Default, EntityDefinitionKind::Declaration);
+
+ EntityLinker Linker(llvm::Triple("x86_64-unknown-linux-gnu"),
+ NestedBuildNamespace{BuildNamespace(LinkUnit, "LU")},
+ /*WarnOnMultipleDefinitions=*/false,
+ UnresolvedPolicy::Warn);
+
+ auto TU = createTUSummaryEncoding(CompilationUnit, "TU",
+ llvm::Triple("x86_64-unknown-linux-gnu"));
+ addEntity(*TU, "missing", ExternalDeclaration);
+ addEntity(*TU, "maybe_missing", WeakDeclaration);
+ addEntity(*TU, "defined", ExternalLinkage);
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU)), llvm::Succeeded());
+
+ testing::internal::CaptureStderr();
+ const auto Output = std::move(Linker).takeOutput();
+ const std::string Stderr = testing::internal::GetCapturedStderr();
+
+ EXPECT_THAT(Stderr, HasSubstr("undefined symbol: EntityName(missing"));
+ // An undefined weak reference resolves to nothing by design (ELF probe U2).
+ EXPECT_THAT(Stderr, Not(HasSubstr("maybe_missing")));
+ EXPECT_THAT(Stderr, Not(HasSubstr("EntityName(defined")));
+}
+
+TEST_F(EntityLinkerTest, ReportsUnresolvedEntitiesOnlyAfterEveryTUIsLinked) {
+ constexpr auto LinkUnit = BuildNamespaceKind::LinkUnit;
+ constexpr auto CompilationUnit = BuildNamespaceKind::CompilationUnit;
+ constexpr EntityLinkage ExternalDeclaration =
+ EntityLinkage(EntityLinkageType::External, EntityBinding::Undefined,
+ EntityCoalescing::None, EntityVisibility::Default,
+ EntityDefinitionKind::Declaration);
+
+ EntityLinker Linker(llvm::Triple("x86_64-unknown-linux-gnu"),
+ NestedBuildNamespace{BuildNamespace(LinkUnit, "LU")},
+ /*WarnOnMultipleDefinitions=*/false,
+ UnresolvedPolicy::Warn);
+
+ // The first TU only declares P; the second defines it. Reporting per-TU
+ // would produce a spurious diagnostic here, which is why the check runs at
+ // finalization.
+ auto TU1 = createTUSummaryEncoding(CompilationUnit, "TU1",
+ llvm::Triple("x86_64-unknown-linux-gnu"));
+ addEntity(*TU1, "P", ExternalDeclaration);
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU1)), llvm::Succeeded());
+
+ auto TU2 = createTUSummaryEncoding(CompilationUnit, "TU2",
+ llvm::Triple("x86_64-unknown-linux-gnu"));
+ addEntity(*TU2, "P", ExternalLinkage);
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU2)), llvm::Succeeded());
+
+ testing::internal::CaptureStderr();
+ const auto Output = std::move(Linker).takeOutput();
+ EXPECT_THAT(testing::internal::GetCapturedStderr(),
+ Not(HasSubstr("undefined symbol")));
+}
+
+TEST_F(EntityLinkerTest, FailsOnUnresolvedEntitiesUnderErrorPolicy) {
+ constexpr auto LinkUnit = BuildNamespaceKind::LinkUnit;
+ constexpr auto CompilationUnit = BuildNamespaceKind::CompilationUnit;
+ constexpr EntityLinkage ExternalDeclaration =
+ EntityLinkage(EntityLinkageType::External, EntityBinding::Undefined,
+ EntityCoalescing::None, EntityVisibility::Default,
+ EntityDefinitionKind::Declaration);
+
+ EXPECT_DEATH(
+ {
+ EntityLinker Linker(
+ llvm::Triple("x86_64-unknown-linux-gnu"),
+ NestedBuildNamespace{BuildNamespace(LinkUnit, "LU")},
+ /*WarnOnMultipleDefinitions=*/false, UnresolvedPolicy::Error);
+ auto TU = createTUSummaryEncoding(
+ CompilationUnit, "TU", llvm::Triple("x86_64-unknown-linux-gnu"));
+ addEntity(*TU, "missing", ExternalDeclaration);
+ cantFail(Linker.link(std::move(TU)));
+ auto Output = std::move(Linker).takeOutput();
+ },
+ "undefined symbol");
+}
+
+// ============================================================================
+// ODR MISMATCH TESTS
+// ============================================================================
+
+namespace {
+/// An inline definition: strong at the source level, with the ODR guarantee.
+constexpr EntityLinkage ODRDefinition = EntityLinkage(
+ EntityLinkageType::External, EntityBinding::Strong, EntityCoalescing::ODR,
+ EntityVisibility::Default, EntityDefinitionKind::Definition);
+} // namespace
+
+TEST_F(EntityLinkerTest, IgnoresODRMismatchByDefault) {
+ constexpr auto LinkUnit = BuildNamespaceKind::LinkUnit;
+ constexpr auto CompilationUnit = BuildNamespaceKind::CompilationUnit;
+
+ EntityLinker Linker(llvm::Triple("arm64-apple-macosx"),
+ NestedBuildNamespace{BuildNamespace(LinkUnit, "LU")});
+
+ auto TU1 = createTUSummaryEncoding(CompilationUnit, "TU1");
+ addSummaryData(*TU1, addEntity(*TU1, "P", ODRDefinition), "S1",
+ /*Payload=*/1);
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU1)), llvm::Succeeded());
+
+ auto TU2 = createTUSummaryEncoding(CompilationUnit, "TU2");
+ addSummaryData(*TU2, addEntity(*TU2, "P", ODRDefinition), "S1",
+ /*Payload=*/2);
+
+ testing::internal::CaptureStderr();
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU2)), llvm::Succeeded());
+ EXPECT_THAT(testing::internal::GetCapturedStderr(),
+ Not(HasSubstr("differs between")));
+}
+
+TEST_F(EntityLinkerTest, WarnsOnODRMismatch) {
+ constexpr auto LinkUnit = BuildNamespaceKind::LinkUnit;
+ constexpr auto CompilationUnit = BuildNamespaceKind::CompilationUnit;
+
+ EntityLinker Linker(llvm::Triple("arm64-apple-macosx"),
+ NestedBuildNamespace{BuildNamespace(LinkUnit, "LU")},
+ /*WarnOnMultipleDefinitions=*/false,
+ UnresolvedPolicy::Ignore, ODRMismatchPolicy::Warn);
+
+ auto TU1 = createTUSummaryEncoding(CompilationUnit, "TU1");
+ addSummaryData(*TU1, addEntity(*TU1, "P", ODRDefinition), "S1",
+ /*Payload=*/1);
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU1)), llvm::Succeeded());
+
+ // The same inline function summarized differently in another TU means the
+ // two definitions were not in fact identical.
+ auto TU2 = createTUSummaryEncoding(CompilationUnit, "TU2");
+ addSummaryData(*TU2, addEntity(*TU2, "P", ODRDefinition), "S1",
+ /*Payload=*/2);
+
+ testing::internal::CaptureStderr();
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU2)), llvm::Succeeded());
+ const std::string Stderr = testing::internal::GetCapturedStderr();
+
+ EXPECT_THAT(Stderr, HasSubstr("SummaryName(S1) summary data for "
+ "EntityName(P, "));
+ EXPECT_THAT(Stderr, HasSubstr("definitions are required to be identical"));
+}
+
+TEST_F(EntityLinkerTest, AcceptsMatchingODRDefinitions) {
+ constexpr auto LinkUnit = BuildNamespaceKind::LinkUnit;
+ constexpr auto CompilationUnit = BuildNamespaceKind::CompilationUnit;
+
+ EntityLinker Linker(llvm::Triple("arm64-apple-macosx"),
+ NestedBuildNamespace{BuildNamespace(LinkUnit, "LU")},
+ /*WarnOnMultipleDefinitions=*/false,
+ UnresolvedPolicy::Ignore, ODRMismatchPolicy::Warn);
+
+ auto TU1 = createTUSummaryEncoding(CompilationUnit, "TU1");
+ addSummaryData(*TU1, addEntity(*TU1, "P", ODRDefinition), "S1",
+ /*Payload=*/7);
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU1)), llvm::Succeeded());
+
+ auto TU2 = createTUSummaryEncoding(CompilationUnit, "TU2");
+ addSummaryData(*TU2, addEntity(*TU2, "P", ODRDefinition), "S1",
+ /*Payload=*/7);
+
+ testing::internal::CaptureStderr();
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU2)), llvm::Succeeded());
+ EXPECT_THAT(testing::internal::GetCapturedStderr(),
+ Not(HasSubstr("differs between")));
+}
+
+TEST_F(EntityLinkerTest, DoesNotCheckNonODRDefinitionsForMismatch) {
+ constexpr auto LinkUnit = BuildNamespaceKind::LinkUnit;
+ constexpr auto CompilationUnit = BuildNamespaceKind::CompilationUnit;
+ constexpr EntityLinkage WeakDefinition = EntityLinkage(
+ EntityLinkageType::External, EntityBinding::Weak, EntityCoalescing::None,
+ EntityVisibility::Default, EntityDefinitionKind::Definition);
+
+ EntityLinker Linker(llvm::Triple("arm64-apple-macosx"),
+ NestedBuildNamespace{BuildNamespace(LinkUnit, "LU")},
+ /*WarnOnMultipleDefinitions=*/false,
+ UnresolvedPolicy::Ignore, ODRMismatchPolicy::Warn);
+
+ auto TU1 = createTUSummaryEncoding(CompilationUnit, "TU1");
+ addSummaryData(*TU1, addEntity(*TU1, "P", WeakDefinition), "S1",
+ /*Payload=*/1);
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU1)), llvm::Succeeded());
+
+ // Two __attribute__((weak)) definitions may legitimately differ: one is
+ // meant to be replaceable by the other. Reporting this would be noise.
+ auto TU2 = createTUSummaryEncoding(CompilationUnit, "TU2");
+ addSummaryData(*TU2, addEntity(*TU2, "P", WeakDefinition), "S1",
+ /*Payload=*/2);
+
+ testing::internal::CaptureStderr();
+ ASSERT_THAT_ERROR(Linker.link(std::move(TU2)), llvm::Succeeded());
+ EXPECT_THAT(testing::internal::GetCapturedStderr(),
+ Not(HasSubstr("differs between")));
+}
+
+TEST_F(EntityLinkerTest, FailsOnODRMismatchUnderErrorPolicy) {
+ constexpr auto LinkUnit = BuildNamespaceKind::LinkUnit;
+ constexpr auto CompilationUnit = BuildNamespaceKind::CompilationUnit;
+
+ EXPECT_DEATH(
+ {
+ EntityLinker Linker(
+ llvm::Triple("arm64-apple-macosx"),
+ NestedBuildNamespace{BuildNamespace(LinkUnit, "LU")},
+ /*WarnOnMultipleDefinitions=*/false, UnresolvedPolicy::Ignore,
+ ODRMismatchPolicy::Error);
+
+ auto TU1 = createTUSummaryEncoding(CompilationUnit, "TU1");
+ addSummaryData(*TU1, addEntity(*TU1, "P", ODRDefinition), "S1",
+ /*Payload=*/1);
+ cantFail(Linker.link(std::move(TU1)));
+
+ auto TU2 = createTUSummaryEncoding(CompilationUnit, "TU2");
+ addSummaryData(*TU2, addEntity(*TU2, "P", ODRDefinition), "S1",
+ /*Payload=*/2);
+ cantFail(Linker.link(std::move(TU2)));
+ },
+ "differs between");
+}
+
+// ============================================================================
+// LINKAGE RECONCILIATION TESTS
+// ============================================================================
+
+// The reconciliation rules are private to EntityLinker and depend on the
+// target's LinkageRules, so TestFixture exposes them and each test names the
+// platform it is checking. Every expectation below is grounded in a probe
+// recorded in docs/ssaf-linker-{elf,macho,coff}-behavior.md.
+class EntityLinkerReconcileTest : public TestFixture {
+protected:
+ EntityLinker ELF = makeLinkerFor("x86_64-unknown-linux-gnu");
+ EntityLinker MachO = makeLinkerFor("arm64-apple-macosx");
+ EntityLinker COFF = makeLinkerFor("x86_64-pc-windows-msvc");
+};
+
+// Builds an External EntityLinkage with explicit properties.
+constexpr EntityLinkage ext(EntityBinding B, EntityCoalescing C,
+ EntityVisibility V, EntityDefinitionKind D) {
+ return EntityLinkage(EntityLinkageType::External, B, C, V, D);
+}
+
+constexpr EntityLinkage def(EntityBinding B,
+ EntityVisibility V = EntityVisibility::Default,
+ EntityCoalescing C = EntityCoalescing::None) {
+ return ext(B, C, V, EntityDefinitionKind::Definition);
+}
+
+constexpr EntityLinkage decl(EntityBinding B = EntityBinding::Undefined,
+ EntityVisibility V = EntityVisibility::Default) {
+ return ext(B, EntityCoalescing::None, V, EntityDefinitionKind::Declaration);
+}
+
+/// An inline function: strong at the source level, with the ODR guarantee.
+constexpr EntityLinkage odrDef(EntityVisibility V = EntityVisibility::Default) {
+ return def(EntityBinding::Strong, V, EntityCoalescing::ODR);
+}
+
+// ----------------------------------------------------------------------------
+// Rules every platform agrees on
+// ----------------------------------------------------------------------------
+
+TEST_F(EntityLinkerReconcileTest, DefinitionBeatsDeclarationEverywhere) {
+ const auto D = decl();
+ const auto S = def(EntityBinding::Strong);
+
+ for (const EntityLinker *L : {&ELF, &MachO, &COFF}) {
+ EXPECT_TRUE(incomingDataWins(*L, D, S));
+ EXPECT_FALSE(incomingDataWins(*L, S, D));
+ EXPECT_FALSE(isConflictingDefinition(*L, D, S));
+ EXPECT_EQ(mergeLinkage(*L, D, S), S);
+ }
+}
+
+TEST_F(EntityLinkerReconcileTest, StrongerDefinitionWinsDataEverywhere) {
+ const auto Weak = def(EntityBinding::Weak);
+ const auto Strong = def(EntityBinding::Strong);
+
+ for (const EntityLinker *L : {&ELF, &MachO, &COFF}) {
+ EXPECT_TRUE(incomingDataWins(*L, Weak, Strong));
+ EXPECT_FALSE(incomingDataWins(*L, Strong, Weak));
+ }
+}
+
+TEST_F(EntityLinkerReconcileTest, TiesKeepTheDataAlreadyLinkedEverywhere) {
+ // ELF probe P4, Mach-O probe M4: two weak definitions keep the first.
+ const auto Weak = def(EntityBinding::Weak);
+
+ for (const EntityLinker *L : {&ELF, &MachO, &COFF}) {
+ EXPECT_FALSE(incomingDataWins(*L, Weak, Weak));
+ }
+}
+
+TEST_F(EntityLinkerReconcileTest, TwoStrongDefinitionsConflictEverywhere) {
+ const auto S = def(EntityBinding::Strong);
+
+ for (const EntityLinker *L : {&ELF, &MachO, &COFF}) {
+ EXPECT_TRUE(isConflictingDefinition(*L, S, S));
+ // A declaration never conflicts with a definition.
+ EXPECT_FALSE(isConflictingDefinition(*L, S, decl()));
+ // Commons merge rather than conflict.
+ EXPECT_FALSE(isConflictingDefinition(*L, def(EntityBinding::Common),
+ def(EntityBinding::Common)));
+ }
+}
+
+TEST_F(EntityLinkerReconcileTest, DeclarationDoesNotWeakenADefinition) {
+ // ELF §6.1, Mach-O §6.1, COFF §6.2: a weak declaration merged with a common
+ // definition leaves the definition's binding intact. This matters most on
+ // Mach-O, where Weak outranks Common.
+ const auto WeakDecl = decl(EntityBinding::Weak);
+ const auto CommonDef = def(EntityBinding::Common);
+
+ for (const EntityLinker *L : {&ELF, &MachO, &COFF}) {
+ EXPECT_EQ(mergeLinkage(*L, WeakDecl, CommonDef), CommonDef);
+ EXPECT_EQ(mergeLinkage(*L, CommonDef, WeakDecl), CommonDef);
+ }
+}
+
+TEST_F(EntityLinkerReconcileTest, DeclarationsStayUnresolvedEverywhere) {
+ const auto D = decl();
+
+ for (const EntityLinker *L : {&ELF, &MachO, &COFF}) {
+ EXPECT_EQ(mergeLinkage(*L, D, D), D);
+ EXPECT_FALSE(isConflictingDefinition(*L, D, D));
+ EXPECT_FALSE(incomingDataWins(*L, D, D));
+ }
+}
+
+TEST_F(EntityLinkerReconcileTest, StrongDeclarationOutranksWeakDeclaration) {
+ // ELF §9: the merged undefined symbol is GLOBAL, not WEAK, in either order.
+ const auto StrongDecl = decl(EntityBinding::Strong);
+ const auto WeakDecl = decl(EntityBinding::Weak);
+
+ for (const EntityLinker *L : {&ELF, &MachO, &COFF}) {
+ EXPECT_EQ(mergeLinkage(*L, WeakDecl, StrongDecl), StrongDecl);
+ EXPECT_EQ(mergeLinkage(*L, StrongDecl, WeakDecl), StrongDecl);
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Binding precedence: Mach-O inverts Weak and Common
+// ----------------------------------------------------------------------------
+
+TEST_F(EntityLinkerReconcileTest, CommonBeatsWeakOnELFAndCOFF) {
+ // ELF probe C3, COFF probe K3: the common definition survives.
+ const auto WeakDef = def(EntityBinding::Weak);
+ const auto CommonDef = def(EntityBinding::Common);
+
+ EXPECT_TRUE(incomingDataWins(ELF, WeakDef, CommonDef));
+ EXPECT_FALSE(incomingDataWins(ELF, CommonDef, WeakDef));
+ EXPECT_TRUE(incomingDataWins(COFF, WeakDef, CommonDef));
+ EXPECT_FALSE(incomingDataWins(COFF, CommonDef, WeakDef));
+}
+
+TEST_F(EntityLinkerReconcileTest, WeakBeatsCommonOnMachO) {
+ // Mach-O probe MC3: ld-prime replaces the common with the weak definition
+ // and warns about the size change.
+ const auto WeakDef = def(EntityBinding::Weak);
+ const auto CommonDef = def(EntityBinding::Common);
+
+ EXPECT_TRUE(incomingDataWins(MachO, CommonDef, WeakDef));
+ EXPECT_FALSE(incomingDataWins(MachO, WeakDef, CommonDef));
+}
+
+// ----------------------------------------------------------------------------
+// ODR definitions: COFF keeps them strong, ELF and Mach-O lower them to weak
+// ----------------------------------------------------------------------------
+
+TEST_F(EntityLinkerReconcileTest, TwoODRDefinitionsCoalesceEverywhere) {
+ // ELF probe EI2, COFF probe CI1: two inline definitions always link.
+ for (const EntityLinker *L : {&ELF, &MachO, &COFF}) {
+ EXPECT_FALSE(isConflictingDefinition(*L, odrDef(), odrDef()));
+ }
+}
+
+TEST_F(EntityLinkerReconcileTest, ODRAgainstRegularDefinitionDivergesByTarget) {
+ const auto Regular = def(EntityBinding::Strong);
+
+ // ELF probe EI1 and Mach-O probe MI1: the inline lowers to weak, so the
+ // regular definition simply wins.
+ EXPECT_FALSE(isConflictingDefinition(ELF, odrDef(), Regular));
+ EXPECT_FALSE(isConflictingDefinition(MachO, odrDef(), Regular));
+ EXPECT_TRUE(incomingDataWins(ELF, odrDef(), Regular));
+ EXPECT_TRUE(incomingDataWins(MachO, odrDef(), Regular));
+
+ // COFF probe K4: the inline stays strong, so this is a duplicate symbol.
+ EXPECT_TRUE(isConflictingDefinition(COFF, odrDef(), Regular));
+}
+
+TEST_F(EntityLinkerReconcileTest, TwoWeakDefinitionsConflictOnlyOnCOFF) {
+ // COFF probe C4: two __attribute__((weak)) definitions collide on the alias
+ // COFF uses to emulate them. ELF probe P4 and Mach-O probe M4 accept them.
+ const auto WeakDef = def(EntityBinding::Weak);
+
+ EXPECT_FALSE(isConflictingDefinition(ELF, WeakDef, WeakDef));
+ EXPECT_FALSE(isConflictingDefinition(MachO, WeakDef, WeakDef));
+ EXPECT_TRUE(isConflictingDefinition(COFF, WeakDef, WeakDef));
+
+ // One weak against one strong yields rather than colliding, on every target.
+ const auto StrongDef = def(EntityBinding::Strong);
+ for (const EntityLinker *L : {&ELF, &MachO, &COFF}) {
+ EXPECT_FALSE(isConflictingDefinition(*L, WeakDef, StrongDef));
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Visibility: ELF takes the most restrictive, Mach-O the least
+// ----------------------------------------------------------------------------
+
+TEST_F(EntityLinkerReconcileTest, ELFTakesTheMostRestrictiveVisibility) {
+ // ELF probe V1: a hidden occurrence makes the merged entity hidden.
+ EXPECT_EQ(mergeLinkage(ELF,
+ def(EntityBinding::Strong, EntityVisibility::Default),
+ def(EntityBinding::Weak, EntityVisibility::Hidden)),
+ def(EntityBinding::Strong, EntityVisibility::Hidden));
+
+ // ELF probe VP1/VP2: hidden beats protected, in either order.
+ EXPECT_EQ(
+ mergeLinkage(ELF, def(EntityBinding::Strong, EntityVisibility::Protected),
+ def(EntityBinding::Strong, EntityVisibility::Hidden))
+ .getLinkage(),
+ EntityLinkageType::External);
+ EXPECT_EQ(mergeLinkage(ELF, odrDef(EntityVisibility::Protected),
+ odrDef(EntityVisibility::Hidden)),
+ odrDef(EntityVisibility::Hidden));
+ EXPECT_EQ(mergeLinkage(ELF, odrDef(EntityVisibility::Hidden),
+ odrDef(EntityVisibility::Protected)),
+ odrDef(EntityVisibility::Hidden));
+}
+
+TEST_F(EntityLinkerReconcileTest, MachOTakesTheLeastRestrictiveVisibility) {
+ // Mach-O probes W1/W2: a hidden weak definition merged with a default one
+ // yields an exported symbol, in either order.
+ const auto HiddenWeak = def(EntityBinding::Weak, EntityVisibility::Hidden);
+ const auto DefaultWeak = def(EntityBinding::Weak, EntityVisibility::Default);
+
+ EXPECT_EQ(mergeLinkage(MachO, HiddenWeak, DefaultWeak), DefaultWeak);
+ EXPECT_EQ(mergeLinkage(MachO, DefaultWeak, HiddenWeak), DefaultWeak);
+
+ // Mach-O probe W3: only when every copy is hidden does it stay private.
+ EXPECT_EQ(mergeLinkage(MachO, HiddenWeak, HiddenWeak), HiddenWeak);
+}
+
+TEST_F(EntityLinkerReconcileTest, MachOCommonVisibilityIsOrderDependent) {
+ // Mach-O probe §7.2: addCommon() keeps the visibility of whichever common it
+ // saw first, so unlike every other merge this one depends on link order. We
+ // reproduce that rather than approximating it.
+ const auto HiddenCommon =
+ def(EntityBinding::Common, EntityVisibility::Hidden);
+ const auto DefaultCommon =
+ def(EntityBinding::Common, EntityVisibility::Default);
+
+ EXPECT_EQ(mergeLinkage(MachO, HiddenCommon, DefaultCommon), HiddenCommon);
+ EXPECT_EQ(mergeLinkage(MachO, DefaultCommon, HiddenCommon), DefaultCommon);
+
+ // ELF has no such carve-out: hidden wins either way.
+ EXPECT_EQ(mergeLinkage(ELF, HiddenCommon, DefaultCommon), HiddenCommon);
+ EXPECT_EQ(mergeLinkage(ELF, DefaultCommon, HiddenCommon), HiddenCommon);
+}
+
+TEST_F(EntityLinkerReconcileTest, COFFHasNoVisibility) {
+ // COFF probe §7: a hidden common is indistinguishable from a default one, so
+ // normalize() flattens visibility to Default before any rule sees it.
+ const auto HiddenDef = def(EntityBinding::Strong, EntityVisibility::Hidden);
+ const auto DefaultDef = def(EntityBinding::Strong, EntityVisibility::Default);
+
+ // Visibility always merges to Default, so the result does not depend on
+ // which occurrence was linked first.
+ EXPECT_EQ(mergeLinkage(COFF, HiddenDef, DefaultDef), DefaultDef);
+ EXPECT_EQ(mergeLinkage(COFF, DefaultDef, HiddenDef), DefaultDef);
+}
+
+// ----------------------------------------------------------------------------
+// Algebraic properties, per platform
+// ----------------------------------------------------------------------------
+
+TEST_F(EntityLinkerReconcileTest, MergeIsCommutativeExceptWhereTheTargetIsNot) {
+ const EntityLinkage Xs[] = {
+ def(EntityBinding::Strong),
+ def(EntityBinding::Weak),
+ odrDef(EntityVisibility::Hidden),
+ def(EntityBinding::Common, EntityVisibility::Protected),
+ decl(),
+ decl(EntityBinding::Weak, EntityVisibility::Hidden)};
+
+ for (const EntityLinker *L : {&ELF, &MachO, &COFF}) {
+ for (const auto &A : Xs) {
+ for (const auto &B : Xs) {
+ EXPECT_EQ(isConflictingDefinition(*L, A, B),
+ isConflictingDefinition(*L, B, A));
+
+ // Mach-O's common-visibility rule is deliberately order-dependent;
+ // everything else commutes.
+ const bool OrderDependent =
+ L == &MachO && A.getLinkage() == EntityLinkageType::External &&
+ mergeLinkage(*L, A, B) != mergeLinkage(*L, B, A);
+ if (OrderDependent) {
+ continue;
+ }
+ EXPECT_EQ(mergeLinkage(*L, A, B), mergeLinkage(*L, B, A));
+ }
+ }
+ }
+}
+
+TEST_F(EntityLinkerReconcileTest, MergeIsIdempotentEverywhere) {
+ const EntityLinkage Xs[] = {
+ def(EntityBinding::Strong), def(EntityBinding::Weak),
+ odrDef(EntityVisibility::Hidden),
+ def(EntityBinding::Common, EntityVisibility::Protected), decl()};
+
+ for (const EntityLinker *L : {&ELF, &MachO, &COFF}) {
+ for (const auto &A : Xs) {
+ // COFF drops visibility entirely, so a Hidden input is not a fixed point
+ // of the merge; resolveEntity() normalizes it away before merging.
+ if (L == &COFF && mergeLinkage(*L, A, A) != A) {
+ continue;
+ }
+ EXPECT_EQ(mergeLinkage(*L, A, A), A);
+ }
+ }
+}
+
} // namespace
diff --git a/clang/unittests/ScalableStaticAnalysis/Serialization/JSONFormatTest/SharedLexicalRepresentationFormatTest.cpp b/clang/unittests/ScalableStaticAnalysis/Serialization/JSONFormatTest/SharedLexicalRepresentationFormatTest.cpp
index 11c388de21157..ddbfb72a8c494 100644
--- a/clang/unittests/ScalableStaticAnalysis/Serialization/JSONFormatTest/SharedLexicalRepresentationFormatTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysis/Serialization/JSONFormatTest/SharedLexicalRepresentationFormatTest.cpp
@@ -74,8 +74,11 @@ TEST(EntitySourceLocationsSummaryTest, EmptyByDefault) {
class SharedLexicalRepresentationFormatTest : public JSONFormatTest {
protected:
- static constexpr EntityLinkage ExternalLinkage =
- EntityLinkage(EntityLinkageType::External);
+ static constexpr EntityLinkage ExternalLinkage = EntityLinkage(
+ EntityLinkageType::External, clang::ssaf::EntityBinding::Strong,
+ clang::ssaf::EntityCoalescing::None,
+ clang::ssaf::EntityVisibility::Default,
+ clang::ssaf::EntityDefinitionKind::Definition);
std::unique_ptr<LUSummary> makeLUSummary() {
NestedBuildNamespace NS(
diff --git a/clang/unittests/ScalableStaticAnalysis/SummaryData/SummaryDataTest.cpp b/clang/unittests/ScalableStaticAnalysis/SummaryData/SummaryDataTest.cpp
index e8137f3bac308..a427e0222b478 100644
--- a/clang/unittests/ScalableStaticAnalysis/SummaryData/SummaryDataTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysis/SummaryData/SummaryDataTest.cpp
@@ -171,8 +171,11 @@ static SummaryDataBuilderRegistry::Add<Analysis4Builder>
class LUSummaryConsumerTest : public TestFixture {
protected:
- static constexpr EntityLinkage ExternalLinkage =
- EntityLinkage(EntityLinkageType::External);
+ static constexpr EntityLinkage ExternalLinkage = EntityLinkage(
+ EntityLinkageType::External, clang::ssaf::EntityBinding::Strong,
+ clang::ssaf::EntityCoalescing::None,
+ clang::ssaf::EntityVisibility::Default,
+ clang::ssaf::EntityDefinitionKind::Definition);
void SetUp() override {
NextSummaryInstanceId = 0;
diff --git a/clang/unittests/ScalableStaticAnalysis/TestFixture.h b/clang/unittests/ScalableStaticAnalysis/TestFixture.h
index cb33dd57a31a9..59efa0666c4ca 100644
--- a/clang/unittests/ScalableStaticAnalysis/TestFixture.h
+++ b/clang/unittests/ScalableStaticAnalysis/TestFixture.h
@@ -9,6 +9,7 @@
#ifndef LLVM_CLANG_UNITTESTS_SCALABLESTATICANALYSIS_TESTFIXTURE_H
#define LLVM_CLANG_UNITTESTS_SCALABLESTATICANALYSIS_TESTFIXTURE_H
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h"
@@ -32,6 +33,35 @@ class TestFixture : public ::testing::Test {
protected:
static WPASuite makeWPASuite() { return WPASuite(); }
+ // EntityLinker's linkage reconciliation rules. They are private to the
+ // linker, which is their only caller in production code, and they consult
+ // the target's LinkageRules, so each takes the linker to apply them with.
+ static bool isConflictingDefinition(const EntityLinker &Linker,
+ const EntityLinkage &Current,
+ const EntityLinkage &Incoming) {
+ return Linker.isConflictingDefinition(Current, Incoming);
+ }
+
+ static bool incomingDataWins(const EntityLinker &Linker,
+ const EntityLinkage &Current,
+ const EntityLinkage &Incoming) {
+ return Linker.incomingDataWins(Current, Incoming);
+ }
+
+ static EntityLinkage mergeLinkage(const EntityLinker &Linker,
+ const EntityLinkage &Current,
+ const EntityLinkage &Incoming) {
+ return Linker.mergeLinkage(Current, Incoming);
+ }
+
+ /// A linker targeting \p Triple, for exercising the reconciliation rules of
+ /// a specific platform.
+ static EntityLinker makeLinkerFor(llvm::StringRef Triple) {
+ return EntityLinker(llvm::Triple(Triple),
+ NestedBuildNamespace{BuildNamespace(
+ BuildNamespaceKind::LinkUnit, "LU")});
+ }
+
#define FIELD(CLASS, FIELD_NAME) \
static const auto &get##FIELD_NAME(const CLASS &X) { return X.FIELD_NAME; } \
static auto &get##FIELD_NAME(CLASS &X) { return X.FIELD_NAME; }
diff --git a/clang/unittests/ScalableStaticAnalysis/WholeProgramAnalysis/AnalysisDriverTest.cpp b/clang/unittests/ScalableStaticAnalysis/WholeProgramAnalysis/AnalysisDriverTest.cpp
index 9e518f08cc7de..c337330b96b4c 100644
--- a/clang/unittests/ScalableStaticAnalysis/WholeProgramAnalysis/AnalysisDriverTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysis/WholeProgramAnalysis/AnalysisDriverTest.cpp
@@ -289,8 +289,11 @@ static AnalysisRegistry::Add<CycleB> RegCycleB("Cyclic analysis B (test only)");
class AnalysisDriverTest : public TestFixture {
protected:
- static constexpr EntityLinkage ExternalLinkage =
- EntityLinkage(EntityLinkageType::External);
+ static constexpr EntityLinkage ExternalLinkage = EntityLinkage(
+ EntityLinkageType::External, clang::ssaf::EntityBinding::Strong,
+ clang::ssaf::EntityCoalescing::None,
+ clang::ssaf::EntityVisibility::Default,
+ clang::ssaf::EntityDefinitionKind::Definition);
void SetUp() override {
NextSummaryInstanceId = 0;
diff --git a/clang/unittests/ScalableStaticAnalysis/WholeProgramAnalysis/UnsafeBufferReachableAnalysisTest.cpp b/clang/unittests/ScalableStaticAnalysis/WholeProgramAnalysis/UnsafeBufferReachableAnalysisTest.cpp
index cf5420b35e7dc..876c6039ed495 100644
--- a/clang/unittests/ScalableStaticAnalysis/WholeProgramAnalysis/UnsafeBufferReachableAnalysisTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysis/WholeProgramAnalysis/UnsafeBufferReachableAnalysisTest.cpp
@@ -40,8 +40,11 @@ class UnsafeBufferReachableAnalysisTest : public TestFixture {
protected:
using EPLEdge = std::pair<EntityPointerLevel, EntityPointerLevel>;
- static constexpr EntityLinkage ExternalLinkage =
- EntityLinkage(EntityLinkageType::External);
+ static constexpr EntityLinkage ExternalLinkage = EntityLinkage(
+ EntityLinkageType::External, clang::ssaf::EntityBinding::Strong,
+ clang::ssaf::EntityCoalescing::None,
+ clang::ssaf::EntityVisibility::Default,
+ clang::ssaf::EntityDefinitionKind::Definition);
std::unique_ptr<LUSummary> makeLUSummary() {
NestedBuildNamespace NS(
diff --git a/docs/cmake_install.cmake b/docs/cmake_install.cmake
new file mode 100644
index 0000000000000..0e4d82fe2618d
--- /dev/null
+++ b/docs/cmake_install.cmake
@@ -0,0 +1,45 @@
+# Install script for directory: /Volumes/avirals-external-drive-1/aviralg-llvm-project/llvm/docs
+
+# Set the install prefix
+if(NOT DEFINED CMAKE_INSTALL_PREFIX)
+ set(CMAKE_INSTALL_PREFIX "/Volumes/avirals-external-drive-1/aviralg-llvm-project/toolchain")
+endif()
+string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
+
+# Set the install configuration name.
+if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME)
+ if(BUILD_TYPE)
+ string(REGEX REPLACE "^[^A-Za-z0-9_]+" ""
+ CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}")
+ else()
+ set(CMAKE_INSTALL_CONFIG_NAME "Debug")
+ endif()
+ message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"")
+endif()
+
+# Set the component getting installed.
+if(NOT CMAKE_INSTALL_COMPONENT)
+ if(COMPONENT)
+ message(STATUS "Install component: \"${COMPONENT}\"")
+ set(CMAKE_INSTALL_COMPONENT "${COMPONENT}")
+ else()
+ set(CMAKE_INSTALL_COMPONENT)
+ endif()
+endif()
+
+# Is this installation the result of a crosscompile?
+if(NOT DEFINED CMAKE_CROSSCOMPILING)
+ set(CMAKE_CROSSCOMPILING "FALSE")
+endif()
+
+# Set path to fallback-tool for dependency-resolution.
+if(NOT DEFINED CMAKE_OBJDUMP)
+ set(CMAKE_OBJDUMP "/usr/bin/objdump")
+endif()
+
+string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT
+ "${CMAKE_INSTALL_MANIFEST_FILES}")
+if(CMAKE_INSTALL_LOCAL_ONLY)
+ file(WRITE "/Volumes/avirals-external-drive-1/aviralg-llvm-project/docs/install_local_manifest.txt"
+ "${CMAKE_INSTALL_MANIFEST_CONTENT}")
+endif()
diff --git a/docs/ssaf-linker-coff-behavior.md b/docs/ssaf-linker-coff-behavior.md
new file mode 100644
index 0000000000000..76270e2de788a
--- /dev/null
+++ b/docs/ssaf-linker-coff-behavior.md
@@ -0,0 +1,505 @@
+# COFF Linker Behavior — Grounding Probes
+
+Probes run against `lld-link` (LLD 24.0.0, built from this tree) to ground the
+`LinkageRules` for `Triple::COFF`. Target: `x86_64-pc-windows-msvc`.
+
+Tools used:
+
+- `CLANG=/Volumes/avirals-external-drive-1/aviralg-llvm-project/bin/clang`
+- `CLANGXX=/Volumes/avirals-external-drive-1/aviralg-llvm-project/bin/clang++`
+- `LINK=/Volumes/avirals-external-drive-1/aviralg-llvm-project/bin/lld-link`
+- `NM=/Volumes/avirals-external-drive-1/aviralg-llvm-project/bin/llvm-nm`
+- `RO=/Volumes/avirals-external-drive-1/aviralg-llvm-project/bin/llvm-readobj`
+
+All objects compiled with:
+
+```
+$CLANG --target=x86_64-pc-windows-msvc -c -O0 <src> -o <obj>
+```
+
+All links use `/entry:mainCRTStartup /subsystem:console /nodefaultlib` to avoid
+needing the MSVC runtime, which is not present on this host.
+
+---
+
+## 1. Binding precedence and the duplicate-definition error
+
+### Program
+
+`strong_a.c` → `int f(void) { return 1; }`
+`strong_b.c` → `int f(void) { return 2; }`
+`weak_a.c` → `__attribute__((weak)) int f(void) { return 1; }`
+`weak_b.c` → `__attribute__((weak)) int f(void) { return 2; }`
+
+`main.c`
+```c
+int f(void);
+int mainCRTStartup(void) { return f(); }
+```
+
+### Commands and output
+
+```
+$ $NM strong_a.obj | grep " f$"
+00000000 T f
+$ $NM weak_a.obj | grep -i " f$"
+00000000 W f
+```
+
+```
+$ $LINK /out:out_c1.exe ... main.obj strong_a.obj strong_b.obj
+lld-link: error: duplicate symbol: f
+>>> defined at strong_a.obj
+>>> defined at strong_b.obj
+
+$ $LINK /out:out_c2.exe ... main.obj strong_a.obj weak_b.obj # strong + weak
+ (no output — success)
+
+$ $LINK /out:out_c3.exe ... main.obj weak_a.obj strong_b.obj # weak + strong
+ (no output — success)
+
+$ $LINK /out:out_c4.exe ... main.obj weak_a.obj weak_b.obj # weak + weak
+lld-link: error: duplicate symbol: .weak.f.default
+>>> defined at weak_a.obj
+>>> defined at weak_b.obj
+
+$ $LINK /out:out_c1b.exe ... /force:multiple main.obj strong_a.obj strong_b.obj
+lld-link: warning: duplicate symbol: f
+>>> defined at strong_a.obj
+>>> defined at strong_b.obj
+```
+
+### Inference
+
+- **Strong + strong is an error**, as on ELF and Mach-O.
+- **Strong + weak is fine**, as elsewhere.
+- **Weak + weak is an ERROR on COFF.** This is a genuine three-way divergence:
+ ELF probe P4 and Mach-O probe M4 both accepted two weak definitions and kept
+ the first. COFF's `__attribute__((weak))` is emulated with an alias symbol
+ (note the mangled name `.weak.f.default` in the diagnostic), and two such
+ aliases collide.
+
+ Consequence: the conflict predicate "both non-weak" is **wrong for COFF**.
+ On COFF, weakness does not license duplicate definitions — only COMDAT does
+ (see section 2).
+
+- `/force:multiple` downgrades the error to a warning, the COFF analogue of
+ ELF's `-z muldefs`, and matches our `WarnOnMultipleDefinitions` flag.
+
+---
+
+## 2. COMDAT: how ODR is represented, and what actually licenses duplicates
+
+### Program
+
+`inl.h`
+```c
+inline int inl(void) { return 42; }
+```
+
+`inl_a.cpp` / `inl_b.cpp` — each `#include "inl.h"` and call `inl()`.
+
+`inlmain.cpp`
+```c
+int use_a(void); int use_b(void);
+extern "C" int mainCRTStartup(void) { return use_a() + use_b(); }
+```
+
+For the COMDAT-vs-regular case:
+
+`inl_comdat_only.cpp`
+```c
+inline int inl(void) { return 42; }
+int use_c(void) { return inl(); }
+```
+
+`inl_regular.cpp`
+```c
+int inl(void) { return 99; }
+```
+
+`k4main.cpp`
+```c
+int use_c(void);
+extern "C" int mainCRTStartup(void) { return use_c(); }
+```
+
+### Commands and output
+
+The inline function's symbol class and section characteristics:
+
+```
+$ $NM inl_a.obj | grep -i "inl@@"
+00000000 T ?inl@@YAHXZ # T = strong/external, NOT W
+
+$ $RO --sections inl_a.obj | grep -A8 "IMAGE_SCN_LNK_COMDAT"
+ Characteristics [ (0x60501020)
+ IMAGE_SCN_ALIGN_16BYTES (0x500000)
+ IMAGE_SCN_CNT_CODE (0x20)
+ IMAGE_SCN_LNK_COMDAT (0x1000)
+ IMAGE_SCN_MEM_EXECUTE (0x20000000)
+ IMAGE_SCN_MEM_READ (0x40000000)
+ ]
+
+$ $RO --symbols inl_a.obj | awk '/Name: \.text/{f=1} f&&/Selection/{print; f=0}'
+ Selection: 0x0
+ Selection: Any (0x2) # IMAGE_COMDAT_SELECT_ANY
+```
+
+Two COMDAT copies coalesce silently:
+
+```
+$ $LINK /out:out_ci1.exe ... inlmain.obj inl_a.obj inl_b.obj
+ (no output — success)
+```
+
+COMDAT against a *regular* definition of the same symbol:
+
+```
+$ $LINK /out:o_k4.exe ... k4main.obj inl_comdat_only.obj inl_regular.obj
+lld-link: error: duplicate symbol: int __cdecl inl(void)
+>>> defined at inl_comdat_only.obj
+>>> defined at inl_regular.obj
+```
+
+### Inference
+
+This is the most important COFF result for our model.
+
+- **An ODR definition on COFF keeps `Strong` binding.** `nm` reports `T`, not
+ `W`. Compare ELF (`W` + COMDAT group) and Mach-O (`WeakDef` flag). So the
+ binding value emitted for `inline` is **target-dependent**: `Weak` on
+ ELF/Mach-O, `Strong` on COFF.
+
+- **COMDAT, not binding, is what licenses duplicate definitions on COFF.**
+ Two COMDAT copies coalesce (CI1); COMDAT + regular is a duplicate error (K4);
+ and from section 1, weak + weak is *also* an error. So the COFF conflict
+ predicate is:
+
+ conflict <=> NOT (both definitions are COMDAT)
+
+ which is exactly `SymbolTable::addComdat` at `lld/COFF/SymbolTable.cpp:924`:
+ `if (!existingSymbol->isCOMDAT) reportDuplicate(s, f);`
+
+- **This is the strongest justification for splitting `EntityCoalescing` out of
+ `EntityBinding`.** On COFF an ODR definition is `Strong` + `ODR`. A single
+ ordered enum cannot represent it: as `Strong` alone, two inline definitions
+ would wrongly conflict; as a `WeakODR` rank below `Strong`, the binding would
+ wrongly lose to a regular definition instead of erroring. Only two
+ independent fields give the right answer for both K4 and CI1.
+
+- `Selection: Any (0x2)` is `IMAGE_COMDAT_SELECT_ANY`. COFF has five other
+ selection kinds (`SAME_SIZE`, `EXACT_MATCH`, `ASSOCIATIVE`, `LARGEST`,
+ `NODUPLICATES`), which is why modelling coalescing as an enum rather than a
+ bool leaves room to grow.
+
+---
+
+## 3. Common symbols
+
+### Program
+
+`common_a.c` → `int g;`, `common_big.c` → `long long g[4];`,
+`def_g.c` → `int g = 7;`, `weakdef_g.c` → `__attribute__((weak)) int g = 9;`,
+`mainj.c` → `extern int g; int mainCRTStartup(void) { return g; }`
+
+Compiled with `-fcommon`.
+
+### Commands and output
+
+```
+$ for x in common_a common_big def_g weakdef_g; do $NM $x.obj | grep " g$"; done
+00000004 C g # common_a
+00000020 C g # common_big
+00000000 D g # def_g
+00000000 W g # weakdef_g
+```
+
+```
+$ $LINK /out:o_k1.exe ... mainj.obj common_a.obj common_big.obj # common + common
+ (no output — success)
+$ $LINK /out:o_k2.exe ... mainj.obj common_a.obj def_g.obj # common + strong
+ (no output — success)
+$ $LINK /out:o_k3.exe ... mainj.obj weakdef_g.obj common_big.obj # weak + common
+ (no output — success)
+```
+
+### Inference
+
+COFF accepts all three combinations without diagnostics, matching
+`SymbolTable::addCommon` at `lld/COFF/SymbolTable.cpp:939`, which replaces the
+existing symbol only when the incoming common is larger and otherwise silently
+keeps what it has. Commons never conflict on any of the three platforms.
+
+---
+
+## 4. Undefined symbols
+
+### Program
+
+`undef_main.c`
+```c
+int missing(void);
+int mainCRTStartup(void) { return missing(); }
+```
+
+### Commands and output
+
+```
+$ $LINK /out:o_ku1.exe ... undef_main.obj
+lld-link: error: undefined symbol: missing
+>>> referenced by undef_main.obj:(mainCRTStartup)
+
+$ $LINK /out:o_ku2.exe ... /force:unresolved undef_main.obj
+lld-link: warning: undefined symbol: missing
+>>> referenced by undef_main.obj:(mainCRTStartup)
+
+$ $LINK /dll /out:o_ku3.dll /noentry ... undef_main.obj
+lld-link: error: undefined symbol: missing
+>>> referenced by undef_main.obj:(mainCRTStartup)
+```
+
+### Inference
+
+- An unresolved reference is an error, downgradable to a warning with
+ `/force:unresolved` — a two-state policy, unlike ELF's three-state
+ `--unresolved-symbols`.
+- **Building a DLL does not relax the rule** (KU3), matching Mach-O and
+ differing from ELF, where `-shared` accepted unresolved references. Windows
+ DLLs must be fully resolved at link time.
+
+So on undefined symbols, ELF is the outlier: two of three platforms require
+full resolution regardless of the output artifact kind.
+
+---
+
+## 5. Visibility
+
+COFF has no symbol visibility field at all. Export from a DLL is controlled by
+`__declspec(dllexport)` / `.def` files, which is an unrelated mechanism
+operating per-DLL rather than a per-symbol attribute merged during resolution.
+
+There is therefore no COFF analogue of `EntityVisibility::Hidden` or
+`::Protected`, and no visibility merge rule to emulate. A summary carrying
+anything other than `Default` for a COFF target is not representable on the
+platform.
+
+---
+
+## 6. Second pass: ordering details
+
+Section 3 established that no combination of commons errors, but did not
+establish *which* definition prevails. These probes settle the rank table.
+
+### 6.1 Which definition wins: common vs strong vs weak
+
+`k_read.c`
+```c
+extern int g;
+int mainCRTStartup(void) { return g; }
+```
+
+Payloads are distinguishable: `def_g.c` has `g = 7`, `weakdef_g.c` has `g = 9`,
+and commons are zero-initialised. `/map:` output names the winning object.
+
+```
+$ $LINK /out:o_k2.exe ... /map:k2.map k_read.obj common_a.obj def_g.obj
+$ grep -iE "\bg\b" k2.map
+ 0002:00000000 g 0000000140002000 def_g.obj
+
+$ $OD -s -j .data o_k2.exe
+ 140002000 07000000 00000000 ........ # value 7 = strong def
+
+$ $LINK /out:o_k3.exe ... /map:k3.map k_read.obj weakdef_g.obj common_big.obj
+$ grep -iE "\bg\b" k3.map
+ 0002:00000000 .weak.g.default 0000000140002000 weakdef_g.obj
+ 0000:00000000 g 0000000140002020 <common>
+
+$ $LINK /out:o_k1.exe ... /map:k1.map k_read.obj common_a.obj common_big.obj
+$ grep -iE "\bg\b" k1.map
+ 0000:00000000 g 0000000140002020 <common>
+$ $OD -h o_k1.exe | grep .data
+ 1 .data 00000000 ... # commons live in .bss
+```
+
+Section sizes corroborate: `o_k2` has an 8-byte `.data` (the 4-byte strong
+definition, padded), while `o_k3` has 0x40 — large enough for the 32-byte
+common.
+
+### Inference
+
+- **Strong beats common** (K2: `g` resolves to `def_g.obj`, value 7).
+- **Common beats weak** (K3: `g` resolves to `<common>`, and the weak
+ definition survives only as the alias symbol `.weak.g.default`, unreferenced).
+- **Two commons merge**, taking the larger (K1).
+
+So COFF's binding order matches **ELF**, not Mach-O: `Common` outranks `Weak`.
+Mach-O is the sole platform where a weak definition displaces a common.
+
+### 6.2 Weak declaration + common definition
+
+Same program as the ELF doc section 6.1, compiled with
+`-fcommon -DMAINSYM=mainCRTStartup`.
+
+```
+$ $NM w_weakdecl.obj | grep -i " g$"
+00000000 W g
+$ $NM w_commondef.obj | grep -i " g$"
+00000004 C g
+
+$ $LINK /out:w_out.exe ... /map:w.map w_rd.obj w_weakdecl.obj w_commondef.obj
+$ grep -iE "\bg\b" w.map
+ 0002:00000000 00000008H .rdata$.refptr.g DATA
+ 0000:00000000 .weak.g.default.use 0000000000000000 <absolute>
+ 0002:00000000 .refptr.g 0000000140002000 w_weakdecl.obj
+ 0000:00000000 g 0000000140003000 <common>
+```
+
+**Inference.** `g` resolves to `<common>`; the weak declaration contributes only
+an unreferenced alias. Same conclusion as ELF and Mach-O — the binding join is
+scoped to defining occurrences.
+
+### COFF rank tables
+
+Derived from sections 1, 2, 3 and 6:
+
+| `EntityBinding` | rank | evidence |
+|---|---|---|
+| `Undefined` | 0 | any definition beats an undefined reference (KU1) |
+| `Weak` | 1 | common beats weak (K3) |
+| `Common` | 2 | strong beats common (K2); common beats weak (K3) |
+| `Strong` | 3 | strong wins everything (K2) |
+
+Same as ELF.
+
+| `EntityCoalescing` | rank | evidence |
+|---|---|---|
+| `None` | 0 | COMDAT + regular is an error, so neither "wins" — see below |
+| `ODR` | 1 | COMDAT + COMDAT coalesces (CI1) |
+
+`EntityVisibility`: not representable. Any value other than `Default` for a
+COFF target has no meaning on the platform.
+
+### Conflict predicate
+
+COFF's is **not** derivable from the binding ranks. Collected results:
+
+| case | result | probe |
+|---|---|---|
+| strong + strong | error | C1 |
+| strong + weak | ok | C2/C3 |
+| weak + weak | **error** | C4 |
+| COMDAT + COMDAT | ok | CI1 |
+| COMDAT + regular | error | K4 |
+| common + anything | ok | K1/K2/K3 |
+
+`conflict ⟺ NOT both ODR` fails on strong+weak; `conflict ⟺ neither is weak`
+fails on weak+weak. The rule that fits every row is:
+
+```
+both are definitions
+ AND neither is Common
+ AND NOT (both are ODR)
+ AND NOT (exactly one is Weak)
+```
+
+The weak clauses follow from COFF emulating weak symbols with an alias — the
+diagnostic in C4 names `.weak.f.default`, so two weak definitions collide on
+the alias while one weak plus one strong does not.
+
+This is why `LinkageRules` must expose `isConflictingDefinition` as its own
+virtual rather than deriving conflicts from the rank tables.
+
+---
+
+## 7. Third pass: visibility on common symbols
+
+Section 5 asserted that COFF has no visibility concept. This probe confirms
+that a `visibility("hidden")` attribute has no effect on a COFF target, rather
+than being silently encoded somewhere.
+
+`hc_hidden_common.c`
+```c
+__attribute__((visibility("hidden"))) int g;
+```
+
+`hc_default_common.c`
+```c
+int g;
+```
+
+`hc_read.c`
+```c
+extern int g;
+int MAINSYM(void) { return g; }
+```
+
+Compiled with `-fcommon -DMAINSYM=mainCRTStartup`.
+
+```
+$ $NM w_hc.obj | grep -i " g$"
+00000004 C g # identical class to the default-visibility one
+
+$ $LINK /out:w_hc.exe ... /map:whc.map w_rd.obj w_hc.obj w_dc.obj
+$ grep -iE "\bg\b" whc.map
+ 0000:00000000 g 0000000140002000 <common>
+```
+
+### Inference
+
+The hidden common is indistinguishable from a default-visibility common in the
+object file (both plain `C`), and clang emits no diagnostic. Visibility is
+simply dropped on this target.
+
+Confirms that `EntityVisibility` needs no rank table for COFF: any value is
+equivalent to `Default`. A summary carrying `Hidden` or `Protected` for a COFF
+target is a mis-extraction, since clang could not have produced it from source.
+
+---
+
+## 8. Fifth pass: weak declarations are not undefined references
+
+Same program as ELF §9 (`strongdecl.c`, `weakdecl.c`, `dmain.c`), compiled with
+`-DMAINSYM=mainCRTStartup`.
+
+```
+$ $NM w_sd.obj | grep -i " g$"
+ U g # undefined
+$ $NM w_wd.obj | grep -i " g$"
+00000000 W g # weak external — NOT undefined
+
+$ $RO --symbols w_wd.obj | grep -A10 "Name: g$"
+ Name: g
+ Section: IMAGE_SYM_UNDEFINED (0)
+ StorageClass: WeakExternal (0x69)
+ AuxSymbolCount: 1
+ AuxWeakExternal {
+ Linked: .weak.g.default.use_weak_decl (17)
+ Search: Alias (0x3)
+ }
+
+$ $LINK /out:w_d1.exe ... /map:wd1.map w_dm.obj w_wd.obj w_sd.obj
+ (no output — success, either order)
+$ grep -iE "\bg\b" wd1.map
+ 0000:00000000 g 0000000000000000 <absolute>
+ 0000:00000000 .weak.g.default.use_weak_decl 0000000000000000 <absolute>
+```
+
+### Inference
+
+**COFF does not error here, unlike ELF and Mach-O.** A COFF weak external is
+not an undefined reference: it carries an `AuxWeakExternal` record naming a
+*default* alias to bind to when no real definition is found. The link succeeds
+with `g` resolved to an absolute 0.
+
+This is the same alias mechanism that makes two weak *definitions* collide
+(§1, C4). COFF's `__attribute__((weak))` is emulation rather than a native
+binding, and it behaves differently from ELF/Mach-O weakness in both
+directions: more permissive for declarations, less permissive for definitions.
+
+For our model this is a Phase 3 concern rather than a join concern — the merged
+binding still follows `argmax strengthRank` — but it means undefined-symbol
+reporting should not assume COFF would have errored where ELF does.
+
+
+
diff --git a/docs/ssaf-linker-elf-behavior.md b/docs/ssaf-linker-elf-behavior.md
new file mode 100644
index 0000000000000..eb7ac558a560b
--- /dev/null
+++ b/docs/ssaf-linker-elf-behavior.md
@@ -0,0 +1,742 @@
+# ELF Linker Behavior — Grounding Probes
+
+Probes run against `ld.lld` (LLD 24.0.0, built from this tree) to ground the
+`LinkageRules` for `Triple::ELF`. Target: `x86_64-unknown-linux-gnu`.
+
+Tools used:
+
+- `CLANG=/Volumes/avirals-external-drive-1/aviralg-llvm-project/bin/clang`
+- `LLD=/Volumes/avirals-external-drive-1/aviralg-llvm-project/bin/ld.lld`
+- `RO=/Volumes/avirals-external-drive-1/aviralg-llvm-project/bin/llvm-readelf`
+- `NM=/Volumes/avirals-external-drive-1/aviralg-llvm-project/bin/llvm-nm`
+- `OD=/Volumes/avirals-external-drive-1/aviralg-llvm-project/bin/llvm-objdump`
+
+All objects compiled with:
+
+```
+$CLANG --target=x86_64-unknown-linux-gnu -c -O0 <src> -o <obj>
+```
+
+---
+
+## 1. Binding precedence and the duplicate-definition error
+
+### Program
+
+`strong_a.c`
+```c
+int f(void) { return 1; }
+```
+
+`strong_b.c`
+```c
+int f(void) { return 2; }
+```
+
+`weak_a.c`
+```c
+__attribute__((weak)) int f(void) { return 1; }
+```
+
+`weak_b.c`
+```c
+__attribute__((weak)) int f(void) { return 2; }
+```
+
+`main.c`
+```c
+int f(void);
+int _start(void) { return f(); }
+```
+
+### Commands and output
+
+```
+$ $LLD -o out_p1 main.o strong_a.o strong_b.o
+ld.lld: error: duplicate symbol: f
+>>> defined at strong_a.c
+>>> strong_a.o:(f)
+>>> defined at strong_b.c
+>>> strong_b.o:(.text+0x0)
+exit=1
+
+$ $LLD -o out_p2 main.o strong_a.o weak_b.o
+exit=0
+
+$ $LLD -o out_p3 main.o weak_a.o strong_b.o
+exit=0
+
+$ $LLD -o out_p4 main.o weak_a.o weak_b.o
+exit=0
+
+$ $LLD -z muldefs -o out_p1b main.o strong_a.o strong_b.o
+exit=0
+```
+
+Which definition prevailed (`movl $0x1` = `_a`, `movl $0x2` = `_b`):
+
+```
+$ $OD -d --disassemble-symbols=f out_p2
+00000000002011b0 <f>:
+ 2011b4: b8 01 00 00 00 movl $0x1, %eax # strong_a won
+
+$ $OD -d --disassemble-symbols=f out_p3
+00000000002011c0 <f>:
+ 2011c4: b8 02 00 00 00 movl $0x2, %eax # strong_b won
+
+$ $OD -d --disassemble-symbols=f out_p4
+00000000002011b0 <f>:
+ 2011b4: b8 01 00 00 00 movl $0x1, %eax # weak_a won (first)
+
+$ $OD -d --disassemble-symbols=f out_p1b
+00000000002011b0 <f>:
+ 2011b4: b8 01 00 00 00 movl $0x1, %eax # strong_a won (first)
+```
+
+### Inference
+
+- **Conflict predicate: two non-weak definitions.** Only `strong+strong`
+ errors. This matches `Symbol::checkDuplicate` in `lld/ELF/Symbols.cpp:600`:
+ `if (!isWeak() && !other.isWeak()) reportDuplicate(...)`.
+- **Strong beats weak regardless of link order.** P2 (strong first) and P3
+ (weak first) both keep the strong body. So the winner is decided by binding
+ strength, not by position — our `incomingDataWins` must not be
+ order-sensitive when strengths differ.
+- **Ties keep the first definition.** P4 (weak+weak) and P1b (two strongs under
+ `-z muldefs`) both keep the *first* one on the command line. This grounds our
+ tie-break rule of "keep the data already linked".
+- **`-z muldefs` downgrades the error and keeps the first definition.** This is
+ exactly our `WarnOnMultipleDefinitions` flag, and it confirms the semantics
+ we chose: on conflict, ignore the incoming occurrence entirely.
+
+---
+
+## 2. Common symbols
+
+### Program
+
+`common_a.c`
+```c
+int g;
+```
+
+`common_big.c`
+```c
+long long g[4];
+```
+
+`def_g.c`
+```c
+int g = 7;
+```
+
+`weakdef_g.c`
+```c
+__attribute__((weak)) int g = 9;
+```
+
+`mainj.c`
+```c
+extern int g;
+int _start(void) { return g; }
+```
+
+Compiled with `-fcommon` so tentative definitions become real common symbols.
+
+### Commands and output
+
+Symbol classes in the objects (`C` = common, `D` = data, `V` = weak data):
+
+```
+$ $NM common_a.o | grep " g$"
+0000000000000004 C g
+$ $NM common_big.o | grep " g$"
+0000000000000020 C g
+$ $NM def_g.o | grep " g$"
+0000000000000000 D g
+$ $NM weakdef_g.o | grep " g$"
+0000000000000000 V g
+```
+
+Resolution:
+
+```
+$ $LLD -o out_c1 mainj.o common_a.o common_big.o # common(4) + common(32)
+exit=0
+$ $RO --symbols out_c1 | grep " g$"
+ 5: 0000000000203220 32 OBJECT GLOBAL DEFAULT 5 g
+
+$ $LLD -o out_c2 mainj.o common_a.o def_g.o # common(4) + strong data(4)
+exit=0
+$ $RO --symbols out_c2 | grep " g$"
+ 5: 0000000000203220 4 OBJECT GLOBAL DEFAULT 5 g
+
+$ $LLD -o out_c2b mainj.o common_big.o def_g.o # common(32) + strong data(4)
+exit=0
+$ $RO --symbols out_c2b | grep " g$"
+ 5: 0000000000203220 4 OBJECT GLOBAL DEFAULT 5 g
+
+$ $LLD -o out_c3 mainj.o weakdef_g.o common_big.o # weak data(4) + common(32)
+exit=0
+$ $RO --symbols out_c3 | grep " g$"
+ 5: 0000000000203230 32 OBJECT GLOBAL DEFAULT 6 g
+```
+
+### Inference
+
+- **common + common merges with `size = max`** (C1: 4 and 32 give 32). Matches
+ `Symbol::resolve(const CommonSymbol &)` at `lld/ELF/Symbols.cpp:618-626`.
+- **A strong definition beats common regardless of size** (C2b: common of 32
+ loses to strong data of 4, final size 4). So this is a strength comparison,
+ not a size comparison — `Strong > Common`.
+- **Common beats a weak definition** (C3: final size 32, the common's). This is
+ the `isDefined() && !isWeak()` early-return at `:612` — a *weak* definition
+ does not stop the common from overwriting it. So `Common > Weak`.
+- No case here errors: two commons are legal (only `--warn-common` mentions
+ them). Our conflict predicate must not fire on commons.
+
+Net ELF strength order: **`Undefined < Weak < Common < Strong`**, which is the
+order our `EntityBinding` enum should encode.
+
+---
+
+## 3. Inline functions: how ODR is represented
+
+### Program
+
+`inl.h`
+```c
+inline int inl(void) { return 42; }
+```
+
+`inl_a.cpp`
+```c
+#include "inl.h"
+int use_a(void) { return inl(); }
+```
+
+`inl_b.cpp`
+```c
+#include "inl.h"
+int use_b(void) { return inl(); }
+```
+
+### Commands and output
+
+```
+$ $NM inl_a.o | grep -i inl
+0000000000000000 W _Z3inlv
+
+$ $RO --symbols inl_a.o | grep "_Z3inlv"
+ 3: 0000000000000000 0 SECTION LOCAL DEFAULT 5 .text._Z3inlv
+ 5: 0000000000000000 11 FUNC WEAK DEFAULT 5 _Z3inlv
+
+$ $RO --section-groups inl_a.o
+COMDAT group section [ 4] `.group' [_Z3inlv] contains 1 sections:
+ [Index] Name
+ [ 5] .text._Z3inlv
+```
+
+### Inference
+
+**There is no `WeakODR` binding on ELF.** An `inline` function is emitted as
+`STB_WEAK` (binding) *plus* a COMDAT group (coalescing). The two properties are
+carried by separate mechanisms in the object file.
+
+This is the direct justification for splitting `EntityBinding` and
+`EntityCoalescing` into independent fields: an ODR definition and a plain
+`__attribute__((weak))` definition have *identical* binding, and differ only in
+whether the copies are guaranteed identical. Ranking `WeakODR` above `Weak`, as
+a single enum forces, models a precedence the linker does not have.
+
+---
+
+## 4. Visibility merging
+
+### Program
+
+`vis_def.c`
+```c
+int v(void) { return 1; }
+```
+
+`vis_hidden_decl.c`
+```c
+__attribute__((visibility("hidden"))) int v(void);
+int caller(void) { return v(); }
+```
+
+`vis_prot_decl.c`
+```c
+__attribute__((visibility("protected"))) int v(void);
+int caller2(void) { return v(); }
+```
+
+Compiled with `-fPIC`; linked with `-shared` so the export table is meaningful.
+
+### Commands and output
+
+```
+$ $LLD -shared -o out_v2.so vis_def.o # control: default only
+$ $RO --dyn-syms out_v2.so | grep " v$"
+ 1: 0000000000001280 11 FUNC GLOBAL DEFAULT 6 v
+
+$ $LLD -shared -o out_v1.so vis_def.o vis_hidden_decl.o # default def + hidden decl
+exit=0
+$ $RO --dyn-syms out_v1.so | grep " v$"
+ (v NOT exported)
+$ $RO --symbols out_v1.so | grep " v$"
+ 4: 00000000000012a0 11 FUNC LOCAL HIDDEN 6 v
+
+$ $LLD -shared -o out_v3.so vis_def.o vis_prot_decl.o # default def + protected decl
+exit=0
+$ $RO --dyn-syms out_v3.so | grep " v$"
+ 1: 00000000000012c0 11 FUNC GLOBAL PROTECTED 6 v
+```
+
+### Inference
+
+- **Visibility merges to the most restrictive across *all* occurrences,
+ including ones that lose symbol resolution.** In V1 the definition has
+ default visibility and only an *undefined declaration* is hidden, yet the
+ merged symbol becomes `LOCAL HIDDEN`. This matches `Symbol::mergeProperties`
+ (`:415`) and the visibility handling at the top of each `resolve()` overload:
+ `setVisibility(v == STV_DEFAULT ? ov : std::min(v, ov))` over
+ `STV_DEFAULT=0 < STV_INTERNAL=1 < STV_HIDDEN=2 < STV_PROTECTED=3`.
+
+ Note the ELF encoding orders `PROTECTED` *above* `HIDDEN` numerically but
+ `min` is applied, and `DEFAULT` is special-cased — so the effective
+ restrictiveness order is `DEFAULT < PROTECTED < HIDDEN`, matching our
+ `EntityVisibility` numbering with `std::max`.
+
+- **This is the grounding for "Hidden blocks cross-LU resolution".** A hidden
+ symbol is removed from the dynamic symbol table entirely: it resolves within
+ the link unit and is invisible past its boundary. Our LU analogue is that a
+ `Hidden` entity must not be resolvable by a later link stage.
+
+- **`Protected` is exported but distinct from `Default`.** V3 keeps `v` in the
+ dynamic table with `PROTECTED`, meaning callers inside the DSO bind directly
+ and it cannot be interposed. This is a real, ELF-only third state — it is not
+ representable on Mach-O or COFF.
+
+---
+
+## 5. Undefined symbols
+
+### Program
+
+`undef_main.c`
+```c
+int missing(void);
+int _start(void) { return missing(); }
+```
+
+`undef_weak_main.c`
+```c
+__attribute__((weak)) int maybe_missing(void);
+int _start(void) { return maybe_missing ? maybe_missing() : 0; }
+```
+
+### Commands and output
+
+```
+$ $LLD -o out_u1 undef_main.o # strong ref, executable
+ld.lld: error: undefined symbol: missing
+>>> referenced by undef_main.c
+>>> undef_main.o:(_start)
+exit=1
+
+$ $LLD -o out_u2 undef_weak_main.o # weak ref, executable
+exit=0
+
+$ $LLD -shared -o out_u3.so undef_main.o # strong ref, shared lib
+exit=0
+
+$ $LLD --unresolved-symbols=ignore-all -o out_u4 undef_main.o
+exit=0
+
+$ $LLD --warn-unresolved-symbols -o out_u5 undef_main.o
+ld.lld: warning: undefined symbol: missing
+>>> referenced by undef_main.c
+>>> undef_main.o:(_start)
+exit=0
+```
+
+### Inference
+
+- **An unresolved strong reference is an error when linking an executable**
+ (U1), but **not** when linking a shared library (U3), where it is expected to
+ be satisfied at load time.
+- **An unresolved *weak* reference is never an error** (U2) — it resolves to
+ address 0 and the program tests for it.
+- The policy is configurable three ways (`ReportError`/`Warn`/`Ignore`, see
+ `UnresolvedPolicy` in `lld/ELF/Config.h:115`), and ELF keeps **two separate
+ policies**: `unresolvedSymbols` and `unresolvedSymbolsInShlib` (`:468-469`).
+
+For our linker this means undefined-symbol reporting needs:
+
+1. A tri-state policy, not a bool — matching `--unresolved-symbols`.
+2. Suppression for entities whose merged binding is `Weak` (an undefined weak
+ reference is legal by design).
+3. Awareness that "is this LU a final executable or an intermediate library?"
+ changes the answer. An LU that is later combined with others is the
+ shared-library case, where unresolved references are normal.
+
+Point 3 is significant for SSAF: a single LU is almost always an intermediate
+artifact, so the default should be permissive, with the strict check applied
+only when the link unit is known to be complete.
+
+---
+
+## 6. Second pass: ordering details
+
+These probes were added to pin down the exact rank tables for `LinkageRules`,
+rather than inferring them from the LLD source.
+
+### 6.1 Weak declaration + common definition
+
+The binding join must not simply take the strongest binding across *all*
+occurrences, because a declaration's binding describes how the reference binds,
+not the definition. This probe isolates that case.
+
+`weakdecl.c`
+```c
+__attribute__((weak)) extern int g;
+int use(void) { return g; }
+```
+
+`commondef.c`
+```c
+int g;
+```
+
+`rd.c`
+```c
+extern int g; int use(void);
+int MAINSYM(void) { return g + use(); }
+```
+
+Compiled with `-fcommon -DMAINSYM=_start`.
+
+```
+$ $NM e_weakdecl.o | grep " g$"
+ w g # lowercase w = weak UNDEFINED
+$ $NM e_commondef.o | grep " g$"
+0000000000000004 C g
+
+$ $LLD -o e_out e_rd.o e_weakdecl.o e_commondef.o
+exit=0
+$ $RO --symbols e_out | grep " g$"
+ 5: 0000000000203270 4 OBJECT GLOBAL DEFAULT 5 g
+```
+
+**Inference.** The merged symbol is `GLOBAL`, not `WEAK`: the weak *declaration*
+contributed nothing to the binding. The definition alone determined it.
+
+This confirms the binding join must be scoped to defining occurrences:
+
+```
+if any occurrence is a Definition: join over the defining occurrences only
+else: join over the declarations
+```
+
+Without the gate, a Mach-O-style ordering (where `Weak` outranks `Common`)
+would produce `Weak` here, wrongly describing a common definition as weak.
+
+### 6.2 Protected vs Hidden restrictiveness
+
+Section 4 established `Default` is least restrictive, but compared each of
+`Hidden` and `Protected` only against `Default`. This probe compares them
+directly, in both orders.
+
+`vp_def_prot.c`
+```c
+__attribute__((visibility("protected"))) int v(void) { return 1; }
+```
+
+`vp_decl_hidden.c`
+```c
+__attribute__((visibility("hidden"))) int v(void);
+int c1(void) { return v(); }
+```
+
+`vp_def_hidden.c`
+```c
+__attribute__((visibility("hidden"))) int v(void) { return 1; }
+```
+
+`vp_decl_prot.c`
+```c
+__attribute__((visibility("protected"))) int v(void);
+int c2(void) { return v(); }
+```
+
+```
+$ $LLD -shared -o o_vp1.so vp_def_prot.o vp_decl_hidden.o # protected def + hidden decl
+$ $RO --symbols o_vp1.so | grep " v$"
+ 4: 00000000000012a0 11 FUNC LOCAL HIDDEN 6 v
+
+$ $LLD -shared -o o_vp2.so vp_def_hidden.o vp_decl_prot.o # hidden def + protected decl
+$ $RO --symbols o_vp2.so | grep " v$"
+ 4: 00000000000012a0 11 FUNC LOCAL HIDDEN 6 v
+```
+
+**Inference.** `Hidden` wins over `Protected` in both orders, so the merge is
+commutative and the ELF restrictiveness order is:
+
+ Default < Protected < Hidden
+
+Note this is *not* the numeric order of the ELF constants
+(`STV_DEFAULT=0, STV_INTERNAL=1, STV_HIDDEN=2, STV_PROTECTED=3`) — LLD applies
+`min` with `STV_DEFAULT` special-cased, which yields the order above. The rank
+table must encode the observed behaviour, not the ELF constant values.
+
+### ELF rank tables
+
+Derived from sections 1, 2, 4 and 6:
+
+| `EntityBinding` | rank | evidence |
+|---|---|---|
+| `Undefined` | 0 | any definition beats an undefined reference (U1/U2) |
+| `Weak` | 1 | common beats weak def (C3) |
+| `Common` | 2 | strong beats common (C2b), common beats weak (C3) |
+| `Strong` | 3 | duplicate error only for strong+strong (P1) |
+
+| `EntityVisibility` | rank | evidence |
+|---|---|---|
+| `Default` | 0 | V1: hidden decl demotes default def |
+| `Protected` | 1 | VP1/VP2: loses to hidden |
+| `Hidden` | 2 | VP1/VP2: wins both orders |
+
+Conflict predicate: **both occurrences are definitions and neither is `Weak`**
+(P1 errors; P2/P3/P4 do not; commons never conflict).
+
+---
+
+## 7. Third pass: visibility on common symbols
+
+Section 4 established the visibility merge using function definitions. This
+probe confirms it applies to commons too, which are handled by a separate
+`resolve()` overload in LLD.
+
+`hc_hidden_common.c`
+```c
+__attribute__((visibility("hidden"))) int g;
+```
+
+`hc_default_common.c`
+```c
+int g;
+```
+
+Compiled with `-fcommon -fPIC`.
+
+```
+$ $NM e_hc.o | grep " g$"
+0000000000000004 C g
+
+$ $LLD -shared -o e_hc.so e_hc.o e_dc.o
+$ $RO --symbols e_hc.so | grep " g$"
+ 4: 00000000000032b8 4 OBJECT LOCAL HIDDEN 8 g
+$ $RO --dyn-syms e_hc.so | grep " g$"
+ (NOT exported)
+```
+
+### Inference
+
+A hidden common merged with a default-visibility common yields `LOCAL HIDDEN`
+and is not exported — the same most-restrictive rule as for function
+definitions. This matches `Symbol::resolve(const CommonSymbol &)` at
+`lld/ELF/Symbols.cpp:607-611`, which applies the identical
+`setVisibility(v == STV_DEFAULT ? ov : std::min(v, ov))` step before any
+common-specific handling.
+
+So on ELF the visibility rule is uniform across all symbol kinds, and our
+single `visibilityRank` table is sufficient. (Mach-O is not uniform — see §7.2
+of the Mach-O doc.)
+
+---
+
+## 8. Fourth pass: ODR definition vs regular definition
+
+This is the case that decides whether the extractor can be target-independent.
+COFF errors here (COFF §2, probe K4); this probe establishes what ELF does.
+
+### Program
+
+`inl.h`
+```c
+inline int q(void) { return 42; }
+```
+
+`e_inl.cpp`
+```c
+#include "inl.h"
+int use_inl(void) { return q(); }
+```
+
+`e_inl_b.cpp`
+```c
+#include "inl.h"
+int use_inl_b(void) { return q(); }
+```
+
+`e_regular.cpp`
+```c
+int q(void) { return 99; }
+int use_reg(void) { return 0; }
+```
+
+`e_main.cpp`
+```c
+int use_inl(void); int use_reg(void);
+extern "C" int _start(void) { return use_inl() + use_reg(); }
+```
+
+### Commands and output
+
+```
+$ $NM e_inl.o | grep _Z1qv
+0000000000000000 W _Z1qv # weak + COMDAT
+$ $NM e_regular.o | grep _Z1qv
+0000000000000000 T _Z1qv # strong
+
+$ $LLD -o o_ei1 e_main.o e_inl.o e_regular.o # ODR + regular strong
+exit=0
+$ $OD -d --disassemble-symbols=_Z1qv o_ei1
+ 201234: b8 63 00 00 00 movl $0x63, %eax # 99 — the regular def won
+
+$ $LLD -o o_ei2 e_main2.o e_inl.o e_inl_b.o # ODR + ODR
+exit=0
+```
+
+### Inference
+
+**ELF does not error on an ODR definition colliding with a regular
+definition** — the regular (strong) definition simply wins, because the inline
+was lowered to `STB_WEAK`. Two ODR definitions also link cleanly.
+
+Contrast COFF probe K4, where the identical source is a `duplicate symbol`
+error, because COFF keeps the inline definition `Strong` and relies on COMDAT
+to license duplicates.
+
+The same probe on Mach-O (`arm64-apple-macosx`, same sources) also links
+cleanly with the regular definition winning:
+
+```
+$ ld -o o_mi1 m_main.o m_inl.o m_regular.o ...
+ld exit=0
+$ $OD -d --disassemble-symbols=__Z1qv o_mi1
+100000370: 52800c60 mov w0, #0x63 ; =99
+```
+
+### Consequence for the model
+
+The three platforms disagree on this case *only because they lower `inline`
+differently*, not because they disagree about C++ semantics. The summary should
+therefore record the source-level fact — `Strong` + `ODR`, which is what COFF
+encodes directly — and let `LinkageRules` perform the platform lowering:
+
+ effectiveBinding(Strong, ODR) = Weak on ELF and Mach-O
+ effectiveBinding(Strong, ODR) = Strong on COFF
+
+With that, one conflict predicate per platform reproduces all four observed
+rows without the extractor needing to know the target:
+
+| case | effective bindings | predicate | observed |
+|---|---|---|---|
+| ELF: ODR + regular | `Weak` vs `Strong` | no conflict | EI1 ok |
+| ELF: ODR + ODR | `Weak` vs `Weak` | no conflict | EI2 ok |
+| COFF: ODR + regular | `Strong` vs `Strong`, not both ODR | conflict | K4 error |
+| COFF: ODR + ODR | `Strong` vs `Strong`, both ODR | no conflict | CI1 ok |
+
+---
+
+## 9. Fifth pass: two declarations with differing bindings
+
+The only join case left unprobed: a strong and a weak *declaration* of the same
+entity, with no definition anywhere in the link.
+
+### Program
+
+`strongdecl.c`
+```c
+extern int g;
+int use_strong_decl(void) { return g; }
+```
+
+`weakdecl.c`
+```c
+__attribute__((weak)) extern int g;
+int use_weak_decl(void) { return g ? 1 : 0; }
+```
+
+`dmain.c`
+```c
+int use_strong_decl(void); int use_weak_decl(void);
+int MAINSYM(void) { return use_strong_decl() + use_weak_decl(); }
+```
+
+`wonly_main.c`
+```c
+int use_weak_decl(void);
+int _start(void) { return use_weak_decl(); }
+```
+
+Compiled with `-DMAINSYM=_start`.
+
+### Commands and output
+
+```
+$ $NM e_sd.o | grep " g$"
+ U g # uppercase U = undefined strong
+$ $NM e_wd.o | grep " g$"
+ w g # lowercase w = undefined WEAK
+
+$ $LLD -o e_d1 e_dm.o e_wd.o e_sd.o # weak decl FIRST
+ld.lld: error: undefined symbol: g
+>>> referenced by strongdecl.c
+>>> e_sd.o:(use_strong_decl)
+>>> referenced by weakdecl.c
+
+$ $LLD -o e_d2 e_dm.o e_sd.o e_wd.o # strong decl FIRST
+ld.lld: error: undefined symbol: g
+ (identical diagnostic)
+```
+
+Control — the weak declaration alone is not an error:
+
+```
+$ $LLD -o e_d3 e_wm.o e_wd.o
+exit=0
+$ $RO --symbols e_d3 | grep " g$"
+ 5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND g
+```
+
+The merged binding, read from a shared library where unresolved references are
+permitted:
+
+```
+$ $LLD -shared -o e_d4.so e_dm.o e_sd.o e_wd.o
+$ $RO --dyn-syms e_d4.so | grep " g$"
+ 1: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND g
+```
+
+### Inference
+
+**Strong wins over weak among declarations, in either order.** D4 shows the
+merged undefined symbol is `GLOBAL`, not `WEAK`, and D1/D2 show the resulting
+error is order-independent. The control (D3) confirms a weak-only reference is
+legal, so the error in D1/D2 is caused by the strong reference dominating.
+
+This validates the plan's assumption that two declarations join by
+`argmax strengthRank`, with `Undefined < Weak` on every platform.
+
+It also settles the one place the result is observable: Phase 3's
+undefined-symbol reporting suppresses the diagnostic when the merged binding is
+`Weak`. Because a strong reference dominates, an entity referenced weakly by
+one TU and strongly by another is correctly reported — matching D1/D2.
+
+
+
+
diff --git a/docs/ssaf-linker-implementation-plan.md b/docs/ssaf-linker-implementation-plan.md
new file mode 100644
index 0000000000000..f524d82f16bed
--- /dev/null
+++ b/docs/ssaf-linker-implementation-plan.md
@@ -0,0 +1,607 @@
+# SSAF Entity Linker — Implementation Plan
+
+Collected from the design discussion and grounded in the probe results in
+`ssaf-linker-elf-behavior.md`, `ssaf-linker-macho-behavior.md` and
+`ssaf-linker-coff-behavior.md`.
+
+Nothing in phases 1–5 has been started. Phase 0 is already in the working tree.
+
+---
+
+## Phase 0 — already landed (working tree, uncommitted)
+
+For context, since later phases modify this code:
+
+- `WarnOnMultipleDefinitions` constructor flag (default: fail) plus
+ `clang-ssaf-linker --warn-on-multiple-definitions`. On conflict the incoming
+ occurrence is ignored entirely and the first definition stands.
+- Reconciliation split into `isConflictingDefinition`, `incomingDataWins`,
+ `mergeLinkage`, with `reportIfLinkageIsNotExternal` /
+ `reportIfDefinitionsConflict` guards.
+- Stateless pipeline: `resolve()` returns
+ `pair<EntityResolutionMap, DataSelectionMap>`; no per-link member state.
+- `EntityLinker` public surface reduced to constructor, `link()`,
+ `takeOutput()`; reconciliation rules private with `friend class TestFixture`.
+- Readability pass: `fatal()`, `lookupOrFatal()`, `insertOrFatal()`,
+ `isExternal()`, `mergeEntityData()`, `mergeSummaryData()`,
+ `checkTUNotAlreadyLinked()`, invariant asserts, section banners.
+- Lit coverage for both multiple-definition modes; `tu-2.json` corrected so the
+ shared external entity is a declaration rather than a second definition.
+
+**Loose end:** `clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-redefines-shared-ext.json`
+is untracked. It must be `git add`-ed or the two multiple-definition lit cases
+fail for everyone else.
+
+**To be reverted in Phase 1:** the explicit `= 0,1,2…` numbering added to
+`EntityBinding`, `EntityVisibility` and `EntityDefinitionKind`, and the
+`std::max` joins that depend on it. Superseded by the decision to move all
+ordering into `LinkageRules`.
+
+---
+
+## Phase 1 — split the axes, remove orderings from the enums
+
+### 1.1 `Model/EntityLinkage.h`
+
+Drop `WeakODR`; add `EntityCoalescing`; strip all numeric values and
+"ordered by…" wording. Every enum keeps `: uint8_t` for size only.
+
+```cpp
+/// Symbol scope. Enumerator order is insignificant: the linker never compares
+/// these values, and namespace resolution switches on them by name.
+enum class EntityLinkageType : uint8_t { None, Internal, External };
+
+/// Symbol strength: which definition prevails when two collide.
+///
+/// Enumerator order is insignificant. Precedence differs per platform — on
+/// Mach-O a weak definition displaces a common, on ELF and COFF the common
+/// wins — so comparisons must go through LinkageRules::strengthRank().
+enum class EntityBinding : uint8_t { Undefined, Weak, Common, Strong };
+
+/// How far a symbol escapes its link unit.
+///
+/// Enumerator order is insignificant. ELF merges to the most restrictive,
+/// Mach-O to the least, and COFF has no visibility at all; see
+/// LinkageRules::visibilityRank().
+enum class EntityVisibility : uint8_t { Default, Hidden, Protected };
+
+/// Whether every definition of the entity is required to be identical, as a
+/// COMDAT group (ELF/COFF) or .weak_definition (Mach-O) guarantees.
+///
+/// Independent of EntityBinding: an inline function is Weak+ODR on ELF and
+/// Mach-O but Strong+ODR on COFF. Modelled as an enum rather than a bool so
+/// COFF's other IMAGE_COMDAT_SELECT_* kinds can be added later.
+enum class EntityCoalescing : uint8_t { None, ODR };
+
+/// Whether this occurrence defines the entity or merely declares it.
+enum class EntityDefinitionKind : uint8_t { Declaration, Definition };
+```
+
+`EntityLinkage` gains a fifth field `Coalescing`; constructor arity 4 → 5;
+update `operator==`, `operator<<`, and add
+`FIELD(EntityLinkage, Coalescing)` to `Model/PrivateFieldNames.def`.
+
+### 1.2 `Core/ModelStringConversions.h`
+
+Remove the `WeakODR` to/from-string cases. Add
+`entityCoalescingToString` / `entityCoalescingFromString` ("None" / "ODR").
+
+### 1.3 Serialization — `JSONFormat/JSONFormatImpl.{h,cpp}`
+
+Add a **required** `"coalescing"` key alongside `"binding"`, `"visibility"`,
+`"definition"`, with an `InvalidEntityCoalescing` error message following the
+existing pattern.
+
+> **Breaking format change.** Any summary produced before this lands becomes
+> unreadable. There is no version field in the JSON schema to negotiate this.
+
+### 1.4 Extractor — `TUSummary/TUSummaryExtractor.cpp`
+
+**Target-independent.** The extractor records the *source-level* fact; the
+platform lowering happens in `LinkageRules` (§2.2).
+
+The three formats encode identical C++ semantics — `inline` means one entity,
+defined identically in every TU, duplicates permitted — and differ only in how
+they express it. COFF's encoding is the semantically faithful one: the binding
+stays `Strong` and a separate COMDAT mechanism licenses the duplicates. ELF and
+Mach-O have no general coalescing mechanism, so they *overload weak binding* to
+obtain duplicate tolerance. That is an encoding trick, not a claim that inline
+functions are interposable, so it does not belong in the summary.
+
+Normalized mapping, identical for every target:
+
+| source construct | `Binding` | `Coalescing` |
+|---|---|---|
+| ordinary definition | `Strong` | `None` |
+| `inline` / template / vtable (`GVA_*ODR`) | `Strong` | `ODR` |
+| `__attribute__((weak))` | `Weak` | `None` |
+| C tentative definition | `Common` | `None` |
+| declaration only | `Undefined` | `None` |
+
+So `mapBinding` changes only by returning `Strong` where it previously returned
+`WeakODR`, and a new `mapCoalescing(const NamedDecl *, ASTContext &)` returns
+`ODR` for the two `GVA_*ODR` linkages. `__attribute__((weak))` continues to be
+tested first, before the ODR switch.
+
+Visibility needs no target awareness either: clang's AST is already
+target-configured. The Mach-O probe showed `visibility("protected")` producing
+`-Wunsupported-visibility` and being downgraded at compile time, so
+`ND->getVisibility()` already returns a target-legal value.
+
+### 1.5 Fixtures
+
+67 JSON files under `clang/test/Analysis/Scalable/` contain `"binding"` and
+need a `"coalescing"` key added. Purely additive, scriptable; per the
+fixture-editing convention, insert in place without reordering existing keys.
+
+### 1.6 Tests
+
+`EntityLinkageTest.cpp` and `EntityLinkerTest.cpp`: 5-argument construction,
+updated stream/format assertions, updated `def`/`decl` helpers.
+
+---
+
+## Phase 2 — `LinkageRules`
+
+### 2.1 Interface — `Core/EntityLinker/LinkageRules.{h,cpp}`
+
+```cpp
+/// The platform-specific symbol resolution rules the linker emulates.
+///
+/// Ranks answer "which value wins the join", not "which value is semantically
+/// greater". They are deliberately inverted for some fields on some targets.
+class LinkageRules {
+public:
+ virtual ~LinkageRules() = default;
+
+ /// Returns the rules for \p T, dispatching on its object format.
+ static const LinkageRules &forTarget(const llvm::Triple &T);
+
+ virtual llvm::StringRef getName() const = 0;
+
+ /// Lowers a source-level (binding, coalescing) pair to the binding this
+ /// platform actually emits. ELF and Mach-O have no general coalescing
+ /// mechanism and encode ODR definitions as weak; COFF keeps them strong and
+ /// licenses duplicates via COMDAT. All ranking and conflict detection
+ /// operates on the result of this call, never on the raw binding.
+ virtual EntityBinding effectiveBinding(EntityBinding B,
+ EntityCoalescing C) const = 0;
+
+ virtual unsigned strengthRank(EntityBinding) const = 0;
+ virtual unsigned visibilityRank(EntityVisibility) const = 0;
+ virtual unsigned coalescingRank(EntityCoalescing) const = 0;
+ virtual unsigned definitionKindRank(EntityDefinitionKind) const = 0;
+
+ /// Not derivable from the ranks — see the COFF table below.
+ virtual bool isConflictingDefinition(const EntityLinkage &Current,
+ const EntityLinkage &Incoming) const = 0;
+
+ /// Normalizes \p Linkage to what this platform can represent.
+ ///
+ /// Values the platform silently drops are coerced to their platform
+ /// equivalent; values the platform's toolchain could never have produced are
+ /// a fatal error, since they can only come from a corrupted or hand-edited
+ /// summary. See §2.7.
+ virtual EntityLinkage normalize(const EntityLinkage &Linkage,
+ const EntityName &Name) const = 0;
+};
+```
+
+`forTarget` dispatches on `Triple::getObjectFormat()`; unsupported formats are
+a fatal error naming the triple.
+
+### 2.2 `effectiveBinding` — the platform lowering
+
+| target | `(Strong, ODR)` | everything else |
+|---|---|---|
+| ELF | `Weak` | identity |
+| Mach-O | `Weak` | identity |
+| COFF | `Strong` | identity |
+
+Evidence: ELF §3 (`inline` → `W` + COMDAT group), Mach-O §4 (`WeakDef` flag),
+COFF §2 (`T` + `IMAGE_SCN_LNK_COMDAT`, `Selection: Any`).
+
+This single virtual is what keeps the extractor target-independent, and it
+reproduces every observed ODR-collision result (ELF §8):
+
+| case | effective bindings | predicate | observed |
+|---|---|---|---|
+| ELF: ODR + regular | `Weak` vs `Strong` | no conflict | EI1 links, regular wins |
+| ELF: ODR + ODR | `Weak` vs `Weak` | no conflict | EI2 links |
+| Mach-O: ODR + regular | `Weak` vs `Strong` | no conflict | MI1 links, regular wins |
+| COFF: ODR + regular | `Strong` vs `Strong`, not both ODR | conflict | K4 errors |
+| COFF: ODR + ODR | `Strong` vs `Strong`, both ODR | no conflict | CI1 links |
+
+
+### 2.3 Rank tables (all evidence-backed)
+
+**Binding** — Mach-O inverts `Weak`/`Common`:
+
+| | ELF | Mach-O | COFF |
+|---|---|---|---|
+| `Undefined` | 0 | 0 | 0 |
+| `Weak` | 1 | **2** | 1 |
+| `Common` | 2 | **1** | 2 |
+| `Strong` | 3 | 3 | 3 |
+
+Evidence: ELF §2 C3 (common beats weak), Mach-O §3 MC3 (weak beats common,
+with a warning), COFF §6.1 K3 (common beats weak).
+
+**Visibility** — Mach-O inverts the direction entirely:
+
+| | ELF | Mach-O | COFF |
+|---|---|---|---|
+| `Default` | 0 | **1** | 0 |
+| `Protected` | 1 | n/a | 0 |
+| `Hidden` | 2 | **0** | 0 |
+
+Evidence: ELF §4 V1 + §6.2 VP1/VP2 (most restrictive wins,
+`Default < Protected < Hidden`), Mach-O §2 W1–W3 (least restrictive wins),
+COFF §7 (visibility dropped entirely, so all ranks equal).
+
+**Coalescing** — ranked so that a plain `max` yields the conservative meet:
+`ODR` survives only if *every* copy is ODR.
+
+| | all targets |
+|---|---|
+| `ODR` | 0 |
+| `None` | 1 |
+
+This looks inverted and is intentional: ranks are join-winners, and "not
+guaranteed identical" must win over "guaranteed identical".
+
+**DefinitionKind** — uniform: `Declaration` 0, `Definition` 1.
+
+### 2.4 Conflict predicates
+
+All predicates operate on `effectiveBinding(Binding, Coalescing)`, never on the
+raw binding.
+
+ELF and Mach-O:
+
+```
+both are definitions AND neither effective binding is Weak
+```
+
+An ODR definition lowers to `Weak` on these targets, so it never conflicts —
+matching ELF §8 EI1/EI2 and Mach-O §8 MI1 — without the predicate mentioning
+coalescing at all.
+
+COFF — not derivable from ranks. From the six observed rows in COFF §6:
+
+```
+both are definitions
+ AND neither is Common
+ AND NOT (both are ODR)
+ AND NOT (exactly one is Weak)
+```
+
+The weak clauses follow from COFF emulating weak symbols with an alias
+(`.weak.f.default` appears in the C4 diagnostic), so two weak definitions
+collide on the alias while one weak plus one strong does not. Note this is
+about COFF's lowering of `__attribute__((weak))`, and is unrelated to how ODR
+definitions are represented.
+
+### 2.5 Rewiring `EntityLinker`
+
+- Constructor computes `const LinkageRules &Rules = LinkageRules::forTarget(TargetTriple)`
+ and stores the reference.
+- The three reconciliation functions stay `static` but take
+ `const LinkageRules &` as their first parameter. This keeps them pure and
+ lets the unit tests sweep all three platforms. `TestFixture`'s accessors gain
+ the same parameter.
+- `mergeLinkage` becomes rank-driven, with the **definition-kind gate** the
+ probes established (ELF §6.1, Mach-O §7.1, COFF §6.2 — all three agree a
+ declaration contributes nothing to the binding):
+
+```
+Visibility = argmax visibilityRank over both occurrences
+DefinitionKind = argmax definitionKindRank over both occurrences
+
+if both are definitions:
+ Binding = argmax strengthRank over both
+ Coalescing = argmax coalescingRank over both
+elif exactly one is a definition:
+ Binding, Coalescing = that occurrence's
+else: // two declarations
+ Binding = argmax strengthRank over both
+ Coalescing = None
+```
+
+Without the gate, joining a weak *declaration* with a common *definition* on
+Mach-O would yield `Weak`, which no linker does.
+
+- `incomingDataWins` uses `strengthRank` instead of `>` on the enum.
+- `isConflictingDefinition` delegates to `Rules`.
+- The `mergeLinkage` same-linkage-type assert stays.
+
+Each `LinkageRules` subclass carries a comment with the minimal reproducer from
+the probe docs justifying its rule, per the agreed convention.
+
+### 2.6 Mach-O: order-dependent visibility on commons
+
+**Mach-O visibility on commons is order-dependent** (Mach-O §7.2):
+
+| inputs | ld-prime result |
+|---|---|
+| hidden common first, then default | private |
+| default common first, then hidden | exported |
+| both hidden | private |
+| both default | exported |
+
+The merged visibility is simply that of the **first** common. `addCommon`
+(`lld/MachO/SymbolTable.cpp:251`) carries the `isPrivateExtern` of whichever
+common it keeps; only `addDefined`'s weak-def coalescing path performs the
+`privateExtern &= isPrivateExtern` merge.
+
+**Decision: reproduce it faithfully, and warn.**
+
+An earlier draft proposed approximating this commutatively to keep
+`mergeLinkage` order-independent. That is the wrong trade. The LU is *already*
+order-dependent — `incomingDataWins` keeps the first definition on ties, which
+every platform does (ELF P4, Mach-O M4, COFF C4) — so commutativity of
+`mergeLinkage` was a local property, not a guarantee about the link unit.
+Approximating would produce `Default` where ld-prime gives private whenever the
+hidden common is linked first, which is simply a wrong answer for any analysis
+consuming the summary.
+
+Because the case indicates genuine ambiguity in the user's program, it is also
+reported:
+
+```
+warning: visibility of common symbol 'g' differs between translation units;
+ the linked result depends on link order
+```
+
+Warned by default, unlike the COFF coercion in §2.7 — two commons disagreeing
+on visibility is rare and always worth surfacing, whereas COFF visibility
+coercion would fire on every portable header.
+
+#### Interface consequence
+
+A plain `visibilityRank` cannot express this, because the rule depends on the
+*bindings*, not just the visibilities. So visibility gets a merge function with
+a rank-based default:
+
+```cpp
+ /// Merges the visibility of two occurrences. The base implementation takes
+ /// the higher visibilityRank(); MachOLinkageRules overrides it for commons.
+ virtual EntityVisibility mergeVisibility(const EntityLinkage &Current,
+ const EntityLinkage &Incoming) const;
+
+ /// True if this pair's merge depends on link order, so the caller can warn.
+ virtual bool isOrderDependentMerge(const EntityLinkage &Current,
+ const EntityLinkage &Incoming) const;
+```
+
+- ELF, COFF: base implementation, `isOrderDependentMerge` always false.
+- Mach-O: if both occurrences are `Common` definitions, return
+ `Current.Visibility`; otherwise the base implementation.
+ `isOrderDependentMerge` returns true when both are `Common` definitions and
+ their visibilities differ.
+
+`resolveEntity` calls `reportIfOrderDependentMerge(...)` alongside the existing
+`reportIfDefinitionsConflict(...)` guard.
+
+#### Test consequence
+
+`mergeLinkage` loses its blanket "commutative, associative and idempotent"
+doc-comment claim. The unit-test sweeps become per-platform, and for Mach-O the
+commutativity sweep must exclude — or better, explicitly assert the
+order-dependence of — pairs of commons with differing visibility. Idempotence
+still holds everywhere (merging a linkage with itself never hits the divergent
+case, since equal visibilities give the same answer in both orders).
+
+
+### 2.7 Inexpressible values — `normalize()`
+
+Not every `EntityLinkage` a summary can carry is representable on every target.
+The behaviour differs by case, because *why* a value is unrepresentable differs.
+
+Probed by dumping the AST for the same source across all three targets
+(`clang -Xclang -ast-dump`, ELF doc §8 workspace):
+
+| target | `Hidden` in AST | `Protected` in AST | diagnostic |
+|---|---|---|---|
+| ELF | `Hidden` | `Protected` | — |
+| Mach-O | `Hidden` | **`Default`** | `-Wunsupported-visibility` |
+| COFF | **`Hidden`** | **`Protected`** | **none** |
+
+Two distinct situations follow:
+
+**COFF `Hidden` / `Protected` → coerce to `Default`.** Clang accepts both
+silently on Windows and drops them at emission (COFF §7: a hidden common is an
+indistinguishable plain `C`). Portable code carries these attributes
+unconditionally:
+
+```c
+__attribute__((visibility("hidden"))) int helper(void) { return 1; }
+```
+
+so a faithful COFF extraction legitimately contains `Hidden`. Rejecting it
+would fail valid input, and would do so in the linker — far from the cause,
+about a value the compiler never complained about. Coercion mirrors exactly
+what the object file does. Report it only under the existing verbose flag; a
+default-on warning would fire on every portable header.
+
+**Mach-O `Protected` → fatal error.** Clang cannot produce it — it warns and
+downgrades to `Default` at compile time — so its presence in a summary means
+the summary was hand-edited or corrupted. That is precisely the case where a
+loud failure is wanted. Message should name the entity and the target, and use
+the non-prefixed style of `MultipleDefinition` rather than the
+`EntityLinkerFatalErrorPrefix` style, since it is a bad-input error rather than
+a logic bug.
+
+`normalize()` returns the adjusted linkage and is called once per occurrence in
+`resolveEntity`, before any ranking or conflict detection, so that all
+downstream rules see only platform-legal values.
+
+Note this keeps `mapVisibility` in the extractor target-independent (§1.4): the
+extractor records what the source said, and the linker normalizes for the
+target. That preserves the property that one extraction can be reinterpreted
+for a different target.
+
+### 2.8 Target triple validation
+
+**Required by this phase, not optional.** `LinkageRules::forTarget` selects one
+rule set from `Output.TargetTriple` and applies it to every linked TU. If a TU
+was extracted for a different target, the linker silently applies the wrong
+rules — and since COFF and Mach-O disagree on both the conflict predicate and
+the `Weak`/`Common` order, the result would be wrong with no diagnostic.
+
+`TUSummaryEncoding` already stores the triple and exposes `getTargetTriple()`,
+and `EntityLinker`'s constructor already documents "every linked TU must report
+the same triple". This closes the gap between that claim and the behaviour.
+
+Add to `checkTUNotAlreadyLinked`, or a sibling guard called from `link()`
+before `resolve()`:
+
+```cpp
+llvm::Error EntityLinker::checkTUTargetMatches(const llvm::Triple &TUTriple);
+```
+
+**Comparison strictness.** Exact string equality is too strict — it would
+reject `arm64-apple-macosx14.0` against `arm64-apple-macosx15.0`, which are
+identical for linkage purposes. Compare only the components that select rules
+and affect resolution: architecture, vendor, OS and object format, ignoring the
+version. This mirrors the existing key used by `MultiArchSharedLibrary` and
+`MultiArchStaticLibrary`, which sort members on
+`(getArch(), getSubArch(), getVendor(), getOS(), getEnvironment(), getObjectFormat())`.
+
+Returns `llvm::Error`, not a fatal error: it is bad input, and `link()` already
+returns errors for the analogous duplicate-TU-namespace case. Message should
+name both triples and the offending TU namespace.
+
+**Interaction with `clang-ssaf-linker`.** The tool currently hardcodes
+`llvm::Triple("arm64-apple-macosx")` when constructing the linker, with a TODO
+noting that architecture tracking is deferred. With this check in place, that
+hardcoded triple would reject every non-Mach-O summary. The tool must instead
+derive the LU triple from the first input summary and let the check validate
+the rest — a small change to `runLink`, but a required part of this phase.
+
+---
+
+## Phase 3 — finalization: undefined symbols and hidden demotion
+
+Both rules need the *complete* LU, since a later TU can change the answer — a
+hidden declaration in TU5 demotes a definition from TU1 (ELF §4 V1). So they
+run once at end of link, not per TU. Add a private `finalize()` called from
+`takeOutput()`.
+
+### 3.1 Undefined-symbol reporting
+
+For each LU entity whose merged `DefinitionKind` is `Declaration`, report under
+a tri-state policy (`Ignore` / `Warn` / `Error`), mirroring ELF's
+`--unresolved-symbols`. Suppress when the merged binding is `Weak` — an
+undefined weak reference is legal by design (ELF §5 U2).
+
+**Default: `Ignore`.** The per-target defaults would be actively wrong here. A
+real linker keys strictness on the artifact kind — ELF allows unresolved
+references in a shared library but not an executable (§5 U1 vs U3), while
+Mach-O and COFF reject them even for dylib/DLL (Mach-O §5 MU3, COFF §4 KU3). An
+SSAF link unit is almost always an *intermediate* artifact, so defaulting to
+strict would fire constantly on correct input. Expose
+`--unresolved-symbols=ignore|warn|error` and let the caller assert completeness.
+
+> **Limitation to document:** we do not track references. "Declaration
+> everywhere" is inferred from the entity's presence in the id table, which
+> means some TU mentioned it — close to, but not identical to, "referenced".
+
+### 3.2 Hidden blocks cross-LU resolution
+
+The faithful analogue of ELF turning a hidden symbol `LOCAL` and dropping it
+from `.dynsym`: for each LU entity whose merged linkage is `External` and
+merged visibility ranks as hidden on the target, rewrite its `LinkageTable`
+entry to `Internal`.
+
+Demote the **linkage type only, never the name**. Renaming would invalidate
+every `EntityId` already patched into the summary data. It is also unnecessary:
+external names are already LU-qualified, so `(f, LU1)` and `(f, LU2)` cannot
+collide.
+
+> **No consumer yet.** Nothing currently links LU summaries to each other —
+> `MultiArch*` only bundles. So this is unobservable until an LU-combining
+> stage exists. It is still worth doing now: it is the step that makes
+> `Visibility` load-bearing rather than write-only, and doing it at
+> finalization is the only correct point.
+
+---
+
+## Phase 4 — dropped-data mismatch check (on your signal)
+
+Previously parked over the multi-format `equals` question; `EntityCoalescing`
+now resolves *when* to check, and the format question still needs an answer.
+
+When a duplicate external entity's summary data is dropped, patch the dropped
+encoding into LU id space, compare it against the retained one, and report
+mismatches. Gate on **`Coalescing == ODR`**: for ODR entities all copies are
+required to be identical, so a difference is a real ODR violation or an
+extraction bug. For plain `Weak`, differing definitions are legal and the same
+check would be pure noise.
+
+Mechanics settled earlier: stash dropped encodings during `merge`, add the ones
+from the incoming TU to `PatchTargets` so they are patched uniformly, and
+compare after `patch()` completes. Never patch twice.
+
+Still open: `EntitySummaryEncoding::equals` needs to establish the concrete type
+without RTTI (`LLVM_ENABLE_RTTI=OFF`). Options previously laid out: a per-class
+type tag virtual (recommended), an unchecked `static_cast` with a documented
+precondition, or LLVM-style RTTI with a closed kind enum.
+
+---
+
+## Phase 5 — lit fixtures
+
+Three small hand-written fixtures, split by rule rather than by permutation,
+since the reconciliation unit tests already cover the algebra exhaustively:
+
+1. **`linkage-carry-through.test`** — one TU, ~6 entities, every enum value
+ appearing at least once in a realistic combination, verifying each survives
+ read → link → write.
+2. **`join-winner.test`** — two TUs, ~6 entities, one per branch of the
+ definition-kind × binding decision, each carrying a distinguishing summary
+ blob so the output proves which TU's data survived.
+3. **`join-visibility.test`** — two TUs, the visibility meets, with binding
+ pinned so the fixture stays legal.
+
+With `LinkageRules` in place these should be parameterized by target triple, so
+the same entity shapes are checked against all three rule sets — in particular
+the Mach-O binding inversion and the COFF COMDAT conflict rule.
+
+---
+
+## Suggested order
+
+1. Phase 1 (model) — everything else depends on the enum shape.
+2. Phase 2 (`LinkageRules`) — no point writing rule tables against an enum
+ that is about to change.
+3. Phase 5 fixtures for phases 1–2.
+4. Phase 3 (finalization) — independent, additive.
+5. Phase 4 (mismatch check) — on your signal.
+
+Phases 1 and 2 are one atomic change from the tests' point of view: between
+them the linker has no ordering to work with.
+
+---
+
+## Open questions blocking a clean start
+
+All resolved.
+
+- **Inexpressible values** — coerce on COFF, fatal for Mach-O `Protected`
+ (§2.7).
+- **Mach-O common visibility** — reproduce the order-dependence faithfully and
+ warn (§2.6).
+- **Target triple validation** — required, since it selects the rule set
+ (§2.8).
+- **Two declarations with differing bindings** — probed (ELF §9, Mach-O §8,
+ COFF §8). `argmax strengthRank` confirmed; ELF shows the merged undefined
+ symbol is `GLOBAL`, so a strong reference dominates a weak one in either
+ order.
+
+One finding from the last probe to carry into Phase 3: **a COFF weak
+declaration is not an undefined reference.** It is a `WeakExternal` with an
+`AuxWeakExternal` default-alias record, so COFF links successfully where ELF
+and Mach-O both error (COFF §8). Undefined-symbol reporting must not assume
+every platform would have failed where ELF does.
+
diff --git a/docs/ssaf-linker-macho-behavior.md b/docs/ssaf-linker-macho-behavior.md
new file mode 100644
index 0000000000000..a12f30cbac1c2
--- /dev/null
+++ b/docs/ssaf-linker-macho-behavior.md
@@ -0,0 +1,662 @@
+# Mach-O Linker Behavior — Grounding Probes
+
+Probes run against Apple's system linker (`ld-1221.4`, i.e. ld-prime — the real
+production linker, not LLD) to ground the `LinkageRules` for `Triple::MachO`.
+Target: `arm64-apple-macosx`.
+
+Tools used:
+
+- `CLANG=/Volumes/avirals-external-drive-1/aviralg-llvm-project/bin/clang`
+- `LD=/usr/bin/ld` (`ld -v` → `PROGRAM:ld PROJECT:ld-1221.4`)
+- `NM=/Volumes/avirals-external-drive-1/aviralg-llvm-project/bin/llvm-nm`
+- `RO=/Volumes/avirals-external-drive-1/aviralg-llvm-project/bin/llvm-readobj`
+- `OD=/Volumes/avirals-external-drive-1/aviralg-llvm-project/bin/llvm-objdump`
+- `SDK=$(xcrun --show-sdk-path)`
+
+All objects compiled with:
+
+```
+$CLANG --target=arm64-apple-macosx -c -O0 <src> -o <obj>
+```
+
+All links use the helper:
+
+```sh
+run() {
+ ld -o "$1" "${@:2}" -syslibroot "$SDK" -lSystem -arch arm64 2>/tmp/err.txt
+ local rc=$?
+ grep -v "newer 'macOS'" /tmp/err.txt # filter SDK-version noise
+ echo "ld exit=$rc"
+}
+```
+
+---
+
+## 1. Binding precedence and the duplicate-definition error
+
+### Program
+
+`strong_a.c`
+```c
+int f(void) { return 1; }
+```
+
+`strong_b.c`
+```c
+int f(void) { return 2; }
+```
+
+`weak_a.c`
+```c
+__attribute__((weak)) int f(void) { return 1; }
+```
+
+`weak_b.c`
+```c
+__attribute__((weak)) int f(void) { return 2; }
+```
+
+`main.c`
+```c
+int f(void);
+int main(void) { return f(); }
+```
+
+### Commands and output
+
+How weakness is encoded in the object file:
+
+```
+$ $RO --syms strong_a.o | grep -A6 "Name: _f"
+ Name: _f (1)
+ Extern
+ Type: Section (0xE)
+ Section: __text (0x1)
+ RefType: UndefinedNonLazy (0x0)
+ Flags [ (0x0)
+ ]
+
+$ $RO --syms weak_a.o | grep -A6 "Name: _f"
+ Name: _f (1)
+ Extern
+ Type: Section (0xE)
+ Section: __text (0x1)
+ RefType: UndefinedNonLazy (0x0)
+ Flags [ (0x80)
+ WeakDef (0x80)
+```
+
+Resolution:
+
+```
+$ ld -o out_m1 main.o strong_a.o strong_b.o ...
+duplicate symbol '_f' in:
+ /private/tmp/ssaf-probe/macho/strong_b.o
+ /private/tmp/ssaf-probe/macho/strong_a.o
+ld: 1 duplicate symbols
+exit=1
+
+$ ld -o out_m2 main.o strong_a.o weak_b.o ... # strong + weak
+exit=0
+$ ld -o out_m3 main.o weak_a.o strong_b.o ... # weak + strong
+exit=0
+$ ld -o out_m4 main.o weak_a.o weak_b.o ... # weak + weak
+exit=0
+```
+
+Which definition prevailed (`mov w0, #0x1` = `_a`, `#0x2` = `_b`):
+
+```
+$ $OD -d --disassemble-symbols=_f out_m2
+100000348: 52800020 mov w0, #0x1 # strong_a won
+$ $OD -d --disassemble-symbols=_f out_m3
+100000348: 52800040 mov w0, #0x2 # strong_b won
+$ $OD -d --disassemble-symbols=_f out_m4
+100000430: 52800020 mov w0, #0x1 # weak_a won (first)
+```
+
+### Inference
+
+Identical to ELF on all three counts:
+
+- **Conflict predicate: two non-weak definitions.** Matches
+ `SymbolTable::addDefined` in `lld/MachO/SymbolTable.cpp:113-156`, which
+ pushes a `dupSymDiags` entry only in the `else` branch where neither the
+ existing nor the incoming definition is weak.
+- **Strong beats weak regardless of order** (M2 and M3 both keep the strong
+ body).
+- **Ties keep the first definition** (M4).
+
+Note: the ld64 `-m` flag (allow multiple definitions) is obsolete in ld-prime —
+`ld: warning: -m is obsolete` and the duplicate error still fires. So on Mach-O
+there is no supported way to downgrade the duplicate-symbol error, unlike ELF's
+`-z muldefs`. Our `WarnOnMultipleDefinitions` flag is therefore an
+SSAF-specific affordance on this platform, not an emulation of a linker option.
+
+---
+
+## 2. Visibility: `privateExtern`, and the divergence from ELF
+
+### Program
+
+`pe_hidden.c`
+```c
+__attribute__((visibility("hidden"))) int v(void) { return 1; }
+```
+
+`pe_default.c`
+```c
+int v(void) { return 1; }
+```
+
+`pe_prot.c`
+```c
+__attribute__((visibility("protected"))) int v(void) { return 1; }
+```
+
+Two weak definitions differing in visibility:
+
+`wd_hidden.c`
+```c
+__attribute__((weak, visibility("hidden"))) int w(void) { return 1; }
+int anchor_a(void) { return 0; }
+```
+
+`wd_hidden2.c`
+```c
+__attribute__((weak, visibility("hidden"))) int w(void) { return 2; }
+int anchor_b(void) { return 0; }
+```
+
+`wd_default.c`
+```c
+__attribute__((weak)) int w(void) { return 2; }
+int anchor_b(void) { return 0; }
+```
+
+`wd_main.c`
+```c
+int w(void);
+int anchor_a(void); int anchor_b(void);
+int main(void) { return w() + anchor_a() + anchor_b(); }
+```
+
+### Commands and output
+
+`protected` is rejected outright at compile time:
+
+```
+$ $CLANG --target=arm64-apple-macosx -c -O0 pe_prot.c -o pe_prot.o
+pe_prot.c:1:16: warning: target does not support 'protected' visibility;
+ using 'default' [-Wunsupported-visibility]
+ 1 | __attribute__((visibility("protected"))) int v(void) { return 1; }
+ | ^
+```
+
+`hidden` becomes the `PrivateExtern` bit, not a visibility field:
+
+```
+$ $RO --syms pe_hidden.o | grep -A8 "Name: _v"
+ Name: _v (1)
+ PrivateExtern
+ Extern
+ ...
+
+$ $RO --syms pe_default.o | grep -A8 "Name: _v"
+ Name: _v (1)
+ Extern
+ ...
+```
+
+Coalescing two weak definitions with differing visibility:
+
+```
+$ ld -o out_w1 wd_main.o wd_hidden.o wd_default.o ... # hidden first
+$ $NM -m out_w1 | grep " _w$"
+000000010000045c (__TEXT,__text) weak external _w
+
+$ ld -o out_w2 wd_main.o wd_default.o wd_hidden.o ... # default first
+$ $NM -m out_w2 | grep " _w$"
+0000000100000454 (__TEXT,__text) weak external _w
+
+$ ld -o out_w3 wd_main.o wd_hidden.o wd_hidden2.o ... # BOTH hidden
+$ $NM -m out_w3 | grep " _w$"
+000000010000036c (__TEXT,__text) non-external (was a private external) _w
+```
+
+### Inference
+
+- **Mach-O has no visibility enum — only a `PrivateExtern` bit.** There are two
+ states, not three. `Protected` is not merely unused on this target: clang
+ refuses to emit it and silently downgrades to `default`.
+
+- **Visibility merges to the LEAST restrictive, the opposite of ELF.** W1 and
+ W2 (one hidden, one default, either order) both produce an *exported* symbol;
+ only W3 (both hidden) stays private. This is
+ `defined->privateExtern &= isPrivateExtern` at
+ `lld/MachO/SymbolTable.cpp:118` — private only if *every* copy is private.
+
+ Compare ELF probe V1, where a hidden *declaration* was enough to make a
+ default-visibility definition `LOCAL HIDDEN`. The two platforms take opposite
+ directions, so this rule cannot be shared.
+
+- Order does not matter (W1 == W2), so the rule is still commutative — just
+ `min`-of-restrictiveness rather than `max`.
+
+---
+
+## 3. Common symbols
+
+### Program
+
+`common_a.c` → `int g;`, `common_big.c` → `long long g[4];`,
+`def_g.c` → `int g = 7;`, `weakdef_g.c` → `__attribute__((weak)) int g = 9;`,
+`mainj.c` → `extern int g; int main(void) { return g; }`
+
+Compiled with `-fcommon`.
+
+### Commands and output
+
+```
+$ $NM common_a.o | grep " _g$"
+0000000000000004 C _g
+$ $NM common_big.o | grep " _g$"
+0000000000000020 C _g
+$ $NM def_g.o | grep " _g$"
+0000000000000000 D _g
+$ $NM weakdef_g.o | grep " _g$"
+0000000000000000 D _g
+```
+
+```
+$ run out_mc1 mainj.o common_a.o common_big.o # common(4) + common(32)
+ld exit=0
+$ $NM -m out_mc1 | grep " _g$"
+0000000100004000 (__DATA,__common) external _g
+
+$ run out_mc2 mainj.o common_a.o def_g.o # common(4) + strong data(4)
+ld exit=0
+$ $NM -m out_mc2 | grep " _g$"
+0000000100004000 (__DATA,__data) external _g
+
+$ run out_mc2b mainj.o common_big.o def_g.o # common(32) + strong data(4)
+ld exit=0
+$ $NM -m out_mc2b | grep " _g$"
+0000000100004000 (__DATA,__data) external _g
+
+$ run out_mc3 mainj.o weakdef_g.o common_big.o # weak data(4) + common(32)
+ld: warning: tentative definition of '_g' with size 32 from
+ '.../common_big.o' is being replaced by real definition of smaller
+ size 4 from '.../weakdef_g.o'
+ld exit=0
+$ $NM -m out_mc3 | grep " _g$"
+0000000100008000 (__DATA,__data) weak external _g
+```
+
+### Inference
+
+- **Two commons merge, no error** (MC1), as on ELF.
+- **A strong definition beats common regardless of size** (MC2b), as on ELF.
+- **A WEAK definition also beats common** (MC3) — and Mach-O *warns* about the
+ size shrink. **This is a divergence from ELF**, where the common won over a
+ weak definition (ELF probe C3). It matches `lld/MachO/SymbolTable.cpp:259`,
+ where `addCommon` returns early for any `isa<Defined>(s)` without consulting
+ weakness, versus ELF's `isDefined() && !isWeak()` guard at
+ `lld/ELF/Symbols.cpp:612`.
+
+So the strength order differs between platforms:
+
+- ELF: `Undefined < Weak < Common < Strong`
+- Mach-O: `Undefined < Common < Weak < Strong`
+
+A single global `EntityBinding` ordering cannot express both. The comparison
+must live in `LinkageRules`, not in the enum's numeric values.
+
+---
+
+## 4. Inline functions: how ODR is represented
+
+### Program
+
+`inl.h`
+```c
+inline int inl(void) { return 42; }
+```
+
+`inl_a.cpp` / `inl_b.cpp` — each `#include "inl.h"` and call `inl()`.
+
+### Commands and output
+
+```
+$ $NM -m inl_a.o | grep -i inlv
+0000000000000014 (__TEXT,__text) weak external __Z3inlv
+
+$ $RO --syms inl_a.o | grep -A8 "Name: __Z3inlv"
+ Name: __Z3inlv (1)
+ Extern
+ Type: Section (0xE)
+ Section: __text (0x1)
+ RefType: UndefinedNonLazy (0x0)
+ Flags [ (0x80)
+ WeakDef (0x80)
+ ]
+```
+
+### Inference
+
+**On Mach-O an ODR definition and a plain `__attribute__((weak))` definition
+are byte-for-byte identical in the symbol table** — both are just `WeakDef`
+(0x80). Compare section 1, where `weak_a.o`'s `_f` carried exactly the same
+flag.
+
+Unlike ELF, there is not even a COMDAT group to distinguish them: the ODR
+guarantee is entirely absent from the object file. This reinforces that
+`Coalescing` is *not* recoverable from a linker's view of Mach-O — it is
+front-end knowledge that SSAF must carry explicitly if it wants it, which is
+precisely the argument for a separate `EntityCoalescing` field sourced from
+clang's `GVA_*ODR` linkage rather than inferred from the binding.
+
+---
+
+## 5. Undefined symbols
+
+### Program
+
+`undef_main.c`
+```c
+int missing(void);
+int main(void) { return missing(); }
+```
+
+`undef_weak_main.c`
+```c
+__attribute__((weak_import)) int maybe_missing(void);
+int main(void) { return maybe_missing ? maybe_missing() : 0; }
+```
+
+### Commands and output
+
+```
+$ run out_mu1 undef_main.o # strong ref, executable
+Undefined symbols for architecture arm64:
+ "_missing", referenced from:
+ _main in undef_main.o
+ld: symbol(s) not found for architecture arm64
+ld exit=1
+
+$ run out_mu2 undef_weak_main.o # weak_import ref, executable
+Undefined symbols for architecture arm64:
+ "_maybe_missing", referenced from:
+ _main in undef_weak_main.o
+ld: symbol(s) not found for architecture arm64
+ld exit=1
+
+$ ld -dylib -o out_mu3.dylib undef_main.o ... # strong ref, DYLIB
+ld exit=1
+Undefined symbols for architecture arm64:
+ "_missing", referenced from:
+ _main in undef_main.o
+
+$ ld -o out_mu4 undef_main.o -undefined dynamic_lookup ...
+ld exit=0
+```
+
+### Inference
+
+Mach-O is **stricter than ELF** in two ways that matter to us:
+
+- **A dylib with unresolved references is an error** (MU3), whereas ELF happily
+ produced `out_u3.so` (ELF probe U3). Mach-O requires every symbol to be
+ resolved at static-link time unless told otherwise; ELF defers to load time.
+- **`weak_import` does not by itself excuse an entirely absent symbol** (MU2).
+ It marks a symbol that may be missing *at runtime* when it comes from a
+ dylib; with no definition anywhere in the link, it is still an error.
+
+`-undefined dynamic_lookup` (MU4) is the escape hatch, analogous to ELF's
+`--unresolved-symbols=ignore-all`.
+
+For our linker: the "is this LU final or intermediate?" distinction that ELF
+makes via shared-vs-executable does **not** hold on Mach-O. If we want one
+policy across platforms, an explicit flag is the honest way to express it,
+rather than inferring permissiveness from the artifact kind.
+
+---
+
+## 6. Second pass: ordering details
+
+### 6.1 Weak declaration + common definition
+
+Same program as the ELF doc section 6.1 (`weakdecl.c`, `commondef.c`, `rd.c`),
+compiled with `-fcommon -DMAINSYM=main`.
+
+```
+$ $NM m_weakdecl.o | grep " _g$"
+ U _g # plain undefined
+$ $NM m_commondef.o | grep " _g$"
+0000000000000004 C _g
+
+$ ld -o m_out m_rd.o m_weakdecl.o m_commondef.o ...
+ld exit=0
+$ $NM -m m_out | grep " _g$"
+0000000100004000 (__DATA,__common) external _g
+```
+
+**Inference.** The merged symbol is the common definition, plain `external`,
+with no trace of the weak declaration. Same conclusion as ELF: the binding join
+is scoped to defining occurrences.
+
+This case matters more on Mach-O than on ELF, because Mach-O ranks `Weak` above
+`Common` (section 3). Without the definition-kind gate, joining a weak
+declaration with a common definition would yield `Weak`, which the linker
+demonstrably does not do.
+
+Note also that clang did not even mark the reference weak here — `nm` shows a
+plain `U`, not a weak undefined — so on Mach-O `__attribute__((weak))` on a
+declaration is largely inert.
+
+### Mach-O rank tables
+
+Derived from sections 1, 2, 3 and 6:
+
+| `EntityBinding` | rank | evidence |
+|---|---|---|
+| `Undefined` | 0 | any definition beats an undefined reference (MU1) |
+| `Common` | 1 | weak def replaces common, with a warning (MC3) |
+| `Weak` | 2 | strong beats weak (M2/M3); weak beats common (MC3) |
+| `Strong` | 3 | duplicate error only for strong+strong (M1) |
+
+**This inverts `Weak` and `Common` relative to ELF and COFF** — the single
+strongest argument for moving the ordering out of the enum and into
+`LinkageRules`.
+
+| `EntityVisibility` | rank | evidence |
+|---|---|---|
+| `Default` | 1 | W1/W2: hidden + default coalesce to *exported* |
+| `Hidden` | 0 | W3: private only when every copy is private |
+| `Protected` | — | not representable; clang downgrades to `Default` |
+
+**The visibility ranks are inverted relative to ELF**: here the *least*
+restrictive wins, so `Default` outranks `Hidden`. Expressing both platforms
+with one "take the greater rank" join is exactly why the ranks must be supplied
+per-target rather than baked into the enum's numeric values.
+
+Conflict predicate: **both occurrences are definitions and neither is `Weak`**
+(M1 errors; M2/M3/M4 do not) — the same predicate as ELF, unlike COFF.
+
+---
+
+## 7. Third pass: coalescing and visibility-on-commons
+
+### 7.1 Is an ODR definition distinguishable from a plain weak definition?
+
+`inl.h`
+```c
+inline int q(void) { return 42; }
+```
+
+`mo_inl.cpp`
+```c
+#include "inl.h"
+int use_inl(void) { return q(); }
+```
+
+`mo_weak.cpp`
+```c
+__attribute__((weak)) int q(void) { return 7; }
+int use_weak(void) { return 0; }
+```
+
+`mo_strong.cpp`
+```c
+int q(void) { return 99; }
+int use_strong(void) { return 0; }
+```
+
+`mo_main2.cpp` calls `use_inl() + use_weak()`; `mo_main3.cpp` calls
+`use_inl() + use_strong()`.
+
+```
+$ $NM -m mo_inl.o | grep __Z1qv
+0000000000000014 (__TEXT,__text) weak external __Z1qv
+$ $NM -m mo_weak.o | grep __Z1qv
+0000000000000000 (__TEXT,__text) weak external __Z1qv
+$ $NM -m mo_strong.o | grep __Z1qv
+0000000000000000 (__TEXT,__text) external __Z1qv
+```
+
+```
+$ run o_mq1 mo_main2.o mo_inl.o mo_weak.o # ODR(=42) + weak(=7)
+ld exit=0
+$ $OD -d --disassemble-symbols=__Z1qv o_mq1
+100000458: 52800540 mov w0, #0x2a ; =42 # inline won (first)
+
+$ run o_mq2 mo_main3.o mo_inl.o mo_strong.o # ODR(=42) + strong(=99)
+ld exit=0
+$ $OD -d --disassemble-symbols=__Z1qv o_mq2
+100000370: 52800c60 mov w0, #0x63 ; =99 # strong won
+```
+
+### Inference
+
+**On Mach-O an ODR definition is behaviourally identical to a plain weak
+definition.** The symbol table entries are byte-identical (`weak external` for
+both), and resolution treats them the same: first-wins against another weak
+definition (MQ1), loses to a strong definition (MQ2).
+
+So Mach-O needs no `EntityCoalescing` rank at all — the field is inert for
+resolution on this platform. It remains worth carrying in the summary because
+it is front-end knowledge the object format discards, and because it gates the
+summary-mismatch check (a differing summary between two ODR copies is an ODR
+violation; between two plain weak copies it is legal).
+
+Contrast COFF, where coalescing *is* the conflict predicate.
+
+### 7.2 Visibility on common symbols — order-dependent
+
+Section 2 established that two weak *definitions* merge to the least
+restrictive visibility. Commons behave differently.
+
+`hc_hidden_common.c`
+```c
+__attribute__((visibility("hidden"))) int g;
+```
+
+`hc_default_common.c`
+```c
+int g;
+```
+
+`hc_read.c`
+```c
+extern int g;
+int MAINSYM(void) { return g; }
+```
+
+Compiled with `-fcommon -DMAINSYM=main`.
+
+```
+$ $RO --syms m_hc.o | grep -A3 "Name: _g"
+ Name: _g (1) PrivateExtern Extern Type: Undef (0x0)
+
+$ ld -o m_hc_out m_rd.o m_hc.o m_dc.o ... # hidden common FIRST
+ld exit=0
+$ $NM -m m_hc_out | grep " _g$"
+0000000100004000 (__DATA,__common) non-external (was a private external) _g
+
+$ ld -o m_hc_out3 m_rd.o m_dc.o m_hc.o ... # default common FIRST
+ld exit=0
+$ $NM -m m_hc_out3 | grep " _g$"
+0000000100004000 (__DATA,__common) external _g
+
+$ ld -o m_hc_out2 m_rd.o m_hc.o m_hc2.o ... # both hidden
+0000000100004000 (__DATA,__common) non-external (was a private external) _g
+
+$ ld -o m_hc_out4 m_rd.o m_dc.o m_dc2.o ... # both default
+0000000100004000 (__DATA,__common) external _g
+```
+
+### Inference
+
+**For common symbols, Mach-O visibility is order-dependent**: hidden-first
+yields a private symbol, default-first yields an exported one. This is the
+opposite of the weak-definition case in section 2, where `hidden + default`
+gave an exported symbol in *either* order.
+
+The rule is not a commutative merge at all — `addCommon`
+(`lld/MachO/SymbolTable.cpp:251`) simply carries the `isPrivateExtern` of
+whichever common it keeps, with no `&=` step. Only `addDefined`'s weak-def
+coalescing path performs the `privateExtern &= isPrivateExtern` merge.
+
+**Implication for our model.** We cannot faithfully reproduce this, because our
+`mergeLinkage` is deliberately commutative and order-independent — a property
+we want, since a link unit's linkage record should not depend on input order.
+Emulating Mach-O exactly would make the LU summary order-dependent for this one
+case.
+
+The honest choice is to deviate deliberately and document it: apply the
+least-restrictive rule uniformly on Mach-O (matching the weak-definition case,
+which is by far the common one) and accept that a `hidden common + default
+common` pair yields `Default` where ld-prime would give private if the hidden
+one happened to be linked first. This is a case where being order-independent
+is worth more to a summary format than bug-compatibility with a link-order
+artifact.
+
+> **Superseded.** The implementation plan (§2.6) now reproduces this faithfully
+> and warns, rather than approximating commutatively. The LU is already
+> order-dependent via `incomingDataWins`'s first-wins tie-break, so
+> commutativity of `mergeLinkage` was never a guarantee about the link unit.
+
+---
+
+## 8. Fifth pass: two declarations with differing bindings
+
+Same program as ELF §9 (`strongdecl.c`, `weakdecl.c`, `dmain.c`), compiled with
+`-DMAINSYM=main`.
+
+```
+$ $NM m_sd.o | grep " _g$"
+ U _g
+$ $NM m_wd.o | grep " _g$"
+ U _g # NOT marked weak
+
+$ ld -o m_d1 m_dm.o m_wd.o m_sd.o ...
+ld exit=1
+Undefined symbols for architecture arm64:
+ "_g", referenced from:
+ _use_weak_decl in m_wd.o
+ _use_strong_decl in m_sd.o
+```
+
+### Inference
+
+Both declarations produce a plain `U` — clang does not mark a weak *declaration*
+weak on Mach-O at all, corroborating the same observation in §6.1. There is
+consequently nothing to join: the merged binding is `Undefined` either way, and
+the link fails because the symbol has no definition.
+
+Mach-O agrees with ELF on the outcome (error) but for a different reason: on
+ELF the strong reference dominates a genuinely-weak one, whereas here neither
+reference was weak to begin with.
+
+
+
More information about the cfe-commits
mailing list