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..792329b 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);