On 9/2/24 1:49 PM, Jakub Jelinek wrote:
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.

If there are SAVE_EXPRs in FUNCTION, why is TREE_SIDE_EFFECTS false?

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).

It seems desirable to only do the bounds-checking once.

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


Reply via email to