SafePointer.h 10 KB

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