Re: [PATCH] c++: Fix up constexpr catching of pointer-to-members [PR126918]

Jason Merrill <[email protected]>
Newsgroups gmane.comp.gcc.patches
Message-ID <[email protected]>
On 8/18/26 10:47 AM, Jakub Jelinek wrote:
> Hi!
> 
> The following patch fixes various problems with constexpr EH related to
> pointer-to-member types.
> http://eel.is/c++draft/except.handle#3.3
> and
> http://eel.is/c++draft/except.handle#3.4
> have some cases where the exception object type and handler type can be
> different.
> If handler type is a pointer type, this is implemented by __cxa_begin_catch
> returning the pointer by value rather than reference (pointer to the value
> actually), so the cast is done during returning the pointer.
> Unfortunately, for pointer-to-member types (both data and function) that is
> not the case, __cxa_begin_catch in that case returns pointer to the
> pointer-to-member type.
> At runtime libsupc++/pbase_type_info.cc (__do_catch) deals with the
> [except.handle]/(3.4) cases
>        else if (typeid (*this) == typeid(__pointer_to_member_type_info))
>          {
>            if (__pointee->__is_function_p ())
>              {
>                using pmf_type = void (__pbase_type_info::*)();
>                static const pmf_type pmf = nullptr;
>                *thr_obj = const_cast<pmf_type*>(&pmf);
>                return true;
>              }
>            else
>              {
>                using pm_type = int __pbase_type_info::*;
>                static const pm_type pm = nullptr;
>                *thr_obj = const_cast<pm_type*>(&pm);
>                return true;
>              }
>          }
> and [except.handle]/(3.3) cases are done presumably by strict aliasing
> violation not visible to the compiler (runtime library returns address of
> the exception type and compiler emitted code reads it using different
> effective type).  E.g. for the foo case in the first testcase, the IL looks
> like
>     <<< Unknown tree: handler
> 
>        {
>          <<< Unknown tree: offset_type >>> p = *(<<< Unknown tree: offset_type >>> &) D.2709;
> 
>          try
>            {
>                          register <<< Unknown tree: offset_type >>> * D.2709;
>              <<cleanup_point <<< Unknown tree: expr_stmt
>                (void) (D.2709 = (<<< Unknown tree: offset_type >>> *) __cxa_begin_catch (__builtin_eh_pointer (0))) >>>>>;
>                          <<< Unknown tree: offset_type >>> p = *(<<< Unknown tree: offset_type >>> &) D.2709;
>              return <retval> = p;
>            }
>          finally
>            {
>              __cxa_end_catch ();
>            }
>        } >>>
> so my attempt to use a TARGET_EXPR for the temporary didn't work,
> there is a CLEANUP_POINT_EXPR wrapping the D.2709 = __cxa_begin_catch (...)
> assignment created from cp_finish_decl that would be quite hard to avoid (we
> already do that through ugly hacks for structured binding CWG2867 support,
> but it has consequences for e.g. coroutines etc.).
> The following patch instead creates special temporaries (as if heap
> allocated but more efficiently) that live just from the __cxa_begin_catch
> (or __cxa_get_exception_ptr) time to the corresponding __cxa_end_catch.
> They are stored in the caught_exception vector because attaching them
> as DECL_CHAIN of the exception object looks unsafe to me, the current
> exception could be queried and thrown again before the catch parameter
> goes out of scope.
> 
> Also, I had to tweak handler_match_for_exception_type, because it only
> handled pointer-to-data-member and not all pointer-to-member types
> that [except.handle]/3 requires.
> 
> So far lightly tested on x86_64-linux, ok for trunk if it passes full
> bootstrap/regtest?
> 
> 2026-08-18  Jakub Jelinek  <[email protected]>
> 
> 	PR c++/126918
> 	* constexpr.cc (class constexpr_global_ctx): Extend description of
> 	caught_exceptions vector.
> 	(cxx_eval_cxa_builtin_fn): When catching a pointer-to-member and
> 	the current exception is nullptr or pointer-to-member with different
> 	type, create a temporary, initialize it from the exception value and
> 	return address of it.  Make sure to free these temporaries at
> 	__cxa_end_catch time.
> 	* call.cc (handler_match_for_exception_type): Allow NULLPTR_TYPE
> 	exception type or different pointer-to-member type even for
> 	TYPE_PTRMEMFUNC_P types, not just TYPE_PTRDATAMEM_P types.
> 
> 	* g++.dg/cpp26/constexpr-eh24.C: New test.
> 	* g++.dg/cpp26/constexpr-eh25.C: New test.
> 
> --- gcc/cp/constexpr.cc.jj	2026-08-17 10:00:21.701856524 +0200
> +++ gcc/cp/constexpr.cc	2026-08-18 16:15:48.766507568 +0200
> @@ -1193,7 +1193,11 @@ public:
>     auto_vec<tree, 16> heap_vars;
>     /* Vector of caught exceptions, including exceptions still not active at
>        the start of a handler (those are immediately followed up by HANDLER_TYPE
> -     until __cxa_begin_catch finishes).  */
> +     until __cxa_begin_catch finishes).  If __cxa_begin_catch or
> +     __cxa_get_exception_ptr need to create temporaries, the VAR_DECL of the
> +     exception object is wrapped in the vector into a TREE_LIST where
> +     TREE_VALUE of it is the VAR_DECL of the exception object and TREE_PURPOSE
> +     one of the temporaries, others chained through DECL_CHAIN.  */
>     auto_vec<tree, 2> caught_exceptions;
>     /* Cleanups that need to be evaluated at the end of CLEANUP_POINT_EXPR.  */
>     vec<tree> *cleanups;
> @@ -1943,10 +1947,13 @@ cxx_eval_cxa_builtin_fn (const constexpr
>   	     VAR_DECL after __cxa_begin_catch serves as the current exception
>   	     and is then popped in __cxa_end_catch evaluation.  */
>   	  tree handler_type = ctx->global->caught_exceptions.last ();
> -	  if (handler_type && VAR_P (handler_type))
> +	  if (handler_type && (VAR_P (handler_type)
> +			       || TREE_CODE (handler_type) == TREE_LIST))

It would be nice to abstract all the var/list handling for clarity, but 
since it's all local to cxx_eval_cxa_builtin_fn I'm not going to insist 
on it.  OK.

>   	    goto no_caught_exceptions;
>   	  unsigned idx = ctx->global->caught_exceptions.length () - 2;
>   	  arg = ctx->global->caught_exceptions[idx];
> +	  if (TREE_CODE (arg) == TREE_LIST)
> +	    arg = TREE_VALUE (arg);
>   	  gcc_assert (VAR_P (arg));
>   	  if (kind == CXA_BEGIN_CATCH)
>   	    {
> @@ -1974,12 +1981,14 @@ cxx_eval_cxa_builtin_fn (const constexpr
>   	    {
>   	      /* Used for catch of a non-pointer type.  */
>   	      tree exc_type = strip_array_types (TREE_TYPE (arg));
> -	      tree exc_ptr_type = build_pointer_type (exc_type);
> -	      arg = build_fold_addr_expr_with_type (arg, exc_ptr_type);
> -	      if (CLASS_TYPE_P (handler_type))
> +	      if (TYPE_PTRMEM_P (handler_type)
> +		  && !same_type_ignoring_top_level_qualifiers_p
> +				(handler_type, exc_type))
>   		{
> -		  tree ptr_type = build_pointer_type (handler_type);
> -		  arg = cp_convert (ptr_type, arg,
> +		  if (TREE_CODE (TREE_TYPE (arg)) == ARRAY_TYPE)
> +		    arg = build4 (ARRAY_REF, TREE_TYPE (TREE_TYPE (arg)), arg,
> +				  size_zero_node, NULL_TREE, NULL_TREE);
> +		  arg = cp_convert (handler_type, arg,
>   				    ctx->quiet ? tf_none
>   				    : tf_warning_or_error);
>   		  if (arg == error_mark_node)
> @@ -1987,6 +1996,42 @@ cxx_eval_cxa_builtin_fn (const constexpr
>   		      *non_constant_p = true;
>   		      return call;
>   		    }
> +		  tree var = build_decl (loc, VAR_DECL, heap_identifier,
> +					 handler_type);
> +		  DECL_ARTIFICIAL (var) = 1;
> +		  ctx->global->heap_vars.safe_push (var);
> +		  ctx->global->put_value (var, NULL_TREE);
> +		  tree init = build2_loc (loc, INIT_EXPR, handler_type,
> +					  var, arg);
> +		  arg = ctx->global->caught_exceptions[idx];
> +		  if (TREE_CODE (arg) == TREE_LIST)
> +		    {
> +		      DECL_CHAIN (var) = TREE_PURPOSE (arg);
> +		      TREE_PURPOSE (arg) = var;
> +		    }
> +		  else
> +		    ctx->global->caught_exceptions[idx]
> +		      = build_tree_list (var, arg);
> +		  arg = cp_build_addr_expr (var, tf_none);
> +		  arg = build2_loc (loc, COMPOUND_EXPR, TREE_TYPE (arg),
> +				    init, arg);
> +		}
> +	      else
> +		{
> +		  tree exc_ptr_type = build_pointer_type (exc_type);
> +		  arg = build_fold_addr_expr_with_type (arg, exc_ptr_type);
> +		  if (CLASS_TYPE_P (handler_type))
> +		    {
> +		      tree ptr_type = build_pointer_type (handler_type);
> +		      arg = cp_convert (ptr_type, arg,
> +					ctx->quiet ? tf_none
> +					: tf_warning_or_error);
> +		      if (arg == error_mark_node)
> +			{
> +			  *non_constant_p = true;
> +			  return call;
> +			}
> +		    }
>   		}
>   	    }
>   	  return cxx_eval_constant_expression (ctx, arg, vc_prvalue,
> @@ -2070,8 +2115,20 @@ cxx_eval_cxa_builtin_fn (const constexpr
>         else
>   	{
>   	  arg = ctx->global->caught_exceptions.pop ();
> -	  if (arg == NULL_TREE || !VAR_P (arg))
> +	  if (arg == NULL_TREE
> +	      || (!VAR_P (arg) && TREE_CODE (arg) != TREE_LIST))
>   	    goto no_active_exc;
> +	  if (TREE_CODE (arg) == TREE_LIST)
> +	    {
> +	      for (tree aux = TREE_PURPOSE (arg); aux; aux = DECL_CHAIN (aux))
> +		{
> +		  DECL_NAME (aux) = heap_deleted_identifier;
> +		  ctx->global->destroy_value (aux);
> +		  ctx->global->heap_dealloc_count++;
> +		}
> +	      arg = TREE_VALUE (arg);
> +	    }
> +	  DECL_CHAIN (arg) = NULL_TREE;
>   	free_except:
>   	  DECL_EXCEPTION_REFCOUNT (arg)
>   	    = size_binop (MINUS_EXPR, DECL_EXCEPTION_REFCOUNT (arg),
> @@ -2107,7 +2164,7 @@ cxx_eval_cxa_builtin_fn (const constexpr
>   	goto invalid_nargs;
>         unsigned idx;
>         FOR_EACH_VEC_ELT_REVERSE (ctx->global->caught_exceptions, idx, arg)
> -	if (arg == NULL_TREE || !VAR_P (arg))
> +	if (arg == NULL_TREE || (!VAR_P (arg) && TREE_CODE (arg) != TREE_LIST))
>   	  --idx;
>   	else
>   	  break;
> @@ -2119,6 +2176,8 @@ cxx_eval_cxa_builtin_fn (const constexpr
>   	  *non_constant_p = true;
>   	  return call;
>   	}
> +      if (TREE_CODE (arg) == TREE_LIST)
> +	arg = TREE_VALUE (arg);
>         DECL_EXCEPTION_REFCOUNT (arg)
>   	= size_binop (PLUS_EXPR, DECL_EXCEPTION_REFCOUNT (arg), size_one_node);
>         ++ctx->global->uncaught_exceptions;
> @@ -2234,7 +2293,8 @@ cxx_eval_cxa_builtin_fn (const constexpr
>   	      return call;
>   	    }
>   	  FOR_EACH_VEC_ELT_REVERSE (ctx->global->caught_exceptions, idx, arg)
> -	    if (arg == NULL_TREE || !VAR_P (arg))
> +	    if (arg == NULL_TREE
> +		|| (!VAR_P (arg) && TREE_CODE (arg) != TREE_LIST))
>   	      --idx;
>   	    else
>   	      break;
> @@ -2253,6 +2313,8 @@ cxx_eval_cxa_builtin_fn (const constexpr
>   	    arg = build_zero_cst (TREE_TYPE (fld));
>   	  else
>   	    {
> +	      if (TREE_CODE (arg) == TREE_LIST)
> +		arg = TREE_VALUE (arg);
>   	      DECL_EXCEPTION_REFCOUNT (arg)
>   		= size_binop (PLUS_EXPR, DECL_EXCEPTION_REFCOUNT (arg),
>   			      size_one_node);
> --- gcc/cp/call.cc.jj	2026-07-27 09:03:58.522469707 +0200
> +++ gcc/cp/call.cc	2026-08-18 14:40:55.163014718 +0200
> @@ -1749,13 +1749,15 @@ handler_match_for_exception_type (tree h
>         if (binfo && binfo != error_mark_node)
>   	return true;
>       }
> -  if (TYPE_PTR_P (handler_type) || TYPE_PTRDATAMEM_P (handler_type))
> +  if (TYPE_PTR_P (handler_type) || TYPE_PTRMEM_P (handler_type))
>       {
>         if (TREE_CODE (except_type) == NULLPTR_TYPE)
>   	return true;
>         if ((TYPE_PTR_P (handler_type) && TYPE_PTR_P (except_type))
>   	  || (TYPE_PTRDATAMEM_P (handler_type)
> -	      && TYPE_PTRDATAMEM_P (except_type)))
> +	      && TYPE_PTRDATAMEM_P (except_type))
> +	  || (TYPE_PTRMEMFUNC_P (handler_type)
> + 	      && TYPE_PTRMEMFUNC_P (except_type)))
>   	{
>   	  conversion *conv
>   	    = standard_conversion (handler_type, except_type, NULL_TREE,
> --- gcc/testsuite/g++.dg/cpp26/constexpr-eh24.C.jj	2026-08-18 15:22:14.679284969 +0200
> +++ gcc/testsuite/g++.dg/cpp26/constexpr-eh24.C	2026-08-18 15:33:27.686532371 +0200
> @@ -0,0 +1,164 @@
> +// PR c++/126918
> +// { dg-do compile { target c++26 } }
> +
> +struct S {
> +  int m, n;
> +  int foo (int x) { return x + 42; }
> +  int bar (int x) noexcept { return x + 42; }
> +};
> +
> +constexpr int S::*
> +foo (bool x)
> +{
> +  if (x)
> +    return nullptr;
> +  try
> +    {
> +      throw nullptr;
> +    }
> +  catch (int S::*p)
> +    {
> +      return p;
> +    }
> +  return &S::m;
> +}
> +
> +constexpr int S::*
> +bar (bool x)
> +{
> +  if (x)
> +    return nullptr;
> +  try
> +    {
> +      throw nullptr;
> +    }
> +  catch (int S::*const &p)
> +    {
> +      return p;
> +    }
> +  return &S::m;
> +}
> +
> +constexpr const int S::*
> +baz (bool x)
> +{
> +  if (x)
> +    return nullptr;
> +  try
> +    {
> +      throw &S::m;
> +    }
> +  catch (const int S::*p)
> +    {
> +      return p;
> +    }
> +  return nullptr;
> +}
> +
> +constexpr const int S::*
> +qux (bool x)
> +{
> +  if (x)
> +    return nullptr;
> +  try
> +    {
> +      throw &S::m;
> +    }
> +  catch (const int S::*const &p)
> +    {
> +      return p;
> +    }
> +  return nullptr;
> +}
> +
> +using F = int (S::*) (int);
> +using FNE = int (S::*) (int) noexcept;
> +
> +constexpr F
> +corge (bool x)
> +{
> +  if (x)
> +    return nullptr;
> +  try
> +    {
> +      throw nullptr;
> +    }
> +  catch (F p)
> +    {
> +      return p;
> +    }
> +  return &S::foo;
> +}
> +
> +constexpr F
> +garply (bool x)
> +{
> +  if (x)
> +    return nullptr;
> +  try
> +    {
> +      throw nullptr;
> +    }
> +  catch (F const &p)
> +    {
> +      return p;
> +    }
> +  return &S::foo;
> +}
> +
> +constexpr F
> +fred (bool x)
> +{
> +  if (x)
> +    return nullptr;
> +  try
> +    {
> +      FNE fne = &S::bar;
> +      throw fne;
> +    }
> +  catch (F p)
> +    {
> +      return p;
> +    }
> +  return nullptr;
> +}
> +
> +constexpr F
> +waldo (bool x)
> +{
> +  if (x)
> +    return nullptr;
> +  try
> +    {
> +      FNE fne = &S::bar;
> +      throw fne;
> +    }
> +  catch (F const &p)
> +    {
> +      return p;
> +    }
> +  return nullptr;
> +}
> +
> +static_assert (foo (false) == nullptr);
> +static_assert (bar (false) == nullptr);
> +static_assert (baz (false) == &S::m);
> +static_assert (qux (false) == &S::m);
> +static_assert (corge (false) == nullptr);
> +static_assert (garply (false) == nullptr);
> +static_assert (fred (false) == F (&S::bar));
> +static_assert (waldo (false) == F (&S::bar));
> +
> +int
> +main ()
> +{
> +  if (foo (false) != nullptr
> +      || bar (false) != nullptr
> +      || baz (false) != &S::m
> +      || qux (false) != &S::m
> +      || corge (false) != nullptr
> +      || garply (false) != nullptr
> +      || fred (false) != F (&S::bar)
> +      || waldo (false) != F (&S::bar))
> +    __builtin_abort ();
> +}
> --- gcc/testsuite/g++.dg/cpp26/constexpr-eh25.C.jj	2026-08-18 15:28:28.969414106 +0200
> +++ gcc/testsuite/g++.dg/cpp26/constexpr-eh25.C	2026-08-18 16:02:25.777943048 +0200
> @@ -0,0 +1,89 @@
> +// PR c++/126918
> +// { dg-do compile { target c++26 } }
> +
> +struct S {
> +  int m, n;
> +  int foo (int x) { return x + 42; }
> +  int bar (int x) noexcept { return x + 42; }
> +};
> +
> +constexpr int S::*const &
> +foo (bool x)
> +{
> +  static constexpr int S::*sm = &S::m;
> +  if (x)
> +    return sm;
> +  try
> +    {
> +      throw nullptr;
> +    }
> +  catch (int S::*const &p)
> +    {
> +      return p;
> +    }
> +  return sm;
> +}
> +
> +constexpr const int S::*const &
> +bar (bool x)
> +{
> +  static constexpr const int S::*np = nullptr;
> +  if (x)
> +    return np;
> +  try
> +    {
> +      throw &S::m;
> +    }
> +  catch (const int S::*const &p)
> +    {
> +      return p;
> +    }
> +  return np;
> +}
> +
> +using F = int (S::*) (int);
> +using FNE = int (S::*) (int) noexcept;
> +
> +constexpr F const &
> +baz (bool x)
> +{
> +  static constexpr F f = &S::foo;
> +  if (x)
> +    return f;
> +  try
> +    {
> +      throw nullptr;
> +    }
> +  catch (F const &p)
> +    {
> +      return p;
> +    }
> +  return f;
> +}
> +
> +constexpr F const &
> +qux (bool x)
> +{
> +  static constexpr F np = nullptr;
> +  if (x)
> +    return np;
> +  try
> +    {
> +      FNE fne = &S::bar;
> +      throw fne;
> +    }
> +  catch (F const &p)
> +    {
> +      return p;
> +    }
> +  return np;
> +}
> +
> +static_assert (foo (false) == nullptr);		// { dg-error "non-constant condition for static assertion" }
> +						// { dg-error "use of allocated storage after deallocation in a constant expression" "" { target *-*-* } .-1 }
> +static_assert (bar (false) == &S::m);		// { dg-error "non-constant condition for static assertion" }
> +						// { dg-error "use of allocated storage after deallocation in a constant expression" "" { target *-*-* } .-1 }
> +static_assert (baz (false) == nullptr);		// { dg-error "non-constant condition for static assertion" }
> +						// { dg-error "use of allocated storage after deallocation in a constant expression" "" { target *-*-* } .-1 }
> +static_assert (qux (false) == F (&S::bar));	// { dg-error "non-constant condition for static assertion" }
> +						// { dg-error "use of allocated storage after deallocation in a constant expression" "" { target *-*-* } .-1 }
> 
> 	Jakub
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.