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
>