[clang] [C23] Fix compound literals within function prototype (PR #132097)
Aaron Ballman via cfe-commits
cfe-commits at lists.llvm.org
Thu Mar 20 05:02:34 PDT 2025
https://github.com/AaronBallman updated https://github.com/llvm/llvm-project/pull/132097
>From 7882bfbbe7ada59de91625c5f0365cec1fbe3c5b Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Wed, 19 Mar 2025 16:50:12 -0400
Subject: [PATCH 1/5] [C23] Fix compound literals within function prototype
WG14 N2819 clarified that a compound literal within a function
prototype has a lifetime similar to that of a local variable within the
function, not a file scope variable.
---
clang/docs/ReleaseNotes.rst | 4 ++++
clang/lib/Sema/SemaExpr.cpp | 8 +++++++-
clang/test/AST/ByteCode/literals.cpp | 6 ++----
clang/test/C/C23/n2819.c | 17 +++++++++--------
clang/www/c_status.html | 2 +-
5 files changed, 23 insertions(+), 14 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 666bbf22acc93..768ba480a7ee7 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -148,6 +148,10 @@ C23 Feature Support
better diagnostic behavior for the ``va_start()`` macro in C23 and later.
This also updates the definition of ``va_start()`` in ``<stdarg.h>`` to use
the new builtin. Fixes #GH124031.
+- Implemented ``WG14 N2819 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2819.pdf`_
+ which clarified that a compound literal used within a function prototype is
+ treated as if the compound literal were within the body rather than at file
+ scope.
Non-comprehensive list of changes in this release
-------------------------------------------------
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index a03acb5fbd273..fca3a2e8c19af 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -7141,7 +7141,13 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo,
return ExprError();
LiteralExpr = Result.get();
- bool isFileScope = !CurContext->isFunctionOrMethod();
+ // We treat the compound literal as being at file scope if it's not in a
+ // function or method body, or within the function's prototype scope. This
+ // means the following compound literal is not at file scope:
+ // void func(char *para[(int [1]){ 0 }[0]);
+ const Scope *S = getCurScope();
+ bool isFileScope = !CurContext->isFunctionOrMethod() &&
+ (!S || !S->isFunctionPrototypeScope());
// In C, compound literals are l-values for some reason.
// For GCC compatibility, in C++, file-scope array compound literals with
diff --git a/clang/test/AST/ByteCode/literals.cpp b/clang/test/AST/ByteCode/literals.cpp
index f206f020ecb47..a9b787892eff2 100644
--- a/clang/test/AST/ByteCode/literals.cpp
+++ b/clang/test/AST/ByteCode/literals.cpp
@@ -849,13 +849,11 @@ namespace CompoundLiterals {
}
static_assert(get5() == 5, "");
- constexpr int get6(int f = (int[]){1,2,6}[2]) { // ref-note {{subexpression not valid in a constant expression}} \
- // ref-note {{declared here}}
+ constexpr int get6(int f = (int[]){1,2,6}[2]) {
return f;
}
static_assert(get6(6) == 6, "");
- // FIXME: Who's right here?
- static_assert(get6() == 6, ""); // ref-error {{not an integral constant expression}}
+ static_assert(get6() == 6, "");
constexpr int x = (int){3};
static_assert(x == 3, "");
diff --git a/clang/test/C/C23/n2819.c b/clang/test/C/C23/n2819.c
index c428fbba80245..eb0f64a69f375 100644
--- a/clang/test/C/C23/n2819.c
+++ b/clang/test/C/C23/n2819.c
@@ -1,4 +1,4 @@
-// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 3
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5
// RUN: %clang_cc1 -triple=x86_64 -emit-llvm -o - -std=c23 %s | FileCheck %s
/* WG14 N2819: No
@@ -6,9 +6,10 @@
*/
int *escaped;
+
// CHECK-LABEL: define dso_local i32 @f(
// CHECK-SAME: ptr noundef [[PTR:%.*]]) #[[ATTR0:[0-9]+]] {
-// CHECK-NEXT: entry:
+// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[PTR_ADDR:%.*]] = alloca ptr, align 8
// CHECK-NEXT: store ptr [[PTR]], ptr [[PTR_ADDR]], align 8
// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[PTR_ADDR]], align 8
@@ -19,23 +20,23 @@ int f(int *ptr) { escaped = ptr; return 1; }
// CHECK-LABEL: define dso_local i32 @g(
// CHECK-SAME: ptr noundef [[PARA:%.*]]) #[[ATTR0]] {
-// CHECK-NEXT: entry:
+// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[PARA_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: [[DOTCOMPOUNDLITERAL:%.*]] = alloca [27 x i32], align 4
// CHECK-NEXT: store ptr [[PARA]], ptr [[PARA_ADDR]], align 8
-// CHECK-NEXT: [[CALL:%.*]] = call i32 @f(ptr noundef @.compoundliteral)
+// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 4 [[DOTCOMPOUNDLITERAL]], i8 0, i64 108, i1 false)
+// CHECK-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [27 x i32], ptr [[DOTCOMPOUNDLITERAL]], i64 0, i64 0
+// CHECK-NEXT: [[CALL:%.*]] = call i32 @f(ptr noundef [[ARRAYDECAY]])
// CHECK-NEXT: [[TMP0:%.*]] = zext i32 [[CALL]] to i64
// CHECK-NEXT: ret i32 0
//
-// FIXME: notice the we are using the global .compoundliteral object, not
-// allocating a new object on each call to g(). That's what was clarified by
-// N2819.
int g(char *para [f(( int [27]) { 0 })]) {
return 0;
}
// CHECK-LABEL: define dso_local i32 @main(
// CHECK-SAME: ) #[[ATTR0]] {
-// CHECK-NEXT: entry:
+// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[RETVAL:%.*]] = alloca i32, align 4
// CHECK-NEXT: store i32 0, ptr [[RETVAL]], align 4
// CHECK-NEXT: [[CALL:%.*]] = call i32 @g(ptr noundef null)
diff --git a/clang/www/c_status.html b/clang/www/c_status.html
index 7cf50bfdb6639..c9e2eda4304f3 100644
--- a/clang/www/c_status.html
+++ b/clang/www/c_status.html
@@ -715,7 +715,7 @@ <h2 id="c2x">C23 implementation status</h2>
<tr>
<td>Disambiguate the storage class of some compound literals</td>
<td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2819.pdf">N2819</a></td>
- <td class="none" align="center">No</td>
+ <td class="unreleased" align="center">Clang 21</td>
</tr>
<tr>
<td>Add annotations for unreachable control flow v2</td>
>From 68a7a74819d0f2ef97270782988faee460a30553 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Wed, 19 Mar 2025 17:04:11 -0400
Subject: [PATCH 2/5] Fix broken RST
---
clang/docs/ReleaseNotes.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 768ba480a7ee7..49b266ae8a5c5 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -148,7 +148,7 @@ C23 Feature Support
better diagnostic behavior for the ``va_start()`` macro in C23 and later.
This also updates the definition of ``va_start()`` in ``<stdarg.h>`` to use
the new builtin. Fixes #GH124031.
-- Implemented ``WG14 N2819 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2819.pdf`_
+- Implemented ``WG14 N2819 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2819.pdf>`_
which clarified that a compound literal used within a function prototype is
treated as if the compound literal were within the body rather than at file
scope.
>From 168af58d4dc34be573e5f5b08904e6bb035285fc Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Wed, 19 Mar 2025 17:26:57 -0400
Subject: [PATCH 3/5] Actually fix the documentation build, this time for sure
---
clang/docs/ReleaseNotes.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 49b266ae8a5c5..022aa908bd1b6 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -148,7 +148,7 @@ C23 Feature Support
better diagnostic behavior for the ``va_start()`` macro in C23 and later.
This also updates the definition of ``va_start()`` in ``<stdarg.h>`` to use
the new builtin. Fixes #GH124031.
-- Implemented ``WG14 N2819 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2819.pdf>`_
+- Implemented `WG14 N2819 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2819.pdf>`_
which clarified that a compound literal used within a function prototype is
treated as if the compound literal were within the body rather than at file
scope.
>From b6ef700d9db387c66aa7eb121e3b0fbeb362de1d Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Wed, 19 Mar 2025 17:46:12 -0400
Subject: [PATCH 4/5] Add more test cases based on review feedback
---
clang/test/AST/ByteCode/literals.cpp | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/clang/test/AST/ByteCode/literals.cpp b/clang/test/AST/ByteCode/literals.cpp
index a9b787892eff2..8366d69cfcb62 100644
--- a/clang/test/AST/ByteCode/literals.cpp
+++ b/clang/test/AST/ByteCode/literals.cpp
@@ -873,8 +873,20 @@ namespace CompoundLiterals {
return m;
}
static_assert(get3() == 3, "");
+
+ constexpr int *f(int *a=(int[]){1,2,3}) { return a; } // both-note {{temporary created here}}
+ constinit int *a1 = f(); // both-error {{variable does not have a constant initializer}} \
+ both-note {{required by 'constinit' specifier here}} \
+ both-note {{pointer to subobject of temporary is not a constant expression}}
+ static_assert(f()[0] == 1); // Ok
#endif
-};
+
+ constexpr int f2(int *x =(int[]){1,2,3}) {
+ return x[0];
+ }
+ constexpr int g = f2(); // Should evaluate to 1?
+ static_assert(g == 1, "");
+}
namespace TypeTraits {
static_assert(__is_trivial(int), "");
>From f89a438b56aa8b96ba5c8ec60ff8f9a6aaa5a52b Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Thu, 20 Mar 2025 08:02:08 -0400
Subject: [PATCH 5/5] Add another test case from review feedback
---
clang/test/AST/ByteCode/literals.cpp | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/clang/test/AST/ByteCode/literals.cpp b/clang/test/AST/ByteCode/literals.cpp
index 8366d69cfcb62..73fcb0f1f2dc3 100644
--- a/clang/test/AST/ByteCode/literals.cpp
+++ b/clang/test/AST/ByteCode/literals.cpp
@@ -886,6 +886,19 @@ namespace CompoundLiterals {
}
constexpr int g = f2(); // Should evaluate to 1?
static_assert(g == 1, "");
+
+ // This example should be rejected because the lifetime of the compound
+ // literal assigned into x is that of the full expression, which is the
+ // parenthesized assignment operator. So the return statement is using a
+ // dangling pointer. FIXME: the note saying it's a read of a dereferenced
+ // null pointer suggests we're doing something odd during constant expression
+ // evaluation: I think it's still taking 'x' as being null from the call to
+ // f3() rather than tracking the assignment happening in the VLA.
+ constexpr int f3(int *x, int (*y)[*(x=(int[]){1,2,3})]) { // both-warning {{object backing the pointer x will be destroyed at the end of the full-expression}}
+ return x[0]; // both-note {{read of dereferenced null pointer is not allowed in a constant expression}}
+ }
+ constexpr int h = f3(0,0); // both-error {{constexpr variable 'h' must be initialized by a constant expression}} \
+ both-note {{in call to 'f3(nullptr, nullptr)'}}
}
namespace TypeTraits {
More information about the cfe-commits
mailing list