Skip to content

Commit c65fa91

Browse files
authored
[C23] Fix compound literals within function prototype (#132097)
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.
1 parent 8c6f309 commit c65fa91

File tree

5 files changed

+55
-21
lines changed

5 files changed

+55
-21
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ C23 Feature Support
148148
better diagnostic behavior for the ``va_start()`` macro in C23 and later.
149149
This also updates the definition of ``va_start()`` in ``<stdarg.h>`` to use
150150
the new builtin. Fixes #GH124031.
151+
- Implemented `WG14 N2819 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2819.pdf>`_
152+
which clarified that a compound literal used within a function prototype is
153+
treated as if the compound literal were within the body rather than at file
154+
scope.
151155

152156
Non-comprehensive list of changes in this release
153157
-------------------------------------------------

clang/lib/Sema/SemaExpr.cpp

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7141,7 +7141,13 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo,
71417141
return ExprError();
71427142
LiteralExpr = Result.get();
71437143

7144-
bool isFileScope = !CurContext->isFunctionOrMethod();
7144+
// We treat the compound literal as being at file scope if it's not in a
7145+
// function or method body, or within the function's prototype scope. This
7146+
// means the following compound literal is not at file scope:
7147+
// void func(char *para[(int [1]){ 0 }[0]);
7148+
const Scope *S = getCurScope();
7149+
bool IsFileScope = !CurContext->isFunctionOrMethod() &&
7150+
(!S || !S->isFunctionPrototypeScope());
71457151

71467152
// In C, compound literals are l-values for some reason.
71477153
// For GCC compatibility, in C++, file-scope array compound literals with
@@ -7162,20 +7168,20 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo,
71627168
// FIXME: GCC supports compound literals of reference type, which should
71637169
// obviously have a value kind derived from the kind of reference involved.
71647170
ExprValueKind VK =
7165-
(getLangOpts().CPlusPlus && !(isFileScope && literalType->isArrayType()))
7171+
(getLangOpts().CPlusPlus && !(IsFileScope && literalType->isArrayType()))
71667172
? VK_PRValue
71677173
: VK_LValue;
71687174

7169-
if (isFileScope)
7175+
if (IsFileScope)
71707176
if (auto ILE = dyn_cast<InitListExpr>(LiteralExpr))
71717177
for (unsigned i = 0, j = ILE->getNumInits(); i != j; i++) {
71727178
Expr *Init = ILE->getInit(i);
71737179
ILE->setInit(i, ConstantExpr::Create(Context, Init));
71747180
}
71757181

7176-
auto *E = new (Context) CompoundLiteralExpr(LParenLoc, TInfo, literalType,
7177-
VK, LiteralExpr, isFileScope);
7178-
if (isFileScope) {
7182+
auto *E = new (Context) CompoundLiteralExpr(LParenLoc, TInfo, literalType, VK,
7183+
LiteralExpr, IsFileScope);
7184+
if (IsFileScope) {
71797185
if (!LiteralExpr->isTypeDependent() &&
71807186
!LiteralExpr->isValueDependent() &&
71817187
!literalType->isDependentType()) // C99 6.5.2.5p3
@@ -7191,7 +7197,7 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo,
71917197
return ExprError();
71927198
}
71937199

7194-
if (!isFileScope && !getLangOpts().CPlusPlus) {
7200+
if (!IsFileScope && !getLangOpts().CPlusPlus) {
71957201
// Compound literals that have automatic storage duration are destroyed at
71967202
// the end of the scope in C; in C++, they're just temporaries.
71977203

clang/test/AST/ByteCode/literals.cpp

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -849,13 +849,11 @@ namespace CompoundLiterals {
849849
}
850850
static_assert(get5() == 5, "");
851851

852-
constexpr int get6(int f = (int[]){1,2,6}[2]) { // ref-note {{subexpression not valid in a constant expression}} \
853-
// ref-note {{declared here}}
852+
constexpr int get6(int f = (int[]){1,2,6}[2]) {
854853
return f;
855854
}
856855
static_assert(get6(6) == 6, "");
857-
// FIXME: Who's right here?
858-
static_assert(get6() == 6, ""); // ref-error {{not an integral constant expression}}
856+
static_assert(get6() == 6, "");
859857

860858
constexpr int x = (int){3};
861859
static_assert(x == 3, "");
@@ -875,8 +873,33 @@ namespace CompoundLiterals {
875873
return m;
876874
}
877875
static_assert(get3() == 3, "");
876+
877+
constexpr int *f(int *a=(int[]){1,2,3}) { return a; } // both-note {{temporary created here}}
878+
constinit int *a1 = f(); // both-error {{variable does not have a constant initializer}} \
879+
both-note {{required by 'constinit' specifier here}} \
880+
both-note {{pointer to subobject of temporary is not a constant expression}}
881+
static_assert(f()[0] == 1); // Ok
878882
#endif
879-
};
883+
884+
constexpr int f2(int *x =(int[]){1,2,3}) {
885+
return x[0];
886+
}
887+
constexpr int g = f2(); // Should evaluate to 1?
888+
static_assert(g == 1, "");
889+
890+
// This example should be rejected because the lifetime of the compound
891+
// literal assigned into x is that of the full expression, which is the
892+
// parenthesized assignment operator. So the return statement is using a
893+
// dangling pointer. FIXME: the note saying it's a read of a dereferenced
894+
// null pointer suggests we're doing something odd during constant expression
895+
// evaluation: I think it's still taking 'x' as being null from the call to
896+
// f3() rather than tracking the assignment happening in the VLA.
897+
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}}
898+
return x[0]; // both-note {{read of dereferenced null pointer is not allowed in a constant expression}}
899+
}
900+
constexpr int h = f3(0,0); // both-error {{constexpr variable 'h' must be initialized by a constant expression}} \
901+
both-note {{in call to 'f3(nullptr, nullptr)'}}
902+
}
880903

881904
namespace TypeTraits {
882905
static_assert(__is_trivial(int), "");

clang/test/C/C23/n2819.c

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 3
1+
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5
22
// RUN: %clang_cc1 -triple=x86_64 -emit-llvm -o - -std=c23 %s | FileCheck %s
33

44
/* WG14 N2819: No
55
* Disambiguate the storage class of some compound literals
66
*/
77

88
int *escaped;
9+
910
// CHECK-LABEL: define dso_local i32 @f(
1011
// CHECK-SAME: ptr noundef [[PTR:%.*]]) #[[ATTR0:[0-9]+]] {
11-
// CHECK-NEXT: entry:
12+
// CHECK-NEXT: [[ENTRY:.*:]]
1213
// CHECK-NEXT: [[PTR_ADDR:%.*]] = alloca ptr, align 8
1314
// CHECK-NEXT: store ptr [[PTR]], ptr [[PTR_ADDR]], align 8
1415
// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[PTR_ADDR]], align 8
@@ -19,23 +20,23 @@ int f(int *ptr) { escaped = ptr; return 1; }
1920

2021
// CHECK-LABEL: define dso_local i32 @g(
2122
// CHECK-SAME: ptr noundef [[PARA:%.*]]) #[[ATTR0]] {
22-
// CHECK-NEXT: entry:
23+
// CHECK-NEXT: [[ENTRY:.*:]]
2324
// CHECK-NEXT: [[PARA_ADDR:%.*]] = alloca ptr, align 8
25+
// CHECK-NEXT: [[DOTCOMPOUNDLITERAL:%.*]] = alloca [27 x i32], align 4
2426
// CHECK-NEXT: store ptr [[PARA]], ptr [[PARA_ADDR]], align 8
25-
// CHECK-NEXT: [[CALL:%.*]] = call i32 @f(ptr noundef @.compoundliteral)
27+
// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 4 [[DOTCOMPOUNDLITERAL]], i8 0, i64 108, i1 false)
28+
// CHECK-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [27 x i32], ptr [[DOTCOMPOUNDLITERAL]], i64 0, i64 0
29+
// CHECK-NEXT: [[CALL:%.*]] = call i32 @f(ptr noundef [[ARRAYDECAY]])
2630
// CHECK-NEXT: [[TMP0:%.*]] = zext i32 [[CALL]] to i64
2731
// CHECK-NEXT: ret i32 0
2832
//
29-
// FIXME: notice the we are using the global .compoundliteral object, not
30-
// allocating a new object on each call to g(). That's what was clarified by
31-
// N2819.
3233
int g(char *para [f(( int [27]) { 0 })]) {
3334
return 0;
3435
}
3536

3637
// CHECK-LABEL: define dso_local i32 @main(
3738
// CHECK-SAME: ) #[[ATTR0]] {
38-
// CHECK-NEXT: entry:
39+
// CHECK-NEXT: [[ENTRY:.*:]]
3940
// CHECK-NEXT: [[RETVAL:%.*]] = alloca i32, align 4
4041
// CHECK-NEXT: store i32 0, ptr [[RETVAL]], align 4
4142
// CHECK-NEXT: [[CALL:%.*]] = call i32 @g(ptr noundef null)

clang/www/c_status.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ <h2 id="c2x">C23 implementation status</h2>
715715
<tr>
716716
<td>Disambiguate the storage class of some compound literals</td>
717717
<td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2819.pdf">N2819</a></td>
718-
<td class="none" align="center">No</td>
718+
<td class="unreleased" align="center">Clang 21</td>
719719
</tr>
720720
<tr>
721721
<td>Add annotations for unreachable control flow v2</td>

0 commit comments

Comments
 (0)