/* * Copyright 2004 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef RTC_BASE_BUFFER_H_ #define RTC_BASE_BUFFER_H_ #include #include #include #include #include #include #include "api/array_view.h" #include "rtc_base/checks.h" #include "rtc_base/type_traits.h" #include "rtc_base/zero_memory.h" namespace rtc { namespace internal { // (Internal; please don't use outside this file.) Determines if elements of // type U are compatible with a BufferT. For most types, we just ignore // top-level const and forbid top-level volatile and require T and U to be // otherwise equal, but all byte-sized integers (notably char, int8_t, and // uint8_t) are compatible with each other. (Note: We aim to get rid of this // behavior, and treat all types the same.) template struct BufferCompat { static constexpr bool value = !std::is_volatile::value && ((std::is_integral::value && sizeof(T) == 1) ? (std::is_integral::value && sizeof(U) == 1) : (std::is_same::type>::value)); }; } // namespace internal // Basic buffer class, can be grown and shrunk dynamically. // Unlike std::string/vector, does not initialize data when increasing size. // If "ZeroOnFree" is true, any memory is explicitly cleared before releasing. // The type alias "ZeroOnFreeBuffer" below should be used instead of setting // "ZeroOnFree" in the template manually to "true". template class BufferT { // We want T's destructor and default constructor to be trivial, i.e. perform // no action, so that we don't have to touch the memory we allocate and // deallocate. And we want T to be trivially copyable, so that we can copy T // instances with std::memcpy. This is precisely the definition of a trivial // type. static_assert(std::is_trivial::value, "T must be a trivial type."); // This class relies heavily on being able to mutate its data. static_assert(!std::is_const::value, "T may not be const"); public: using value_type = T; using const_iterator = const T*; // An empty BufferT. BufferT() : size_(0), capacity_(0), data_(nullptr) { RTC_DCHECK(IsConsistent()); } // Disable copy construction and copy assignment, since copying a buffer is // expensive enough that we want to force the user to be explicit about it. BufferT(const BufferT&) = delete; BufferT& operator=(const BufferT&) = delete; BufferT(BufferT&& buf) : size_(buf.size()), capacity_(buf.capacity()), data_(std::move(buf.data_)) { RTC_DCHECK(IsConsistent()); buf.OnMovedFrom(); } // Construct a buffer with the specified number of uninitialized elements. explicit BufferT(size_t size) : BufferT(size, size) {} BufferT(size_t size, size_t capacity) : size_(size), capacity_(std::max(size, capacity)), data_(capacity_ > 0 ? new T[capacity_] : nullptr) { RTC_DCHECK(IsConsistent()); } // Construct a buffer and copy the specified number of elements into it. template ::value>::type* = nullptr> BufferT(const U* data, size_t size) : BufferT(data, size, size) {} template ::value>::type* = nullptr> BufferT(U* data, size_t size, size_t capacity) : BufferT(size, capacity) { static_assert(sizeof(T) == sizeof(U), ""); std::memcpy(data_.get(), data, size * sizeof(U)); } // Construct a buffer from the contents of an array. template ::value>::type* = nullptr> BufferT(U (&array)[N]) : BufferT(array, N) {} ~BufferT() { MaybeZeroCompleteBuffer(); } // Get a pointer to the data. Just .data() will give you a (const) T*, but if // T is a byte-sized integer, you may also use .data() for any other // byte-sized integer U. template ::value>::type* = nullptr> const U* data() const { RTC_DCHECK(IsConsistent()); return reinterpret_cast(data_.get()); } template ::value>::type* = nullptr> U* data() { RTC_DCHECK(IsConsistent()); return reinterpret_cast(data_.get()); } bool empty() const { RTC_DCHECK(IsConsistent()); return size_ == 0; } size_t size() const { RTC_DCHECK(IsConsistent()); return size_; } size_t capacity() const { RTC_DCHECK(IsConsistent()); return capacity_; } BufferT& operator=(BufferT&& buf) { RTC_DCHECK(buf.IsConsistent()); MaybeZeroCompleteBuffer(); size_ = buf.size_; capacity_ = buf.capacity_; using std::swap; swap(data_, buf.data_); buf.data_.reset(); buf.OnMovedFrom(); return *this; } bool operator==(const BufferT& buf) const { RTC_DCHECK(IsConsistent()); if (size_ != buf.size_) { return false; } if (std::is_integral::value) { // Optimization. return std::memcmp(data_.get(), buf.data_.get(), size_ * sizeof(T)) == 0; } for (size_t i = 0; i < size_; ++i) { if (data_[i] != buf.data_[i]) { return false; } } return true; } bool operator!=(const BufferT& buf) const { return !(*this == buf); } T& operator[](size_t index) { RTC_DCHECK_LT(index, size_); return data()[index]; } T operator[](size_t index) const { RTC_DCHECK_LT(index, size_); return data()[index]; } T* begin() { return data(); } T* end() { return data() + size(); } const T* begin() const { return data(); } const T* end() const { return data() + size(); } const T* cbegin() const { return data(); } const T* cend() const { return data() + size(); } // The SetData functions replace the contents of the buffer. They accept the // same input types as the constructors. template ::value>::type* = nullptr> void SetData(const U* data, size_t size) { RTC_DCHECK(IsConsistent()); const size_t old_size = size_; size_ = 0; AppendData(data, size); if (ZeroOnFree && size_ < old_size) { ZeroTrailingData(old_size - size_); } } template ::value>::type* = nullptr> void SetData(const U (&array)[N]) { SetData(array, N); } template ::value>::type* = nullptr> void SetData(const W& w) { SetData(w.data(), w.size()); } // Replaces the data in the buffer with at most |max_elements| of data, using // the function |setter|, which should have the following signature: // // size_t setter(ArrayView view) // // |setter| is given an appropriately typed ArrayView of length exactly // |max_elements| that describes the area where it should write the data; it // should return the number of elements actually written. (If it doesn't fill // the whole ArrayView, it should leave the unused space at the end.) template ::value>::type* = nullptr> size_t SetData(size_t max_elements, F&& setter) { RTC_DCHECK(IsConsistent()); const size_t old_size = size_; size_ = 0; const size_t written = AppendData(max_elements, std::forward(setter)); if (ZeroOnFree && size_ < old_size) { ZeroTrailingData(old_size - size_); } return written; } // The AppendData functions add data to the end of the buffer. They accept // the same input types as the constructors. template ::value>::type* = nullptr> void AppendData(const U* data, size_t size) { RTC_DCHECK(IsConsistent()); const size_t new_size = size_ + size; EnsureCapacityWithHeadroom(new_size, true); static_assert(sizeof(T) == sizeof(U), ""); std::memcpy(data_.get() + size_, data, size * sizeof(U)); size_ = new_size; RTC_DCHECK(IsConsistent()); } template ::value>::type* = nullptr> void AppendData(const U (&array)[N]) { AppendData(array, N); } template ::value>::type* = nullptr> void AppendData(const W& w) { AppendData(w.data(), w.size()); } template ::value>::type* = nullptr> void AppendData(const U& item) { AppendData(&item, 1); } // Appends at most |max_elements| to the end of the buffer, using the function // |setter|, which should have the following signature: // // size_t setter(ArrayView view) // // |setter| is given an appropriately typed ArrayView of length exactly // |max_elements| that describes the area where it should write the data; it // should return the number of elements actually written. (If it doesn't fill // the whole ArrayView, it should leave the unused space at the end.) template ::value>::type* = nullptr> size_t AppendData(size_t max_elements, F&& setter) { RTC_DCHECK(IsConsistent()); const size_t old_size = size_; SetSize(old_size + max_elements); U* base_ptr = data() + old_size; size_t written_elements = setter(rtc::ArrayView(base_ptr, max_elements)); RTC_CHECK_LE(written_elements, max_elements); size_ = old_size + written_elements; RTC_DCHECK(IsConsistent()); return written_elements; } // Sets the size of the buffer. If the new size is smaller than the old, the // buffer contents will be kept but truncated; if the new size is greater, // the existing contents will be kept and the new space will be // uninitialized. void SetSize(size_t size) { const size_t old_size = size_; EnsureCapacityWithHeadroom(size, true); size_ = size; if (ZeroOnFree && size_ < old_size) { ZeroTrailingData(old_size - size_); } } // Ensure that the buffer size can be increased to at least capacity without // further reallocation. (Of course, this operation might need to reallocate // the buffer.) void EnsureCapacity(size_t capacity) { // Don't allocate extra headroom, since the user is asking for a specific // capacity. EnsureCapacityWithHeadroom(capacity, false); } // Resets the buffer to zero size without altering capacity. Works even if the // buffer has been moved from. void Clear() { MaybeZeroCompleteBuffer(); size_ = 0; RTC_DCHECK(IsConsistent()); } // Swaps two buffers. Also works for buffers that have been moved from. friend void swap(BufferT& a, BufferT& b) { using std::swap; swap(a.size_, b.size_); swap(a.capacity_, b.capacity_); swap(a.data_, b.data_); } private: void EnsureCapacityWithHeadroom(size_t capacity, bool extra_headroom) { RTC_DCHECK(IsConsistent()); if (capacity <= capacity_) return; // If the caller asks for extra headroom, ensure that the new capacity is // >= 1.5 times the old capacity. Any constant > 1 is sufficient to prevent // quadratic behavior; as to why we pick 1.5 in particular, see // https://github.com/facebook/folly/blob/master/folly/docs/FBVector.md and // http://www.gahcep.com/cpp-internals-stl-vector-part-1/. const size_t new_capacity = extra_headroom ? std::max(capacity, capacity_ + capacity_ / 2) : capacity; std::unique_ptr new_data(new T[new_capacity]); if (data_ != nullptr) { std::memcpy(new_data.get(), data_.get(), size_ * sizeof(T)); } MaybeZeroCompleteBuffer(); data_ = std::move(new_data); capacity_ = new_capacity; RTC_DCHECK(IsConsistent()); } // Zero the complete buffer if template argument "ZeroOnFree" is true. void MaybeZeroCompleteBuffer() { if (ZeroOnFree && capacity_ > 0) { // It would be sufficient to only zero "size_" elements, as all other // methods already ensure that the unused capacity contains no sensitive // data---but better safe than sorry. ExplicitZeroMemory(data_.get(), capacity_ * sizeof(T)); } } // Zero the first "count" elements of unused capacity. void ZeroTrailingData(size_t count) { RTC_DCHECK(IsConsistent()); RTC_DCHECK_LE(count, capacity_ - size_); ExplicitZeroMemory(data_.get() + size_, count * sizeof(T)); } // Precondition for all methods except Clear, operator= and the destructor. // Postcondition for all methods except move construction and move // assignment, which leave the moved-from object in a possibly inconsistent // state. bool IsConsistent() const { return (data_ || capacity_ == 0) && capacity_ >= size_; } // Called when *this has been moved from. Conceptually it's a no-op, but we // can mutate the state slightly to help subsequent sanity checks catch bugs. void OnMovedFrom() { RTC_DCHECK(!data_); // Our heap block should have been stolen. #if RTC_DCHECK_IS_ON // Ensure that *this is always inconsistent, to provoke bugs. size_ = 1; capacity_ = 0; #else // Make *this consistent and empty. Shouldn't be necessary, but better safe // than sorry. size_ = 0; capacity_ = 0; #endif } size_t size_; size_t capacity_; std::unique_ptr data_; }; // By far the most common sort of buffer. using Buffer = BufferT; // A buffer that zeros memory before releasing it. template using ZeroOnFreeBuffer = BufferT; } // namespace rtc #endif // RTC_BASE_BUFFER_H_