https://github.com/kevinlzh1108 updated 
https://github.com/llvm/llvm-project/pull/181482

>From 7f9f455552f801a11033371e8d0acbca07e52273 Mon Sep 17 00:00:00 2001
From: kevinlzh1108 <[email protected]>
Date: Sat, 14 Feb 2026 22:41:32 +0800
Subject: [PATCH 1/2] [Clang] Fix isWeakImported() to traverse redeclaration
 chain for availability attributes

Decl::isWeakImported() only checked attributes on getMostRecentDecl(),
which missed availability attributes on earlier declarations in the
redeclaration chain. This caused incorrect strong linking when a forward
declaration (@class) without availability attributes became the most
recent declaration, shadowing the original @interface declaration that
carried the availability(ios,introduced=14.0) attribute.

This is particularly problematic with Clang Modules and Precompiled
Headers (PCH), where module deserialization order can cause a forward
declaration from one module (e.g., UIKit's @class UTType) to become
the most recent declaration, even though the original @interface
declaration (in UniformTypeIdentifiers) has the availability attribute.

The fix changes isWeakImported() to iterate over all redeclarations
using redecls(), consistent with how Decl::isReferenced() already
traverses the redeclaration chain in the same file.

Fixes a bug where apps targeting iOS < 14.0 would strongly link
UniformTypeIdentifiers symbols instead of weak-linking them, causing
crashes on older iOS versions.
---
 clang/lib/AST/DeclBase.cpp                    | 18 ++++---
 .../attr-availability-redecl-weak.m           | 47 +++++++++++++++++++
 2 files changed, 58 insertions(+), 7 deletions(-)
 create mode 100644 clang/test/CodeGenObjC/attr-availability-redecl-weak.m

diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp
index 0a1e442656c35..0c122f808dea4 100644
--- a/clang/lib/AST/DeclBase.cpp
+++ b/clang/lib/AST/DeclBase.cpp
@@ -872,14 +872,18 @@ 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)) {
-      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))
+        if (CheckAvailability(getASTContext(), Availability, nullptr,
+                              VersionTuple()) == AR_NotYetIntroduced)
+          return true;
     }
   }
 
diff --git a/clang/test/CodeGenObjC/attr-availability-redecl-weak.m 
b/clang/test/CodeGenObjC/attr-availability-redecl-weak.m
new file mode 100644
index 0000000000000..e1000e66a6042
--- /dev/null
+++ b/clang/test/CodeGenObjC/attr-availability-redecl-weak.m
@@ -0,0 +1,47 @@
+// RUN: %clang_cc1 -triple arm64-apple-ios12.0 -emit-llvm -o - %s | FileCheck 
%s
+
+// Test that isWeakImported() correctly traverses the redeclaration chain
+// to find availability attributes, even when a forward declaration (@class)
+// without availability attributes becomes the most recent declaration.
+
+// Case 1: @interface (with availability) first, then @class (without 
availability).
+// The @class becomes getMostRecentDecl(). Without the fix, isWeakImported()
+// would only check the @class's attributes and miss the availability 
attribute,
+// resulting in strong linkage instead of extern_weak.
+
+__attribute__((availability(ios,introduced=14.0)))
+@interface WeakRedecl1
+@end
+
+@class WeakRedecl1;
+
+@implementation WeakRedecl1 (TestCategory1)
+@end
+
+// CHECK: @"OBJC_CLASS_$_WeakRedecl1" = extern_weak global
+
+// Case 2: @class first, then @interface (with availability).
+// This order already worked before the fix because @interface becomes
+// getMostRecentDecl() and carries the availability attribute.
+// We test it here to ensure the fix doesn't regress this case.
+
+@class WeakRedecl2;
+
+__attribute__((availability(ios,introduced=14.0)))
+@interface WeakRedecl2
+@end
+
+@implementation WeakRedecl2 (TestCategory2)
+@end
+
+// CHECK: @"OBJC_CLASS_$_WeakRedecl2" = extern_weak global
+
+// Case 3: Single declaration with availability (baseline, no redeclaration).
+__attribute__((availability(ios,introduced=14.0)))
+@interface WeakSingle
+@end
+
+@implementation WeakSingle (TestCategory3)
+@end
+
+// CHECK: @"OBJC_CLASS_$_WeakSingle" = extern_weak global

>From 374420f77132fa1809c34eeb768827115c1ba1c7 Mon Sep 17 00:00:00 2001
From: kevinlzh1108 <[email protected]>
Date: Mon, 13 Apr 2026 20:08:31 +0800
Subject: [PATCH 2/2] [Clang] Replace single-file test with Clang Modules test
 for isWeakImported() fix

The previous single-file test (attr-availability-redecl-weak.m) was not a
valid reduction: within a single translation unit, Sema's mergeDeclAttributes()
automatically merges all availability attributes from @interface to @class,
so the old getMostRecentDecl()->attrs() code also found the ios availability
attribute. The test passed with or without the fix.

Replace it with a Clang Modules test that reproduces the actual bug:
- InterfaceMod: @interface with availability(macos,introduced=11.0) and
  availability(ios,introduced=14.0) (macos listed first)
- ForwardMod: bare @class (no availability attributes)

When InterfaceMod is imported first and ForwardMod second, the @class becomes
getMostRecentDecl(). Cross-PCM mergeInheritableAttributes (ASTReaderDecl.cpp)
uses getAttr<AvailabilityAttr>() which only copies the first AvailabilityAttr
(macos), losing the ios attr. The old isWeakImported() only checked
getMostRecentDecl()->attrs(), found only macos (wrong platform), and
incorrectly returned false (strong linkage).

Both import orders are tested to ensure no regression.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 .../attr-availability-redecl-weak.m           | 47 -------------------
 .../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 +++++++++++++++
 5 files changed, 50 insertions(+), 47 deletions(-)
 delete mode 100644 clang/test/CodeGenObjC/attr-availability-redecl-weak.m
 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/test/CodeGenObjC/attr-availability-redecl-weak.m 
b/clang/test/CodeGenObjC/attr-availability-redecl-weak.m
deleted file mode 100644
index e1000e66a6042..0000000000000
--- a/clang/test/CodeGenObjC/attr-availability-redecl-weak.m
+++ /dev/null
@@ -1,47 +0,0 @@
-// RUN: %clang_cc1 -triple arm64-apple-ios12.0 -emit-llvm -o - %s | FileCheck 
%s
-
-// Test that isWeakImported() correctly traverses the redeclaration chain
-// to find availability attributes, even when a forward declaration (@class)
-// without availability attributes becomes the most recent declaration.
-
-// Case 1: @interface (with availability) first, then @class (without 
availability).
-// The @class becomes getMostRecentDecl(). Without the fix, isWeakImported()
-// would only check the @class's attributes and miss the availability 
attribute,
-// resulting in strong linkage instead of extern_weak.
-
-__attribute__((availability(ios,introduced=14.0)))
-@interface WeakRedecl1
-@end
-
-@class WeakRedecl1;
-
-@implementation WeakRedecl1 (TestCategory1)
-@end
-
-// CHECK: @"OBJC_CLASS_$_WeakRedecl1" = extern_weak global
-
-// Case 2: @class first, then @interface (with availability).
-// This order already worked before the fix because @interface becomes
-// getMostRecentDecl() and carries the availability attribute.
-// We test it here to ensure the fix doesn't regress this case.
-
-@class WeakRedecl2;
-
-__attribute__((availability(ios,introduced=14.0)))
-@interface WeakRedecl2
-@end
-
-@implementation WeakRedecl2 (TestCategory2)
-@end
-
-// CHECK: @"OBJC_CLASS_$_WeakRedecl2" = extern_weak global
-
-// Case 3: Single declaration with availability (baseline, no redeclaration).
-__attribute__((availability(ios,introduced=14.0)))
-@interface WeakSingle
-@end
-
-@implementation WeakSingle (TestCategory3)
-@end
-
-// CHECK: @"OBJC_CLASS_$_WeakSingle" = extern_weak global
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 @@
+@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)))
+@interface WeakRedecl1
+@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.
+@import InterfaceMod;
+@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.
+@import ForwardMod;
+@import InterfaceMod;
+#endif
+
+@implementation WeakRedecl1 (TestCategory1)
+@end
+
+// CHECK: @"OBJC_CLASS_$_WeakRedecl1" = extern_weak global

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

Reply via email to