c++, v3: Implement C++29 P3424R2 - Deallocation Functions with Throwing Exception Specification Are Ill-formed [PR125833]

Message ID aljwO5DFhOF1gXAe@tucnak
State New
Headers
Series c++, v3: Implement C++29 P3424R2 - Deallocation Functions with Throwing Exception Specification Are Ill-formed [PR125833] |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-arm-bootstrap fail Patch failed to apply

Commit Message

Jakub Jelinek July 16, 2026, 2:52 p.m. UTC
  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  <jakub@redhat.com>

	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.



	Jakub
  

Comments

Jason Merrill July 16, 2026, 3:09 p.m. UTC | #1
On 7/16/26 10:52 AM, Jakub Jelinek wrote:
> 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.

OK, thanks.

> 2026-07-16  Jakub Jelinek  <jakub@redhat.com>
> 
> 	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
>
  

Patch

--- 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 } }
+};