https://github.com/e-kud created https://github.com/llvm/llvm-project/pull/225354
#pragma weak alias = target synthesizes a fresh declaration for the alias instead of redeclaring an existing one, so it does not inherit the attributes written on a declaration of the alias. An explicit visibility was therefore dropped and the alias silently took the -fvisibility default, which could make symbols meant to be exported local to a shared object. Carry the visibility over to the synthesized declaration. As GCC does, use the visibility of the alias' own declaration rather than the target's. >From e4bcb3974f9c22328bb0cc23a9119eca4822981b Mon Sep 17 00:00:00 2001 From: Evgenii Kudriashov <[email protected]> Date: Tue, 22 Sep 2026 03:16:06 -0700 Subject: [PATCH] [clang] Keep explicit visibility on a #pragma weak alias #pragma weak alias = target synthesizes a fresh declaration for the alias instead of redeclaring an existing one, so it does not inherit the attributes written on a declaration of the alias. An explicit visibility was therefore dropped and the alias silently took the -fvisibility default, which could make symbols meant to be exported local to a shared object. Carry the visibility over to the synthesized declaration. As GCC does, use the visibility of the alias' own declaration rather than the target's. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> --- clang/docs/ReleaseNotes.md | 7 ++ clang/lib/Sema/SemaDeclAttr.cpp | 10 +++ clang/test/CodeGen/pragma-weak-visibility.c | 81 +++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 clang/test/CodeGen/pragma-weak-visibility.c diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 303f972fcaae1..72b57778c132e 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -578,6 +578,13 @@ features cannot lower the translation-unit ABI level; written after the declarator-id, where it appertains to the declared entity rather than to a declarator chunk. (#GH196982, #GH111463) +- An explicit `visibility` attribute (or a `#pragma GCC visibility`) on the + declaration of the weak name in `#pragma weak X = Y` is no longer dropped. The + alias used to silently take the `-fvisibility` default instead, which could + make symbols meant to be exported local to a shared object. Matching GCC, the + alias now takes the visibility of the weak name's own declaration, + independently of the aliasee's. + #### Bug Fixes to C++ Support - Fixed lambdas with specifiers or attributes after the capture list being diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index eb4a8c2ab9ae0..f8063f6f9796a 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -8805,6 +8805,16 @@ void Sema::DeclApplyPragmaWeak(Scope *S, NamedDecl *ND, const WeakInfo &W) { if (W.getAlias()) { // clone decl, impersonate __attribute(weak,alias(...)) IdentifierInfo *NDId = ND->getIdentifier(); NamedDecl *NewD = DeclClonePragmaWeak(ND, W.getAlias(), W.getLocation()); + // The clone is not a redeclaration of any existing declaration of the weak + // name, so it does not inherit attributes written on one. Carry over an + // explicit visibility, which would otherwise be lost and replaced by the + // -fvisibility default. GCC uses the visibility of the weak name's own + // declaration here, not that of the aliasee. This lookup has to happen + // before the PushOnScopeChains() below, which would make it find NewD. + if (NamedDecl *WeakND = LookupSingleName(S, W.getAlias(), W.getLocation(), + LookupOrdinaryName)) + if (const auto *VA = WeakND->getAttr<VisibilityAttr>()) + NewD->addAttr(VA->clone(Context)); NewD->addAttr( AliasAttr::CreateImplicit(Context, NDId->getName(), W.getLocation())); NewD->addAttr(WeakAttr::CreateImplicit(Context, W.getLocation())); diff --git a/clang/test/CodeGen/pragma-weak-visibility.c b/clang/test/CodeGen/pragma-weak-visibility.c new file mode 100644 index 0000000000000..7a0d14553da60 --- /dev/null +++ b/clang/test/CodeGen/pragma-weak-visibility.c @@ -0,0 +1,81 @@ +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fvisibility=hidden -emit-llvm %s -o - | FileCheck %s --check-prefixes=CHECK,HIDDEN +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -emit-llvm %s -o - | FileCheck %s --check-prefixes=CHECK,DEFAULT +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fvisibility=hidden -DLATE_DECL -verify -emit-llvm %s -o - | FileCheck %s --check-prefixes=CHECK,HIDDEN,LATE + +// Check the visibility of the alias that #pragma weak alias = target creates. +// As in GCC, the alias takes the visibility explicitly written on its own +// declaration, independently of the visibility of the target; with no such +// declaration it gets the -fvisibility default. The pragma synthesizes a fresh +// declaration for the alias rather than redeclaring an existing one, so the +// cases below cover the ways that visibility can reach it. + +// visibility("default") on the alias, declared before the pragma. +// CHECK-DAG: @alias_declared_first = weak alias i32 (), ptr @target_declared_first +int target_declared_first(void) __attribute__((visibility("default"))); +int alias_declared_first(void) __attribute__((visibility("default"))); +#pragma weak alias_declared_first = target_declared_first +int target_declared_first(void) { return 42; } + +// Same, but the pragma precedes both declarations, so the alias is created +// later from ProcessPragmaWeak() instead of ActOnPragmaWeakAlias(). +// CHECK-DAG: @alias_pragma_first = weak alias i32 (), ptr @target_pragma_first +#pragma weak alias_pragma_first = target_pragma_first +int alias_pragma_first(void) __attribute__((visibility("default"))); +int target_pragma_first(void) __attribute__((visibility("default"))); +int target_pragma_first(void) { return 1; } + +// The visibility may also come from #pragma GCC visibility. Here the target is +// hidden, which must not affect the alias. +// CHECK-DAG: @alias_from_gcc_pragma = weak alias i32 (), ptr @target_from_gcc_pragma +int target_from_gcc_pragma(void); +#pragma GCC visibility push(default) +int alias_from_gcc_pragma(void); +#pragma GCC visibility pop +#pragma weak alias_from_gcc_pragma = target_from_gcc_pragma +int target_from_gcc_pragma(void) { return 1; } + +// Conversely, an explicit visibility on the target alone does not propagate to +// the alias. +// HIDDEN-DAG: @alias_without_visibility = weak hidden alias i32 (), ptr @target_with_visibility +// DEFAULT-DAG: @alias_without_visibility = weak alias i32 (), ptr @target_with_visibility +int target_with_visibility(void) __attribute__((visibility("default"))); +int alias_without_visibility(void); +#pragma weak alias_without_visibility = target_with_visibility +int target_with_visibility(void) { return 1; } + +// An explicit visibility("hidden") is honored too, even with -fvisibility left +// at its default. +// CHECK-DAG: @alias_explicitly_hidden = weak hidden alias i32 (), ptr @target_of_hidden_alias +int target_of_hidden_alias(void); +int alias_explicitly_hidden(void) __attribute__((visibility("hidden"))); +#pragma weak alias_explicitly_hidden = target_of_hidden_alias +int target_of_hidden_alias(void) { return 1; } + +// With no declaration of the alias there is no explicit visibility to carry +// over, so the -fvisibility default still applies. +// HIDDEN-DAG: @undeclared_alias = weak hidden alias i32 (), ptr @target_of_undeclared_alias +// DEFAULT-DAG: @undeclared_alias = weak alias i32 (), ptr @target_of_undeclared_alias +int target_of_undeclared_alias(void) __attribute__((visibility("default"))); +#pragma weak undeclared_alias = target_of_undeclared_alias +int target_of_undeclared_alias(void) { return 1; } + +// Variables take the same path through DeclClonePragmaWeak(). +// CHECK-DAG: @alias_variable = weak alias i32, ptr @target_variable +extern int target_variable; +extern int alias_variable __attribute__((visibility("default"))); +#pragma weak alias_variable = target_variable +int target_variable = 7; + +#ifdef LATE_DECL +// When the alias is declared only *after* the pragma has synthesized it, the +// declaration is a redeclaration of the alias, and its visibility attribute +// does not apply: the alias already counts as a definition, so the attribute +// arrives too late and is diagnosed. GCC instead accepts this and gives the +// alias default visibility; the case is pinned here so that following GCC, +// which would mean changing the diagnostic path, is a deliberate change. +// LATE-DAG: @alias_declared_late = weak hidden alias i32 (), ptr @target_of_late_alias +int target_of_late_alias(void); +#pragma weak alias_declared_late = target_of_late_alias // expected-note {{previous definition is here}} +int alias_declared_late(void) __attribute__((visibility("default"))); // expected-warning {{attribute declaration must precede definition}} +int target_of_late_alias(void) { return 1; } +#endif _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
