SafePointer.h 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. // zlib open source license
  2. //
  3. // Copyright (c) 2017 to 2023 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, then check the following.
  24. // * Are you compiling all of your code in debug mode?
  25. // The release mode does not perform SafePointer checks, because it is supposed to be zero overhead by letting the compiler inline the pointers.
  26. // * Did you create a SafePointer from a memory region that you do not have access to, expired stack memory, or a region larger than the allocation?
  27. // SafePointer can not know which memory is safe to call if you do not give it correct information.
  28. // If the pointer was created without an allocation, make sure that regionStart is nullptr and claimedSize is zero.
  29. // * Did you deallocate the memory before using the SafePointer?
  30. // SafePointer can not keep the allocation alive, because that would require counting references in both debug and release.
  31. // To stay safe when using SafePointer:
  32. // * Compile in debug mode by habit, until it is time for profiling or relase.
  33. // The operating system can not detect out of bound access in stack memory or arena allocations, so it may silently corrupt the memory without being caught if safety is disabled.
  34. // * Let the Buffer create the safe pointer for you to prevent accidentally giving the wrong size, or use the default constructor for expressing null.
  35. // If you only need a part of the buffer's memory, use the slice function to get a subset of the memory with bound checks on construction.
  36. // * Either create a SafePointer when needed within the buffer's scope, or store both in the same structure.
  37. // This makes sure that the allocation is not freed while the pointer still exists.
  38. #ifndef DFPSR_SAFE_POINTER
  39. #define DFPSR_SAFE_POINTER
  40. #include <cstring>
  41. #include <cassert>
  42. #include <cstdint>
  43. // Disabled in release mode
  44. #ifndef NDEBUG
  45. #define SAFE_POINTER_CHECKS
  46. #endif
  47. namespace dsr {
  48. struct AllocationHeader {
  49. uintptr_t totalSize; // Size of both header and payload.
  50. #ifdef SAFE_POINTER_CHECKS
  51. uint64_t threadHash; // Hash of the owning thread identity for thread local memory, 0 for shared memory.
  52. uint64_t allocationIdentity; // Rotating identity of the allocation, to know if the memory has been freed and reused within a memory allocator.
  53. #endif
  54. // Header for freed memory.
  55. AllocationHeader();
  56. // Header for allocated memory.
  57. // threadLocal should be true iff the memory may not be accessed from other threads, such as virtual stack memory.
  58. AllocationHeader(uintptr_t totalSize, bool threadLocal);
  59. };
  60. #ifdef SAFE_POINTER_CHECKS
  61. void assertInsideSafePointer(const char* method, const char* name, const uint8_t* pointer, const uint8_t* data, const uint8_t* regionStart, const uint8_t* regionEnd, const AllocationHeader *header, uint64_t allocationIdentity, intptr_t claimedSize, intptr_t elementSize);
  62. void assertNonNegativeSize(intptr_t size);
  63. #endif
  64. template<typename T>
  65. class SafePointer {
  66. private:
  67. // A pointer from regionStart to regionEnd
  68. // Mutable because only the data being pointed to is write protected in a const SafePointer
  69. mutable T *data;
  70. #ifdef SAFE_POINTER_CHECKS
  71. // Points to the first accessible byte, which should have the same alignment as the data pointer.
  72. mutable T *regionStart;
  73. // Marks the end of the allowed region, pointing to the first byte that is not accessible.
  74. mutable T *regionEnd;
  75. // Pointer to an ascii literal containing the name for improving error messages for crashes in debug mode.
  76. mutable const char *name;
  77. // Optional pointer to an allocation header to know if it still exists and which threads are allowed to access it.
  78. mutable AllocationHeader *header = nullptr;
  79. // The identity that should match the allocation header's identity.
  80. mutable uint64_t allocationIdentity = 0;
  81. #endif
  82. public:
  83. #ifdef SAFE_POINTER_CHECKS
  84. SafePointer() : data(nullptr), regionStart(nullptr), regionEnd(nullptr), name("Unnamed null pointer") {}
  85. explicit SafePointer(const char* name) : data(nullptr), regionStart(nullptr), regionEnd(nullptr), name(name) {}
  86. SafePointer(const char* name, T* regionStart, intptr_t regionByteSize = sizeof(T), AllocationHeader *header = nullptr)
  87. : data(regionStart), regionStart(regionStart), regionEnd((T*)(((uint8_t*)regionStart) + (intptr_t)regionByteSize)), name(name), header(header) {
  88. assertNonNegativeSize(regionByteSize);
  89. // If the pointer has a header, then store the allocation's identity in the pointer.
  90. if (header != nullptr) {
  91. this->allocationIdentity = header->allocationIdentity;
  92. }
  93. }
  94. SafePointer(const char* name, T* regionStart, intptr_t regionByteSize, T* data, AllocationHeader *header = nullptr)
  95. : data(data), regionStart(regionStart), regionEnd((T*)(((uint8_t*)regionStart) + (intptr_t)regionByteSize)), name(name), header(header) {
  96. assertNonNegativeSize(regionByteSize);
  97. }
  98. #else
  99. SafePointer() : data(nullptr) {}
  100. explicit SafePointer(const char* name) : data(nullptr) {}
  101. SafePointer(const char* name, T* regionStart, intptr_t regionByteSize = sizeof(T), AllocationHeader *header = nullptr) : data(regionStart) {}
  102. SafePointer(const char* name, T* regionStart, intptr_t regionByteSize, T* data, AllocationHeader *header = nullptr) : data(data) {}
  103. #endif
  104. public:
  105. #ifdef SAFE_POINTER_CHECKS
  106. inline void assertInside(const char* method, const T* pointer, intptr_t size = (intptr_t)sizeof(T)) const {
  107. assertInsideSafePointer(method, this->name, (const uint8_t*)pointer, (const uint8_t*)this->data, (const uint8_t*)this->regionStart, (const uint8_t*)this->regionEnd, this->header, this->allocationIdentity, size, sizeof(T));
  108. }
  109. inline void assertInside(const char* method) const {
  110. this->assertInside(method, this->data);
  111. }
  112. #endif
  113. public:
  114. // Back to unsafe pointer with a clearly visible method name as a warning
  115. // The same can be done by mistake using the & operator on a reference
  116. // p.getUnsafe() = &(*p) = &(p[0])
  117. inline T* getUnsafe() {
  118. #ifdef SAFE_POINTER_CHECKS
  119. this->assertInside("getUnsafe");
  120. #endif
  121. return this->data;
  122. }
  123. inline const T* getUnsafe() const {
  124. #ifdef SAFE_POINTER_CHECKS
  125. this->assertInside("getUnsafe");
  126. #endif
  127. return this->data;
  128. }
  129. // Get unsafe pointer without bound checks for implementing your own safety
  130. inline T* getUnchecked() {
  131. return this->data;
  132. }
  133. inline const T* getUnchecked() const {
  134. return this->data;
  135. }
  136. // Returns the pointer in modulo byteAlignment
  137. // Returns 0 if the pointer is aligned with byteAlignment
  138. inline int32_t getAlignmentOffset(int32_t byteAlignment) const {
  139. return ((uintptr_t)this->data) % byteAlignment;
  140. }
  141. inline bool isNull() const {
  142. return this->data == nullptr;
  143. }
  144. inline bool isNotNull() const {
  145. return this->data != nullptr;
  146. }
  147. // Get a new safe pointer from a sub-set of data
  148. // byteOffset is which byte in the source will be index zero in the new pointer
  149. // size is the new pointer's size, which may not exceed the remaining available space
  150. inline SafePointer<T> slice(const char* name, intptr_t byteOffset, intptr_t size) {
  151. T *newStart = (T*)(((uint8_t*)(this->data)) + byteOffset);
  152. #ifdef SAFE_POINTER_CHECKS
  153. assertInside("getSlice", newStart, size);
  154. return SafePointer<T>(name, newStart, size);
  155. #else
  156. return SafePointer<T>(name, newStart);
  157. #endif
  158. }
  159. inline const SafePointer<T> slice(const char* name, intptr_t byteOffset, intptr_t size) const {
  160. T *newStart = (T*)(((uint8_t*)(this->data)) + byteOffset);
  161. #ifdef SAFE_POINTER_CHECKS
  162. assertInside("getSlice", newStart, size);
  163. return SafePointer<T>(name, newStart, size);
  164. #else
  165. return SafePointer<T>(name, newStart);
  166. #endif
  167. }
  168. // Dereference
  169. template <typename S = T>
  170. inline S& get() {
  171. #ifdef SAFE_POINTER_CHECKS
  172. assertInside("get", this->data, sizeof(S));
  173. #endif
  174. return *((S*)this->data);
  175. }
  176. template <typename S = T>
  177. inline const S& get() const {
  178. #ifdef SAFE_POINTER_CHECKS
  179. assertInside("get", this->data, sizeof(S));
  180. #endif
  181. return *((const S*)this->data);
  182. }
  183. inline T& operator*() {
  184. #ifdef SAFE_POINTER_CHECKS
  185. assertInside("operator*");
  186. #endif
  187. return *(this->data);
  188. }
  189. inline const T& operator*() const {
  190. #ifdef SAFE_POINTER_CHECKS
  191. assertInside("operator*");
  192. #endif
  193. return *(this->data);
  194. }
  195. inline T& operator[] (intptr_t index) {
  196. T* address = this->data + index;
  197. #ifdef SAFE_POINTER_CHECKS
  198. assertInside("operator[]", address);
  199. #endif
  200. return *address;
  201. }
  202. inline const T& operator[] (intptr_t index) const {
  203. T* address = this->data + index;
  204. #ifdef SAFE_POINTER_CHECKS
  205. assertInside("operator[]", address);
  206. #endif
  207. return *address;
  208. }
  209. inline void increaseBytes(intptr_t byteOffset) const {
  210. this->data = (T*)(((uint8_t*)(this->data)) + byteOffset);
  211. }
  212. inline void increaseElements(intptr_t elementOffset) const {
  213. this->data += elementOffset;
  214. }
  215. inline SafePointer<T>& operator+=(intptr_t elementOffset) {
  216. this->data += elementOffset;
  217. return *this;
  218. }
  219. inline const SafePointer<T>& operator+=(intptr_t elementOffset) const {
  220. this->data += elementOffset;
  221. return *this;
  222. }
  223. inline SafePointer<T>& operator-=(intptr_t elementOffset) {
  224. this->data -= elementOffset;
  225. return *this;
  226. }
  227. inline const SafePointer<T>& operator-=(intptr_t elementOffset) const {
  228. this->data -= elementOffset;
  229. return *this;
  230. }
  231. inline SafePointer<T> operator+(intptr_t elementOffset) {
  232. SafePointer<T> result = *this;
  233. result += elementOffset;
  234. return result;
  235. }
  236. inline const SafePointer<T> operator+(intptr_t elementOffset) const {
  237. SafePointer<T> result = *this;
  238. result += elementOffset;
  239. return result;
  240. }
  241. inline SafePointer<T> operator-(intptr_t elementOffset) {
  242. SafePointer<T> result = *this;
  243. result -= elementOffset;
  244. return result;
  245. }
  246. inline const SafePointer<T> operator-(intptr_t elementOffset) const {
  247. SafePointer<T> result = *this;
  248. result -= elementOffset;
  249. return result;
  250. }
  251. inline const SafePointer<T>& operator=(const SafePointer<T>& source) const {
  252. this->data = source.data;
  253. #ifdef SAFE_POINTER_CHECKS
  254. this->header = source.header;
  255. this->allocationIdentity = source.allocationIdentity;
  256. this->regionStart = source.regionStart;
  257. this->regionEnd = source.regionEnd;
  258. this->name = source.name;
  259. #endif
  260. return *this;
  261. }
  262. };
  263. template <typename T, typename S>
  264. inline void safeMemoryCopy(SafePointer<T> target, const SafePointer<S>& source, intptr_t byteSize) {
  265. #ifdef SAFE_POINTER_CHECKS
  266. // Both target and source must be in valid memory
  267. target.assertInside("memoryCopy (target)", target.getUnchecked(), (size_t)byteSize);
  268. source.assertInside("memoryCopy (source)", source.getUnchecked(), (size_t)byteSize);
  269. // memcpy doesn't allow pointer aliasing
  270. // TODO: Make a general assertion with the same style as out of bound exceptions
  271. assert(((const uint8_t*)target.getUnchecked()) + byteSize <= (uint8_t*)source.getUnchecked() || ((const uint8_t*)source.getUnchecked()) + byteSize <= (uint8_t*)target.getUnchecked());
  272. #endif
  273. std::memcpy(target.getUnchecked(), source.getUnchecked(), (size_t)byteSize);
  274. }
  275. template <typename T>
  276. inline void safeMemorySet(SafePointer<T>& target, uint8_t value, intptr_t byteSize) {
  277. #ifdef SAFE_POINTER_CHECKS
  278. // Target must be in valid memory
  279. target.assertInside("memoryCopy (target)", target.getUnchecked(), byteSize);
  280. #endif
  281. std::memset((char*)(target.getUnchecked()), value, (size_t)byteSize);
  282. }
  283. }
  284. #endif