1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sw=4 et tw=80:
3 : *
4 : * ***** BEGIN LICENSE BLOCK *****
5 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 : *
7 : * The contents of this file are subject to the Mozilla Public License Version
8 : * 1.1 (the "License"); you may not use this file except in compliance with
9 : * the License. You may obtain a copy of the License at
10 : * http://www.mozilla.org/MPL/
11 : *
12 : * Software distributed under the License is distributed on an "AS IS" basis,
13 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 : * for the specific language governing rights and limitations under the
15 : * License.
16 : *
17 : * The Original Code is Mozilla Communicator client code, released
18 : * March 31, 1998.
19 : *
20 : * The Initial Developer of the Original Code is
21 : * Netscape Communications Corporation.
22 : * Portions created by the Initial Developer are Copyright (C) 1998
23 : * the Initial Developer. All Rights Reserved.
24 : *
25 : * Contributor(s):
26 : * John Bandhauer <jband@netscape.com> (original author)
27 : *
28 : * Alternatively, the contents of this file may be used under the terms of
29 : * either of the GNU General Public License Version 2 or later (the "GPL"),
30 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 : * in which case the provisions of the GPL or the LGPL are applicable instead
32 : * of those above. If you wish to allow use of your version of this file only
33 : * under the terms of either the GPL or the LGPL, and not to allow others to
34 : * use your version of this file under the terms of the MPL, indicate your
35 : * decision by deleting the provisions above and replace them with the notice
36 : * and other provisions required by the GPL or the LGPL. If you do not delete
37 : * the provisions above, a recipient may use your version of this file under
38 : * the terms of any one of the MPL, the GPL or the LGPL.
39 : *
40 : * ***** END LICENSE BLOCK ***** */
41 :
42 : /* Implement global service to track stack of JSContext per thread. */
43 :
44 : #include "xpcprivate.h"
45 : #include "XPCWrapper.h"
46 : #include "mozilla/Mutex.h"
47 : #include "nsDOMJSUtils.h"
48 : #include "nsIScriptGlobalObject.h"
49 : #include "nsNullPrincipal.h"
50 :
51 : using namespace mozilla;
52 :
53 : /***************************************************************************/
54 :
55 4218 : XPCJSContextStack::~XPCJSContextStack()
56 : {
57 1406 : if (mOwnSafeJSContext) {
58 1403 : JS_DestroyContext(mOwnSafeJSContext);
59 1403 : mOwnSafeJSContext = nsnull;
60 : }
61 5624 : }
62 :
63 : JSContext*
64 435334 : XPCJSContextStack::Pop()
65 : {
66 435334 : MOZ_ASSERT(!mStack.IsEmpty());
67 :
68 435334 : uint32_t idx = mStack.Length() - 1; // The thing we're popping
69 :
70 435334 : JSContext *cx = mStack[idx].cx;
71 :
72 435334 : mStack.RemoveElementAt(idx);
73 435334 : if (idx == 0)
74 26826 : return cx;
75 :
76 408508 : --idx; // Advance to new top of the stack
77 :
78 408508 : XPCJSContextInfo &e = mStack[idx];
79 408508 : NS_ASSERTION(!e.suspendDepth || e.cx, "Shouldn't have suspendDepth without a cx!");
80 408508 : if (e.cx) {
81 220322 : if (e.suspendDepth) {
82 159500 : JS_ResumeRequest(e.cx, e.suspendDepth);
83 159500 : e.suspendDepth = 0;
84 : }
85 :
86 220322 : if (e.savedFrameChain) {
87 : // Pop() can be called outside any request for e.cx.
88 440644 : JSAutoRequest ar(e.cx);
89 220322 : JS_RestoreFrameChain(e.cx);
90 220322 : e.savedFrameChain = false;
91 : }
92 : }
93 408508 : return cx;
94 : }
95 :
96 : static nsIPrincipal*
97 44453 : GetPrincipalFromCx(JSContext *cx)
98 : {
99 44453 : nsIScriptContextPrincipal* scp = GetScriptContextPrincipalFromJSContext(cx);
100 44453 : if (scp) {
101 0 : nsIScriptObjectPrincipal* globalData = scp->GetObjectPrincipal();
102 0 : if (globalData)
103 0 : return globalData->GetPrincipal();
104 : }
105 44453 : return nsnull;
106 : }
107 :
108 : bool
109 435334 : XPCJSContextStack::Push(JSContext *cx)
110 : {
111 435334 : if (mStack.Length() == 0) {
112 26826 : mStack.AppendElement(cx);
113 26826 : return true;
114 : }
115 :
116 408508 : XPCJSContextInfo &e = mStack[mStack.Length() - 1];
117 408508 : if (e.cx) {
118 220322 : if (e.cx == cx) {
119 44453 : nsIScriptSecurityManager* ssm = XPCWrapper::GetSecurityManager();
120 44453 : if (ssm) {
121 44453 : if (nsIPrincipal* globalObjectPrincipal = GetPrincipalFromCx(cx)) {
122 0 : nsIPrincipal* subjectPrincipal = ssm->GetCxSubjectPrincipal(cx);
123 0 : bool equals = false;
124 0 : globalObjectPrincipal->Equals(subjectPrincipal, &equals);
125 0 : if (equals) {
126 0 : mStack.AppendElement(cx);
127 0 : return true;
128 : }
129 : }
130 : }
131 : }
132 :
133 : {
134 : // Push() can be called outside any request for e.cx.
135 440644 : JSAutoRequest ar(e.cx);
136 220322 : if (!JS_SaveFrameChain(e.cx))
137 0 : return false;
138 440644 : e.savedFrameChain = true;
139 : }
140 :
141 220322 : if (!cx)
142 159500 : e.suspendDepth = JS_SuspendRequest(e.cx);
143 : }
144 :
145 408508 : mStack.AppendElement(cx);
146 408508 : return true;
147 : }
148 :
149 : #ifdef DEBUG
150 : bool
151 0 : XPCJSContextStack::DEBUG_StackHasJSContext(JSContext *cx)
152 : {
153 0 : for (PRUint32 i = 0; i < mStack.Length(); i++)
154 0 : if (cx == mStack[i].cx)
155 0 : return true;
156 0 : return false;
157 : }
158 : #endif
159 :
160 : static JSBool
161 0 : SafeGlobalResolve(JSContext *cx, JSObject *obj, jsid id)
162 : {
163 : JSBool resolved;
164 0 : return JS_ResolveStandardClass(cx, obj, id, &resolved);
165 : }
166 :
167 : static void
168 1403 : SafeFinalize(JSContext* cx, JSObject* obj)
169 : {
170 : nsIScriptObjectPrincipal* sop =
171 1403 : static_cast<nsIScriptObjectPrincipal*>(xpc_GetJSPrivate(obj));
172 1403 : NS_IF_RELEASE(sop);
173 1403 : }
174 :
175 : static JSClass global_class = {
176 : "global_for_XPCJSContextStack_SafeJSContext",
177 : XPCONNECT_GLOBAL_FLAGS,
178 : JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
179 : JS_EnumerateStub, SafeGlobalResolve, JS_ConvertStub, SafeFinalize,
180 : NULL, NULL, NULL, NULL, TraceXPCGlobal
181 : };
182 :
183 : // We just use the same reporter as the component loader
184 : // XXX #include angels cry.
185 : extern void
186 : mozJSLoaderErrorReporter(JSContext *cx, const char *message, JSErrorReport *rep);
187 :
188 : JSContext*
189 102120 : XPCJSContextStack::GetSafeJSContext()
190 : {
191 102120 : if (mSafeJSContext)
192 100716 : return mSafeJSContext;
193 :
194 : // Start by getting the principal holder and principal for this
195 : // context. If we can't manage that, don't bother with the rest.
196 2808 : nsRefPtr<nsNullPrincipal> principal = new nsNullPrincipal();
197 1404 : nsresult rv = principal->Init();
198 1404 : if (NS_FAILED(rv))
199 0 : return NULL;
200 :
201 4212 : nsCOMPtr<nsIScriptObjectPrincipal> sop = new PrincipalHolder(principal);
202 :
203 2808 : nsRefPtr<nsXPConnect> xpc = nsXPConnect::GetXPConnect();
204 1404 : if (!xpc)
205 0 : return NULL;
206 :
207 1404 : XPCJSRuntime* xpcrt = xpc->GetRuntime();
208 1404 : if (!xpcrt)
209 0 : return NULL;
210 :
211 1404 : JSRuntime *rt = xpcrt->GetJSRuntime();
212 1404 : if (!rt)
213 0 : return NULL;
214 :
215 1404 : mSafeJSContext = JS_NewContext(rt, 8192);
216 1404 : if (!mSafeJSContext)
217 0 : return NULL;
218 :
219 : JSObject *glob;
220 : {
221 : // scoped JS Request
222 2808 : JSAutoRequest req(mSafeJSContext);
223 :
224 1404 : JS_SetErrorReporter(mSafeJSContext, mozJSLoaderErrorReporter);
225 :
226 : JSCompartment *compartment;
227 : nsresult rv = xpc_CreateGlobalObject(mSafeJSContext, &global_class,
228 : principal, principal, false,
229 1404 : &glob, &compartment);
230 1404 : if (NS_FAILED(rv))
231 0 : glob = nsnull;
232 :
233 1404 : if (glob) {
234 : // Make sure the context is associated with a proper compartment
235 : // and not the default compartment.
236 1404 : JS_SetGlobalObject(mSafeJSContext, glob);
237 :
238 : // Note: make sure to set the private before calling
239 : // InitClasses
240 1404 : nsIScriptObjectPrincipal* priv = nsnull;
241 1404 : sop.swap(priv);
242 1404 : JS_SetPrivate(glob, priv);
243 : }
244 :
245 : // After this point either glob is null and the
246 : // nsIScriptObjectPrincipal ownership is either handled by the
247 : // nsCOMPtr or dealt with, or we'll release in the finalize
248 : // hook.
249 1404 : if (glob && NS_FAILED(xpc->InitClasses(mSafeJSContext, glob))) {
250 0 : glob = nsnull;
251 : }
252 : }
253 1404 : if (mSafeJSContext && !glob) {
254 : // Destroy the context outside the scope of JSAutoRequest that
255 : // uses the context in its destructor.
256 0 : JS_DestroyContext(mSafeJSContext);
257 0 : mSafeJSContext = nsnull;
258 : }
259 :
260 : // Save it off so we can destroy it later.
261 1404 : mOwnSafeJSContext = mSafeJSContext;
262 :
263 1404 : return mSafeJSContext;
264 : }
265 :
266 : /***************************************************************************/
267 :
268 : PRUintn XPCPerThreadData::gTLSIndex = BAD_TLS_INDEX;
269 : Mutex* XPCPerThreadData::gLock = nsnull;
270 : XPCPerThreadData* XPCPerThreadData::gThreads = nsnull;
271 : XPCPerThreadData *XPCPerThreadData::sMainThreadData = nsnull;
272 : void * XPCPerThreadData::sMainJSThread = nsnull;
273 :
274 1407 : XPCPerThreadData::XPCPerThreadData()
275 : : mJSContextStack(new XPCJSContextStack()),
276 : mNextThread(nsnull),
277 : mCallContext(nsnull),
278 : mResolveName(JSID_VOID),
279 : mResolvingWrapper(nsnull),
280 : mExceptionManager(nsnull),
281 : mException(nsnull),
282 : mExceptionManagerNotAvailable(false),
283 : mAutoRoots(nsnull)
284 : #ifdef XPC_CHECK_WRAPPER_THREADSAFETY
285 1407 : , mWrappedNativeThreadsafetyReportDepth(0)
286 : #endif
287 : {
288 1407 : MOZ_COUNT_CTOR(xpcPerThreadData);
289 1407 : if (gLock) {
290 2814 : MutexAutoLock lock(*gLock);
291 1407 : mNextThread = gThreads;
292 1407 : gThreads = this;
293 : }
294 1407 : }
295 :
296 : void
297 2809 : XPCPerThreadData::Cleanup()
298 : {
299 5618 : while (mAutoRoots)
300 0 : mAutoRoots->Unlink();
301 2809 : NS_IF_RELEASE(mExceptionManager);
302 2809 : NS_IF_RELEASE(mException);
303 2809 : delete mJSContextStack;
304 2809 : mJSContextStack = nsnull;
305 :
306 2809 : if (mCallContext)
307 0 : mCallContext->SystemIsBeingShutDown();
308 2809 : }
309 :
310 1406 : XPCPerThreadData::~XPCPerThreadData()
311 : {
312 : /* Be careful to ensure that both any update to |gThreads| and the
313 : decision about whether or not to destroy the lock, are done
314 : atomically. See bug 557586. */
315 1406 : bool doDestroyLock = false;
316 :
317 1406 : MOZ_COUNT_DTOR(xpcPerThreadData);
318 :
319 1406 : Cleanup();
320 :
321 : // Unlink 'this' from the list of threads.
322 1406 : if (gLock) {
323 2812 : MutexAutoLock lock(*gLock);
324 1406 : if (gThreads == this)
325 1406 : gThreads = mNextThread;
326 : else {
327 0 : XPCPerThreadData* cur = gThreads;
328 0 : while (cur) {
329 0 : if (cur->mNextThread == this) {
330 0 : cur->mNextThread = mNextThread;
331 0 : break;
332 : }
333 0 : cur = cur->mNextThread;
334 : }
335 : }
336 1406 : if (!gThreads)
337 1403 : doDestroyLock = true;
338 : }
339 :
340 1406 : if (gLock && doDestroyLock) {
341 1403 : delete gLock;
342 1403 : gLock = nsnull;
343 : }
344 1406 : }
345 :
346 : static void
347 1406 : xpc_ThreadDataDtorCB(void* ptr)
348 : {
349 1406 : XPCPerThreadData* data = (XPCPerThreadData*) ptr;
350 1406 : delete data;
351 1406 : }
352 :
353 11922 : void XPCPerThreadData::TraceJS(JSTracer *trc)
354 : {
355 : #ifdef XPC_TRACK_AUTOMARKINGPTR_STATS
356 : {
357 : static int maxLength = 0;
358 : int length = 0;
359 : for (AutoMarkingPtr* p = mAutoRoots; p; p = p->GetNext())
360 : length++;
361 : if (length > maxLength)
362 : maxLength = length;
363 : printf("XPC gc on thread %x with %d AutoMarkingPtrs (%d max so far)\n",
364 : this, length, maxLength);
365 : }
366 : #endif
367 :
368 11922 : if (mAutoRoots)
369 7 : mAutoRoots->TraceJS(trc);
370 11922 : }
371 :
372 11922 : void XPCPerThreadData::MarkAutoRootsAfterJSFinalize()
373 : {
374 11922 : if (mAutoRoots)
375 7 : mAutoRoots->MarkAfterJSFinalize();
376 11922 : }
377 :
378 : // static
379 : XPCPerThreadData*
380 2812 : XPCPerThreadData::GetDataImpl(JSContext *cx)
381 : {
382 : XPCPerThreadData* data;
383 :
384 2812 : if (!gLock) {
385 1404 : gLock = new Mutex("XPCPerThreadData.gLock");
386 : }
387 :
388 2812 : if (gTLSIndex == BAD_TLS_INDEX) {
389 2808 : MutexAutoLock lock(*gLock);
390 : // check again now that we have the lock...
391 1404 : if (gTLSIndex == BAD_TLS_INDEX) {
392 1404 : if (PR_FAILURE ==
393 1404 : PR_NewThreadPrivateIndex(&gTLSIndex, xpc_ThreadDataDtorCB)) {
394 0 : NS_ERROR("PR_NewThreadPrivateIndex failed!");
395 0 : gTLSIndex = BAD_TLS_INDEX;
396 0 : return nsnull;
397 : }
398 : }
399 : }
400 :
401 2812 : data = (XPCPerThreadData*) PR_GetThreadPrivate(gTLSIndex);
402 2812 : if (!data) {
403 1407 : data = new XPCPerThreadData();
404 1407 : if (!data || !data->IsValid()) {
405 0 : NS_ERROR("new XPCPerThreadData() failed!");
406 0 : delete data;
407 0 : return nsnull;
408 : }
409 1407 : if (PR_FAILURE == PR_SetThreadPrivate(gTLSIndex, data)) {
410 0 : NS_ERROR("PR_SetThreadPrivate failed!");
411 0 : delete data;
412 0 : return nsnull;
413 : }
414 : }
415 :
416 2812 : if (cx && !sMainJSThread && NS_IsMainThread()) {
417 1404 : sMainJSThread = js::GetOwnerThread(cx);
418 :
419 1404 : sMainThreadData = data;
420 :
421 1404 : sMainThreadData->mThread = PR_GetCurrentThread();
422 : }
423 :
424 2812 : return data;
425 : }
426 :
427 : // static
428 : void
429 1403 : XPCPerThreadData::CleanupAllThreads()
430 : {
431 : // I've questioned the sense of cleaning up other threads' data from the
432 : // start. But I got talked into it. Now I see that we *can't* do all the
433 : // cleaup while holding this lock. So, we are going to go to the trouble
434 : // to copy out the data that needs to be cleaned up *outside* of
435 : // the lock. Yuk!
436 :
437 1403 : XPCJSContextStack** stacks = nsnull;
438 1403 : int count = 0;
439 : int i;
440 :
441 1403 : if (gLock) {
442 2806 : MutexAutoLock lock(*gLock);
443 :
444 2806 : for (XPCPerThreadData* cur = gThreads; cur; cur = cur->mNextThread)
445 1403 : count++;
446 :
447 2806 : stacks = (XPCJSContextStack**) new XPCJSContextStack*[count] ;
448 1403 : if (stacks) {
449 1403 : i = 0;
450 2806 : for (XPCPerThreadData* cur = gThreads; cur; cur = cur->mNextThread) {
451 1403 : stacks[i++] = cur->mJSContextStack;
452 1403 : cur->mJSContextStack = nsnull;
453 1403 : cur->Cleanup();
454 : }
455 : }
456 : }
457 :
458 1403 : if (stacks) {
459 2806 : for (i = 0; i < count; i++)
460 1403 : delete stacks[i];
461 1403 : delete [] stacks;
462 : }
463 :
464 1403 : if (gTLSIndex != BAD_TLS_INDEX)
465 1403 : PR_SetThreadPrivate(gTLSIndex, nsnull);
466 1403 : }
467 :
468 : // static
469 : XPCPerThreadData*
470 71532 : XPCPerThreadData::IterateThreads(XPCPerThreadData** iteratorp)
471 : {
472 71532 : *iteratorp = (*iteratorp == nsnull) ? gThreads : (*iteratorp)->mNextThread;
473 71532 : return *iteratorp;
474 : }
475 :
476 0 : NS_IMPL_ISUPPORTS1(nsXPCJSContextStackIterator, nsIJSContextStackIterator)
477 :
478 : NS_IMETHODIMP
479 0 : nsXPCJSContextStackIterator::Reset(nsIJSContextStack *aStack)
480 : {
481 0 : NS_ASSERTION(aStack == nsXPConnect::GetXPConnect(),
482 : "aStack must be implemented by XPConnect singleton");
483 0 : XPCPerThreadData* data = XPCPerThreadData::GetData(nsnull);
484 0 : if (!data)
485 0 : return NS_ERROR_FAILURE;
486 0 : mStack = data->GetJSContextStack()->GetStack();
487 0 : if (mStack->IsEmpty())
488 0 : mStack = nsnull;
489 : else
490 0 : mPosition = mStack->Length() - 1;
491 :
492 0 : return NS_OK;
493 : }
494 :
495 : NS_IMETHODIMP
496 0 : nsXPCJSContextStackIterator::Done(bool *aDone)
497 : {
498 0 : *aDone = !mStack;
499 0 : return NS_OK;
500 : }
501 :
502 : NS_IMETHODIMP
503 0 : nsXPCJSContextStackIterator::Prev(JSContext **aContext)
504 : {
505 0 : if (!mStack)
506 0 : return NS_ERROR_NOT_INITIALIZED;
507 :
508 0 : *aContext = mStack->ElementAt(mPosition).cx;
509 :
510 0 : if (mPosition == 0)
511 0 : mStack = nsnull;
512 : else
513 0 : --mPosition;
514 :
515 0 : return NS_OK;
516 : }
517 :
|