/ externals / biscuit / include / biscuit / code_buffer.hpp
code_buffer.hpp
  1  #pragma once
  2  
  3  #include <cstddef>
  4  #include <cstdint>
  5  #include <cstring>
  6  #include <type_traits>
  7  
  8  #include <biscuit/assert.hpp>
  9  
 10  namespace biscuit {
 11  
 12  /**
 13   * An arbitrarily sized buffer that code is written into.
 14   *
 15   * Also contains other member functions for manipulating
 16   * the data within the code buffer.
 17   */
 18  class CodeBuffer {
 19  public:
 20      // Default capacity of 4KB.
 21      static constexpr size_t default_capacity = 4096;
 22  
 23      /**
 24       * Constructor
 25       *
 26       * @param capacity The initial capacity of the code buffer in bytes.
 27       */
 28      explicit CodeBuffer(size_t capacity = default_capacity);
 29  
 30      /**
 31       * Constructor
 32       *
 33       * @param buffer   A non-null pointer to an allocated buffer of size `capacity`.
 34       * @param capacity The capacity of the memory pointed to by `buffer`.
 35       *
 36       * @pre The given memory buffer must not be null.
 37       * @pre The given memory buffer must be at minimum `capacity` bytes in size.
 38       *
 39       * @note The caller is responsible for managing the lifetime of the given memory.
 40       *       CodeBuffer will *not* free the memory once it goes out of scope.
 41       */
 42      explicit CodeBuffer(uint8_t* buffer, size_t capacity);
 43  
 44      // Copy constructor and assignment is deleted in order to prevent unintentional memory leaks.
 45      CodeBuffer(const CodeBuffer&) = delete;
 46      CodeBuffer& operator=(const CodeBuffer&) = delete;
 47  
 48      // Move constructing or moving the buffer in general is allowed, as it's a transfer of control.
 49      CodeBuffer(CodeBuffer&& other) noexcept;
 50      CodeBuffer& operator=(CodeBuffer&& other) noexcept;
 51  
 52      /**
 53       * Destructor
 54       *
 55       * If a custom memory buffer is not given to the code buffer,
 56       * then the code buffer will automatically free any memory
 57       * it had allocated in order to be able to emit code.
 58       */
 59      ~CodeBuffer() noexcept;
 60  
 61      /// Returns whether or not the memory is managed by the code buffer.
 62      [[nodiscard]] bool IsManaged() const noexcept { return m_is_managed; }
 63  
 64      /// Retrieves the current cursor position within the buffer.
 65      [[nodiscard]] ptrdiff_t GetCursorOffset() const noexcept {
 66          return m_cursor - m_buffer;
 67      }
 68  
 69      /// Retrieves the current address of the cursor within the buffer.
 70      [[nodiscard]] uintptr_t GetCursorAddress() const noexcept {
 71          return GetOffsetAddress(GetCursorOffset());
 72      }
 73  
 74      /// Retrieves the cursor pointer
 75      [[nodiscard]] uint8_t* GetCursorPointer() noexcept {
 76          return GetOffsetPointer(GetCursorOffset());
 77      }
 78  
 79      /// Retrieves the cursor pointer
 80      [[nodiscard]] const uint8_t* GetCursorPointer() const noexcept {
 81          return GetOffsetPointer(GetCursorOffset());
 82      }
 83  
 84      /// Retrieves the address of an arbitrary offset within the buffer.
 85      [[nodiscard]] uintptr_t GetOffsetAddress(ptrdiff_t offset) const noexcept {
 86          return reinterpret_cast<uintptr_t>(GetOffsetPointer(offset));
 87      }
 88  
 89      /// Retrieves the pointer to an arbitrary location within the buffer.
 90      [[nodiscard]] uint8_t* GetOffsetPointer(ptrdiff_t offset) noexcept {
 91          BISCUIT_ASSERT(offset >= 0 && offset <= GetCursorOffset());
 92          return m_buffer + offset;
 93      }
 94  
 95      /// Retrieves the pointer to an arbitrary location within the buffer.
 96      [[nodiscard]] const uint8_t* GetOffsetPointer(ptrdiff_t offset) const noexcept {
 97          BISCUIT_ASSERT(offset >= 0 && offset <= GetCursorOffset());
 98          return m_buffer + offset;
 99      }
100  
101      /**
102       * Allows rewinding of the code buffer cursor.
103       *
104       * @param offset The offset to rewind the cursor by.
105       *
106       * @note If no offset is provided, then this function rewinds the
107       *       cursor to the beginning of the buffer.
108       *
109       * @note The offset may not be larger than the current cursor offset
110       *       and may not be less than the current buffer starting address.
111       */
112      void RewindCursor(ptrdiff_t offset = 0) noexcept {
113          auto* rewound = m_buffer + offset;
114          BISCUIT_ASSERT(m_buffer <= rewound && rewound <= m_cursor);
115          m_cursor = rewound;
116      }
117  
118      /**
119       * Whether or not the underlying buffer has enough room for the
120       * given number of bytes.
121       *
122       * @param num_bytes The number of bytes to store in the buffer.
123       */
124      [[nodiscard]] bool HasSpaceFor(size_t num_bytes) const noexcept {
125          return GetRemainingBytes() >= num_bytes;
126      }
127  
128      /// Returns the size of the data written to the buffer in bytes.
129      [[nodiscard]] size_t GetSizeInBytes() const noexcept {
130          EnsureBufferRange();
131          return static_cast<size_t>(m_cursor - m_buffer);
132      }
133  
134      /// Returns the total number of remaining bytes in the buffer.
135      [[nodiscard]] size_t GetRemainingBytes() const noexcept {
136          EnsureBufferRange();
137          return static_cast<size_t>((m_buffer + m_capacity) - m_cursor);
138      }
139  
140      /**
141       * Grows the underlying memory of the code buffer
142       *
143       * @param new_capacity The new capacity of the code buffer in bytes.
144       *
145       * @pre The underlying memory of the code buffer *must* be managed
146       *      by the code buffer itself. Attempts to grow the buffer
147       *      with memory that is not managed by it will result in
148       *      an assertion being hit.
149       *
150       * @note Calling this with a new capacity that is less than or equal
151       *       to the current capacity of the buffer will result in
152       *       this function doing nothing.
153       */
154      void Grow(size_t new_capacity);
155  
156      /**
157       * Emits a given value into the code buffer.
158       *
159       * @param value The value to emit into the code buffer.
160       * @tparam T    A trivially-copyable type.
161       */
162      template <typename T>
163      void Emit(T value) noexcept {
164          static_assert(std::is_trivially_copyable_v<T>,
165                        "It's undefined behavior to memcpy a non-trivially-copyable type.");
166          BISCUIT_ASSERT(HasSpaceFor(sizeof(T)));
167  
168          std::memcpy(m_cursor, &value, sizeof(T));
169          m_cursor += sizeof(T);
170      }
171  
172      /// Emits a 16-bit value into the code buffer.
173      void Emit16(uint32_t value) noexcept {
174          Emit(static_cast<uint16_t>(value));
175      }
176  
177      /// Emits a 32-bit value into the code buffer.
178      void Emit32(uint32_t value) noexcept {
179          Emit(value);
180      }
181  
182      /**
183       * Sets the internal code buffer to be executable.
184       *
185       * @note This will make the contained region of memory non-writable
186       *       to satisfy operating under W^X contexts. To make the
187       *       region writable again, use SetWritable().
188       */
189      void SetExecutable();
190  
191      /**
192       * Sets the internal code buffer to be writable
193       *
194       * @note This will make the contained region of memory non-executable
195       *       to satisfy operating under W^X contexts. To make the region
196       *       executable again, use SetExecutable().
197       */
198      void SetWritable();
199  
200  private:
201      void EnsureBufferRange() const noexcept {
202          BISCUIT_ASSERT(m_cursor >= m_buffer && m_cursor <= m_buffer + m_capacity);
203      }
204  
205      uint8_t* m_buffer = nullptr;
206      uint8_t* m_cursor = nullptr;
207      size_t m_capacity = 0;
208      bool m_is_managed = false;
209  };
210  
211  } // namespace biscuit