[clang] 75b5df8 - [Clang] Support libstdc++ workarounds when using `-E` (#210802)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 30 09:09:14 PDT 2026
Author: Sirraide
Date: 2026-07-30T18:09:08+02:00
New Revision: 75b5df8f0af1000cae8e21fedbc14d0c51a01318
URL: https://github.com/llvm/llvm-project/commit/75b5df8f0af1000cae8e21fedbc14d0c51a01318
DIFF: https://github.com/llvm/llvm-project/commit/75b5df8f0af1000cae8e21fedbc14d0c51a01318.diff
LOG: [Clang] Support libstdc++ workarounds when using `-E` (#210802)
We have a libstdc++ workaround in place to address a hack in libstdc++15
that is used in the definition of `std::format_kind`. GCC accepts the
hack, while Clang does not. libstdc++ was eventually updated to remove
the hack, but we still need the workaround for some versions of
libstdc++15 (see #139560 for more information).
Whether this workaround (and others that address libstdc++ hacks) is
active depends on the value of `__GLIBCXX__`. This stops working if
someone first preprocesses the input (via `-E`) and then attempts to
compile the preprocessed code with Clang (see #160314): since
preprocessing has already happened, `__GLIBCXX__` will be undefined and
the workaround will not be applied.
In case of this particular workaround, this results in `#include
<format>` failing to compile on some versions of libstdc++ if you’re
using separate preprocessing.
To resolve this problem, this patch introduces a new pragma (`#pragma
clang __set_pp_state MACRO_NAME INTEGER`). This is intended as a general
solution to preserving preprocessor state across runs of the
preprocessor.
Currently, the only supported value for `MACRO_NAME`is `__GLIBCXX__`.
When we encounter this form of the pragma, we update
`Preprocessor::CXXStandardLibraryVersion` (we don’t actually define
`__GLIBCXX__` or do anything w/ that macro here). In `-E` mode, the
pragma is retained in the output, and a pragma is emitted for `#define
__GLIBCXX__` if printing macro definitions is disabled.
Things like `#undef __GLIBCXX__` and redefinitions of `__GLIBCXX__` are
deliberately left unhandled (i.e. you get what you get), since users
shouldn’t ever be undefining or redefining this macro anyway (and since
it starts w/ `__`, it would also be UB to do so).
Fixes #160314.
Added:
clang/test/Preprocessor/pragma_set_pp_state.cpp
Modified:
clang/docs/ReleaseNotes.md
clang/include/clang/Basic/DiagnosticLexKinds.td
clang/include/clang/Lex/PPCallbacks.h
clang/include/clang/Lex/Preprocessor.h
clang/lib/Frontend/PrintPreprocessedOutput.cpp
clang/lib/Lex/PPExpressions.cpp
clang/lib/Lex/Pragma.cpp
clang/lib/Lex/Preprocessor.cpp
clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
Removed:
################################################################################
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index ac70a35a8b456..4a3df2f73618f 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -411,6 +411,10 @@ features cannot lower the translation-unit ABI level;
operator required an access check that ran while an enclosing declaration
was still being parsed. (#GH210692)
+- A workaround that was introduced to fix an issue with the `<format>` header present in some versions of
+ 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
- Fixed a non-deterministic ordering of unused local typedefs that made
diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td
index 8330166e05199..f0791ed486a74 100644
--- a/clang/include/clang/Basic/DiagnosticLexKinds.td
+++ b/clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -866,6 +866,13 @@ 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_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">,
ShowInSystemHeader, DefaultIgnore, InGroup<DiagGroup<"date-time">>;
diff --git a/clang/include/clang/Lex/PPCallbacks.h b/clang/include/clang/Lex/PPCallbacks.h
index 2f62fa8d52b1a..5f117bab6848e 100644
--- a/clang/include/clang/Lex/PPCallbacks.h
+++ b/clang/include/clang/Lex/PPCallbacks.h
@@ -342,6 +342,10 @@ class PPCallbacks {
/// is read.
virtual void PragmaAssumeNonNullEnd(SourceLocation Loc) {}
+ /// 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.
virtual void MacroExpands(const Token &MacroNameTok,
@@ -700,6 +704,12 @@ class PPChainedCallbacks : public PPCallbacks {
Second->PragmaAssumeNonNullEnd(Loc);
}
+ 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,
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 1ddc52f3a4a65..e752010dd2062 100644
--- a/clang/include/clang/Lex/Preprocessor.h
+++ b/clang/include/clang/Lex/Preprocessor.h
@@ -2827,6 +2827,7 @@ class Preprocessor {
public:
std::optional<std::uint64_t> getStdLibCxxVersion();
+ void setStdLibCxxVersion(std::uint64_t Version);
bool NeedsStdLibCxxWorkaroundBefore(std::uint64_t FixedVersion);
private:
@@ -2987,6 +2988,9 @@ class Preprocessor {
// Pragmas.
void HandlePragmaDirective(PragmaIntroducer Introducer);
+ // Cached identifiers used to implement __set_pp_state.
+ IdentifierInfo *Ident__GLIBCXX__;
+
public:
void HandlePragmaOnce(Token &OnceTok);
void HandlePragmaMark(Token &MarkTok);
@@ -2998,8 +3002,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 a3fbda5ab597c..79477d70ff397 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,8 @@ class PrintPPOutputPPCallbacks : public PPCallbacks {
void PragmaExecCharsetPop(SourceLocation Loc) override;
void PragmaAssumeNonNullBegin(SourceLocation Loc) override;
void PragmaAssumeNonNullEnd(SourceLocation Loc) override;
+ void PragmaSetPPState(SourceLocation Loc, IdentifierInfo *MacroName,
+ std::uint64_t Value) override;
/// Insert whitespace before emitting the next token.
///
@@ -559,22 +563,38 @@ 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;
}
+
+ IdentifierInfo *MacroName = MacroNameTok.getIdentifierInfo();
+ if (!ShouldEmitDefine) {
+ // 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 __set_pp_state " << MacroName->getName();
+ PrintMacroDefinition(/*II=*/nullptr, *MI, PP, OS);
+ setEmittedDirectiveOnThisLine();
+ }
+ return;
+ }
+
MoveToLine(DefLoc, /*RequireStartOfLine=*/true);
- PrintMacroDefinition(*MacroNameTok.getIdentifierInfo(), *MI, PP, OS);
+ PrintMacroDefinition(MacroName, *MI, PP, OS);
setEmittedDirectiveOnThisLine();
}
@@ -752,6 +772,15 @@ PragmaAssumeNonNullEnd(SourceLocation Loc) {
setEmittedDirectiveOnThisLine();
}
+void PrintPPOutputPPCallbacks::PragmaSetPPState(SourceLocation Loc,
+ IdentifierInfo *MacroName,
+ std::uint64_t Value) {
+ MoveToLine(Loc, /*RequireStartOfLine=*/true);
+ *OS << "#pragma clang __set_pp_state " << MacroName->getName() << " "
+ << Value;
+ setEmittedDirectiveOnThisLine();
+}
+
void PrintPPOutputPPCallbacks::HandleWhitespaceBeforeTok(const Token &Tok,
bool RequireSpace,
bool RequireSameLine) {
@@ -1093,7 +1122,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..1040b83e8745d 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;
@@ -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..9e9f1d21980e3 100644
--- a/clang/lib/Lex/Pragma.cpp
+++ b/clang/lib/Lex/Pragma.cpp
@@ -913,6 +913,47 @@ 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->getName() == "__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,6 +2189,25 @@ struct PragmaFinalHandler : public PragmaHandler {
}
};
+/// "\#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 __set_pp_state glibcxx_version INTEGER
+/// \endcode
+struct PragmaSetPPStateHandler : PragmaHandler {
+ PragmaSetPPStateHandler() : PragmaHandler("__set_pp_state") {}
+ void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer,
+ Token &Tok) override {
+ PP.HandlePragmaSetPPState(Introducer, Tok);
+ }
+};
} // namespace
/// RegisterBuiltinPragmas - Install the standard preprocessor pragmas:
@@ -2179,6 +2239,7 @@ void Preprocessor::RegisterBuiltinPragmas() {
AddPragmaHandler("clang", new PragmaDeprecatedHandler());
AddPragmaHandler("clang", new PragmaRestrictExpansionHandler());
AddPragmaHandler("clang", new PragmaFinalHandler());
+ AddPragmaHandler("clang", new PragmaSetPPStateHandler());
// #pragma clang module ...
auto *ModuleHandler = new PragmaNamespace("module");
diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp
index 212b78154610f..11d37877eb41d 100644
--- a/clang/lib/Lex/Preprocessor.cpp
+++ b/clang/lib/Lex/Preprocessor.cpp
@@ -159,6 +159,8 @@ Preprocessor::Preprocessor(const PreprocessorOptions &PPOpts,
Ident_AbnormalTermination = nullptr;
}
+ 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_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 35611c870b8d1..b8cde0b6b2f2e 100644
--- a/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
+++ b/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
@@ -1,12 +1,46 @@
-// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %s
+// 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
+
+// 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
// 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.
+
+#ifdef DEFINE_GLIBCXX
+# define __GLIBCXX__ 20250513
+#endif
-#define __GLIBCXX__ 20250513
+#ifdef USE_PRAGMA
+# pragma clang __set_pp_state __GLIBCXX__ 20250513
+#endif
namespace std {
template<typename _Rg>
More information about the cfe-commits
mailing list