[clang] [Clang] Accept [[clang::lifetimebound]] after a parameter's array brackets (PR #221627)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Sep 6 20:48:07 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: David Meng (davidmenggx)
<details>
<summary>Changes</summary>
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
---
Full diff: https://github.com/llvm/llvm-project/pull/221627.diff
5 Files Affected:
- (modified) clang/docs/ReleaseNotes.md (+5)
- (modified) clang/lib/Sema/SemaType.cpp (+14-1)
- (modified) clang/test/Sema/LifetimeSafety/annotation-suggestions-fixits.cpp (+6)
- (modified) clang/test/Sema/attr-lifetimebound.c (+1)
- (modified) clang/test/SemaCXX/attr-lifetimebound.cpp (+37)
``````````diff
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 (¶m)[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 (¶m)[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
``````````
</details>
https://github.com/llvm/llvm-project/pull/221627
More information about the cfe-commits
mailing list