1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 /*
6  * tracker.c
7  *
8  * This file contains the code used by the pointer-tracking calls used
9  * in the debug builds to catch bad pointers.  The entire contents are
10  * only available in debug builds (both internal and external builds).
11  */
12 
13 #ifndef BASE_H
14 #include "base.h"
15 #endif /* BASE_H */
16 
17 #ifdef DEBUG
18 /*
19  * identity_hash
20  *
21  * This static callback is a PLHashFunction as defined in plhash.h
22  * It merely returns the value of the object pointer as its hash.
23  * There are no possible errors.
24  */
25 
26 static PLHashNumber PR_CALLBACK
identity_hash(const void * key)27 identity_hash(const void *key)
28 {
29     return (PLHashNumber)((char *)key - (char *)NULL);
30 }
31 
32 /*
33  * trackerOnceFunc
34  *
35  * This function is called once, using the nssCallOnce function above.
36  * It creates a new pointer tracker object; initialising its hash
37  * table and protective lock.
38  */
39 
40 static PRStatus
trackerOnceFunc(void * arg)41 trackerOnceFunc(void *arg)
42 {
43     nssPointerTracker *tracker = (nssPointerTracker *)arg;
44 
45     tracker->lock = PZ_NewLock(nssILockOther);
46     if ((PZLock *)NULL == tracker->lock) {
47         return PR_FAILURE;
48     }
49 
50     tracker->table =
51         PL_NewHashTable(0, identity_hash, PL_CompareValues, PL_CompareValues,
52                         (PLHashAllocOps *)NULL, (void *)NULL);
53     if ((PLHashTable *)NULL == tracker->table) {
54         PZ_DestroyLock(tracker->lock);
55         tracker->lock = (PZLock *)NULL;
56         return PR_FAILURE;
57     }
58 
59     return PR_SUCCESS;
60 }
61 
62 /*
63  * nssPointerTracker_initialize
64  *
65  * This method is only present in debug builds.
66  *
67  * This routine initializes an nssPointerTracker object.  Note that
68  * the object must have been declared *static* to guarantee that it
69  * is in a zeroed state initially.  This routine is idempotent, and
70  * may even be safely called by multiple threads simultaneously with
71  * the same argument.  This routine returns a PRStatus value; if
72  * successful, it will return PR_SUCCESS.  On failure it will set an
73  * error on the error stack and return PR_FAILURE.
74  *
75  * The error may be one of the following values:
76  *  NSS_ERROR_NO_MEMORY
77  *
78  * Return value:
79  *  PR_SUCCESS
80  *  PR_FAILURE
81  */
82 
83 NSS_IMPLEMENT PRStatus
nssPointerTracker_initialize(nssPointerTracker * tracker)84 nssPointerTracker_initialize(nssPointerTracker *tracker)
85 {
86     PRStatus rv = PR_CallOnceWithArg(&tracker->once, trackerOnceFunc, tracker);
87     if (PR_SUCCESS != rv) {
88         nss_SetError(NSS_ERROR_NO_MEMORY);
89     }
90 
91     return rv;
92 }
93 
94 #ifdef DONT_DESTROY_EMPTY_TABLES
95 /* See same #ifdef below */
96 /*
97  * count_entries
98  *
99  * This static routine is a PLHashEnumerator, as defined in plhash.h.
100  * It merely causes the enumeration function to count the number of
101  * entries.
102  */
103 
104 static PRIntn PR_CALLBACK
count_entries(PLHashEntry * he,PRIntn index,void * arg)105 count_entries(PLHashEntry *he, PRIntn index, void *arg)
106 {
107     return HT_ENUMERATE_NEXT;
108 }
109 #endif /* DONT_DESTROY_EMPTY_TABLES */
110 
111 /*
112  * zero_once
113  *
114  * This is a guaranteed zeroed once block.  It's used to help clear
115  * the tracker.
116  */
117 
118 static const PRCallOnceType zero_once;
119 
120 /*
121  * nssPointerTracker_finalize
122  *
123  * This method is only present in debug builds.
124  *
125  * This routine returns the nssPointerTracker object to the pre-
126  * initialized state, releasing all resources used by the object.
127  * It will *NOT* destroy the objects being tracked by the pointer
128  * (should any remain), and therefore cannot be used to "sweep up"
129  * remaining objects.  This routine returns a PRStatus value; if
130  * successful, it will return PR_SUCCES.  On failure it will set an
131  * error on the error stack and return PR_FAILURE.  If any objects
132  * remain in the tracker when it is finalized, that will be treated
133  * as an error.
134  *
135  * The error may be one of the following values:
136  *  NSS_ERROR_INVALID_POINTER
137  *  NSS_ERROR_TRACKER_NOT_INITIALIZED
138  *  NSS_ERROR_TRACKER_NOT_EMPTY
139  *
140  * Return value:
141  *  PR_SUCCESS
142  *  PR_FAILURE
143  */
144 
145 NSS_IMPLEMENT PRStatus
nssPointerTracker_finalize(nssPointerTracker * tracker)146 nssPointerTracker_finalize(nssPointerTracker *tracker)
147 {
148     PZLock *lock;
149 
150     if ((nssPointerTracker *)NULL == tracker) {
151         nss_SetError(NSS_ERROR_INVALID_POINTER);
152         return PR_FAILURE;
153     }
154 
155     if ((PZLock *)NULL == tracker->lock) {
156         nss_SetError(NSS_ERROR_TRACKER_NOT_INITIALIZED);
157         return PR_FAILURE;
158     }
159 
160     lock = tracker->lock;
161     PZ_Lock(lock);
162 
163     if ((PLHashTable *)NULL == tracker->table) {
164         PZ_Unlock(lock);
165         nss_SetError(NSS_ERROR_TRACKER_NOT_INITIALIZED);
166         return PR_FAILURE;
167     }
168 
169 #ifdef DONT_DESTROY_EMPTY_TABLES
170     /*
171      * I changed my mind; I think we don't want this after all.
172      * Comments?
173      */
174     count = PL_HashTableEnumerateEntries(tracker->table, count_entries,
175                                          (void *)NULL);
176 
177     if (0 != count) {
178         PZ_Unlock(lock);
179         nss_SetError(NSS_ERROR_TRACKER_NOT_EMPTY);
180         return PR_FAILURE;
181     }
182 #endif /* DONT_DESTROY_EMPTY_TABLES */
183 
184     PL_HashTableDestroy(tracker->table);
185     /* memset(tracker, 0, sizeof(nssPointerTracker)); */
186     tracker->once = zero_once;
187     tracker->lock = (PZLock *)NULL;
188     tracker->table = (PLHashTable *)NULL;
189 
190     PZ_Unlock(lock);
191     PZ_DestroyLock(lock);
192 
193     return PR_SUCCESS;
194 }
195 
196 /*
197  * nssPointerTracker_add
198  *
199  * This method is only present in debug builds.
200  *
201  * This routine adds the specified pointer to the nssPointerTracker
202  * object.  It should be called in constructor objects to register
203  * new valid objects.  The nssPointerTracker is threadsafe, but this
204  * call is not idempotent.  This routine returns a PRStatus value;
205  * if successful it will return PR_SUCCESS.  On failure it will set
206  * an error on the error stack and return PR_FAILURE.
207  *
208  * The error may be one of the following values:
209  *  NSS_ERROR_INVALID_POINTER
210  *  NSS_ERROR_NO_MEMORY
211  *  NSS_ERROR_TRACKER_NOT_INITIALIZED
212  *  NSS_ERROR_DUPLICATE_POINTER
213  *
214  * Return value:
215  *  PR_SUCCESS
216  *  PR_FAILURE
217  */
218 
219 NSS_IMPLEMENT PRStatus
nssPointerTracker_add(nssPointerTracker * tracker,const void * pointer)220 nssPointerTracker_add(nssPointerTracker *tracker, const void *pointer)
221 {
222     void *check;
223     PLHashEntry *entry;
224 
225     if ((nssPointerTracker *)NULL == tracker) {
226         nss_SetError(NSS_ERROR_INVALID_POINTER);
227         return PR_FAILURE;
228     }
229 
230     if ((PZLock *)NULL == tracker->lock) {
231         nss_SetError(NSS_ERROR_TRACKER_NOT_INITIALIZED);
232         return PR_FAILURE;
233     }
234 
235     PZ_Lock(tracker->lock);
236 
237     if ((PLHashTable *)NULL == tracker->table) {
238         PZ_Unlock(tracker->lock);
239         nss_SetError(NSS_ERROR_TRACKER_NOT_INITIALIZED);
240         return PR_FAILURE;
241     }
242 
243     check = PL_HashTableLookup(tracker->table, pointer);
244     if ((void *)NULL != check) {
245         PZ_Unlock(tracker->lock);
246         nss_SetError(NSS_ERROR_DUPLICATE_POINTER);
247         return PR_FAILURE;
248     }
249 
250     entry = PL_HashTableAdd(tracker->table, pointer, (void *)pointer);
251 
252     PZ_Unlock(tracker->lock);
253 
254     if ((PLHashEntry *)NULL == entry) {
255         nss_SetError(NSS_ERROR_NO_MEMORY);
256         return PR_FAILURE;
257     }
258 
259     return PR_SUCCESS;
260 }
261 
262 /*
263  * nssPointerTracker_remove
264  *
265  * This method is only present in debug builds.
266  *
267  * This routine removes the specified pointer from the
268  * nssPointerTracker object.  It does not call any destructor for the
269  * object; rather, this should be called from the object's destructor.
270  * The nssPointerTracker is threadsafe, but this call is not
271  * idempotent.  This routine returns a PRStatus value; if successful
272  * it will return PR_SUCCESS.  On failure it will set an error on the
273  * error stack and return PR_FAILURE.
274  *
275  * The error may be one of the following values:
276  *  NSS_ERROR_INVALID_POINTER
277  *  NSS_ERROR_TRACKER_NOT_INITIALIZED
278  *  NSS_ERROR_POINTER_NOT_REGISTERED
279  *
280  * Return value:
281  *  PR_SUCCESS
282  *  PR_FAILURE
283  */
284 
285 NSS_IMPLEMENT PRStatus
nssPointerTracker_remove(nssPointerTracker * tracker,const void * pointer)286 nssPointerTracker_remove(nssPointerTracker *tracker, const void *pointer)
287 {
288     PRBool registered;
289 
290     if ((nssPointerTracker *)NULL == tracker) {
291         nss_SetError(NSS_ERROR_INVALID_POINTER);
292         return PR_FAILURE;
293     }
294 
295     if ((PZLock *)NULL == tracker->lock) {
296         nss_SetError(NSS_ERROR_TRACKER_NOT_INITIALIZED);
297         return PR_FAILURE;
298     }
299 
300     PZ_Lock(tracker->lock);
301 
302     if ((PLHashTable *)NULL == tracker->table) {
303         PZ_Unlock(tracker->lock);
304         nss_SetError(NSS_ERROR_TRACKER_NOT_INITIALIZED);
305         return PR_FAILURE;
306     }
307 
308     registered = PL_HashTableRemove(tracker->table, pointer);
309     PZ_Unlock(tracker->lock);
310 
311     if (!registered) {
312         nss_SetError(NSS_ERROR_POINTER_NOT_REGISTERED);
313         return PR_FAILURE;
314     }
315 
316     return PR_SUCCESS;
317 }
318 
319 /*
320  * nssPointerTracker_verify
321  *
322  * This method is only present in debug builds.
323  *
324  * This routine verifies that the specified pointer has been registered
325  * with the nssPointerTracker object.  The nssPointerTracker object is
326  * threadsafe, and this call may be safely called from multiple threads
327  * simultaneously with the same arguments.  This routine returns a
328  * PRStatus value; if the pointer is registered this will return
329  * PR_SUCCESS.  Otherwise it will set an error on the error stack and
330  * return PR_FAILURE.  Although the error is suitable for leaving on
331  * the stack, callers may wish to augment the information available by
332  * placing a more type-specific error on the stack.
333  *
334  * The error may be one of the following values:
335  *  NSS_ERROR_INVALID_POINTER
336  *  NSS_ERROR_TRACKER_NOT_INITIALIZED
337  *  NSS_ERROR_POINTER_NOT_REGISTERED
338  *
339  * Return value:
340  *  PR_SUCCESS
341  *  PR_FAILRUE
342  */
343 
344 NSS_IMPLEMENT PRStatus
nssPointerTracker_verify(nssPointerTracker * tracker,const void * pointer)345 nssPointerTracker_verify(nssPointerTracker *tracker, const void *pointer)
346 {
347     void *check;
348 
349     if ((nssPointerTracker *)NULL == tracker) {
350         nss_SetError(NSS_ERROR_INVALID_POINTER);
351         return PR_FAILURE;
352     }
353 
354     if ((PZLock *)NULL == tracker->lock) {
355         nss_SetError(NSS_ERROR_TRACKER_NOT_INITIALIZED);
356         return PR_FAILURE;
357     }
358 
359     PZ_Lock(tracker->lock);
360 
361     if ((PLHashTable *)NULL == tracker->table) {
362         PZ_Unlock(tracker->lock);
363         nss_SetError(NSS_ERROR_TRACKER_NOT_INITIALIZED);
364         return PR_FAILURE;
365     }
366 
367     check = PL_HashTableLookup(tracker->table, pointer);
368     PZ_Unlock(tracker->lock);
369 
370     if ((void *)NULL == check) {
371         nss_SetError(NSS_ERROR_POINTER_NOT_REGISTERED);
372         return PR_FAILURE;
373     }
374 
375     return PR_SUCCESS;
376 }
377 
378 #endif /* DEBUG */
379