https://github.com/AaronBallman updated https://github.com/llvm/llvm-project/pull/130299
>From 8ee44f1f2beb659a37c693b4f323491f6bfd8caa Mon Sep 17 00:00:00 2001 From: Aaron Ballman <aa...@aaronballman.com> Date: Fri, 7 Mar 2025 11:04:31 -0500 Subject: [PATCH 1/2] [C2y] Implement WG14 N3409 This paper removes UB around use of void expressions. Previously, code like this had undefined behavior: void foo(void) { (void)(void)1; extern void x; x; } and this is now well-defined in C2y. Functionally, this now means that it is valid to use `void` as a `_Generic` association. --- clang/docs/ReleaseNotes.rst | 4 +++ .../clang/Basic/DiagnosticSemaKinds.td | 9 ++++-- clang/lib/Sema/SemaExpr.cpp | 7 +++- clang/test/C/C2y/n3409.c | 32 +++++++++++++++++++ 4 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 clang/test/C/C2y/n3409.c diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 577b3f2130df7..df8d2eed1ec0c 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -114,6 +114,10 @@ C2y Feature Support - Implemented N3411 which allows a source file to not end with a newline character. This is still reported as a conforming extension in earlier language modes. +- Implement `WG14 N3409 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3409.pdf>`_ + which removes UB around use of ``void`` expressions. In practice, this means + that ``_Generic`` selection associations may now have ``void`` type, but it + also removes UB with code like ``(void)(void)1;``. C23 Feature Support ^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 1b46920e09619..d6e5005003322 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10425,8 +10425,13 @@ def warn_type_safety_null_pointer_required : Warning< "specified %0 type tag requires a null pointer">, InGroup<TypeSafety>; // Generic selections. -def err_assoc_type_incomplete : Error< - "type %0 in generic association incomplete">; +def ext_assoc_type_incomplete : Extension< + "ISO C requires a complete type in a '_Generic' association; %0 is an " + "incomplete type">; +def warn_c2y_compat_assoc_type_incomplete : Warning< + "use of an incomplete type in a '_Generic' association is incompatible with " + "C standards before C2y; %0 is an incomplete type">, + InGroup<CPre2yCompat>, DefaultIgnore; def err_assoc_type_nonobject : Error< "type %0 in generic association not an object type">; def err_assoc_type_variably_modified : Error< diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index f896ccab53a54..de7be6b2805af 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -1748,9 +1748,14 @@ ExprResult Sema::CreateGenericSelectionExpr( // // C11 6.5.1.1p2 "The type name in a generic association shall specify a // complete object type other than a variably modified type." + // C2y removed the requirement that an expression form must + // use a complete type, though it's still as-if the type has undergone + // lvalue conversion. We support this as an extension in C23 and + // earlier because GCC does so. unsigned D = 0; if (ControllingExpr && Types[i]->getType()->isIncompleteType()) - D = diag::err_assoc_type_incomplete; + D = LangOpts.C2y ? diag::warn_c2y_compat_assoc_type_incomplete + : diag::ext_assoc_type_incomplete; else if (ControllingExpr && !Types[i]->getType()->isObjectType()) D = diag::err_assoc_type_nonobject; else if (Types[i]->getType()->isVariablyModifiedType()) diff --git a/clang/test/C/C2y/n3409.c b/clang/test/C/C2y/n3409.c new file mode 100644 index 0000000000000..2fc789891c71d --- /dev/null +++ b/clang/test/C/C2y/n3409.c @@ -0,0 +1,32 @@ +// RUN: %clang_cc1 -verify -std=c2y -pedantic %s +// RUN: %clang_cc1 -verify=pre-c2y -std=c2y -Wpre-c2y-compat %s +// RUN: %clang_cc1 -verify=ext -std=c23 -pedantic %s +// expected-no-diagnostics + +/* WG14 N3409: Clang 21 + * Slay Some Earthly Demons X + * + * Removes the requirement that an expression with type void cannot be used in + * any way. This was making it UB to use a void expression in a _Generic + * selection expression for no good reason, as well as making it UB to cast a + * void expression to void, etc. + */ + +extern void x; +void foo() { + // FIXME: this is technically an extension before C2y and should be diagnosed + // under -pedantic. + (void)(void)1; + // FIXME: same with this. + x; + _Generic(x, void: 1); /* pre-c2y-warning {{use of an incomplete type in a '_Generic' association is incompatible with C standards before C2y; 'void' is an incomplete type}} + ext-warning {{ISO C requires a complete type in a '_Generic' association; 'void' is an incomplete type}} + */ + _Generic(x, typeof(x): 1); /* pre-c2y-warning {{use of an incomplete type in a '_Generic' association is incompatible with C standards before C2y; 'typeof (x)' (aka 'void') is an incomplete type}} + ext-warning {{ISO C requires a complete type in a '_Generic' association; 'typeof (x)' (aka 'void') is an incomplete type}} + */ + (void)_Generic(void, default : 1); /* pre-c2y-warning {{passing a type argument as the first operand to '_Generic' is incompatible with C standards before C2y}} + ext-warning {{passing a type argument as the first operand to '_Generic' is a C2y extension}} + */ +} + >From c6a0b3f4c486a4210c1d20daa015809adf70995b Mon Sep 17 00:00:00 2001 From: Aaron Ballman <aa...@aaronballman.com> Date: Fri, 7 Mar 2025 13:08:20 -0500 Subject: [PATCH 2/2] Update based on review feedback; fix tests --- clang-tools-extra/clangd/IncludeFixer.cpp | 1 - .../include/clang/Basic/DiagnosticSemaKinds.td | 8 ++++---- clang/lib/Sema/SemaStmt.cpp | 17 +++++++---------- clang/test/C/C2y/n3409.c | 14 +++++++++----- .../Sema/generic-selection-type-extension.c | 2 +- clang/test/Sema/generic-selection.c | 2 +- clang/test/SemaCXX/generic-selection.cpp | 2 +- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/clang-tools-extra/clangd/IncludeFixer.cpp b/clang-tools-extra/clangd/IncludeFixer.cpp index fadd1105691fc..8b74c761d23ca 100644 --- a/clang-tools-extra/clangd/IncludeFixer.cpp +++ b/clang-tools-extra/clangd/IncludeFixer.cpp @@ -84,7 +84,6 @@ std::vector<Fix> IncludeFixer::fix(DiagnosticsEngine::Level DiagLevel, case diag::err_array_incomplete_or_sizeless_type: case diag::err_array_size_incomplete_type: case diag::err_asm_incomplete_type: - case diag::err_assoc_type_incomplete: case diag::err_bad_cast_incomplete: case diag::err_call_function_incomplete_return: case diag::err_call_incomplete_argument: diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index d6e5005003322..21be7c358a61d 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10426,11 +10426,11 @@ def warn_type_safety_null_pointer_required : Warning< // Generic selections. def ext_assoc_type_incomplete : Extension< - "ISO C requires a complete type in a '_Generic' association; %0 is an " - "incomplete type">; + "incomplete type %0 in a '_Generic' association is a C2y extension">, + InGroup<C2y>; def warn_c2y_compat_assoc_type_incomplete : Warning< - "use of an incomplete type in a '_Generic' association is incompatible with " - "C standards before C2y; %0 is an incomplete type">, + "use of incomplete type %0 in a '_Generic' association is incompatible with " + "C standards before C2y">, InGroup<CPre2yCompat>, DefaultIgnore; def err_assoc_type_nonobject : Error< "type %0 in generic association not an object type">; diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index 0a193b5299bcc..01d1b3da9ff22 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -2269,11 +2269,10 @@ StmtResult Sema::ActOnForStmt(SourceLocation ForLoc, SourceLocation LParenLoc, for (auto *DI : DS->decls()) { if (VarDecl *VD = dyn_cast<VarDecl>(DI)) { VarDeclSeen = true; - if (VD->isLocalVarDecl() && !VD->hasLocalStorage()) - Diag(DI->getLocation(), - getLangOpts().C23 - ? diag::warn_c17_non_local_variable_decl_in_for - : diag::ext_c23_non_local_variable_decl_in_for); + if (VD->isLocalVarDecl() && !VD->hasLocalStorage()) { + Diag(DI->getLocation(), diag::err_non_local_variable_decl_in_for); + DI->setInvalidDecl(); + } } else if (!NonVarSeen) { // Keep track of the first non-variable declaration we saw so that // we can diagnose if we don't see any variable declarations. This @@ -2285,9 +2284,7 @@ StmtResult Sema::ActOnForStmt(SourceLocation ForLoc, SourceLocation LParenLoc, // Diagnose if we saw a non-variable declaration but no variable // declarations. if (NonVarSeen && !VarDeclSeen) - Diag(NonVarSeen->getLocation(), - getLangOpts().C23 ? diag::warn_c17_non_variable_decl_in_for - : diag::ext_c23_non_variable_decl_in_for); + Diag(NonVarSeen->getLocation(), diag::err_non_variable_decl_in_for); } } @@ -4052,9 +4049,9 @@ StmtResult Sema::BuildReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp, Diag(ReturnLoc, D) << CurDecl << isa<CXXDestructorDecl>(CurDecl) << RetValExp->getSourceRange(); } - // return (some void expression); is legal in C++. + // return (some void expression); is legal in C++ and C2y. else if (D != diag::ext_return_has_void_expr || - !getLangOpts().CPlusPlus) { + (!getLangOpts().CPlusPlus && !getLangOpts().C2y)) { NamedDecl *CurDecl = getCurFunctionOrMethodDecl(); int FunctionKind = 0; diff --git a/clang/test/C/C2y/n3409.c b/clang/test/C/C2y/n3409.c index 2fc789891c71d..01be716132b11 100644 --- a/clang/test/C/C2y/n3409.c +++ b/clang/test/C/C2y/n3409.c @@ -19,14 +19,18 @@ void foo() { (void)(void)1; // FIXME: same with this. x; - _Generic(x, void: 1); /* pre-c2y-warning {{use of an incomplete type in a '_Generic' association is incompatible with C standards before C2y; 'void' is an incomplete type}} - ext-warning {{ISO C requires a complete type in a '_Generic' association; 'void' is an incomplete type}} + _Generic(x, void: 1); /* pre-c2y-warning {{use of incomplete type 'void' in a '_Generic' association is incompatible with C standards before C2y}} + ext-warning {{incomplete type 'void' in a '_Generic' association is a C2y extension}} */ - _Generic(x, typeof(x): 1); /* pre-c2y-warning {{use of an incomplete type in a '_Generic' association is incompatible with C standards before C2y; 'typeof (x)' (aka 'void') is an incomplete type}} - ext-warning {{ISO C requires a complete type in a '_Generic' association; 'typeof (x)' (aka 'void') is an incomplete type}} + _Generic(x, typeof(x): 1); /* pre-c2y-warning {{use of incomplete type 'typeof (x)' (aka 'void') in a '_Generic' association is incompatible with C standards before C2y}} + ext-warning {{incomplete type 'typeof (x)' (aka 'void') in a '_Generic' association is a C2y extension}} */ (void)_Generic(void, default : 1); /* pre-c2y-warning {{passing a type argument as the first operand to '_Generic' is incompatible with C standards before C2y}} ext-warning {{passing a type argument as the first operand to '_Generic' is a C2y extension}} */ -} + // This is not sufficiently important of an extension to warrant a "not + // compatible with standards before C2y" warning, but it is an extension in + // C23 and earlier. + return x; // ext-warning {{void function 'foo' should not return void expression}} +} diff --git a/clang/test/Sema/generic-selection-type-extension.c b/clang/test/Sema/generic-selection-type-extension.c index 89ac3235807da..1efd045acba2d 100644 --- a/clang/test/Sema/generic-selection-type-extension.c +++ b/clang/test/Sema/generic-selection-type-extension.c @@ -37,7 +37,7 @@ static_assert(_Generic(ci, int : 1, const int : 0) == 1); // expected-warning {{ // but the expression operand form still rejects them. static_assert(_Generic(struct incomplete, struct incomplete : 1, default : 0) == 1); static_assert(_Generic(struct another_incomplete, struct incomplete : 1, default : 0) == 0); -static_assert(_Generic(1, struct also_incomplete : 1, default : 0) == 0); // expected-error {{type 'struct also_incomplete' in generic association incomplete}} +static_assert(_Generic(1, struct also_incomplete : 1, default : 0) == 0); void foo(int); static_assert(_Generic(__typeof__(foo), void(int) : 1, default : 0) == 1); diff --git a/clang/test/Sema/generic-selection.c b/clang/test/Sema/generic-selection.c index 1f17896ca4cda..8e7b6ed00dd50 100644 --- a/clang/test/Sema/generic-selection.c +++ b/clang/test/Sema/generic-selection.c @@ -5,7 +5,7 @@ void g(void); void foo(int n) { (void) _Generic(0, // ext-warning {{'_Generic' is a C11 extension}} - struct A: 0, // expected-error {{type 'struct A' in generic association incomplete}} + struct A: 0, // ext-warning {{incomplete type 'struct A' in a '_Generic' association is a C2y extension}} void(): 0, // expected-error {{type 'void ()' in generic association not an object type}} int[n]: 0); // expected-error {{type 'int[n]' in generic association is a variably modified type}} diff --git a/clang/test/SemaCXX/generic-selection.cpp b/clang/test/SemaCXX/generic-selection.cpp index 9e47dde3ea444..aa4a4c435adec 100644 --- a/clang/test/SemaCXX/generic-selection.cpp +++ b/clang/test/SemaCXX/generic-selection.cpp @@ -81,10 +81,10 @@ void func(struct S s) { // is an elaborated type specifier followed by the association's value and // it should work the same as in C. (void)_Generic(s, struct S : 1); + (void)_Generic(s, struct T : 1); // The rest of these cases test that we still produce a reasonable diagnostic // when referencing an unknown type or trying to define a type in other ways. - (void)_Generic(s, struct T : 1); // expected-error {{type 'struct T' in generic association incomplete}} (void)_Generic(s, struct U { int a; } : 1); // expected-error {{'U' cannot be defined in a type specifier}} (void)_Generic(s, struct V : S); // expected-error {{'S' does not refer to a value}} (void)_Generic(s, struct W : S { int b; } : 1); // expected-error {{expected '(' for function-style cast or type construction}} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits