SafePointer.h 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. // zlib open source license
  2. //
  3. // Copyright (c) 2017 to 2025 David Forsgren Piuva
  4. //
  5. // This software is provided 'as-is', without any express or implied
  6. // warranty. In no event will the authors be held liable for any damages
  7. // arising from the use of this software.
  8. //
  9. // Permission is granted to anyone to use this software for any purpose,
  10. // including commercial applications, and to alter it and redistribute it
  11. // freely, subject to the following restrictions:
  12. //
  13. // 1. The origin of this software must not be misrepresented; you must not
  14. // claim that you wrote the original software. If you use this software
  15. // in a product, an acknowledgment in the product documentation would be
  16. // appreciated but is not required.
  17. //
  18. // 2. Altered source versions must be plainly marked as such, and must not be
  19. // misrepresented as being the original software.
  20. //
  21. // 3. This notice may not be removed or altered from any source
  22. // distribution.
  23. // If you get segmentation faults despite using SafePointer, make sure to compile a debug version of the program to activate safety checks.
  24. // In debug mode, bound checks make sure that memory access do not go a single bit outside of the allowed region.
  25. // If SafePointer is constructed with a pointer to the allocation head and its allocation identity (when the memory is allocated by the framework), more safety checks are done in debug mode.
  26. // The allocation identity is a 64-bit nonce stored in both the allocation's head and SafePointer, making sure that the memory accessed has not been freed or reused for something else.
  27. // The 64-bit thread hash prevent access of another thread's private memory, for consistent access rights when the virtual stack may allocate in either thread local or heap memory.
  28. #ifndef DFPSR_SAFE_POINTER
  29. #define DFPSR_SAFE_POINTER
  30. #include <cstring>
  31. #include <cassert>
  32. #include <cstdint>
  33. #include "memory.h"
  34. #include "DsrTraits.h"
  35. namespace dsr {
  36. #ifdef SAFE_POINTER_CHECKS
  37. void impl_assertInsideSafePointer(const char* methodName, const char* pointerName, const uint8_t* claimedStart, const uint8_t* claimedEnd, intptr_t elementSize, const uint8_t* permittedStart, const uint8_t* permittedEnd, const AllocationHeader *header, uint64_t allocationIdentity);
  38. void impl_assertNonNegativeSize(intptr_t size);
  39. #endif
  40. template<typename T>
  41. class SafePointer {
  42. private:
  43. // A pointer from permittedStart to permittedEnd
  44. // Mutable because only the data being pointed to is write protected in a const SafePointer
  45. T *data;
  46. public:
  47. #ifdef SAFE_POINTER_CHECKS
  48. // Points to the first accessible byte, which should have the same alignment as the data pointer.
  49. T *permittedStart;
  50. // Marks the end of the allowed region, pointing to the first byte that is not accessible.
  51. T *permittedEnd;
  52. // Pointer to an ascii literal containing the name for improving error messages for crashes in debug mode.
  53. const char *name;
  54. // Optional pointer to an allocation header to know if it still exists and which threads are allowed to access it.
  55. AllocationHeader *header = nullptr;
  56. // The identity that should match the allocation header's identity.
  57. uint64_t allocationIdentity = 0;
  58. #endif
  59. public:
  60. #ifdef SAFE_POINTER_CHECKS
  61. // Create a null pointer.
  62. SafePointer() : data(nullptr), permittedStart(nullptr), permittedEnd(nullptr), name("Unnamed null pointer") {}
  63. explicit SafePointer(const char* name) : data(nullptr), permittedStart(nullptr), permittedEnd(nullptr), name(name) {}
  64. SafePointer(const char* name, T* permittedStart, intptr_t permittedByteSize = sizeof(T))
  65. : data(permittedStart), permittedStart(permittedStart), permittedEnd((T*)(((uint8_t*)permittedStart) + (intptr_t)permittedByteSize)), name(name) {
  66. impl_assertNonNegativeSize(permittedByteSize);
  67. }
  68. SafePointer(const char* name, T* permittedStart, intptr_t permittedByteSize, T* data)
  69. : data(data), permittedStart(permittedStart), permittedEnd((T*)(((uint8_t*)permittedStart) + (intptr_t)permittedByteSize)), name(name) {
  70. impl_assertNonNegativeSize(permittedByteSize);
  71. }
  72. SafePointer(AllocationHeader *header, uint64_t allocationIdentity, const char* name, T* permittedStart, intptr_t permittedByteSize = sizeof(T))
  73. : data(permittedStart), permittedStart(permittedStart), permittedEnd((T*)(((uint8_t*)permittedStart) + (intptr_t)permittedByteSize)), name(name), header(header), allocationIdentity(allocationIdentity) {
  74. impl_assertNonNegativeSize(permittedByteSize);
  75. }
  76. SafePointer(AllocationHeader *header, uint64_t allocationIdentity, const char* name, T* permittedStart, intptr_t permittedByteSize, T* data)
  77. : data(data), permittedStart(permittedStart), permittedEnd((T*)(((uint8_t*)permittedStart) + (intptr_t)permittedByteSize)), name(name), header(header), allocationIdentity(allocationIdentity) {
  78. impl_assertNonNegativeSize(permittedByteSize);
  79. }
  80. #else
  81. SafePointer() : data(nullptr) {}
  82. explicit SafePointer(const char* name) : data(nullptr) {}
  83. SafePointer(const char* name, T* permittedStart, intptr_t permittedByteSize = sizeof(T)) : data(permittedStart) {}
  84. SafePointer(const char* name, T* permittedStart, intptr_t permittedByteSize, T* data) : data(data) {}
  85. SafePointer(AllocationHeader *header, uint64_t allocationIdentity, const char* name, T* permittedStart, intptr_t permittedByteSize = sizeof(T)) : data(permittedStart) {}
  86. SafePointer(AllocationHeader *header, uint64_t allocationIdentity, const char* name, T* permittedStart, intptr_t permittedByteSize, T* data) : data(data) {}
  87. #endif
  88. public:
  89. #ifdef SAFE_POINTER_CHECKS
  90. inline void assertInside(const char* methodName, const T* claimedStart, intptr_t size = (intptr_t)sizeof(T)) const {
  91. impl_assertInsideSafePointer(methodName, this->name, (const uint8_t*)claimedStart, ((const uint8_t*)claimedStart) + size, sizeof(T), (const uint8_t*)this->permittedStart, (const uint8_t*)this->permittedEnd, this->header, this->allocationIdentity);
  92. }
  93. inline void assertInside(const char* methodName) const {
  94. this->assertInside(methodName, this->data);
  95. }
  96. #endif
  97. public:
  98. // Back to unsafe pointer with a clearly visible method name as a warning
  99. // The same can be done by mistake using the & operator on a reference
  100. // p.getUnsafe() = &(*p) = &(p[0])
  101. inline T* getUnsafe() const {
  102. #ifdef SAFE_POINTER_CHECKS
  103. this->assertInside("getUnsafe");
  104. #endif
  105. return this->data;
  106. }
  107. // Get unsafe pointer without bound checks for implementing your own safety
  108. inline T* getUnchecked() const {
  109. return this->data;
  110. }
  111. inline bool isNull() const {
  112. return this->data == nullptr;
  113. }
  114. inline bool isNotNull() const {
  115. return this->data != nullptr;
  116. }
  117. // Get a new safe pointer from a sub-set of data
  118. // byteOffset is which byte in the source will be index zero in the new pointer
  119. // size is the new pointer's size, which may not exceed the remaining available space
  120. inline SafePointer<T> slice(const char* name, intptr_t byteOffset, intptr_t size) {
  121. T *newStart = (T*)(((uint8_t*)(this->data)) + byteOffset);
  122. #ifdef SAFE_POINTER_CHECKS
  123. assertInside("getSlice", newStart, size);
  124. return SafePointer<T>(this->header, this->allocationIdentity, name, newStart, size);
  125. #else
  126. return SafePointer<T>(name, newStart);
  127. #endif
  128. }
  129. // Dereference
  130. template <typename S = T>
  131. inline S& get() const {
  132. #ifdef SAFE_POINTER_CHECKS
  133. assertInside("get", this->data, sizeof(S));
  134. #endif
  135. return *((S*)this->data);
  136. }
  137. inline T& operator*() const {
  138. #ifdef SAFE_POINTER_CHECKS
  139. assertInside("operator *");
  140. #endif
  141. return *(this->data);
  142. }
  143. inline T* operator ->() const {
  144. #ifdef SAFE_POINTER_CHECKS
  145. assertInside("operator ->");
  146. #endif
  147. return this->data;
  148. }
  149. inline T& operator[] (intptr_t index) const {
  150. T* address = this->data + index;
  151. #ifdef SAFE_POINTER_CHECKS
  152. assertInside("operator []", address);
  153. #endif
  154. return *address;
  155. }
  156. inline SafePointer<T> &increaseBytes(intptr_t byteOffset) {
  157. this->data = (T*)(((uint8_t*)(this->data)) + byteOffset);
  158. return *this;
  159. }
  160. inline SafePointer<T> &increaseElements(intptr_t elementOffset) {
  161. this->data += elementOffset;
  162. return *this;
  163. }
  164. inline SafePointer<T>& operator+=(intptr_t elementOffset) {
  165. this->data += elementOffset;
  166. return *this;
  167. }
  168. inline SafePointer<T>& operator-=(intptr_t elementOffset) {
  169. this->data -= elementOffset;
  170. return *this;
  171. }
  172. inline SafePointer<T> operator+(intptr_t elementOffset) const {
  173. SafePointer<T> result = *this;
  174. result += elementOffset;
  175. return result;
  176. }
  177. inline SafePointer<T> operator-(intptr_t elementOffset) const {
  178. SafePointer<T> result = *this;
  179. result -= elementOffset;
  180. return result;
  181. }
  182. // Copy constructor.
  183. SafePointer(const SafePointer<T> &other) noexcept
  184. : data(other.getUnchecked()) {
  185. #ifdef SAFE_POINTER_CHECKS
  186. this->header = other.header;
  187. this->allocationIdentity = other.allocationIdentity;
  188. this->permittedStart = other.permittedStart;
  189. this->permittedEnd = other.permittedEnd;
  190. this->name = other.name;
  191. #endif
  192. }
  193. // Copy constructor from non-const to const.
  194. template <typename U, DSR_ENABLE_IF(DSR_CHECK_RELATION(DsrTrait_SameType, T, const U))>
  195. SafePointer(const SafePointer<U> &other) noexcept
  196. : data(other.getUnchecked()) {
  197. #ifdef SAFE_POINTER_CHECKS
  198. this->header = other.header;
  199. this->allocationIdentity = other.allocationIdentity;
  200. this->permittedStart = other.permittedStart;
  201. this->permittedEnd = other.permittedEnd;
  202. this->name = other.name;
  203. #endif
  204. }
  205. // Assignment.
  206. SafePointer<T>& operator = (const SafePointer<T> &other) noexcept {
  207. this->data = other.getUnchecked();
  208. #ifdef SAFE_POINTER_CHECKS
  209. this->header = other.header;
  210. this->allocationIdentity = other.allocationIdentity;
  211. this->permittedStart = other.permittedStart;
  212. this->permittedEnd = other.permittedEnd;
  213. this->name = other.name;
  214. #endif
  215. return *this;
  216. }
  217. // Assignment from non-const to const.
  218. template <typename U, DSR_ENABLE_IF(DSR_CHECK_RELATION(DsrTrait_SameType, T, const U))>
  219. SafePointer<T>& operator = (const SafePointer<U> &other) noexcept {
  220. this->data = other.getUnchecked();
  221. #ifdef SAFE_POINTER_CHECKS
  222. this->header = other.header;
  223. this->allocationIdentity = other.allocationIdentity;
  224. this->permittedStart = other.permittedStart;
  225. this->permittedEnd = other.permittedEnd;
  226. this->name = other.name;
  227. #endif
  228. return *this;
  229. }
  230. };
  231. template <typename T, typename S>
  232. inline void safeMemoryCopy(SafePointer<T> target, SafePointer<S> source, intptr_t byteSize) {
  233. T *targetPointer = target.getUnchecked();
  234. const T *sourcePointer = source.getUnchecked();
  235. #ifdef SAFE_POINTER_CHECKS
  236. // Both target and source must be in valid memory
  237. target.assertInside("memoryCopy (target)", targetPointer, (size_t)byteSize);
  238. source.assertInside("memoryCopy (source)", sourcePointer, (size_t)byteSize);
  239. // memcpy doesn't allow pointer aliasing
  240. // TODO: Make a general assertion with the same style as out of bound exceptions
  241. assert(((const uint8_t*)target.getUnchecked()) + byteSize <= (uint8_t*)source.getUnchecked() || ((const uint8_t*)source.getUnchecked()) + byteSize <= (uint8_t*)target.getUnchecked());
  242. assert(targetPointer != nullptr);
  243. assert(sourcePointer != nullptr);
  244. assert(byteSize > 0);
  245. #endif
  246. std::memcpy(targetPointer, sourcePointer, (size_t)byteSize);
  247. }
  248. template <typename T>
  249. inline void safeMemorySet(SafePointer<T> target, uint8_t value, intptr_t byteSize) {
  250. T *targetPointer = target.getUnchecked();
  251. #ifdef SAFE_POINTER_CHECKS
  252. // Target must be in valid memory
  253. target.assertInside("memoryCopy (target)", targetPointer, byteSize);
  254. assert(targetPointer != nullptr);
  255. assert(byteSize > 0);
  256. #endif
  257. std::memset(targetPointer, value, (size_t)byteSize);
  258. }
  259. }
  260. #endif