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

Reply via email to