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> _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
