From c1dc33febb6ba69b8910a379285c10864fce5514 Mon Sep 17 00:00:00 2001 From: Parth Arora Date: Fri, 8 May 2026 02:39:15 -0700 Subject: [PATCH] Make version script pattern matching GNU-compatible This commit makes version script pattern matching GNU-compatible. Uptil now, eld version script pattern matching was very intuitive, and straightforward. Iterate the version nodes and blocks in order and match them with the global symbols, and the first match wins. However, GNU version script pattern matching rules are a little complex. The updated GNU-compatible version script pattern matching follows the below rules: 1) Phase 1: Iterate the version nodes and blocks in order, match symbols with the non-wildcard patterns. The first match wins. 2) Phase 2: Iterate the version nodes and blocks in order, match unmatched symbols with the non-* wildcard patterns. The last match wins. 3) Phase 3: Iterate the version nodes and blocks in order, match unmatched symbols with the '*' wildcard pattern. The last match wins. Resolves #1132 Signed-off-by: Parth Arora --- include/eld/Config/LinkerConfig.h | 14 ++ include/eld/Diagnostics/DiagDynamicLink.inc | 2 + .../eld/Diagnostics/DiagSymbolVersioning.inc | 1 - include/eld/Driver/GnuLinkerOptions.td | 2 + include/eld/Object/ObjectLinker.h | 6 + include/eld/Script/VersionScript.h | 7 +- include/eld/Target/GNULDBackend.h | 2 - lib/Config/LinkerConfig.cpp | 9 + lib/Object/ObjectLinker.cpp | 154 ++++++++++++------ lib/Script/VersionScript.cpp | 4 +- ...ngSharedLibsWithSymbolVersioningTrace.test | 6 +- .../Inputs/exact_first_wins.vs | 6 + .../Inputs/exact_over_wildcard.vs | 6 + .../Inputs/star_lowest_priority.vs | 6 + .../Inputs/symbols.c | 7 + .../Inputs/wildcard_last_wins.vs | 6 + .../VersionScriptPatternMatching.test | 71 ++++++++ .../Inputs/exact_first_wins.vs | 6 + .../Inputs/exact_over_wildcard.vs | 6 + .../Inputs/multiple_versions.vs | 9 + .../Inputs/star_lowest_priority.vs | 6 + .../Inputs/symbols.c | 7 + .../Inputs/three_versions_precedence.vs | 16 ++ .../Inputs/wildcard_across_versions.vs | 9 + .../Inputs/wildcard_last_wins.vs | 6 + ...VersionScriptPatternMatchingVersioned.test | 119 ++++++++++++++ 26 files changed, 435 insertions(+), 58 deletions(-) create mode 100644 test/Common/standalone/VersionScriptPatternMatching/Inputs/exact_first_wins.vs create mode 100644 test/Common/standalone/VersionScriptPatternMatching/Inputs/exact_over_wildcard.vs create mode 100644 test/Common/standalone/VersionScriptPatternMatching/Inputs/star_lowest_priority.vs create mode 100644 test/Common/standalone/VersionScriptPatternMatching/Inputs/symbols.c create mode 100644 test/Common/standalone/VersionScriptPatternMatching/Inputs/wildcard_last_wins.vs create mode 100644 test/Common/standalone/VersionScriptPatternMatching/VersionScriptPatternMatching.test create mode 100644 test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/exact_first_wins.vs create mode 100644 test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/exact_over_wildcard.vs create mode 100644 test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/multiple_versions.vs create mode 100644 test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/star_lowest_priority.vs create mode 100644 test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/symbols.c create mode 100644 test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/three_versions_precedence.vs create mode 100644 test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/wildcard_across_versions.vs create mode 100644 test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/wildcard_last_wins.vs create mode 100644 test/Common/standalone/VersionScriptPatternMatchingVersioned/VersionScriptPatternMatchingVersioned.test diff --git a/include/eld/Config/LinkerConfig.h b/include/eld/Config/LinkerConfig.h index 3d18459b6..0dd718347 100644 --- a/include/eld/Config/LinkerConfig.h +++ b/include/eld/Config/LinkerConfig.h @@ -85,6 +85,7 @@ class LinkerConfig { std::optional EnableWholeArchiveWarnings; std::optional EnableCommandLineWarnings; std::optional EnableOSABIWarnings; + std::optional EnableVersionScriptWarnings; }; struct MappingFileInfo { @@ -348,6 +349,14 @@ class LinkerConfig { return (hasOSABIWarnings() && *WarnOpt.EnableOSABIWarnings); } + bool hasVersionScriptWarnings() const { + return WarnOpt.EnableVersionScriptWarnings.has_value(); + } + + bool showVersionScriptWarnings() const { + return (hasVersionScriptWarnings() && *WarnOpt.EnableVersionScriptWarnings); + } + void setShowAllWarnings() { WarnOpt.EnableAllWarnings = true; WarnOpt.EnableLinkerScriptWarnings = true; @@ -358,6 +367,7 @@ class LinkerConfig { WarnOpt.EnableBadDotAssignmentWarnings = true; WarnOpt.EnableWholeArchiveWarnings = true; WarnOpt.EnableOSABIWarnings = true; + WarnOpt.EnableVersionScriptWarnings = true; } void setShowLinkerScriptWarning(bool Option) { @@ -396,6 +406,10 @@ class LinkerConfig { WarnOpt.EnableOSABIWarnings = Option; } + void setShowVersionScriptWarning(bool Option) { + WarnOpt.EnableVersionScriptWarnings = Option; + } + bool setWarningOption(llvm::StringRef WarnOpt); /// Returns true if UseOldStyleTrampolineNames contains any value. diff --git a/include/eld/Diagnostics/DiagDynamicLink.inc b/include/eld/Diagnostics/DiagDynamicLink.inc index 836d57fda..220cfc8a9 100644 --- a/include/eld/Diagnostics/DiagDynamicLink.inc +++ b/include/eld/Diagnostics/DiagDynamicLink.inc @@ -19,3 +19,5 @@ DIAG(error_discarded_dynamic_section_required, DiagnosticEngine::Error, "symbol '%0' referenced in file '%1' depends upon discarded %2 section") DIAG(trace_set_soname, DiagnosticEngine::Trace, "Setting '%0' as the soname") +DIAG(warn_version_script_reassign, DiagnosticEngine::Warning, + "%0: attempt to reassign symbol '%1' of version '%2' to version '%3'") \ No newline at end of file diff --git a/include/eld/Diagnostics/DiagSymbolVersioning.inc b/include/eld/Diagnostics/DiagSymbolVersioning.inc index 0684f1922..95c2f58b0 100644 --- a/include/eld/Diagnostics/DiagSymbolVersioning.inc +++ b/include/eld/Diagnostics/DiagSymbolVersioning.inc @@ -26,6 +26,5 @@ DIAG(trace_adding_verneed_entry, DiagnosticEngine::Trace, "Adding verneed entry: '%0' -> '%1'") DIAG(trace_reading_symbol_versioning_section, DiagnosticEngine::Trace, "Reading %0[%1]") - DIAG(trace_symbol_to_output_version_id, DiagnosticEngine::Trace, "Symbol '%0' -> version id %1") diff --git a/include/eld/Driver/GnuLinkerOptions.td b/include/eld/Driver/GnuLinkerOptions.td index 637c181c5..5ced7c378 100644 --- a/include/eld/Driver/GnuLinkerOptions.td +++ b/include/eld/Driver/GnuLinkerOptions.td @@ -844,6 +844,8 @@ def W : Joined<["-"], "W">, "is enabled for any archive file\n" "\t\t\t -W[no-]osabi : display a warning when linking objects " "with different values for OS/ABI\n" + "\t\t\t -W[no-]version-script : display warnings detected while " + "processing version scripts\n" >, Group; diff --git a/include/eld/Object/ObjectLinker.h b/include/eld/Object/ObjectLinker.h index 1ef0e4669..fb436a1da 100644 --- a/include/eld/Object/ObjectLinker.h +++ b/include/eld/Object/ObjectLinker.h @@ -398,6 +398,12 @@ class ObjectLinker { bool emitArchiveMemberReport(llvm::StringRef Filename) const; private: + /// Assigns version nodes to symbols with GNU ld semantics: + /// - Pass 1: Exact matches (forward order, first wins, warns on reassign) + /// - Pass 2: Non-* wildcards (reverse order, last wins) + /// - Pass 3: * wildcard (reverse order, last wins, lowest priority) + void assignVersionNodesToSymbols(); + std::unique_ptr ltoInit(llvm::lto::Config Conf, bool CompileToAssembly); diff --git a/include/eld/Script/VersionScript.h b/include/eld/Script/VersionScript.h index 1a5664836..24270c2bd 100644 --- a/include/eld/Script/VersionScript.h +++ b/include/eld/Script/VersionScript.h @@ -145,9 +145,11 @@ class GlobalVersionScriptBlock : public VersionScriptBlock { const override; }; +class VersionScript; + class VersionScriptNode { public: - VersionScriptNode(); + VersionScriptNode(const VersionScript &); VersionScriptBlock *switchToGlobal(); @@ -182,6 +184,8 @@ class VersionScriptNode { void dump(llvm::raw_ostream &Ostream, std::function GetDecoratedPath) const; + const VersionScript &getVersionScript() const { return VS; } + private: VersionScriptBlock *MLocal = nullptr; VersionScriptBlock *MGlobal = nullptr; @@ -190,6 +194,7 @@ class VersionScriptNode { eld::StrToken *Name = nullptr; eld::StrToken *MDependency = nullptr; bool MHasErrorDuringParsing = false; + const VersionScript &VS; }; class VersionScript { diff --git a/include/eld/Target/GNULDBackend.h b/include/eld/Target/GNULDBackend.h index 5b0d81d9e..3161d3a6a 100644 --- a/include/eld/Target/GNULDBackend.h +++ b/include/eld/Target/GNULDBackend.h @@ -594,14 +594,12 @@ class GNULDBackend { void addSymbolScope(ResolveInfo *R, VersionSymbol *V) { SymbolScopes[R] = V; } -#ifdef ELD_ENABLE_SYMBOL_VERSIONING VersionSymbol *getSymbolScope(const ResolveInfo *R) const { auto it = SymbolScopes.find(R); if (it != SymbolScopes.end()) return it->second; return nullptr; } -#endif std::vector &getInternalRelocs() { return m_InternalRelocs; } diff --git a/lib/Config/LinkerConfig.cpp b/lib/Config/LinkerConfig.cpp index 7d2db7193..7ee31f113 100644 --- a/lib/Config/LinkerConfig.cpp +++ b/lib/Config/LinkerConfig.cpp @@ -256,6 +256,15 @@ bool LinkerConfig::setWarningOption(llvm::StringRef WarnOption) { if (WarnOpt == "no-osabi") { setShowOSABIWarning(false); } + // -Wversion-script and -Wno-version-script + if (WarnOpt == "version-script") { + setShowVersionScriptWarning(true); + return true; + } + if (WarnOpt == "no-version-script") { + setShowVersionScriptWarning(false); + return true; + } return false; } diff --git a/lib/Object/ObjectLinker.cpp b/lib/Object/ObjectLinker.cpp index e4df93dba..acee03d2b 100644 --- a/lib/Object/ObjectLinker.cpp +++ b/lib/Object/ObjectLinker.cpp @@ -407,65 +407,121 @@ bool ObjectLinker::parseVersionScript() { ThisModule->addVersionScriptNode(VersionScriptNode); } } - auto &SymbolScopes = getTargetBackend().symbolScopes(); + assignVersionNodesToSymbols(); + return true; +} + +void ObjectLinker::assignVersionNodesToSymbols() { auto &NP = ThisModule->getNamePool(); + auto &VersionNodes = ThisModule->getVersionScriptNodes(); + + if (VersionNodes.empty()) + return; + #ifdef ELD_ENABLE_SYMBOL_VERSIONING - DemangledNamesMap DemangledNames; -#endif - for (auto &G : NP.getGlobals()) { - ResolveInfo *R = G.getValue(); - for (auto &VersionScriptNode : ThisModule->getVersionScriptNodes()) { - if (VersionScriptNode->getGlobalBlock()) { - for (auto *Sym : VersionScriptNode->getGlobalBlock()->getSymbols()) { -#ifdef ELD_ENABLE_SYMBOL_VERSIONING - bool isMatched = Sym->matched(*R, NP, DemangledNames); -#else - bool isMatched = Sym->getSymbolPattern()->matched(*R); + DemangledNamesMap demangledNames; #endif - if (isMatched) { - getTargetBackend().addSymbolScope(R, Sym); + + auto canAssignVersionNode = [](const ResolveInfo &R) { + return (R.isDefine() || R.isCommon()) && !R.isDyn(); + }; + + auto getVersionName = [](const VersionSymbol *VS) -> llvm::StringRef { + auto *block = VS->getBlock(); + auto *node = block->getNode(); + if (node->isAnonymous()) { + if (block->isGlobal()) + return "VER_NDX_GLOBAL"; + else + return "VER_NDX_LOCAL"; + } + return (block->isLocal() ? "VER_NDX_LOCAL" : node->getName()); + }; + + auto tryAssign = [&](ResolveInfo *R, VersionSymbol *VS, bool warnOnReassign) { + VersionSymbol *existing = getTargetBackend().getSymbolScope(R); + InputFile *verSymInputFile = + VS->getBlock()->getNode()->getVersionScript().getInputFile(); + if (existing != nullptr) { + if (warnOnReassign && ThisConfig.showVersionScriptWarnings()) { + ThisConfig.raise(Diag::warn_version_script_reassign) + << verSymInputFile->getInput()->decoratedPath() << R->name() + << getVersionName(existing) << getVersionName(VS); + } + return false; + } + + getTargetBackend().addSymbolScope(R, VS); + #ifdef ELD_ENABLE_SYMBOL_VERSIONING - if (ThisConfig.getPrinter()->traceSymbolVersioning()) - ThisConfig.raise(Diag::trace_version_script_matched_scope) - << "global" << R->name(); - break; + if (ThisConfig.getPrinter()->traceSymbolVersioning()) { + ThisConfig.raise(Diag::trace_version_script_matched_scope) + << (VS->isGlobal() ? "global" : "local") << R->name(); + } #endif - } // end Symbol Match - } // end Symbols - } // end Global - if (SymbolScopes.find(R) != SymbolScopes.end()) { -#ifdef ELD_ENABLE_SYMBOL_VERSIONING - break; -#else + return true; + }; + + using PatternFilter = std::function; + + auto processBlock = [&](VersionScriptBlock *block, PatternFilter filter, + bool warnOnReassign) { + if (!block) + return; + + for (auto *sym : block->getSymbols()) { + auto *pattern = sym->getSymbolPattern(); + if (!filter(*pattern)) continue; -#endif - } - if (VersionScriptNode->getLocalBlock()) { - for (auto *Sym : VersionScriptNode->getLocalBlock()->getSymbols()) { + + for (auto &G : NP.getGlobals()) { + ResolveInfo *R = G.getValue(); + if (!canAssignVersionNode(*R)) + continue; + if (!warnOnReassign && getTargetBackend().getSymbolScope(R) != nullptr) + continue; + #ifdef ELD_ENABLE_SYMBOL_VERSIONING - bool isMatched = Sym->matched(*R, NP, DemangledNames); + if (sym->matched(*R, NP, demangledNames)) #else - bool isMatched = Sym->getSymbolPattern()->matched(*R); + if (pattern->matched(*R)) #endif - if (isMatched) { - getTargetBackend().addSymbolScope(R, Sym); -#ifdef ELD_ENABLE_SYMBOL_VERSIONING - if (ThisConfig.getPrinter()->traceSymbolVersioning()) - ThisConfig.raise(Diag::trace_version_script_matched_scope) - << "local" << R->name(); - break; -#endif - } // end Symbol Match - } // end Symbols - } // end Local -#ifdef ELD_ENABLE_SYMBOL_VERSIONING - if (SymbolScopes.find(R) != SymbolScopes.end()) { - break; + { + tryAssign(R, sym, warnOnReassign); + } } -#endif - } // end all Nodes - } // end Globals - return true; + } + }; + + auto processNodeFirstWins = [&](const VersionScriptNode *node, + PatternFilter filter, bool warnOnReassign) { + processBlock(node->getGlobalBlock(), filter, warnOnReassign); + processBlock(node->getLocalBlock(), filter, warnOnReassign); + }; + + auto processNodeLastWins = [&](const VersionScriptNode *node, + PatternFilter filter, bool warnOnReassign) { + processBlock(node->getLocalBlock(), filter, warnOnReassign); + processBlock(node->getGlobalBlock(), filter, warnOnReassign); + }; + + auto isExact = [](const WildcardPattern &P) { return !P.hasGlob(); }; + auto isNonStarWildcard = [](const WildcardPattern &P) { + return P.hasGlob() && !P.isMatchAll(); + }; + auto isMatchAll = [](const WildcardPattern &P) { return P.isMatchAll(); }; + + for (const auto *N : VersionNodes) { + processNodeFirstWins(N, isExact, true); + } + + for (auto It = VersionNodes.rbegin(); It != VersionNodes.rend(); ++It) { + processNodeLastWins(*It, isNonStarWildcard, false); + } + + for (auto It = VersionNodes.rbegin(); It != VersionNodes.rend(); ++It) { + processNodeLastWins(*It, isMatchAll, false); + } } std::string ObjectLinker::getLTOTempPrefix() const { diff --git a/lib/Script/VersionScript.cpp b/lib/Script/VersionScript.cpp index 56b533d06..536458e09 100644 --- a/lib/Script/VersionScript.cpp +++ b/lib/Script/VersionScript.cpp @@ -20,7 +20,7 @@ using namespace eld; VersionScript::VersionScript(InputFile *Inp) : MInputFile(Inp) {} VersionScriptNode *VersionScript::createVersionScriptNode() { - MCurrentNode = eld::make(); + MCurrentNode = eld::make(*this); VersionScriptNodes.push_back(MCurrentNode); return MCurrentNode; } @@ -49,7 +49,7 @@ void VersionScript::dump( } // -----------------------VersionScriptNode ------------------------------- -VersionScriptNode::VersionScriptNode() {} +VersionScriptNode::VersionScriptNode(const VersionScript &pVS) : VS(pVS) {} VersionScriptBlock *VersionScriptNode::switchToGlobal() { if (MLocal && !MGlobal) { diff --git a/test/Common/standalone/SymbolVersioning/BuildingSharedLibsWithSymbolVersioning/BuildingSharedLibsWithSymbolVersioningTrace.test b/test/Common/standalone/SymbolVersioning/BuildingSharedLibsWithSymbolVersioning/BuildingSharedLibsWithSymbolVersioningTrace.test index 68959ccf2..850a8a6a8 100644 --- a/test/Common/standalone/SymbolVersioning/BuildingSharedLibsWithSymbolVersioning/BuildingSharedLibsWithSymbolVersioningTrace.test +++ b/test/Common/standalone/SymbolVersioning/BuildingSharedLibsWithSymbolVersioning/BuildingSharedLibsWithSymbolVersioningTrace.test @@ -9,9 +9,9 @@ RUN: %clang %clangopts -o %t1.3.o %p/Inputs/3.c -c -fPIC RUN: %link %linkopts -o %t1.lib1.so %t1.3.o -shared --version-script %p/Inputs/vs.2.t \ RUN: --trace=symbol-versioning 2>&1 | %filecheck %s --check-prefix=TRACE1 #END_TEST -TRACE1: Trace: Version script node matched global symbol 'baz' -TRACE1: Trace: Version script node matched global symbol 'foo@V1' -TRACE1: Trace: Version script node matched global symbol 'bar@@V1' +TRACE1-DAG: Trace: Version script node matched global symbol 'baz' +TRACE1-DAG: Trace: Version script node matched global symbol 'foo@V1' +TRACE1-DAG: Trace: Version script node matched global symbol 'bar@@V1' TRACE1: Trace: Creating symbol versioning section: .gnu.version TRACE1: Trace: Creating symbol versioning section: .gnu.version_d TRACE1: Trace: Assigning version IDs to output symbols diff --git a/test/Common/standalone/VersionScriptPatternMatching/Inputs/exact_first_wins.vs b/test/Common/standalone/VersionScriptPatternMatching/Inputs/exact_first_wins.vs new file mode 100644 index 000000000..1e1291cab --- /dev/null +++ b/test/Common/standalone/VersionScriptPatternMatching/Inputs/exact_first_wins.vs @@ -0,0 +1,6 @@ +{ + global: + foo; + local: + foo; +}; diff --git a/test/Common/standalone/VersionScriptPatternMatching/Inputs/exact_over_wildcard.vs b/test/Common/standalone/VersionScriptPatternMatching/Inputs/exact_over_wildcard.vs new file mode 100644 index 000000000..fffbfc5cd --- /dev/null +++ b/test/Common/standalone/VersionScriptPatternMatching/Inputs/exact_over_wildcard.vs @@ -0,0 +1,6 @@ +{ + global: + foo*; + local: + foo; +}; diff --git a/test/Common/standalone/VersionScriptPatternMatching/Inputs/star_lowest_priority.vs b/test/Common/standalone/VersionScriptPatternMatching/Inputs/star_lowest_priority.vs new file mode 100644 index 000000000..34d045bd4 --- /dev/null +++ b/test/Common/standalone/VersionScriptPatternMatching/Inputs/star_lowest_priority.vs @@ -0,0 +1,6 @@ +{ + global: + *; + local: + foo*; +}; diff --git a/test/Common/standalone/VersionScriptPatternMatching/Inputs/symbols.c b/test/Common/standalone/VersionScriptPatternMatching/Inputs/symbols.c new file mode 100644 index 000000000..c136f84b4 --- /dev/null +++ b/test/Common/standalone/VersionScriptPatternMatching/Inputs/symbols.c @@ -0,0 +1,7 @@ +int foo() { return 0; } +int foo1() { return 1; } +int foo2() { return 2; } +int bar() { return 3; } +int bar1() { return 4; } +int baz() { return 5; } +int qux() { return 6; } diff --git a/test/Common/standalone/VersionScriptPatternMatching/Inputs/wildcard_last_wins.vs b/test/Common/standalone/VersionScriptPatternMatching/Inputs/wildcard_last_wins.vs new file mode 100644 index 000000000..12a656284 --- /dev/null +++ b/test/Common/standalone/VersionScriptPatternMatching/Inputs/wildcard_last_wins.vs @@ -0,0 +1,6 @@ +{ + global: + foo*; + local: + foo*; +}; diff --git a/test/Common/standalone/VersionScriptPatternMatching/VersionScriptPatternMatching.test b/test/Common/standalone/VersionScriptPatternMatching/VersionScriptPatternMatching.test new file mode 100644 index 000000000..2cb5f9ef2 --- /dev/null +++ b/test/Common/standalone/VersionScriptPatternMatching/VersionScriptPatternMatching.test @@ -0,0 +1,71 @@ +#---VersionScriptPatternMatching.test--------------------- Exe------------------# + +#BEGIN_COMMENT +# Test version script pattern matching precedence when the version script +# only contains anonymous version nodes. +#END_COMMENT + +# Compile the test source +RUN: %clang %clangopts -c -fpic -O2 %p/Inputs/symbols.c -o %t.o + +#--------------------------------------------------------------------------- +# TEST 1: Exact matches - first match wins +# When "foo" appears in both global and local (exact), global wins (first) +#--------------------------------------------------------------------------- +RUN: %link %linkopts -shared -o %t1.so %t.o --version-script=%p/Inputs/exact_first_wins.vs +RUN: %readelf --dyn-syms %t1.so | %filecheck --check-prefix=EXACT_FIRST %s + +# foo should be global (first exact match wins) +EXACT_FIRST: foo + +#--------------------------------------------------------------------------- +# TEST 2: Wildcards - last match wins +# When foo* appears in global first, then local, local wins (last) +#--------------------------------------------------------------------------- +RUN: %link %linkopts -shared -o %t2.so %t.o --version-script=%p/Inputs/wildcard_last_wins.vs +RUN: %readelf --dyn-syms %t2.so | %filecheck --check-prefix=WILDCARD_LAST %s + +# foo, foo1, foo2 should be global (last wildcard match wins) +WILDCARD_LAST-NOT: foo +WILDCARD_LAST-NOT: foo1 +WILDCARD_LAST-NOT: foo2 + +#--------------------------------------------------------------------------- +# TEST 3: * has lowest priority +# global: * and local: foo* - foo* should win over * +#--------------------------------------------------------------------------- +RUN: %link %linkopts -shared -o %t3.so %t.o --version-script=%p/Inputs/star_lowest_priority.vs +RUN: %readelf --dyn-syms %t3.so | %filecheck --check-prefix=STAR_LOW %s + +# foo, foo1, foo2 should be local (foo* wins over *) +# bar, bar1, baz, qux should be global (matched by *) +STAR_LOW-NOT: foo +STAR_LOW-NOT: foo1 +STAR_LOW-NOT: foo2 +STAR_LOW-DAG: bar +STAR_LOW-DAG: bar1 +STAR_LOW-DAG: baz +STAR_LOW-DAG: qux + +#--------------------------------------------------------------------------- +# TEST 4: Exact match takes precedence over wildcard +# global: foo* and local: foo (exact) - foo should be local, foo1/foo2 global +#--------------------------------------------------------------------------- +RUN: %link %linkopts -shared -o %t4.so %t.o --version-script=%p/Inputs/exact_over_wildcard.vs +RUN: %readelf --dyn-syms %t4.so | %filecheck --check-prefix=EXACT_OVER_WILD %s + +# foo should be local (exact match), foo1/foo2 should be global (wildcard) +EXACT_OVER_WILD-NOT: foo{{$}} +EXACT_OVER_WILD-DAG: foo1 +EXACT_OVER_WILD-DAG: foo2 + +#--------------------------------------------------------------------------- +# TEST 5: Reassignment warning for exact matches +# When same exact symbol appears in multiple version nodes, warn on reassign +#--------------------------------------------------------------------------- +RUN: %link %linkopts -shared -o %t5.so %t.o --version-script=%p/Inputs/exact_first_wins.vs -Wversion-script 2>&1 | %filecheck --check-prefix=REASSIGN_WARN %s +RUN: %link %linkopts -shared -o %t5.so %t.o --version-script=%p/Inputs/exact_first_wins.vs 2>&1 | %filecheck --check-prefix=NO_REASSIGN_WARN %s --allow-empty + +# Should warn about attempting to reassign foo +REASSIGN_WARN: Warning: {{.*}}exact_first_wins.vs: attempt to reassign symbol 'foo' of version 'VER_NDX_GLOBAL' to version 'VER_NDX_LOCAL' +NO_REASSIGN_WARN-NOT: attempt to reassign symbol \ No newline at end of file diff --git a/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/exact_first_wins.vs b/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/exact_first_wins.vs new file mode 100644 index 000000000..4c6570c14 --- /dev/null +++ b/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/exact_first_wins.vs @@ -0,0 +1,6 @@ +V1 { + global: + foo; + local: + foo; +}; diff --git a/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/exact_over_wildcard.vs b/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/exact_over_wildcard.vs new file mode 100644 index 000000000..8db762d41 --- /dev/null +++ b/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/exact_over_wildcard.vs @@ -0,0 +1,6 @@ +V1 { + global: + foo*; + local: + foo; +}; diff --git a/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/multiple_versions.vs b/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/multiple_versions.vs new file mode 100644 index 000000000..ea1be4c72 --- /dev/null +++ b/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/multiple_versions.vs @@ -0,0 +1,9 @@ +V1 { + global: + foo*; +}; + +V2 { + global: + bar*; +} V1; diff --git a/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/star_lowest_priority.vs b/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/star_lowest_priority.vs new file mode 100644 index 000000000..c404ce388 --- /dev/null +++ b/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/star_lowest_priority.vs @@ -0,0 +1,6 @@ +V1 { + global: + *; + local: + foo*; +}; diff --git a/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/symbols.c b/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/symbols.c new file mode 100644 index 000000000..c136f84b4 --- /dev/null +++ b/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/symbols.c @@ -0,0 +1,7 @@ +int foo() { return 0; } +int foo1() { return 1; } +int foo2() { return 2; } +int bar() { return 3; } +int bar1() { return 4; } +int baz() { return 5; } +int qux() { return 6; } diff --git a/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/three_versions_precedence.vs b/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/three_versions_precedence.vs new file mode 100644 index 000000000..e56130b1d --- /dev/null +++ b/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/three_versions_precedence.vs @@ -0,0 +1,16 @@ +V1 { + global: + *; +}; + +V2 { + global: + foo*; +} V1; + +V3 { + global: + foo1; + local: + *; +} V2; diff --git a/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/wildcard_across_versions.vs b/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/wildcard_across_versions.vs new file mode 100644 index 000000000..d684719ad --- /dev/null +++ b/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/wildcard_across_versions.vs @@ -0,0 +1,9 @@ +V1 { + global: + foo*; +}; + +V2 { + local: + foo*; +} V1; diff --git a/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/wildcard_last_wins.vs b/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/wildcard_last_wins.vs new file mode 100644 index 000000000..4984c7634 --- /dev/null +++ b/test/Common/standalone/VersionScriptPatternMatchingVersioned/Inputs/wildcard_last_wins.vs @@ -0,0 +1,6 @@ +V1 { + global: + foo*; + local: + foo*; +}; diff --git a/test/Common/standalone/VersionScriptPatternMatchingVersioned/VersionScriptPatternMatchingVersioned.test b/test/Common/standalone/VersionScriptPatternMatchingVersioned/VersionScriptPatternMatchingVersioned.test new file mode 100644 index 000000000..aeedc38ac --- /dev/null +++ b/test/Common/standalone/VersionScriptPatternMatchingVersioned/VersionScriptPatternMatchingVersioned.test @@ -0,0 +1,119 @@ +REQUIRES: symbol_versioning +#---VersionScriptPatternMatchingVersioned.test--------------------- Exe------------------# + +#BEGIN_COMMENT +# Test version script pattern matching precedence with named version nodes. +#END_COMMENT + +# Compile the test source +RUN: %clang %clangopts -c -fpic -O2 %p/Inputs/symbols.c -o %t.o + +#--------------------------------------------------------------------------- +# TEST 1: Exact matches - first match wins (within named version node) +# When "foo" appears in both global and local (exact), global wins (first) +#--------------------------------------------------------------------------- +RUN: %link %linkopts -shared -o %t1.so %t.o --version-script=%p/Inputs/exact_first_wins.vs +RUN: %readelf --dyn-syms %t1.so | %filecheck --check-prefix=EXACT_FIRST %s + +# foo should be global (first exact match wins) +EXACT_FIRST: foo + +#--------------------------------------------------------------------------- +# TEST 2: Wildcards - last match wins (within named version node) +# When foo* appears in global first, then local, local wins (last) +#--------------------------------------------------------------------------- +RUN: %link %linkopts -shared -o %t2.so %t.o --version-script=%p/Inputs/wildcard_last_wins.vs +RUN: %readelf --dyn-syms %t2.so | %filecheck --check-prefix=WILDCARD_LAST %s + +# foo, foo1, foo2 should be local (last wildcard match wins) +WILDCARD_LAST-NOT: foo +WILDCARD_LAST-NOT: foo1 +WILDCARD_LAST-NOT: foo2 + +#--------------------------------------------------------------------------- +# TEST 3: * has lowest priority (within named version node) +# global: * and local: foo* - foo* should win over * +#--------------------------------------------------------------------------- +RUN: %link %linkopts -shared -o %t3.so %t.o --version-script=%p/Inputs/star_lowest_priority.vs +RUN: %readelf --dyn-syms %t3.so | %filecheck --check-prefix=STAR_LOW %s + +# foo, foo1, foo2 should be local (foo* wins over *) +# bar, bar1, baz, qux should be global (matched by *) +STAR_LOW-NOT: foo +STAR_LOW-NOT: foo1 +STAR_LOW-NOT: foo2 +STAR_LOW-DAG: bar +STAR_LOW-DAG: bar1 +STAR_LOW-DAG: baz +STAR_LOW-DAG: qux + +#--------------------------------------------------------------------------- +# TEST 4: Exact match takes precedence over wildcard (within named version node) +# global: foo* and local: foo (exact) - foo should be local, foo1/foo2 global +#--------------------------------------------------------------------------- +RUN: %link %linkopts -shared -o %t4.so %t.o --version-script=%p/Inputs/exact_over_wildcard.vs +RUN: %readelf --dyn-syms %t4.so | %filecheck --check-prefix=EXACT_OVER_WILD %s + +# foo should be local (exact match), foo1/foo2 should be global (wildcard) +EXACT_OVER_WILD-NOT: foo{{$}} +EXACT_OVER_WILD-DAG: foo1 +EXACT_OVER_WILD-DAG: foo2 + +#--------------------------------------------------------------------------- +# TEST 5: Multiple version nodes - symbols assigned to correct versions +# V1: foo*, V2: bar* - check version assignment +#--------------------------------------------------------------------------- +RUN: %link %linkopts -shared -o %t5.so %t.o --version-script=%p/Inputs/multiple_versions.vs +RUN: %readelf --dyn-syms %t5.so | %filecheck --check-prefix=MULTI_VER %s + +# foo* should be in V1, bar* should be in V2 +MULTI_VER-DAG: foo@@V1 +MULTI_VER-DAG: foo1@@V1 +MULTI_VER-DAG: foo2@@V1 +MULTI_VER-DAG: bar@@V2 +MULTI_VER-DAG: bar1@@V2 + +#--------------------------------------------------------------------------- +# TEST 6: Wildcards across version nodes - last match wins +# V1: global foo*, V2: local foo* - V2's local should win (last) +#--------------------------------------------------------------------------- +RUN: %link %linkopts -shared -o %t6.so %t.o --version-script=%p/Inputs/wildcard_across_versions.vs +RUN: %readelf --dyn-syms %t6.so | %filecheck --check-prefix=WILDCARD_ACROSS %s + +# foo* should be local (V2's local: foo* wins over V1's global: foo*) +WILDCARD_ACROSS-NOT: foo +WILDCARD_ACROSS-NOT: foo1 +WILDCARD_ACROSS-NOT: foo2 + +#--------------------------------------------------------------------------- +# TEST 7: Reassignment warning for exact matches across version nodes +#--------------------------------------------------------------------------- +RUN: %link %linkopts -shared -o %t7.so %t.o --version-script=%p/Inputs/exact_first_wins.vs -Wversion-script 2>&1 | %filecheck --check-prefix=REASSIGN_WARN %s +RUN: %link %linkopts -shared -o %t7.so %t.o --version-script=%p/Inputs/exact_first_wins.vs 2>&1 | %filecheck --check-prefix=NO_REASSIGN_WARN %s --allow-empty + + +# Should warn about attempting to reassign foo +REASSIGN_WARN: Warning: {{.*}}exact_first_wins.vs: attempt to reassign symbol 'foo' of version 'V1' to version 'VER_NDX_LOCAL' +NO_REASSIGN_WARN-NOT: attempt to reassign symbol + +#--------------------------------------------------------------------------- +# TEST 8: Three version nodes with mixed pattern types +# V1: global: * +# V2: global: foo* +# V3: global: foo1 (exact), local: * +# Expected: foo1 -> V3 (exact wins), foo/foo2 -> V2 (foo* wins over *), +# bar/bar1/baz/qux -> local (V3's local: * wins over V1's global: *) +#--------------------------------------------------------------------------- +RUN: %link %linkopts -shared -o %t8.so %t.o --version-script=%p/Inputs/three_versions_precedence.vs +RUN: %readelf --dyn-syms %t8.so | %filecheck --check-prefix=THREE_VER %s + +# foo1 should be global V3 (exact match takes precedence) +THREE_VER-DAG: foo1@@V3 +# foo and foo2 should be global V2 (foo* wildcard wins over *) +THREE_VER-DAG: foo@@V2 +THREE_VER-DAG: foo2@@V2 +# bar, bar1, baz, qux should be local (V3's local: * wins over V1's global: *) +THREE_VER-NOT: bar +THREE_VER-NOT: bar1 +THREE_VER-NOT: baz +THREE_VER-NOT: qux