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.