/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * The contents of this file are subject to the Netscape 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/NPL/ * * 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 mozilla.org code. * * The Initial Developer of the Original Code is ActiveState Tool Corp. * Portions created by ActiveState are Copyright (C) 2001 ActiveState * Tool Corporation. All Rights Reserved. * * Contributor(s): Mark Hammond */ #include "nsISupports.h" #include "nsExceptionService.h" #include "nsIServiceManager.h" #include "nsCOMPtr.h" #include "prthread.h" #include "prlock.h" static const PRUintn BAD_TLS_INDEX = (PRUintn) -1; #define CHECK_SERVICE_USE_OK() if (!lock) return NS_ERROR_NOT_INITIALIZED #define CHECK_MANAGER_USE_OK() if (!mService || !nsExceptionService::lock) return NS_ERROR_NOT_INITIALIZED // A key for our registered module providers hashtable class nsProviderKey : public nsHashKey { protected: PRUint32 mKey; public: nsProviderKey(PRUint32 key) : mKey(key) {} PRUint32 HashCode(void) const { return mKey; } PRBool Equals(const nsHashKey *aKey) const { return mKey == ((const nsProviderKey *) aKey)->mKey; } nsHashKey *Clone() const { return new nsProviderKey(mKey); } PRUint32 GetValue() { return mKey; } }; /** Exception Manager definition **/ class nsExceptionManager : public nsIExceptionManager { public: NS_DECL_ISUPPORTS NS_DECL_NSIEXCEPTIONMANAGER nsExceptionManager(nsExceptionService *svc); virtual ~nsExceptionManager(); /* additional members */ nsCOMPtr mCurrentException; nsExceptionManager *mNextThread; // not ref-counted. nsExceptionService *mService; // not ref-counted #ifdef NS_DEBUG static PRInt32 totalInstances; #endif }; #ifdef NS_DEBUG PRInt32 nsExceptionManager::totalInstances = 0; #endif // Note this object is single threaded - the service itself ensures // one per thread. // An exception if the destructor, which may be called on // the thread shutting down xpcom NS_IMPL_ISUPPORTS1(nsExceptionManager, nsIExceptionManager) nsExceptionManager::nsExceptionManager(nsExceptionService *svc) : mNextThread(nsnull), mService(svc) { /* member initializers and constructor code */ NS_INIT_ISUPPORTS(); #ifdef NS_DEBUG PR_AtomicIncrement(&totalInstances); #endif } nsExceptionManager::~nsExceptionManager() { /* destructor code */ #ifdef NS_DEBUG PR_AtomicDecrement(&totalInstances); #endif // NS_DEBUG } /* void setCurrentException (in nsIException error); */ NS_IMETHODIMP nsExceptionManager::SetCurrentException(nsIException *error) { CHECK_MANAGER_USE_OK(); mCurrentException = error; return NS_OK; } /* nsIException getCurrentException (); */ NS_IMETHODIMP nsExceptionManager::GetCurrentException(nsIException **_retval) { CHECK_MANAGER_USE_OK(); *_retval = mCurrentException; NS_IF_ADDREF(*_retval); return NS_OK; } /* nsIException GetExceptionFromProvider(in nsresult rc); */ NS_IMETHODIMP nsExceptionManager::GetExceptionFromProvider(nsresult rc, nsIException **_retval) { CHECK_MANAGER_USE_OK(); // Just delegate back to the service with the provider map. return mService->GetExceptionFromProvider(rc, _retval); } /* The Exception Service */ class ProviderInfo { public: ProviderInfo(nsIExceptionProvider *_rep, PRUint32 _mod) : provider(_rep), errorModule(_mod) {} nsCOMPtr provider; PRUint32 errorModule; }; PRUintn nsExceptionService::tlsIndex = BAD_TLS_INDEX; PRLock *nsExceptionService::lock = PR_FALSE; nsExceptionManager *nsExceptionService::firstThread = nsnull; #ifdef NS_DEBUG PRInt32 nsExceptionService::totalInstances = 0; #endif NS_IMPL_THREADSAFE_ISUPPORTS2(nsExceptionService, nsIExceptionService, nsIObserver) nsExceptionService::nsExceptionService() : mProviders(4, PR_TRUE) /* small, thread-safe hashtable */ { #ifdef NS_DEBUG if (PR_AtomicIncrement(&totalInstances)!=1) { NS_ERROR("The nsExceptionService is a singleton!"); } #endif NS_INIT_ISUPPORTS(); /* member initializers and constructor code */ if (tlsIndex == BAD_TLS_INDEX) { PRStatus status; status = PR_NewThreadPrivateIndex( &tlsIndex, ThreadDestruct ); NS_WARN_IF_FALSE(status==0, "ScriptErrorService could not allocate TLS storage."); } lock = PR_NewLock(); NS_WARN_IF_FALSE(lock, "Error allocating ExceptionService lock"); // observe XPCOM shutdown. nsCOMPtr observerService = do_GetService(NS_OBSERVERSERVICE_CONTRACTID); NS_WARN_IF_FALSE(observerService, "Could not get observer service!"); if (observerService) observerService->AddObserver(this, NS_LITERAL_STRING(NS_XPCOM_SHUTDOWN_OBSERVER_ID).get()); } nsExceptionService::~nsExceptionService() { Shutdown(); /* destructor code */ #ifdef NS_DEBUG PR_AtomicDecrement(&totalInstances); #endif } /*static*/ void nsExceptionService::ThreadDestruct( void *data ) { if (!lock) { NS_WARNING("nsExceptionService ignoring thread destruction after shutdown"); return; } DropThread( (nsExceptionManager *)data ); } void nsExceptionService::Shutdown() { mProviders.Reset(); if (lock) { DropAllThreads(); PR_DestroyLock(lock); lock = nsnull; } } /* void setCurrentException (in nsIException error); */ NS_IMETHODIMP nsExceptionService::SetCurrentException(nsIException *error) { CHECK_SERVICE_USE_OK(); nsCOMPtr sm; nsresult nr = GetCurrentExceptionManager(getter_AddRefs(sm)); if (NS_FAILED(nr)) return nr; return sm->SetCurrentException(error); } /* nsIException getCurrentException (); */ NS_IMETHODIMP nsExceptionService::GetCurrentException(nsIException **_retval) { CHECK_SERVICE_USE_OK(); nsCOMPtr sm; nsresult nr = GetCurrentExceptionManager(getter_AddRefs(sm)); if (NS_FAILED(nr)) return nr; return sm->GetCurrentException(_retval); } /* nsIException GetExceptionFromProvider (in nsresult rc); */ NS_IMETHODIMP nsExceptionService::GetExceptionFromProvider(nsresult rc, nsIException **_retval) { CHECK_SERVICE_USE_OK(); return DoGetExceptionFromProvider(rc, _retval); } /* readonly attribute nsIExceptionManager currentExceptionManager; */ NS_IMETHODIMP nsExceptionService::GetCurrentExceptionManager(nsIExceptionManager * *aCurrentScriptManager) { CHECK_SERVICE_USE_OK(); nsExceptionManager *mgr = (nsExceptionManager *)PR_GetThreadPrivate(tlsIndex); if (mgr == nsnull) { // Stick the new exception object in with no reference count. mgr = new nsExceptionManager(this); if (mgr == nsnull) return NS_ERROR_OUT_OF_MEMORY; PR_SetThreadPrivate(tlsIndex, mgr); // The reference count is held in the thread-list AddThread(mgr); } *aCurrentScriptManager = mgr; NS_ADDREF(*aCurrentScriptManager); return NS_OK; } /* void registerErrorProvider (in nsIExceptionProvider provider, in PRUint32 moduleCode); */ NS_IMETHODIMP nsExceptionService::RegisterExceptionProvider(nsIExceptionProvider *provider, PRUint32 errorModule) { CHECK_SERVICE_USE_OK(); ProviderInfo *pi = new ProviderInfo(provider, errorModule); if (pi==nsnull) return NS_ERROR_OUT_OF_MEMORY; nsProviderKey key(errorModule); if (mProviders.Put(&key, provider)) { NS_WARNING("Registration of exception provider overwrote another provider with the same module code!"); } return NS_OK; } /* void unregisterErrorProvider (in nsIExceptionProvider provider, in PRUint32 errorModule); */ NS_IMETHODIMP nsExceptionService::UnregisterExceptionProvider(nsIExceptionProvider *provider, PRUint32 errorModule) { CHECK_SERVICE_USE_OK(); nsProviderKey key(errorModule); if (!mProviders.Remove(&key)) { NS_WARNING("Attempt to unregister an unregistered exception provider!"); return NS_ERROR_UNEXPECTED; } return NS_OK; } // nsIObserver NS_IMETHODIMP nsExceptionService::Observe(nsISupports *aSubject, const PRUnichar *aTopic, const PRUnichar *someData) { Shutdown(); return NS_OK; } nsresult nsExceptionService::DoGetExceptionFromProvider( nsresult errCode, nsIException **_exc) { nsProviderKey key(NS_ERROR_GET_MODULE(errCode)); nsCOMPtr provider((nsIExceptionProvider *)mProviders.Get(&key)); if (!provider) { *_exc = nsnull; return NS_COMFALSE; } return provider->GetException(errCode, _exc); } // thread management /*static*/ void nsExceptionService::AddThread(nsExceptionManager *thread) { PR_Lock(lock); thread->mNextThread = firstThread; firstThread = thread; NS_ADDREF(thread); PR_Unlock(lock); } /*static*/ void nsExceptionService::DoDropThread(nsExceptionManager *thread) { nsExceptionManager **emp = &firstThread; while (*emp != thread) { NS_ABORT_IF_FALSE(*emp, "Could not find the thread to drop!"); emp = &(*emp)->mNextThread; } *emp = thread->mNextThread; NS_RELEASE(thread); } /*static*/ void nsExceptionService::DropThread(nsExceptionManager *thread) { PR_Lock(lock); DoDropThread(thread); PR_Unlock(lock); } /*static*/ void nsExceptionService::DropAllThreads() { PR_Lock(lock); while (firstThread) DoDropThread(firstThread); PR_Unlock(lock); }