[clang] [Clang] Support libstdc++ workarounds when using `-E` (PR #210802)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 23 13:00:43 PDT 2026
https://github.com/Sirraide updated https://github.com/llvm/llvm-project/pull/210802
>From e8d94c9d5cf208d94823f6da44685ab3825ed647 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Mon, 20 Jul 2026 21:57:59 +0200
Subject: [PATCH 1/5] [Clang] Support libstdc++ hack in preprocessed input
---
clang/docs/ReleaseNotes.md | 4 ++++
clang/lib/Sema/SemaTemplate.cpp | 14 ++++----------
clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp | 3 +++
3 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 5a07e77076d17..87b447165e970 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -323,6 +323,10 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the
- Fixed a crash when a using-declaration naming an unresolvable member of a
dependent base was shadowed by an invalid using-declaration. (#GH209427)
+- A workaround that was introduced to fix an issue with the `<format>` header present in some versions of
+ libstdc++15 has been extended to handle the case of the input being preprocessed first (via `-E`) and
+ only then compiled; previously, this would result in the fix not being applied. (#GH160314)
+
#### Bug Fixes to AST Handling
- Fixed a non-deterministic ordering of unused local typedefs that made
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 643392833759d..7268a9aa5b4ef 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -4586,16 +4586,10 @@ static bool IsLibstdcxxStdFormatKind(Preprocessor &PP, VarDecl *Var) {
!Var->getDeclContext()->isStdNamespace())
return false;
- // Checking old versions of libstdc++ is not needed because 15.1 is the first
- // release in which users can access std::format_kind.
- // We can use 20250520 as the final date, see the following commits.
- // GCC releases/gcc-15 branch:
- // https://gcc.gnu.org/g:fedf81ef7b98e5c9ac899b8641bb670746c51205
- // https://gcc.gnu.org/g:53680c1aa92d9f78e8255fbf696c0ed36f160650
- // GCC master branch:
- // https://gcc.gnu.org/g:9361966d80f625c5accc25cbb439f0278dd8b278
- // https://gcc.gnu.org/g:c65725eccbabf3b9b5965f27fff2d3b9f6c75930
- return PP.NeedsStdLibCxxWorkaroundBefore(2025'05'20);
+ // We don't check the libstdc++ version for this one since that doesn't work
+ // if a user preprocesses the input as a separate step (because __GLIBCXX__
+ // will not be defined).
+ return true;
}
} // end anonymous namespace
diff --git a/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp b/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
index 35611c870b8d1..698b6ccf697e6 100644
--- a/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
+++ b/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
@@ -1,4 +1,7 @@
// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %s
+// RUN: %clang_cc1 -E -std=c++23 %s -o %t.ii
+// RUN: echo '// expected-no-diagnostics' >> %t.ii
+// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %t.ii
// expected-no-diagnostics
>From 2758463b208e51a04576227d45f406bc5db742bb Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Mon, 20 Jul 2026 21:59:31 +0200
Subject: [PATCH 2/5] amend release note
---
clang/docs/ReleaseNotes.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 87b447165e970..045aac959140d 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -324,8 +324,8 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the
dependent base was shadowed by an invalid using-declaration. (#GH209427)
- A workaround that was introduced to fix an issue with the `<format>` header present in some versions of
- libstdc++15 has been extended to handle the case of the input being preprocessed first (via `-E`) and
- only then compiled; previously, this would result in the fix not being applied. (#GH160314)
+ libstdc++15 has been extended to support preprocessed input. Previously, splitting the preprocessing and
+ compilation step would result in the fix not being applied. (#GH160314)
#### Bug Fixes to AST Handling
>From 6bdc1c9d794d1d2f5fec3ef7cbf77d6c32309f03 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Wed, 22 Jul 2026 21:01:42 +0200
Subject: [PATCH 3/5] use a pragma instead
---
.../include/clang/Basic/DiagnosticLexKinds.td | 3 ++
clang/include/clang/Lex/PPCallbacks.h | 8 ++++
clang/include/clang/Lex/Preprocessor.h | 1 +
.../lib/Frontend/PrintPreprocessedOutput.cpp | 47 ++++++++++++++-----
clang/lib/Lex/PPExpressions.cpp | 7 +++
clang/lib/Lex/Pragma.cpp | 25 ++++++++++
clang/lib/Sema/SemaTemplate.cpp | 14 ++++--
.../pragma_clang_glibcxx_version_invalid.cpp | 5 ++
.../SemaCXX/libstdcxx_format_kind_hack.cpp | 22 ++++++++-
9 files changed, 116 insertions(+), 16 deletions(-)
create mode 100644 clang/test/Preprocessor/pragma_clang_glibcxx_version_invalid.cpp
diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td
index 79e74a846e3ea..fd4e843601e3d 100644
--- a/clang/include/clang/Basic/DiagnosticLexKinds.td
+++ b/clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -864,6 +864,9 @@ def err_pp_include_in_arc_cf_code_audited : Error<
def err_pp_eof_in_arc_cf_code_audited : Error<
"'#pragma clang arc_cf_code_audited' was not ended within this file">;
+def err_pp_pragma_glibcxx_version_requires_integer : Error<
+ "expected integer after '#pragma clang glibcxx_version'">;
+
def warn_pp_date_time : Warning<
"expansion of date or time macro is not reproducible">,
ShowInSystemHeader, DefaultIgnore, InGroup<DiagGroup<"date-time">>;
diff --git a/clang/include/clang/Lex/PPCallbacks.h b/clang/include/clang/Lex/PPCallbacks.h
index 2f62fa8d52b1a..706f08f7c0735 100644
--- a/clang/include/clang/Lex/PPCallbacks.h
+++ b/clang/include/clang/Lex/PPCallbacks.h
@@ -342,6 +342,9 @@ class PPCallbacks {
/// is read.
virtual void PragmaAssumeNonNullEnd(SourceLocation Loc) {}
+ /// Callback invoked when a \#pragma clang glibcxx_version directive is read.
+ virtual void PragmaGLIBCXXVersion(SourceLocation Loc, std::uint64_t Value) {}
+
/// Called by Preprocessor::HandleMacroExpandedIdentifier when a
/// macro invocation is found.
virtual void MacroExpands(const Token &MacroNameTok,
@@ -700,6 +703,11 @@ class PPChainedCallbacks : public PPCallbacks {
Second->PragmaAssumeNonNullEnd(Loc);
}
+ void PragmaGLIBCXXVersion(SourceLocation Loc, std::uint64_t Value) override {
+ First->PragmaGLIBCXXVersion(Loc, Value);
+ Second->PragmaGLIBCXXVersion(Loc, Value);
+ }
+
void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD,
SourceRange Range, const MacroArgs *Args) override {
First->MacroExpands(MacroNameTok, MD, Range, Args);
diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h
index 1c917dcfe7b7e..5038bc87b210c 100644
--- a/clang/include/clang/Lex/Preprocessor.h
+++ b/clang/include/clang/Lex/Preprocessor.h
@@ -2818,6 +2818,7 @@ class Preprocessor {
public:
std::optional<std::uint64_t> getStdLibCxxVersion();
+ void setStdLibCxxVersion(std::uint64_t Version);
bool NeedsStdLibCxxWorkaroundBefore(std::uint64_t FixedVersion);
private:
diff --git a/clang/lib/Frontend/PrintPreprocessedOutput.cpp b/clang/lib/Frontend/PrintPreprocessedOutput.cpp
index 02266882c4c4a..87b6ded660507 100644
--- a/clang/lib/Frontend/PrintPreprocessedOutput.cpp
+++ b/clang/lib/Frontend/PrintPreprocessedOutput.cpp
@@ -29,10 +29,12 @@
using namespace clang;
/// PrintMacroDefinition - Print a macro definition in a form that will be
-/// properly accepted back as a definition.
-static void PrintMacroDefinition(const IdentifierInfo &II, const MacroInfo &MI,
+/// properly accepted back as a definition. If 'II' is nullptr, only the
+/// expansion will be printed.
+static void PrintMacroDefinition(const IdentifierInfo *II, const MacroInfo &MI,
Preprocessor &PP, raw_ostream *OS) {
- *OS << "#define " << II.getName();
+ if (II)
+ *OS << "#define " << II->getName();
if (MI.isFunctionLike()) {
*OS << '(';
@@ -182,6 +184,7 @@ class PrintPPOutputPPCallbacks : public PPCallbacks {
void PragmaExecCharsetPop(SourceLocation Loc) override;
void PragmaAssumeNonNullBegin(SourceLocation Loc) override;
void PragmaAssumeNonNullEnd(SourceLocation Loc) override;
+ void PragmaGLIBCXXVersion(SourceLocation Loc, std::uint64_t Value) override;
/// Insert whitespace before emitting the next token.
///
@@ -559,22 +562,37 @@ void PrintPPOutputPPCallbacks::Ident(SourceLocation Loc, StringRef S) {
/// MacroDefined - This hook is called whenever a macro definition is seen.
void PrintPPOutputPPCallbacks::MacroDefined(const Token &MacroNameTok,
const MacroDirective *MD) {
+ bool ShouldEmitDefine = true;
const MacroInfo *MI = MD->getMacroInfo();
+ SourceLocation DefLoc = MI->getDefinitionLoc();
+
// Print out macro definitions in -dD mode and when we have -fdirectives-only
// for C++20 header units.
if ((!DumpDefines && !DirectivesOnly) ||
// Ignore __FILE__ etc.
- MI->isBuiltinMacro())
- return;
-
- SourceLocation DefLoc = MI->getDefinitionLoc();
- if (DirectivesOnly && !MI->isUsed()) {
+ MI->isBuiltinMacro()) {
+ ShouldEmitDefine = false;
+ } else if (DirectivesOnly && !MI->isUsed()) {
SourceManager &SM = PP.getSourceManager();
if (SM.isInPredefinedFile(DefLoc))
- return;
+ ShouldEmitDefine = false;
}
+
+ if (!ShouldEmitDefine) {
+ // Preserve '__GLIBCXX__' as a pragma if we shouldn't print '#define's; this
+ // is required for a number of workarounds in Sema (which are enabled
+ // depending on the value of this macro).
+ if (MacroNameTok.getIdentifierInfo()->getName() == "__GLIBCXX__") {
+ MoveToLine(DefLoc, /*RequireStartOfLine=*/true);
+ *OS << "#pragma clang glibcxx_version";
+ PrintMacroDefinition(/*II=*/nullptr, *MI, PP, OS);
+ setEmittedDirectiveOnThisLine();
+ }
+ return;
+ }
+
MoveToLine(DefLoc, /*RequireStartOfLine=*/true);
- PrintMacroDefinition(*MacroNameTok.getIdentifierInfo(), *MI, PP, OS);
+ PrintMacroDefinition(MacroNameTok.getIdentifierInfo(), *MI, PP, OS);
setEmittedDirectiveOnThisLine();
}
@@ -752,6 +770,13 @@ PragmaAssumeNonNullEnd(SourceLocation Loc) {
setEmittedDirectiveOnThisLine();
}
+void PrintPPOutputPPCallbacks::PragmaGLIBCXXVersion(SourceLocation Loc,
+ std::uint64_t Value) {
+ MoveToLine(Loc, /*RequireStartOfLine=*/true);
+ *OS << "#pragma clang glibcxx_version " << Value;
+ setEmittedDirectiveOnThisLine();
+}
+
void PrintPPOutputPPCallbacks::HandleWhitespaceBeforeTok(const Token &Tok,
bool RequireSpace,
bool RequireSameLine) {
@@ -1093,7 +1118,7 @@ static void DoPrintMacros(Preprocessor &PP, raw_ostream *OS) {
// Ignore computed macros like __LINE__ and friends.
if (MI.isBuiltinMacro()) continue;
- PrintMacroDefinition(*MacrosByID[i].first, MI, PP, OS);
+ PrintMacroDefinition(MacrosByID[i].first, MI, PP, OS);
*OS << '\n';
}
}
diff --git a/clang/lib/Lex/PPExpressions.cpp b/clang/lib/Lex/PPExpressions.cpp
index 887fd25ac318d..50e7040291bc4 100644
--- a/clang/lib/Lex/PPExpressions.cpp
+++ b/clang/lib/Lex/PPExpressions.cpp
@@ -1018,6 +1018,13 @@ std::optional<uint64_t> Preprocessor::getStdLibCxxVersion() {
return std::nullopt;
}
+void Preprocessor::setStdLibCxxVersion(std::uint64_t Version) {
+ CXXStandardLibraryVersion = {
+ CXXStandardLibraryVersionInfo::LibStdCXX,
+ Version,
+ };
+}
+
bool Preprocessor::NeedsStdLibCxxWorkaroundBefore(uint64_t FixedVersion) {
assert(FixedVersion >= 2000'00'00 && FixedVersion <= 2100'00'00 &&
"invalid value for __GLIBCXX__");
diff --git a/clang/lib/Lex/Pragma.cpp b/clang/lib/Lex/Pragma.cpp
index 9b48a45ade668..6de348f9d63e5 100644
--- a/clang/lib/Lex/Pragma.cpp
+++ b/clang/lib/Lex/Pragma.cpp
@@ -2148,6 +2148,30 @@ struct PragmaFinalHandler : public PragmaHandler {
}
};
+/// "\#pragma clang glibcxx_version ..."
+///
+/// The syntax is
+/// \code
+/// #pragma clang glibcxx_version INTEGER
+/// \endcode
+struct PragmaGLIBCXXVersionHandler : PragmaHandler {
+ PragmaGLIBCXXVersionHandler() : PragmaHandler("glibcxx_version") {}
+ void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer,
+ Token &Tok) override {
+ PP.Lex(Tok);
+ std::uint64_t Value;
+ if (Tok.is(tok::numeric_constant) &&
+ PP.parseSimpleIntegerLiteral(Tok, Value)) {
+ PP.setStdLibCxxVersion(Value);
+
+ if (PP.getPPCallbacks())
+ PP.getPPCallbacks()->PragmaGLIBCXXVersion(Introducer.Loc, Value);
+ } else {
+ PP.Diag(Tok.getLocation(),
+ diag::err_pp_pragma_glibcxx_version_requires_integer);
+ }
+ }
+};
} // namespace
/// RegisterBuiltinPragmas - Install the standard preprocessor pragmas:
@@ -2179,6 +2203,7 @@ void Preprocessor::RegisterBuiltinPragmas() {
AddPragmaHandler("clang", new PragmaDeprecatedHandler());
AddPragmaHandler("clang", new PragmaRestrictExpansionHandler());
AddPragmaHandler("clang", new PragmaFinalHandler());
+ AddPragmaHandler("clang", new PragmaGLIBCXXVersionHandler());
// #pragma clang module ...
auto *ModuleHandler = new PragmaNamespace("module");
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 7268a9aa5b4ef..643392833759d 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -4586,10 +4586,16 @@ static bool IsLibstdcxxStdFormatKind(Preprocessor &PP, VarDecl *Var) {
!Var->getDeclContext()->isStdNamespace())
return false;
- // We don't check the libstdc++ version for this one since that doesn't work
- // if a user preprocesses the input as a separate step (because __GLIBCXX__
- // will not be defined).
- return true;
+ // Checking old versions of libstdc++ is not needed because 15.1 is the first
+ // release in which users can access std::format_kind.
+ // We can use 20250520 as the final date, see the following commits.
+ // GCC releases/gcc-15 branch:
+ // https://gcc.gnu.org/g:fedf81ef7b98e5c9ac899b8641bb670746c51205
+ // https://gcc.gnu.org/g:53680c1aa92d9f78e8255fbf696c0ed36f160650
+ // GCC master branch:
+ // https://gcc.gnu.org/g:9361966d80f625c5accc25cbb439f0278dd8b278
+ // https://gcc.gnu.org/g:c65725eccbabf3b9b5965f27fff2d3b9f6c75930
+ return PP.NeedsStdLibCxxWorkaroundBefore(2025'05'20);
}
} // end anonymous namespace
diff --git a/clang/test/Preprocessor/pragma_clang_glibcxx_version_invalid.cpp b/clang/test/Preprocessor/pragma_clang_glibcxx_version_invalid.cpp
new file mode 100644
index 0000000000000..fc613bc827497
--- /dev/null
+++ b/clang/test/Preprocessor/pragma_clang_glibcxx_version_invalid.cpp
@@ -0,0 +1,5 @@
+// RUN: %clang_cc1 -E -verify %s
+
+#pragma clang glibcxx_version // expected-error {{expected integer after '#pragma clang glibcxx_version'}}
+#pragma clang glibcxx_version foo // expected-error {{expected integer after '#pragma clang glibcxx_version'}}
+#pragma clang glibcxx_version 100000000000000000000000000000 // expected-error {{expected integer after '#pragma clang glibcxx_version'}}
diff --git a/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp b/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
index 698b6ccf697e6..bff53344ef21b 100644
--- a/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
+++ b/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
@@ -1,6 +1,17 @@
+// '__GLIBCXX__' defined:
+//
+// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %s -DDEFINE
+// RUN: %clang_cc1 -E -std=c++23 %s -o %t.ii -DDEFINE
+// RUN: echo '// expected-no-diagnostics' >> %t.ii
+// RUN: FileCheck --input-file=%t.ii %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %t.ii
+
+// Version set via pragma:
+//
// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %s
// RUN: %clang_cc1 -E -std=c++23 %s -o %t.ii
// RUN: echo '// expected-no-diagnostics' >> %t.ii
+// RUN: FileCheck --input-file=%t.ii %s
// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %t.ii
// expected-no-diagnostics
@@ -8,8 +19,17 @@
// Primary variable template std::format_kind is defined as followed since
// libstdc++ 15.1, which triggers compilation error introduced by GH134522.
// This file tests the workaround.
+//
+// Since the workaround relies on '__GLIBCXX__' being defined, we emit a pragma
+// that ensures '__GLIBCXX__' is defined if the user first preprocesses the file
+// with '-E' before passing the output of that back to Clang.
-#define __GLIBCXX__ 20250513
+// CHECK: #pragma clang glibcxx_version 20250513
+#ifdef DEFINE
+# define __GLIBCXX__ 20250513
+#else
+# pragma clang glibcxx_version 20250513
+#endif
namespace std {
template<typename _Rg>
>From 185353f6e466b811c7f4df8d631aa56966e2c52a Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Thu, 23 Jul 2026 22:00:21 +0200
Subject: [PATCH 4/5] Generalise the pragma
---
.../include/clang/Basic/DiagnosticLexKinds.td | 8 ++-
clang/include/clang/Lex/PPCallbacks.h | 12 ++--
clang/include/clang/Lex/Preprocessor.h | 9 +++
.../lib/Frontend/PrintPreprocessedOutput.cpp | 24 ++++---
clang/lib/Lex/PPExpressions.cpp | 8 +--
clang/lib/Lex/Pragma.cpp | 67 ++++++++++++++-----
clang/lib/Lex/Preprocessor.cpp | 3 +
.../pragma_clang_glibcxx_version_invalid.cpp | 5 --
.../test/Preprocessor/pragma_set_pp_state.cpp | 27 ++++++++
.../SemaCXX/libstdcxx_format_kind_hack.cpp | 39 +++++++----
10 files changed, 147 insertions(+), 55 deletions(-)
delete mode 100644 clang/test/Preprocessor/pragma_clang_glibcxx_version_invalid.cpp
create mode 100644 clang/test/Preprocessor/pragma_set_pp_state.cpp
diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td
index fd4e843601e3d..f6b98e8caeaff 100644
--- a/clang/include/clang/Basic/DiagnosticLexKinds.td
+++ b/clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -864,8 +864,12 @@ def err_pp_include_in_arc_cf_code_audited : Error<
def err_pp_eof_in_arc_cf_code_audited : Error<
"'#pragma clang arc_cf_code_audited' was not ended within this file">;
-def err_pp_pragma_glibcxx_version_requires_integer : Error<
- "expected integer after '#pragma clang glibcxx_version'">;
+def err_pp_pragma_set_pp_state_expected_name : Error<
+ "expected identifier after '#pragma clang __set_pp_state'">;
+def err_pp_pragma_set_pp_state_expected_int_after : Error<
+ "expected integer after '#pragma clang __set_pp_state %0'">;
+def err_pp_pragma_set_pp_state_invalid_arg : Error<
+ "invalid argument %0 in '#pragma clang __set_pp_state'">;
def warn_pp_date_time : Warning<
"expansion of date or time macro is not reproducible">,
diff --git a/clang/include/clang/Lex/PPCallbacks.h b/clang/include/clang/Lex/PPCallbacks.h
index 706f08f7c0735..5f117bab6848e 100644
--- a/clang/include/clang/Lex/PPCallbacks.h
+++ b/clang/include/clang/Lex/PPCallbacks.h
@@ -342,8 +342,9 @@ class PPCallbacks {
/// is read.
virtual void PragmaAssumeNonNullEnd(SourceLocation Loc) {}
- /// Callback invoked when a \#pragma clang glibcxx_version directive is read.
- virtual void PragmaGLIBCXXVersion(SourceLocation Loc, std::uint64_t Value) {}
+ /// Callback invoked when a \#pragma clang __set_pp_state directive is read.
+ virtual void PragmaSetPPState(SourceLocation Loc, IdentifierInfo *MacroName,
+ std::uint64_t Value) {}
/// Called by Preprocessor::HandleMacroExpandedIdentifier when a
/// macro invocation is found.
@@ -703,9 +704,10 @@ class PPChainedCallbacks : public PPCallbacks {
Second->PragmaAssumeNonNullEnd(Loc);
}
- void PragmaGLIBCXXVersion(SourceLocation Loc, std::uint64_t Value) override {
- First->PragmaGLIBCXXVersion(Loc, Value);
- Second->PragmaGLIBCXXVersion(Loc, Value);
+ void PragmaSetPPState(SourceLocation Loc, IdentifierInfo *MacroName,
+ std::uint64_t Value) override {
+ First->PragmaSetPPState(Loc, MacroName, Value);
+ Second->PragmaSetPPState(Loc, MacroName, Value);
}
void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD,
diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h
index 5038bc87b210c..ec30c5b50ca59 100644
--- a/clang/include/clang/Lex/Preprocessor.h
+++ b/clang/include/clang/Lex/Preprocessor.h
@@ -2979,6 +2979,10 @@ class Preprocessor {
// Pragmas.
void HandlePragmaDirective(PragmaIntroducer Introducer);
+ // Cached identifiers used to implement __set_pp_state.
+ IdentifierInfo *Ident__set_pp_state;
+ IdentifierInfo *Ident__GLIBCXX__;
+
public:
void HandlePragmaOnce(Token &OnceTok);
void HandlePragmaMark(Token &MarkTok);
@@ -2990,8 +2994,13 @@ class Preprocessor {
void HandlePragmaIncludeAlias(Token &Tok);
void HandlePragmaModuleBuild(Token &Tok);
void HandlePragmaHdrstop(Token &Tok);
+ void HandlePragmaSetPPState(PragmaIntroducer Introducer, Token& Tok);
IdentifierInfo *ParsePragmaPushOrPopMacro(Token &Tok);
+ /// Check whether this is a macro name that can be used as an argument to
+ /// '#pragma clang __set_pp_state'.
+ bool isPragmaSetPPStateMacro(IdentifierInfo *II);
+
// Return true and store the first token only if any CommentHandler
// has inserted some tokens and getCommentRetentionState() is false.
bool HandleComment(Token &result, SourceRange Comment);
diff --git a/clang/lib/Frontend/PrintPreprocessedOutput.cpp b/clang/lib/Frontend/PrintPreprocessedOutput.cpp
index 87b6ded660507..b2452aa45cd45 100644
--- a/clang/lib/Frontend/PrintPreprocessedOutput.cpp
+++ b/clang/lib/Frontend/PrintPreprocessedOutput.cpp
@@ -184,7 +184,8 @@ class PrintPPOutputPPCallbacks : public PPCallbacks {
void PragmaExecCharsetPop(SourceLocation Loc) override;
void PragmaAssumeNonNullBegin(SourceLocation Loc) override;
void PragmaAssumeNonNullEnd(SourceLocation Loc) override;
- void PragmaGLIBCXXVersion(SourceLocation Loc, std::uint64_t Value) override;
+ void PragmaSetPPState(SourceLocation Loc, IdentifierInfo *MacroName,
+ std::uint64_t Value) override;
/// Insert whitespace before emitting the next token.
///
@@ -578,13 +579,14 @@ void PrintPPOutputPPCallbacks::MacroDefined(const Token &MacroNameTok,
ShouldEmitDefine = false;
}
+ IdentifierInfo *MacroName = MacroNameTok.getIdentifierInfo();
if (!ShouldEmitDefine) {
- // Preserve '__GLIBCXX__' as a pragma if we shouldn't print '#define's; this
- // is required for a number of workarounds in Sema (which are enabled
- // depending on the value of this macro).
- if (MacroNameTok.getIdentifierInfo()->getName() == "__GLIBCXX__") {
+ // Preserve macro definitions of macros that can be used with
+ // '#pragma clang __set_pp_state' as pragmas if printing '#define's
+ // is disabled.
+ if (PP.isPragmaSetPPStateMacro(MacroName)) {
MoveToLine(DefLoc, /*RequireStartOfLine=*/true);
- *OS << "#pragma clang glibcxx_version";
+ *OS << "#pragma clang __set_pp_state " << MacroName->getName();
PrintMacroDefinition(/*II=*/nullptr, *MI, PP, OS);
setEmittedDirectiveOnThisLine();
}
@@ -592,7 +594,7 @@ void PrintPPOutputPPCallbacks::MacroDefined(const Token &MacroNameTok,
}
MoveToLine(DefLoc, /*RequireStartOfLine=*/true);
- PrintMacroDefinition(MacroNameTok.getIdentifierInfo(), *MI, PP, OS);
+ PrintMacroDefinition(MacroName, *MI, PP, OS);
setEmittedDirectiveOnThisLine();
}
@@ -770,10 +772,12 @@ PragmaAssumeNonNullEnd(SourceLocation Loc) {
setEmittedDirectiveOnThisLine();
}
-void PrintPPOutputPPCallbacks::PragmaGLIBCXXVersion(SourceLocation Loc,
- std::uint64_t Value) {
+void PrintPPOutputPPCallbacks::PragmaSetPPState(SourceLocation Loc,
+ IdentifierInfo *MacroName,
+ std::uint64_t Value) {
MoveToLine(Loc, /*RequireStartOfLine=*/true);
- *OS << "#pragma clang glibcxx_version " << Value;
+ *OS << "#pragma clang __set_pp_state " << MacroName->getName() << " "
+ << Value;
setEmittedDirectiveOnThisLine();
}
diff --git a/clang/lib/Lex/PPExpressions.cpp b/clang/lib/Lex/PPExpressions.cpp
index 50e7040291bc4..9f15abfdf4f1b 100644
--- a/clang/lib/Lex/PPExpressions.cpp
+++ b/clang/lib/Lex/PPExpressions.cpp
@@ -983,9 +983,9 @@ Preprocessor::EvaluateDirectiveExpression(IdentifierInfo *&IfNDefMacro,
}
static std::optional<CXXStandardLibraryVersionInfo>
-getCXXStandardLibraryVersion(Preprocessor &PP, StringRef MacroName,
+getCXXStandardLibraryVersion(Preprocessor &PP, IdentifierInfo *MacroName,
CXXStandardLibraryVersionInfo::Library Lib) {
- MacroInfo *Macro = PP.getMacroInfo(PP.getIdentifierInfo(MacroName));
+ MacroInfo *Macro = PP.getMacroInfo(MacroName);
if (!Macro || Macro->getNumTokens() != 1 || !Macro->isObjectLike())
return std::nullopt;
@@ -1008,7 +1008,7 @@ getCXXStandardLibraryVersion(Preprocessor &PP, StringRef MacroName,
std::optional<uint64_t> Preprocessor::getStdLibCxxVersion() {
if (!CXXStandardLibraryVersion)
CXXStandardLibraryVersion = getCXXStandardLibraryVersion(
- *this, "__GLIBCXX__", CXXStandardLibraryVersionInfo::LibStdCXX);
+ *this, Ident__GLIBCXX__, CXXStandardLibraryVersionInfo::LibStdCXX);
if (!CXXStandardLibraryVersion)
return std::nullopt;
@@ -1027,7 +1027,7 @@ void Preprocessor::setStdLibCxxVersion(std::uint64_t Version) {
bool Preprocessor::NeedsStdLibCxxWorkaroundBefore(uint64_t FixedVersion) {
assert(FixedVersion >= 2000'00'00 && FixedVersion <= 2100'00'00 &&
- "invalid value for __GLIBCXX__");
+ "invalid libstdc++ version number");
std::optional<std::uint64_t> Ver = getStdLibCxxVersion();
if (!Ver)
return false;
diff --git a/clang/lib/Lex/Pragma.cpp b/clang/lib/Lex/Pragma.cpp
index 6de348f9d63e5..c5bd6abff5766 100644
--- a/clang/lib/Lex/Pragma.cpp
+++ b/clang/lib/Lex/Pragma.cpp
@@ -913,6 +913,48 @@ void Preprocessor::HandlePragmaHdrstop(Token &Tok) {
SkippingUntilPragmaHdrStop = false;
}
+bool Preprocessor::isPragmaSetPPStateMacro(IdentifierInfo *MacroName) {
+ return MacroName == Ident__GLIBCXX__;
+}
+
+void Preprocessor::HandlePragmaSetPPState(PragmaIntroducer Introducer,
+ Token &Tok) {
+ // Lex the macro name we want to set.
+ LexUnexpandedToken(Tok);
+ if (!Tok.getIdentifierInfo()) {
+ Diag(Tok.getLocation(), diag::err_pp_pragma_set_pp_state_expected_name);
+ return;
+ }
+
+ IdentifierInfo *MacroName = Tok.getIdentifierInfo();
+ if (!isPragmaSetPPStateMacro(MacroName)) {
+ Diag(Tok.getLocation(), diag::err_pp_pragma_set_pp_state_invalid_arg)
+ << MacroName;
+ return;
+ }
+
+ // Lex the integer argument.
+ Lex(Tok);
+ std::uint64_t Value;
+ if (!Tok.is(tok::numeric_constant) ||
+ !parseSimpleIntegerLiteral(Tok, Value)) {
+ Diag(Tok.getLocation(), diag::err_pp_pragma_set_pp_state_expected_int_after)
+ // Don't pass an IdentifierInfo* here to avoid quoting.
+ << MacroName->getName();
+ return;
+ }
+
+ // Update the state.
+ if (MacroName == Ident__GLIBCXX__) {
+ setStdLibCxxVersion(Value);
+ } else {
+ llvm_unreachable("forgot to handle a possible argument to __set_pp_state");
+ }
+
+ if (Callbacks)
+ Callbacks->PragmaSetPPState(Introducer.Loc, MacroName, Value);
+}
+
/// AddPragmaHandler - Add the specified pragma handler to the preprocessor.
/// If 'Namespace' is non-null, then it is a token required to exist on the
/// pragma line before the pragma string starts, e.g. "STDC" or "GCC".
@@ -2148,28 +2190,23 @@ struct PragmaFinalHandler : public PragmaHandler {
}
};
-/// "\#pragma clang glibcxx_version ..."
+/// "\#pragma clang __set_pp_state ..."
+///
+/// This pragma takes an identifier+value pair and sets some internal state in
+/// the compiler; it is intended primarily to preserve preprocessor state that
+/// is required for compilation to function properly across preprocessor runs
+/// if '-E' is used. This is an internal pragma that should not be used by
+/// users.
///
/// The syntax is
/// \code
-/// #pragma clang glibcxx_version INTEGER
+/// #pragma clang __set_pp_state glibcxx_version INTEGER
/// \endcode
struct PragmaGLIBCXXVersionHandler : PragmaHandler {
- PragmaGLIBCXXVersionHandler() : PragmaHandler("glibcxx_version") {}
+ PragmaGLIBCXXVersionHandler() : PragmaHandler("__set_pp_state") {}
void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer,
Token &Tok) override {
- PP.Lex(Tok);
- std::uint64_t Value;
- if (Tok.is(tok::numeric_constant) &&
- PP.parseSimpleIntegerLiteral(Tok, Value)) {
- PP.setStdLibCxxVersion(Value);
-
- if (PP.getPPCallbacks())
- PP.getPPCallbacks()->PragmaGLIBCXXVersion(Introducer.Loc, Value);
- } else {
- PP.Diag(Tok.getLocation(),
- diag::err_pp_pragma_glibcxx_version_requires_integer);
- }
+ PP.HandlePragmaSetPPState(Introducer, Tok);
}
};
} // namespace
diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp
index 427a3826b299a..7badfe812706d 100644
--- a/clang/lib/Lex/Preprocessor.cpp
+++ b/clang/lib/Lex/Preprocessor.cpp
@@ -159,6 +159,9 @@ Preprocessor::Preprocessor(const PreprocessorOptions &PPOpts,
Ident_AbnormalTermination = nullptr;
}
+ Ident__set_pp_state = getIdentifierInfo("__set_pp_state");
+ Ident__GLIBCXX__ = getIdentifierInfo("__GLIBCXX__");
+
// Default incremental processing to -fincremental-extensions, clients can
// override with `enableIncrementalProcessing` if desired.
IncrementalProcessing = LangOpts.IncrementalExtensions;
diff --git a/clang/test/Preprocessor/pragma_clang_glibcxx_version_invalid.cpp b/clang/test/Preprocessor/pragma_clang_glibcxx_version_invalid.cpp
deleted file mode 100644
index fc613bc827497..0000000000000
--- a/clang/test/Preprocessor/pragma_clang_glibcxx_version_invalid.cpp
+++ /dev/null
@@ -1,5 +0,0 @@
-// RUN: %clang_cc1 -E -verify %s
-
-#pragma clang glibcxx_version // expected-error {{expected integer after '#pragma clang glibcxx_version'}}
-#pragma clang glibcxx_version foo // expected-error {{expected integer after '#pragma clang glibcxx_version'}}
-#pragma clang glibcxx_version 100000000000000000000000000000 // expected-error {{expected integer after '#pragma clang glibcxx_version'}}
diff --git a/clang/test/Preprocessor/pragma_set_pp_state.cpp b/clang/test/Preprocessor/pragma_set_pp_state.cpp
new file mode 100644
index 0000000000000..763f23ce5868f
--- /dev/null
+++ b/clang/test/Preprocessor/pragma_set_pp_state.cpp
@@ -0,0 +1,27 @@
+// RUN: %clang_cc1 -E -verify %s
+
+#pragma clang __set_pp_state // expected-error {{expected identifier after '#pragma clang __set_pp_state'}}
+#pragma clang __set_pp_state 123984 // expected-error {{expected identifier after '#pragma clang __set_pp_state'}}
+
+#pragma clang __set_pp_state foo // expected-error {{invalid argument 'foo' in '#pragma clang __set_pp_state'}}
+#pragma clang __set_pp_state void // expected-error {{invalid argument 'void' in '#pragma clang __set_pp_state'}}
+
+#pragma clang __set_pp_state __GLIBCXX__ foo // expected-error {{expected integer after '#pragma clang __set_pp_state __GLIBCXX__'}}
+#pragma clang __set_pp_state __GLIBCXX__ 100000000000000000000000000000 // expected-error {{expected integer after '#pragma clang __set_pp_state __GLIBCXX__'}}
+#pragma clang __set_pp_state __GLIBCXX__ 42.0 // expected-error {{expected integer after '#pragma clang __set_pp_state __GLIBCXX__'}}
+
+#pragma clang __set_pp_state __GLIBCXX__ 42L
+#pragma clang __set_pp_state __GLIBCXX__ 42
+
+// Check that we treat the identifier after '__set_pp_state' literally.
+#define MACRO __GLIBCXX__
+#pragma clang __set_pp_state MACRO // expected-error {{invalid argument 'MACRO' in '#pragma clang __set_pp_state'}}
+
+// Check that we treat '__set_pp_state' literally.
+#define __set_pp_state foobar
+#pragma clang __set_pp_state __GLIBCXX__ 42
+
+// The pragma does *not* define __GLIBCXX__!
+#ifdef __GLIBCXX__
+# error __set_pp_state __GLIBCXX__ should not define __GLIBCXX__
+#endif
diff --git a/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp b/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
index bff53344ef21b..b8cde0b6b2f2e 100644
--- a/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
+++ b/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
@@ -1,19 +1,29 @@
-// '__GLIBCXX__' defined:
-//
-// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %s -DDEFINE
-// RUN: %clang_cc1 -E -std=c++23 %s -o %t.ii -DDEFINE
-// RUN: echo '// expected-no-diagnostics' >> %t.ii
+// Check that we accept the program if '__GLIBCXX__' is defined:
+// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %s -DDEFINE_GLIBCXX
+
+// Check that we preserve the value of __GLIBCXX__ via a pragma when preprocessing:
+// RUN: %clang_cc1 -E -std=c++23 %s -o %t.ii -DDEFINE_GLIBCXX
// RUN: FileCheck --input-file=%t.ii %s
-// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %t.ii
-// Version set via pragma:
-//
-// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %s
-// RUN: %clang_cc1 -E -std=c++23 %s -o %t.ii
+// Check that the preprocessed file compiles with no diagnostics:
// RUN: echo '// expected-no-diagnostics' >> %t.ii
+// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %t.ii
+
+// Check that we accept the program if the pragma is present:
+// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %s -DUSE_PRAGMA
+
+// Check that we preserve the pragma when preprocessing:
+// RUN: %clang_cc1 -E -std=c++23 %s -o %t.ii -DUSE_PRAGMA
// RUN: FileCheck --input-file=%t.ii %s
+
+// Check that the preprocessed file compiles with no diagnostics:
+// RUN: echo '// expected-no-diagnostics' >> %t.ii
// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %t.ii
+// Irrespective of whether we used the pragma directly or defined __GLIBCXX__,
+// the preprocessed output should contain the pragma:
+// CHECK: #pragma clang __set_pp_state __GLIBCXX__ 20250513
+
// expected-no-diagnostics
// Primary variable template std::format_kind is defined as followed since
@@ -24,11 +34,12 @@
// that ensures '__GLIBCXX__' is defined if the user first preprocesses the file
// with '-E' before passing the output of that back to Clang.
-// CHECK: #pragma clang glibcxx_version 20250513
-#ifdef DEFINE
+#ifdef DEFINE_GLIBCXX
# define __GLIBCXX__ 20250513
-#else
-# pragma clang glibcxx_version 20250513
+#endif
+
+#ifdef USE_PRAGMA
+# pragma clang __set_pp_state __GLIBCXX__ 20250513
#endif
namespace std {
>From 31817288590026a2806f71cbb8bd2cdd1ff762d1 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Thu, 23 Jul 2026 22:00:29 +0200
Subject: [PATCH 5/5] clang-format
---
clang/include/clang/Lex/Preprocessor.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h
index ec30c5b50ca59..b01639d21010e 100644
--- a/clang/include/clang/Lex/Preprocessor.h
+++ b/clang/include/clang/Lex/Preprocessor.h
@@ -2994,7 +2994,7 @@ class Preprocessor {
void HandlePragmaIncludeAlias(Token &Tok);
void HandlePragmaModuleBuild(Token &Tok);
void HandlePragmaHdrstop(Token &Tok);
- void HandlePragmaSetPPState(PragmaIntroducer Introducer, Token& Tok);
+ void HandlePragmaSetPPState(PragmaIntroducer Introducer, Token &Tok);
IdentifierInfo *ParsePragmaPushOrPopMacro(Token &Tok);
/// Check whether this is a macro name that can be used as an argument to
More information about the cfe-commits
mailing list