1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183

base / containers / vector_buffer.h [blame]

// Copyright 2017 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_VECTOR_BUFFER_H_
#define BASE_CONTAINERS_VECTOR_BUFFER_H_

#include <stdlib.h>
#include <string.h>

#include <type_traits>
#include <utility>

#include "base/check.h"
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/containers/util.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/numerics/checked_math.h"

namespace base::internal {

// Internal implementation detail of base/containers.
//
// Implements a vector-like buffer that holds a certain capacity of T. Unlike
// std::vector, VectorBuffer never constructs or destructs its arguments, and
// can't change sizes. But it does implement templates to assist in efficient
// moving and destruction of those items manually.
//
// In particular, the destructor function does not iterate over the items if
// there is no destructor. Moves should be implemented as a memcpy/memmove for
// trivially copyable objects (POD) otherwise, it should be a std::move if
// possible, and as a last resort it falls back to a copy. This behavior is
// similar to std::vector.
//
// No special consideration is done for noexcept move constructors since
// we compile without exceptions.
//
// The current API does not support moving overlapping ranges.
template <typename T>
class VectorBuffer {
 public:
  constexpr VectorBuffer() = default;

#if defined(__clang__) && !defined(__native_client__)
  // This constructor converts an uninitialized void* to a T* which triggers
  // clang Control Flow Integrity. Since this is as-designed, disable.
  __attribute__((no_sanitize("cfi-unrelated-cast", "vptr")))
#endif
  VectorBuffer(size_t count)
      : buffer_(reinterpret_cast<T*>(
            malloc(CheckMul(sizeof(T), count).ValueOrDie()))),
        capacity_(count) {
  }
  VectorBuffer(VectorBuffer&& other) noexcept
      : buffer_(other.buffer_), capacity_(other.capacity_) {
    other.buffer_ = nullptr;
    other.capacity_ = 0;
  }

  VectorBuffer(const VectorBuffer&) = delete;
  VectorBuffer& operator=(const VectorBuffer&) = delete;

  ~VectorBuffer() { free(buffer_); }

  VectorBuffer& operator=(VectorBuffer&& other) {
    free(buffer_);
    buffer_ = other.buffer_;
    capacity_ = other.capacity_;

    other.buffer_ = nullptr;
    other.capacity_ = 0;
    return *this;
  }

  size_t capacity() const { return capacity_; }

  T& operator[](size_t i) {
    // TODO(crbug.com/40565371): Some call sites (at least circular_deque.h) are
    // calling this with `i == capacity_` as a way of getting `end()`. Therefore
    // we have to allow this for now (`i <= capacity_`), until we fix those call
    // sites to use real iterators. This comment applies here and to `const T&
    // operator[]`, below.
    CHECK_LT(i, capacity_);
    // SAFETY: `capacity_` is the size of the array pointed to by `buffer_`,
    // which `i` is less than, so the dereference is inside the allocation.
    return UNSAFE_BUFFERS(buffer_[i]);
  }

  const T& operator[](size_t i) const {
    CHECK_LT(i, capacity_);
    // SAFETY: `capacity_` is the size of the array pointed to by `buffer_`,
    // which `i` is less than, so the dereference is inside the allocation.
    return UNSAFE_BUFFERS(buffer_[i]);
  }

  const T* data() const { return buffer_; }
  T* data() { return buffer_; }

  T* begin() { return buffer_; }
  T* end() {
    // SAFETY: `capacity_` is the size of the array pointed to by `buffer_`.
    return UNSAFE_BUFFERS(buffer_ + capacity_);
  }

  span<T> as_span() {
    // SAFETY: The `buffer_` array's size is `capacity_` so this gives the
    // pointer to the start and one-past-the-end of the `buffer_`.
    return UNSAFE_BUFFERS(span(buffer_, buffer_ + capacity_));
  }

  span<T> subspan(size_t index) { return as_span().subspan(index); }

  span<T> subspan(size_t index, size_t size) {
    return as_span().subspan(index, size);
  }

  T* get_at(size_t index) { return as_span().get_at(index); }

  // DestructRange ------------------------------------------------------------

  static void DestructRange(span<T> range) {
    // Trivially destructible objects need not have their destructors called.
    if constexpr (!std::is_trivially_destructible_v<T>) {
      for (T& t : range) {
        t.~T();
      }
    }
  }

  // MoveRange ----------------------------------------------------------------

  template <typename T2>
  static inline constexpr bool is_trivially_copyable_or_relocatable =
      std::is_trivially_copyable_v<T2> || IS_TRIVIALLY_RELOCATABLE(T2);

  // Moves or copies elements from the `from` span to the `to` span. Uses memcpy
  // when possible. The memory in `to` must be uninitialized and the ranges must
  // not overlap.
  //
  // The objects in `from` are destroyed afterward.
  static void MoveConstructRange(span<T> from, span<T> to) {
    CHECK(!RangesOverlap(from, to));
    CHECK_EQ(from.size(), to.size());

    if constexpr (is_trivially_copyable_or_relocatable<T>) {
      // We can't use span::copy_from() as it tries to call copy constructors,
      // and fails to compile on move-only trivially-relocatable types.
      memcpy(to.data(), from.data(), to.size_bytes());
      // Destructors are skipped because they are trivial or should be elided in
      // trivial relocation (https://reviews.llvm.org/D114732).
    } else {
      for (size_t i = 0; i < from.size(); ++i) {
        T* to_pointer = to.subspan(i).data();
        if constexpr (std::move_constructible<T>) {
          new (to_pointer) T(std::move(from[i]));
        } else {
          new (to_pointer) T(from[i]);
        }
        from[i].~T();
      }
    }
  }

 private:
  static bool RangesOverlap(span<T> a, span<T> b) {
    const auto a_start = reinterpret_cast<uintptr_t>(a.data());
    const auto a_end = reinterpret_cast<uintptr_t>(a.data()) + a.size();
    const auto b_start = reinterpret_cast<uintptr_t>(b.data());
    const auto b_end = reinterpret_cast<uintptr_t>(b.data()) + b.size();
    return a_end > b_start && a_start < b_end;
  }

  // `buffer_` is not a raw_ptr<...> for performance reasons (based on analysis
  // of sampling profiler data and tab_search:top100:2020).
  RAW_PTR_EXCLUSION T* buffer_ = nullptr;
  size_t capacity_ = 0;
};

}  // namespace base::internal

#endif  // BASE_CONTAINERS_VECTOR_BUFFER_H_