Make bssl::Vector not require a default constructor Rather than leaving default-initialized things in there, a real std::vector implementation leaves the elements passed size_ unconstructed and constructs them as needed. Change-Id: Ib884e2c10894de011d75a46037b446088195ba96 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/71828 Commit-Queue: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/internal.h b/ssl/internal.h index 247f8c6..d4c1c27 100644 --- a/ssl/internal.h +++ b/ssl/internal.h
@@ -204,6 +204,16 @@ return first; } +template <typename InputIt, typename OutputIt> +InputIt cxx17_uninitialized_move(InputIt first, InputIt last, OutputIt out) { + using OutputT = typename std::iterator_traits<OutputIt>::value_type; + for (; first != last; ++first) { + new (std::addressof(*out)) OutputT(std::move(*first)); + ++out; + } + return out; +} + template <typename ForwardIt> ForwardIt cxx17_destroy_n(ForwardIt first, size_t n) { using T = typename std::iterator_traits<ForwardIt>::value_type; @@ -367,42 +377,42 @@ }; // Vector<T> is a resizable array of elements of |T|. -// -// Note, for simplicity, this class currently differs from |std::vector| in that -// |T| must be efficiently default-constructible. Allocated elements beyond the -// end of the array are constructed and destructed. template <typename T> class Vector { public: Vector() = default; Vector(const Vector &) = delete; Vector(Vector &&other) { *this = std::move(other); } - ~Vector() {} + ~Vector() { clear(); } Vector &operator=(const Vector &) = delete; Vector &operator=(Vector &&other) { - size_ = other.size_; - other.size_ = 0; - array_ = std::move(other.array_); + clear(); + std::swap(data_, other.data_); + std::swap(size_, other.size_); + std::swap(capacity_, other.capacity_); return *this; } - const T *data() const { return array_.data(); } - T *data() { return array_.data(); } + const T *data() const { return data_; } + T *data() { return data_; } size_t size() const { return size_; } bool empty() const { return size_ == 0; } - const T &operator[](size_t i) const { return array_[i]; } - T &operator[](size_t i) { return array_[i]; } + const T &operator[](size_t i) const { return data_[i]; } + T &operator[](size_t i) { return data_[i]; } - T *begin() { return array_.data(); } - const T *begin() const { return array_.data(); } - T *end() { return array_.data() + size_; } - const T *end() const { return array_.data() + size_; } + T *begin() { return data_; } + const T *begin() const { return data_; } + T *end() { return data_ + size_; } + const T *end() const { return data_ + size_; } void clear() { + cxx17_destroy_n(data_, size_); + OPENSSL_free(data_); + data_ = nullptr; size_ = 0; - array_.Reset(); + capacity_ = 0; } // Push adds |elem| at the end of the internal array, growing if necessary. It @@ -411,7 +421,7 @@ if (!MaybeGrow()) { return false; } - array_[size_] = std::move(elem); + new (&data_[size_]) T(std::move(elem)); size_++; return true; } @@ -419,10 +429,14 @@ // CopyFrom replaces the contents of the array with a copy of |in|. It returns // true on success and false on allocation error. bool CopyFrom(Span<const T> in) { - if (!array_.CopyFrom(in)) { + Array<T> copy; + if (!copy.CopyFrom(in)) { return false; } - size_ = in.size(); + + clear(); + copy.Release(&data_, &size_); + capacity_ = size_; return true; } @@ -430,35 +444,44 @@ // If there is no room for one more element, creates a new backing array with // double the size of the old one and copies elements over. bool MaybeGrow() { - if (array_.size() == 0) { - return array_.Init(kDefaultSize); - } // No need to grow if we have room for one more T. - if (size_ < array_.size()) { + if (size_ < capacity_) { return true; } - // Double the array's size if it's safe to do so. - if (array_.size() > std::numeric_limits<size_t>::max() / 2) { + size_t new_capacity = kDefaultSize; + if (capacity_ > 0) { + // Double the array's size if it's safe to do so. + if (capacity_ > std::numeric_limits<size_t>::max() / 2) { + OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW); + return false; + } + new_capacity = capacity_ * 2; + } + if (new_capacity > std::numeric_limits<size_t>::max() / sizeof(T)) { OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW); return false; } - Array<T> new_array; - if (!new_array.Init(array_.size() * 2)) { + T *new_data = + reinterpret_cast<T *>(OPENSSL_malloc(new_capacity * sizeof(T))); + if (new_data == nullptr) { return false; } - for (size_t i = 0; i < array_.size(); i++) { - new_array[i] = std::move(array_[i]); - } - array_ = std::move(new_array); - + size_t new_size = size_; + cxx17_uninitialized_move(begin(), end(), new_data); + clear(); + data_ = new_data; + size_ = new_size; + capacity_ = new_capacity; return true; } + // data_ is a pointer to |capacity_| objects of size |T|, the first |size_| of + // which are constructed. + T *data_ = nullptr; // |size_| is the number of elements stored in this Vector. size_t size_ = 0; - // |array_| is the backing array. Note that |array_.size()| is this - // Vector's current capacity and that |size_ <= array_.size()|. - Array<T> array_; + // |capacity_| is the number of elements allocated in this Vector. + size_t capacity_ = 0; // |kDefaultSize| is the default initial size of the backing array. static constexpr size_t kDefaultSize = 16; };
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc index b552885..b21c798 100644 --- a/ssl/ssl_test.cc +++ b/ssl/ssl_test.cc
@@ -587,6 +587,16 @@ for (size_t i = 0; i < vec.size(); i++) { EXPECT_EQ(vec[i], i == 0 ? 42 : i); } + + // Clearing the vector should give an empty one. + vec.clear(); + ASSERT_TRUE(vec.empty()); + EXPECT_EQ(vec.size(), 0u); + + ASSERT_TRUE(vec.Push(42)); + ASSERT_TRUE(!vec.empty()); + EXPECT_EQ(vec.size(), 1u); + EXPECT_EQ(vec[0], 42u); } TEST(VectorTest, MoveConstructor) { @@ -635,6 +645,24 @@ } } +TEST(VectorTest, NotDefaultConstructible) { + struct NotDefaultConstructible { + explicit NotDefaultConstructible(size_t n) { array.Init(n); } + Array<int> array; + }; + + Vector<NotDefaultConstructible> vec; + vec.Push(NotDefaultConstructible(0)); + vec.Push(NotDefaultConstructible(1)); + vec.Push(NotDefaultConstructible(2)); + vec.Push(NotDefaultConstructible(3)); + EXPECT_EQ(vec.size(), 4u); + EXPECT_EQ(0u, vec[0].array.size()); + EXPECT_EQ(1u, vec[1].array.size()); + EXPECT_EQ(2u, vec[2].array.size()); + EXPECT_EQ(3u, vec[3].array.size()); +} + TEST(ReconstructSeqnumTest, Increment) { // Test simple cases from the beginning of an epoch with both 8- and 16-bit // wire sequence numbers.