https://github.com/ianayl created https://github.com/llvm/llvm-project/pull/224769
This PR implements [CWG3013](https://wg21.link/cwg3013) in clang, where error diagnostics are now issued if an `#embed` directive or a `__has_embed` expression contains a parameter (i.e. `limit`, `prefix`, `suffix`, `if_empty`) that has previously been defined as a macro, e.g.: ```cpp #define limit something_else const char foo[] = { #embed <file.txt> limit(42) }; ``` Such code now generates the following diagnostic: ``` foo.cpp:3:19: error: cannot use 'limit' as an '#embed' parameter if also defined as a macro 3 | #embed <file.txt> limit(42) | ^ foo.cpp:1:9: note: macro 'limit' defined here 1 | #define limit something_else | ^ ``` This adheres to [CWG3013](https://wg21.link/cwg3013), which stipulates such code is now "ill-formed" in C++. However, (AFAIK) no such rules exist in C. Thus, the previous behavior is still in-place for C, e.g. macros named as e.g. `limit` or `prefix` are still expanded in-place, i.e. in our previous code: ```c #embed <file.txt> limit(42) ``` In C, this directive becomes the following after macro expansion: ```c #embed <file.txt> something_else(42) ``` >From fd0ec1d6dd50bdd02a1e32ba49c7146dcf8bad73 Mon Sep 17 00:00:00 2001 From: "Li, Ian" <[email protected]> Date: Fri, 18 Sep 2026 15:34:24 -0700 Subject: [PATCH] Implement CWG3013 in clang, but only for C++ --- clang/docs/ReleaseNotes.md | 18 ++++++ clang/include/clang/Basic/DiagnosticGroups.td | 4 +- .../include/clang/Basic/DiagnosticLexKinds.td | 8 +++ clang/include/clang/Lex/Preprocessor.h | 4 ++ clang/lib/Lex/PPDirectives.cpp | 4 ++ clang/lib/Lex/Preprocessor.cpp | 24 ++++++++ clang/test/CXX/drs/cwg3013.cpp | 58 +++++++++++++++++++ clang/test/CXX/drs/inputs/media/art.txt | 13 +++++ clang/test/CXX/drs/inputs/media/empty | 0 9 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 clang/test/CXX/drs/cwg3013.cpp create mode 100644 clang/test/CXX/drs/inputs/media/art.txt create mode 100644 clang/test/CXX/drs/inputs/media/empty diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index f4a34a37aff52..da925b260ee1c 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -54,6 +54,13 @@ in a future version of Clang. mode, as it was removed from the standard by [P3475R2](https://wg21.link/P3475R2). +- An error diagnostic is now issued if an `#embed` directive or a `__has_embed` + expression contains a parameter (i.e. `limit`, `prefix`, `suffix`, `if_empty`) + that has previously been defined as a macro, as per + [CWG3013](https://wg21.link/cwg3013). Previously macros that shared names with + `#embed` parameter names were expanded regardless, similar to its behavior in + C: Note that this expansion behavior is still present in C since there are no + rule analogue to CWG3013 in C. ### Objective-C Specific Potentially Breaking Changes @@ -194,6 +201,11 @@ features cannot lower the translation-unit ABI level; them to an enumeration type with a fixed `bool` underlying type. This resolves [CWG1094](https://wg21.link/cwg1094). +- Clang now diagnoses an error if an `#embed` directive or `__has_embed` + statement uses a parameter name (i.e. `limit`, `prefix`, `suffix`, `if_empty`) + that has previously been defined as a macro. This resolves + [CWG3013](https://wg21.link/cwg3013), which marks such code as ill-formed. + ### C Language Changes #### C2y Feature Support @@ -518,6 +530,12 @@ features cannot lower the translation-unit ABI level; - Improve Clang diagnoses when unary `__imag` operator with non-complex type operand is used as lvalue. (GH222383) +- Added `-Wembed-parameter-is-macro`, which warns in C if an `#embed` directive + or a `__has_embed` expression uses a parameter (i.e. `limit`, `prefix`, + `suffix`, `if_empty`) that has also been defined as a macro. C expands the + macro, but the same code is ill-formed in C++, so `-Wembed-parameter-is-macro` + is also part of `-Wc++-compat`; this warning is disabled by default otherwise. + ### Improvements to Clang's time-trace ### Improvements to Coverage Mapping diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 1da7698944b24..b3c36c76d490b 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -187,6 +187,7 @@ def C23Compat : DiagGroup<"c23-compat">; def : DiagGroup<"c2x-compat", [C23Compat]>; def CppKeywordInC : DiagGroup<"c++-keyword">; +def EmbedParameterIsMacro : DiagGroup<"embed-parameter-is-macro">; def DuplicateDeclSpecifier : DiagGroup<"duplicate-decl-specifier">; def InitStringTooLongMissingNonString : DiagGroup<"unterminated-string-initialization">; @@ -212,7 +213,8 @@ def CXXCompat: DiagGroup<"c++-compat", [ImplicitVoidPtrCast, DefaultConstInit, ImplicitIntToEnumCast, HiddenCppDecl, InitStringTooLongForCpp, CppKeywordInC, TentativeDefnCompat, JumpBypassesInit, - DuplicateDeclSpecifier]>; + DuplicateDeclSpecifier, + EmbedParameterIsMacro]>; def ExternCCompat : DiagGroup<"extern-c-compat">; def KeywordCompat : DiagGroup<"keyword-compat">; diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td index ff51485a1810b..006da9a53e96a 100644 --- a/clang/include/clang/Basic/DiagnosticLexKinds.td +++ b/clang/include/clang/Basic/DiagnosticLexKinds.td @@ -492,6 +492,14 @@ def warn_compat_pp_embed_directive : Warning< InGroup<CPre23Compat>, DefaultIgnore; def err_pp_embed_dup_params : Error< "cannot specify parameter '%0' twice in the same '#embed' directive">; +def err_pp_embed_parameter_is_macro : Error< + "cannot use %0 as %select{an '#embed'|a '__has_embed'}1 parameter if also" + " defined as a macro">; +def warn_c_pp_embed_parameter_is_macro : Warning< + "%0 is defined as a macro and gets expanded when used as a " + "%select{an '#embed'|a '__has_embed'}1 parameter in C; this is " + "ill-formed in C++">, + InGroup<EmbedParameterIsMacro>, DefaultIgnore; def err_pp_embed_device_file : Error< "device files are not yet supported by '#embed' directive">; diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h index 31b68a0fd0670..4711728b1b764 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -302,6 +302,10 @@ class Preprocessor { /// True if we are currently preprocessing a #if or #elif directive bool ParsingIfOrElifDirective; + /// True if we are preprocessing the parameters of an #embed directive or a + /// __has_embed expression. + bool ParsingEmbedParameters; + /// True if we are pre-expanding macro arguments. bool InMacroArgPreExpansion; diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp index f1e9aaa72ff04..0b7c727d2ce89 100644 --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -3771,6 +3771,10 @@ void Preprocessor::HandleElifFamilyDirective(Token &ElifToken, std::optional<LexEmbedParametersResult> Preprocessor::LexEmbedParameters(Token &CurTok, bool ForHasEmbed) { LexEmbedParametersResult Result{}; + if (ForHasEmbed) + assert(isParsingIfOrElifDirective() && + "__has_embed outside of #if or #elif directive?"); + llvm::SaveAndRestore InEmbedParams(ParsingEmbedParameters, true); tok::TokenKind EndTokenKind = ForHasEmbed ? tok::r_paren : tok::eod; auto DiagMismatchedBracesAndSkipToEOD = diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp index 84907adc5d744..9bab63818007c 100644 --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -119,6 +119,7 @@ Preprocessor::Preprocessor(const PreprocessorOptions &PPOpts, NumCachedTokenLexers = 0; PragmasEnabled = true; ParsingIfOrElifDirective = false; + ParsingEmbedParameters = false; PreprocessedOutput = false; // We haven't read anything from the external source. @@ -879,6 +880,11 @@ void Preprocessor::updateOutOfDateIdentifier(const IdentifierInfo &II) const { getExternalSource()->updateOutOfDateIdentifier(II); } +static bool isProtectedEmbedParameterName(const IdentifierInfo *II) { + return II->isStr("limit") || II->isStr("prefix") || II->isStr("suffix") || + II->isStr("if_empty"); +} + /// HandleIdentifier - This callback is invoked when the lexer reads an /// identifier. This callback looks up the identifier in the map and/or /// potentially macro expands it or turns it into a named token (like 'for'). @@ -922,6 +928,24 @@ bool Preprocessor::HandleIdentifier(Token &Identifier) { if (const MacroDefinition MD = getMacroDefinition(&II)) { const auto *MI = MD.getMacroInfo(); assert(MI && "macro definition with no macro info?"); + // C++ [cpp.pre]/p4, [cpp.cond]/p9: if one of the pp-tokens of a #embed + // directive (or a has-embed-expression) is the identifier limit, prefix, + // suffix, or if_empty and that identifier is defined as a macro, the + // program is ill-formed. + // + // Thus, do not continue processing if compiling for C++. C doesn't have + // this restriction however, so only issue a warning for C if -Wc++-compat + // is enabled. + if (ParsingEmbedParameters && isProtectedEmbedParameterName(&II)) { + Diag(Identifier, getLangOpts().CPlusPlus + ? diag::err_pp_embed_parameter_is_macro + : diag::warn_c_pp_embed_parameter_is_macro) + << &II << isParsingIfOrElifDirective(); + Diag(MI->getDefinitionLoc(), diag::note_macro_here) << &II; + if (getLangOpts().CPlusPlus) + return true; + } + if (!DisableMacroExpansion) { if (!Identifier.isExpandDisabled() && MI->isEnabled()) { // C99 6.10.3p10: If the preprocessing token immediately after the diff --git a/clang/test/CXX/drs/cwg3013.cpp b/clang/test/CXX/drs/cwg3013.cpp new file mode 100644 index 0000000000000..95f4f6fc6f9e2 --- /dev/null +++ b/clang/test/CXX/drs/cwg3013.cpp @@ -0,0 +1,58 @@ +// RUN: %clang_cc1 --embed-dir=%S/inputs -Wno-c23-extensions -fsyntax-only -verify=cxx,common -x c++ %s +// RUN: %clang_cc1 --embed-dir=%S/inputs -fsyntax-only -verify=c -std=c23 -x c %s +// RUN: %clang_cc1 --embed-dir=%S/inputs -fsyntax-only -verify=c-compat,common -std=c23 -Wc++-compat -x c %s +// +// Test -Wembed-parameter-is-macro: +// RUN: %clang_cc1 --embed-dir=%S/inputs -fsyntax-only -verify=c-compat,common -std=c23 -Wembed-parameter-is-macro -x c %s +// RUN: %clang_cc1 --embed-dir=%S/inputs -fsyntax-only -verify=c -std=c23 -Wc++-compat -Wno-embed-parameter-is-macro -x c %s + +// CWG3013: if one of the pp-tokens of a #embed directive (or a +// has-embed-expression) is the identifier limit, prefix, suffix, or if_empty +// and that identifier is defined as a macro, the program is ill-formed. +// (C++ [cpp.pre]/p4, [cpp.cond]/p9) +// +// However, C doesn't have this restriction, so we should only issue a warning +// for C if -Wc++-compat/-Wembed-parameter-is-macro is enabled. + +// c-no-diagnostics + +#define limit limit +// common-note@-1 2 {{macro 'limit' defined here}} +const int a[] = { +#embed <media/art.txt> limit(2) +// cxx-error@-1 {{cannot use 'limit' as an '#embed' parameter if also defined as a macro}} +// c-compat-warning@-2 {{'limit' is defined as a macro and gets expanded when used as an '#embed' parameter in C; this is ill-formed in C++}} +}; + +#define prefix prefix +// common-note@-1 {{macro 'prefix' defined here}} +const int b[] = { +#embed <media/art.txt> prefix(0,) +// cxx-error@-1 {{cannot use 'prefix' as an '#embed' parameter if also defined as a macro}} +// c-compat-warning@-2 {{'prefix' is defined as a macro and gets expanded when used as an '#embed' parameter in C; this is ill-formed in C++}} +}; + +#define suffix suffix +// common-note@-1 2 {{macro 'suffix' defined here}} +const int c[] = { +#embed <media/art.txt> suffix(,0) +// cxx-error@-1 {{cannot use 'suffix' as an '#embed' parameter if also defined as a macro}} +// c-compat-warning@-2 {{'suffix' is defined as a macro and gets expanded when used as an '#embed' parameter in C; this is ill-formed in C++}} +}; + +#define if_empty if_empty +// common-note@-1 {{macro 'if_empty' defined here}} +const int d[] = { +#embed <media/empty> if_empty(0) +// cxx-error@-1 {{cannot use 'if_empty' as an '#embed' parameter if also defined as a macro}} +// c-compat-warning@-2 {{'if_empty' is defined as a macro and gets expanded when used as an '#embed' parameter in C; this is ill-formed in C++}} +}; + +// The prohibition also covers the __has_embed argument. +#if __has_embed(<media/art.txt> limit(1) suffix(0)) +// cxx-error@-1 {{cannot use 'limit' as a '__has_embed' parameter if also defined as a macro}} +// cxx-error@-2 {{cannot use 'suffix' as a '__has_embed' parameter if also defined as a macro}} +// c-compat-warning@-3 {{'limit' is defined as a macro and gets expanded when used as a '__has_embed' parameter in C; this is ill-formed in C++}} +// c-compat-warning@-4 {{'suffix' is defined as a macro and gets expanded when used as a '__has_embed' parameter in C; this is ill-formed in C++}} +int e; +#endif diff --git a/clang/test/CXX/drs/inputs/media/art.txt b/clang/test/CXX/drs/inputs/media/art.txt new file mode 100644 index 0000000000000..f4536f39af349 --- /dev/null +++ b/clang/test/CXX/drs/inputs/media/art.txt @@ -0,0 +1,13 @@ + + ------------------------------------------- + . . + . _ . + . _ _ >(. ) _ . + . >(. )__ >(- )__ //___ >(. )__ . + . ~(____/ -~(_(=-/-~~(_(__/-~~(____/~ . + . ~. -~~~ . -~. ~~- . + . ~- . + . . + ------------------------------------------- + + O Pato \ No newline at end of file diff --git a/clang/test/CXX/drs/inputs/media/empty b/clang/test/CXX/drs/inputs/media/empty new file mode 100644 index 0000000000000..e69de29bb2d1d _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
