diff mbox series

c++: New way to sanitize destructor calls [PR101355]

Message ID 6a42fa4f-8c17-5fb7-d3b6-7d56cf478c1f@gmail.com
State New
Headers show
Series c++: New way to sanitize destructor calls [PR101355] | expand

Commit Message

Dan Klishch Aug. 30, 2021, 5:11 p.m. UTC
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  <daklishch@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 mbox series

Patch

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 ({});
+}