On Wed, Jul 15, 2026 at 10:49:23PM -0400, Jason Merrill wrote: > > maybe_instantiate_noexcept is guarded by > > /* Don't instantiate a noexcept-specification from template context. */ > > if (processing_template_decl > > && (!flag_noexcept_type || type_dependent_expression_p (fn))) > > return true; > > So, shall that be > > if (processing_template_decl > > && (!flag_noexcept_type > > || dependent_type_p (TREE_TYPE (fn)) > > || type_dependent_expression_p (fn))) > > return true; > > or shall I use that check in maybe_diagnose_deallocation_noexcept_false? > > operator delete has to return void, so maybe I don't need to be afraid > > about undeduced_auto_decl. > > I think the problem is more that calling maybe_instantiate_noexcept is wrong > here; this was not added to the list in > https://eel.is/c++draft/except#spec-13 > > Anyway, isn't grokfndecl too early? I mean for cases like > > struct E { > > static void operator delete (void *) noexcept (C) {} > > static constexpr bool C = false; > > }; > > which I should add to the testsuite. When grokfndecl is called, > > TYPE_RAISES_EXCEPTION (TREE_TYPE (fn)) is > > <tree_list 0x7fffe99a4f28 > > purpose <deferred_parse 0x7fffe99bbe70>> > > and so nothing is reported. > > This is only changed in fixup_deferred_exception_variants. > > Or should cp_parser_class_specifier call > > maybe_diagnose_deallocation_noexcept_false again if it parses it? > > Yes, and in maybe_instantiate_noexcept, rather than tsubst_function_decl.
Ok, I've done it that way. Note the disadvantage is that we don't diagnose when somebody just instantiates template containing a deallocation method with dependent noexcept and doesn't actually odr-use (etc., the https://eel.is/c++draft/except#spec-13 list) the deallocation function, but if that is ok, then this patch does that. The testsuite now includes one case for the deferred parsing of exception specifier (to test cp_parser_class_specifier), and cases where the deallocation function with dependent noexcept isn't and is odr-used, and case where a template has deallocation method with non-dependent noexcept. 2026-07-16 Jakub Jelinek <[email protected]> PR c++/125833 * cp-tree.h: Implement C++29 P3424R2 - Deallocation Functions with Throwing Exception Specification Are Ill-formed. (maybe_diagnose_deallocation_noexcept_false): Declare. * decl.cc (maybe_diagnose_deallocation_noexcept_false): New function. (grokfndecl): Call it. * pt.cc (maybe_instantiate_noexcept): Likewise. * parser.cc (cp_parser_class_specifier): Likewise. * g++.dg/cpp0x/dealloc2.C: Expect extra diagnostics in C++29. * g++.dg/cpp29/dealloc1.C: New test. --- gcc/cp/cp-tree.h.jj 2026-07-15 10:26:19.552203498 +0200 +++ gcc/cp/cp-tree.h 2026-07-16 14:11:24.301372159 +0200 @@ -7651,6 +7651,7 @@ extern tree start_decl (const cp_decl extern void start_decl_1 (tree, bool); extern bool check_array_initializer (tree, tree, tree); extern void omp_declare_variant_finalize (tree, tree); +extern void maybe_diagnose_deallocation_noexcept_false (tree); struct cp_decomp { tree decl; unsigned int count; }; extern void cp_finish_decl (tree, tree, bool, tree, int, cp_decomp * = nullptr); extern tree lookup_decomp_type (tree); --- gcc/cp/decl.cc.jj 2026-07-15 10:26:19.554203473 +0200 +++ gcc/cp/decl.cc 2026-07-16 14:11:57.989947721 +0200 @@ -9430,6 +9430,25 @@ omp_declare_variant_finalize (tree decl, } } +/* [basic.stc.dynamic.deallocation]/3 - A deallocation function shall not + have a potentially throwing exception specification. */ + +void +maybe_diagnose_deallocation_noexcept_false (tree decl) +{ + if (cxx_dialect >= cxx29 + && DECL_NAME (decl) + && IDENTIFIER_NEWDEL_OP_P (DECL_NAME (decl)) + && !IDENTIFIER_NEW_OP_P (DECL_NAME (decl))) + { + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); + if (spec && spec == noexcept_false_spec) + error_at (DECL_SOURCE_LOCATION (decl), + "deallocation function %qD declared possibly throwing", + decl); + } +} + static void cp_maybe_mangle_decomp (tree, cp_decomp *); /* Finish processing of a declaration; @@ -12621,6 +12640,10 @@ grokfndecl (tree ctype, } } + /* [basic.stc.dynamic.deallocation]/3 - A deallocation function shall not + have a potentially throwing exception specification. */ + maybe_diagnose_deallocation_noexcept_false (decl); + /* Caller will do the rest of this. */ if (check < 0) { --- gcc/cp/pt.cc.jj 2026-07-15 10:26:19.556203447 +0200 +++ gcc/cp/pt.cc 2026-07-16 15:41:18.507170923 +0200 @@ -28802,6 +28802,10 @@ maybe_instantiate_noexcept (tree fn, tsu if (orig_fn) TREE_TYPE (orig_fn) = TREE_TYPE (fn); + /* [basic.stc.dynamic.deallocation]/3 - A deallocation function shall not + have a potentially throwing exception specification. */ + maybe_diagnose_deallocation_noexcept_false (fn); + return true; } --- gcc/cp/parser.cc.jj 2026-07-15 17:41:44.777718295 +0200 +++ gcc/cp/parser.cc 2026-07-16 14:36:23.588496699 +0200 @@ -30135,6 +30135,10 @@ cp_parser_class_specifier (cp_parser* pa /* Remove any template parameters from the symbol table. */ maybe_end_member_template_processing (); + + /* [basic.stc.dynamic.deallocation]/3 - A deallocation function + shall not have a potentially throwing exception specification. */ + maybe_diagnose_deallocation_noexcept_false (decl); } vec_safe_truncate (unparsed_noexcepts, 0); --- gcc/testsuite/g++.dg/cpp0x/dealloc2.C.jj 2026-07-15 10:26:19.556203447 +0200 +++ gcc/testsuite/g++.dg/cpp0x/dealloc2.C 2026-07-16 14:11:24.308433550 +0200 @@ -3,6 +3,7 @@ struct A {}; void operator delete (void *, A); void operator delete (void *, A) noexcept (false); // { dg-error "declaration of 'void operator delete\\\(void\\\*, A\\\) noexcept \\\(false\\\)' has a different exception specifier" } +// { dg-error "deallocation function 'void operator delete\\\(void\\\*, A\\\)' declared possibly throwing" "" { target c++29 } .-1 } struct B {}; -void operator delete (void *, B) noexcept (false); +void operator delete (void *, B) noexcept (false); // { dg-error "deallocation function 'void operator delete\\\(void\\\*, B\\\)' declared possibly throwing" "" { target c++29 } } void operator delete (void *, B); // { dg-error "declaration of 'void operator delete\\\(void\\\*, B\\\) noexcept' has a different exception specifier" } --- gcc/testsuite/g++.dg/cpp29/dealloc1.C.jj 2026-07-16 14:11:24.308624193 +0200 +++ gcc/testsuite/g++.dg/cpp29/dealloc1.C 2026-07-16 15:26:04.013705590 +0200 @@ -0,0 +1,45 @@ +// P3424R2 - Deallocation Functions with Throwing Exception Specification Are Ill-formed +// { dg-do compile { target c++11 } } + +struct A {}; +struct B {}; +void operator delete (void *, A) noexcept (false); // { dg-error "deallocation function 'void operator delete\\\(void\\\*, A\\\)' declared possibly throwing" "" { target c++29 } } +void operator delete (void *, B) noexcept (false) {} // { dg-error "deallocation function 'void operator delete\\\(void\\\*, B\\\)' declared possibly throwing" "" { target c++29 } } +void operator delete[] (void *, A) noexcept (false); // { dg-error "deallocation function 'void operator delete \\\[\\\]\\\(void\\\*, A\\\)' declared possibly throwing" "" { target c++29 } } +void operator delete[] (void *, B) noexcept (false) {} // { dg-error "deallocation function 'void operator delete \\\[\\\]\\\(void\\\*, B\\\)' declared possibly throwing" "" { target c++29 } } +template <bool T, bool U> +struct C { + static void operator delete (void *) noexcept (T); // { dg-error "deallocation function 'static void C<T, U>::operator delete\\\(void\\\*\\\) \\\[with bool T = false; bool U = true\\\]' declared possibly throwing" "" { target c++29 } } + static void operator delete[] (void *) noexcept (T); // { dg-error "deallocation function 'static void C<T, U>::operator delete \\\[\\\]\\\(void\\\*\\\) \\\[with bool T = false; bool U = true\\\]' declared possibly throwing" "" { target c++29 } } +}; +template <bool T, bool U> +struct D { + static void operator delete (void *) noexcept (T) {} // { dg-error "deallocation function 'static void D<T, U>::operator delete\\\(void\\\*\\\) \\\[with bool T = false; bool U = true\\\]' declared possibly throwing" "" { target c++29 } } + static void operator delete[] (void *) noexcept (T) {}// { dg-error "deallocation function 'static void D<T, U>::operator delete \\\[\\\]\\\(void\\\*\\\) \\\[with bool T = false; bool U = true\\\]' declared possibly throwing" "" { target c++29 } } +}; +C <false, false> a; +C <true, false> b; +D <false, false> c; +D <true, false> d; +auto e = &C <false, true>::operator delete; +auto f = &C <true, true>::operator delete; +auto g = &D <false, true>::operator delete; +auto h = &D <true, true>::operator delete; +auto i = &C <false, true>::operator delete[]; +auto j = &C <true, true>::operator delete[]; +auto k = &D <false, true>::operator delete[]; +auto l = &D <true, true>::operator delete[]; +struct E { + static void operator delete (void *) noexcept (C) {} // { dg-error "deallocation function 'static void E::operator delete\\\(void\\\*\\\)' declared possibly throwing" "" { target c++29 } } + static constexpr bool C = false; +}; +template <int N> +struct F { + static void operator delete (void *) noexcept (false); // { dg-error "deallocation function 'static void F<N>::operator delete\\\(void\\\*\\\)' declared possibly throwing" "" { target c++29 } } + static void operator delete[] (void *) noexcept (false); // { dg-error "deallocation function 'static void F<N>::operator delete \\\[\\\]\\\(void\\\*\\\)' declared possibly throwing" "" { target c++29 } } +}; +template <int N> +struct G { + static void operator delete (void *) noexcept (false) {} // { dg-error "deallocation function 'static void G<N>::operator delete\\\(void\\\*\\\)' declared possibly throwing" "" { target c++29 } } + static void operator delete[] (void *) noexcept (false) {} // { dg-error "deallocation function 'static void G<N>::operator delete \\\[\\\]\\\(void\\\*\\\)' declared possibly throwing" "" { target c++29 } } +}; Jakub
