blob: 5f44647f728980b5a321871641a204b8c3b20b25 [file] [log] [blame]
From 67b1b0352f38a531781bb0311441b77f092a6845 Mon Sep 17 00:00:00 2001
From: Brett Brotherton <bbrotherton@google.com>
Date: Fri, 11 Aug 2023 07:21:32 -0600
Subject: [PATCH] Revert "Remove base::StackVector"
This reverts commit 70f8b27d8e7b42cd461e388af427a361ca509808.
---
base/containers/stack_container.h | 277 ++++++++++++++++++++
base/containers/stack_container_unittest.cc | 214 +++++++++++++++
2 files changed, 491 insertions(+)
create mode 100644 base/containers/stack_container.h
create mode 100644 base/containers/stack_container_unittest.cc
diff --git a/base/containers/stack_container.h b/base/containers/stack_container.h
new file mode 100644
index 0000000000..ba3842a28d
--- /dev/null
+++ b/base/containers/stack_container.h
@@ -0,0 +1,277 @@
+// Copyright 2012 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_CONTAINERS_STACK_CONTAINER_H_
+#define BASE_CONTAINERS_STACK_CONTAINER_H_
+
+// ================== DEPRECATION NOTICE ==================
+// These classes are deprecated and will be removed soon. Use
+// absl::InlinedVector instead. If absl::InlinedVector doesn't fit your use
+// case, please email cxx@chromium.org with details.
+
+#include <stddef.h>
+#include <memory>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/raw_ptr_exclusion.h"
+#include "build/build_config.h"
+
+namespace base {
+
+// This allocator can be used with STL containers to provide a stack buffer
+// from which to allocate memory and overflows onto the heap. This stack buffer
+// would be allocated on the stack and allows us to avoid heap operations in
+// some situations.
+//
+// STL likes to make copies of allocators, so the allocator itself can't hold
+// the data. Instead, we make the creator responsible for creating a
+// StackAllocator::Source which contains the data. Copying the allocator
+// merely copies the pointer to this shared source, so all allocators created
+// based on our allocator will share the same stack buffer.
+//
+// This stack buffer implementation is very simple. The first allocation that
+// fits in the stack buffer will use the stack buffer. Any subsequent
+// allocations will not use the stack buffer, even if there is unused room.
+// This makes it appropriate for array-like containers, but the caller should
+// be sure to reserve() in the container up to the stack buffer size. Otherwise
+// the container will allocate a small array which will "use up" the stack
+// buffer.
+template <typename T, size_t stack_capacity, typename FallbackAllocator>
+class StackAllocator : public FallbackAllocator {
+ public:
+ using pointer = typename std::allocator_traits<FallbackAllocator>::pointer;
+ using size_type =
+ typename std::allocator_traits<FallbackAllocator>::size_type;
+
+ // Backing store for the allocator. The container owner is responsible for
+ // maintaining this for as long as any containers using this allocator are
+ // live.
+ struct Source {
+ Source() : used_stack_buffer_(false) {
+ }
+
+ // Casts the buffer in its right type.
+ NO_SANITIZE("cfi-unrelated-cast")
+ T* stack_buffer() { return reinterpret_cast<T*>(stack_buffer_); }
+ NO_SANITIZE("cfi-unrelated-cast")
+ const T* stack_buffer() const {
+ return reinterpret_cast<const T*>(&stack_buffer_);
+ }
+
+ // The buffer itself. It is not of type T because we don't want the
+ // constructors and destructors to be automatically called. Define a POD
+ // buffer of the right size instead.
+ alignas(T) char stack_buffer_[sizeof(T[stack_capacity])];
+#if defined(__GNUC__) && !defined(ARCH_CPU_X86_FAMILY)
+ static_assert(alignof(T) <= 16, "http://crbug.com/115612");
+#endif
+
+ // Set when the stack buffer is used for an allocation. We do not track
+ // how much of the buffer is used, only that somebody is using it.
+ bool used_stack_buffer_;
+ };
+
+ // Used by containers when they want to refer to an allocator of type U.
+ template<typename U>
+ struct rebind {
+ typedef StackAllocator<U, stack_capacity, FallbackAllocator> other;
+ };
+
+ // For the straight up copy c-tor, we can share storage.
+ StackAllocator(
+ const StackAllocator<T, stack_capacity, FallbackAllocator>& rhs)
+ : source_(rhs.source_) {}
+
+ // ISO C++ requires the following constructor to be defined,
+ // and std::vector in VC++2008SP1 Release fails with an error
+ // in the class _Container_base_aux_alloc_real (from <xutility>)
+ // if the constructor does not exist.
+ // For this constructor, we cannot share storage; there's
+ // no guarantee that the Source buffer of Ts is large enough
+ // for Us.
+ // TODO: If we were fancy pants, perhaps we could share storage
+ // iff sizeof(T) == sizeof(U).
+ template <typename U, size_t other_capacity, typename FA>
+ StackAllocator(const StackAllocator<U, other_capacity, FA>& other)
+ : source_(nullptr) {}
+
+ // This constructor must exist. It creates a default allocator that doesn't
+ // actually have a stack buffer. glibc's std::string() will compare the
+ // current allocator against the default-constructed allocator, so this
+ // should be fast.
+ StackAllocator() : source_(nullptr) {}
+
+ explicit StackAllocator(Source* source) : source_(source) {
+ }
+
+ // Actually do the allocation. Use the stack buffer if nobody has used it yet
+ // and the size requested fits. Otherwise, fall through to the standard
+ // allocator.
+ pointer allocate(size_type n) {
+ if (source_ && !source_->used_stack_buffer_ && n <= stack_capacity) {
+ source_->used_stack_buffer_ = true;
+ return source_->stack_buffer();
+ } else {
+ return std::allocator_traits<FallbackAllocator>::allocate(*this, n);
+ }
+ }
+
+ // Free: when trying to free the stack buffer, just mark it as free. For
+ // non-stack-buffer pointers, just fall though to the standard allocator.
+ void deallocate(pointer p, size_type n) {
+ if (source_ && p == source_->stack_buffer())
+ source_->used_stack_buffer_ = false;
+ else
+ std::allocator_traits<FallbackAllocator>::deallocate(*this, p, n);
+ }
+
+ private:
+ // `source_` is not a raw_ptr<T> for performance reasons: on-stack pointee.
+ RAW_PTR_EXCLUSION Source* source_;
+};
+
+// A wrapper around STL containers that maintains a stack-sized buffer that the
+// initial capacity of the vector is based on. Growing the container beyond the
+// stack capacity will transparently overflow onto the heap. The container must
+// support reserve().
+//
+// This will not work with std::string since some implementations allocate
+// more bytes than requested in calls to reserve(), forcing the allocation onto
+// the heap. http://crbug.com/709273
+//
+// WATCH OUT: the ContainerType MUST use the proper StackAllocator for this
+// type. This object is really intended to be used only internally. You'll want
+// to use the wrappers below for different types.
+template <typename TContainerType, int stack_capacity>
+class StackContainer {
+ public:
+ using ContainerType = TContainerType;
+ using ContainedType = typename ContainerType::value_type;
+ using Allocator = typename ContainerType::allocator_type;
+
+ // Allocator must be constructed before the container!
+ StackContainer() : allocator_(&stack_data_), container_(allocator_) {
+ // Make the container use the stack allocation by reserving our buffer size
+ // before doing anything else.
+ container_.reserve(stack_capacity);
+ }
+ StackContainer(const StackContainer&) = delete;
+ StackContainer& operator=(const StackContainer&) = delete;
+
+ // Getters for the actual container.
+ //
+ // Danger: any copies of this made using the copy constructor must have
+ // shorter lifetimes than the source. The copy will share the same allocator
+ // and therefore the same stack buffer as the original. Use std::copy to
+ // copy into a "real" container for longer-lived objects.
+ ContainerType& container() { return container_; }
+ const ContainerType& container() const { return container_; }
+
+ // Support operator-> to get to the container. This allows nicer syntax like:
+ // StackContainer<...> foo;
+ // std::sort(foo->begin(), foo->end());
+ ContainerType* operator->() { return &container_; }
+ const ContainerType* operator->() const { return &container_; }
+
+#ifdef UNIT_TEST
+ // Retrieves the stack source so that that unit tests can verify that the
+ // buffer is being used properly.
+ const typename Allocator::Source& stack_data() const {
+ return stack_data_;
+ }
+#endif
+
+ protected:
+ typename Allocator::Source stack_data_;
+ NO_UNIQUE_ADDRESS Allocator allocator_;
+ ContainerType container_;
+};
+
+// Range-based iteration support for StackContainer.
+template <typename TContainerType, int stack_capacity>
+auto begin(
+ const StackContainer<TContainerType, stack_capacity>& stack_container)
+ -> decltype(begin(stack_container.container())) {
+ return begin(stack_container.container());
+}
+
+template <typename TContainerType, int stack_capacity>
+auto begin(StackContainer<TContainerType, stack_capacity>& stack_container)
+ -> decltype(begin(stack_container.container())) {
+ return begin(stack_container.container());
+}
+
+template <typename TContainerType, int stack_capacity>
+auto end(StackContainer<TContainerType, stack_capacity>& stack_container)
+ -> decltype(end(stack_container.container())) {
+ return end(stack_container.container());
+}
+
+template <typename TContainerType, int stack_capacity>
+auto end(const StackContainer<TContainerType, stack_capacity>& stack_container)
+ -> decltype(end(stack_container.container())) {
+ return end(stack_container.container());
+}
+
+// StackVector -----------------------------------------------------------------
+
+// THIS CLASS IS DEPRECATED. Use absl::InlinedVector instead.
+
+// Example:
+// StackVector<int, 16> foo;
+// foo->push_back(22); // we have overloaded operator->
+// foo[0] = 10; // as well as operator[]
+template <typename T,
+ size_t stack_capacity,
+ typename FallbackAllocator = std::allocator<T>>
+class StackVector
+ : public StackContainer<
+ std::vector<T, StackAllocator<T, stack_capacity, FallbackAllocator>>,
+ stack_capacity> {
+ public:
+ StackVector()
+ : StackContainer<
+ std::vector<T,
+ StackAllocator<T, stack_capacity, FallbackAllocator>>,
+ stack_capacity>() {}
+
+ // We need to put this in STL containers sometimes, which requires a copy
+ // constructor. We can't call the regular copy constructor because that will
+ // take the stack buffer from the original. Here, we create an empty object
+ // and make a stack buffer of its own.
+ StackVector(const StackVector<T, stack_capacity, FallbackAllocator>& other)
+ : StackContainer<
+ std::vector<T,
+ StackAllocator<T, stack_capacity, FallbackAllocator>>,
+ stack_capacity>() {
+ this->container().assign(other->begin(), other->end());
+ }
+
+ StackVector<T, stack_capacity, FallbackAllocator>& operator=(
+ const StackVector<T, stack_capacity, FallbackAllocator>& other) {
+ this->container().assign(other->begin(), other->end());
+ return *this;
+ }
+
+ // Vectors are commonly indexed, which isn't very convenient even with
+ // operator-> (using "->at()" does exception stuff we don't want).
+ T& operator[](size_t i) { return this->container().operator[](i); }
+ const T& operator[](size_t i) const {
+ return this->container().operator[](i);
+ }
+};
+
+} // namespace base
+
+// Opt out of libc++ container annotations for StackAllocator. It seems to slow
+// down some tests enough to cause timeouts(?) crbug.com/1444659
+#ifdef _LIBCPP_HAS_ASAN_CONTAINER_ANNOTATIONS_FOR_ALL_ALLOCATORS
+template <typename T, size_t stack_capacity, typename FallbackAllocator>
+struct ::std::__asan_annotate_container_with_allocator<
+ base::StackAllocator<T, stack_capacity, FallbackAllocator>>
+ : ::std::false_type {};
+#endif // _LIBCPP_HAS_ASAN_CONTAINER_ANNOTATIONS_FOR_ALL_ALLOCATORS
+
+#endif // BASE_CONTAINERS_STACK_CONTAINER_H_
diff --git a/base/containers/stack_container_unittest.cc b/base/containers/stack_container_unittest.cc
new file mode 100644
index 0000000000..68c6321824
--- /dev/null
+++ b/base/containers/stack_container_unittest.cc
@@ -0,0 +1,214 @@
+// Copyright 2012 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/containers/stack_container.h"
+
+#include <stddef.h>
+
+#include "base/memory/aligned_memory.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/ranges/algorithm.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+class Dummy : public RefCounted<Dummy> {
+ public:
+ explicit Dummy(int* alive) : alive_(alive) {
+ ++*alive_;
+ }
+
+ private:
+ friend class RefCounted<Dummy>;
+
+ ~Dummy() {
+ --*alive_;
+ }
+
+ const raw_ptr<int> alive_;
+};
+
+} // namespace
+
+TEST(StackContainer, Vector) {
+ const int stack_size = 3;
+ StackVector<int, stack_size> vect;
+ const int* stack_buffer = &vect.stack_data().stack_buffer()[0];
+
+ // The initial |stack_size| elements should appear in the stack buffer.
+ EXPECT_EQ(static_cast<size_t>(stack_size), vect.container().capacity());
+ for (int i = 0; i < stack_size; i++) {
+ vect.container().push_back(i);
+ EXPECT_EQ(stack_buffer, &vect.container()[0]);
+ EXPECT_TRUE(vect.stack_data().used_stack_buffer_);
+ }
+
+ // Adding more elements should push the array onto the heap.
+ for (int i = 0; i < stack_size; i++) {
+ vect.container().push_back(i + stack_size);
+ EXPECT_NE(stack_buffer, &vect.container()[0]);
+ EXPECT_FALSE(vect.stack_data().used_stack_buffer_);
+ }
+
+ // The array should still be in order.
+ for (int i = 0; i < stack_size * 2; i++)
+ EXPECT_EQ(i, vect.container()[i]);
+
+ // Resize to smaller. Our STL implementation won't reallocate in this case,
+ // otherwise it might use our stack buffer. We reserve right after the resize
+ // to guarantee it isn't using the stack buffer, even though it doesn't have
+ // much data.
+ vect.container().resize(stack_size);
+ vect.container().reserve(stack_size * 2);
+ EXPECT_FALSE(vect.stack_data().used_stack_buffer_);
+
+ // Copying the small vector to another should use the same allocator and use
+ // the now-unused stack buffer. GENERALLY CALLERS SHOULD NOT DO THIS since
+ // they have to get the template types just right and it can cause errors.
+ std::vector<int, StackAllocator<int, stack_size, std::allocator<int>>> other(
+ vect.container());
+ EXPECT_EQ(stack_buffer, &other.front());
+ EXPECT_TRUE(vect.stack_data().used_stack_buffer_);
+ for (int i = 0; i < stack_size; i++)
+ EXPECT_EQ(i, other[i]);
+}
+
+TEST(StackContainer, VectorDoubleDelete) {
+ // Regression testing for double-delete.
+ typedef StackVector<scoped_refptr<Dummy>, 2> Vector;
+ Vector vect;
+
+ int alive = 0;
+ scoped_refptr<Dummy> dummy(new Dummy(&alive));
+ EXPECT_EQ(alive, 1);
+
+ vect->push_back(dummy);
+ EXPECT_EQ(alive, 1);
+
+ Dummy* dummy_unref = dummy.get();
+ dummy = nullptr;
+ EXPECT_EQ(alive, 1);
+
+ auto itr = ranges::find(vect, dummy_unref);
+ EXPECT_EQ(itr->get(), dummy_unref);
+ vect->erase(itr);
+ EXPECT_EQ(alive, 0);
+
+ // Shouldn't crash at exit.
+}
+
+namespace {
+
+template <size_t alignment>
+class AlignedData {
+ public:
+ AlignedData() { memset(data_, 0, alignment); }
+ ~AlignedData() = default;
+ alignas(alignment) char data_[alignment];
+};
+
+} // namespace
+
+TEST(StackContainer, BufferAlignment) {
+ StackVector<wchar_t, 16> text;
+ text->push_back(L'A');
+ EXPECT_TRUE(IsAligned(&text[0], alignof(wchar_t)));
+
+ StackVector<double, 1> doubles;
+ doubles->push_back(0.0);
+ EXPECT_TRUE(IsAligned(&doubles[0], alignof(double)));
+
+ StackVector<AlignedData<16>, 1> aligned16;
+ aligned16->push_back(AlignedData<16>());
+ EXPECT_TRUE(IsAligned(&aligned16[0], 16));
+
+#if !defined(__GNUC__) || defined(ARCH_CPU_X86_FAMILY)
+ // It seems that non-X86 gcc doesn't respect greater than 16 byte alignment.
+ // See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=33721 for details.
+ // TODO(sbc): Re-enable this if GCC starts respecting higher alignments.
+ StackVector<AlignedData<256>, 1> aligned256;
+ aligned256->push_back(AlignedData<256>());
+ EXPECT_TRUE(IsAligned(&aligned256[0], 256));
+#endif
+}
+
+template class StackVector<int, 2>;
+template class StackVector<scoped_refptr<Dummy>, 2>;
+
+template <typename T, size_t size>
+void CheckStackVectorElements(const StackVector<T, size>& vec,
+ std::initializer_list<T> expected) {
+ auto expected_it = expected.begin();
+ EXPECT_EQ(vec->size(), expected.size());
+ for (T t : vec) {
+ EXPECT_NE(expected.end(), expected_it);
+ EXPECT_EQ(*expected_it, t);
+ ++expected_it;
+ }
+ EXPECT_EQ(expected.end(), expected_it);
+}
+
+TEST(StackContainer, Iteration) {
+ StackVector<int, 3> vect;
+ vect->push_back(7);
+ vect->push_back(11);
+
+ CheckStackVectorElements(vect, {7, 11});
+ for (int& i : vect) {
+ ++i;
+ }
+ CheckStackVectorElements(vect, {8, 12});
+ vect->push_back(13);
+ CheckStackVectorElements(vect, {8, 12, 13});
+ vect->resize(5);
+ CheckStackVectorElements(vect, {8, 12, 13, 0, 0});
+ vect->resize(1);
+ CheckStackVectorElements(vect, {8});
+}
+
+namespace {
+struct Allocator : std::allocator<int> {
+ using Base = std::allocator<int>;
+
+ int* allocate(size_t n) {
+ ++allocated;
+ return Base::allocate(n);
+ }
+ void deallocate(int* p, size_t n) {
+ ++deallocated;
+ Base::deallocate(p, n);
+ }
+
+ static int allocated;
+ static int deallocated;
+};
+
+int Allocator::allocated = 0;
+int Allocator::deallocated = 0;
+} // namespace
+
+TEST(StackContainer, CustomAllocator) {
+ StackVector<int, 2, Allocator> v;
+
+ EXPECT_EQ(0, Allocator::allocated);
+ EXPECT_EQ(0, Allocator::deallocated);
+
+ v->push_back(1);
+ v->push_back(1);
+ EXPECT_EQ(0, Allocator::allocated);
+ v->push_back(1);
+ EXPECT_EQ(1, Allocator::allocated);
+
+ EXPECT_EQ(0, Allocator::deallocated);
+ v->clear();
+ // shrink_to_fit() makes sure to destroy empty backing store.
+ v->shrink_to_fit();
+ EXPECT_EQ(1, Allocator::deallocated);
+}
+
+} // namespace base
--
2.41.0.640.ga95def55d0-goog