[clang] [clang] Improve diagnostics and recovery for nested pragmas (PR #230895)
Chen Miao via cfe-commits
cfe-commits at lists.llvm.org
Sat Oct 10 22:25:55 PDT 2026
https://github.com/ChenMiaoi created https://github.com/llvm/llvm-project/pull/230895
Nested pragmas can leave empty quoted arguments in diagnostics or let an inner end-of-directive token truncate the enclosing pragma. Omit annotation argument spellings and track pragma lexer boundaries during argument reads and `__pragma` capture, including captures that finish outside a macro expansion.
Depends on #228990. This draft starts from `main` and contains the diagnostic and recovery improvements split from that PR; the crash fixes remain in #228990. After it merges, merge the updated `main`, reconcile the overlapping diagnostic calls, update the crash regression expectations, and complete runtime validation.
Validation: C++ syntax checks passed for all four modified implementation files; the Parse diagnostic table generated successfully; `git clang-format` and whitespace checks passed. The new nested-annotation runtime tests await the crash-fix dependency.
AI tools assisted with splitting the changes and preparing validation.
>From e0ab017a87828d5bc72d519a4c0a3b169f29fb47 Mon Sep 17 00:00:00 2001
From: Chen Miao <chenmiao.ku at gmail.com>
Date: Sun, 11 Oct 2026 13:25:15 +0800
Subject: [PATCH] [clang] Improve diagnostics and recovery for nested pragmas
---
clang/docs/ReleaseNotes.md | 4 ++
.../clang/Basic/DiagnosticParseKinds.td | 10 ++--
clang/include/clang/Lex/Preprocessor.h | 9 ++++
clang/include/clang/Lex/TokenLexer.h | 7 +++
clang/lib/Lex/Pragma.cpp | 31 +++++++++++
clang/lib/Lex/Preprocessor.cpp | 14 ++++-
clang/lib/Lex/TokenLexer.cpp | 2 +
clang/lib/Parse/ParsePragma.cpp | 53 ++++++++++++++-----
.../Parser/pragma-nested-recovery-target.c | 19 +++++++
clang/test/Parser/pragma-nested-recovery.c | 48 +++++++++++++++++
.../Preprocessor/print-pragma-microsoft.c | 12 +++++
11 files changed, 191 insertions(+), 18 deletions(-)
create mode 100644 clang/test/Parser/pragma-nested-recovery-target.c
create mode 100644 clang/test/Parser/pragma-nested-recovery.c
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 77a1e73bc8af6..c7f453b1113a6 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -1179,6 +1179,10 @@ The `alpha.cplusplus.UseAfterLifetimeEnd` checker was renamed to `alpha.core.Use
#### Diagnostic changes
+- Improved diagnostics and error recovery for nested `_Pragma` and Microsoft
+ `__pragma` expressions, including arguments that cross macro expansion
+ boundaries. Annotation arguments no longer appear as empty quoted strings,
+ and inner pragma terminators no longer disrupt subsequent declarations.
- For self-assignments during initialization (`T v = v;`), `core.uninitialized.Assign` will not report them as uninitialized accesses (except C++ reference types), and the checks will be delayed until the first accesses of these variables; `deadcode.DeadStores` will not report them as dead stores. (#GH187530)
(release-notes-sanitizers)=
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 594d158d25e92..df82177ebe4b5 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1088,8 +1088,10 @@ def warn_pragma_expected_string : Warning<
"expected string literal in '#pragma %0' - ignoring">, InGroup<IgnoredPragmas>;
def warn_pragma_missing_argument : Warning<
"missing argument to '#pragma %0'%select{|; expected %2}1">, InGroup<IgnoredPragmas>;
+// Annotation tokens have no source spelling; allow omitting it.
def warn_pragma_invalid_argument : Warning<
- "unexpected argument '%0' to '#pragma %1'%select{|; expected %3}2">, InGroup<IgnoredPragmas>;
+ "unexpected argument%select{ '%0'|}4 to '#pragma %1'"
+ "%select{|; expected %3}2">, InGroup<IgnoredPragmas>;
def err_pragma_misplaced_in_decl : Error<"this pragma cannot appear in %0 declaration">;
@@ -1217,10 +1219,10 @@ def err_pragma_pointers_to_members_unknown_kind : Error<
"'single_inheritance', 'multiple_inheritance', or 'virtual_inheritance'">;
// - #pragma clang optimize on/off
def err_pragma_optimize_invalid_argument : Error<
- "unexpected argument '%0' to '#pragma clang optimize'; "
+ "unexpected argument%select{ '%0'|}1 to '#pragma clang optimize'; "
"expected 'on' or 'off'">;
def err_pragma_optimize_extra_argument : Error<
- "unexpected extra argument '%0' to '#pragma clang optimize'">;
+ "unexpected extra argument%select{ '%0'|}1 to '#pragma clang optimize'">;
// - #pragma clang attribute
def err_pragma_attribute_expected_push_pop_paren : Error<
"expected 'push', 'pop', or '(' after '#pragma clang attribute'">;
@@ -1597,7 +1599,7 @@ def note_pragma_loop_invalid_vectorize_option : Note<
def err_pragma_fp_invalid_option : Error<
"%select{invalid|missing}0 option%select{ %1|}0; expected 'contract', 'reassociate', 'reciprocal', or 'exceptions'">;
def err_pragma_fp_invalid_argument : Error<
- "unexpected argument '%0' to '#pragma clang fp %1'; expected "
+ "unexpected argument%select{ '%0'|}3 to '#pragma clang fp %1'; expected "
"%select{"
"'fast' or 'on' or 'off'|"
"'on' or 'off'|"
diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h
index 31b68a0fd0670..1f1b5253bfb3f 100644
--- a/clang/include/clang/Lex/Preprocessor.h
+++ b/clang/include/clang/Lex/Preprocessor.h
@@ -883,6 +883,10 @@ class Preprocessor {
};
std::vector<IncludeStackInfo> IncludeMacroStack;
+ /// Stack depths of the pragmas whose arguments are being read. Track all
+ /// active scopes so that macro stack pops update enclosing boundaries too.
+ SmallVector<size_t, 4> PragmaLexerStackDepths;
+
/// Actions invoked when some preprocessor activity is
/// encountered (e.g. a file is \#included, etc).
std::unique_ptr<PPCallbacks> Callbacks;
@@ -2655,6 +2659,11 @@ class Preprocessor {
CurLexerSubmodule = IncludeMacroStack.back().TheSubmodule;
CurLexerCallback = IncludeMacroStack.back().CurLexerCallback;
IncludeMacroStack.pop_back();
+
+ // A __pragma can start in a macro and finish in its enclosing token stream.
+ // Keep all active pragma boundaries within the remaining lexer stack.
+ for (size_t &Depth : PragmaLexerStackDepths)
+ Depth = std::min(Depth, IncludeMacroStack.size());
}
void PropagateLineStartLeadingSpaceInfo(Token &Result);
diff --git a/clang/include/clang/Lex/TokenLexer.h b/clang/include/clang/Lex/TokenLexer.h
index 0c0c574267364..df7b0bebc4c06 100644
--- a/clang/include/clang/Lex/TokenLexer.h
+++ b/clang/include/clang/Lex/TokenLexer.h
@@ -104,6 +104,9 @@ class TokenLexer {
/// directive.
bool LexingCXXModuleDirective : 1;
+ /// This lexer contains the captured tokens of a __pragma directive.
+ bool IsPragmaLexer : 1;
+
public:
/// Create a TokenLexer for the specified macro with the specified actual
/// arguments. Note that this ctor takes ownership of the ActualArgs pointer.
@@ -155,6 +158,10 @@ class TokenLexer {
/// preprocessor directive.
bool isParsingPreprocessorDirective() const;
+ /// Return true if this lexer contains the captured tokens of a __pragma
+ /// directive.
+ bool isPragmaLexer() const { return IsPragmaLexer; }
+
/// setLexingCXXModuleDirective - This is set to true if this TokenLexer is
/// created when handling a C++ module directive.
void setLexingCXXModuleDirective(bool Val = true);
diff --git a/clang/lib/Lex/Pragma.cpp b/clang/lib/Lex/Pragma.cpp
index 078842ab08c5e..f227cc5f99336 100644
--- a/clang/lib/Lex/Pragma.cpp
+++ b/clang/lib/Lex/Pragma.cpp
@@ -35,6 +35,7 @@
#include "clang/Lex/TokenLexer.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Compiler.h"
@@ -170,6 +171,21 @@ void Preprocessor::HandlePragmaDirective(PragmaIntroducer Introducer) {
++NumPragma;
+ // Record this pragma's lexer stack depth so Lex() can distinguish its eod
+ // from an eod left by a nested pragma. For example:
+ //
+ // #pragma vtordisp(_Pragma("clang __debug dump x"))
+ // int after;
+ //
+ // The inner __debug dump leaves its arguments and eod for the parser:
+ //
+ // annot_pragma_dump x <inner eod> ) <outer eod>
+ //
+ // Lex() must skip the inner eod so the outer handler's error recovery also
+ // consumes the trailing ')' and leaves the following declaration intact.
+ PragmaLexerStackDepths.push_back(IncludeMacroStack.size());
+ llvm::scope_exit PragmaScope([this] { PragmaLexerStackDepths.pop_back(); });
+
// Invoke the first level of pragma handlers which reads the namespace id.
Token Tok;
PragmaHandlers->HandlePragma(*this, Introducer, Tok);
@@ -353,6 +369,16 @@ void clang::prepare_PragmaString(SmallVectorImpl<char> &StrVal) {
/// HandleMicrosoft__pragma - Like Handle_Pragma except the pragma text
/// is not enclosed within a string literal.
void Preprocessor::HandleMicrosoft__pragma(Token &Tok) {
+ // Expanding the captured arguments can execute nested pragmas before the
+ // outer handler runs. For example:
+ //
+ // __pragma(optimize("", _Pragma("clang __debug dump x")))
+ //
+ // Keep a boundary active during capture too, so Lex() skips the inner
+ // __debug dump's leftover eod rather than including it in PragmaToks.
+ PragmaLexerStackDepths.push_back(IncludeMacroStack.size());
+ llvm::scope_exit PragmaScope([this] { PragmaLexerStackDepths.pop_back(); });
+
// During macro pre-expansion, check the syntax now but put the tokens back
// into the token stream for later consumption. Same as Handle_Pragma.
TokenCollector Toks = {*this, InMacroArgPreExpansion, {}, Tok};
@@ -402,6 +428,7 @@ void Preprocessor::HandleMicrosoft__pragma(Token &Tok) {
// Push the tokens onto the stack.
EnterTokenStream(TokArray, PragmaToks.size(), true, true,
/*IsReinject*/ false);
+ CurTokenLexer->IsPragmaLexer = true;
// With everything set up, lex this as a #pragma directive.
HandlePragmaDirective({PIK___pragma, PragmaLoc});
@@ -1487,6 +1514,8 @@ struct PragmaWarningHandler : public PragmaHandler {
II = Tok.getIdentifierInfo();
if (!II && !Tok.is(tok::numeric_constant)) {
PP.Diag(Tok, diag::warn_pragma_warning_spec_invalid);
+ if (Tok.isAnnotation())
+ PP.DiscardUntilEndOfDirective();
return;
}
@@ -1630,6 +1659,8 @@ struct PragmaExecCharsetHandler : public PragmaHandler {
Callbacks->PragmaExecCharsetPop(DiagLoc);
} else {
PP.Diag(Tok, diag::warn_pragma_exec_charset_spec_invalid);
+ if (Tok.isAnnotation())
+ PP.DiscardUntilEndOfDirective();
return;
}
diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp
index 84907adc5d744..20626fa87466a 100644
--- a/clang/lib/Lex/Preprocessor.cpp
+++ b/clang/lib/Lex/Preprocessor.cpp
@@ -981,8 +981,18 @@ void Preprocessor::Lex(Token &Result) {
++LexLevel;
// We loop here until a lex function returns a token; this avoids recursion.
- while (!CurLexerCallback(*this, Result))
- ;
+ do {
+ while (!CurLexerCallback(*this, Result))
+ ;
+ // A nested pragma can defer parsing by injecting an annotation followed by
+ // its arguments and an eod. Its eod must not terminate the enclosing
+ // pragma. Only skip eod from actual pragma lexers; a token stream can also
+ // replay the enclosing pragma's own eod, for example when producing -E
+ // output.
+ } while (Result.is(tok::eod) && !PragmaLexerStackDepths.empty() &&
+ IncludeMacroStack.size() > PragmaLexerStackDepths.back() &&
+ ((CurLexer && CurLexer->isPragmaLexer()) ||
+ (CurTokenLexer && CurTokenLexer->isPragmaLexer())));
if (Result.is(tok::unknown) && TheModuleLoader.HadFatalFailure)
return;
diff --git a/clang/lib/Lex/TokenLexer.cpp b/clang/lib/Lex/TokenLexer.cpp
index 1e6132887e319..7478e820a8693 100644
--- a/clang/lib/Lex/TokenLexer.cpp
+++ b/clang/lib/Lex/TokenLexer.cpp
@@ -58,6 +58,7 @@ void TokenLexer::Init(Token &Tok, SourceLocation ELEnd, MacroInfo *MI,
NumTokens = Macro->tokens_end()-Macro->tokens_begin();
MacroExpansionStart = SourceLocation();
LexingCXXModuleDirective = false;
+ IsPragmaLexer = false;
SourceManager &SM = PP.getSourceManager();
MacroStartSLocOffset = SM.getNextLocalOffset();
@@ -115,6 +116,7 @@ void TokenLexer::Init(const Token *TokArray, unsigned NumToks,
NextTokGetsSpace = false;
MacroExpansionStart = SourceLocation();
LexingCXXModuleDirective = false;
+ IsPragmaLexer = false;
// Set HasLeadingSpace/AtStartOfLine so that the first token will be
// returned unmodified.
diff --git a/clang/lib/Parse/ParsePragma.cpp b/clang/lib/Parse/ParsePragma.cpp
index 04e7bb5a9b7dd..e1a1b12136837 100644
--- a/clang/lib/Parse/ParsePragma.cpp
+++ b/clang/lib/Parse/ParsePragma.cpp
@@ -2846,6 +2846,8 @@ void PragmaMSPointersToMembers::HandlePragma(Preprocessor &PP,
if (!Arg) {
PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_identifier)
<< "pointers_to_members";
+ if (Tok.isAnnotation())
+ PP.DiscardUntilEndOfDirective();
return;
}
PP.Lex(Tok);
@@ -2863,6 +2865,8 @@ void PragmaMSPointersToMembers::HandlePragma(Preprocessor &PP,
PP.Diag(Tok.getLocation(),
diag::err_pragma_pointers_to_members_unknown_kind)
<< Tok.getKind() << /*OnlyInheritanceModels*/ 0;
+ if (Tok.isAnnotation())
+ PP.DiscardUntilEndOfDirective();
return;
}
PP.Lex(Tok);
@@ -2982,6 +2986,8 @@ void PragmaMSVtorDisp::HandlePragma(Preprocessor &PP,
} else {
PP.Diag(Tok.getLocation(), diag::warn_pragma_invalid_action)
<< "vtordisp";
+ if (Tok.isAnnotation())
+ PP.DiscardUntilEndOfDirective();
return;
}
}
@@ -3337,8 +3343,11 @@ void PragmaOptimizeHandler::HandlePragma(Preprocessor &PP,
return;
}
if (Tok.isNot(tok::identifier)) {
+ bool IsAnnotation = Tok.isAnnotation();
PP.Diag(Tok.getLocation(), diag::err_pragma_optimize_invalid_argument)
- << PP.getSpelling(Tok);
+ << PP.getSpelling(Tok) << IsAnnotation;
+ if (IsAnnotation)
+ PP.DiscardUntilEndOfDirective();
return;
}
const IdentifierInfo *II = Tok.getIdentifierInfo();
@@ -3348,14 +3357,17 @@ 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) << /*OmitArgument=*/false;
return;
}
PP.Lex(Tok);
if (Tok.isNot(tok::eod)) {
+ bool IsAnnotation = Tok.isAnnotation();
PP.Diag(Tok.getLocation(), diag::err_pragma_optimize_extra_argument)
- << PP.getSpelling(Tok);
+ << PP.getSpelling(Tok) << IsAnnotation;
+ if (IsAnnotation)
+ PP.DiscardUntilEndOfDirective();
return;
}
@@ -3418,9 +3430,12 @@ void PragmaFPHandler::HandlePragma(Preprocessor &PP,
// Don't diagnose if we have an eval_metod pragma with "double" kind.
if (Tok.isNot(tok::identifier) && !isEvalMethodDouble) {
+ bool IsAnnotation = Tok.isAnnotation();
PP.Diag(Tok.getLocation(), diag::err_pragma_fp_invalid_argument)
<< PP.getSpelling(Tok) << OptionInfo->getName()
- << static_cast<int>(*FlagKind);
+ << static_cast<int>(*FlagKind) << IsAnnotation;
+ if (IsAnnotation)
+ PP.DiscardUntilEndOfDirective();
return;
}
const IdentifierInfo *II = Tok.getIdentifierInfo();
@@ -3435,7 +3450,8 @@ void PragmaFPHandler::HandlePragma(Preprocessor &PP,
.Default(std::nullopt);
if (!AnnotValue->ContractValue) {
PP.Diag(Tok.getLocation(), diag::err_pragma_fp_invalid_argument)
- << PP.getSpelling(Tok) << OptionInfo->getName() << *FlagKind;
+ << PP.getSpelling(Tok) << OptionInfo->getName() << *FlagKind
+ << /*OmitArgument=*/false;
return;
}
} else if (FlagKind == PFK_Reassociate || FlagKind == PFK_Reciprocal) {
@@ -3448,7 +3464,8 @@ void PragmaFPHandler::HandlePragma(Preprocessor &PP,
.Default(std::nullopt);
if (!Value) {
PP.Diag(Tok.getLocation(), diag::err_pragma_fp_invalid_argument)
- << PP.getSpelling(Tok) << OptionInfo->getName() << *FlagKind;
+ << PP.getSpelling(Tok) << OptionInfo->getName() << *FlagKind
+ << /*OmitArgument=*/false;
return;
}
} else if (FlagKind == PFK_Exceptions) {
@@ -3461,7 +3478,8 @@ void PragmaFPHandler::HandlePragma(Preprocessor &PP,
.Default(std::nullopt);
if (!AnnotValue->ExceptionsValue) {
PP.Diag(Tok.getLocation(), diag::err_pragma_fp_invalid_argument)
- << PP.getSpelling(Tok) << OptionInfo->getName() << *FlagKind;
+ << PP.getSpelling(Tok) << OptionInfo->getName() << *FlagKind
+ << /*OmitArgument=*/false;
return;
}
} else if (FlagKind == PFK_EvalMethod) {
@@ -3474,7 +3492,8 @@ void PragmaFPHandler::HandlePragma(Preprocessor &PP,
.Default(std::nullopt);
if (!AnnotValue->EvalMethodValue) {
PP.Diag(Tok.getLocation(), diag::err_pragma_fp_invalid_argument)
- << PP.getSpelling(Tok) << OptionInfo->getName() << *FlagKind;
+ << PP.getSpelling(Tok) << OptionInfo->getName() << *FlagKind
+ << /*OmitArgument=*/false;
return;
}
}
@@ -3885,9 +3904,10 @@ bool Parser::HandlePragmaMSOptimize(StringRef PragmaName,
}
IdentifierInfo *II = Tok.getIdentifierInfo();
if (!II || (!II->isStr("on") && !II->isStr("off"))) {
+ bool IsAnnotation = Tok.isAnnotation();
PP.Diag(PragmaLocation, diag::warn_pragma_invalid_argument)
<< PP.getSpelling(Tok) << PragmaName << /*Expected=*/true
- << "'on' or 'off'";
+ << "'on' or 'off'" << IsAnnotation;
return false;
}
bool IsOn = II->isStr("on");
@@ -3901,7 +3921,7 @@ bool Parser::HandlePragmaMSOptimize(StringRef PragmaName,
if (!OptimizationList->getString().empty()) {
PP.Diag(PragmaLocation, diag::warn_pragma_invalid_argument)
<< OptimizationList->getString() << PragmaName << /*Expected=*/true
- << "\"\"";
+ << "\"\"" << /*OmitArgument=*/false;
return false;
}
@@ -3972,6 +3992,8 @@ void PragmaForceCUDAHostDeviceHandler::HandlePragma(
if (!Info || (!Info->isStr("begin") && !Info->isStr("end"))) {
PP.Diag(FirstTok.getLocation(),
diag::warn_pragma_force_cuda_host_device_bad_arg);
+ if (Tok.isAnnotation())
+ PP.DiscardUntilEndOfDirective();
return;
}
@@ -4230,8 +4252,12 @@ void PragmaRISCVHandler::HandlePragma(Preprocessor &PP,
IdentifierInfo *II = Tok.getIdentifierInfo();
if (!II || !II->isStr("intrinsic")) {
+ bool IsAnnotation = Tok.isAnnotation();
PP.Diag(Tok.getLocation(), diag::warn_pragma_invalid_argument)
- << PP.getSpelling(Tok) << "riscv" << /*Expected=*/true << "'intrinsic'";
+ << PP.getSpelling(Tok) << "riscv" << /*Expected=*/true << "'intrinsic'"
+ << IsAnnotation;
+ if (IsAnnotation)
+ PP.DiscardUntilEndOfDirective();
return;
}
@@ -4239,9 +4265,12 @@ void PragmaRISCVHandler::HandlePragma(Preprocessor &PP,
II = Tok.getIdentifierInfo();
if (!II || !(II->isStr("vector") || II->isStr("sifive_vector") ||
II->isStr("andes_vector"))) {
+ bool IsAnnotation = Tok.isAnnotation();
PP.Diag(Tok.getLocation(), diag::warn_pragma_invalid_argument)
<< PP.getSpelling(Tok) << "riscv" << /*Expected=*/true
- << "'vector', 'sifive_vector' or 'andes_vector'";
+ << "'vector', 'sifive_vector' or 'andes_vector'" << IsAnnotation;
+ if (IsAnnotation)
+ PP.DiscardUntilEndOfDirective();
return;
}
diff --git a/clang/test/Parser/pragma-nested-recovery-target.c b/clang/test/Parser/pragma-nested-recovery-target.c
new file mode 100644
index 0000000000000..72515e42e82e8
--- /dev/null
+++ b/clang/test/Parser/pragma-nested-recovery-target.c
@@ -0,0 +1,19 @@
+// 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
+
+#ifdef TEST_RISCV
+#pragma clang riscv _Pragma("weak foobar") intrinsic vector // riscv-warning {{unexpected argument to '#pragma riscv'; expected 'intrinsic'}}
+#pragma clang riscv intrinsic _Pragma("weak foobar") vector // riscv-warning {{unexpected argument to '#pragma riscv'; expected 'vector', 'sifive_vector' or 'andes_vector'}}
+#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}}
+#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-recovery.c b/clang/test/Parser/pragma-nested-recovery.c
new file mode 100644
index 0000000000000..a7529e7868a44
--- /dev/null
+++ b/clang/test/Parser/pragma-nested-recovery.c
@@ -0,0 +1,48 @@
+// RUN: %clang_cc1 -fsyntax-only -fms-extensions -verify %s
+// RUN: %clang_cc1 -fsyntax-only -fms-extensions -fms-compatibility -verify %s
+// RUN: %clang_cc1 -x c++ -fsyntax-only -fms-extensions -verify %s
+
+// Annotation arguments have no spelling. Omit the empty quoted argument and
+// consume the remaining directive, so subsequent declarations remain intact.
+#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'}}
+#pragma optimize("", _Pragma("weak foobar")) // expected-warning {{unexpected argument to '#pragma optimize'; expected 'on' or 'off'}}
+
+#pragma vtordisp(_Pragma("weak foobar")) // expected-warning {{unknown action for '#pragma vtordisp' - ignored}}
+#pragma pointers_to_members(_Pragma("weak foobar")) // expected-warning {{expected identifier in '#pragma pointers_to_members' - ignored}}
+#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'}}
+#pragma warning(_Pragma("weak foobar")) // expected-warning {{#pragma warning expected 'push', 'pop', 'default', 'disable', 'error', 'once', 'suppress', 1, 2, 3, or 4}}
+#pragma execution_character_set(_Pragma("weak foobar")) // expected-warning {{#pragma execution_character_set expected 'push' or 'pop'}}
+
+// Ordinary invalid arguments retain their original spelling.
+#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 optimize("", 42) // expected-warning {{unexpected argument '42' to '#pragma optimize'; expected 'on' or 'off'}}
+
+// __debug dump leaves its arguments and eod for the parser. Its inner eod
+// must not terminate the enclosing pragma's recovery or argument capture.
+#pragma vtordisp(_Pragma("clang __debug dump x")) // expected-warning {{unknown action for '#pragma vtordisp' - ignored}}
+int after_vtordisp;
+#pragma optimize("", _Pragma("clang __debug dump x")) // expected-warning {{unexpected argument to '#pragma optimize'; expected 'on' or 'off'}}
+int after_ms_optimize;
+__pragma(optimize("", _Pragma("clang __debug dump x"))) // expected-warning {{unexpected argument to '#pragma optimize'; expected 'on' or 'off'}}
+int after_ms_capture;
+
+// Captured __pragma token streams have their own eod too.
+#pragma vtordisp(__pragma(clang __debug dump x)) // expected-warning {{unknown action for '#pragma vtordisp' - ignored}}
+int after_nested_ms_pragma;
+__pragma(optimize("", __pragma(clang __debug dump x))) // expected-warning {{unexpected argument to '#pragma optimize'; expected 'on' or 'off'}}
+int after_nested_ms_capture;
+
+// Both captures start in the same macro, then finish after its lexer is popped.
+#define BEGIN_NESTED_PRAGMA __pragma(optimize("", __pragma(clang __debug dump
+BEGIN_NESTED_PRAGMA x))) // expected-warning {{unexpected argument to '#pragma optimize'; expected 'on' or 'off'}}
+#undef BEGIN_NESTED_PRAGMA
+int after_macro;
+
+int use_after_pragmas(void) {
+ return after_vtordisp + after_ms_optimize + after_ms_capture +
+ after_nested_ms_pragma + after_nested_ms_capture + after_macro;
+}
diff --git a/clang/test/Preprocessor/print-pragma-microsoft.c b/clang/test/Preprocessor/print-pragma-microsoft.c
index d121d93f0d857..20244db4f7aa4 100644
--- a/clang/test/Preprocessor/print-pragma-microsoft.c
+++ b/clang/test/Preprocessor/print-pragma-microsoft.c
@@ -18,3 +18,15 @@
#pragma pack(push, _CRT_PACKING)
// CHECK: #pragma pack(push, 8)
#pragma pack(pop)
+
+// Printing an unknown pragma replays its first token for macro expansion. In
+// the empty case that token is the outer pragma's eod, which must be preserved.
+__pragma()
+// CHECK: #pragma{{$}}
+int after_empty;
+// CHECK: int after_empty;
+
+__pragma(unknown argument)
+// CHECK: #pragma unknown argument
+int after_unknown;
+// CHECK: int after_unknown;
More information about the cfe-commits
mailing list