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 <[email protected]>
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) <[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

>From aa123bfb0f02602d2b0bd7d1731fc45f8edaba6b Mon Sep 17 00:00:00 2001
From: Evgenii Kudriashov <[email protected]>
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}}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to