Hi!

The following testcase is miscompiled, because when cp_build_modify_expr
processes assignment into lvalue COND_EXPR, the rhs doesn't have
side-effects and so stabilize_expr doesn't do anything to it, then we
use that rhs in both COND_EXPR branches (but shared) and finally during
genericization we ubsan instrument that rhs, adding some SAVE_EXPRs in there
because we want to evaluate parts of it multiple times.  Unfortunately,
the SAVE_EXPRs will be initialized only in one of the two COND_EXPR branches
and will just use uninitialized temporary in the other one.

Fixed by unsharing the rhs if we do this (but only for ubsan, so that
we don't waste compile time/memory on that when we don't need that).

Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

2019-03-29  Jakub Jelinek  <ja...@redhat.com>

        PR sanitizer/89869
        * typeck.c: Include gimplify.h.
        (cp_build_modify_expr) <case COND_EXPR>: Unshare rhs before using it
        for second time.  Formatting fixes.

        * g++.dg/ubsan/vptr-14.C: New test.

--- gcc/cp/typeck.c.jj  2019-03-22 15:42:02.456993944 +0100
+++ gcc/cp/typeck.c     2019-03-29 11:23:36.823067316 +0100
@@ -40,6 +40,7 @@ along with GCC; see the file COPYING3.
 #include "stringpool.h"
 #include "attribs.h"
 #include "asan.h"
+#include "gimplify.h"
 
 static tree cp_build_addr_expr_strict (tree, tsubst_flags_t);
 static tree cp_build_function_call (tree, tree, tsubst_flags_t);
@@ -8129,8 +8130,6 @@ cp_build_modify_expr (location_t loc, tr
        /* Produce (a ? (b = rhs) : (c = rhs))
           except that the RHS goes through a save-expr
           so the code to compute it is only emitted once.  */
-       tree cond;
-
        if (VOID_TYPE_P (TREE_TYPE (rhs)))
          {
            if (complain & tf_error)
@@ -8145,13 +8144,21 @@ cp_build_modify_expr (location_t loc, tr
        if (!lvalue_or_else (lhs, lv_assign, complain))
          return error_mark_node;
 
-       cond = build_conditional_expr
-         (input_location, TREE_OPERAND (lhs, 0),
-          cp_build_modify_expr (loc, TREE_OPERAND (lhs, 1),
-                                modifycode, rhs, complain),
-          cp_build_modify_expr (loc, TREE_OPERAND (lhs, 2),
-                                modifycode, rhs, complain),
-           complain);
+       tree op1 = cp_build_modify_expr (loc, TREE_OPERAND (lhs, 1),
+                                        modifycode, rhs, complain);
+       /* When sanitizing undefined behavior, even when rhs doesn't need
+          stabilization at this point, the sanitization might add extra
+          SAVE_EXPRs in there and so make sure there is no tree sharing
+          in the rhs, otherwise those SAVE_EXPRs will have initialization
+          only in one of the two branches.  */
+       if (sanitize_flags_p (SANITIZE_UNDEFINED
+                             | SANITIZE_UNDEFINED_NONDEFAULT))
+         rhs = unshare_expr (rhs);
+       tree op2 = cp_build_modify_expr (loc, TREE_OPERAND (lhs, 2),
+                                        modifycode, rhs, complain);
+       tree cond = build_conditional_expr (input_location,
+                                           TREE_OPERAND (lhs, 0), op1, op2,
+                                           complain);
 
        if (cond == error_mark_node)
          return cond;
--- gcc/testsuite/g++.dg/ubsan/vptr-14.C.jj     2019-03-29 11:25:19.634389671 
+0100
+++ gcc/testsuite/g++.dg/ubsan/vptr-14.C        2019-03-29 11:24:37.564076159 
+0100
@@ -0,0 +1,18 @@
+// PR sanitizer/89869
+// { dg-do run }
+// { dg-options "-fsanitize=vptr -fno-sanitize-recover=vptr" }
+
+struct S { S *s = 0; virtual ~S () {} };
+
+void
+foo (S *x, S *y)
+{
+  (x->s ? y : x) = x->s;
+}
+
+int
+main ()
+{
+  S a;
+  foo (&a, 0);
+}

        Jakub

Reply via email to