Add GrowableArray<T> to ssl/internal.h. Change-Id: I07aced6d2830dd5a2a04c296b1ffe7e8557369fe Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/37504 Reviewed-by: David Benjamin <davidben@google.com> Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/ssl/internal.h b/ssl/internal.h index c635583..792329bb 100644 --- a/ssl/internal.h +++ b/ssl/internal.h
@@ -353,6 +353,97 @@ size_t size_ = 0; }; +// GrowableArray<T> is an array that owns elements of |T|, backed by an +// Array<T>. When necessary, pushing will automatically trigger a resize. +// +// 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 GrowableArray { + public: + GrowableArray() = default; + GrowableArray(const GrowableArray &) = delete; + GrowableArray(GrowableArray &&other) { *this = std::move(other); } + ~GrowableArray() {} + + GrowableArray &operator=(const GrowableArray &) = delete; + GrowableArray &operator=(GrowableArray &&other) { + size_ = other.size_; + other.size_ = 0; + array_ = std::move(other.array_); + return *this; + } + + 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]; } + + T *begin() { return array_.data(); } + const T *cbegin() const { return array_.data(); } + T *end() { return array_.data() + size_; } + const T *cend() const { return array_.data() + size_; } + + // Push adds |elem| at the end of the internal array, growing if necessary. It + // returns false when allocation fails. + bool Push(T elem) { + if (!MaybeGrow()) { + return false; + } + array_[size_] = std::move(elem); + size_++; + return true; + } + + // 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)) { + return false; + } + size_ = in.size(); + return true; + } + + private: + // 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()) { + return true; + } + // Double the array's size if it's safe to do so. + if (array_.size() > std::numeric_limits<size_t>::max() / 2) { + OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW); + return false; + } + Array<T> new_array; + if (!new_array.Init(array_.size() * 2)) { + return false; + } + for (size_t i = 0; i < array_.size(); i++) { + new_array[i] = std::move(array_[i]); + } + array_ = std::move(new_array); + + return true; + } + + // |size_| is the number of elements stored in this GrowableArray. + size_t size_ = 0; + // |array_| is the backing array. Note that |array_.size()| is this + // GrowableArray's current capacity and that |size_ <= array_.size()|. + Array<T> array_; + // |kDefaultSize| is the default initial size of the backing array. + static constexpr size_t kDefaultSize = 16; +}; + // CBBFinishArray behaves like |CBB_finish| but stores the result in an Array. OPENSSL_EXPORT bool CBBFinishArray(CBB *cbb, Array<uint8_t> *out);
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc index e702762..c01443e 100644 --- a/ssl/ssl_test.cc +++ b/ssl/ssl_test.cc
@@ -473,6 +473,74 @@ return true; } +TEST(GrowableArrayTest, Resize) { + GrowableArray<size_t> array; + ASSERT_TRUE(array.empty()); + EXPECT_EQ(array.size(), 0u); + + ASSERT_TRUE(array.Push(42)); + ASSERT_TRUE(!array.empty()); + EXPECT_EQ(array.size(), 1u); + + // Force a resize operation to occur + for (size_t i = 0; i < 16; i++) { + ASSERT_TRUE(array.Push(i + 1)); + } + + EXPECT_EQ(array.size(), 17u); + + // Verify that expected values are still contained in array + for (size_t i = 0; i < array.size(); i++) { + EXPECT_EQ(array[i], i == 0 ? 42 : i); + } +} + +TEST(GrowableArrayTest, MoveConstructor) { + GrowableArray<size_t> array; + for (size_t i = 0; i < 100; i++) { + ASSERT_TRUE(array.Push(i)); + } + + GrowableArray<size_t> array_moved(std::move(array)); + for (size_t i = 0; i < 100; i++) { + EXPECT_EQ(array_moved[i], i); + } +} + +TEST(GrowableArrayTest, GrowableArrayContainingGrowableArrays) { + // Representative example of a struct that contains a GrowableArray. + struct TagAndArray { + size_t tag; + GrowableArray<size_t> array; + }; + + GrowableArray<TagAndArray> array; + for (size_t i = 0; i < 100; i++) { + TagAndArray elem; + elem.tag = i; + for (size_t j = 0; j < i; j++) { + ASSERT_TRUE(elem.array.Push(j)); + } + ASSERT_TRUE(array.Push(std::move(elem))); + } + EXPECT_EQ(array.size(), static_cast<size_t>(100)); + + GrowableArray<TagAndArray> array_moved(std::move(array)); + EXPECT_EQ(array_moved.size(), static_cast<size_t>(100)); + size_t count = 0; + for (const TagAndArray &elem : array_moved) { + // Test the square bracket operator returns the same value as iteration. + EXPECT_EQ(&elem, &array_moved[count]); + + EXPECT_EQ(elem.tag, count); + EXPECT_EQ(elem.array.size(), count); + for (size_t j = 0; j < count; j++) { + EXPECT_EQ(elem.array[j], j); + } + count++; + } +} + TEST(SSLTest, CipherRules) { for (const CipherTest &t : kCipherTests) { SCOPED_TRACE(t.rule);