RetroZilla/js/src/xpconnect/src/xpcthreadcontext.cpp

689 lines
18 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sw=4 et tw=80:
*
* ***** 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 Mozilla Communicator client code, released
* March 31, 1998.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* John Bandhauer <jband@netscape.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either of 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 ***** */
/* Implement global service to track stack of JSContext per thread. */
#include "xpcprivate.h"
#include "nsIScriptGlobalObject.h"
#include "nsIScriptContext.h"
/***************************************************************************/
XPCJSContextStack::XPCJSContextStack()
: mStack(),
mSafeJSContext(nsnull),
mOwnSafeJSContext(nsnull)
{
// empty...
}
XPCJSContextStack::~XPCJSContextStack()
{
if(mOwnSafeJSContext)
{
JS_SetContextThread(mOwnSafeJSContext);
JS_DestroyContext(mOwnSafeJSContext);
mOwnSafeJSContext = nsnull;
SyncJSContexts();
}
}
void
XPCJSContextStack::SyncJSContexts()
{
nsXPConnect* xpc = nsXPConnect::GetXPConnect();
if(xpc)
xpc->SyncJSContexts();
}
/* readonly attribute PRInt32 count; */
NS_IMETHODIMP
XPCJSContextStack::GetCount(PRInt32 *aCount)
{
*aCount = mStack.Length();
return NS_OK;
}
/* JSContext peek (); */
NS_IMETHODIMP
XPCJSContextStack::Peek(JSContext * *_retval)
{
*_retval = mStack.IsEmpty() ? nsnull : mStack[mStack.Length() - 1].cx;
return NS_OK;
}
/* JSContext pop (); */
NS_IMETHODIMP
XPCJSContextStack::Pop(JSContext * *_retval)
{
NS_ASSERTION(!mStack.IsEmpty(), "ThreadJSContextStack underflow");
PRUint32 idx = mStack.Length() - 1; // The thing we're popping
NS_ASSERTION(!mStack[idx].frame,
"Shouldn't have a pending frame to restore on the context "
"we're popping!");
if(_retval)
*_retval = mStack[idx].cx;
mStack.RemoveElementAt(idx);
if(idx > 0)
{
--idx; // Advance to new top of the stack
JSContextAndFrame & e = mStack[idx];
NS_ASSERTION(!e.frame || e.cx, "Shouldn't have frame without a cx!");
if(e.cx && e.frame)
{
JS_RestoreFrameChain(e.cx, e.frame);
e.frame = nsnull;
}
}
return NS_OK;
}
static nsIPrincipal*
GetPrincipalFromCx(JSContext *cx)
{
nsIScriptContext* scriptContext = GetScriptContextFromJSContext(cx);
if (scriptContext)
{
nsCOMPtr<nsIScriptObjectPrincipal> globalData =
do_QueryInterface(scriptContext->GetGlobalObject());
if (globalData)
return globalData->GetPrincipal();
}
return nsnull;
}
/* void push (in JSContext cx); */
NS_IMETHODIMP
XPCJSContextStack::Push(JSContext * cx)
{
if(!mStack.AppendElement(cx))
return NS_ERROR_OUT_OF_MEMORY;
if(mStack.Length() > 1)
{
JSContextAndFrame & e = mStack[mStack.Length() - 2];
if(e.cx)
{
if (e.cx == cx)
{ nsresult rv;
nsCOMPtr<nsIScriptSecurityManager> ssm =
do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv) && ssm)
{
nsIPrincipal* globalObjectPrincipal =
GetPrincipalFromCx(cx);
if (globalObjectPrincipal)
{
nsCOMPtr<nsIPrincipal> subjectPrincipal;
ssm->GetSubjectPrincipal(getter_AddRefs(subjectPrincipal));
PRBool equals = PR_FALSE;
globalObjectPrincipal->Equals(subjectPrincipal, &equals);
if (equals)
{
return NS_OK;
}
}
}
}
e.frame = JS_SaveFrameChain(e.cx);
}
}
return NS_OK;
}
#ifdef DEBUG
JSBool
XPCJSContextStack::DEBUG_StackHasJSContext(JSContext* aJSContext)
{
for(PRUint32 i = 0; i < mStack.Length(); i++)
if(aJSContext == mStack[i].cx)
return JS_TRUE;
return JS_FALSE;
}
#endif
JS_STATIC_DLL_CALLBACK(JSBool)
SafeGlobalResolve(JSContext *cx, JSObject *obj, jsval id)
{
JSBool resolved;
return JS_ResolveStandardClass(cx, obj, id, &resolved);
}
static JSClass global_class = {
"global_for_XPCJSContextStack_SafeJSContext", 0,
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, SafeGlobalResolve, JS_ConvertStub, JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS
};
/* attribute JSContext safeJSContext; */
NS_IMETHODIMP
XPCJSContextStack::GetSafeJSContext(JSContext * *aSafeJSContext)
{
if(!mSafeJSContext)
{
JSRuntime *rt;
XPCJSRuntime* xpcrt;
nsXPConnect* xpc = nsXPConnect::GetXPConnect();
nsCOMPtr<nsIXPConnect> xpcholder(NS_STATIC_CAST(nsIXPConnect*, xpc));
if(xpc && (xpcrt = xpc->GetRuntime()) && (rt = xpcrt->GetJSRuntime()))
{
mSafeJSContext = JS_NewContext(rt, 8192);
if(mSafeJSContext)
{
// scoped JS Request
AutoJSRequestWithNoCallContext req(mSafeJSContext);
JSObject *glob;
glob = JS_NewObject(mSafeJSContext, &global_class, NULL, NULL);
if(!glob || NS_FAILED(xpc->InitClasses(mSafeJSContext, glob)))
{
// Explicitly end the request since we are about to kill
// the JSContext that 'req' will try to use when it
// goes out of scope.
req.EndRequest();
JS_DestroyContext(mSafeJSContext);
mSafeJSContext = nsnull;
}
// Save it off so we can destroy it later, even if
// mSafeJSContext has been set to another context
// via SetSafeJSContext. If we don't get here,
// then mSafeJSContext must have been set via
// SetSafeJSContext, and we're not responsible for
// destroying the passed-in context.
mOwnSafeJSContext = mSafeJSContext;
}
}
}
*aSafeJSContext = mSafeJSContext;
return mSafeJSContext ? NS_OK : NS_ERROR_UNEXPECTED;
}
NS_IMETHODIMP
XPCJSContextStack::SetSafeJSContext(JSContext * aSafeJSContext)
{
if(mOwnSafeJSContext &&
mOwnSafeJSContext == mSafeJSContext &&
mOwnSafeJSContext != aSafeJSContext)
{
JS_DestroyContext(mOwnSafeJSContext);
mOwnSafeJSContext = nsnull;
SyncJSContexts();
}
mSafeJSContext = aSafeJSContext;
return NS_OK;
}
/***************************************************************************/
/*
* nsXPCThreadJSContextStackImpl holds state that we don't want to lose!
*
* The plan is that once created nsXPCThreadJSContextStackImpl never goes
* away until FreeSingleton is called. We do an intentional extra addref at
* construction to keep it around even if no one is using it.
*/
NS_IMPL_THREADSAFE_ISUPPORTS3(nsXPCThreadJSContextStackImpl,
nsIThreadJSContextStack,
nsIJSContextStack,
nsISupportsWeakReference)
nsXPCThreadJSContextStackImpl*
nsXPCThreadJSContextStackImpl::gXPCThreadJSContextStack = nsnull;
nsXPCThreadJSContextStackImpl::nsXPCThreadJSContextStackImpl()
{
}
nsXPCThreadJSContextStackImpl::~nsXPCThreadJSContextStackImpl()
{
gXPCThreadJSContextStack = nsnull;
}
//static
nsXPCThreadJSContextStackImpl*
nsXPCThreadJSContextStackImpl::GetSingleton()
{
if(!gXPCThreadJSContextStack)
{
gXPCThreadJSContextStack = new nsXPCThreadJSContextStackImpl();
// hold an extra reference to lock it down
NS_IF_ADDREF(gXPCThreadJSContextStack);
}
NS_IF_ADDREF(gXPCThreadJSContextStack);
return gXPCThreadJSContextStack;
}
void
nsXPCThreadJSContextStackImpl::FreeSingleton()
{
nsXPCThreadJSContextStackImpl* tcs = gXPCThreadJSContextStack;
if(tcs)
{
nsrefcnt cnt;
NS_RELEASE2(tcs, cnt);
#ifdef XPC_DUMP_AT_SHUTDOWN
if(0 != cnt)
printf("*** dangling reference to nsXPCThreadJSContextStackImpl: refcnt=%d\n", cnt);
#endif
}
}
/* readonly attribute PRInt32 Count; */
NS_IMETHODIMP
nsXPCThreadJSContextStackImpl::GetCount(PRInt32 *aCount)
{
if(!aCount)
return NS_ERROR_NULL_POINTER;
XPCJSContextStack* myStack = GetStackForCurrentThread();
if(!myStack)
{
*aCount = 0;
return NS_ERROR_FAILURE;
}
return myStack->GetCount(aCount);
}
/* JSContext Peek (); */
NS_IMETHODIMP
nsXPCThreadJSContextStackImpl::Peek(JSContext * *_retval)
{
if(!_retval)
return NS_ERROR_NULL_POINTER;
XPCJSContextStack* myStack = GetStackForCurrentThread();
if(!myStack)
{
*_retval = nsnull;
return NS_ERROR_FAILURE;
}
return myStack->Peek(_retval);
}
/* JSContext Pop (); */
NS_IMETHODIMP
nsXPCThreadJSContextStackImpl::Pop(JSContext * *_retval)
{
XPCJSContextStack* myStack = GetStackForCurrentThread();
if(!myStack)
{
if(_retval)
*_retval = nsnull;
return NS_ERROR_FAILURE;
}
return myStack->Pop(_retval);
}
/* void Push (in JSContext cx); */
NS_IMETHODIMP
nsXPCThreadJSContextStackImpl::Push(JSContext * cx)
{
XPCJSContextStack* myStack = GetStackForCurrentThread();
if(!myStack)
return NS_ERROR_FAILURE;
return myStack->Push(cx);
}
/* readonly attribute JSContext SafeJSContext; */
NS_IMETHODIMP
nsXPCThreadJSContextStackImpl::GetSafeJSContext(JSContext * *aSafeJSContext)
{
NS_ASSERTION(aSafeJSContext, "loser!");
XPCJSContextStack* myStack = GetStackForCurrentThread();
if(!myStack)
{
*aSafeJSContext = nsnull;
return NS_ERROR_FAILURE;
}
return myStack->GetSafeJSContext(aSafeJSContext);
}
NS_IMETHODIMP
nsXPCThreadJSContextStackImpl::SetSafeJSContext(JSContext * aSafeJSContext)
{
XPCJSContextStack* myStack = GetStackForCurrentThread();
if(!myStack)
return NS_ERROR_FAILURE;
return myStack->SetSafeJSContext(aSafeJSContext);
}
/***************************************************************************/
PRUintn XPCPerThreadData::gTLSIndex = BAD_TLS_INDEX;
PRLock* XPCPerThreadData::gLock = nsnull;
XPCPerThreadData* XPCPerThreadData::gThreads = nsnull;
static jsuword
GetThreadStackLimit()
{
int stackDummy;
jsuword stackLimit, currentStackAddr = (jsuword)&stackDummy;
const jsuword kStackSize = 0x80000; // 512k
#if JS_STACK_GROWTH_DIRECTION < 0
stackLimit = (currentStackAddr > kStackSize)
? currentStackAddr - kStackSize
: 0;
#else
stackLimit = (currentStackAddr + kStackSize > currentStackAddr)
? currentStackAddr + kStackSize
: (jsuword) -1;
#endif
return stackLimit;
}
XPCPerThreadData::XPCPerThreadData()
: mJSContextStack(new XPCJSContextStack()),
mNextThread(nsnull),
mCallContext(nsnull),
mResolveName(0),
mResolvingWrapper(nsnull),
mMostRecentJSContext(nsnull),
mMostRecentXPCContext(nsnull),
mExceptionManager(nsnull),
mException(nsnull),
mExceptionManagerNotAvailable(JS_FALSE),
mAutoRoots(nsnull),
mStackLimit(GetThreadStackLimit())
#ifdef XPC_CHECK_WRAPPER_THREADSAFETY
, mWrappedNativeThreadsafetyReportDepth(0)
#endif
{
if(gLock)
{
nsAutoLock lock(gLock);
mNextThread = gThreads;
gThreads = this;
}
}
void
XPCPerThreadData::Cleanup()
{
while(mAutoRoots)
mAutoRoots->Unlink();
NS_IF_RELEASE(mExceptionManager);
NS_IF_RELEASE(mException);
delete mJSContextStack;
mJSContextStack = nsnull;
if(mCallContext)
mCallContext->SystemIsBeingShutDown();
}
XPCPerThreadData::~XPCPerThreadData()
{
Cleanup();
// Unlink 'this' from the list of threads.
if(gLock)
{
nsAutoLock lock(gLock);
if(gThreads == this)
gThreads = mNextThread;
else
{
XPCPerThreadData* cur = gThreads;
while(cur)
{
if(cur->mNextThread == this)
{
cur->mNextThread = mNextThread;
break;
}
cur = cur->mNextThread;
}
}
}
if(gLock && !gThreads)
{
PR_DestroyLock(gLock);
gLock = nsnull;
}
}
PR_STATIC_CALLBACK(void)
xpc_ThreadDataDtorCB(void* ptr)
{
XPCPerThreadData* data = (XPCPerThreadData*) ptr;
if(data)
delete data;
}
void XPCPerThreadData::MarkAutoRootsBeforeJSFinalize(JSContext* cx)
{
#ifdef XPC_TRACK_AUTOMARKINGPTR_STATS
{
static int maxLength = 0;
int length = 0;
for(AutoMarkingPtr* p = mAutoRoots; p; p = p->GetNext())
length++;
if(length > maxLength)
maxLength = length;
printf("XPC gc on thread %x with %d AutoMarkingPtrs (%d max so far)\n",
this, length, maxLength);
}
#endif
if(mAutoRoots)
mAutoRoots->MarkBeforeJSFinalize(cx);
}
void XPCPerThreadData::MarkAutoRootsAfterJSFinalize()
{
if(mAutoRoots)
mAutoRoots->MarkAfterJSFinalize();
}
// static
XPCPerThreadData*
XPCPerThreadData::GetData()
{
XPCPerThreadData* data;
if(!gLock)
{
gLock = PR_NewLock();
if(!gLock)
return nsnull;
}
if(gTLSIndex == BAD_TLS_INDEX)
{
nsAutoLock lock(gLock);
// check again now that we have the lock...
if(gTLSIndex == BAD_TLS_INDEX)
{
if(PR_FAILURE ==
PR_NewThreadPrivateIndex(&gTLSIndex, xpc_ThreadDataDtorCB))
{
NS_ASSERTION(0, "PR_NewThreadPrivateIndex failed!");
gTLSIndex = BAD_TLS_INDEX;
return nsnull;
}
}
}
data = (XPCPerThreadData*) PR_GetThreadPrivate(gTLSIndex);
if(!data)
{
data = new XPCPerThreadData();
if(!data || !data->IsValid())
{
NS_ASSERTION(0, "new XPCPerThreadData() failed!");
if(data)
delete data;
return nsnull;
}
if(PR_FAILURE == PR_SetThreadPrivate(gTLSIndex, data))
{
NS_ASSERTION(0, "PR_SetThreadPrivate failed!");
delete data;
return nsnull;
}
}
return data;
}
// static
void
XPCPerThreadData::CleanupAllThreads()
{
// I've questioned the sense of cleaning up other threads' data from the
// start. But I got talked into it. Now I see that we *can't* do all the
// cleaup while holding this lock. So, we are going to go to the trouble
// to copy out the data that needs to be cleaned up *outside* of
// the lock. Yuk!
XPCJSContextStack** stacks = nsnull;
int count = 0;
int i;
if(gLock)
{
nsAutoLock lock(gLock);
for(XPCPerThreadData* cur = gThreads; cur; cur = cur->mNextThread)
count++;
stacks = (XPCJSContextStack**) new XPCJSContextStack*[count] ;
if(stacks)
{
i = 0;
for(XPCPerThreadData* cur = gThreads; cur; cur = cur->mNextThread)
{
stacks[i++] = cur->mJSContextStack;
cur->mJSContextStack = nsnull;
cur->Cleanup();
}
}
}
if(stacks)
{
for(i = 0; i < count; i++)
delete stacks[i];
delete [] stacks;
}
if(gTLSIndex != BAD_TLS_INDEX)
PR_SetThreadPrivate(gTLSIndex, nsnull);
}
// static
XPCPerThreadData*
XPCPerThreadData::IterateThreads(XPCPerThreadData** iteratorp)
{
*iteratorp = (*iteratorp == nsnull) ? gThreads : (*iteratorp)->mNextThread;
return *iteratorp;
}
NS_IMPL_ISUPPORTS1(nsXPCJSContextStackIterator, nsIJSContextStackIterator)
NS_IMETHODIMP
nsXPCJSContextStackIterator::Reset(nsIJSContextStack *aStack)
{
// XXX This is pretty ugly.
nsXPCThreadJSContextStackImpl *impl =
NS_STATIC_CAST(nsXPCThreadJSContextStackImpl*, aStack);
XPCJSContextStack *stack = impl->GetStackForCurrentThread();
if(!stack)
return NS_ERROR_FAILURE;
mStack = stack->GetStack();
if(mStack->IsEmpty())
mStack = nsnull;
else
mPosition = mStack->Length() - 1;
return NS_OK;
}
NS_IMETHODIMP
nsXPCJSContextStackIterator::Done(PRBool *aDone)
{
*aDone = !mStack;
return NS_OK;
}
NS_IMETHODIMP
nsXPCJSContextStackIterator::Prev(JSContext **aContext)
{
if(!mStack)
return NS_ERROR_NOT_INITIALIZED;
*aContext = mStack->ElementAt(mPosition).cx;
if(mPosition == 0)
mStack = nsnull;
else
--mPosition;
return NS_OK;
}