Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions clang/docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,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
Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/Basic/DiagnosticLexKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,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">>;
Expand Down
10 changes: 10 additions & 0 deletions clang/include/clang/Lex/PPCallbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions clang/include/clang/Lex/Preprocessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -2978,6 +2979,9 @@ class Preprocessor {
// Pragmas.
void HandlePragmaDirective(PragmaIntroducer Introducer);

// Cached identifiers used to implement __set_pp_state.
IdentifierInfo *Ident__GLIBCXX__;
Comment on lines +2982 to +2983

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is neither needed nor used

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see it being used; we need an IdentifierInfo * for the macro names we care about and we don't want to re-create the IdentifierInfo * each time so we cache it.

I'd rather these were grouped with the rest of them towards the top of the class though.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment below. we are gonna parse that once per TU

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, we're not going to parse it often, but I don't see what the problem is with following the usual pattern of comparing IdentifierInfo *, either. I don't insist one way or the other.

@Sirraide Sirraide Jul 24, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment below. we are gonna parse that once per TU

That is true, but we do the comparison against __GLIBCXX__ every time we print a #pragma in -E mode; whether we care about that, I don’t know; we can probably get rid of Ident__set_pp_state though

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather these were grouped with the rest of them towards the top of the class though.

All of those are specifically predefined/builtin macros; we also have groups of cached IdentifierInfo*s elsewhere in the preprocessor, so I felt putting these here made more sense

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do the comparison against __GLIBCXX__ every time we print a #pragma in -E mode

(via isPragmaSetPPStateMacro())


public:
void HandlePragmaOnce(Token &OnceTok);
void HandlePragmaMark(Token &MarkTok);
Expand All @@ -2989,8 +2993,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);
Comment thread
Sirraide marked this conversation as resolved.

// 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);
Expand Down
51 changes: 40 additions & 11 deletions clang/lib/Frontend/PrintPreprocessedOutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 << '(';
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't clear to me what we're doing here? This change and the one in PrintMacroDefinition seem odd? What am I missign?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This handles printing macro definitions (if doing that is enabled); basically, the idea here is that even if we don’t want to print macro definitions, we still need to emit a pragma for __GLIBCXX__ when we encounter a #define for it (and conversely, if we’re emitting the #define anyway, we don’t need the pragma).

The changes to PrintMacroDefinition are so we can print just the expansion of __GLIBCXX__ without printing the leading #define (since we want to print #pragma clang glibcxx_version instead)

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();
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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';
}
}
Expand Down
13 changes: 10 additions & 3 deletions clang/lib/Lex/PPExpressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;

Expand All @@ -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__");
Expand Down
61 changes: 61 additions & 0 deletions clang/lib/Lex/Pragma.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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".
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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");
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Lex/Preprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
27 changes: 27 additions & 0 deletions clang/test/Preprocessor/pragma_set_pp_state.cpp
Original file line number Diff line number Diff line change
@@ -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__'}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we expect boolean, binary interer or hex integer literals to work?

Maybe check char and string literals.

nullptr?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, currently, we always generate a decimal literal so that’s the only thing we need to support I think since this isn’t supposed to be user-facing anyway. Anything that’s not a numeric literal would be rejected here since that’s what we check for; I can add some tests for that if you’d like

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More tests here seems like a good thing.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #217735 for this

#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
38 changes: 36 additions & 2 deletions clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
Original file line number Diff line number Diff line change
@@ -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>
Expand Down
Loading