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