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