[clang] [Clang][Lex] Implement CWG3013: Error if #embed parameters were defined as macros (PR #224769)
Ian Li via cfe-commits
cfe-commits at lists.llvm.org
Fri Sep 18 15:51:34 PDT 2026
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" <ian.li at intel.com>
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 at -1 2 {{macro 'limit' defined here}}
+const int a[] = {
+#embed <media/art.txt> limit(2)
+// cxx-error at -1 {{cannot use 'limit' as an '#embed' parameter if also defined as a macro}}
+// c-compat-warning at -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 at -1 {{macro 'prefix' defined here}}
+const int b[] = {
+#embed <media/art.txt> prefix(0,)
+// cxx-error at -1 {{cannot use 'prefix' as an '#embed' parameter if also defined as a macro}}
+// c-compat-warning at -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 at -1 2 {{macro 'suffix' defined here}}
+const int c[] = {
+#embed <media/art.txt> suffix(,0)
+// cxx-error at -1 {{cannot use 'suffix' as an '#embed' parameter if also defined as a macro}}
+// c-compat-warning at -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 at -1 {{macro 'if_empty' defined here}}
+const int d[] = {
+#embed <media/empty> if_empty(0)
+// cxx-error at -1 {{cannot use 'if_empty' as an '#embed' parameter if also defined as a macro}}
+// c-compat-warning at -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 at -1 {{cannot use 'limit' as a '__has_embed' parameter if also defined as a macro}}
+// cxx-error at -2 {{cannot use 'suffix' as a '__has_embed' parameter if also defined as a macro}}
+// c-compat-warning at -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 at -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
More information about the cfe-commits
mailing list