https://github.com/yronglin updated https://github.com/llvm/llvm-project/pull/192073
>From d5c3cd1f77a34e6b0b281d2a40f23a16dc760457 Mon Sep 17 00:00:00 2001 From: yronglin <[email protected]> Date: Tue, 14 Apr 2026 22:44:09 +0800 Subject: [PATCH 1/3] [NFC][Clang] Add test for P2843R3 - Preprocessing is never undefined Signed-off-by: yronglin <[email protected]> --- clang/docs/ReleaseNotes.rst | 4 ++++ clang/test/Preprocessor/p2843r3.cpp | 35 +++++++++++++++++++++++++++++ clang/www/cxx_status.html | 2 +- 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 clang/test/Preprocessor/p2843r3.cpp diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 3e2d287d1eb1f..859b7e999848a 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -133,6 +133,10 @@ C++ Language Changes C++2c Feature Support ^^^^^^^^^^^^^^^^^^^^^ +- Implemented `P2843R3 <https://wg21.link/P2843R3>`_ Preprocessing is never undefined. + The constructs it makes ill-formed were already diagnosed by Clang under + ``-pedantic``; no behavior change, just conformance. + C++23 Feature Support ^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/test/Preprocessor/p2843r3.cpp b/clang/test/Preprocessor/p2843r3.cpp new file mode 100644 index 0000000000000..53c272390527c --- /dev/null +++ b/clang/test/Preprocessor/p2843r3.cpp @@ -0,0 +1,35 @@ +// RUN: %clang_cc1 -std=c++26 -pedantic -verify -Wno-invalid-pp-token %s +// RUN: %clang_cc1 -std=c++23 -pedantic -verify -Wno-invalid-pp-token %s + +// P2843R3: Preprocessing is never undefined. +// These constructs were previously "undefined behavior" in the preprocessor; +// as of C++26 they are ill-formed (diagnostic required). Clang already +// diagnoses them under -pedantic, so this test just pins that behavior down. + +// [cpp.cond] A macro that expands to 'defined' in a conditional expression. +#define DEFINED defined +#if DEFINED(bar) // expected-warning {{macro expansion producing 'defined' has undefined behavior}} +#endif + +// [cpp.replace.general] A preprocessing directive inside the arguments of a +// function-like macro invocation. +#define FUNCTION_MACRO(...) +FUNCTION_MACRO( + #if 0 // expected-warning {{embedding a directive within macro arguments has undefined behavior}} + #endif +) + +// [cpp.concat] Concatenation that does not form a valid preprocessing token. +#define CONCAT(A, B) A ## B +CONCAT(=, >) // expected-error {{pasting formed '=>', an invalid preprocessing token}} +// expected-error@-1 {{expected unqualified-id}} + +// [cpp.predefined] #undef of a reserved identifier / builtin macro. +#undef defined // expected-error {{'defined' cannot be used as a macro name}} +#undef __DATE__ // expected-warning {{undefining builtin macro}} + +// [cpp.line] #line with a non-positive or out-of-range argument. +#line 0 // expected-warning {{#line directive with zero argument is a GNU extension}} +#line -1 // expected-error {{#line directive requires a positive integer argument}} +#line 2147483647 // ok, largest value required to be accepted +#line 2147483648 // expected-warning {{C requires #line number to be less than 2147483648, allowed as extension}} diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index 2c834b07f9a8f..4a669ff4da8a1 100755 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -330,7 +330,7 @@ <h2 id="cxx26">C++2c implementation status</h2> <tr> <td>Preprocessing is never undefined</td> <td><a href="https://wg21.link/P2843">P2843R3</a></td> - <td class="none" align="center">No</td> + <td class="full" align="center">Clang 23</td> </tr> <!-- Kona, Fall 2025--> <tr> >From 3cf297853d16cb4215fb1b98468b4537c0c98406 Mon Sep 17 00:00:00 2001 From: yronglin <[email protected]> Date: Tue, 14 Apr 2026 23:00:19 +0800 Subject: [PATCH 2/3] Update ReleaseNotes Signed-off-by: yronglin <[email protected]> --- 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 859b7e999848a..8d69c4716620b 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -133,7 +133,7 @@ C++ Language Changes C++2c Feature Support ^^^^^^^^^^^^^^^^^^^^^ -- Implemented `P2843R3 <https://wg21.link/P2843R3>`_ Preprocessing is never undefined. +- Mark `P2843R3 <https://wg21.link/P2843R3>`_ Preprocessing is never undefined as implemented. The constructs it makes ill-formed were already diagnosed by Clang under ``-pedantic``; no behavior change, just conformance. >From f374e17bf507f7786d3268c5fcdbb46a94d47894 Mon Sep 17 00:00:00 2001 From: yronglin <[email protected]> Date: Wed, 15 Apr 2026 00:32:18 +0800 Subject: [PATCH 3/3] Convert ext_embedded_directive to hard error and add more test Signed-off-by: yronglin <[email protected]> --- .../include/clang/Basic/DiagnosticLexKinds.td | 2 + clang/lib/Lex/PPDirectives.cpp | 42 +++++++++++++------ clang/lib/Lex/PPExpressions.cpp | 4 +- clang/test/Lexer/cxx-features.cpp | 20 +++++---- clang/test/Preprocessor/p2843r3.cpp | 36 +++++++++++----- 5 files changed, 72 insertions(+), 32 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td index 30efb0d90c124..2d80538efb932 100644 --- a/clang/include/clang/Basic/DiagnosticLexKinds.td +++ b/clang/include/clang/Basic/DiagnosticLexKinds.td @@ -1053,6 +1053,8 @@ def warn_defined_in_object_type_macro : Warning< def warn_defined_in_function_type_macro : Extension< "macro expansion producing 'defined' has undefined behavior">, InGroup<ExpansionToDefined>; +def err_defined_in_macro : Error< + "macro expansion producing 'defined' is not allowed">; let CategoryName = "Nullability Issue" in { diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp index be2878076510d..be68a61db4051 100644 --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -1351,8 +1351,14 @@ void Preprocessor::HandleDirective(Token &Result) { // not support this for #include-like directives, since that can result in // terrible diagnostics, and does not work in GCC. if (InMacroArgs) { - if (IdentifierInfo *II = Result.getIdentifierInfo()) { - switch (II->getPPKeywordID()) { + IdentifierInfo *II = Result.getIdentifierInfo(); + + // Certain #include-like and module-related directives never work inside + // a macro argument list: supporting them would produce terrible + // diagnostics and is already incompatible with GCC. They are always an + // error regardless of language mode. + auto IsAlwaysUnsupported = [](tok::PPKeywordKind K) { + switch (K) { case tok::pp_include: case tok::pp_import: case tok::pp_include_next: @@ -1362,20 +1368,30 @@ void Preprocessor::HandleDirective(Token &Result) { case tok::pp_module: case tok::pp___preprocessed_module: case tok::pp___preprocessed_import: - Diag(Result, diag::err_embedded_directive) - << (getLangOpts().CPlusPlusModules && - Introducer.isModuleContextualKeyword( - /*AllowExport=*/false)) - << II->getName(); - Diag(*ArgMacro, diag::note_macro_expansion_here) - << ArgMacro->getIdentifierInfo(); - DiscardUntilEndOfDirective(); - return; + return true; default: - break; + return false; } + }; + + // [cpp.replace.general] makes any embedded directive ill-formed in + // C++26; in earlier modes the construct is undefined behavior and only + // pedantically warned about. + const bool IsError = LangOpts.CPlusPlus26 || + (II && IsAlwaysUnsupported(II->getPPKeywordID())); + + if (!IsError) { + Diag(Result, diag::ext_embedded_directive); + } else { + Diag(Result, diag::err_embedded_directive) + << (LangOpts.CPlusPlusModules && + Introducer.isModuleContextualKeyword(/*AllowExport=*/false)) + << (II ? II->getName() : StringRef()); + Diag(*ArgMacro, diag::note_macro_expansion_here) + << ArgMacro->getIdentifierInfo(); + DiscardUntilEndOfDirective(); + return; } - Diag(Result, diag::ext_embedded_directive); } // Temporarily enable macro expansion if set so diff --git a/clang/lib/Lex/PPExpressions.cpp b/clang/lib/Lex/PPExpressions.cpp index 887fd25ac318d..46eb66a2eac7e 100644 --- a/clang/lib/Lex/PPExpressions.cpp +++ b/clang/lib/Lex/PPExpressions.cpp @@ -204,7 +204,9 @@ static bool EvaluateDefined(PPValue &Result, Token &PeekTok, DefinedTracker &DT, // in a different way, and compilers seem to agree on how to behave here. // So warn by default on object-type macros, but only warn in -pedantic // mode on function-type macros. - if (IsFunctionTypeMacro) + if (PP.getLangOpts().CPlusPlus26) + PP.Diag(beginLoc, diag::err_defined_in_macro); + else if (IsFunctionTypeMacro) PP.Diag(beginLoc, diag::warn_defined_in_function_type_macro); else PP.Diag(beginLoc, diag::warn_defined_in_object_type_macro); diff --git a/clang/test/Lexer/cxx-features.cpp b/clang/test/Lexer/cxx-features.cpp index 8eb9ea032879c..446bb0ac76056 100644 --- a/clang/test/Lexer/cxx-features.cpp +++ b/clang/test/Lexer/cxx-features.cpp @@ -14,21 +14,25 @@ // expected-no-diagnostics -// FIXME using `defined` in a macro has undefined behavior. +// An undefined feature-test macro evaluates to 0 in an #if expression, so +// `__cpp_##macro != N` tests the feature is defined with the exact value N +// when N is nonzero, and tests the feature is not defined (or is defined to 0, +// which feature-test macros never are) when N is 0. Avoiding `defined` inside +// a macro expansion is required for conformance with [cpp.cond]. #if __cplusplus < 201103L -#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (cxx98 == 0 ? defined(__cpp_##macro) : __cpp_##macro != cxx98) +#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (__cpp_##macro != cxx98) #elif __cplusplus < 201402L -#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (cxx11 == 0 ? defined(__cpp_##macro) : __cpp_##macro != cxx11) +#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (__cpp_##macro != cxx11) #elif __cplusplus < 201703L -#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (cxx14 == 0 ? defined(__cpp_##macro) : __cpp_##macro != cxx14) +#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (__cpp_##macro != cxx14) #elif __cplusplus < 202002L -#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (cxx17 == 0 ? defined(__cpp_##macro) : __cpp_##macro != cxx17) +#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (__cpp_##macro != cxx17) #elif __cplusplus < 202302L -#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (cxx20 == 0 ? defined(__cpp_##macro) : __cpp_##macro != cxx20) +#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (__cpp_##macro != cxx20) #elif __cplusplus == 202302L -#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (cxx23 == 0 ? defined(__cpp_##macro) : __cpp_##macro != cxx23) +#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (__cpp_##macro != cxx23) #else -#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (cxx26 == 0 ? defined(__cpp_##macro) : __cpp_##macro != cxx26) +#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (__cpp_##macro != cxx26) #endif // --- C++26 features --- diff --git a/clang/test/Preprocessor/p2843r3.cpp b/clang/test/Preprocessor/p2843r3.cpp index 53c272390527c..747fe59e25e45 100644 --- a/clang/test/Preprocessor/p2843r3.cpp +++ b/clang/test/Preprocessor/p2843r3.cpp @@ -1,21 +1,37 @@ -// RUN: %clang_cc1 -std=c++26 -pedantic -verify -Wno-invalid-pp-token %s -// RUN: %clang_cc1 -std=c++23 -pedantic -verify -Wno-invalid-pp-token %s +// RUN: %clang_cc1 -std=c++26 -pedantic -verify=cxx26,expected -Wno-invalid-pp-token %s +// RUN: %clang_cc1 -std=c++23 -pedantic -verify=cxx23,expected -Wno-invalid-pp-token %s -// P2843R3: Preprocessing is never undefined. -// These constructs were previously "undefined behavior" in the preprocessor; -// as of C++26 they are ill-formed (diagnostic required). Clang already -// diagnoses them under -pedantic, so this test just pins that behavior down. +// P2843R3: Preprocessing is never undefined. The constructs this paper makes +// ill-formed were previously undefined behavior; under C++26 Clang now +// diagnoses them as errors, while retaining the pre-existing pedantic warning +// in earlier language modes for compatibility. -// [cpp.cond] A macro that expands to 'defined' in a conditional expression. +// [cpp.cond] A macro expansion that produces 'defined' in a conditional +// expression. P2843R3 makes this ill-formed; promoted to a hard error in +// C++26. #define DEFINED defined -#if DEFINED(bar) // expected-warning {{macro expansion producing 'defined' has undefined behavior}} +// cxx26-error@+2 {{macro expansion producing 'defined' is not allowed}} +// cxx23-warning@+1 {{macro expansion producing 'defined' has undefined behavior}} +#if DEFINED(bar) +#endif + +// Malformed 'defined' operands are ill-formed in all modes. +#if defined() // expected-error {{macro name must be an identifier}} +#endif +#if defined(a b) // expected-error {{missing ')' after 'defined'}} expected-note {{to match this '('}} +#endif +#if defined(a, b) // expected-error {{missing ')' after 'defined'}} expected-note {{to match this '('}} #endif // [cpp.replace.general] A preprocessing directive inside the arguments of a -// function-like macro invocation. +// function-like macro invocation. Promoted to a hard error in C++26. #define FUNCTION_MACRO(...) +// cxx26-note@+1 2 {{expansion of macro 'FUNCTION_MACRO' requested here}} FUNCTION_MACRO( - #if 0 // expected-warning {{embedding a directive within macro arguments has undefined behavior}} + // cxx26-error@+2 {{embedding a #if directive within macro arguments is not supported}} + // cxx23-warning@+1 {{embedding a directive within macro arguments has undefined behavior}} + #if 0 + // cxx26-error@+1 {{embedding a #endif directive within macro arguments is not supported}} #endif ) _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
