SafePointer.h 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. // zlib open source license
  2. //
  3. // Copyright (c) 2017 to 2024 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. #include "memory.h"
  44. namespace dsr {
  45. #ifdef SAFE_POINTER_CHECKS
  46. 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);
  47. void assertNonNegativeSize(intptr_t size);
  48. #endif
  49. template<typename T>
  50. class SafePointer {
  51. private:
  52. // A pointer from regionStart to regionEnd
  53. // Mutable because only the data being pointed to is write protected in a const SafePointer
  54. mutable T *data;
  55. #ifdef SAFE_POINTER_CHECKS
  56. // Points to the first accessible byte, which should have the same alignment as the data pointer.
  57. mutable T *regionStart;
  58. // Marks the end of the allowed region, pointing to the first byte that is not accessible.
  59. mutable T *regionEnd;
  60. // Pointer to an ascii literal containing the name for improving error messages for crashes in debug mode.
  61. mutable const char *name;
  62. // Optional pointer to an allocation header to know if it still exists and which threads are allowed to access it.
  63. mutable AllocationHeader *header = nullptr;
  64. // The identity that should match the allocation header's identity.
  65. mutable uint64_t allocationIdentity = 0;
  66. #endif
  67. public:
  68. #ifdef SAFE_POINTER_CHECKS
  69. SafePointer() : data(nullptr), regionStart(nullptr), regionEnd(nullptr), name("Unnamed null pointer") {}
  70. explicit SafePointer(const char* name) : data(nullptr), regionStart(nullptr), regionEnd(nullptr), name(name) {}
  71. SafePointer(const char* name, T* regionStart, intptr_t regionByteSize = sizeof(T), AllocationHeader *header = nullptr)
  72. : data(regionStart), regionStart(regionStart), regionEnd((T*)(((uint8_t*)regionStart) + (intptr_t)regionByteSize)), name(name), header(header) {
  73. assertNonNegativeSize(regionByteSize);
  74. // If the pointer has a header, then store the allocation's identity in the pointer.
  75. if (header != nullptr) {
  76. this->allocationIdentity = header->allocationIdentity;
  77. }
  78. }
  79. SafePointer(const char* name, T* regionStart, intptr_t regionByteSize, T* data, AllocationHeader *header = nullptr)
  80. : data(data), regionStart(regionStart), regionEnd((T*)(((uint8_t*)regionStart) + (intptr_t)regionByteSize)), name(name), header(header) {
  81. assertNonNegativeSize(regionByteSize);
  82. // If the pointer has a header, then store the allocation's identity in the pointer.
  83. if (header != nullptr) {
  84. this->allocationIdentity = header->allocationIdentity;
  85. }
  86. }
  87. #else
  88. SafePointer() : data(nullptr) {}
  89. explicit SafePointer(const char* name) : data(nullptr) {}
  90. SafePointer(const char* name, T* regionStart, intptr_t regionByteSize = sizeof(T), AllocationHeader *header = nullptr) : data(regionStart) {}
  91. SafePointer(const char* name, T* regionStart, intptr_t regionByteSize, T* data, AllocationHeader *header = nullptr) : data(data) {}
  92. #endif
  93. public:
  94. #ifdef SAFE_POINTER_CHECKS
  95. inline void assertInside(const char* method, const T* pointer, intptr_t size = (intptr_t)sizeof(T)) const {
  96. 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));
  97. }
  98. inline void assertInside(const char* method) const {
  99. this->assertInside(method, this->data);
  100. }
  101. #endif
  102. public:
  103. // Back to unsafe pointer with a clearly visible method name as a warning
  104. // The same can be done by mistake using the & operator on a reference
  105. // p.getUnsafe() = &(*p) = &(p[0])
  106. inline T* getUnsafe() {
  107. #ifdef SAFE_POINTER_CHECKS
  108. this->assertInside("getUnsafe");
  109. #endif
  110. return this->data;
  111. }
  112. inline const T* getUnsafe() const {
  113. #ifdef SAFE_POINTER_CHECKS
  114. this->assertInside("getUnsafe");
  115. #endif
  116. return this->data;
  117. }
  118. // Get unsafe pointer without bound checks for implementing your own safety
  119. inline T* getUnchecked() {
  120. return this->data;
  121. }
  122. inline const T* getUnchecked() const {
  123. return this->data;
  124. }
  125. // Returns the pointer in modulo byteAlignment
  126. // Returns 0 if the pointer is aligned with byteAlignment
  127. inline int32_t getAlignmentOffset(int32_t byteAlignment) const {
  128. return ((uintptr_t)this->data) % byteAlignment;
  129. }
  130. inline bool isNull() const {
  131. return this->data == nullptr;
  132. }
  133. inline bool isNotNull() const {
  134. return this->data != nullptr;
  135. }
  136. // Get a new safe pointer from a sub-set of data
  137. // byteOffset is which byte in the source will be index zero in the new pointer
  138. // size is the new pointer's size, which may not exceed the remaining available space
  139. inline SafePointer<T> slice(const char* name, intptr_t byteOffset, intptr_t size) {
  140. T *newStart = (T*)(((uint8_t*)(this->data)) + byteOffset);
  141. #ifdef SAFE_POINTER_CHECKS
  142. assertInside("getSlice", newStart, size);
  143. return SafePointer<T>(name, newStart, size, this->header);
  144. #else
  145. return SafePointer<T>(name, newStart);
  146. #endif
  147. }
  148. inline const SafePointer<T> slice(const char* name, intptr_t byteOffset, intptr_t size) const {
  149. T *newStart = (T*)(((uint8_t*)(this->data)) + byteOffset);
  150. #ifdef SAFE_POINTER_CHECKS
  151. assertInside("getSlice", newStart, size);
  152. return SafePointer<T>(name, newStart, size, this->header);
  153. #else
  154. return SafePointer<T>(name, newStart);
  155. #endif
  156. }
  157. // Dereference
  158. template <typename S = T>
  159. inline S& get() {
  160. #ifdef SAFE_POINTER_CHECKS
  161. assertInside("get", this->data, sizeof(S));
  162. #endif
  163. return *((S*)this->data);
  164. }
  165. template <typename S = T>
  166. inline const S& get() const {
  167. #ifdef SAFE_POINTER_CHECKS
  168. assertInside("get", this->data, sizeof(S));
  169. #endif
  170. return *((const S*)this->data);
  171. }
  172. inline T& operator*() {
  173. #ifdef SAFE_POINTER_CHECKS
  174. assertInside("operator*");
  175. #endif
  176. return *(this->data);
  177. }
  178. inline const T& operator*() const {
  179. #ifdef SAFE_POINTER_CHECKS
  180. assertInside("operator*");
  181. #endif
  182. return *(this->data);
  183. }
  184. inline T& operator[] (intptr_t index) {
  185. T* address = this->data + index;
  186. #ifdef SAFE_POINTER_CHECKS
  187. assertInside("operator[]", address);
  188. #endif
  189. return *address;
  190. }
  191. inline const T& operator[] (intptr_t index) const {
  192. T* address = this->data + index;
  193. #ifdef SAFE_POINTER_CHECKS
  194. assertInside("operator[]", address);
  195. #endif
  196. return *address;
  197. }
  198. inline void increaseBytes(intptr_t byteOffset) const {
  199. this->data = (T*)(((uint8_t*)(this->data)) + byteOffset);
  200. }
  201. inline void increaseElements(intptr_t elementOffset) const {
  202. this->data += elementOffset;
  203. }
  204. inline SafePointer<T>& operator+=(intptr_t elementOffset) {
  205. this->data += elementOffset;
  206. return *this;
  207. }
  208. inline const SafePointer<T>& operator+=(intptr_t elementOffset) const {
  209. this->data += elementOffset;
  210. return *this;
  211. }
  212. inline SafePointer<T>& operator-=(intptr_t elementOffset) {
  213. this->data -= elementOffset;
  214. return *this;
  215. }
  216. inline const SafePointer<T>& operator-=(intptr_t elementOffset) const {
  217. this->data -= elementOffset;
  218. return *this;
  219. }
  220. inline SafePointer<T> operator+(intptr_t elementOffset) {
  221. SafePointer<T> result = *this;
  222. result += elementOffset;
  223. return result;
  224. }
  225. inline const SafePointer<T> operator+(intptr_t elementOffset) const {
  226. SafePointer<T> result = *this;
  227. result += elementOffset;
  228. return result;
  229. }
  230. inline SafePointer<T> operator-(intptr_t elementOffset) {
  231. SafePointer<T> result = *this;
  232. result -= elementOffset;
  233. return result;
  234. }
  235. inline const SafePointer<T> operator-(intptr_t elementOffset) const {
  236. SafePointer<T> result = *this;
  237. result -= elementOffset;
  238. return result;
  239. }
  240. inline const SafePointer<T>& operator=(const SafePointer<T>& source) const {
  241. this->data = source.data;
  242. #ifdef SAFE_POINTER_CHECKS
  243. this->header = source.header;
  244. this->allocationIdentity = source.allocationIdentity;
  245. this->regionStart = source.regionStart;
  246. this->regionEnd = source.regionEnd;
  247. this->name = source.name;
  248. #endif
  249. return *this;
  250. }
  251. };
  252. template <typename T, typename S>
  253. inline void safeMemoryCopy(SafePointer<T> target, const SafePointer<S>& source, intptr_t byteSize) {
  254. #ifdef SAFE_POINTER_CHECKS
  255. // Both target and source must be in valid memory
  256. target.assertInside("memoryCopy (target)", target.getUnchecked(), (size_t)byteSize);
  257. source.assertInside("memoryCopy (source)", source.getUnchecked(), (size_t)byteSize);
  258. // memcpy doesn't allow pointer aliasing
  259. // TODO: Make a general assertion with the same style as out of bound exceptions
  260. assert(((const uint8_t*)target.getUnchecked()) + byteSize <= (uint8_t*)source.getUnchecked() || ((const uint8_t*)source.getUnchecked()) + byteSize <= (uint8_t*)target.getUnchecked());
  261. #endif
  262. std::memcpy(target.getUnchecked(), source.getUnchecked(), (size_t)byteSize);
  263. }
  264. template <typename T>
  265. inline void safeMemorySet(SafePointer<T>& target, uint8_t value, intptr_t byteSize) {
  266. #ifdef SAFE_POINTER_CHECKS
  267. // Target must be in valid memory
  268. target.assertInside("memoryCopy (target)", target.getUnchecked(), byteSize);
  269. #endif
  270. std::memset((char*)(target.getUnchecked()), value, (size_t)byteSize);
  271. }
  272. }
  273. #endif