1 /* 2 * Copyright 2004 The WebRTC Project Authors. All rights reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 #ifndef RTC_BASE_BUFFER_H_ 12 #define RTC_BASE_BUFFER_H_ 13 14 #include <stdint.h> 15 16 #include <algorithm> 17 #include <cstring> 18 #include <memory> 19 #include <type_traits> 20 #include <utility> 21 22 #include "api/array_view.h" 23 #include "rtc_base/checks.h" 24 #include "rtc_base/type_traits.h" 25 #include "rtc_base/zero_memory.h" 26 27 namespace rtc { 28 29 namespace internal { 30 31 // (Internal; please don't use outside this file.) Determines if elements of 32 // type U are compatible with a BufferT<T>. For most types, we just ignore 33 // top-level const and forbid top-level volatile and require T and U to be 34 // otherwise equal, but all byte-sized integers (notably char, int8_t, and 35 // uint8_t) are compatible with each other. (Note: We aim to get rid of this 36 // behavior, and treat all types the same.) 37 template <typename T, typename U> 38 struct BufferCompat { 39 static constexpr bool value = 40 !std::is_volatile<U>::value && 41 ((std::is_integral<T>::value && sizeof(T) == 1) 42 ? (std::is_integral<U>::value && sizeof(U) == 1) 43 : (std::is_same<T, typename std::remove_const<U>::type>::value)); 44 }; 45 46 } // namespace internal 47 48 // Basic buffer class, can be grown and shrunk dynamically. 49 // Unlike std::string/vector, does not initialize data when increasing size. 50 // If "ZeroOnFree" is true, any memory is explicitly cleared before releasing. 51 // The type alias "ZeroOnFreeBuffer" below should be used instead of setting 52 // "ZeroOnFree" in the template manually to "true". 53 template <typename T, bool ZeroOnFree = false> 54 class BufferT { 55 // We want T's destructor and default constructor to be trivial, i.e. perform 56 // no action, so that we don't have to touch the memory we allocate and 57 // deallocate. And we want T to be trivially copyable, so that we can copy T 58 // instances with std::memcpy. This is precisely the definition of a trivial 59 // type. 60 static_assert(std::is_trivial<T>::value, "T must be a trivial type."); 61 62 // This class relies heavily on being able to mutate its data. 63 static_assert(!std::is_const<T>::value, "T may not be const"); 64 65 public: 66 using value_type = T; 67 using const_iterator = const T*; 68 69 // An empty BufferT. BufferT()70 BufferT() : size_(0), capacity_(0), data_(nullptr) { 71 RTC_DCHECK(IsConsistent()); 72 } 73 74 // Disable copy construction and copy assignment, since copying a buffer is 75 // expensive enough that we want to force the user to be explicit about it. 76 BufferT(const BufferT&) = delete; 77 BufferT& operator=(const BufferT&) = delete; 78 BufferT(BufferT && buf)79 BufferT(BufferT&& buf) 80 : size_(buf.size()), 81 capacity_(buf.capacity()), 82 data_(std::move(buf.data_)) { 83 RTC_DCHECK(IsConsistent()); 84 buf.OnMovedFrom(); 85 } 86 87 // Construct a buffer with the specified number of uninitialized elements. BufferT(size_t size)88 explicit BufferT(size_t size) : BufferT(size, size) {} 89 BufferT(size_t size,size_t capacity)90 BufferT(size_t size, size_t capacity) 91 : size_(size), 92 capacity_(std::max(size, capacity)), 93 data_(capacity_ > 0 ? new T[capacity_] : nullptr) { 94 RTC_DCHECK(IsConsistent()); 95 } 96 97 // Construct a buffer and copy the specified number of elements into it. 98 template <typename U, 99 typename std::enable_if< 100 internal::BufferCompat<T, U>::value>::type* = nullptr> BufferT(const U * data,size_t size)101 BufferT(const U* data, size_t size) : BufferT(data, size, size) {} 102 103 template <typename U, 104 typename std::enable_if< 105 internal::BufferCompat<T, U>::value>::type* = nullptr> BufferT(U * data,size_t size,size_t capacity)106 BufferT(U* data, size_t size, size_t capacity) : BufferT(size, capacity) { 107 static_assert(sizeof(T) == sizeof(U), ""); 108 std::memcpy(data_.get(), data, size * sizeof(U)); 109 } 110 111 // Construct a buffer from the contents of an array. 112 template <typename U, 113 size_t N, 114 typename std::enable_if< 115 internal::BufferCompat<T, U>::value>::type* = nullptr> BufferT(U (& array)[N])116 BufferT(U (&array)[N]) : BufferT(array, N) {} 117 ~BufferT()118 ~BufferT() { MaybeZeroCompleteBuffer(); } 119 120 // Get a pointer to the data. Just .data() will give you a (const) T*, but if 121 // T is a byte-sized integer, you may also use .data<U>() for any other 122 // byte-sized integer U. 123 template <typename U = T, 124 typename std::enable_if< 125 internal::BufferCompat<T, U>::value>::type* = nullptr> data()126 const U* data() const { 127 RTC_DCHECK(IsConsistent()); 128 return reinterpret_cast<U*>(data_.get()); 129 } 130 131 template <typename U = T, 132 typename std::enable_if< 133 internal::BufferCompat<T, U>::value>::type* = nullptr> data()134 U* data() { 135 RTC_DCHECK(IsConsistent()); 136 return reinterpret_cast<U*>(data_.get()); 137 } 138 empty()139 bool empty() const { 140 RTC_DCHECK(IsConsistent()); 141 return size_ == 0; 142 } 143 size()144 size_t size() const { 145 RTC_DCHECK(IsConsistent()); 146 return size_; 147 } 148 capacity()149 size_t capacity() const { 150 RTC_DCHECK(IsConsistent()); 151 return capacity_; 152 } 153 154 BufferT& operator=(BufferT&& buf) { 155 RTC_DCHECK(buf.IsConsistent()); 156 MaybeZeroCompleteBuffer(); 157 size_ = buf.size_; 158 capacity_ = buf.capacity_; 159 using std::swap; 160 swap(data_, buf.data_); 161 buf.data_.reset(); 162 buf.OnMovedFrom(); 163 return *this; 164 } 165 166 bool operator==(const BufferT& buf) const { 167 RTC_DCHECK(IsConsistent()); 168 if (size_ != buf.size_) { 169 return false; 170 } 171 if (std::is_integral<T>::value) { 172 // Optimization. 173 return std::memcmp(data_.get(), buf.data_.get(), size_ * sizeof(T)) == 0; 174 } 175 for (size_t i = 0; i < size_; ++i) { 176 if (data_[i] != buf.data_[i]) { 177 return false; 178 } 179 } 180 return true; 181 } 182 183 bool operator!=(const BufferT& buf) const { return !(*this == buf); } 184 185 T& operator[](size_t index) { 186 RTC_DCHECK_LT(index, size_); 187 return data()[index]; 188 } 189 190 T operator[](size_t index) const { 191 RTC_DCHECK_LT(index, size_); 192 return data()[index]; 193 } 194 begin()195 T* begin() { return data(); } end()196 T* end() { return data() + size(); } begin()197 const T* begin() const { return data(); } end()198 const T* end() const { return data() + size(); } cbegin()199 const T* cbegin() const { return data(); } cend()200 const T* cend() const { return data() + size(); } 201 202 // The SetData functions replace the contents of the buffer. They accept the 203 // same input types as the constructors. 204 template <typename U, 205 typename std::enable_if< 206 internal::BufferCompat<T, U>::value>::type* = nullptr> SetData(const U * data,size_t size)207 void SetData(const U* data, size_t size) { 208 RTC_DCHECK(IsConsistent()); 209 const size_t old_size = size_; 210 size_ = 0; 211 AppendData(data, size); 212 if (ZeroOnFree && size_ < old_size) { 213 ZeroTrailingData(old_size - size_); 214 } 215 } 216 217 template <typename U, 218 size_t N, 219 typename std::enable_if< 220 internal::BufferCompat<T, U>::value>::type* = nullptr> SetData(const U (& array)[N])221 void SetData(const U (&array)[N]) { 222 SetData(array, N); 223 } 224 225 template <typename W, 226 typename std::enable_if< 227 HasDataAndSize<const W, const T>::value>::type* = nullptr> SetData(const W & w)228 void SetData(const W& w) { 229 SetData(w.data(), w.size()); 230 } 231 232 // Replaces the data in the buffer with at most |max_elements| of data, using 233 // the function |setter|, which should have the following signature: 234 // 235 // size_t setter(ArrayView<U> view) 236 // 237 // |setter| is given an appropriately typed ArrayView of length exactly 238 // |max_elements| that describes the area where it should write the data; it 239 // should return the number of elements actually written. (If it doesn't fill 240 // the whole ArrayView, it should leave the unused space at the end.) 241 template <typename U = T, 242 typename F, 243 typename std::enable_if< 244 internal::BufferCompat<T, U>::value>::type* = nullptr> SetData(size_t max_elements,F && setter)245 size_t SetData(size_t max_elements, F&& setter) { 246 RTC_DCHECK(IsConsistent()); 247 const size_t old_size = size_; 248 size_ = 0; 249 const size_t written = AppendData<U>(max_elements, std::forward<F>(setter)); 250 if (ZeroOnFree && size_ < old_size) { 251 ZeroTrailingData(old_size - size_); 252 } 253 return written; 254 } 255 256 // The AppendData functions add data to the end of the buffer. They accept 257 // the same input types as the constructors. 258 template <typename U, 259 typename std::enable_if< 260 internal::BufferCompat<T, U>::value>::type* = nullptr> AppendData(const U * data,size_t size)261 void AppendData(const U* data, size_t size) { 262 RTC_DCHECK(IsConsistent()); 263 const size_t new_size = size_ + size; 264 EnsureCapacityWithHeadroom(new_size, true); 265 static_assert(sizeof(T) == sizeof(U), ""); 266 std::memcpy(data_.get() + size_, data, size * sizeof(U)); 267 size_ = new_size; 268 RTC_DCHECK(IsConsistent()); 269 } 270 271 template <typename U, 272 size_t N, 273 typename std::enable_if< 274 internal::BufferCompat<T, U>::value>::type* = nullptr> AppendData(const U (& array)[N])275 void AppendData(const U (&array)[N]) { 276 AppendData(array, N); 277 } 278 279 template <typename W, 280 typename std::enable_if< 281 HasDataAndSize<const W, const T>::value>::type* = nullptr> AppendData(const W & w)282 void AppendData(const W& w) { 283 AppendData(w.data(), w.size()); 284 } 285 286 template <typename U, 287 typename std::enable_if< 288 internal::BufferCompat<T, U>::value>::type* = nullptr> AppendData(const U & item)289 void AppendData(const U& item) { 290 AppendData(&item, 1); 291 } 292 293 // Appends at most |max_elements| to the end of the buffer, using the function 294 // |setter|, which should have the following signature: 295 // 296 // size_t setter(ArrayView<U> view) 297 // 298 // |setter| is given an appropriately typed ArrayView of length exactly 299 // |max_elements| that describes the area where it should write the data; it 300 // should return the number of elements actually written. (If it doesn't fill 301 // the whole ArrayView, it should leave the unused space at the end.) 302 template <typename U = T, 303 typename F, 304 typename std::enable_if< 305 internal::BufferCompat<T, U>::value>::type* = nullptr> AppendData(size_t max_elements,F && setter)306 size_t AppendData(size_t max_elements, F&& setter) { 307 RTC_DCHECK(IsConsistent()); 308 const size_t old_size = size_; 309 SetSize(old_size + max_elements); 310 U* base_ptr = data<U>() + old_size; 311 size_t written_elements = setter(rtc::ArrayView<U>(base_ptr, max_elements)); 312 313 RTC_CHECK_LE(written_elements, max_elements); 314 size_ = old_size + written_elements; 315 RTC_DCHECK(IsConsistent()); 316 return written_elements; 317 } 318 319 // Sets the size of the buffer. If the new size is smaller than the old, the 320 // buffer contents will be kept but truncated; if the new size is greater, 321 // the existing contents will be kept and the new space will be 322 // uninitialized. SetSize(size_t size)323 void SetSize(size_t size) { 324 const size_t old_size = size_; 325 EnsureCapacityWithHeadroom(size, true); 326 size_ = size; 327 if (ZeroOnFree && size_ < old_size) { 328 ZeroTrailingData(old_size - size_); 329 } 330 } 331 332 // Ensure that the buffer size can be increased to at least capacity without 333 // further reallocation. (Of course, this operation might need to reallocate 334 // the buffer.) EnsureCapacity(size_t capacity)335 void EnsureCapacity(size_t capacity) { 336 // Don't allocate extra headroom, since the user is asking for a specific 337 // capacity. 338 EnsureCapacityWithHeadroom(capacity, false); 339 } 340 341 // Resets the buffer to zero size without altering capacity. Works even if the 342 // buffer has been moved from. Clear()343 void Clear() { 344 MaybeZeroCompleteBuffer(); 345 size_ = 0; 346 RTC_DCHECK(IsConsistent()); 347 } 348 349 // Swaps two buffers. Also works for buffers that have been moved from. swap(BufferT & a,BufferT & b)350 friend void swap(BufferT& a, BufferT& b) { 351 using std::swap; 352 swap(a.size_, b.size_); 353 swap(a.capacity_, b.capacity_); 354 swap(a.data_, b.data_); 355 } 356 357 private: EnsureCapacityWithHeadroom(size_t capacity,bool extra_headroom)358 void EnsureCapacityWithHeadroom(size_t capacity, bool extra_headroom) { 359 RTC_DCHECK(IsConsistent()); 360 if (capacity <= capacity_) 361 return; 362 363 // If the caller asks for extra headroom, ensure that the new capacity is 364 // >= 1.5 times the old capacity. Any constant > 1 is sufficient to prevent 365 // quadratic behavior; as to why we pick 1.5 in particular, see 366 // https://github.com/facebook/folly/blob/master/folly/docs/FBVector.md and 367 // http://www.gahcep.com/cpp-internals-stl-vector-part-1/. 368 const size_t new_capacity = 369 extra_headroom ? std::max(capacity, capacity_ + capacity_ / 2) 370 : capacity; 371 372 std::unique_ptr<T[]> new_data(new T[new_capacity]); 373 if (data_ != nullptr) { 374 std::memcpy(new_data.get(), data_.get(), size_ * sizeof(T)); 375 } 376 MaybeZeroCompleteBuffer(); 377 data_ = std::move(new_data); 378 capacity_ = new_capacity; 379 RTC_DCHECK(IsConsistent()); 380 } 381 382 // Zero the complete buffer if template argument "ZeroOnFree" is true. MaybeZeroCompleteBuffer()383 void MaybeZeroCompleteBuffer() { 384 if (ZeroOnFree && capacity_ > 0) { 385 // It would be sufficient to only zero "size_" elements, as all other 386 // methods already ensure that the unused capacity contains no sensitive 387 // data---but better safe than sorry. 388 ExplicitZeroMemory(data_.get(), capacity_ * sizeof(T)); 389 } 390 } 391 392 // Zero the first "count" elements of unused capacity. ZeroTrailingData(size_t count)393 void ZeroTrailingData(size_t count) { 394 RTC_DCHECK(IsConsistent()); 395 RTC_DCHECK_LE(count, capacity_ - size_); 396 ExplicitZeroMemory(data_.get() + size_, count * sizeof(T)); 397 } 398 399 // Precondition for all methods except Clear, operator= and the destructor. 400 // Postcondition for all methods except move construction and move 401 // assignment, which leave the moved-from object in a possibly inconsistent 402 // state. IsConsistent()403 bool IsConsistent() const { 404 return (data_ || capacity_ == 0) && capacity_ >= size_; 405 } 406 407 // Called when *this has been moved from. Conceptually it's a no-op, but we 408 // can mutate the state slightly to help subsequent sanity checks catch bugs. OnMovedFrom()409 void OnMovedFrom() { 410 RTC_DCHECK(!data_); // Our heap block should have been stolen. 411 #if RTC_DCHECK_IS_ON 412 // Ensure that *this is always inconsistent, to provoke bugs. 413 size_ = 1; 414 capacity_ = 0; 415 #else 416 // Make *this consistent and empty. Shouldn't be necessary, but better safe 417 // than sorry. 418 size_ = 0; 419 capacity_ = 0; 420 #endif 421 } 422 423 size_t size_; 424 size_t capacity_; 425 std::unique_ptr<T[]> data_; 426 }; 427 428 // By far the most common sort of buffer. 429 using Buffer = BufferT<uint8_t>; 430 431 // A buffer that zeros memory before releasing it. 432 template <typename T> 433 using ZeroOnFreeBuffer = BufferT<T, true>; 434 435 } // namespace rtc 436 437 #endif // RTC_BASE_BUFFER_H_ 438