[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