https://github.com/ChenMiaoi created
https://github.com/llvm/llvm-project/pull/230929
Nested `_Pragma` or Microsoft `__pragma` expressions can inject annotation
tokens into the enclosing pragma’s token stream. Some handlers check the
argument’s token kind but still call `getSpelling()` when reporting invalid or
extra arguments. Annotation tokens do not have the length field used by
ordinary tokens, so constructing the diagnostic triggers an assertion in
`getLength()` and crashes.
For example:
``` cc
#pragma clang optimize _Pragma("weak foobar") on
```
This patch temporarily uses an empty string ("") for annotation tokens to avoid
looking up their spelling. The diagnostic wording will be improved in a
separate patch.
Split by #228990
>From 4aca700e417005ca4b3d824bb28b010df5b80b11 Mon Sep 17 00:00:00 2001
From: Chen Miao <[email protected]>
Date: Sun, 11 Oct 2026 15:16:19 +0800
Subject: [PATCH] [clang] Avoid spelling annotation tokens in pragma
diagnostics
---
clang/docs/ReleaseNotes.md | 3 ++
clang/lib/Lex/Pragma.cpp | 3 +-
clang/lib/Parse/ParsePragma.cpp | 10 +++---
clang/test/Parser/pragma-nested-spelling.c | 32 +++++++++++++++++++
.../pragma-dependency-nested-annotation.c | 13 ++++++++
5 files changed, 55 insertions(+), 6 deletions(-)
create mode 100644 clang/test/Parser/pragma-nested-spelling.c
create mode 100644
clang/test/Preprocessor/pragma-dependency-nested-annotation.c
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 77a1e73bc8af6..77885d53fcaaf 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 caused by calling `getSpelling()` on annotation tokens
introduced
+ by nested `_Pragma` or Microsoft `__pragma` expressions. (#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 078842ab08c5e..310de844d555f 100644
--- a/clang/lib/Lex/Pragma.cpp
+++ b/clang/lib/Lex/Pragma.cpp
@@ -553,7 +553,8 @@ void Preprocessor::HandlePragmaDependency(Token
&DependencyTok) {
std::string Message;
Lex(DependencyTok);
while (DependencyTok.isNot(tok::eod)) {
- Message += getSpelling(DependencyTok) + " ";
+ if (!DependencyTok.isAnnotation())
+ Message += getSpelling(DependencyTok) + " ";
Lex(DependencyTok);
}
diff --git a/clang/lib/Parse/ParsePragma.cpp b/clang/lib/Parse/ParsePragma.cpp
index 04e7bb5a9b7dd..5fed63fd90464 100644
--- a/clang/lib/Parse/ParsePragma.cpp
+++ b/clang/lib/Parse/ParsePragma.cpp
@@ -3338,7 +3338,7 @@ void PragmaOptimizeHandler::HandlePragma(Preprocessor &PP,
}
if (Tok.isNot(tok::identifier)) {
PP.Diag(Tok.getLocation(), diag::err_pragma_optimize_invalid_argument)
- << PP.getSpelling(Tok);
+ << (Tok.isAnnotation() ? "" : PP.getSpelling(Tok));
return;
}
const IdentifierInfo *II = Tok.getIdentifierInfo();
@@ -3348,14 +3348,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);
+ << (Tok.isAnnotation() ? "" : PP.getSpelling(Tok));
return;
}
@@ -3419,8 +3419,8 @@ void PragmaFPHandler::HandlePragma(Preprocessor &PP,
// Don't diagnose if we have an eval_metod pragma with "double" kind.
if (Tok.isNot(tok::identifier) && !isEvalMethodDouble) {
PP.Diag(Tok.getLocation(), diag::err_pragma_fp_invalid_argument)
- << PP.getSpelling(Tok) << OptionInfo->getName()
- << static_cast<int>(*FlagKind);
+ << (Tok.isAnnotation() ? "" : PP.getSpelling(Tok))
+ << OptionInfo->getName() << static_cast<int>(*FlagKind);
return;
}
const IdentifierInfo *II = Tok.getIdentifierInfo();
diff --git a/clang/test/Parser/pragma-nested-spelling.c
b/clang/test/Parser/pragma-nested-spelling.c
new file mode 100644
index 0000000000000..7a18e7c74b9a3
--- /dev/null
+++ b/clang/test/Parser/pragma-nested-spelling.c
@@ -0,0 +1,32 @@
+// 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
+
+// Avoid requesting the spelling of annotation tokens in diagnostics.
+// Expect existing recovery errors and use empty declarations to isolate cases.
+#pragma clang optimize _Pragma("weak foobar") on // expected-error
{{unexpected argument '' to '#pragma clang optimize'; expected 'on' or 'off'}}
+// expected-error@-1 {{unknown type name 'foobar'}} expected-error@-1
{{expected ';' after top level declarator}}
+;
+#pragma clang optimize on _Pragma("weak foobar") // expected-error
{{unexpected extra argument '' to '#pragma clang optimize'}}
+// expected-error@-1 {{unknown type name 'foobar'}} c-error@-1 {{expected
identifier or '('}} cxx-error@-1 {{expected unqualified-id}}
+;
+#pragma clang fp contract(_Pragma("weak foobar")) // expected-error
{{unexpected argument '' to '#pragma clang fp contract'; expected 'fast' or
'on' or 'off'}}
+// c-error@-1 {{type specifier missing}} c-error@-1 {{expected ';' after top
level declarator}}
+// cxx-error@-2 {{unknown type name 'foobar'}} cxx-error@-2 {{expected
unqualified-id}}
+;
+
+// Ordinary invalid tokens retain their spelling in the diagnostics.
+#pragma clang optimize 42 // expected-error {{unexpected argument '42' to
'#pragma clang optimize'; expected 'on' or 'off'}}
+#pragma clang optimize on 42 // expected-error {{unexpected extra argument
'42' to '#pragma clang optimize'}}
+#pragma clang fp contract(42) // expected-error {{unexpected argument '42' to
'#pragma clang fp contract'; expected 'fast' or 'on' or 'off'}}
+
+// __pragma expands and captures annotations before invoking the handler.
+__pragma(clang optimize _Pragma("weak foobar") on) // expected-error
{{unexpected argument '' to '#pragma clang optimize'; expected 'on' or 'off'}}
+__pragma(clang optimize on _Pragma("weak foobar")) // expected-error
{{unexpected extra argument '' to '#pragma clang optimize'}}
+__pragma(clang fp contract(_Pragma("weak foobar"))) // expected-error
{{unexpected argument '' to '#pragma clang fp contract'; expected 'fast' or
'on' or 'off'}}
+
+// Preserve the keyword alternative accepted by eval_method.
+#pragma clang fp eval_method(double)
+
+int after_pragmas;
+int use_after_pragmas(void) { return after_pragmas; }
diff --git a/clang/test/Preprocessor/pragma-dependency-nested-annotation.c
b/clang/test/Preprocessor/pragma-dependency-nested-annotation.c
new file mode 100644
index 0000000000000..6b65d849fc658
--- /dev/null
+++ b/clang/test/Preprocessor/pragma-dependency-nested-annotation.c
@@ -0,0 +1,13 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: cp %s %t/test.c
+// RUN: echo "// dependency" > %t/header.h
+// RUN: %python -c "import os; ts = os.stat(r'%t/test.c').st_mtime + 60;
os.utime(r'%t/header.h', (ts, ts))"
+// RUN: %clang_cc1 -fsyntax-only -verify %t/test.c
+
+// The message contains tokens injected by a nested pragma. Skip the annotation
+// while preserving surrounding ordinary tokens and the dependency warning.
+#pragma GCC dependency "header.h" before _Pragma("weak foobar") after //
expected-warning {{current file is older than dependency before foobar after}}
+
+int after_pragmas;
+int use_after_pragmas(void) { return after_pragmas; }
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits