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