RangedPtr.h (7832B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* 8 * Implements a smart pointer asserted to remain within a range specified at 9 * construction. 10 */ 11 12 #ifndef mozilla_RangedPtr_h 13 #define mozilla_RangedPtr_h 14 15 #include "mozilla/ArrayUtils.h" 16 #include "mozilla/Assertions.h" 17 #include "mozilla/Attributes.h" 18 19 #include <cstddef> 20 #include <cstdint> 21 22 namespace mozilla { 23 24 /* 25 * RangedPtr is a smart pointer restricted to an address range specified at 26 * creation. The pointer (and any smart pointers derived from it) must remain 27 * within the range [start, end] (inclusive of end to facilitate use as 28 * sentinels). Dereferencing or indexing into the pointer (or pointers derived 29 * from it) must remain within the range [start, end). All the standard pointer 30 * operators are defined on it; in debug builds these operations assert that the 31 * range specified at construction is respected. 32 * 33 * In theory passing a smart pointer instance as an argument can be slightly 34 * slower than passing a T* (due to ABI requirements for passing structs versus 35 * passing pointers), if the method being called isn't inlined. If you are in 36 * extremely performance-critical code, you may want to be careful using this 37 * smart pointer as an argument type. 38 * 39 * RangedPtr<T> intentionally does not implicitly convert to T*. Use get() to 40 * explicitly convert to T*. Keep in mind that the raw pointer of course won't 41 * implement bounds checking in debug builds. 42 */ 43 template <typename T> 44 class RangedPtr { 45 template <typename U> 46 friend class RangedPtr; 47 48 T* mPtr; 49 50 #if defined(DEBUG) || defined(FUZZING) 51 T* const mRangeStart; 52 T* const mRangeEnd; 53 #endif 54 55 void checkSanity() { 56 MOZ_ASSERT_DEBUG_OR_FUZZING(mRangeStart <= mPtr); 57 MOZ_ASSERT_DEBUG_OR_FUZZING(mPtr <= mRangeEnd); 58 } 59 60 /* Creates a new pointer for |aPtr|, restricted to this pointer's range. */ 61 RangedPtr<T> create(T* aPtr) const { 62 #if defined(DEBUG) || defined(FUZZING) 63 return RangedPtr<T>(aPtr, mRangeStart, mRangeEnd); 64 #else 65 return RangedPtr<T>(aPtr, nullptr, nullptr); 66 #endif 67 } 68 69 uintptr_t asUintptr() const { return reinterpret_cast<uintptr_t>(mPtr); } 70 71 public: 72 RangedPtr(T* aPtr, T* aStart, T* aEnd) 73 : mPtr(aPtr) 74 #if defined(DEBUG) || defined(FUZZING) 75 , 76 mRangeStart(aStart), 77 mRangeEnd(aEnd) 78 #endif 79 { 80 MOZ_ASSERT_DEBUG_OR_FUZZING(mRangeStart <= mRangeEnd); 81 checkSanity(); 82 } 83 84 RangedPtr(T* aPtr, T* aStart, size_t aLength) 85 : RangedPtr(aPtr, aStart, aStart + aLength) { 86 /* Extra overflow check. */ 87 MOZ_ASSERT_DEBUG_OR_FUZZING(aLength <= size_t(-1) / sizeof(T)); 88 } 89 90 RangedPtr(T* aPtr, size_t aLength) : RangedPtr(aPtr, aPtr, aLength) {} 91 92 template <size_t N> 93 explicit RangedPtr(T (&aArr)[N]) : RangedPtr(aArr, aArr, N) {} 94 95 RangedPtr(const RangedPtr& aOther) 96 #if defined(DEBUG) || defined(FUZZING) 97 : RangedPtr(aOther.mPtr, aOther.mRangeStart, aOther.mRangeEnd) 98 #else 99 : RangedPtr(aOther.mPtr, nullptr, nullptr) 100 #endif 101 { 102 } 103 104 template <typename U> 105 MOZ_IMPLICIT RangedPtr(const RangedPtr<U>& aOther) 106 #if defined(DEBUG) || defined(FUZZING) 107 : RangedPtr(aOther.mPtr, aOther.mRangeStart, aOther.mRangeEnd) 108 #else 109 : RangedPtr(aOther.mPtr, nullptr, nullptr) 110 #endif 111 { 112 } 113 114 T* get() const { return mPtr; } 115 116 explicit operator bool() const { return mPtr != nullptr; } 117 118 void checkIdenticalRange(const RangedPtr<T>& aOther) const { 119 MOZ_ASSERT_DEBUG_OR_FUZZING(mRangeStart == aOther.mRangeStart); 120 MOZ_ASSERT_DEBUG_OR_FUZZING(mRangeEnd == aOther.mRangeEnd); 121 } 122 123 template <typename U> 124 RangedPtr<U> ReinterpretCast() const { 125 #if defined(DEBUG) || defined(FUZZING) 126 return {reinterpret_cast<U*>(mPtr), reinterpret_cast<U*>(mRangeStart), 127 reinterpret_cast<U*>(mRangeEnd)}; 128 #else 129 return {reinterpret_cast<U*>(mPtr), nullptr, nullptr}; 130 #endif 131 } 132 133 /* 134 * You can only assign one RangedPtr into another if the two pointers have 135 * the same valid range: 136 * 137 * char arr1[] = "hi"; 138 * char arr2[] = "bye"; 139 * RangedPtr<char> p1(arr1, 2); 140 * p1 = RangedPtr<char>(arr1 + 1, arr1, arr1 + 2); // works 141 * p1 = RangedPtr<char>(arr2, 3); // asserts 142 */ 143 RangedPtr<T>& operator=(const RangedPtr<T>& aOther) { 144 checkIdenticalRange(aOther); 145 mPtr = aOther.mPtr; 146 checkSanity(); 147 return *this; 148 } 149 150 RangedPtr<T> operator+(size_t aInc) const { 151 MOZ_ASSERT_DEBUG_OR_FUZZING(aInc <= size_t(-1) / sizeof(T)); 152 MOZ_ASSERT_DEBUG_OR_FUZZING(asUintptr() + aInc * sizeof(T) >= asUintptr()); 153 return create(mPtr + aInc); 154 } 155 156 RangedPtr<T> operator-(size_t aDec) const { 157 MOZ_ASSERT_DEBUG_OR_FUZZING(aDec <= size_t(-1) / sizeof(T)); 158 MOZ_ASSERT_DEBUG_OR_FUZZING(asUintptr() - aDec * sizeof(T) <= asUintptr()); 159 return create(mPtr - aDec); 160 } 161 162 /* 163 * You can assign a raw pointer into a RangedPtr if the raw pointer is 164 * within the range specified at creation. 165 */ 166 template <typename U> 167 RangedPtr<T>& operator=(U* aPtr) { 168 *this = create(aPtr); 169 return *this; 170 } 171 172 template <typename U> 173 RangedPtr<T>& operator=(const RangedPtr<U>& aPtr) { 174 MOZ_ASSERT_DEBUG_OR_FUZZING(mRangeStart <= aPtr.mPtr); 175 MOZ_ASSERT_DEBUG_OR_FUZZING(aPtr.mPtr <= mRangeEnd); 176 mPtr = aPtr.mPtr; 177 checkSanity(); 178 return *this; 179 } 180 181 RangedPtr<T>& operator++() { return (*this += 1); } 182 183 RangedPtr<T> operator++(int) { 184 RangedPtr<T> rcp = *this; 185 ++*this; 186 return rcp; 187 } 188 189 RangedPtr<T>& operator--() { return (*this -= 1); } 190 191 RangedPtr<T> operator--(int) { 192 RangedPtr<T> rcp = *this; 193 --*this; 194 return rcp; 195 } 196 197 RangedPtr<T>& operator+=(size_t aInc) { 198 *this = *this + aInc; 199 return *this; 200 } 201 202 RangedPtr<T>& operator-=(size_t aDec) { 203 *this = *this - aDec; 204 return *this; 205 } 206 207 T& operator[](ptrdiff_t aIndex) const { 208 MOZ_ASSERT_DEBUG_OR_FUZZING(size_t(aIndex > 0 ? aIndex : -aIndex) <= 209 size_t(-1) / sizeof(T)); 210 return *create(mPtr + aIndex); 211 } 212 213 T& operator*() const { 214 MOZ_ASSERT_DEBUG_OR_FUZZING(mPtr >= mRangeStart); 215 MOZ_ASSERT_DEBUG_OR_FUZZING(mPtr < mRangeEnd); 216 return *mPtr; 217 } 218 219 T* operator->() const { 220 MOZ_ASSERT_DEBUG_OR_FUZZING(mPtr >= mRangeStart); 221 MOZ_ASSERT_DEBUG_OR_FUZZING(mPtr < mRangeEnd); 222 return mPtr; 223 } 224 225 template <typename U> 226 bool operator==(const RangedPtr<U>& aOther) const { 227 return mPtr == aOther.mPtr; 228 } 229 template <typename U> 230 bool operator!=(const RangedPtr<U>& aOther) const { 231 return !(*this == aOther); 232 } 233 234 template <typename U> 235 bool operator==(const U* u) const { 236 return mPtr == u; 237 } 238 template <typename U> 239 bool operator!=(const U* u) const { 240 return !(*this == u); 241 } 242 243 bool operator==(std::nullptr_t) const { return mPtr == nullptr; } 244 bool operator!=(std::nullptr_t) const { return mPtr != nullptr; } 245 246 template <typename U> 247 bool operator<(const RangedPtr<U>& aOther) const { 248 return mPtr < aOther.mPtr; 249 } 250 template <typename U> 251 bool operator<=(const RangedPtr<U>& aOther) const { 252 return mPtr <= aOther.mPtr; 253 } 254 255 template <typename U> 256 bool operator>(const RangedPtr<U>& aOther) const { 257 return mPtr > aOther.mPtr; 258 } 259 template <typename U> 260 bool operator>=(const RangedPtr<U>& aOther) const { 261 return mPtr >= aOther.mPtr; 262 } 263 264 size_t operator-(const RangedPtr<T>& aOther) const { 265 MOZ_ASSERT_DEBUG_OR_FUZZING(mPtr >= aOther.mPtr); 266 return PointerRangeSize(aOther.mPtr, mPtr); 267 } 268 269 private: 270 RangedPtr() = delete; 271 }; 272 273 } /* namespace mozilla */ 274 275 #endif /* mozilla_RangedPtr_h */