[clang] [Clang] Fix isWeakImported() to traverse redeclaration chain for avai… (PR #181482)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Apr 20 00:16:11 PDT 2026
https://github.com/kevinlzh1108 updated https://github.com/llvm/llvm-project/pull/181482
>From 36d5d414c31b73c72964b32ff68f6e9efe37e4c8 Mon Sep 17 00:00:00 2001
From: kevinlzh1108 <kevinlzh1108 at gmail.com>
Date: Mon, 20 Apr 2026 15:15:26 +0800
Subject: [PATCH] [Clang] Fix isWeakImported() to traverse redeclaration chain
for availability attributes
Also fix mergeInheritableAttributes() to propagate all AvailabilityAttrs
across redeclarations, and add a Clang Modules test that reproduces the
import-order issue where a weak-linked symbol becomes strong-linked.
Fixes an issue where availability attributes attached to an earlier
declaration were not observed by isWeakImported() when querying a later
redeclaration, causing weak_import to be dropped under certain module
import orders.
---
clang/lib/AST/DeclBase.cpp | 21 ++++++----
clang/lib/Serialization/ASTReaderDecl.cpp | 15 ++++---
.../Inputs/availability-redecl-weak/forward.h | 1 +
.../availability-redecl-weak/interface.h | 8 ++++
.../availability-redecl-weak/module.modulemap | 2 +
clang/test/Modules/availability-redecl-weak.m | 39 +++++++++++++++++++
6 files changed, 73 insertions(+), 13 deletions(-)
create mode 100644 clang/test/Modules/Inputs/availability-redecl-weak/forward.h
create mode 100644 clang/test/Modules/Inputs/availability-redecl-weak/interface.h
create mode 100644 clang/test/Modules/Inputs/availability-redecl-weak/module.modulemap
create mode 100644 clang/test/Modules/availability-redecl-weak.m
diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp
index 8f72fd08dbb1f..5f24f62fad7cc 100644
--- a/clang/lib/AST/DeclBase.cpp
+++ b/clang/lib/AST/DeclBase.cpp
@@ -875,15 +875,20 @@ bool Decl::isWeakImported() const {
if (!canBeWeakImported(IsDefinition))
return false;
- for (const auto *A : getMostRecentDecl()->attrs()) {
- if (isa<WeakImportAttr>(A))
- return true;
-
- if (const auto *Availability = dyn_cast<AvailabilityAttr>(A)) {
- Availability = Availability->getEffectiveAttr();
- if (CheckAvailability(getASTContext(), Availability, nullptr,
- VersionTuple()) == AR_NotYetIntroduced)
+ // Traverse the entire redeclaration chain, since availability attributes
+ // may not be present on the most recent declaration (e.g., a @class forward
+ // declaration may lack the availability attribute from the @interface).
+ for (const auto *D : redecls()) {
+ for (const auto *A : D->attrs()) {
+ if (isa<WeakImportAttr>(A))
return true;
+
+ if (const auto *Availability = dyn_cast<AvailabilityAttr>(A)) {
+ Availability = Availability->getEffectiveAttr();
+ if (CheckAvailability(getASTContext(), Availability, nullptr,
+ VersionTuple()) == AR_NotYetIntroduced)
+ return true;
+ }
}
}
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 9033ea55bc5e2..664544f7809b3 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -3620,11 +3620,16 @@ void mergeInheritableAttributes(ASTReader &Reader, Decl *D, Decl *Previous) {
D->addAttr(NewAttr);
}
- const auto *AA = Previous->getAttr<AvailabilityAttr>();
- if (AA && !D->hasAttr<AvailabilityAttr>()) {
- NewAttr = AA->clone(Context);
- NewAttr->setInherited(true);
- D->addAttr(NewAttr);
+ // getAttr<AvailabilityAttr>() only returns the first match, but a
+ // declaration can carry multiple AvailabilityAttrs (one per platform,
+ // e.g. macos + ios). Iterate over all of them so every platform's
+ // availability is propagated to the new redeclaration.
+ if (!D->hasAttr<AvailabilityAttr>()) {
+ for (const auto *AA : Previous->specific_attrs<AvailabilityAttr>()) {
+ NewAttr = AA->clone(Context);
+ NewAttr->setInherited(true);
+ D->addAttr(NewAttr);
+ }
}
}
} // namespace
diff --git a/clang/test/Modules/Inputs/availability-redecl-weak/forward.h b/clang/test/Modules/Inputs/availability-redecl-weak/forward.h
new file mode 100644
index 0000000000000..c9123d49b7d99
--- /dev/null
+++ b/clang/test/Modules/Inputs/availability-redecl-weak/forward.h
@@ -0,0 +1 @@
+ at class WeakRedecl1;
diff --git a/clang/test/Modules/Inputs/availability-redecl-weak/interface.h b/clang/test/Modules/Inputs/availability-redecl-weak/interface.h
new file mode 100644
index 0000000000000..9de6f4aaa30e2
--- /dev/null
+++ b/clang/test/Modules/Inputs/availability-redecl-weak/interface.h
@@ -0,0 +1,8 @@
+// Mimics a class like UTType that has availability attrs for multiple platforms.
+// The 'macos' attr comes before 'ios', so getAttr<AvailabilityAttr>() returns
+// 'macos' first. When mergeInheritableAttributes copies only the first attr
+// across PCM boundaries, the 'ios' attr is lost on the @class redeclaration.
+__attribute__((availability(macos,introduced=11.0)))
+__attribute__((availability(ios,introduced=14.0)))
+ at interface WeakRedecl1
+ at end
diff --git a/clang/test/Modules/Inputs/availability-redecl-weak/module.modulemap b/clang/test/Modules/Inputs/availability-redecl-weak/module.modulemap
new file mode 100644
index 0000000000000..0123708ab26cd
--- /dev/null
+++ b/clang/test/Modules/Inputs/availability-redecl-weak/module.modulemap
@@ -0,0 +1,2 @@
+module InterfaceMod { header "interface.h" export * }
+module ForwardMod { header "forward.h" export * }
diff --git a/clang/test/Modules/availability-redecl-weak.m b/clang/test/Modules/availability-redecl-weak.m
new file mode 100644
index 0000000000000..1f093e2a8b3e6
--- /dev/null
+++ b/clang/test/Modules/availability-redecl-weak.m
@@ -0,0 +1,39 @@
+// RUN: rm -rf %t
+// RUN: %clang_cc1 -triple arm64-apple-ios12.0 -fmodules-cache-path=%t \
+// RUN: -fmodules -fimplicit-module-maps \
+// RUN: -I %S/Inputs/availability-redecl-weak \
+// RUN: -DINTERFACE_FIRST -emit-llvm -o - %s | FileCheck %s
+// RUN: rm -rf %t
+// RUN: %clang_cc1 -triple arm64-apple-ios12.0 -fmodules-cache-path=%t \
+// RUN: -fmodules -fimplicit-module-maps \
+// RUN: -I %S/Inputs/availability-redecl-weak \
+// RUN: -emit-llvm -o - %s | FileCheck %s
+
+// Test that isWeakImported() traverses the redeclaration chain across module
+// boundaries to find availability attributes.
+//
+// InterfaceMod has @interface WeakRedecl1 with availability(macos,introduced=11.0)
+// and availability(ios,introduced=14.0). ForwardMod has a bare @class WeakRedecl1.
+//
+// When InterfaceMod is imported first and ForwardMod second, the @class becomes
+// getMostRecentDecl(). Cross-PCM mergeInheritableAttributes only copies the first
+// AvailabilityAttr (macos), losing the ios attr. The old isWeakImported() only
+// checked getMostRecentDecl()->attrs(), found only macos (not the ios target
+// platform), and incorrectly returned false (strong linkage).
+
+#ifdef INTERFACE_FIRST
+// This order triggers the bug: @interface loaded first, then @class becomes
+// the most recent decl with only an inherited macos availability attr.
+ at import InterfaceMod;
+ at import ForwardMod;
+#else
+// This order works even without the fix: @class loaded first, then @interface
+// becomes the most recent decl with all availability attrs intact.
+ at import ForwardMod;
+ at import InterfaceMod;
+#endif
+
+ at implementation WeakRedecl1 (TestCategory1)
+ at end
+
+// CHECK: @"OBJC_CLASS_$_WeakRedecl1" = extern_weak global
More information about the cfe-commits
mailing list