From: feedable <[email protected]>

Currently, auto_vec defaults its copy&move operations. This is bad,
since a user that tries to copy or move an auto_vec will unknowingly
set himself up for trouble, since the pointers from the base and members
will be copied verbatim, leading to double free situtaions and the like.

Introduce proper constructor and operator overloads, that perform proper
transfer of memory controlled by auto_vec.

gcc/ChangeLog:

        * vec.cc (struct count_cdtors): Test utility type.
        (test_auto_vec_copy_move_construct): New test.
        (test_auto_vec_move_assign): Likewise.
        (test_auto_vec_copy_assign): Likewise.
        (vec_cc_tests): Likewise.
        * vec.h (vec<T, A, vl_embed>::quick_emplace): New function.
        (vec<T, va_heap, vl_ptr>::quick_emplace): Likewise.
        (vec<T, va_heap, vl_ptr>::safe_emplace): Likewise.
---
 gcc/vec.cc | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 gcc/vec.h  | 129 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 272 insertions(+)

diff --git a/gcc/vec.cc b/gcc/vec.cc
index 8313adde2b22b..3d7fc387ba428 100644
--- a/gcc/vec.cc
+++ b/gcc/vec.cc
@@ -640,6 +640,146 @@ test_auto_delete_vec ()
   ASSERT_EQ (dtor_count, 2);
 }
 
+struct count_cdtors{
+  static int copy_cnt, move_cnt, ctor_cnt, dtor_cnt;
+  static void reset ()
+  {
+    copy_cnt = 0;
+    move_cnt = 0;
+    ctor_cnt = 0;
+    dtor_cnt = 0;
+  }
+  static void validate (int copy, int move, int ctor, int dtor)
+  {
+    ASSERT_EQ (copy_cnt, copy);
+    ASSERT_EQ (move_cnt, move);
+    ASSERT_EQ (ctor_cnt, ctor);
+    ASSERT_EQ (dtor_cnt, dtor);
+    reset ();
+  }
+  count_cdtors () { ++ctor_cnt; }
+  ~count_cdtors () { ++dtor_cnt; }
+  count_cdtors (count_cdtors &&) { ++move_cnt; }
+  count_cdtors (const count_cdtors &) { ++copy_cnt; }
+  count_cdtors &operator= (count_cdtors &&) { return ++move_cnt, *this; }
+  count_cdtors &operator= (const count_cdtors &) { return ++copy_cnt, *this; }
+};
+int count_cdtors::copy_cnt = 0,
+    count_cdtors::move_cnt = 0,
+    count_cdtors::ctor_cnt = 0,
+    count_cdtors::dtor_cnt = 0;
+
+static void
+test_auto_vec_copy_move_construct ()
+{
+  count_cdtors::reset ();
+  {
+    count_cdtors::validate (0, 0, 0, 0);
+    auto_vec<count_cdtors, 1> test (2);
+    count_cdtors::validate (0, 0, 0, 0);
+    test.quick_grow_cleared (2);
+    count_cdtors::validate (0, 0, 2, 0);
+    auto test2 = test;
+    count_cdtors::validate (2, 0, 0, 0);
+    auto test3 = std::move (test);
+    count_cdtors::validate (0, 0, 0, 0);
+  }
+  count_cdtors::validate (0, 0, 0, 4);
+  {
+    auto_vec<count_cdtors, 2> test (2);
+    count_cdtors::validate (0, 0, 0, 0);
+    test.quick_grow_cleared (2);
+    count_cdtors::validate (0, 0, 2, 0);
+    auto test2 = test;
+    count_cdtors::validate (2, 0, 0, 0);
+    auto test3 = std::move (test);
+    count_cdtors::validate (0, 2, 0, 0);
+  }
+  count_cdtors::validate (0, 0, 0, 6);
+}
+
+static void
+test_auto_vec_move_assign ()
+{
+  {
+    auto_vec<count_cdtors, 1> from (1), to (1);
+    from.quick_grow_cleared (1);
+    to.quick_grow_cleared (1);
+    count_cdtors::validate (0, 0, 2, 0);
+    to = std::move (from);
+    count_cdtors::validate (0, 1, 0, 0);
+  }
+  count_cdtors::validate (0, 0, 0, 2);
+  {
+    auto_vec<count_cdtors, 1> from (1), to (2);
+    from.quick_grow_cleared (1);
+    to.quick_grow_cleared (2);
+    count_cdtors::validate (0, 0, 3, 0);
+    to = std::move (from);
+    count_cdtors::validate (0, 1, 0, 2);
+  }
+  count_cdtors::validate (0, 0, 0, 2);
+  {
+    auto_vec<count_cdtors, 1> from (2), to (1);
+    from.quick_grow_cleared (2);
+    to.quick_grow_cleared (1);
+    count_cdtors::validate (0, 0, 3, 0);
+    to = std::move (from);
+    count_cdtors::validate (0, 0, 0, 1);
+  }
+  count_cdtors::validate (0, 0, 0, 2);
+  {
+    auto_vec<count_cdtors, 1> from (2), to (2);
+    from.quick_grow_cleared (2);
+    to.quick_grow_cleared (2);
+    count_cdtors::validate (0, 0, 4, 0);
+    to = std::move (from);
+    count_cdtors::validate (0, 0, 0, 2);
+  }
+  count_cdtors::validate (0, 0, 0, 2);
+}
+
+static void
+test_auto_vec_copy_assign ()
+{
+  {
+    auto_vec<count_cdtors, 1> from (1), to (1);
+    from.quick_grow_cleared (1);
+    to.quick_grow_cleared (1);
+    count_cdtors::validate (0, 0, 2, 0);
+    to = from;
+    count_cdtors::validate (1, 0, 0, 0);
+  }
+  count_cdtors::validate (0, 0, 0, 2);
+  {
+    auto_vec<count_cdtors, 1> from (1), to (2);
+    from.quick_grow_cleared (1);
+    to.quick_grow_cleared (2);
+    count_cdtors::validate (0, 0, 3, 0);
+    to = from;
+    count_cdtors::validate (1, 0, 0, 1);
+  }
+  count_cdtors::validate (0, 0, 0, 2);
+  {
+    auto_vec<count_cdtors, 1> from (2), to (1);
+    from.quick_grow_cleared (2);
+    to.quick_grow_cleared (1);
+    count_cdtors::validate (0, 0, 3, 0);
+    to = from;
+    count_cdtors::validate (2, 0, 0, 1);
+  }
+  count_cdtors::validate (0, 0, 0, 4);
+  {
+    auto_vec<count_cdtors, 1> from (2), to (2);
+    from.quick_grow_cleared (2);
+    to.quick_grow_cleared (2);
+    count_cdtors::validate (0, 0, 4, 0);
+    to = from;
+    count_cdtors::validate (2, 0, 0, 0);
+  }
+  count_cdtors::validate (0, 0, 0, 4);
+}
+
 /* Verify accesses to vector elements are done indirectly.  */
 
 static void
@@ -828,6 +968,9 @@ vec_cc_tests ()
   test_qsort ();
   test_reverse ();
   test_auto_delete_vec ();
+  test_auto_vec_copy_move_construct ();
+  test_auto_vec_copy_assign ();
+  test_auto_vec_move_assign ();
   test_auto_alias ();
   test_string_slice_initializers ();
   test_string_slice_tokenize ();
diff --git a/gcc/vec.h b/gcc/vec.h
index 9d25678029832..fcab26d5c418d 100644
--- a/gcc/vec.h
+++ b/gcc/vec.h
@@ -620,6 +620,8 @@ public:
   void splice (const vec &);
   void splice (const vec *src);
   T *quick_push (const T &);
+  template<typename ...Ts>
+  T *quick_emplace (Ts &&...);
   using pop_ret_type
     = typename std::conditional <std::is_trivially_destructible <T>::value,
                                 T &, void>::type;
@@ -1053,6 +1055,16 @@ vec<T, A, vl_embed>::quick_push (const T &obj)
   return slot;
 }
 
+template<typename T, typename A>
+template<typename ...Ts>
+inline T *
+vec<T, A, vl_embed>::quick_emplace (Ts &&...obj)
+{
+  gcc_checking_assert (space (1));
+  T *slot = &address ()[m_vecpfx.m_num++];
+  ::new (static_cast<void*>(slot)) T ((Ts &&)obj...);
+  return slot;
+}
 
 /* Pop and return a reference to the last element off the end of the
    vector.  If T has non-trivial destructor, this method just pops
@@ -1623,7 +1635,11 @@ public:
   void splice (const vec &);
   void safe_splice (const vec & CXX_MEM_STAT_INFO);
   T *quick_push (const T &);
+  template<typename ...Ts>
+  T *quick_emplace (Ts &&...);
   T *safe_push (const T &CXX_MEM_STAT_INFO);
+  template<typename ...Ts>
+  T *safe_emplace (Ts &&...);
   using pop_ret_type
     = typename std::conditional <std::is_trivially_destructible <T>::value,
                                 T &, void>::type;
@@ -1691,6 +1707,91 @@ public:
     size_t off = (char *) &m_auto - (char *) this;
     this->m_vec = (vec<T, va_heap, vl_embed> *) ((char *) this + off);
   }
+  auto_vec (size_t s CXX_MEM_STAT_INFO, T fill): auto_vec (s)
+  {
+    for (size_t i = 0; i != s; ++i)
+      this->quick_push (fill);
+  }
+
+  auto_vec (auto_vec &&r)
+    noexcept (std::is_nothrow_move_constructible<T>::value)
+    : auto_vec ()
+  {
+    if (!r.using_auto_storage ())
+      {
+       this->m_vec = std::exchange (r.m_vec, nullptr);
+       return;
+      }
+    for (auto &v: r)
+      this->quick_emplace (std::move (v));
+  }
+
+  auto_vec &operator= (auto_vec &&r)
+    noexcept (std::is_nothrow_move_constructible<T>::value)
+  {
+    if (!r.using_auto_storage () && !this->using_auto_storage ())
+      {
+       std::swap (this->m_vec, r.m_vec);
+       r.release ();
+      }
+    else if (!r.using_auto_storage () && this->using_auto_storage ())
+      {
+       auto data = std::exchange (r.m_vec, nullptr);
+       std::exchange (this->m_vec, data)->truncate (0);
+      }
+    else if (r.using_auto_storage () && !this->using_auto_storage ())
+      {
+       /* To avoid losing temporaries, we have to fill the internal array
+          first, and only then destroy the residual elems.  */
+       m_auto.embedded_init (N, 0, 1);
+       vec<T, va_heap> replacement;
+       size_t off = (char *) &m_auto - (char *) this;
+       replacement.m_vec = (vec<T, va_heap, vl_embed> *) ((char *) this + off);
+       for (auto &v: r)
+         replacement.quick_emplace (std::move (v));
+       std::exchange ((vec<T, va_heap> &)*this, replacement).release ();
+      }
+    else if (r.using_auto_storage () && this->using_auto_storage ())
+      {
+       /* The most complicated case: move-assign all the elements that are live
+          in both, rthen move-construct or destruct the rest.  */
+       int a = this->length (), b = r.length ();
+       int i;
+       for (i = 0; i < a && i < b; ++i)
+         (*this)[i] = std::move (r[i]);
+       for (; i < b; ++i)
+         this->quick_emplace (std::move (r[i]));
+       this->truncate (b);
+      }
+    else gcc_unreachable ();
+    return *this;
+  }
+
+  auto_vec (const auto_vec &r): auto_vec (r.length ())
+  {
+    for (auto &v: r)
+      this->quick_push (v);
+  }
+
+  auto_vec &operator= (const auto_vec &r)
+  {
+    if (r.length () <= this->allocated ())
+      {
+       int a = this->length (), b = r.length ();
+       int i;
+       for (i = 0; i < a && i < b; ++i)
+         (*this)[i] = r[i];
+       for (; i < b; ++i)
+         this->quick_emplace (r[i]);
+       this->truncate (b);
+      }
+    else
+      {
+       auto_vec fresh = r;
+       *this = std::move (fresh);
+      }
+    return *this;
+  }
 
   ~auto_vec ()
   {
@@ -2082,6 +2183,19 @@ vec<T, va_heap, vl_ptr>::quick_push (const T &obj)
 }
 
 
+/* Construct a new element from constructor ARGS at the end of the vector.
+   There must be sufficient space in the vector.  Return a pointer to the slot
+   where the element was inserted.  */
+
+template<typename T>
+template<typename ...Ts>
+inline T *
+vec<T, va_heap, vl_ptr>::quick_emplace (Ts &&...args)
+{
+  return m_vec->quick_emplace ((Ts &&)args...);
+}
+
+
 /* Push a new element OBJ onto the end of this vector.  Reallocates
    the embedded vector, if needed.  Return a pointer to the slot where
    OBJ was inserted.  */
@@ -2095,6 +2209,21 @@ vec<T, va_heap, vl_ptr>::safe_push (const T &obj 
MEM_STAT_DECL)
 }
 
 
+/* Construct a new element from constructor ARGS at the end of the vector.
+   Reallocates the embedded vector, if needed.  Return a pointer to the slot
+   where the element was inserted.  */
+
+template<typename T>
+template<typename ...Ts>
+inline T *
+vec<T, va_heap, vl_ptr>::safe_emplace (Ts &&...args)
+{
+  reserve (1, false PASS_MEM_STAT);
+  return quick_emplace ((Ts &&)args...);
+}
+
+
+
 /* Pop and return a reference to the last element off the end of the
    vector.  If T has non-trivial destructor, this method just pops
    last element and returns void.  */
-- 
2.54.0

Reply via email to