https://github.com/Sirraide updated https://github.com/llvm/llvm-project/pull/210802
>From e8d94c9d5cf208d94823f6da44685ab3825ed647 Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
