[clang] 8c8cc1c - [Clang][Parse] Fix assertion when annotating a failed decltype-specifier (#211221)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 28 05:28:31 PDT 2026
Author: Akash Agrawal
Date: 2026-07-28T20:28:27+08:00
New Revision: 8c8cc1cfb9f04fc57716b9ccc3ea74c9a5495bf7
URL: https://github.com/llvm/llvm-project/commit/8c8cc1cfb9f04fc57716b9ccc3ea74c9a5495bf7
DIFF: https://github.com/llvm/llvm-project/commit/8c8cc1cfb9f04fc57716b9ccc3ea74c9a5495bf7.diff
LOG: [Clang][Parse] Fix assertion when annotating a failed decltype-specifier (#211221)
`ParseDeclaratorInternal's` member-pointer special case enters a
speculative scope-specifier parse whenever it sees `decltype`, without
checking that `( `follows:
```
if (getLangOpts().CPlusPlus &&
(Tok.is(tok::coloncolon) || Tok.is(tok::kw_decltype) || ...)) {
```
But `decltype-specifier` is always `decltype ( expression ) — decltype`
not followed by `(` can never be valid. When that happens,
`ParseOptionalCXXScopeSpecifier` still tries to parse it as one, fails,
and error recovery skips tokens. The EndLoc returned is then stale by
the time `AnnotateExistingDecltypeSpecifier` uses it to annotate, which
trips the invariant in `Preprocessor::AnnotatePreviousCachedTokens`:
```
int decltype; // crashed
int *decltype = 0; // crashed
```
Fix: only take this path when decltype is followed by (.
`(Tok.is(tok::kw_decltype) && NextToken().is(tok::l_paren)) ||`
`ParseDecltypeSpecifier` already requires `(` right after `decltype` or
bails with `TST_error`, so this check drops no valid parse —
`decltype(expr)` (including as a member-pointer scope, e.g.` int
decltype(obj)::*p = &S::m;) `is unaffected.
Fixes #211207
Co-authored - Claude-Sonnet
Added:
clang/test/Parser/decltype-crash.cpp
Modified:
clang/docs/ReleaseNotes.md
clang/lib/Parse/ParseDecl.cpp
Removed:
################################################################################
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 63f898a89f10b..c348ddaf23017 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -388,6 +388,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)
+
- Fixed a crash when computing the implicit deletion of a defaulted comparison
operator required an access check that ran while an enclosing declaration
was still being parsed. (#GH210692)
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index cb2f6c07a6824..9fa3b96527c08 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -6496,8 +6496,12 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
// C++ member pointers start with a '::' or a nested-name.
// Member pointers get special handling, since there's no place for the
// scope spec in the generic path below.
+ // A 'decltype' can only begin a nested-name-specifier if it is followed by
+ // '('; otherwise it is not a decltype-specifier at all and must not be
+ // parsed as one.
if (getLangOpts().CPlusPlus &&
- (Tok.is(tok::coloncolon) || Tok.is(tok::kw_decltype) ||
+ (Tok.is(tok::coloncolon) ||
+ (Tok.is(tok::kw_decltype) && NextToken().is(tok::l_paren)) ||
(Tok.is(tok::identifier) &&
(NextToken().is(tok::coloncolon) || NextToken().is(tok::less))) ||
Tok.is(tok::annot_cxxscope))) {
diff --git a/clang/test/Parser/decltype-crash.cpp b/clang/test/Parser/decltype-crash.cpp
new file mode 100644
index 0000000000000..af4622df5e54a
--- /dev/null
+++ b/clang/test/Parser/decltype-crash.cpp
@@ -0,0 +1,15 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 %s
+
+// 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 at -1 {{expected '(' after 'decltype'}}
+// expected-error at -2 {{expected unqualified-id}}
+
+int *decltype = 0;
+// expected-error at -1 {{expected '(' after 'decltype'}}
+// expected-error at -2 {{expected unqualified-id}}
More information about the cfe-commits
mailing list