Hi!

The following patch changes the way destructor calls are sanitized. Currently,
they are sanitized just like any other member calls, transforming `a::~a(this)'
to `a::~a(.UBSAN_NULL(SAVE_EXPR(this)), SAVE_EXPR(this))'. However, this is a
problem for coroutines. In some cases, a destructor call is cloned to another
location, so then the saved expression is not evaluated before the second call
to the destructor.

The patch gets rid of SAVE_EXPRs by creating a temporary variable, so the calls
are transformed to something resembling:
{
  void *ptr;
  ptr = this;
  .UBSAN_NULL(ptr);
  a::~a(ptr);
}
Unfortunately, the implementation is not so straight-forward. Firstly, we do not
want to sanitize again already sanitized call. I accomplish this by creating a
hash set of instrumented expressions on the way to the tree's root. Secondly,
the affected calls are cloned, so I need to explicitly copy the tree node to
change it's argument. I am not sure if the proposed solution is optimal and will
not cause the creation of an exponential number of nodes.

The patch survives bootstrapping and regression testing on x86_64-pc-linux-gnu.
If approved, I would like to note that I do not have write access to the
repository and this is my first contribution to GCC.

2021-08-30  Dan Klishch  <daklis...@gmail.com>

PR c++/101355
gcc/testsuite/
        * g++.dg/coroutines/pr101355.C: New test.

gcc/c-family/
        * c-ubsan.h (ubsan_maybe_instrument_member_call): Change prototype.
        * c-ubsan.c: Add new include.
        (ubsan_should_instrument_reference_or_call): Add new function.
        (ubsan_maybe_instrument_reference_or_call): Add possibility to convert
        call-expr to bind-expr, so it would not use save-expr.
        (ubsan_maybe_instrument_member_call): Add flag to allow using new
        functionality of the previous.
        (ubsan_maybe_instrument_reference): Fix arguments of call to
        ubsan_maybe_instrument_reference_or_call.

gcc/cp/
        * cp-gimplify.c (struct cp_genericize_data): Add instrumented_dtor_set
        field.
        (cp_genericize_r): Tell ubsan to instrument destructors without using
        save-expr.
        (cp_genericize_tree): Add initialization of instrumented_dtor_set.


diff --git a/gcc/c-family/c-ubsan.c b/gcc/c-family/c-ubsan.c
index 12a7bca5c32..593836984a5 100644
--- a/gcc/c-family/c-ubsan.c
+++ b/gcc/c-family/c-ubsan.c
@@ -23,6 +23,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "coretypes.h"
 #include "tm.h"
 #include "c-family/c-common.h"
+#include "tree-iterator.h"
 #include "ubsan.h"
 #include "c-family/c-ubsan.h"
 #include "stor-layout.h"
@@ -398,22 +399,15 @@ ubsan_maybe_instrument_array_ref (tree *expr_p, bool 
ignore_off_by_one)
     }
 }

-static tree
-ubsan_maybe_instrument_reference_or_call (location_t loc, tree op, tree ptype,
-                                         enum ubsan_null_ckind ckind)
+static bool
+ubsan_should_instrument_reference_or_call (tree op, tree ptype,
+                                          unsigned int &mina)
 {
-  if (!sanitize_flags_p (SANITIZE_ALIGNMENT | SANITIZE_NULL)
-      || current_function_decl == NULL_TREE)
-    return NULL_TREE;
-
-  tree type = TREE_TYPE (ptype);
-  tree orig_op = op;
   bool instrument = false;
-  unsigned int mina = 0;

   if (sanitize_flags_p (SANITIZE_ALIGNMENT))
     {
-      mina = min_align_of_type (type);
+      mina = min_align_of_type (TREE_TYPE (ptype));
       if (mina <= 1)
        mina = 0;
     }
@@ -454,19 +448,68 @@ ubsan_maybe_instrument_reference_or_call (location_t loc, 
tree op, tree ptype,
            instrument = true;
        }
     }
-  if (!instrument)
+  return instrument;
+}
+
+static tree
+ubsan_maybe_instrument_reference_or_call (location_t loc, tree op, tree ptype,
+                                         enum ubsan_null_ckind ckind,
+                                         bool use_save_expr, tree *stmt_p,
+                                         tree *copied_call)
+{
+  if (!sanitize_flags_p (SANITIZE_ALIGNMENT | SANITIZE_NULL)
+      || current_function_decl == NULL_TREE)
     return NULL_TREE;
-  op = save_expr (orig_op);
+
+  unsigned int mina = 0;
+  if (!ubsan_should_instrument_reference_or_call (op, ptype, mina))
+    return NULL_TREE;
+
   gcc_assert (POINTER_TYPE_P (ptype));
   if (TREE_CODE (ptype) == REFERENCE_TYPE)
     ptype = build_pointer_type (TREE_TYPE (ptype));
   tree kind = build_int_cst (ptype, ckind);
   tree align = build_int_cst (pointer_sized_int_node, mina);
-  tree call
-    = build_call_expr_internal_loc (loc, IFN_UBSAN_NULL, void_type_node,
-                                   3, op, kind, align);
-  TREE_SIDE_EFFECTS (call) = 1;
-  return fold_build2 (COMPOUND_EXPR, TREE_TYPE (op), call, op);
+
+  if (use_save_expr)
+    {
+      op = save_expr (op);
+      tree call = build_call_expr_internal_loc (
+         loc, IFN_UBSAN_NULL, void_type_node, 3, op, kind, align);
+      TREE_SIDE_EFFECTS (call) = 1;
+      return fold_build2 (COMPOUND_EXPR, TREE_TYPE (op), call, op);
+    }
+  else
+    {
+      tree stmt = *stmt_p;
+      tree var
+         = build_decl (EXPR_LOCATION (stmt), VAR_DECL, NULL, ptr_type_node);
+      DECL_ARTIFICIAL (var) = 1;
+      DECL_IGNORED_P (var) = 1;
+      DECL_NAMELESS (var) = 1;
+      TREE_READONLY (var) = 0;
+      DECL_EXTERNAL (var) = 0;
+      TREE_STATIC (var) = 0;
+
+      tree exprs = NULL_TREE;
+
+      tree modify_expr
+         = build2 (MODIFY_EXPR, ptr_type_node, var, CALL_EXPR_ARG (stmt, 0));
+      append_to_statement_list (modify_expr, &exprs);
+
+      tree sanitize_expr = build_call_expr_internal_loc (
+         loc, IFN_UBSAN_NULL, void_type_node, 3, var, kind, align);
+      append_to_statement_list (sanitize_expr, &exprs);
+
+      tree call_expr = copy_node (stmt);
+      CALL_EXPR_ARG (call_expr, 0) = var;
+      *copied_call = call_expr;
+      append_to_statement_list (call_expr, &exprs);
+
+      tree bind = build3 (BIND_EXPR, void_type_node, var, exprs, NULL);
+      TREE_SIDE_EFFECTS (bind) = 1;
+      return bind;
+    }
 }

 /* Instrument a NOP_EXPR to REFERENCE_TYPE or INTEGER_CST with REFERENCE_TYPE
@@ -481,7 +524,8 @@ ubsan_maybe_instrument_reference (tree *stmt_p)
     op = TREE_OPERAND (stmt, 0);
   op = ubsan_maybe_instrument_reference_or_call (EXPR_LOCATION (stmt), op,
                                                 TREE_TYPE (stmt),
-                                                UBSAN_REF_BINDING);
+                                                UBSAN_REF_BINDING, true,
+                                                stmt_p, NULL);
   if (op)
     {
       if (TREE_CODE (stmt) == NOP_EXPR)
@@ -493,19 +537,28 @@ ubsan_maybe_instrument_reference (tree *stmt_p)

 /* Instrument a CALL_EXPR to a method if needed.  */

-void
-ubsan_maybe_instrument_member_call (tree stmt, bool is_ctor)
+tree
+ubsan_maybe_instrument_member_call (tree *stmt_p, bool is_ctor, bool is_dtor)
 {
+  tree stmt = *stmt_p;
   if (call_expr_nargs (stmt) == 0)
-    return;
+    return NULL_TREE;
   tree op = CALL_EXPR_ARG (stmt, 0);
-  if (op == error_mark_node
-      || !POINTER_TYPE_P (TREE_TYPE (op)))
-    return;
-  op = ubsan_maybe_instrument_reference_or_call (EXPR_LOCATION (stmt), op,
-                                                TREE_TYPE (op),
-                                                is_ctor ? UBSAN_CTOR_CALL
-                                                : UBSAN_MEMBER_CALL);
+  if (op == error_mark_node || !POINTER_TYPE_P (TREE_TYPE (op)))
+    return NULL_TREE;
+  tree node;
+  op = ubsan_maybe_instrument_reference_or_call (
+      EXPR_LOCATION (stmt), op, TREE_TYPE (op),
+      is_ctor ? UBSAN_CTOR_CALL : UBSAN_MEMBER_CALL, !is_dtor, stmt_p, &node);
   if (op)
-    CALL_EXPR_ARG (stmt, 0) = op;
+    {
+      if (!is_dtor)
+       CALL_EXPR_ARG (stmt, 0) = op;
+      else
+       {
+         *stmt_p = op;
+         return node;
+       }
+    }
+  return NULL_TREE;
 }
diff --git a/gcc/c-family/c-ubsan.h b/gcc/c-family/c-ubsan.h
index 19c3b4464ae..b5cf0b4131d 100644
--- a/gcc/c-family/c-ubsan.h
+++ b/gcc/c-family/c-ubsan.h
@@ -29,6 +29,6 @@ extern tree ubsan_instrument_bounds (location_t, tree, tree 
*, bool);
 extern bool ubsan_array_ref_instrumented_p (const_tree);
 extern void ubsan_maybe_instrument_array_ref (tree *, bool);
 extern void ubsan_maybe_instrument_reference (tree *);
-extern void ubsan_maybe_instrument_member_call (tree, bool);
+extern tree ubsan_maybe_instrument_member_call (tree *, bool, bool);

 #endif  /* GCC_C_UBSAN_H  */
diff --git a/gcc/cp/cp-gimplify.c b/gcc/cp/cp-gimplify.c
index bf928a82ce9..4fb6a9cfd04 100644
--- a/gcc/cp/cp-gimplify.c
+++ b/gcc/cp/cp-gimplify.c
@@ -824,6 +824,7 @@ omp_cxx_notice_variable (struct cp_genericize_omp_taskreg 
*omp_ctx, tree decl)
 struct cp_genericize_data
 {
   hash_set<tree> *p_set;
+  hash_set<tree> *instrumented_dtor_set;
   auto_vec<tree> bind_expr_stack;
   struct cp_genericize_omp_taskreg *omp_ctx;
   tree try_block;
@@ -1435,12 +1436,25 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void 
*data)
              && INDIRECT_TYPE_P (TREE_TYPE (fn))
              && TREE_CODE (TREE_TYPE (TREE_TYPE (fn))) == METHOD_TYPE)
            {
+             bool is_func
+                 = TREE_CODE (fn) == ADDR_EXPR
+                   && TREE_CODE (TREE_OPERAND (fn, 0)) == FUNCTION_DECL;
              bool is_ctor
-               = TREE_CODE (fn) == ADDR_EXPR
-                 && TREE_CODE (TREE_OPERAND (fn, 0)) == FUNCTION_DECL
-                 && DECL_CONSTRUCTOR_P (TREE_OPERAND (fn, 0));
-             if (sanitize_flags_p (SANITIZE_NULL | SANITIZE_ALIGNMENT))
-               ubsan_maybe_instrument_member_call (stmt, is_ctor);
+                 = is_func && DECL_CONSTRUCTOR_P (TREE_OPERAND (fn, 0));
+             bool is_dtor
+                 = is_func && DECL_DESTRUCTOR_P (TREE_OPERAND (fn, 0));
+             tree node;
+             if (!wtd->instrumented_dtor_set->contains (stmt)
+                 && sanitize_flags_p (SANITIZE_NULL | SANITIZE_ALIGNMENT)
+                 && (node = ubsan_maybe_instrument_member_call (stmt_p,
+                                                               is_ctor,
+                                                               is_dtor)))
+               {
+                 wtd->instrumented_dtor_set->add (node);
+                 tree result = cp_genericize_r (stmt_p, walk_subtrees, data);
+                 wtd->instrumented_dtor_set->remove (node);
+                 return result;
+               }
              if (sanitize_flags_p (SANITIZE_VPTR) && !is_ctor)
                cp_ubsan_maybe_instrument_member_call (stmt);
            }
@@ -1592,6 +1606,7 @@ cp_genericize_tree (tree* t_p, bool 
handle_invisiref_parm_p)
   struct cp_genericize_data wtd;

   wtd.p_set = new hash_set<tree>;
+  wtd.instrumented_dtor_set = new hash_set<tree>;
   wtd.bind_expr_stack.create (0);
   wtd.omp_ctx = NULL;
   wtd.try_block = NULL_TREE;
@@ -1599,6 +1614,7 @@ cp_genericize_tree (tree* t_p, bool 
handle_invisiref_parm_p)
   wtd.handle_invisiref_parm_p = handle_invisiref_parm_p;
   cp_walk_tree (t_p, cp_genericize_r, &wtd, NULL);
   delete wtd.p_set;
+  delete wtd.instrumented_dtor_set;
   if (sanitize_flags_p (SANITIZE_VPTR))
     cp_ubsan_instrument_member_accesses (t_p);
 }
diff --git a/gcc/testsuite/g++.dg/coroutines/pr101355.C 
b/gcc/testsuite/g++.dg/coroutines/pr101355.C
new file mode 100644
index 00000000000..cd0becbf62a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr101355.C
@@ -0,0 +1,79 @@
+// { dg-do run }
+// { dg-options "-fsanitize=undefined -Wall -Werror -std=c++20" }
+#if __has_include(<coroutine>)
+#include <coroutine>
+#elif defined(__clang__) && __has_include(<experimental/coroutine>)
+#include <experimental/coroutine>
+namespace std
+{
+using namespace experimental;
+}
+#endif
+#include <utility>
+
+struct coro
+{
+  struct promise_type
+  {
+    coro
+    get_return_object ()
+    {
+      return {};
+    }
+
+    std::suspend_never
+    initial_suspend () noexcept
+    {
+      return {};
+    }
+
+    std::suspend_never
+    final_suspend () noexcept
+    {
+      return {};
+    }
+
+    void
+    unhandled_exception () {}
+
+    void
+    return_void () {}
+  };
+
+  bool
+  await_ready ()
+  {
+    return true;
+  }
+
+  void
+  await_resume () {}
+
+  template <typename U>
+  void
+  await_suspend (U &) {}
+};
+
+struct b
+{
+  ~b () {}
+};
+
+struct a
+{
+  a (b) {}
+  ~a () {}
+};
+
+coro
+f (b obj)
+{
+  auto obj2 = a{ obj };
+  co_return;
+}
+
+int
+main ()
+{
+  f ({});
+}

Reply via email to