[clang] [clang] Keep explicit visibility on a #pragma weak alias (PR #225354)
Evgenii Kudriashov via cfe-commits
cfe-commits at lists.llvm.org
Tue Oct 20 01:08:17 PDT 2026
https://github.com/e-kud updated https://github.com/llvm/llvm-project/pull/225354
>From e4bcb3974f9c22328bb0cc23a9119eca4822981b Mon Sep 17 00:00:00 2001
From: Evgenii Kudriashov <evgenii.kudriashov at intel.com>
Date: Tue, 22 Sep 2026 03:16:06 -0700
Subject: [PATCH 1/2] [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) <noreply at anthropic.com>
---
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
>From aa123bfb0f02602d2b0bd7d1731fc45f8edaba6b Mon Sep 17 00:00:00 2001
From: Evgenii Kudriashov <evgenii.kudriashov at intel.com>
Date: Thu, 8 Oct 2026 11:12:08 -0700
Subject: [PATCH 2/2] Accept visibility after define from pragma
---
clang/docs/ReleaseNotes.md | 8 ++----
clang/lib/Sema/SemaDecl.cpp | 5 ++++
clang/lib/Sema/SemaDeclAttr.cpp | 9 +++----
clang/test/CodeGen/pragma-weak-visibility.c | 29 ++++++++++++---------
clang/test/Sema/pragma-weak.c | 12 +++++++++
5 files changed, 39 insertions(+), 24 deletions(-)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index c750cf249093c..4f51bcf88c8d7 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -721,12 +721,8 @@ features cannot lower the translation-unit ABI level;
`alias` are now correctly diagnosed as definitions when followed by an
out-of-line definition. (#GH204762)
-- 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.
+- `#pragma weak X = Y` no longer drops an explicit visibility on the declaration
+ of `X`; as in GCC, the alias takes `X`'s visibility rather than `Y`'s.
#### Bug Fixes to C++ Support
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 1459b71326375..22e5cbbb19fa4 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3236,6 +3236,11 @@ static void checkNewAttributesAfterDef(Sema &S, Decl *New, const Decl *Old) {
// SYCLExternalAttr may be added after a definition.
++I;
continue;
+ } else if (isa<VisibilityAttr>(NewAttribute) &&
+ llvm::is_contained(S.WeakTopLevelDecls(), Def)) {
+ // A #pragma weak alias is emitted at the end of the TU (as in GCC).
+ ++I;
+ continue;
}
S.Diag(NewAttribute->getLocation(),
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 6d136c6918ecd..743c8fb91c3c3 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -8849,12 +8849,9 @@ 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.
+ // The clone does not redeclare an earlier declaration of the weak name, so
+ // copy its visibility, as GCC does. Look it up before PushOnScopeChains()
+ // below, which would make the lookup find NewD.
if (NamedDecl *WeakND = LookupSingleName(S, W.getAlias(), W.getLocation(),
LookupOrdinaryName))
if (const auto *VA = WeakND->getAttr<VisibilityAttr>())
diff --git a/clang/test/CodeGen/pragma-weak-visibility.c b/clang/test/CodeGen/pragma-weak-visibility.c
index 7a0d14553da60..d8f2839a2a65e 100644
--- a/clang/test/CodeGen/pragma-weak-visibility.c
+++ b/clang/test/CodeGen/pragma-weak-visibility.c
@@ -1,6 +1,5 @@
// 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
@@ -66,16 +65,22 @@ 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
+// As in GCC, a later declaration may set the visibility too.
+// CHECK-DAG: @alias_declared_late = weak 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}}
+#pragma weak alias_declared_late = target_of_late_alias
+int alias_declared_late(void) __attribute__((visibility("default")));
int target_of_late_alias(void) { return 1; }
-#endif
+
+// CHECK-DAG: @alias_late_gcc_pragma = weak alias i32 (), ptr @target_of_late_gcc_pragma
+int target_of_late_gcc_pragma(void);
+#pragma weak alias_late_gcc_pragma = target_of_late_gcc_pragma
+#pragma GCC visibility push(default)
+int alias_late_gcc_pragma(void);
+#pragma GCC visibility pop
+int target_of_late_gcc_pragma(void) { return 1; }
+
+// CHECK-DAG: @alias_variable_late = weak alias i32, ptr @target_variable_late
+int target_variable_late = 7;
+#pragma weak alias_variable_late = target_variable_late
+extern int alias_variable_late __attribute__((visibility("default")));
diff --git a/clang/test/Sema/pragma-weak.c b/clang/test/Sema/pragma-weak.c
index bc7e963fbe213..97d4fe7603fa9 100644
--- a/clang/test/Sema/pragma-weak.c
+++ b/clang/test/Sema/pragma-weak.c
@@ -36,3 +36,15 @@ void pragma_ifunc_on_redecl(void) __attribute__((ifunc("resolver")));
// A weak alias of an ifunc does not make the ifunc itself weak.
#pragma weak pragma_alias = ifunc_alias_target
void ifunc_alias_target(void) __attribute__((ifunc("resolver")));
+
+// A visibility given after the pragma must still match an earlier one.
+int vis_target(void);
+int vis_alias(void) __attribute__((visibility("hidden"))); // expected-note {{previous attribute is here}}
+#pragma weak vis_alias = vis_target
+int vis_alias(void) __attribute__((visibility("default"))); // expected-error {{visibility does not match previous declaration}}
+
+// Only the alias created by #pragma weak accepts a visibility after it is
+// defined.
+int explicit_alias_target(void) { return 0; }
+int explicit_alias(void) __attribute__((alias("explicit_alias_target"))); // expected-note {{previous definition}}
+int explicit_alias(void) __attribute__((visibility("default"))); // expected-warning {{attribute declaration must precede definition}}
More information about the cfe-commits
mailing list