[clang] [clang][Parse] Skip late-parsed attributes after a fatal error (PR #197647)
Soowon Jeong via cfe-commits
cfe-commits at lists.llvm.org
Thu May 14 03:16:32 PDT 2026
https://github.com/swjng created https://github.com/llvm/llvm-project/pull/197647
## Summary
- Bail out of `Parser::ParseLexedAttribute` when `DiagnosticsEngine::hasFatalErrorOccurred()` is true, draining the cached attribute tokens without invoking the late parser.
- Fixes a clang frontend assertion crash (`Expression evaluator can't be called on a dependent expression`) when a translation unit exceeds `-ferror-limit` before a class with late-parsed attributes (e.g. `diagnose_if`, `enable_if`) closes.
## Root cause
After fatal-error state is set, name lookup performed by the late attribute parser can silently fail. The parser then wraps successful sub-expressions in a `RecoveryExpr`, which makes the whole attribute condition `<dependent type>` / value-dependent. The condition is attached to the (non-template) declaration as-is, so on a later call to that declaration `Sema::diagnoseArgDependentDiagnoseIfAttrs` calls `Expr::EvaluateWithSubstitution` on a value-dependent condition and the assertion in `clang/lib/AST/ExprConstant.cpp` fires.
`Sema::CheckEnableIf` already guards the same call with `isValueDependent()`; the `diagnose_if` counterpart never did, but the cleaner fix is to keep the malformed condition out of the AST entirely.
## Test plan
- New regression test `clang/test/SemaCXX/diagnose-if-fatal-error.cpp` reproduces #197625 with `-ferror-limit 19` and verifies the compiler no longer aborts.
- Existing `clang/test/SemaCXX/diagnose_if.cpp` continues to pass.
## Tool-use disclosure
This patch was developed with the assistance of an AI coding agent (Claude Code, Anthropic). The agent performed the regression bisection on the reporter's input, identified the RecoveryExpr-from-fatal-error mechanism, drafted the fix and the regression test, and ran the relevant lit subsets locally. All code, the commit message, and this PR description were reviewed and approved by the human author before submission.
Fixes #197625
>From c0349bf351c2715ef1cc496353f4693aa706a4a8 Mon Sep 17 00:00:00 2001
From: Soowon Jeong <soowon1106 at gmail.com>
Date: Thu, 14 May 2026 19:14:01 +0900
Subject: [PATCH] [clang][Parse] Skip late-parsed attributes after a fatal
error
When the diagnostic engine has entered a fatal-error state (e.g. after
-ferror-limit is exceeded), name lookup performed by the late attribute
parser can silently fail, leaving a RecoveryExpr in the attribute's
condition. Attaching that value-dependent condition to a non-template
declaration later trips an assertion in Expr::EvaluateWithSubstitution
when the declaration is referenced (e.g. via diagnose_if).
Bail out of ParseLexedAttribute when a fatal error has occurred: drain
the cached tokens and return without attaching the half-formed
attribute.
---
clang/lib/Parse/ParseCXXInlineMethods.cpp | 14 ++++++
.../test/SemaCXX/diagnose-if-fatal-error.cpp | 46 +++++++++++++++++++
2 files changed, 60 insertions(+)
create mode 100644 clang/test/SemaCXX/diagnose-if-fatal-error.cpp
diff --git a/clang/lib/Parse/ParseCXXInlineMethods.cpp b/clang/lib/Parse/ParseCXXInlineMethods.cpp
index 6189c854e5fbf..e78e3e6ff0c46 100644
--- a/clang/lib/Parse/ParseCXXInlineMethods.cpp
+++ b/clang/lib/Parse/ParseCXXInlineMethods.cpp
@@ -757,6 +757,20 @@ void Parser::ParseLexedAttribute(LateParsedAttribute &LA,
ParsedAttributes Attrs(AttrFactory);
+ // If a fatal error has occurred, semantic analysis is no longer reliable
+ // (name lookup may silently fail and wrap references in RecoveryExpr,
+ // producing value-dependent attribute conditions on non-template decls
+ // that later confuse the attribute consumers). Skip the parse and drain
+ // the cached tokens instead. The diagnostics emitted up to this point
+ // already justify failing the compilation.
+ if (Actions.getDiagnostics().hasFatalErrorOccurred()) {
+ while (Tok.isNot(tok::eof))
+ ConsumeAnyToken();
+ if (Tok.is(tok::eof) && Tok.getEofData() == AttrEnd.getEofData())
+ ConsumeAnyToken();
+ return;
+ }
+
if (LA.Decls.size() > 0) {
Decl *D = LA.Decls[0];
NamedDecl *ND = dyn_cast<NamedDecl>(D);
diff --git a/clang/test/SemaCXX/diagnose-if-fatal-error.cpp b/clang/test/SemaCXX/diagnose-if-fatal-error.cpp
new file mode 100644
index 0000000000000..57dea815e9ac7
--- /dev/null
+++ b/clang/test/SemaCXX/diagnose-if-fatal-error.cpp
@@ -0,0 +1,46 @@
+// RUN: %clang_cc1 -fsyntax-only -std=c++17 -ferror-limit 19 -verify %s
+
+// After -ferror-limit is exceeded the parser enters a fatal-error state.
+// Late-parsed attribute conditions must not be attached to the declaration
+// in that state: name lookup of the condition's sub-expressions may fail
+// silently and wrap the result in a RecoveryExpr, producing a
+// value-dependent condition on a non-template function that later confuses
+// the diagnose_if consumer (Expr::EvaluateWithSubstitution asserts on
+// value-dependent expressions).
+// We test that we do not crash in such cases (#197625).
+
+#define _diagnose_if(...) __attribute__((diagnose_if(__VA_ARGS__)))
+
+using size_t = decltype(sizeof(0));
+namespace std {
+template <typename T>
+struct initializer_list {
+ const T *p;
+ size_t s;
+ constexpr size_t size() const { return s; }
+};
+} // namespace std
+
+template <typename T>
+struct E {
+ void f(int i) _diagnose_if(i, "bad i", "error"); // expected-note 19 {{from 'diagnose_if' attribute on 'f'}}
+};
+void blast() {
+ E<int> e;
+ e.f(1); e.f(1); e.f(1); e.f(1); e.f(1); // expected-error 5 {{bad i}}
+ e.f(1); e.f(1); e.f(1); e.f(1); e.f(1); // expected-error 5 {{bad i}}
+ e.f(1); e.f(1); e.f(1); e.f(1); e.f(1); // expected-error 5 {{bad i}}
+ e.f(1); e.f(1); e.f(1); e.f(1); e.f(1); // expected-error 4 {{bad i}} expected-error@* {{too many errors emitted}}
+}
+
+struct Foo {
+ Foo(std::initializer_list<int> l)
+ _diagnose_if(l.size() == 1, "first", "warning")
+ _diagnose_if(l.size() == 2, "second", "error") {}
+};
+
+void run() {
+ Foo{std::initializer_list<int>{}};
+ Foo{std::initializer_list<int>{1}};
+ Foo{std::initializer_list<int>{1, 2}};
+}
More information about the cfe-commits
mailing list