/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Netscape security libraries. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1994-2000 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifdef DEBUG static const char CVS_ID[] = "@(#) $RCSfile: tracker.c,v $ $Revision: 1.5 $ $Date: 2004-04-25 15:03:02 $ $Name: $"; #endif /* DEBUG */ /* * tracker.c * * This file contains the code used by the pointer-tracking calls used * in the debug builds to catch bad pointers. The entire contents are * only available in debug builds (both internal and external builds). */ #ifndef BASE_H #include "base.h" #endif /* BASE_H */ #ifdef DEBUG /* * call_once * * Unfortunately, NSPR's PR_CallOnce function doesn't accept a closure * variable. So I have a static version here which does. This code * is based on NSPR's, and uses the NSPR function to initialize the * required lock. */ /* * The is the "once block" that's passed to the "real" PR_CallOnce * function, to call the local initializer myOnceFunction once. */ static PRCallOnceType myCallOnce; /* * This structure is used by the call_once function to make sure that * any "other" threads calling the call_once don't return too quickly, * before the initializer has finished. */ static struct { PZLock *ml; PZCondVar *cv; } mod_init; /* * This is the initializer for the above mod_init structure. */ static PRStatus myOnceFunction ( void ) { mod_init.ml = PZ_NewLock(nssILockOther); if( (PZLock *)NULL == mod_init.ml ) { return PR_FAILURE; } mod_init.cv = PZ_NewCondVar(mod_init.ml); if( (PZCondVar *)NULL == mod_init.cv ) { PZ_DestroyLock(mod_init.ml); mod_init.ml = (PZLock *)NULL; return PR_FAILURE; } return PR_SUCCESS; } /* * The nss call_once callback takes a closure argument. */ typedef PRStatus (PR_CALLBACK *nssCallOnceFN)(void *arg); /* * NSS's call_once function. */ static PRStatus call_once ( PRCallOnceType *once, nssCallOnceFN func, void *arg ) { PRStatus rv; if( !myCallOnce.initialized ) { rv = PR_CallOnce(&myCallOnce, myOnceFunction); if( PR_SUCCESS != rv ) { return rv; } } if( !once->initialized ) { if( 0 == PR_AtomicSet(&once->inProgress, 1) ) { once->status = (*func)(arg); PZ_Lock(mod_init.ml); once->initialized = 1; PZ_NotifyAllCondVar(mod_init.cv); PZ_Unlock(mod_init.ml); } else { PZ_Lock(mod_init.ml); while( !once->initialized ) { PZ_WaitCondVar(mod_init.cv, PR_INTERVAL_NO_TIMEOUT); } PZ_Unlock(mod_init.ml); } } return once->status; } /* * Now we actually get to my own "call once" payload function. * But wait, to create the hash, I need a hash function! */ /* * identity_hash * * This static callback is a PLHashFunction as defined in plhash.h * It merely returns the value of the object pointer as its hash. * There are no possible errors. */ static PLHashNumber PR_CALLBACK identity_hash ( const void *key ) { return (PLHashNumber)key; } /* * trackerOnceFunc * * This function is called once, using the nssCallOnce function above. * It creates a new pointer tracker object; initialising its hash * table and protective lock. */ static PRStatus trackerOnceFunc ( void *arg ) { nssPointerTracker *tracker = (nssPointerTracker *)arg; tracker->lock = PZ_NewLock(nssILockOther); if( (PZLock *)NULL == tracker->lock ) { return PR_FAILURE; } tracker->table = PL_NewHashTable(0, identity_hash, PL_CompareValues, PL_CompareValues, (PLHashAllocOps *)NULL, (void *)NULL); if( (PLHashTable *)NULL == tracker->table ) { PZ_DestroyLock(tracker->lock); tracker->lock = (PZLock *)NULL; return PR_FAILURE; } return PR_SUCCESS; } /* * nssPointerTracker_initialize * * This method is only present in debug builds. * * This routine initializes an nssPointerTracker object. Note that * the object must have been declared *static* to guarantee that it * is in a zeroed state initially. This routine is idempotent, and * may even be safely called by multiple threads simultaneously with * the same argument. This routine returns a PRStatus value; if * successful, it will return PR_SUCCESS. On failure it will set an * error on the error stack and return PR_FAILURE. * * The error may be one of the following values: * NSS_ERROR_NO_MEMORY * * Return value: * PR_SUCCESS * PR_FAILURE */ NSS_IMPLEMENT PRStatus nssPointerTracker_initialize ( nssPointerTracker *tracker ) { PRStatus rv = call_once(&tracker->once, trackerOnceFunc, tracker); if( PR_SUCCESS != rv ) { nss_SetError(NSS_ERROR_NO_MEMORY); } return rv; } #ifdef DONT_DESTROY_EMPTY_TABLES /* See same #ifdef below */ /* * count_entries * * This static routine is a PLHashEnumerator, as defined in plhash.h. * It merely causes the enumeration function to count the number of * entries. */ static PRIntn PR_CALLBACK count_entries ( PLHashEntry *he, PRIntn index, void *arg ) { return HT_ENUMERATE_NEXT; } #endif /* DONT_DESTROY_EMPTY_TABLES */ /* * zero_once * * This is a guaranteed zeroed once block. It's used to help clear * the tracker. */ static const PRCallOnceType zero_once; /* * nssPointerTracker_finalize * * This method is only present in debug builds. * * This routine returns the nssPointerTracker object to the pre- * initialized state, releasing all resources used by the object. * It will *NOT* destroy the objects being tracked by the pointer * (should any remain), and therefore cannot be used to "sweep up" * remaining objects. This routine returns a PRStatus value; if * successful, it will return PR_SUCCES. On failure it will set an * error on the error stack and return PR_FAILURE. If any objects * remain in the tracker when it is finalized, that will be treated * as an error. * * The error may be one of the following values: * NSS_ERROR_INVALID_POINTER * NSS_ERROR_TRACKER_NOT_INITIALIZED * NSS_ERROR_TRACKER_NOT_EMPTY * * Return value: * PR_SUCCESS * PR_FAILURE */ NSS_IMPLEMENT PRStatus nssPointerTracker_finalize ( nssPointerTracker *tracker ) { PZLock *lock; if( (nssPointerTracker *)NULL == tracker ) { nss_SetError(NSS_ERROR_INVALID_POINTER); return PR_FAILURE; } if( (PZLock *)NULL == tracker->lock ) { nss_SetError(NSS_ERROR_TRACKER_NOT_INITIALIZED); return PR_FAILURE; } lock = tracker->lock; PZ_Lock(lock); if( (PLHashTable *)NULL == tracker->table ) { PZ_Unlock(lock); nss_SetError(NSS_ERROR_TRACKER_NOT_INITIALIZED); return PR_FAILURE; } #ifdef DONT_DESTROY_EMPTY_TABLES /* * I changed my mind; I think we don't want this after all. * Comments? */ count = PL_HashTableEnumerateEntries(tracker->table, count_entries, (void *)NULL); if( 0 != count ) { PZ_Unlock(lock); nss_SetError(NSS_ERROR_TRACKER_NOT_EMPTY); return PR_FAILURE; } #endif /* DONT_DESTROY_EMPTY_TABLES */ PL_HashTableDestroy(tracker->table); /* memset(tracker, 0, sizeof(nssPointerTracker)); */ tracker->once = zero_once; tracker->lock = (PZLock *)NULL; tracker->table = (PLHashTable *)NULL; PZ_Unlock(lock); PZ_DestroyLock(lock); return PR_SUCCESS; } /* * nssPointerTracker_add * * This method is only present in debug builds. * * This routine adds the specified pointer to the nssPointerTracker * object. It should be called in constructor objects to register * new valid objects. The nssPointerTracker is threadsafe, but this * call is not idempotent. This routine returns a PRStatus value; * if successful it will return PR_SUCCESS. On failure it will set * an error on the error stack and return PR_FAILURE. * * The error may be one of the following values: * NSS_ERROR_INVALID_POINTER * NSS_ERROR_NO_MEMORY * NSS_ERROR_TRACKER_NOT_INITIALIZED * NSS_ERROR_DUPLICATE_POINTER * * Return value: * PR_SUCCESS * PR_FAILURE */ NSS_IMPLEMENT PRStatus nssPointerTracker_add ( nssPointerTracker *tracker, const void *pointer ) { void *check; PLHashEntry *entry; if( (nssPointerTracker *)NULL == tracker ) { nss_SetError(NSS_ERROR_INVALID_POINTER); return PR_FAILURE; } if( (PZLock *)NULL == tracker->lock ) { nss_SetError(NSS_ERROR_TRACKER_NOT_INITIALIZED); return PR_FAILURE; } PZ_Lock(tracker->lock); if( (PLHashTable *)NULL == tracker->table ) { PZ_Unlock(tracker->lock); nss_SetError(NSS_ERROR_TRACKER_NOT_INITIALIZED); return PR_FAILURE; } check = PL_HashTableLookup(tracker->table, pointer); if( (void *)NULL != check ) { PZ_Unlock(tracker->lock); nss_SetError(NSS_ERROR_DUPLICATE_POINTER); return PR_FAILURE; } entry = PL_HashTableAdd(tracker->table, pointer, (void *)pointer); PZ_Unlock(tracker->lock); if( (PLHashEntry *)NULL == entry ) { nss_SetError(NSS_ERROR_NO_MEMORY); return PR_FAILURE; } return PR_SUCCESS; } /* * nssPointerTracker_remove * * This method is only present in debug builds. * * This routine removes the specified pointer from the * nssPointerTracker object. It does not call any destructor for the * object; rather, this should be called from the object's destructor. * The nssPointerTracker is threadsafe, but this call is not * idempotent. This routine returns a PRStatus value; if successful * it will return PR_SUCCESS. On failure it will set an error on the * error stack and return PR_FAILURE. * * The error may be one of the following values: * NSS_ERROR_INVALID_POINTER * NSS_ERROR_TRACKER_NOT_INITIALIZED * NSS_ERROR_POINTER_NOT_REGISTERED * * Return value: * PR_SUCCESS * PR_FAILURE */ NSS_IMPLEMENT PRStatus nssPointerTracker_remove ( nssPointerTracker *tracker, const void *pointer ) { PRBool registered; if( (nssPointerTracker *)NULL == tracker ) { nss_SetError(NSS_ERROR_INVALID_POINTER); return PR_FAILURE; } if( (PZLock *)NULL == tracker->lock ) { nss_SetError(NSS_ERROR_TRACKER_NOT_INITIALIZED); return PR_FAILURE; } PZ_Lock(tracker->lock); if( (PLHashTable *)NULL == tracker->table ) { PZ_Unlock(tracker->lock); nss_SetError(NSS_ERROR_TRACKER_NOT_INITIALIZED); return PR_FAILURE; } registered = PL_HashTableRemove(tracker->table, pointer); PZ_Unlock(tracker->lock); if( !registered ) { nss_SetError(NSS_ERROR_POINTER_NOT_REGISTERED); return PR_FAILURE; } return PR_SUCCESS; } /* * nssPointerTracker_verify * * This method is only present in debug builds. * * This routine verifies that the specified pointer has been registered * with the nssPointerTracker object. The nssPointerTracker object is * threadsafe, and this call may be safely called from multiple threads * simultaneously with the same arguments. This routine returns a * PRStatus value; if the pointer is registered this will return * PR_SUCCESS. Otherwise it will set an error on the error stack and * return PR_FAILURE. Although the error is suitable for leaving on * the stack, callers may wish to augment the information available by * placing a more type-specific error on the stack. * * The error may be one of the following values: * NSS_ERROR_INVALID_POINTER * NSS_ERROR_TRACKER_NOT_INITIALIZED * NSS_ERROR_POINTER_NOT_REGISTERED * * Return value: * PR_SUCCESS * PR_FAILRUE */ NSS_IMPLEMENT PRStatus nssPointerTracker_verify ( nssPointerTracker *tracker, const void *pointer ) { void *check; if( (nssPointerTracker *)NULL == tracker ) { nss_SetError(NSS_ERROR_INVALID_POINTER); return PR_FAILURE; } if( (PZLock *)NULL == tracker->lock ) { nss_SetError(NSS_ERROR_TRACKER_NOT_INITIALIZED); return PR_FAILURE; } PZ_Lock(tracker->lock); if( (PLHashTable *)NULL == tracker->table ) { PZ_Unlock(tracker->lock); nss_SetError(NSS_ERROR_TRACKER_NOT_INITIALIZED); return PR_FAILURE; } check = PL_HashTableLookup(tracker->table, pointer); PZ_Unlock(tracker->lock); if( (void *)NULL == check ) { nss_SetError(NSS_ERROR_POINTER_NOT_REGISTERED); return PR_FAILURE; } return PR_SUCCESS; } #endif /* DEBUG */