Hi! The following testcase is miscompiled, because get_member_function_from_ptrfunc emits something like (((FUNCTION.__pfn & 1) != 0) ? ptr + FUNCTION.__delta + FUNCTION.__pfn - 1 : FUNCTION.__pfn) (ptr + FUNCTION.__delta, ...) or so, so FUNCTION tree is used there 5 times. There is if (TREE_SIDE_EFFECTS (function)) function = save_expr (function); but in this case function doesn't have side-effects, just nested ARRAY_REFs. Now, if all the FUNCTION trees would be shared, it would work fine, FUNCTION is evaluated in the first operand of COND_EXPR; but unfortunately that isn't the case, both the BIT_AND_EXPR shortening and conversion to bool done for build_conditional_expr actually unshare_expr that first expression, but none of the other 4 are unshared. With -fsanitize=bounds, .UBSAN_BOUNDS calls are added to the ARRAY_REFs and use SAVE_EXPRs to avoid evaluating the argument multiple times, but because that FUNCTION tree is first used in the second argument of COND_EXPR (i.e. conditionally), the SAVE_EXPR initialization is done just there and then the third argument of COND_EXPR just uses the uninitialized temporary and so does the first argument computation as well.
The following patch fixes it by also unsharing the trees that end up in the third COND_EXPR argument and unsharing what is passed as the first argument too. Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk? Guess another possibility would be to somehow arrange for the BIT_AND_EXPR and NE_EXPR not to do unshare_expr (but that would mean build2 most likely rather than cp_build_binary_op), or force FUNCTION into a SAVE_EXPR whenever it isn't say a tcc_declaration or tcc_constant even if it doesn't have side-effects (but that would mean creating the SAVE_EXPR by hand) or create a TARGET_EXPR instead (force_target_expr). 2024-09-02 Jakub Jelinek <ja...@redhat.com> PR c++/116449 * typeck.cc (get_member_function_from_ptrfunc): Call unshare_expr on *instance_ptrptr and e3. * g++.dg/ubsan/pr116449.C: New test. --- gcc/cp/typeck.cc.jj 2024-07-26 08:34:18.117159928 +0200 +++ gcc/cp/typeck.cc 2024-09-02 12:35:54.254380135 +0200 @@ -4255,8 +4255,11 @@ get_member_function_from_ptrfunc (tree * /* ...and then the delta in the PMF. */ instance_ptr = fold_build_pointer_plus (instance_ptr, delta); - /* Hand back the adjusted 'this' argument to our caller. */ - *instance_ptrptr = instance_ptr; + /* Hand back the adjusted 'this' argument to our caller. + The e1 computation unfortunately can result in unshare_expr + and we need to make sure the delta tree isn't first evaluated + in the COND_EXPR branch. */ + *instance_ptrptr = unshare_expr (instance_ptr); if (nonvirtual) /* Now just return the pointer. */ @@ -4283,6 +4286,9 @@ get_member_function_from_ptrfunc (tree * cp_build_addr_expr (e2, complain)); e2 = fold_convert (TREE_TYPE (e3), e2); + /* As e1 computation can result in unshare_expr, make sure e3 isn't + shared with the e2 trees. */ + e3 = unshare_expr (e3); e1 = build_conditional_expr (input_location, e1, e2, e3, complain); if (e1 == error_mark_node) return error_mark_node; --- gcc/testsuite/g++.dg/ubsan/pr116449.C.jj 2024-09-02 12:34:18.545629851 +0200 +++ gcc/testsuite/g++.dg/ubsan/pr116449.C 2024-09-02 12:31:49.070581617 +0200 @@ -0,0 +1,14 @@ +// PR c++/116449 +// { dg-do compile } +// { dg-options "-O2 -Wall -fsanitize=undefined" } + +struct C { void foo (int); void bar (); int c[16]; }; +typedef void (C::*P) (); +struct D { P d; }; +static D e[1] = { { &C::bar } }; + +void +C::foo (int x) +{ + (this->*e[c[x]].d) (); +} Jakub