[clang] [clang] Guard identifier reads in pragma handlers (PR #230927)
Chen Miao via cfe-commits
cfe-commits at lists.llvm.org
Sat Oct 17 14:17:20 PDT 2026
https://github.com/ChenMiaoi updated https://github.com/llvm/llvm-project/pull/230927
>From 09394ace8975bf78ab78b364067595e92db7f540 Mon Sep 17 00:00:00 2001
From: Chen Miao <chenmiao.ku at gmail.com>
Date: Sun, 11 Oct 2026 15:16:17 +0800
Subject: [PATCH 1/2] [clang] Guard identifier reads in pragma handlers
---
clang/docs/ReleaseNotes.md | 3 +
clang/lib/Lex/Pragma.cpp | 43 +++++++---
clang/lib/Parse/ParsePragma.cpp | 62 ++++++++------
clang/test/FixIt/fixit-pragma-attribute.cpp | 5 ++
clang/test/Parser/pragma-attribute.cpp | 7 ++
.../Parser/pragma-nested-annotation-target.c | 31 +++++++
clang/test/Parser/pragma-nested-annotation.c | 85 +++++++++++++++++++
clang/unittests/Lex/LexerTest.cpp | 25 ++++++
8 files changed, 224 insertions(+), 37 deletions(-)
create mode 100644 clang/test/Parser/pragma-nested-annotation-target.c
create mode 100644 clang/test/Parser/pragma-nested-annotation.c
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 77a1e73bc8af60..4867f40c51cae7 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -947,6 +947,9 @@ features cannot lower the translation-unit ABI level;
#### Miscellaneous Clang Crashes Fixed
+- Fixed crashes when nested `_Pragma` or Microsoft `__pragma` expressions
+ introduce annotation tokens where a pragma expects an identifier. (#GH225035)
+
- Fixed a crash when Microsoft extensions were enabled and an unterminated
`__identifier` expression reached the end of a preprocessing directive or
source file. (#GH222310)
diff --git a/clang/lib/Lex/Pragma.cpp b/clang/lib/Lex/Pragma.cpp
index 078842ab08c5ef..e47bbc7402ef59 100644
--- a/clang/lib/Lex/Pragma.cpp
+++ b/clang/lib/Lex/Pragma.cpp
@@ -108,10 +108,11 @@ void PragmaNamespace::HandlePragma(Preprocessor &PP,
PP.LexUnexpandedToken(Tok);
// Get the handler for this token. If there is no handler, ignore the pragma.
- PragmaHandler *Handler
- = FindHandler(Tok.getIdentifierInfo() ? Tok.getIdentifierInfo()->getName()
- : StringRef(),
- /*IgnoreNull=*/false);
+ PragmaHandler *Handler = FindHandler(
+ Tok.is(tok::identifier) || tok::getKeywordSpelling(Tok.getKind())
+ ? Tok.getIdentifierInfo()->getName()
+ : StringRef(),
+ /*IgnoreNull=*/false);
if (!Handler) {
PP.Diag(Tok, diag::warn_pragma_ignored);
return;
@@ -921,7 +922,7 @@ void Preprocessor::HandlePragmaSetPPState(PragmaIntroducer Introducer,
Token &Tok) {
// Lex the macro name we want to set.
LexUnexpandedToken(Tok);
- if (!Tok.getIdentifierInfo()) {
+ if (Tok.isNot(tok::identifier) && !tok::getKeywordSpelling(Tok.getKind())) {
Diag(Tok.getLocation(), diag::err_pp_pragma_set_pp_state_expected_name);
return;
}
@@ -1149,7 +1150,10 @@ struct PragmaDebugHandler : public PragmaHandler {
} else if (II->isStr("macro")) {
Token MacroName;
PP.LexUnexpandedToken(MacroName);
- auto *MacroII = MacroName.getIdentifierInfo();
+ auto *MacroII = MacroName.is(tok::identifier) ||
+ tok::getKeywordSpelling(MacroName.getKind())
+ ? MacroName.getIdentifierInfo()
+ : nullptr;
if (MacroII)
PP.dumpMacroInfo(MacroII);
else
@@ -1174,7 +1178,10 @@ struct PragmaDebugHandler : public PragmaHandler {
} else if (II->isStr("module_lookup")) {
Token MName;
PP.LexUnexpandedToken(MName);
- auto *MNameII = MName.getIdentifierInfo();
+ auto *MNameII =
+ MName.is(tok::identifier) || tok::getKeywordSpelling(MName.getKind())
+ ? MName.getIdentifierInfo()
+ : nullptr;
if (!MNameII) {
PP.Diag(MName, diag::warn_pragma_debug_missing_argument)
<< II->getName();
@@ -1219,7 +1226,10 @@ struct PragmaDebugHandler : public PragmaHandler {
Token Kind;
PP.LexUnexpandedToken(Kind);
- auto *DumpII = Kind.getIdentifierInfo();
+ auto *DumpII =
+ Kind.is(tok::identifier) || tok::getKeywordSpelling(Kind.getKind())
+ ? Kind.getIdentifierInfo()
+ : nullptr;
if (!DumpII) {
PP.Diag(Kind, diag::warn_pragma_debug_missing_argument)
<< II->getName();
@@ -1453,7 +1463,9 @@ struct PragmaWarningHandler : public PragmaHandler {
}
PP.Lex(Tok);
- IdentifierInfo *II = Tok.getIdentifierInfo();
+ IdentifierInfo *II = Tok.isOneOf(tok::identifier, tok::kw_default)
+ ? Tok.getIdentifierInfo()
+ : nullptr;
if (II && II->isStr("push")) {
// #pragma warning( push[ ,n ] )
@@ -1484,7 +1496,9 @@ struct PragmaWarningHandler : public PragmaHandler {
// #pragma warning( warning-specifier : warning-number-list
// [; warning-specifier : warning-number-list...] )
while (true) {
- II = Tok.getIdentifierInfo();
+ II = Tok.isOneOf(tok::identifier, tok::kw_default)
+ ? Tok.getIdentifierInfo()
+ : nullptr;
if (!II && !Tok.is(tok::numeric_constant)) {
PP.Diag(Tok, diag::warn_pragma_warning_spec_invalid);
return;
@@ -1601,7 +1615,8 @@ struct PragmaExecCharsetHandler : public PragmaHandler {
}
PP.Lex(Tok);
- IdentifierInfo *II = Tok.getIdentifierInfo();
+ IdentifierInfo *II =
+ Tok.is(tok::identifier) ? Tok.getIdentifierInfo() : nullptr;
if (II && II->isStr("push")) {
// #pragma execution_character_set( push[ , string ] )
@@ -1930,7 +1945,8 @@ struct PragmaARCCFCodeAuditedHandler : public PragmaHandler {
// Lex the 'begin' or 'end'.
PP.LexUnexpandedToken(Tok);
- const IdentifierInfo *BeginEnd = Tok.getIdentifierInfo();
+ const IdentifierInfo *BeginEnd =
+ Tok.is(tok::identifier) ? Tok.getIdentifierInfo() : nullptr;
if (BeginEnd && BeginEnd->isStr("begin")) {
IsBegin = true;
} else if (BeginEnd && BeginEnd->isStr("end")) {
@@ -1985,7 +2001,8 @@ struct PragmaAssumeNonNullHandler : public PragmaHandler {
// Lex the 'begin' or 'end'.
PP.LexUnexpandedToken(Tok);
- const IdentifierInfo *BeginEnd = Tok.getIdentifierInfo();
+ const IdentifierInfo *BeginEnd =
+ Tok.is(tok::identifier) ? Tok.getIdentifierInfo() : nullptr;
if (BeginEnd && BeginEnd->isStr("begin")) {
IsBegin = true;
} else if (BeginEnd && BeginEnd->isStr("end")) {
diff --git a/clang/lib/Parse/ParsePragma.cpp b/clang/lib/Parse/ParsePragma.cpp
index 04e7bb5a9b7dd9..77c2f3e0b200ea 100644
--- a/clang/lib/Parse/ParsePragma.cpp
+++ b/clang/lib/Parse/ParsePragma.cpp
@@ -1310,7 +1310,8 @@ bool Parser::HandlePragmaMSStrictGuardStackCheck(
bool Value = false;
if (Action & Sema::PSK_Push || Action & Sema::PSK_Set) {
- const IdentifierInfo *II = Tok.getIdentifierInfo();
+ const IdentifierInfo *II =
+ Tok.is(tok::identifier) ? Tok.getIdentifierInfo() : nullptr;
if (II && II->isStr("off")) {
PP.Lex(Tok);
Value = false;
@@ -1553,7 +1554,8 @@ bool Parser::HandlePragmaLoopHint(LoopHint &Hint) {
if (StateOption) {
ConsumeAnnotationToken();
SourceLocation StateLoc = Toks[0].getLocation();
- IdentifierInfo *StateInfo = Toks[0].getIdentifierInfo();
+ IdentifierInfo *StateInfo =
+ Toks[0].is(tok::identifier) ? Toks[0].getIdentifierInfo() : nullptr;
bool Valid =
StateInfo &&
@@ -1583,7 +1585,8 @@ bool Parser::HandlePragmaLoopHint(LoopHint &Hint) {
ConsumeAnnotationToken();
SourceLocation StateLoc = Toks[0].getLocation();
- IdentifierInfo *StateInfo = Toks[0].getIdentifierInfo();
+ IdentifierInfo *StateInfo =
+ Toks[0].is(tok::identifier) ? Toks[0].getIdentifierInfo() : nullptr;
StringRef IsScalableStr = StateInfo ? StateInfo->getName() : "";
// Look for vectorize_width(fixed|scalable)
@@ -1612,7 +1615,7 @@ bool Parser::HandlePragmaLoopHint(LoopHint &Hint) {
if (Tok.is(tok::comma)) {
PP.Lex(Tok); // ,
- StateInfo = Tok.getIdentifierInfo();
+ StateInfo = Tok.is(tok::identifier) ? Tok.getIdentifierInfo() : nullptr;
IsScalableStr = StateInfo ? StateInfo->getName() : "";
if (IsScalableStr != "scalable" && IsScalableStr != "fixed") {
@@ -1860,7 +1863,8 @@ enum class MissingAttributeSubjectRulesRecoveryPoint {
MissingAttributeSubjectRulesRecoveryPoint
getAttributeSubjectRulesRecoveryPointForToken(const Token &Tok) {
- if (const auto *II = Tok.getIdentifierInfo()) {
+ if (Tok.is(tok::identifier)) {
+ const auto *II = Tok.getIdentifierInfo();
if (II->isStr("apply_to"))
return MissingAttributeSubjectRulesRecoveryPoint::ApplyTo;
if (II->isStr("any"))
@@ -2028,7 +2032,7 @@ void Parser::HandlePragmaAttribute() {
ParseMicrosoftDeclSpecs(Attrs);
} else {
Diag(Tok, diag::err_pragma_attribute_expected_attribute_syntax);
- if (Tok.getIdentifierInfo()) {
+ if (Tok.is(tok::identifier) || tok::getKeywordSpelling(Tok.getKind())) {
// If we suspect that this is an attribute suggest the use of
// '__attribute__'.
if (ParsedAttr::getParsedKind(
@@ -2141,7 +2145,8 @@ void PragmaGCCVisibilityHandler::HandlePragma(Preprocessor &PP,
Token Tok;
PP.LexUnexpandedToken(Tok);
- const IdentifierInfo *PushPop = Tok.getIdentifierInfo();
+ const IdentifierInfo *PushPop =
+ Tok.is(tok::identifier) ? Tok.getIdentifierInfo() : nullptr;
const IdentifierInfo *VisType;
if (PushPop && PushPop->isStr("pop")) {
@@ -2154,7 +2159,9 @@ void PragmaGCCVisibilityHandler::HandlePragma(Preprocessor &PP,
return;
}
PP.LexUnexpandedToken(Tok);
- VisType = Tok.getIdentifierInfo();
+ VisType = Tok.isOneOf(tok::identifier, tok::kw_default, tok::kw_protected)
+ ? Tok.getIdentifierInfo()
+ : nullptr;
if (!VisType) {
PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_identifier)
<< "visibility";
@@ -2842,7 +2849,8 @@ void PragmaMSPointersToMembers::HandlePragma(Preprocessor &PP,
return;
}
PP.Lex(Tok);
- const IdentifierInfo *Arg = Tok.getIdentifierInfo();
+ const IdentifierInfo *Arg =
+ Tok.is(tok::identifier) ? Tok.getIdentifierInfo() : nullptr;
if (!Arg) {
PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_identifier)
<< "pointers_to_members";
@@ -2858,7 +2866,7 @@ void PragmaMSPointersToMembers::HandlePragma(Preprocessor &PP,
if (Tok.is(tok::comma)) {
PP.Lex(Tok);
- Arg = Tok.getIdentifierInfo();
+ Arg = Tok.is(tok::identifier) ? Tok.getIdentifierInfo() : nullptr;
if (!Arg) {
PP.Diag(Tok.getLocation(),
diag::err_pragma_pointers_to_members_unknown_kind)
@@ -2937,7 +2945,8 @@ void PragmaMSVtorDisp::HandlePragma(Preprocessor &PP,
PP.Lex(Tok);
Sema::PragmaMsStackAction Action = Sema::PSK_Set;
- const IdentifierInfo *II = Tok.getIdentifierInfo();
+ const IdentifierInfo *II =
+ Tok.is(tok::identifier) ? Tok.getIdentifierInfo() : nullptr;
if (II) {
if (II->isStr("push")) {
// #pragma vtordisp(push, mode)
@@ -2965,7 +2974,8 @@ void PragmaMSVtorDisp::HandlePragma(Preprocessor &PP,
uint64_t Value = 0;
if (Action & Sema::PSK_Push || Action & Sema::PSK_Set) {
- const IdentifierInfo *II = Tok.getIdentifierInfo();
+ const IdentifierInfo *II =
+ Tok.is(tok::identifier) ? Tok.getIdentifierInfo() : nullptr;
if (II && II->isStr("off")) {
PP.Lex(Tok);
Value = 0;
@@ -3338,7 +3348,7 @@ void PragmaOptimizeHandler::HandlePragma(Preprocessor &PP,
}
if (Tok.isNot(tok::identifier)) {
PP.Diag(Tok.getLocation(), diag::err_pragma_optimize_invalid_argument)
- << PP.getSpelling(Tok);
+ << PP.getSpelling(Tok);
return;
}
const IdentifierInfo *II = Tok.getIdentifierInfo();
@@ -3348,14 +3358,14 @@ void PragmaOptimizeHandler::HandlePragma(Preprocessor &PP,
IsOn = true;
} else if (!II->isStr("off")) {
PP.Diag(Tok.getLocation(), diag::err_pragma_optimize_invalid_argument)
- << PP.getSpelling(Tok);
+ << PP.getSpelling(Tok);
return;
}
PP.Lex(Tok);
if (Tok.isNot(tok::eod)) {
PP.Diag(Tok.getLocation(), diag::err_pragma_optimize_extra_argument)
- << PP.getSpelling(Tok);
+ << PP.getSpelling(Tok);
return;
}
@@ -3883,11 +3893,12 @@ bool Parser::HandlePragmaMSOptimize(StringRef PragmaName,
<< PragmaName << /*Expected=*/true << "'on' or 'off'";
return false;
}
- IdentifierInfo *II = Tok.getIdentifierInfo();
+ IdentifierInfo *II =
+ Tok.is(tok::identifier) ? Tok.getIdentifierInfo() : nullptr;
if (!II || (!II->isStr("on") && !II->isStr("off"))) {
PP.Diag(PragmaLocation, diag::warn_pragma_invalid_argument)
- << PP.getSpelling(Tok) << PragmaName << /*Expected=*/true
- << "'on' or 'off'";
+ << (Tok.isAnnotation() ? "" : PP.getSpelling(Tok)) << PragmaName
+ << /*Expected=*/true << "'on' or 'off'";
return false;
}
bool IsOn = II->isStr("on");
@@ -3968,7 +3979,8 @@ void PragmaForceCUDAHostDeviceHandler::HandlePragma(
Token FirstTok = Tok;
PP.Lex(Tok);
- IdentifierInfo *Info = Tok.getIdentifierInfo();
+ IdentifierInfo *Info =
+ Tok.is(tok::identifier) ? Tok.getIdentifierInfo() : nullptr;
if (!Info || (!Info->isStr("begin") && !Info->isStr("end"))) {
PP.Diag(FirstTok.getLocation(),
diag::warn_pragma_force_cuda_host_device_bad_arg);
@@ -4227,21 +4239,23 @@ void PragmaRISCVHandler::HandlePragma(Preprocessor &PP,
Token &FirstToken) {
Token Tok;
PP.Lex(Tok);
- IdentifierInfo *II = Tok.getIdentifierInfo();
+ IdentifierInfo *II =
+ Tok.is(tok::identifier) ? Tok.getIdentifierInfo() : nullptr;
if (!II || !II->isStr("intrinsic")) {
PP.Diag(Tok.getLocation(), diag::warn_pragma_invalid_argument)
- << PP.getSpelling(Tok) << "riscv" << /*Expected=*/true << "'intrinsic'";
+ << (Tok.isAnnotation() ? "" : PP.getSpelling(Tok)) << "riscv"
+ << /*Expected=*/true << "'intrinsic'";
return;
}
PP.Lex(Tok);
- II = Tok.getIdentifierInfo();
+ II = Tok.is(tok::identifier) ? Tok.getIdentifierInfo() : nullptr;
if (!II || !(II->isStr("vector") || II->isStr("sifive_vector") ||
II->isStr("andes_vector"))) {
PP.Diag(Tok.getLocation(), diag::warn_pragma_invalid_argument)
- << PP.getSpelling(Tok) << "riscv" << /*Expected=*/true
- << "'vector', 'sifive_vector' or 'andes_vector'";
+ << (Tok.isAnnotation() ? "" : PP.getSpelling(Tok)) << "riscv"
+ << /*Expected=*/true << "'vector', 'sifive_vector' or 'andes_vector'";
return;
}
diff --git a/clang/test/FixIt/fixit-pragma-attribute.cpp b/clang/test/FixIt/fixit-pragma-attribute.cpp
index 3824e45d8fca54..ac6e1b5a9041a5 100644
--- a/clang/test/FixIt/fixit-pragma-attribute.cpp
+++ b/clang/test/FixIt/fixit-pragma-attribute.cpp
@@ -7,6 +7,11 @@
// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:31-[[@LINE-1]]:31}:"__attribute__(("
// CHECK: fix-it:{{.*}}:{[[@LINE-2]]:49-[[@LINE-2]]:49}:"))"
+#pragma clang attribute push(const, apply_to=function)
+// CHECK: note: use the GNU '__attribute__' syntax
+// CHECK: fix-it:{{.*}}:{[[@LINE-2]]:30-[[@LINE-2]]:30}:"__attribute__(("
+// CHECK: fix-it:{{.*}}:{[[@LINE-3]]:36-[[@LINE-3]]:36}:"))"
+
#pragma clang attribute push(__attribute__((annotate("test"))), apply_to = any( enum, function, function, namespace, function ))
// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:97-[[@LINE-1]]:107}:""
// CHECK: fix-it:{{.*}}:{[[@LINE-2]]:118-[[@LINE-2]]:127}:""
diff --git a/clang/test/Parser/pragma-attribute.cpp b/clang/test/Parser/pragma-attribute.cpp
index 1f90a8990bb96e..78699c06262cd7 100644
--- a/clang/test/Parser/pragma-attribute.cpp
+++ b/clang/test/Parser/pragma-attribute.cpp
@@ -180,6 +180,13 @@ _Pragma("clang attribute pop");
#pragma clang attribute push (annotate("test")) // expected-error {{expected an attribute that is specified using the GNU, C++11 or '__declspec' syntax}}
// expected-note at -1 {{use the GNU '__attribute__' syntax}}
+// Nested pragmas must be diagnosed without crashing (GH225035).
+#pragma clang attribute (_Pragma("clang attribute push")) // expected-error {{expected an attribute that is specified using the GNU, C++11 or '__declspec' syntax}}
+#pragma clang attribute push (_Pragma("clang attribute push")) // expected-error {{expected an attribute that is specified using the GNU, C++11 or '__declspec' syntax}}
+#pragma clang attribute push(__attribute__((annotate("test"))) _Pragma("clang attribute push")) // expected-error {{expected ','}}
+#pragma clang attribute push(__attribute__((annotate("test"))), _Pragma("clang attribute push")) // expected-error {{expected attribute subject set specifier 'apply_to'}}
+#pragma clang attribute push(__attribute__((annotate("test"))), apply_to _Pragma("clang attribute push")) // expected-error {{expected '='}}
+
#pragma clang attribute push([[clang::uninitialized]], apply_to = variable(is_local))
#pragma clang attribute pop
#pragma clang attribute push([[clang::uninitialized]], apply_to = function) // expected-error {{attribute 'clang::uninitialized' cannot be applied to 'function'}}
diff --git a/clang/test/Parser/pragma-nested-annotation-target.c b/clang/test/Parser/pragma-nested-annotation-target.c
new file mode 100644
index 00000000000000..345a5b60ef7cc7
--- /dev/null
+++ b/clang/test/Parser/pragma-nested-annotation-target.c
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -triple riscv64 -fsyntax-only -fms-extensions -DTEST_RISCV -verify=riscv %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x cuda -fsyntax-only -fms-extensions -DTEST_CUDA -verify=cuda %s
+
+// Existing recovery can leave inner pragma arguments in the token stream.
+// Use empty declarations to keep the invalid cases independent.
+
+#ifdef TEST_RISCV
+#pragma clang riscv _Pragma("weak foobar") intrinsic vector // riscv-warning {{unexpected argument '' to '#pragma riscv'; expected 'intrinsic'}}
+// riscv-error at -1 {{unknown type name 'foobar'}} riscv-error at -1 {{expected ';' after top level declarator}}
+;
+#pragma clang riscv intrinsic _Pragma("weak foobar") vector // riscv-warning {{unexpected argument '' to '#pragma riscv'; expected 'vector', 'sifive_vector' or 'andes_vector'}}
+// riscv-error at -1 {{unknown type name 'foobar'}} riscv-error at -1 {{expected ';' after top level declarator}}
+;
+
+// Invalid ordinary tokens keep their spelling.
+#pragma clang riscv 42 // riscv-warning {{unexpected argument '42' to '#pragma riscv'; expected 'intrinsic'}}
+#pragma clang riscv intrinsic wrong // riscv-warning {{unexpected argument 'wrong' to '#pragma riscv'; expected 'vector', 'sifive_vector' or 'andes_vector'}}
+#endif
+
+#ifdef TEST_CUDA
+#pragma clang force_cuda_host_device _Pragma("weak foobar") // cuda-warning {{incorrect use of #pragma clang force_cuda_host_device begin|end}}
+// cuda-error at -1 {{unknown type name 'foobar'}} cuda-error at -1 {{expected unqualified-id}}
+;
+
+#pragma clang force_cuda_host_device begin
+void host_device_function(void) {}
+#pragma clang force_cuda_host_device end
+#endif
+
+int after_pragmas;
+int use_after_pragmas(void) { return after_pragmas; }
diff --git a/clang/test/Parser/pragma-nested-annotation.c b/clang/test/Parser/pragma-nested-annotation.c
new file mode 100644
index 00000000000000..aef659ca0475d2
--- /dev/null
+++ b/clang/test/Parser/pragma-nested-annotation.c
@@ -0,0 +1,85 @@
+// RUN: %clang_cc1 -fsyntax-only -fms-extensions -verify=expected,c %s
+// RUN: %clang_cc1 -fsyntax-only -fms-extensions -fms-compatibility -verify=expected,c %s
+// RUN: %clang_cc1 -x c++ -fsyntax-only -fms-extensions -verify=expected,cxx %s
+
+// Nested _Pragma annotations must be rejected without querying identifier
+// information on the annotation token. Existing recovery can leave
+// inner pragma arguments in the token stream; expect the resulting errors and
+// use empty declarations to keep the cases independent.
+#pragma strict_gs_check(push, _Pragma("pack(1)")) // expected-warning {{unknown action for '#pragma strict_gs_check' - ignored}}
+#pragma optimize("", _Pragma("weak foobar")) // expected-warning {{unexpected argument '' to '#pragma optimize'; expected 'on' or 'off'}}
+#pragma optimize("", 42) // expected-warning {{unexpected argument '42' to '#pragma optimize'; expected 'on' or 'off'}}
+#pragma vtordisp(_Pragma("weak foobar")) // expected-warning {{unknown action for '#pragma vtordisp' - ignored}}
+// c-error at -1 {{type specifier missing}} c-error at -1 {{expected ';' after top level declarator}}
+// cxx-error at -2 {{unknown type name 'foobar'}} cxx-error at -2 {{expected unqualified-id}}
+;
+#pragma vtordisp(push, _Pragma("weak foobar")) // expected-warning {{unknown action for '#pragma vtordisp' - ignored}}
+// c-error at -1 {{type specifier missing}} c-error at -1 {{expected ';' after top level declarator}}
+// cxx-error at -2 {{unknown type name 'foobar'}} cxx-error at -2 {{expected unqualified-id}}
+;
+#pragma pointers_to_members(_Pragma("weak foobar")) // expected-warning {{expected identifier in '#pragma pointers_to_members' - ignored}}
+// c-error at -1 {{type specifier missing}} c-error at -1 {{expected ';' after top level declarator}}
+// cxx-error at -2 {{unknown type name 'foobar'}} cxx-error at -2 {{expected unqualified-id}}
+;
+#pragma pointers_to_members(full_generality, _Pragma("weak foobar")) // expected-error {{unexpected <annot_pragma_weak>, expected to see one of 'single_inheritance', 'multiple_inheritance', or 'virtual_inheritance'}}
+// c-error at -1 {{type specifier missing}} c-error at -1 {{expected ';' after top level declarator}}
+// cxx-error at -2 {{unknown type name 'foobar'}} cxx-error at -2 {{expected unqualified-id}}
+;
+#pragma warning(_Pragma("weak foobar")) // expected-warning {{#pragma warning expected 'push', 'pop', 'default', 'disable', 'error', 'once', 'suppress', 1, 2, 3, or 4}}
+// c-error at -1 {{type specifier missing}} c-error at -1 {{expected ';' after top level declarator}}
+// cxx-error at -2 {{unknown type name 'foobar'}} cxx-error at -2 {{expected unqualified-id}}
+;
+#pragma warning(disable : 1; _Pragma("weak foobar")) // expected-warning {{#pragma warning expected 'push', 'pop', 'default', 'disable', 'error', 'once', 'suppress', 1, 2, 3, or 4}}
+// c-error at -1 {{type specifier missing}} c-error at -1 {{expected ';' after top level declarator}}
+// cxx-error at -2 {{unknown type name 'foobar'}} cxx-error at -2 {{expected unqualified-id}}
+;
+#pragma execution_character_set(_Pragma("weak foobar")) // expected-warning {{#pragma execution_character_set expected 'push' or 'pop'}}
+// c-error at -1 {{type specifier missing}} c-error at -1 {{expected ';' after top level declarator}}
+// cxx-error at -2 {{unknown type name 'foobar'}} cxx-error at -2 {{expected unqualified-id}}
+;
+
+void loop_state(void) {
+#pragma clang loop vectorize(_Pragma("weak foobar")) // expected-error {{invalid argument; expected 'enable', 'assume_safety' or 'disable'}}
+ for (int i = 0; i < 4; ++i) {}
+}
+
+void loop_width(void) {
+#pragma clang loop vectorize_width(_Pragma("weak foobar")) // expected-error {{expected expression}} expected-note {{vectorize_width loop hint malformed}} expected-warning {{extra tokens at end of '#pragma clang loop vectorize_width' - ignored}}
+ for (int i = 0; i < 4; ++i) {}
+}
+
+void loop_width_second_arg(void) {
+#pragma clang loop vectorize_width(4, _Pragma("weak foobar")) // expected-error {{vectorize_width loop hint malformed}} expected-warning {{extra tokens at end of '#pragma clang loop vectorize_width' - ignored}}
+ for (int i = 0; i < 4; ++i) {}
+}
+
+// Preserve the non-identifier alternatives in valid pragmas.
+#pragma vtordisp()
+#pragma vtordisp(2)
+#pragma vtordisp(push, 1)
+#pragma vtordisp(pop)
+#pragma warning(default : 1; 2 : 2)
+#pragma GCC visibility push(default)
+#pragma GCC visibility pop
+#pragma GCC visibility push(protected)
+#pragma GCC visibility pop
+
+// __pragma captures expanded tokens, so even LexUnexpandedToken can return
+// annotations to the namespace dispatch and pragma handlers.
+__pragma(GCC _Pragma("weak foobar") ivdep)
+__pragma(GCC visibility _Pragma("weak foobar")) // expected-warning {{expected identifier in '#pragma visibility' - ignored}}
+__pragma(GCC visibility push(_Pragma("weak foobar"))) // expected-warning {{expected identifier in '#pragma visibility' - ignored}}
+__pragma(clang arc_cf_code_audited _Pragma("weak foobar")) // expected-error {{expected 'begin' or 'end'}}
+__pragma(clang assume_nonnull _Pragma("weak foobar")) // expected-error {{expected 'begin' or 'end'}}
+__pragma(clang __set_pp_state _Pragma("weak foobar")) // expected-error {{expected identifier after '#pragma clang __set_pp_state'}}
+__pragma(clang __debug macro _Pragma("weak foobar")) // expected-warning {{missing argument to debug command 'macro'}}
+__pragma(clang __debug module_lookup _Pragma("weak foobar")) // expected-warning {{missing argument to debug command 'module_lookup'}}
+__pragma(clang __debug modules _Pragma("weak foobar")) // expected-warning {{missing argument to debug command 'modules'}}
+
+// Macro names may be keywords.
+#define int int
+__pragma(clang __debug macro int)
+#undef int
+
+int after_pragmas;
+int use_after_pragmas(void) { return after_pragmas; }
diff --git a/clang/unittests/Lex/LexerTest.cpp b/clang/unittests/Lex/LexerTest.cpp
index 8f632ccc5b9315..6dd78d0d1ade8e 100644
--- a/clang/unittests/Lex/LexerTest.cpp
+++ b/clang/unittests/Lex/LexerTest.cpp
@@ -22,6 +22,7 @@
#include "clang/Lex/MacroArgs.h"
#include "clang/Lex/MacroInfo.h"
#include "clang/Lex/ModuleLoader.h"
+#include "clang/Lex/Pragma.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Lex/PreprocessorOptions.h"
#include "llvm/ADT/ArrayRef.h"
@@ -135,6 +136,30 @@ class LexerTest : public ::testing::Test {
std::string PreDefines;
};
+TEST_F(LexerTest, PragmaKeywordAliasesKeepTheirNames) {
+ class RecordingPragmaHandler : public PragmaHandler {
+ std::vector<std::string> &Names;
+
+ public:
+ RecordingPragmaHandler(StringRef Name, std::vector<std::string> &Names)
+ : PragmaHandler(Name), Names(Names) {}
+
+ void HandlePragma(Preprocessor &, PragmaIntroducer, Token &) override {
+ Names.push_back(getName().str());
+ }
+ };
+
+ std::vector<std::string> Names;
+ for (StringRef Name : {"const", "__const__"})
+ PP->AddPragmaHandler(new RecordingPragmaHandler(Name, Names));
+ PP->AddPragmaHandler("test", new RecordingPragmaHandler("__const__", Names));
+
+ Lex("#pragma const\n"
+ "#pragma __const__\n"
+ "#pragma test __const__\n");
+ EXPECT_THAT(Names, ElementsAre("const", "__const__", "__const__"));
+}
+
TEST_F(LexerTest, GetSourceTextExpandsToMaximumInMacroArgument) {
std::vector<tok::TokenKind> ExpectedTokens;
ExpectedTokens.push_back(tok::identifier);
>From d3f45ee062695b81146025a25785762f25927e4c Mon Sep 17 00:00:00 2001
From: Chen Miao <chenmiao.ku at gmail.com>
Date: Sun, 18 Oct 2026 05:16:28 +0800
Subject: [PATCH 2/2] [clang] Preserve operator keyword names in pragma
handlers
---
clang/lib/Lex/Pragma.cpp | 34 +++++++++-----------
clang/test/Parser/pragma-attribute.cpp | 3 --
clang/test/Parser/pragma-nested-annotation.c | 9 +++---
clang/unittests/Lex/LexerTest.cpp | 18 +++++++++--
4 files changed, 35 insertions(+), 29 deletions(-)
diff --git a/clang/lib/Lex/Pragma.cpp b/clang/lib/Lex/Pragma.cpp
index e47bbc7402ef59..6a2e209ff5510f 100644
--- a/clang/lib/Lex/Pragma.cpp
+++ b/clang/lib/Lex/Pragma.cpp
@@ -101,6 +101,13 @@ void PragmaNamespace::RemovePragmaHandler(PragmaHandler *Handler) {
Handlers.erase(I);
}
+// Names may be C++ operator keywords, which have punctuator token kinds.
+static IdentifierInfo *getPragmaIdentifierInfo(const Token &Tok) {
+ if (Tok.isAnnotation() || Tok.is(tok::raw_identifier))
+ return nullptr;
+ return Tok.getIdentifierInfo();
+}
+
void PragmaNamespace::HandlePragma(Preprocessor &PP,
PragmaIntroducer Introducer, Token &Tok) {
// Read the 'namespace' that the directive is in, e.g. STDC. Do not macro
@@ -108,11 +115,9 @@ void PragmaNamespace::HandlePragma(Preprocessor &PP,
PP.LexUnexpandedToken(Tok);
// Get the handler for this token. If there is no handler, ignore the pragma.
- PragmaHandler *Handler = FindHandler(
- Tok.is(tok::identifier) || tok::getKeywordSpelling(Tok.getKind())
- ? Tok.getIdentifierInfo()->getName()
- : StringRef(),
- /*IgnoreNull=*/false);
+ IdentifierInfo *II = getPragmaIdentifierInfo(Tok);
+ PragmaHandler *Handler =
+ FindHandler(II ? II->getName() : StringRef(), /*IgnoreNull=*/false);
if (!Handler) {
PP.Diag(Tok, diag::warn_pragma_ignored);
return;
@@ -922,12 +927,12 @@ void Preprocessor::HandlePragmaSetPPState(PragmaIntroducer Introducer,
Token &Tok) {
// Lex the macro name we want to set.
LexUnexpandedToken(Tok);
- if (Tok.isNot(tok::identifier) && !tok::getKeywordSpelling(Tok.getKind())) {
+ IdentifierInfo *MacroName = getPragmaIdentifierInfo(Tok);
+ if (!MacroName) {
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;
@@ -1150,10 +1155,7 @@ struct PragmaDebugHandler : public PragmaHandler {
} else if (II->isStr("macro")) {
Token MacroName;
PP.LexUnexpandedToken(MacroName);
- auto *MacroII = MacroName.is(tok::identifier) ||
- tok::getKeywordSpelling(MacroName.getKind())
- ? MacroName.getIdentifierInfo()
- : nullptr;
+ auto *MacroII = getPragmaIdentifierInfo(MacroName);
if (MacroII)
PP.dumpMacroInfo(MacroII);
else
@@ -1178,10 +1180,7 @@ struct PragmaDebugHandler : public PragmaHandler {
} else if (II->isStr("module_lookup")) {
Token MName;
PP.LexUnexpandedToken(MName);
- auto *MNameII =
- MName.is(tok::identifier) || tok::getKeywordSpelling(MName.getKind())
- ? MName.getIdentifierInfo()
- : nullptr;
+ auto *MNameII = getPragmaIdentifierInfo(MName);
if (!MNameII) {
PP.Diag(MName, diag::warn_pragma_debug_missing_argument)
<< II->getName();
@@ -1226,10 +1225,7 @@ struct PragmaDebugHandler : public PragmaHandler {
Token Kind;
PP.LexUnexpandedToken(Kind);
- auto *DumpII =
- Kind.is(tok::identifier) || tok::getKeywordSpelling(Kind.getKind())
- ? Kind.getIdentifierInfo()
- : nullptr;
+ auto *DumpII = getPragmaIdentifierInfo(Kind);
if (!DumpII) {
PP.Diag(Kind, diag::warn_pragma_debug_missing_argument)
<< II->getName();
diff --git a/clang/test/Parser/pragma-attribute.cpp b/clang/test/Parser/pragma-attribute.cpp
index 78699c06262cd7..e2fb747d035b8f 100644
--- a/clang/test/Parser/pragma-attribute.cpp
+++ b/clang/test/Parser/pragma-attribute.cpp
@@ -182,10 +182,7 @@ _Pragma("clang attribute pop");
// Nested pragmas must be diagnosed without crashing (GH225035).
#pragma clang attribute (_Pragma("clang attribute push")) // expected-error {{expected an attribute that is specified using the GNU, C++11 or '__declspec' syntax}}
-#pragma clang attribute push (_Pragma("clang attribute push")) // expected-error {{expected an attribute that is specified using the GNU, C++11 or '__declspec' syntax}}
#pragma clang attribute push(__attribute__((annotate("test"))) _Pragma("clang attribute push")) // expected-error {{expected ','}}
-#pragma clang attribute push(__attribute__((annotate("test"))), _Pragma("clang attribute push")) // expected-error {{expected attribute subject set specifier 'apply_to'}}
-#pragma clang attribute push(__attribute__((annotate("test"))), apply_to _Pragma("clang attribute push")) // expected-error {{expected '='}}
#pragma clang attribute push([[clang::uninitialized]], apply_to = variable(is_local))
#pragma clang attribute pop
diff --git a/clang/test/Parser/pragma-nested-annotation.c b/clang/test/Parser/pragma-nested-annotation.c
index aef659ca0475d2..7b4a517629295a 100644
--- a/clang/test/Parser/pragma-nested-annotation.c
+++ b/clang/test/Parser/pragma-nested-annotation.c
@@ -1,5 +1,4 @@
// RUN: %clang_cc1 -fsyntax-only -fms-extensions -verify=expected,c %s
-// RUN: %clang_cc1 -fsyntax-only -fms-extensions -fms-compatibility -verify=expected,c %s
// RUN: %clang_cc1 -x c++ -fsyntax-only -fms-extensions -verify=expected,cxx %s
// Nested _Pragma annotations must be rejected without querying identifier
@@ -54,10 +53,7 @@ void loop_width_second_arg(void) {
}
// Preserve the non-identifier alternatives in valid pragmas.
-#pragma vtordisp()
#pragma vtordisp(2)
-#pragma vtordisp(push, 1)
-#pragma vtordisp(pop)
#pragma warning(default : 1; 2 : 2)
#pragma GCC visibility push(default)
#pragma GCC visibility pop
@@ -81,5 +77,10 @@ __pragma(clang __debug modules _Pragma("weak foobar")) // expected-warning {{mis
__pragma(clang __debug macro int)
#undef int
+// MS extensions also permit C++ operator names as macro names.
+#define and 1
+#pragma clang __debug macro and
+#undef and
+
int after_pragmas;
int use_after_pragmas(void) { return after_pragmas; }
diff --git a/clang/unittests/Lex/LexerTest.cpp b/clang/unittests/Lex/LexerTest.cpp
index 6dd78d0d1ade8e..8d06ccf0222a71 100644
--- a/clang/unittests/Lex/LexerTest.cpp
+++ b/clang/unittests/Lex/LexerTest.cpp
@@ -137,6 +137,11 @@ class LexerTest : public ::testing::Test {
};
TEST_F(LexerTest, PragmaKeywordAliasesKeepTheirNames) {
+ LangOpts.CPlusPlus = true;
+ LangOpts.CXXOperatorNames = true;
+ PP = std::make_unique<Preprocessor>(PPOpts, Diags, LangOpts, SourceMgr,
+ HeaderInfo, ModLoader);
+
class RecordingPragmaHandler : public PragmaHandler {
std::vector<std::string> &Names;
@@ -150,14 +155,21 @@ TEST_F(LexerTest, PragmaKeywordAliasesKeepTheirNames) {
};
std::vector<std::string> Names;
- for (StringRef Name : {"const", "__const__"})
+ for (StringRef Name : {"const", "__const__", "and", "or"})
PP->AddPragmaHandler(new RecordingPragmaHandler(Name, Names));
PP->AddPragmaHandler("test", new RecordingPragmaHandler("__const__", Names));
+ PP->AddPragmaHandler("test", new RecordingPragmaHandler("or", Names));
Lex("#pragma const\n"
"#pragma __const__\n"
- "#pragma test __const__\n");
- EXPECT_THAT(Names, ElementsAre("const", "__const__", "__const__"));
+ "#pragma test __const__\n"
+ "#pragma and\n"
+ "#pragma or\n"
+ "#pragma test or\n"
+ "#pragma &&\n"
+ "#pragma test ||\n");
+ EXPECT_THAT(
+ Names, ElementsAre("const", "__const__", "__const__", "and", "or", "or"));
}
TEST_F(LexerTest, GetSourceTextExpandsToMaximumInMacroArgument) {
More information about the cfe-commits
mailing list