From 5c5c2d0f21935270897454527af98be6506df75a Mon Sep 17 00:00:00 2001 From: Akash Manna Date: Tue, 25 Aug 2026 20:49:51 +0530 Subject: [PATCH] [Clang] Fix crash on expansion-init-list elements that need cleanups The parser wrapped the syntactic expansion-init-list in an ExprWithCleanups whenever an element needed cleanups (e.g. a temporary bound to a reference parameter). The list has no type, so the wrapper had none either, and ActOnCXXExpansionStmtPattern no longer recognised it as an init list and dereferenced the null type. Discard those cleanups instead: the elements are only evaluated as the initializer of the expansion variable in each expansion, where they are rebuilt anyway. Do the same when building the dependent CXXExpansionSelectExpr so it can't get wrapped during instantiation either, which HasDependentSize/ComputeExpansionSize don't expect. Fixes #212630 --- clang/docs/ReleaseNotes.md | 6 ++++ clang/lib/Parse/ParseStmt.cpp | 11 +++++-- clang/lib/Sema/SemaExpand.cpp | 6 +++- clang/test/SemaTemplate/GH212630.cpp | 49 ++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 clang/test/SemaTemplate/GH212630.cpp diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 3c6694f510952..c49428a2a95d0 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -531,6 +531,12 @@ features cannot lower the translation-unit ABI level; parameter that follows a parameter pack (e.g. `template S::S(T..., int = 10) {}`). (#GH216211) +- Fixed an assertion failure in an enumerating expansion statement + (`template for`) when an element of the expansion-init-list needed cleanups, + e.g. a temporary bound to a reference parameter such as `{g(1), g(2)}` with + `int g(const int&)`, or a temporary of a type with a non-trivial destructor. + (#GH212630) + #### Bug Fixes to AST Handling - Fixed a non-deterministic ordering of unused local typedefs that made diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index 219bcd980e860..9f5a37e840c1b 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -1965,9 +1965,14 @@ void Parser::ParseForRangeInitializerAfterColon(ForRangeInit &FRI, assert(Actions.CurContext->isExpansionStmt()); Sema::ContextRAII CtxGuard(Actions, Actions.CurContext->getParent(), /*NewThis=*/false); - FRI.RangeExpr = - Tok.is(tok::l_brace) ? ParseExpansionInitList() : ParseExpression(); - FRI.RangeExpr = Actions.MaybeCreateExprWithCleanups(FRI.RangeExpr); + if (Tok.is(tok::l_brace)) { + // The elements are only evaluated as the initializer of the expansion + // variable in each expansion, so their cleanups belong there. + FRI.RangeExpr = ParseExpansionInitList(); + Actions.DiscardCleanupsInEvaluationContext(); + } else { + FRI.RangeExpr = Actions.MaybeCreateExprWithCleanups(ParseExpression()); + } } else if (Tok.is(tok::l_brace)) { FRI.RangeExpr = ParseBraceInitializer(); } else { diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp index 779b7add08344..77e7cb282c5a0 100644 --- a/clang/lib/Sema/SemaExpand.cpp +++ b/clang/lib/Sema/SemaExpand.cpp @@ -589,8 +589,12 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) { } ExprResult Sema::BuildCXXExpansionSelectExpr(InitListExpr *Range, Expr *Idx) { - if (Idx->isValueDependent() || InitListContainsPack(Range)) + if (Idx->isValueDependent() || InitListContainsPack(Range)) { + // The elements are only evaluated by the expansion that selects them, so + // their cleanups must not wrap this expression. + DiscardCleanupsInEvaluationContext(); return new (Context) CXXExpansionSelectExpr(Context, Range, Idx); + } // The index is a DRE to a template parameter; we should never // fail to evaluate it. diff --git a/clang/test/SemaTemplate/GH212630.cpp b/clang/test/SemaTemplate/GH212630.cpp new file mode 100644 index 0000000000000..aab9d9d7fd5ad --- /dev/null +++ b/clang/test/SemaTemplate/GH212630.cpp @@ -0,0 +1,49 @@ +// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify %s +// expected-no-diagnostics + +namespace GH212630 { + +void f(int g(const int&)) { + template for (auto x : {g(1), g(2), g(3)}) + g(0); +} + +struct M { + int m(const int &x) const { return x; } +}; + +int overloaded(const int &); +long overloaded(const long &); + +void related(int (*fp)(const int &), int (&fr)(const int &), M m) { + template for (auto x : {fp(1), fr(2), m.m(3), overloaded(4), overloaded(5L)}) {} +} + +constexpr int h(const int &x) { return x * 2; } + +struct S { + int v; + constexpr S(int v) : v(v) {} + constexpr ~S() {} +}; + +constexpr int direct() { + int sum = 0; + template for (auto x : {h(1), h(2), h(3)}) { sum += x; } + template for (constexpr auto x : {h(1), h(2), h(3)}) { sum += x; } + template for (auto s : {S(1), S(2)}) { sum += s.v; } + return sum; +} +static_assert(direct() == 27); + +// With a pack, the elements are rebuilt when the template is instantiated. +template +constexpr int pack(Ts... ts) { + int sum = 0; + template for (auto x : {h(1), h(ts)...}) { sum += x; } + template for (auto s : {S(ts)...}) { sum += s.v; } + return sum; +} +static_assert(pack(2, 3) == 17); + +} // namespace GH212630