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