https://github.com/akashagrwl updated https://github.com/llvm/llvm-project/pull/211221
>From 91df3e236c2486c4c7c669cea3ea8dfcfae5d4df Mon Sep 17 00:00:00 2001 From: Akash Agrawal <[email protected]> Date: Wed, 22 Jul 2026 02:55:39 -0700 Subject: [PATCH 1/2] [Clang][Parse] Fix assertion when annotating a failed decltype-specifier When `ParseOptionalCXXScopeSpecifier` sees a leading `decltype`, it parses the decltype-specifier and then calls `AnnotateExistingDecltypeSpecifier` to turn the consumed tokens into a single `annot_decltype` token. If the decltype-specifier fails to parse (e.g. `decltype` is not followed by `(`), error recovery may skip tokens, leaving the `EndLoc` returned by `ParseDecltypeSpecifier` stale. Annotating with that stale end location no longer matches the most recent cached token and trips the invariant asserted in `Preprocessor::AnnotatePreviousCachedTokens`: ```cpp int decltype; // crashed int *decltype = 0; // crashed ``` Fix it the same way the sibling pack-indexing path (`AnnotateExistingIndexedTypeNamePack`) already does: on error, re-derive `EndLoc` from the last cached token. Unlike the pack-indexing branch, this path can be entered with a single `decltype` token, so the backtracking cache may be empty after `RevertCachedTokens`; guard the query with a new `Preprocessor::hasCachedTokenLocation()` helper so we never call `getLastCachedTokenLocation()` (which asserts `CachedLexPos != 0`) on an empty cache. Fixes #211207 --- clang/include/clang/Lex/Preprocessor.h | 4 ++++ clang/lib/Parse/ParseDeclCXX.cpp | 14 ++++++++++++++ clang/test/Parser/decltype-crash.cpp | 14 ++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 clang/test/Parser/decltype-crash.cpp diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h index 1c917dcfe7b7e..95a8074215014 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -1991,6 +1991,10 @@ class Preprocessor { return CachedTokens[CachedLexPos-1].getLastLoc(); } + /// Whether there is a cached token whose location + /// getLastCachedTokenLocation() can return. + bool hasCachedTokenLocation() const { return CachedLexPos != 0; } + /// Whether \p Tok is the most recent token (`CachedLexPos - 1`) in /// CachedTokens. bool IsPreviousCachedToken(const Token &Tok) const; diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index 150987dae332d..dd57495634218 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -1136,6 +1136,20 @@ void Parser::AnnotateExistingDecltypeSpecifier(const DeclSpec &DS, // make sure we have a token we can turn into an annotation token if (PP.isBacktrackEnabled()) { PP.RevertCachedTokens(1); + if (DS.getTypeSpecType() == TST_error && PP.hasCachedTokenLocation()) { + // The decltype-specifier failed to parse (e.g. 'decltype' not followed + // by '('), so error recovery may have skipped tokens to resynchronize. + // The EndLoc computed before that skip is now stale and no longer + // matches the most recent cached token, which would trip the invariant + // in Preprocessor::AnnotatePreviousCachedTokens. Annotate up to the last + // cached token instead, as AnnotateExistingIndexedTypeNamePack does. + // Unlike that pack-indexing path (which needs several lookahead tokens + // to be reached), this one can fire with just the 'decltype' token + // cached, so the cache may be empty after RevertCachedTokens; guard + // against that, since getLastCachedTokenLocation() requires a cached + // token. + EndLoc = PP.getLastCachedTokenLocation(); + } } else PP.EnterToken(Tok, /*IsReinject*/ true); diff --git a/clang/test/Parser/decltype-crash.cpp b/clang/test/Parser/decltype-crash.cpp new file mode 100644 index 0000000000000..c892c18069d87 --- /dev/null +++ b/clang/test/Parser/decltype-crash.cpp @@ -0,0 +1,14 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 %s + +// These used to assert in Preprocessor::AnnotatePreviousCachedTokens instead +// of just diagnosing the error: ParseOptionalCXXScopeSpecifier annotated a +// decltype-specifier that failed to parse (no '(' after 'decltype'), using a +// stale end-location left over from error recovery. +int decltype = 0; +// expected-error@-1 {{expected '(' after 'decltype'}} +// expected-error@-2 {{expected unqualified-id}} + +int *decltype = 0; +// expected-error@-1 {{expected '(' after 'decltype'}} +// expected-error@-2 {{expected unqualified-id}} >From d743aa4830ce451b09aec48fef11fbb9fc55f919 Mon Sep 17 00:00:00 2001 From: Akash Agrawal <[email protected]> Date: Thu, 23 Jul 2026 22:24:16 -0700 Subject: [PATCH 2/2] Reworked the fix as per the comments --- clang/docs/ReleaseNotes.md | 4 ++++ clang/include/clang/Lex/Preprocessor.h | 4 ---- clang/lib/Parse/ParseDeclCXX.cpp | 14 -------------- clang/lib/Parse/ParseExprCXX.cpp | 8 ++++++++ clang/test/Parser/decltype-crash.cpp | 9 +++++---- 5 files changed, 17 insertions(+), 22 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 50e1c4ce88797..1d410f8da145e 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -355,6 +355,10 @@ features cannot lower the translation-unit ABI level; `[](Types... = args...) {}`). Clang now diagnoses the illegal default argument instead of asserting. (#GH210714) +- Fixed a crash on invalid code where a ``decltype`` not followed by ``(`` was + parsed where a nested-name-specifier could appear (e.g. ``int decltype = 0;``). + Clang now diagnoses the error instead of asserting. (#GH211207) + #### Bug Fixes to AST Handling - Fixed a non-deterministic ordering of unused local typedefs that made diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h index 95a8074215014..1c917dcfe7b7e 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -1991,10 +1991,6 @@ class Preprocessor { return CachedTokens[CachedLexPos-1].getLastLoc(); } - /// Whether there is a cached token whose location - /// getLastCachedTokenLocation() can return. - bool hasCachedTokenLocation() const { return CachedLexPos != 0; } - /// Whether \p Tok is the most recent token (`CachedLexPos - 1`) in /// CachedTokens. bool IsPreviousCachedToken(const Token &Tok) const; diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index dd57495634218..150987dae332d 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -1136,20 +1136,6 @@ void Parser::AnnotateExistingDecltypeSpecifier(const DeclSpec &DS, // make sure we have a token we can turn into an annotation token if (PP.isBacktrackEnabled()) { PP.RevertCachedTokens(1); - if (DS.getTypeSpecType() == TST_error && PP.hasCachedTokenLocation()) { - // The decltype-specifier failed to parse (e.g. 'decltype' not followed - // by '('), so error recovery may have skipped tokens to resynchronize. - // The EndLoc computed before that skip is now stale and no longer - // matches the most recent cached token, which would trip the invariant - // in Preprocessor::AnnotatePreviousCachedTokens. Annotate up to the last - // cached token instead, as AnnotateExistingIndexedTypeNamePack does. - // Unlike that pack-indexing path (which needs several lookahead tokens - // to be reached), this one can fire with just the 'decltype' token - // cached, so the cache may be empty after RevertCachedTokens; guard - // against that, since getLastCachedTokenLocation() requires a cached - // token. - EndLoc = PP.getLastCachedTokenLocation(); - } } else PP.EnterToken(Tok, /*IsReinject*/ true); diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp index 06ff45da413ca..ea5aa447fe4fa 100644 --- a/clang/lib/Parse/ParseExprCXX.cpp +++ b/clang/lib/Parse/ParseExprCXX.cpp @@ -169,6 +169,14 @@ bool Parser::ParseOptionalCXXScopeSpecifier( SourceLocation DeclLoc = Tok.getLocation(); SourceLocation EndLoc = ParseDecltypeSpecifier(DS); + // Drop an invalid decltype-specifier instead of annotating it as a + // nested-name-specifier: the EndLoc may be stale from error recovery, + // which would trip an assertion in + // Preprocessor::AnnotatePreviousCachedTokens. Mirrors the pack-indexing + // check below. + if (DS.getTypeSpecType() == DeclSpec::TST_error) + return false; + SourceLocation CCLoc; // Work around a standard defect: 'decltype(auto)::' is not a // nested-name-specifier. diff --git a/clang/test/Parser/decltype-crash.cpp b/clang/test/Parser/decltype-crash.cpp index c892c18069d87..af4622df5e54a 100644 --- a/clang/test/Parser/decltype-crash.cpp +++ b/clang/test/Parser/decltype-crash.cpp @@ -1,10 +1,11 @@ // RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s // RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 %s -// These used to assert in Preprocessor::AnnotatePreviousCachedTokens instead -// of just diagnosing the error: ParseOptionalCXXScopeSpecifier annotated a -// decltype-specifier that failed to parse (no '(' after 'decltype'), using a -// stale end-location left over from error recovery. +// A 'decltype' that is not followed by '(' fails to parse as a +// decltype-specifier. ParseOptionalCXXScopeSpecifier used to still annotate it +// and treat it as a nested-name-specifier, which tripped an assertion in +// Preprocessor::AnnotatePreviousCachedTokens. It should just diagnose the +// error instead. int decltype = 0; // expected-error@-1 {{expected '(' after 'decltype'}} // expected-error@-2 {{expected unqualified-id}} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
