https://github.com/davidmenggx created 
https://github.com/llvm/llvm-project/pull/221627

In `const char (&s)[N] [[clang::lifetimebound]]`, the attribute comes after the 
array brackets, so C++ attaches it to the array type rather than the parameter, 
and Clang rejected it there.

Move it to the parameter instead, which is what 
`__attribute__((lifetimebound))` in that position already did.

Fixes https://github.com/llvm/llvm-project/issues/221221

>From 17fb165b693d49440bb6af026b947bc967c82fac Mon Sep 17 00:00:00 2001
From: David Meng <[email protected]>
Date: Sun, 6 Sep 2026 20:44:16 -0700
Subject: [PATCH] [Clang] Accept [[clang::lifetimebound]] after a parameter's
 array brackets

In `const char (&s)[N] [[clang::lifetimebound]]`, the attribute comes after the
array brackets, so C++ attaches it to the array type rather than the parameter
([dcl.array]p1), and Clang rejected it there.

Move it to the parameter instead, which is what
`__attribute__((lifetimebound))` in that position already did.

Fixes https://github.com/llvm/llvm-project/issues/221221
---
 clang/docs/ReleaseNotes.md                    |  5 +++
 clang/lib/Sema/SemaType.cpp                   | 15 +++++++-
 .../annotation-suggestions-fixits.cpp         |  6 +++
 clang/test/Sema/attr-lifetimebound.c          |  1 +
 clang/test/SemaCXX/attr-lifetimebound.cpp     | 37 +++++++++++++++++++
 5 files changed, 63 insertions(+), 1 deletion(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 1686537008499..d0c357281a2e4 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -534,6 +534,11 @@ 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)
 
+- `[[clang::lifetimebound]]` is no longer rejected when written after a
+  parameter's array bound (e.g. `const char (&s)[N] [[clang::lifetimebound]]`);
+  it now marks the parameter, as `__attribute__((lifetimebound))` already did.
+  (#GH221221)
+
 #### Bug Fixes to C++ Support
 
 - Fixed a false type mismatch when a typedef naming an anonymous enumeration
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 9de4f12aabf68..e54b8516438bb 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8993,12 +8993,25 @@ static void HandleAnnotateTypeAttr(TypeProcessingState 
&State,
 static void HandleLifetimeBoundAttr(TypeProcessingState &State,
                                     QualType &CurType,
                                     ParsedAttr &Attr) {
-  if (State.getDeclarator().isDeclarationOfFunction()) {
+  Declarator &D = State.getDeclarator();
+  if (D.isDeclarationOfFunction()) {
     CurType = State.getAttributedType(
         createSimpleAttr<LifetimeBoundAttr>(State.getSema().Context, Attr),
         CurType, CurType);
     return;
   }
+  // An attribute-specifier-seq after an array bound appertains to the array
+  // type ([dcl.array]p1), so it lands on the array chunk rather than on the
+  // parameter:
+  //   int *f(const char (&s)[4] [[clang::lifetimebound]]);
+  // Apply it to the parameter, as the GNU spelling already does.
+  if (D.isPrototypeContext() &&
+      D.getTypeObject(State.getCurrentChunkIndex()).Kind ==
+          DeclaratorChunk::Array) {
+    moveAttrFromListToList(Attr, State.getCurrentAttributes(),
+                           D.getAttributes());
+    return;
+  }
   State.getSema().Diag(Attr.getLoc(), diag::err_attribute_wrong_decl_type)
       << Attr << Attr.isRegularKeywordAttribute()
       << ExpectedParameterOrImplicitObjectParameter;
diff --git a/clang/test/Sema/LifetimeSafety/annotation-suggestions-fixits.cpp 
b/clang/test/Sema/LifetimeSafety/annotation-suggestions-fixits.cpp
index dde1c8f7e2431..839dfcc61e6c8 100644
--- a/clang/test/Sema/LifetimeSafety/annotation-suggestions-fixits.cpp
+++ b/clang/test/Sema/LifetimeSafety/annotation-suggestions-fixits.cpp
@@ -81,6 +81,12 @@ View param_default(View a = View()) {
   return a;
 }
 
+const char *arr_ref_param(const char (&a)[2]) {
+  // CHECK: :[[@LINE-1]]:45: warning: parameter in intra-TU function should be 
marked
+  // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:45-[[@LINE-2]]:45}:" 
{{\[\[}}clang::lifetimebound]]"
+  return a;
+}
+
 int *arr_default(int a[2] = nullptr) {
   // CHECK: :[[@LINE-1]]:23: warning: parameter in intra-TU function should be 
marked
   // CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:23-[[@LINE-2]]:23}:" 
{{\[\[}}clang::lifetimebound]]"
diff --git a/clang/test/Sema/attr-lifetimebound.c 
b/clang/test/Sema/attr-lifetimebound.c
index 6292fe90bdf68..e7ea37f5c9081 100644
--- a/clang/test/Sema/attr-lifetimebound.c
+++ b/clang/test/Sema/attr-lifetimebound.c
@@ -1,6 +1,7 @@
 // RUN: %clang_cc1 -std=c2x -verify %s
 
 int *ptr_param(int *param [[clang::lifetimebound]]);
+int *array_param(int param[4] [[clang::lifetimebound]]);
 int *ptr_param_gnu(int *param __attribute__((lifetimebound)));
 int *ptr_param_redecl(int *param);
 int *ptr_param_redecl(int *param [[clang::lifetimebound]]);
diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp 
b/clang/test/SemaCXX/attr-lifetimebound.cpp
index 9e2aaff6559c4..74800202a7bb0 100644
--- a/clang/test/SemaCXX/attr-lifetimebound.cpp
+++ b/clang/test/SemaCXX/attr-lifetimebound.cpp
@@ -28,6 +28,13 @@ namespace usage_invalid {
   int (*(*func_ptr_ptr)(int) [[clang::lifetimebound]])(int); // expected-error 
{{'clang::lifetimebound' attribute only applies to parameters and implicit 
object parameters}}
   struct X {};
   int (X::*member_func_ptr)(int) [[clang::lifetimebound]]; // expected-error 
{{'clang::lifetimebound' attribute only applies to parameters and implicit 
object parameters}}
+
+  int attr_on_array_var[4] [[clang::lifetimebound]]; // expected-error 
{{'clang::lifetimebound' attribute only applies to parameters and implicit 
object parameters}}
+  void attr_on_array_param_void_return(int (&param)[4] 
[[clang::lifetimebound]]); // expected-error {{'lifetimebound' attribute cannot 
be applied to a parameter of a function that returns void; did you mean 
'lifetime_capture_by(X)'}}
+  int *attr_on_array_param_with_args(int (&param)[4] 
[[clang::lifetimebound(42)]]); // expected-error {{takes no arguments}}
+  using attr_on_array_alias = int[4] [[clang::lifetimebound]]; // 
expected-error {{'clang::lifetimebound' attribute only applies to parameters 
and implicit object parameters}}
+  static_assert(sizeof(int[4] [[clang::lifetimebound]]) > 0); // 
expected-error {{'clang::lifetimebound' attribute only applies to parameters 
and implicit object parameters}} \
+                                                              // 
expected-warning {{'clang::lifetimebound' attribute ignored when parsing type}}
 }
 
 namespace usage_ok {
@@ -383,3 +390,33 @@ void test(StatusOr<FooView> foo1, 
StatusOr<NonAnnotatedFooView> foo2) {
   foo2 = NonAnnotatedFoo(); // expected-warning {{object backing 'foo2' will 
be destroyed at the end}}
 }
 } // namespace GH106372
+
+namespace array_params {
+  // An attribute written after an array bound is applied to the parameter.
+  const char *ref_to_array(const char (&a)[4] [[clang::lifetimebound]]) { 
return a; }
+  int *decayed_array(int a[4] [[clang::lifetimebound]]);
+  int *ptr_to_array(int (*a)[4] [[clang::lifetimebound]]);
+  template <int N> const char *dependent_ref_to_array(const char (&a)[N] 
[[clang::lifetimebound]]);
+  const char *gnu_ref_to_array(const char (&a)[4] 
__attribute__((lifetimebound)));
+
+  const char *test_ref_to_array() {
+    char a[4];
+    return ref_to_array(a); // expected-warning {{address of stack memory 
associated with local variable 'a' returned}}
+  }
+  int *test_decayed_array() {
+    int a[4];
+    return decayed_array(a); // expected-warning {{address of stack memory 
associated with local variable 'a' returned}}
+  }
+  int *test_ptr_to_array() {
+    int a[4];
+    return ptr_to_array(&a); // expected-warning {{address of stack memory 
associated with local variable 'a' returned}}
+  }
+  const char *test_dependent_ref_to_array() {
+    char a[4];
+    return dependent_ref_to_array(a); // expected-warning {{address of stack 
memory associated with local variable 'a' returned}}
+  }
+  const char *test_gnu_ref_to_array() {
+    char a[4];
+    return gnu_ref_to_array(a); // expected-warning {{address of stack memory 
associated with local variable 'a' returned}}
+  }
+} // namespace array_params

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

Reply via email to