Author: Aaron Ballman Date: 2025-03-20T08:03:52-04:00 New Revision: c65fa9163e47af5e86b2a187dd45ae665cf5a996
URL: https://github.com/llvm/llvm-project/commit/c65fa9163e47af5e86b2a187dd45ae665cf5a996 DIFF: https://github.com/llvm/llvm-project/commit/c65fa9163e47af5e86b2a187dd45ae665cf5a996.diff LOG: [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. Added: Modified: clang/docs/ReleaseNotes.rst clang/lib/Sema/SemaExpr.cpp clang/test/AST/ByteCode/literals.cpp clang/test/C/C23/n2819.c clang/www/c_status.html Removed: ################################################################################ diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 4258d0d72c950..88862f7661191 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..42f0acb62ee5d 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 @@ -7162,20 +7168,20 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, // FIXME: GCC supports compound literals of reference type, which should // obviously have a value kind derived from the kind of reference involved. ExprValueKind VK = - (getLangOpts().CPlusPlus && !(isFileScope && literalType->isArrayType())) + (getLangOpts().CPlusPlus && !(IsFileScope && literalType->isArrayType())) ? VK_PRValue : VK_LValue; - if (isFileScope) + if (IsFileScope) if (auto ILE = dyn_cast<InitListExpr>(LiteralExpr)) for (unsigned i = 0, j = ILE->getNumInits(); i != j; i++) { Expr *Init = ILE->getInit(i); ILE->setInit(i, ConstantExpr::Create(Context, Init)); } - auto *E = new (Context) CompoundLiteralExpr(LParenLoc, TInfo, literalType, - VK, LiteralExpr, isFileScope); - if (isFileScope) { + auto *E = new (Context) CompoundLiteralExpr(LParenLoc, TInfo, literalType, VK, + LiteralExpr, IsFileScope); + if (IsFileScope) { if (!LiteralExpr->isTypeDependent() && !LiteralExpr->isValueDependent() && !literalType->isDependentType()) // C99 6.5.2.5p3 @@ -7191,7 +7197,7 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, return ExprError(); } - if (!isFileScope && !getLangOpts().CPlusPlus) { + if (!IsFileScope && !getLangOpts().CPlusPlus) { // Compound literals that have automatic storage duration are destroyed at // the end of the scope in C; in C++, they're just temporaries. diff --git a/clang/test/AST/ByteCode/literals.cpp b/clang/test/AST/ByteCode/literals.cpp index f206f020ecb47..73fcb0f1f2dc3 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, ""); @@ -875,8 +873,33 @@ 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, ""); + + // 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 { static_assert(__is_trivial(int), ""); 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> _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits