| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 1 | // Copyright 2024 The Chromium Authors |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #ifndef BASE_CONTAINERS_HEAP_ARRAY_H_ |
| 6 | #define BASE_CONTAINERS_HEAP_ARRAY_H_ |
| 7 | |
| 8 | #include <stddef.h> |
| 9 | |
| 10 | #include <memory> |
| 11 | #include <type_traits> |
| 12 | #include <utility> |
| 13 | |
| Tom Sepez | 03a0b435 | 2024-12-04 16:22:30 | [diff] [blame] | 14 | #include "base/check_op.h" |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 15 | #include "base/compiler_specific.h" |
| 16 | #include "base/containers/span.h" |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 17 | |
| 18 | namespace base { |
| 19 | |
| 20 | // HeapArray<T> is a replacement for std::unique_ptr<T[]> that keeps track |
| 21 | // of its size. It is intended to provide easy conversion to span<T> for most |
| 22 | // usage, but it also provides bounds-checked indexing. |
| 23 | // |
| 24 | // By default, elements in the array are either value-initialized (i.e. zeroed |
| 25 | // for primitive types) when the array is created using the WithSize() |
| 26 | // static method, or uninitialized when the array is created via the Uninit() |
| 27 | // static method. |
| danakj | 216d6edf | 2024-03-28 22:30:30 | [diff] [blame] | 28 | template <typename T, typename Deleter = void> |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 29 | class TRIVIAL_ABI GSL_OWNER HeapArray { |
| 30 | public: |
| 31 | static_assert(!std::is_const_v<T>, "HeapArray cannot hold const types"); |
| 32 | static_assert(!std::is_reference_v<T>, |
| 33 | "HeapArray cannot hold reference types"); |
| 34 | |
| 35 | using iterator = base::span<T>::iterator; |
| 36 | using const_iterator = base::span<const T>::iterator; |
| danakj | 216d6edf | 2024-03-28 22:30:30 | [diff] [blame] | 37 | // We don't put this default value in the template parameter list to allow the |
| 38 | // static_assert on is_reference_v to give a nicer error message. |
| 39 | using deleter_type = std:: |
| 40 | conditional_t<std::is_void_v<Deleter>, std::default_delete<T[]>, Deleter>; |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 41 | |
| 42 | // Allocates initialized memory capable of holding `size` elements. No memory |
| 43 | // is allocated for zero-sized arrays. |
| 44 | static HeapArray WithSize(size_t size) |
| 45 | requires(std::constructible_from<T>) |
| 46 | { |
| 47 | if (!size) { |
| 48 | return HeapArray(); |
| 49 | } |
| danakj | 216d6edf | 2024-03-28 22:30:30 | [diff] [blame] | 50 | return HeapArray(std::unique_ptr<T[], deleter_type>(new T[size]()), size); |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 51 | } |
| 52 | |
| 53 | // Allocates uninitialized memory capable of holding `size` elements. T must |
| 54 | // be trivially constructible and destructible. No memory is allocated for |
| 55 | // zero-sized arrays. |
| 56 | static HeapArray Uninit(size_t size) |
| 57 | requires(std::is_trivially_constructible_v<T> && |
| 58 | std::is_trivially_destructible_v<T>) |
| 59 | { |
| 60 | if (!size) { |
| 61 | return HeapArray(); |
| 62 | } |
| danakj | 216d6edf | 2024-03-28 22:30:30 | [diff] [blame] | 63 | return HeapArray(std::unique_ptr<T[], deleter_type>(new T[size]), size); |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 64 | } |
| 65 | |
| Tom Sepez | 419a9866 | 2024-01-17 17:53:06 | [diff] [blame] | 66 | static HeapArray CopiedFrom(base::span<const T> that) { |
| 67 | auto result = HeapArray::Uninit(that.size()); |
| 68 | result.copy_from(that); |
| 69 | return result; |
| 70 | } |
| 71 | |
| danakj | 216d6edf | 2024-03-28 22:30:30 | [diff] [blame] | 72 | // Constructs a HeapArray from an existing pointer, taking ownership of the |
| 73 | // pointer. |
| 74 | // |
| 75 | // # Safety |
| 76 | // The pointer must be correctly aligned for type `T` and able to be deleted |
| 77 | // through the `deleter_type`, which defaults to the `delete[]` operation. The |
| 78 | // `ptr` must point to an array of at least `size` many elements. If these are |
| 79 | // not met, then Undefined Behaviour can result. |
| 80 | // |
| 81 | // # Checks |
| 82 | // When the `size` is zero, the `ptr` must be null. |
| 83 | UNSAFE_BUFFER_USAGE static HeapArray FromOwningPointer(T* ptr, size_t size) { |
| 84 | if (!size) { |
| 85 | CHECK_EQ(ptr, nullptr); |
| 86 | return HeapArray(); |
| 87 | } |
| 88 | return HeapArray(std::unique_ptr<T[], deleter_type>(ptr), size); |
| 89 | } |
| 90 | |
| Kelsen Liu | a1d268ec | 2025-09-12 18:08:54 | [diff] [blame] | 91 | // Constructs a HeapArray from an existing pointer and a deleter instance, |
| 92 | // taking ownership of both. |
| 93 | // |
| 94 | // # Safety |
| 95 | // The pointer must be correctly aligned for type `T` and able to be deleted |
| 96 | // through the provided `deleter` instance. The `ptr` must point to an array |
| 97 | // of at least `size` many elements. If these are not met, then Undefined |
| 98 | // Behaviour can result. |
| 99 | // |
| 100 | // # Checks |
| 101 | // When the `size` is zero, the `ptr` must be null. |
| 102 | UNSAFE_BUFFER_USAGE static HeapArray FromOwningPointer(T* ptr, |
| 103 | size_t size, |
| 104 | deleter_type deleter) { |
| 105 | if (!size) { |
| 106 | CHECK_EQ(ptr, nullptr); |
| 107 | return HeapArray( |
| 108 | std::unique_ptr<T[], deleter_type>(nullptr, std::move(deleter)), 0); |
| 109 | } |
| 110 | return HeapArray( |
| 111 | std::unique_ptr<T[], deleter_type>(ptr, std::move(deleter)), size); |
| 112 | } |
| 113 | |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 114 | // Constructs an empty array and does not allocate any memory. |
| 115 | HeapArray() |
| 116 | requires(std::constructible_from<T>) |
| 117 | = default; |
| 118 | |
| 119 | // Move-only type since the memory is owned. |
| 120 | HeapArray(const HeapArray&) = delete; |
| 121 | HeapArray& operator=(const HeapArray&) = delete; |
| 122 | |
| 123 | // Move-construction leaves the moved-from object empty and containing |
| 124 | // no allocated memory. |
| 125 | HeapArray(HeapArray&& that) |
| 126 | : data_(std::move(that.data_)), size_(std::exchange(that.size_, 0u)) {} |
| 127 | |
| 128 | // Move-assigment leaves the moved-from object empty and containing |
| 129 | // no allocated memory. |
| 130 | HeapArray& operator=(HeapArray&& that) { |
| 131 | data_ = std::move(that.data_); |
| 132 | size_ = std::exchange(that.size_, 0u); |
| 133 | return *this; |
| 134 | } |
| 135 | ~HeapArray() = default; |
| 136 | |
| 137 | bool empty() const { return size_ == 0u; } |
| 138 | size_t size() const { return size_; } |
| 139 | |
| 140 | // Prefer span-based methods below over data() where possible. The data() |
| 141 | // method exists primarily to allow implicit constructions of spans. |
| 142 | // Returns nullptr for a zero-sized (or moved-from) array. |
| Peter Kasting | 4b18d0c | 2024-09-18 00:56:11 | [diff] [blame] | 143 | T* data() LIFETIME_BOUND { return data_.get(); } |
| 144 | const T* data() const LIFETIME_BOUND { return data_.get(); } |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 145 | |
| Peter Kasting | 4b18d0c | 2024-09-18 00:56:11 | [diff] [blame] | 146 | iterator begin() LIFETIME_BOUND { return as_span().begin(); } |
| 147 | const_iterator begin() const LIFETIME_BOUND { return as_span().begin(); } |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 148 | |
| Peter Kasting | 4b18d0c | 2024-09-18 00:56:11 | [diff] [blame] | 149 | iterator end() LIFETIME_BOUND { return as_span().end(); } |
| 150 | const_iterator end() const LIFETIME_BOUND { return as_span().end(); } |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 151 | |
| Tom Sepez | 03a0b435 | 2024-12-04 16:22:30 | [diff] [blame] | 152 | ALWAYS_INLINE T& operator[](size_t idx) LIFETIME_BOUND { |
| 153 | CHECK_LT(idx, size_); |
| 154 | // SAFETY: bounds checked above. |
| 155 | return UNSAFE_BUFFERS(data_.get()[idx]); |
| 156 | } |
| 157 | ALWAYS_INLINE const T& operator[](size_t idx) const LIFETIME_BOUND { |
| 158 | CHECK_LT(idx, size_); |
| 159 | // SAFETY: bounds checked above. |
| 160 | return UNSAFE_BUFFERS(data_.get()[idx]); |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 161 | } |
| 162 | |
| 163 | // Access the HeapArray via spans. Note that span<T> is implicilty |
| danakj | 1018fdb | 2024-01-09 21:21:50 | [diff] [blame] | 164 | // constructible from HeapArray<T>, so an explicit call to .as_span() is |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 165 | // most useful, say, when the compiler can't deduce a template |
| 166 | // argument type. |
| Tom Sepez | 03a0b435 | 2024-12-04 16:22:30 | [diff] [blame] | 167 | ALWAYS_INLINE base::span<T> as_span() LIFETIME_BOUND { |
| danakj | dcf74b7 | 2024-07-16 23:06:29 | [diff] [blame] | 168 | // SAFETY: `size_` is the number of elements in the `data_` allocation` at |
| 169 | // all times. |
| 170 | return UNSAFE_BUFFERS(base::span<T>(data_.get(), size_)); |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 171 | } |
| Tom Sepez | 03a0b435 | 2024-12-04 16:22:30 | [diff] [blame] | 172 | ALWAYS_INLINE base::span<const T> as_span() const LIFETIME_BOUND { |
| danakj | dcf74b7 | 2024-07-16 23:06:29 | [diff] [blame] | 173 | // SAFETY: `size_` is the number of elements in the `data_` allocation` at |
| 174 | // all times. |
| 175 | return UNSAFE_BUFFERS(base::span<const T>(data_.get(), size_)); |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 176 | } |
| 177 | |
| Tom Sepez | 7bda540 | 2024-08-13 21:22:21 | [diff] [blame] | 178 | // Convenience method to copy the contents of a span<> into the entire array. |
| 179 | // Hard CHECK occurs in span<>::copy_from() if the HeapArray and the span |
| 180 | // have different sizes. |
| Tom Sepez | 648a427 | 2024-01-12 18:21:34 | [diff] [blame] | 181 | void copy_from(base::span<const T> other) { as_span().copy_from(other); } |
| 182 | |
| Tom Sepez | 7bda540 | 2024-08-13 21:22:21 | [diff] [blame] | 183 | // Convenience method to copy the contents of a span<> into the start of the |
| 184 | // array. Hard CHECK occurs in span<>::copy_prefix_from() if the HeapArray |
| 185 | // isn't large enough to contain the entire span. |
| 186 | void copy_prefix_from(base::span<const T> other) { |
| 187 | as_span().copy_prefix_from(other); |
| 188 | } |
| 189 | |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 190 | // Convenience methods to slice the vector into spans. |
| 191 | // Returns a span over the HeapArray starting at `offset` of `count` elements. |
| 192 | // If `count` is unspecified, all remaining elements are included. A CHECK() |
| 193 | // occurs if any of the parameters results in an out-of-range position in |
| 194 | // the HeapArray. |
| Tom Sepez | c7cadace | 2025-02-24 19:18:26 | [diff] [blame] | 195 | base::span<T> subspan(size_t offset) LIFETIME_BOUND { |
| 196 | return as_span().subspan(offset); |
| 197 | } |
| 198 | base::span<const T> subspan(size_t offset) const LIFETIME_BOUND { |
| 199 | return as_span().subspan(offset); |
| 200 | } |
| 201 | base::span<T> subspan(size_t offset, size_t count) LIFETIME_BOUND { |
| danakj | 1018fdb | 2024-01-09 21:21:50 | [diff] [blame] | 202 | return as_span().subspan(offset, count); |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 203 | } |
| 204 | base::span<const T> subspan(size_t offset, |
| Tom Sepez | c7cadace | 2025-02-24 19:18:26 | [diff] [blame] | 205 | size_t count) const LIFETIME_BOUND { |
| danakj | 1018fdb | 2024-01-09 21:21:50 | [diff] [blame] | 206 | return as_span().subspan(offset, count); |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 207 | } |
| 208 | |
| 209 | // Returns a span over the first `count` elements of the HeapArray. A CHECK() |
| 210 | // occurs if the `count` is larger than size of the HeapArray. |
| Peter Kasting | 4b18d0c | 2024-09-18 00:56:11 | [diff] [blame] | 211 | base::span<T> first(size_t count) LIFETIME_BOUND { |
| danakj | 1018fdb | 2024-01-09 21:21:50 | [diff] [blame] | 212 | return as_span().first(count); |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 213 | } |
| Peter Kasting | 4b18d0c | 2024-09-18 00:56:11 | [diff] [blame] | 214 | base::span<const T> first(size_t count) const LIFETIME_BOUND { |
| danakj | 1018fdb | 2024-01-09 21:21:50 | [diff] [blame] | 215 | return as_span().first(count); |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 216 | } |
| 217 | |
| 218 | // Returns a span over the last `count` elements of the HeapArray. A CHECK() |
| 219 | // occurs if the `count` is larger than size of the HeapArray. |
| Peter Kasting | 4b18d0c | 2024-09-18 00:56:11 | [diff] [blame] | 220 | base::span<T> last(size_t count) LIFETIME_BOUND { |
| danakj | 1018fdb | 2024-01-09 21:21:50 | [diff] [blame] | 221 | return as_span().last(count); |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 222 | } |
| Peter Kasting | 4b18d0c | 2024-09-18 00:56:11 | [diff] [blame] | 223 | base::span<const T> last(size_t count) const LIFETIME_BOUND { |
| danakj | 1018fdb | 2024-01-09 21:21:50 | [diff] [blame] | 224 | return as_span().last(count); |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 225 | } |
| 226 | |
| danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 227 | // Leaks the memory in the HeapArray so that it will never be freed, and |
| 228 | // consumes the HeapArray, returning an unowning span that points to the |
| 229 | // memory. |
| 230 | base::span<T> leak() && { |
| 231 | HeapArray<T> dropped = std::move(*this); |
| 232 | T* leaked = dropped.data_.release(); |
| danakj | dcf74b7 | 2024-07-16 23:06:29 | [diff] [blame] | 233 | // SAFETY: The `size_` is the number of elements in the allocation in |
| 234 | // `data_` at all times, which is renamed as `leaked` here. |
| 235 | return UNSAFE_BUFFERS(span(leaked, dropped.size_)); |
| danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 236 | } |
| 237 | |
| Dale Curtis | ea39465 | 2024-07-12 21:01:15 | [diff] [blame] | 238 | // Allows construction of a smaller HeapArray from an existing HeapArray w/o |
| 239 | // reallocations or copies. Note: The original allocation is preserved, so |
| 240 | // CopiedFrom() should be preferred for significant size reductions. |
| 241 | base::HeapArray<T> take_first(size_t reduced_size) && { |
| 242 | CHECK_LE(reduced_size, size_); |
| 243 | size_ = 0u; |
| 244 | if (!reduced_size) { |
| 245 | data_.reset(); |
| 246 | } |
| 247 | return base::HeapArray(std::move(data_), reduced_size); |
| 248 | } |
| 249 | |
| Tom Sepez | d09479a2 | 2024-03-05 22:37:02 | [diff] [blame] | 250 | // Delete the memory previously obtained from leak(). Argument is a pointer |
| 251 | // rather than a span to facilitate use by callers that have lost track of |
| 252 | // size information, as may happen when being passed through a C-style |
| 253 | // function callback. The void* argument type makes its signature compatible |
| 254 | // with typical void (*cb)(void*) C-style deletion callback. |
| 255 | static void DeleteLeakedData(void* ptr) { |
| 256 | // Memory is freed by unique ptr going out of scope. |
| danakj | 216d6edf | 2024-03-28 22:30:30 | [diff] [blame] | 257 | std::unique_ptr<T[], deleter_type> deleter(static_cast<T*>(ptr)); |
| Tom Sepez | d09479a2 | 2024-03-05 22:37:02 | [diff] [blame] | 258 | } |
| 259 | |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 260 | private: |
| danakj | 216d6edf | 2024-03-28 22:30:30 | [diff] [blame] | 261 | HeapArray(std::unique_ptr<T[], deleter_type> data, size_t size) |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 262 | : data_(std::move(data)), size_(size) {} |
| 263 | |
| danakj | 216d6edf | 2024-03-28 22:30:30 | [diff] [blame] | 264 | std::unique_ptr<T[], deleter_type> data_; |
| Tom Sepez | d986abb2 | 2024-01-05 23:32:23 | [diff] [blame] | 265 | size_t size_ = 0u; |
| 266 | }; |
| 267 | |
| 268 | } // namespace base |
| 269 | |
| 270 | #endif // BASE_CONTAINERS_HEAP_ARRAY_H_ |