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 |
* |
27 |
* Alternatively, the contents of this file may be used under the terms of |
28 |
* either of the GNU General Public License Version 2 or later (the "GPL"), |
29 |
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
30 |
* in which case the provisions of the GPL or the LGPL are applicable instead |
31 |
* of those above. If you wish to allow use of your version of this file only |
32 |
* under the terms of either the GPL or the LGPL, and not to allow others to |
33 |
* use your version of this file under the terms of the MPL, indicate your |
34 |
* decision by deleting the provisions above and replace them with the notice |
35 |
* and other provisions required by the GPL or the LGPL. If you do not delete |
36 |
* the provisions above, a recipient may use your version of this file under |
37 |
* the terms of any one of the MPL, the GPL or the LGPL. |
38 |
* |
39 |
* ***** END LICENSE BLOCK ***** */ |
40 |
|
41 |
/* |
42 |
* JS execution context. |
43 |
*/ |
44 |
#include <new> |
45 |
#include <stdarg.h> |
46 |
#include <stdlib.h> |
47 |
#include <string.h> |
48 |
#include "jstypes.h" |
49 |
#include "jsstdint.h" |
50 |
#include "jsarena.h" /* Added by JSIFY */ |
51 |
#include "jsutil.h" /* Added by JSIFY */ |
52 |
#include "jsclist.h" |
53 |
#include "jsprf.h" |
54 |
#include "jsatom.h" |
55 |
#include "jscntxt.h" |
56 |
#include "jsversion.h" |
57 |
#include "jsdbgapi.h" |
58 |
#include "jsexn.h" |
59 |
#include "jsfun.h" |
60 |
#include "jsgc.h" |
61 |
#include "jslock.h" |
62 |
#include "jsmath.h" |
63 |
#include "jsnum.h" |
64 |
#include "jsobj.h" |
65 |
#include "jsopcode.h" |
66 |
#include "jspubtd.h" |
67 |
#include "jsscan.h" |
68 |
#include "jsscope.h" |
69 |
#include "jsscript.h" |
70 |
#include "jsstaticcheck.h" |
71 |
#include "jsstr.h" |
72 |
#include "jstracer.h" |
73 |
|
74 |
static void |
75 |
FreeContext(JSContext *cx); |
76 |
|
77 |
static void |
78 |
InitThreadData(JSThreadData *data) |
79 |
{ |
80 |
#ifdef DEBUG |
81 |
/* The data must be already zeroed. */ |
82 |
for (size_t i = 0; i != sizeof(*data); ++i) |
83 |
JS_ASSERT(reinterpret_cast<uint8*>(data)[i] == 0); |
84 |
#endif |
85 |
#ifdef JS_TRACER |
86 |
js_InitJIT(&data->traceMonitor); |
87 |
#endif |
88 |
js_InitRandom(data); |
89 |
} |
90 |
|
91 |
static void |
92 |
FinishThreadData(JSThreadData *data) |
93 |
{ |
94 |
#ifdef DEBUG |
95 |
/* All GC-related things must be already removed at this point. */ |
96 |
for (size_t i = 0; i != JS_ARRAY_LENGTH(data->scriptsToGC); ++i) |
97 |
JS_ASSERT(!data->scriptsToGC[i]); |
98 |
for (size_t i = 0; i != JS_ARRAY_LENGTH(data->nativeEnumCache); ++i) |
99 |
JS_ASSERT(!data->nativeEnumCache[i]); |
100 |
#endif |
101 |
|
102 |
js_FinishGSNCache(&data->gsnCache); |
103 |
js_FinishPropertyCache(&data->propertyCache); |
104 |
#if defined JS_TRACER |
105 |
js_FinishJIT(&data->traceMonitor); |
106 |
#endif |
107 |
} |
108 |
|
109 |
static void |
110 |
PurgeThreadData(JSContext *cx, JSThreadData *data) |
111 |
{ |
112 |
js_PurgeGSNCache(&data->gsnCache); |
113 |
|
114 |
/* FIXME: bug 506341. */ |
115 |
js_PurgePropertyCache(cx, &data->propertyCache); |
116 |
|
117 |
# ifdef JS_TRACER |
118 |
JSTraceMonitor *tm = &data->traceMonitor; |
119 |
|
120 |
/* |
121 |
* If we are about to regenerate shapes, we have to flush the JIT cache, |
122 |
* which will eventually abort any current recording. |
123 |
*/ |
124 |
if (cx->runtime->gcRegenShapes) |
125 |
tm->needFlush = JS_TRUE; |
126 |
|
127 |
/* |
128 |
* We want to keep reserved doubles and objects after the GC. So, unless we |
129 |
* are shutting down, we don't purge them here and rather mark them during |
130 |
* the GC, see MarkReservedObjects in jsgc.cpp. |
131 |
*/ |
132 |
if (cx->runtime->state == JSRTS_LANDING) { |
133 |
tm->reservedDoublePoolPtr = tm->reservedDoublePool; |
134 |
tm->reservedObjects = NULL; |
135 |
} |
136 |
# endif |
137 |
|
138 |
/* Destroy eval'ed scripts. */ |
139 |
js_DestroyScriptsToGC(cx, data); |
140 |
|
141 |
js_PurgeCachedNativeEnumerators(cx, data); |
142 |
} |
143 |
|
144 |
#ifdef JS_THREADSAFE |
145 |
|
146 |
static JSThread * |
147 |
NewThread(jsword id) |
148 |
{ |
149 |
JS_ASSERT(js_CurrentThreadId() == id); |
150 |
JSThread *thread = (JSThread *) js_calloc(sizeof(JSThread)); |
151 |
if (!thread) |
152 |
return NULL; |
153 |
JS_INIT_CLIST(&thread->contextList); |
154 |
thread->id = id; |
155 |
InitThreadData(&thread->data); |
156 |
return thread; |
157 |
} |
158 |
|
159 |
static void |
160 |
DestroyThread(JSThread *thread) |
161 |
{ |
162 |
/* The thread must have zero contexts. */ |
163 |
JS_ASSERT(JS_CLIST_IS_EMPTY(&thread->contextList)); |
164 |
JS_ASSERT(!thread->titleToShare); |
165 |
FinishThreadData(&thread->data); |
166 |
js_free(thread); |
167 |
} |
168 |
|
169 |
JSThread * |
170 |
js_CurrentThread(JSRuntime *rt) |
171 |
{ |
172 |
jsword id = js_CurrentThreadId(); |
173 |
JS_LOCK_GC(rt); |
174 |
|
175 |
/* |
176 |
* We must not race with a GC that accesses cx->thread for JSContext |
177 |
* instances on all threads, see bug 476934. |
178 |
*/ |
179 |
js_WaitForGC(rt); |
180 |
JSThreadsHashEntry *entry = (JSThreadsHashEntry *) |
181 |
JS_DHashTableOperate(&rt->threads, |
182 |
(const void *) id, |
183 |
JS_DHASH_LOOKUP); |
184 |
JSThread *thread; |
185 |
if (JS_DHASH_ENTRY_IS_BUSY(&entry->base)) { |
186 |
thread = entry->thread; |
187 |
JS_ASSERT(thread->id == id); |
188 |
} else { |
189 |
JS_UNLOCK_GC(rt); |
190 |
thread = NewThread(id); |
191 |
if (!thread) |
192 |
return NULL; |
193 |
JS_LOCK_GC(rt); |
194 |
js_WaitForGC(rt); |
195 |
entry = (JSThreadsHashEntry *) |
196 |
JS_DHashTableOperate(&rt->threads, (const void *) id, |
197 |
JS_DHASH_ADD); |
198 |
if (!entry) { |
199 |
JS_UNLOCK_GC(rt); |
200 |
DestroyThread(thread); |
201 |
return NULL; |
202 |
} |
203 |
|
204 |
/* Another thread cannot initialize entry->thread. */ |
205 |
JS_ASSERT(!entry->thread); |
206 |
entry->thread = thread; |
207 |
} |
208 |
|
209 |
return thread; |
210 |
} |
211 |
|
212 |
JSBool |
213 |
js_InitContextThread(JSContext *cx) |
214 |
{ |
215 |
JSThread *thread = js_CurrentThread(cx->runtime); |
216 |
if (!thread) |
217 |
return false; |
218 |
|
219 |
JS_APPEND_LINK(&cx->threadLinks, &thread->contextList); |
220 |
cx->thread = thread; |
221 |
return true; |
222 |
} |
223 |
|
224 |
void |
225 |
js_ClearContextThread(JSContext *cx) |
226 |
{ |
227 |
JS_ASSERT(CURRENT_THREAD_IS_ME(cx->thread)); |
228 |
JS_REMOVE_AND_INIT_LINK(&cx->threadLinks); |
229 |
cx->thread = NULL; |
230 |
} |
231 |
|
232 |
static JSBool |
233 |
thread_matchEntry(JSDHashTable *table, |
234 |
const JSDHashEntryHdr *hdr, |
235 |
const void *key) |
236 |
{ |
237 |
const JSThreadsHashEntry *entry = (const JSThreadsHashEntry *) hdr; |
238 |
|
239 |
return entry->thread->id == (jsword) key; |
240 |
} |
241 |
|
242 |
static const JSDHashTableOps threads_ops = { |
243 |
JS_DHashAllocTable, |
244 |
JS_DHashFreeTable, |
245 |
JS_DHashVoidPtrKeyStub, |
246 |
thread_matchEntry, |
247 |
JS_DHashMoveEntryStub, |
248 |
JS_DHashClearEntryStub, |
249 |
JS_DHashFinalizeStub, |
250 |
NULL |
251 |
}; |
252 |
|
253 |
static JSDHashOperator |
254 |
thread_destroyer(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 /* index */, |
255 |
void * /* arg */) |
256 |
{ |
257 |
JSThreadsHashEntry *entry = (JSThreadsHashEntry *) hdr; |
258 |
JSThread *thread = entry->thread; |
259 |
|
260 |
JS_ASSERT(JS_CLIST_IS_EMPTY(&thread->contextList)); |
261 |
DestroyThread(thread); |
262 |
return JS_DHASH_REMOVE; |
263 |
} |
264 |
|
265 |
static JSDHashOperator |
266 |
thread_purger(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 /* index */, |
267 |
void *arg) |
268 |
{ |
269 |
JSContext* cx = (JSContext *) arg; |
270 |
JSThread *thread = ((JSThreadsHashEntry *) hdr)->thread; |
271 |
|
272 |
if (JS_CLIST_IS_EMPTY(&thread->contextList)) { |
273 |
JS_ASSERT(cx->thread != thread); |
274 |
js_DestroyScriptsToGC(cx, &thread->data); |
275 |
|
276 |
/* |
277 |
* The following is potentially suboptimal as it also zeros the cache |
278 |
* in data, but the code simplicity wins here. |
279 |
*/ |
280 |
js_PurgeCachedNativeEnumerators(cx, &thread->data); |
281 |
DestroyThread(thread); |
282 |
return JS_DHASH_REMOVE; |
283 |
} |
284 |
PurgeThreadData(cx, &thread->data); |
285 |
memset(thread->gcFreeLists, 0, sizeof(thread->gcFreeLists)); |
286 |
return JS_DHASH_NEXT; |
287 |
} |
288 |
|
289 |
static JSDHashOperator |
290 |
thread_tracer(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 /* index */, |
291 |
void *arg) |
292 |
{ |
293 |
JSThread *thread = ((JSThreadsHashEntry *) hdr)->thread; |
294 |
thread->data.mark((JSTracer *)arg); |
295 |
return JS_DHASH_NEXT; |
296 |
} |
297 |
|
298 |
#endif /* JS_THREADSAFE */ |
299 |
|
300 |
JSThreadData * |
301 |
js_CurrentThreadData(JSRuntime *rt) |
302 |
{ |
303 |
#ifdef JS_THREADSAFE |
304 |
JSThread *thread = js_CurrentThread(rt); |
305 |
if (!thread) |
306 |
return NULL; |
307 |
|
308 |
return &thread->data; |
309 |
#else |
310 |
return &rt->threadData; |
311 |
#endif |
312 |
} |
313 |
|
314 |
JSBool |
315 |
js_InitThreads(JSRuntime *rt) |
316 |
{ |
317 |
#ifdef JS_THREADSAFE |
318 |
if (!JS_DHashTableInit(&rt->threads, &threads_ops, NULL, |
319 |
sizeof(JSThreadsHashEntry), 4)) { |
320 |
rt->threads.ops = NULL; |
321 |
return false; |
322 |
} |
323 |
#else |
324 |
InitThreadData(&rt->threadData); |
325 |
#endif |
326 |
return true; |
327 |
} |
328 |
|
329 |
void |
330 |
js_FinishThreads(JSRuntime *rt) |
331 |
{ |
332 |
#ifdef JS_THREADSAFE |
333 |
if (!rt->threads.ops) |
334 |
return; |
335 |
JS_DHashTableEnumerate(&rt->threads, thread_destroyer, NULL); |
336 |
JS_DHashTableFinish(&rt->threads); |
337 |
rt->threads.ops = NULL; |
338 |
#else |
339 |
FinishThreadData(&rt->threadData); |
340 |
#endif |
341 |
} |
342 |
|
343 |
void |
344 |
js_PurgeThreads(JSContext *cx) |
345 |
{ |
346 |
#ifdef JS_THREADSAFE |
347 |
JS_DHashTableEnumerate(&cx->runtime->threads, thread_purger, cx); |
348 |
#else |
349 |
PurgeThreadData(cx, &cx->runtime->threadData); |
350 |
#endif |
351 |
} |
352 |
|
353 |
void |
354 |
js_TraceThreads(JSRuntime *rt, JSTracer *trc) |
355 |
{ |
356 |
#ifdef JS_THREADSAFE |
357 |
JS_DHashTableEnumerate(&rt->threads, thread_tracer, trc); |
358 |
#else |
359 |
rt->threadData.mark(trc); |
360 |
#endif |
361 |
} |
362 |
|
363 |
/* |
364 |
* JSOPTION_XML and JSOPTION_ANONFUNFIX must be part of the JS version |
365 |
* associated with scripts, so in addition to storing them in cx->options we |
366 |
* duplicate them in cx->version (script->version, etc.) and ensure each bit |
367 |
* remains synchronized between the two through these two functions. |
368 |
*/ |
369 |
void |
370 |
js_SyncOptionsToVersion(JSContext* cx) |
371 |
{ |
372 |
if (cx->options & JSOPTION_XML) |
373 |
cx->version |= JSVERSION_HAS_XML; |
374 |
else |
375 |
cx->version &= ~JSVERSION_HAS_XML; |
376 |
if (cx->options & JSOPTION_ANONFUNFIX) |
377 |
cx->version |= JSVERSION_ANONFUNFIX; |
378 |
else |
379 |
cx->version &= ~JSVERSION_ANONFUNFIX; |
380 |
} |
381 |
|
382 |
inline void |
383 |
js_SyncVersionToOptions(JSContext* cx) |
384 |
{ |
385 |
if (cx->version & JSVERSION_HAS_XML) |
386 |
cx->options |= JSOPTION_XML; |
387 |
else |
388 |
cx->options &= ~JSOPTION_XML; |
389 |
if (cx->version & JSVERSION_ANONFUNFIX) |
390 |
cx->options |= JSOPTION_ANONFUNFIX; |
391 |
else |
392 |
cx->options &= ~JSOPTION_ANONFUNFIX; |
393 |
} |
394 |
|
395 |
void |
396 |
js_OnVersionChange(JSContext *cx) |
397 |
{ |
398 |
#ifdef DEBUG |
399 |
JSVersion version = JSVERSION_NUMBER(cx); |
400 |
|
401 |
JS_ASSERT(version == JSVERSION_DEFAULT || version >= JSVERSION_ECMA_3); |
402 |
#endif |
403 |
} |
404 |
|
405 |
void |
406 |
js_SetVersion(JSContext *cx, JSVersion version) |
407 |
{ |
408 |
cx->version = version; |
409 |
js_SyncVersionToOptions(cx); |
410 |
js_OnVersionChange(cx); |
411 |
} |
412 |
|
413 |
JSContext * |
414 |
js_NewContext(JSRuntime *rt, size_t stackChunkSize) |
415 |
{ |
416 |
JSContext *cx; |
417 |
JSBool ok, first; |
418 |
JSContextCallback cxCallback; |
419 |
|
420 |
/* |
421 |
* We need to initialize the new context fully before adding it to the |
422 |
* runtime list. After that it can be accessed from another thread via |
423 |
* js_ContextIterator. |
424 |
*/ |
425 |
void *mem = js_calloc(sizeof *cx); |
426 |
if (!mem) |
427 |
return NULL; |
428 |
|
429 |
cx = new (mem) JSContext(rt); |
430 |
cx->debugHooks = &rt->globalDebugHooks; |
431 |
#if JS_STACK_GROWTH_DIRECTION > 0 |
432 |
cx->stackLimit = (jsuword) -1; |
433 |
#endif |
434 |
cx->scriptStackQuota = JS_DEFAULT_SCRIPT_STACK_QUOTA; |
435 |
JS_STATIC_ASSERT(JSVERSION_DEFAULT == 0); |
436 |
JS_ASSERT(cx->version == JSVERSION_DEFAULT); |
437 |
VOUCH_DOES_NOT_REQUIRE_STACK(); |
438 |
JS_INIT_ARENA_POOL(&cx->stackPool, "stack", stackChunkSize, sizeof(jsval), |
439 |
&cx->scriptStackQuota); |
440 |
|
441 |
JS_INIT_ARENA_POOL(&cx->tempPool, "temp", |
442 |
1024, /* FIXME: bug 421435 */ |
443 |
sizeof(jsdouble), &cx->scriptStackQuota); |
444 |
|
445 |
js_InitRegExpStatics(cx); |
446 |
JS_ASSERT(cx->resolveFlags == 0); |
447 |
|
448 |
if (!js_InitContextBusyArrayTable(cx)) { |
449 |
FreeContext(cx); |
450 |
return NULL; |
451 |
} |
452 |
|
453 |
#ifdef JS_THREADSAFE |
454 |
if (!js_InitContextThread(cx)) { |
455 |
FreeContext(cx); |
456 |
return NULL; |
457 |
} |
458 |
#endif |
459 |
|
460 |
/* |
461 |
* Here the GC lock is still held after js_InitContextThread took it and |
462 |
* the GC is not running on another thread. |
463 |
*/ |
464 |
for (;;) { |
465 |
if (rt->state == JSRTS_UP) { |
466 |
JS_ASSERT(!JS_CLIST_IS_EMPTY(&rt->contextList)); |
467 |
first = JS_FALSE; |
468 |
break; |
469 |
} |
470 |
if (rt->state == JSRTS_DOWN) { |
471 |
JS_ASSERT(JS_CLIST_IS_EMPTY(&rt->contextList)); |
472 |
first = JS_TRUE; |
473 |
rt->state = JSRTS_LAUNCHING; |
474 |
break; |
475 |
} |
476 |
JS_WAIT_CONDVAR(rt->stateChange, JS_NO_TIMEOUT); |
477 |
|
478 |
/* |
479 |
* During the above wait after we are notified about the state change |
480 |
* but before we wake up, another thread could enter the GC from |
481 |
* js_DestroyContext, bug 478336. So we must wait here to ensure that |
482 |
* when we exit the loop with the first flag set to true, that GC is |
483 |
* finished. |
484 |
*/ |
485 |
js_WaitForGC(rt); |
486 |
} |
487 |
JS_APPEND_LINK(&cx->link, &rt->contextList); |
488 |
JS_UNLOCK_GC(rt); |
489 |
|
490 |
/* |
491 |
* If cx is the first context on this runtime, initialize well-known atoms, |
492 |
* keywords, numbers, and strings. If one of these steps should fail, the |
493 |
* runtime will be left in a partially initialized state, with zeroes and |
494 |
* nulls stored in the default-initialized remainder of the struct. We'll |
495 |
* clean the runtime up under js_DestroyContext, because cx will be "last" |
496 |
* as well as "first". |
497 |
*/ |
498 |
if (first) { |
499 |
#ifdef JS_THREADSAFE |
500 |
JS_BeginRequest(cx); |
501 |
#endif |
502 |
ok = js_InitCommonAtoms(cx); |
503 |
|
504 |
/* |
505 |
* scriptFilenameTable may be left over from a previous episode of |
506 |
* non-zero contexts alive in rt, so don't re-init the table if it's |
507 |
* not necessary. |
508 |
*/ |
509 |
if (ok && !rt->scriptFilenameTable) |
510 |
ok = js_InitRuntimeScriptState(rt); |
511 |
if (ok) |
512 |
ok = js_InitRuntimeNumberState(cx); |
513 |
if (ok) |
514 |
ok = js_InitRuntimeStringState(cx); |
515 |
#ifdef JS_THREADSAFE |
516 |
JS_EndRequest(cx); |
517 |
#endif |
518 |
if (!ok) { |
519 |
js_DestroyContext(cx, JSDCM_NEW_FAILED); |
520 |
return NULL; |
521 |
} |
522 |
|
523 |
JS_LOCK_GC(rt); |
524 |
rt->state = JSRTS_UP; |
525 |
JS_NOTIFY_ALL_CONDVAR(rt->stateChange); |
526 |
JS_UNLOCK_GC(rt); |
527 |
} |
528 |
|
529 |
cxCallback = rt->cxCallback; |
530 |
if (cxCallback && !cxCallback(cx, JSCONTEXT_NEW)) { |
531 |
js_DestroyContext(cx, JSDCM_NEW_FAILED); |
532 |
return NULL; |
533 |
} |
534 |
|
535 |
return cx; |
536 |
} |
537 |
|
538 |
#if defined DEBUG && defined XP_UNIX |
539 |
# include <stdio.h> |
540 |
|
541 |
class JSAutoFile { |
542 |
public: |
543 |
JSAutoFile() : mFile(NULL) {} |
544 |
|
545 |
~JSAutoFile() { |
546 |
if (mFile) |
547 |
fclose(mFile); |
548 |
} |
549 |
|
550 |
FILE *open(const char *fname, const char *mode) { |
551 |
return mFile = fopen(fname, mode); |
552 |
} |
553 |
operator FILE *() { |
554 |
return mFile; |
555 |
} |
556 |
|
557 |
private: |
558 |
FILE *mFile; |
559 |
}; |
560 |
|
561 |
#ifdef JS_EVAL_CACHE_METERING |
562 |
static void |
563 |
DumpEvalCacheMeter(JSContext *cx) |
564 |
{ |
565 |
struct { |
566 |
const char *name; |
567 |
ptrdiff_t offset; |
568 |
} table[] = { |
569 |
#define frob(x) { #x, offsetof(JSEvalCacheMeter, x) } |
570 |
EVAL_CACHE_METER_LIST(frob) |
571 |
#undef frob |
572 |
}; |
573 |
JSEvalCacheMeter *ecm = &JS_THREAD_DATA(cx)->evalCacheMeter; |
574 |
|
575 |
static JSAutoFile fp; |
576 |
if (!fp) { |
577 |
fp.open("/tmp/evalcache.stats", "w"); |
578 |
if (!fp) |
579 |
return; |
580 |
} |
581 |
|
582 |
fprintf(fp, "eval cache meter (%p):\n", |
583 |
#ifdef JS_THREADSAFE |
584 |
(void *) cx->thread |
585 |
#else |
586 |
(void *) cx->runtime |
587 |
#endif |
588 |
); |
589 |
for (uintN i = 0; i < JS_ARRAY_LENGTH(table); ++i) { |
590 |
fprintf(fp, "%-8.8s %llu\n", |
591 |
table[i].name, |
592 |
(unsigned long long int) *(uint64 *)((uint8 *)ecm + table[i].offset)); |
593 |
} |
594 |
fprintf(fp, "hit ratio %g%%\n", ecm->hit * 100. / ecm->probe); |
595 |
fprintf(fp, "avg steps %g\n", double(ecm->step) / ecm->probe); |
596 |
fflush(fp); |
597 |
} |
598 |
# define DUMP_EVAL_CACHE_METER(cx) DumpEvalCacheMeter(cx) |
599 |
#endif |
600 |
|
601 |
#ifdef JS_FUNCTION_METERING |
602 |
static void |
603 |
DumpFunctionMeter(JSContext *cx) |
604 |
{ |
605 |
struct { |
606 |
const char *name; |
607 |
ptrdiff_t offset; |
608 |
} table[] = { |
609 |
#define frob(x) { #x, offsetof(JSFunctionMeter, x) } |
610 |
FUNCTION_KIND_METER_LIST(frob) |
611 |
#undef frob |
612 |
}; |
613 |
JSFunctionMeter *fm = &cx->runtime->functionMeter; |
614 |
|
615 |
static JSAutoFile fp; |
616 |
if (!fp) { |
617 |
fp.open("/tmp/function.stats", "a"); |
618 |
if (!fp) |
619 |
return; |
620 |
} |
621 |
|
622 |
fprintf(fp, "function meter (%s):\n", cx->runtime->lastScriptFilename); |
623 |
for (uintN i = 0; i < JS_ARRAY_LENGTH(table); ++i) { |
624 |
fprintf(fp, "%-11.11s %d\n", |
625 |
table[i].name, *(int32 *)((uint8 *)fm + table[i].offset)); |
626 |
} |
627 |
fflush(fp); |
628 |
} |
629 |
# define DUMP_FUNCTION_METER(cx) DumpFunctionMeter(cx) |
630 |
#endif |
631 |
|
632 |
#endif /* DEBUG && XP_UNIX */ |
633 |
|
634 |
#ifndef DUMP_EVAL_CACHE_METER |
635 |
# define DUMP_EVAL_CACHE_METER(cx) ((void) 0) |
636 |
#endif |
637 |
|
638 |
#ifndef DUMP_FUNCTION_METER |
639 |
# define DUMP_FUNCTION_METER(cx) ((void) 0) |
640 |
#endif |
641 |
|
642 |
void |
643 |
js_DestroyContext(JSContext *cx, JSDestroyContextMode mode) |
644 |
{ |
645 |
JSRuntime *rt; |
646 |
JSContextCallback cxCallback; |
647 |
JSBool last; |
648 |
|
649 |
rt = cx->runtime; |
650 |
#ifdef JS_THREADSAFE |
651 |
/* |
652 |
* For API compatibility we allow to destroy contexts without a thread in |
653 |
* optimized builds. We assume that the embedding knows that an OOM error |
654 |
* cannot happen in JS_SetContextThread. |
655 |
*/ |
656 |
JS_ASSERT(cx->thread && CURRENT_THREAD_IS_ME(cx->thread)); |
657 |
if (!cx->thread) |
658 |
JS_SetContextThread(cx); |
659 |
|
660 |
JS_ASSERT_IF(rt->gcRunning, cx->outstandingRequests == 0); |
661 |
#endif |
662 |
|
663 |
if (mode != JSDCM_NEW_FAILED) { |
664 |
cxCallback = rt->cxCallback; |
665 |
if (cxCallback) { |
666 |
/* |
667 |
* JSCONTEXT_DESTROY callback is not allowed to fail and must |
668 |
* return true. |
669 |
*/ |
670 |
#ifdef DEBUG |
671 |
JSBool callbackStatus = |
672 |
#endif |
673 |
cxCallback(cx, JSCONTEXT_DESTROY); |
674 |
JS_ASSERT(callbackStatus); |
675 |
} |
676 |
} |
677 |
|
678 |
JS_LOCK_GC(rt); |
679 |
JS_ASSERT(rt->state == JSRTS_UP || rt->state == JSRTS_LAUNCHING); |
680 |
#ifdef JS_THREADSAFE |
681 |
/* |
682 |
* Typically we are called outside a request, so ensure that the GC is not |
683 |
* running before removing the context from rt->contextList, see bug 477021. |
684 |
*/ |
685 |
if (cx->requestDepth == 0) |
686 |
js_WaitForGC(rt); |
687 |
#endif |
688 |
JS_REMOVE_LINK(&cx->link); |
689 |
last = (rt->contextList.next == &rt->contextList); |
690 |
if (last) |
691 |
rt->state = JSRTS_LANDING; |
692 |
if (last || mode == JSDCM_FORCE_GC || mode == JSDCM_MAYBE_GC |
693 |
#ifdef JS_THREADSAFE |
694 |
|| cx->requestDepth != 0 |
695 |
#endif |
696 |
) { |
697 |
JS_ASSERT(!rt->gcRunning); |
698 |
|
699 |
JS_UNLOCK_GC(rt); |
700 |
|
701 |
if (last) { |
702 |
#ifdef JS_THREADSAFE |
703 |
/* |
704 |
* If cx is not in a request already, begin one now so that we wait |
705 |
* for any racing GC started on a not-last context to finish, before |
706 |
* we plow ahead and unpin atoms. Note that even though we begin a |
707 |
* request here if necessary, we end all requests on cx below before |
708 |
* forcing a final GC. This lets any not-last context destruction |
709 |
* racing in another thread try to force or maybe run the GC, but by |
710 |
* that point, rt->state will not be JSRTS_UP, and that GC attempt |
711 |
* will return early. |
712 |
*/ |
713 |
if (cx->requestDepth == 0) |
714 |
JS_BeginRequest(cx); |
715 |
#endif |
716 |
|
717 |
/* Unlock and clear GC things held by runtime pointers. */ |
718 |
js_FinishRuntimeNumberState(cx); |
719 |
js_FinishRuntimeStringState(cx); |
720 |
|
721 |
/* Unpin all common atoms before final GC. */ |
722 |
js_FinishCommonAtoms(cx); |
723 |
|
724 |
/* Clear debugging state to remove GC roots. */ |
725 |
JS_ClearAllTraps(cx); |
726 |
JS_ClearAllWatchPoints(cx); |
727 |
} |
728 |
|
729 |
/* Remove more GC roots in regExpStatics, then collect garbage. */ |
730 |
JS_ClearRegExpRoots(cx); |
731 |
|
732 |
#ifdef JS_THREADSAFE |
733 |
/* |
734 |
* Destroying a context implicitly calls JS_EndRequest(). Also, we must |
735 |
* end our request here in case we are "last" -- in that event, another |
736 |
* js_DestroyContext that was not last might be waiting in the GC for our |
737 |
* request to end. We'll let it run below, just before we do the truly |
738 |
* final GC and then free atom state. |
739 |
*/ |
740 |
while (cx->requestDepth != 0) |
741 |
JS_EndRequest(cx); |
742 |
#endif |
743 |
|
744 |
if (last) { |
745 |
js_GC(cx, GC_LAST_CONTEXT); |
746 |
DUMP_EVAL_CACHE_METER(cx); |
747 |
DUMP_FUNCTION_METER(cx); |
748 |
|
749 |
/* Take the runtime down, now that it has no contexts or atoms. */ |
750 |
JS_LOCK_GC(rt); |
751 |
rt->state = JSRTS_DOWN; |
752 |
JS_NOTIFY_ALL_CONDVAR(rt->stateChange); |
753 |
} else { |
754 |
if (mode == JSDCM_FORCE_GC) |
755 |
js_GC(cx, GC_NORMAL); |
756 |
else if (mode == JSDCM_MAYBE_GC) |
757 |
JS_MaybeGC(cx); |
758 |
JS_LOCK_GC(rt); |
759 |
js_WaitForGC(rt); |
760 |
} |
761 |
} |
762 |
#ifdef JS_THREADSAFE |
763 |
js_ClearContextThread(cx); |
764 |
#endif |
765 |
JS_UNLOCK_GC(rt); |
766 |
FreeContext(cx); |
767 |
} |
768 |
|
769 |
static void |
770 |
FreeContext(JSContext *cx) |
771 |
{ |
772 |
JSArgumentFormatMap *map; |
773 |
JSLocalRootStack *lrs; |
774 |
JSLocalRootChunk *lrc; |
775 |
|
776 |
#ifdef JS_THREADSAFE |
777 |
JS_ASSERT(!cx->thread); |
778 |
#endif |
779 |
|
780 |
/* Free the stuff hanging off of cx. */ |
781 |
js_FreeRegExpStatics(cx); |
782 |
VOUCH_DOES_NOT_REQUIRE_STACK(); |
783 |
JS_FinishArenaPool(&cx->stackPool); |
784 |
JS_FinishArenaPool(&cx->tempPool); |
785 |
|
786 |
if (cx->lastMessage) |
787 |
js_free(cx->lastMessage); |
788 |
|
789 |
/* Remove any argument formatters. */ |
790 |
map = cx->argumentFormatMap; |
791 |
while (map) { |
792 |
JSArgumentFormatMap *temp = map; |
793 |
map = map->next; |
794 |
cx->free(temp); |
795 |
} |
796 |
|
797 |
/* Destroy the busy array table. */ |
798 |
if (cx->busyArrayTable) { |
799 |
JS_HashTableDestroy(cx->busyArrayTable); |
800 |
cx->busyArrayTable = NULL; |
801 |
} |
802 |
|
803 |
/* Destroy the resolve recursion damper. */ |
804 |
if (cx->resolvingTable) { |
805 |
JS_DHashTableDestroy(cx->resolvingTable); |
806 |
cx->resolvingTable = NULL; |
807 |
} |
808 |
|
809 |
lrs = cx->localRootStack; |
810 |
if (lrs) { |
811 |
while ((lrc = lrs->topChunk) != &lrs->firstChunk) { |
812 |
lrs->topChunk = lrc->down; |
813 |
cx->free(lrc); |
814 |
} |
815 |
cx->free(lrs); |
816 |
} |
817 |
|
818 |
/* Finally, free cx itself. */ |
819 |
js_free(cx); |
820 |
} |
821 |
|
822 |
JSBool |
823 |
js_ValidContextPointer(JSRuntime *rt, JSContext *cx) |
824 |
{ |
825 |
JSCList *cl; |
826 |
|
827 |
for (cl = rt->contextList.next; cl != &rt->contextList; cl = cl->next) { |
828 |
if (cl == &cx->link) |
829 |
return JS_TRUE; |
830 |
} |
831 |
JS_RUNTIME_METER(rt, deadContexts); |
832 |
return JS_FALSE; |
833 |
} |
834 |
|
835 |
JSContext * |
836 |
js_ContextIterator(JSRuntime *rt, JSBool unlocked, JSContext **iterp) |
837 |
{ |
838 |
JSContext *cx = *iterp; |
839 |
|
840 |
if (unlocked) |
841 |
JS_LOCK_GC(rt); |
842 |
cx = js_ContextFromLinkField(cx ? cx->link.next : rt->contextList.next); |
843 |
if (&cx->link == &rt->contextList) |
844 |
cx = NULL; |
845 |
*iterp = cx; |
846 |
if (unlocked) |
847 |
JS_UNLOCK_GC(rt); |
848 |
return cx; |
849 |
} |
850 |
|
851 |
JS_FRIEND_API(JSContext *) |
852 |
js_NextActiveContext(JSRuntime *rt, JSContext *cx) |
853 |
{ |
854 |
JSContext *iter = cx; |
855 |
#ifdef JS_THREADSAFE |
856 |
while ((cx = js_ContextIterator(rt, JS_FALSE, &iter)) != NULL) { |
857 |
if (cx->requestDepth) |
858 |
break; |
859 |
} |
860 |
return cx; |
861 |
#else |
862 |
return js_ContextIterator(rt, JS_FALSE, &iter); |
863 |
#endif |
864 |
} |
865 |
|
866 |
#ifdef JS_THREADSAFE |
867 |
|
868 |
uint32 |
869 |
js_CountThreadRequests(JSContext *cx) |
870 |
{ |
871 |
JSCList *head, *link; |
872 |
uint32 nrequests; |
873 |
|
874 |
JS_ASSERT(CURRENT_THREAD_IS_ME(cx->thread)); |
875 |
head = &cx->thread->contextList; |
876 |
nrequests = 0; |
877 |
for (link = head->next; link != head; link = link->next) { |
878 |
JSContext *acx = CX_FROM_THREAD_LINKS(link); |
879 |
JS_ASSERT(acx->thread == cx->thread); |
880 |
if (acx->requestDepth) |
881 |
nrequests++; |
882 |
} |
883 |
return nrequests; |
884 |
} |
885 |
|
886 |
/* |
887 |
* If the GC is running and we're called on another thread, wait for this GC |
888 |
* activation to finish. We can safely wait here without fear of deadlock (in |
889 |
* the case where we are called within a request on another thread's context) |
890 |
* because the GC doesn't set rt->gcRunning until after it has waited for all |
891 |
* active requests to end. |
892 |
* |
893 |
* We call here js_CurrentThreadId() after checking for rt->gcRunning to avoid |
894 |
* expensive calls when the GC is not running. |
895 |
*/ |
896 |
void |
897 |
js_WaitForGC(JSRuntime *rt) |
898 |
{ |
899 |
JS_ASSERT_IF(rt->gcRunning, rt->gcLevel > 0); |
900 |
if (rt->gcRunning && rt->gcThread->id != js_CurrentThreadId()) { |
901 |
do { |
902 |
JS_AWAIT_GC_DONE(rt); |
903 |
} while (rt->gcRunning); |
904 |
} |
905 |
} |
906 |
|
907 |
uint32 |
908 |
js_DiscountRequestsForGC(JSContext *cx) |
909 |
{ |
910 |
uint32 requestDebit; |
911 |
|
912 |
JS_ASSERT(cx->thread); |
913 |
JS_ASSERT(cx->runtime->gcThread != cx->thread); |
914 |
|
915 |
#ifdef JS_TRACER |
916 |
if (JS_ON_TRACE(cx)) { |
917 |
JS_UNLOCK_GC(cx->runtime); |
918 |
js_LeaveTrace(cx); |
919 |
JS_LOCK_GC(cx->runtime); |
920 |
} |
921 |
#endif |
922 |
|
923 |
requestDebit = js_CountThreadRequests(cx); |
924 |
if (requestDebit != 0) { |
925 |
JSRuntime *rt = cx->runtime; |
926 |
JS_ASSERT(requestDebit <= rt->requestCount); |
927 |
rt->requestCount -= requestDebit; |
928 |
if (rt->requestCount == 0) |
929 |
JS_NOTIFY_REQUEST_DONE(rt); |
930 |
} |
931 |
return requestDebit; |
932 |
} |
933 |
|
934 |
void |
935 |
js_RecountRequestsAfterGC(JSRuntime *rt, uint32 requestDebit) |
936 |
{ |
937 |
while (rt->gcLevel > 0) { |
938 |
JS_ASSERT(rt->gcThread); |
939 |
JS_AWAIT_GC_DONE(rt); |
940 |
} |
941 |
if (requestDebit != 0) |
942 |
rt->requestCount += requestDebit; |
943 |
} |
944 |
|
945 |
#endif |
946 |
|
947 |
static JSDHashNumber |
948 |
resolving_HashKey(JSDHashTable *table, const void *ptr) |
949 |
{ |
950 |
const JSResolvingKey *key = (const JSResolvingKey *)ptr; |
951 |
|
952 |
return ((JSDHashNumber)JS_PTR_TO_UINT32(key->obj) >> JSVAL_TAGBITS) ^ key->id; |
953 |
} |
954 |
|
955 |
JS_PUBLIC_API(JSBool) |
956 |
resolving_MatchEntry(JSDHashTable *table, |
957 |
const JSDHashEntryHdr *hdr, |
958 |
const void *ptr) |
959 |
{ |
960 |
const JSResolvingEntry *entry = (const JSResolvingEntry *)hdr; |
961 |
const JSResolvingKey *key = (const JSResolvingKey *)ptr; |
962 |
|
963 |
return entry->key.obj == key->obj && entry->key.id == key->id; |
964 |
} |
965 |
|
966 |
static const JSDHashTableOps resolving_dhash_ops = { |
967 |
JS_DHashAllocTable, |
968 |
JS_DHashFreeTable, |
969 |
resolving_HashKey, |
970 |
resolving_MatchEntry, |
971 |
JS_DHashMoveEntryStub, |
972 |
JS_DHashClearEntryStub, |
973 |
JS_DHashFinalizeStub, |
974 |
NULL |
975 |
}; |
976 |
|
977 |
JSBool |
978 |
js_StartResolving(JSContext *cx, JSResolvingKey *key, uint32 flag, |
979 |
JSResolvingEntry **entryp) |
980 |
{ |
981 |
JSDHashTable *table; |
982 |
JSResolvingEntry *entry; |
983 |
|
984 |
table = cx->resolvingTable; |
985 |
if (!table) { |
986 |
table = JS_NewDHashTable(&resolving_dhash_ops, NULL, |
987 |
sizeof(JSResolvingEntry), |
988 |
JS_DHASH_MIN_SIZE); |
989 |
if (!table) |
990 |
goto outofmem; |
991 |
cx->resolvingTable = table; |
992 |
} |
993 |
|
994 |
entry = (JSResolvingEntry *) |
995 |
JS_DHashTableOperate(table, key, JS_DHASH_ADD); |
996 |
if (!entry) |
997 |
goto outofmem; |
998 |
|
999 |
if (entry->flags & flag) { |
1000 |
/* An entry for (key, flag) exists already -- dampen recursion. */ |
1001 |
entry = NULL; |
1002 |
} else { |
1003 |
/* Fill in key if we were the first to add entry, then set flag. */ |
1004 |
if (!entry->key.obj) |
1005 |
entry->key = *key; |
1006 |
entry->flags |= flag; |
1007 |
} |
1008 |
*entryp = entry; |
1009 |
return JS_TRUE; |
1010 |
|
1011 |
outofmem: |
1012 |
JS_ReportOutOfMemory(cx); |
1013 |
return JS_FALSE; |
1014 |
} |
1015 |
|
1016 |
void |
1017 |
js_StopResolving(JSContext *cx, JSResolvingKey *key, uint32 flag, |
1018 |
JSResolvingEntry *entry, uint32 generation) |
1019 |
{ |
1020 |
JSDHashTable *table; |
1021 |
|
1022 |
/* |
1023 |
* Clear flag from entry->flags and return early if other flags remain. |
1024 |
* We must take care to re-lookup entry if the table has changed since |
1025 |
* it was found by js_StartResolving. |
1026 |
*/ |
1027 |
table = cx->resolvingTable; |
1028 |
if (!entry || table->generation != generation) { |
1029 |
entry = (JSResolvingEntry *) |
1030 |
JS_DHashTableOperate(table, key, JS_DHASH_LOOKUP); |
1031 |
} |
1032 |
JS_ASSERT(JS_DHASH_ENTRY_IS_BUSY(&entry->hdr)); |
1033 |
entry->flags &= ~flag; |
1034 |
if (entry->flags) |
1035 |
return; |
1036 |
|
1037 |
/* |
1038 |
* Do a raw remove only if fewer entries were removed than would cause |
1039 |
* alpha to be less than .5 (alpha is at most .75). Otherwise, we just |
1040 |
* call JS_DHashTableOperate to re-lookup the key and remove its entry, |
1041 |
* compressing or shrinking the table as needed. |
1042 |
*/ |
1043 |
if (table->removedCount < JS_DHASH_TABLE_SIZE(table) >> 2) |
1044 |
JS_DHashTableRawRemove(table, &entry->hdr); |
1045 |
else |
1046 |
JS_DHashTableOperate(table, key, JS_DHASH_REMOVE); |
1047 |
} |
1048 |
|
1049 |
JSBool |
1050 |
js_EnterLocalRootScope(JSContext *cx) |
1051 |
{ |
1052 |
JSLocalRootStack *lrs; |
1053 |
int mark; |
1054 |
|
1055 |
lrs = cx->localRootStack; |
1056 |
if (!lrs) { |
1057 |
lrs = (JSLocalRootStack *) cx->malloc(sizeof *lrs); |
1058 |
if (!lrs) |
1059 |
return JS_FALSE; |
1060 |
lrs->scopeMark = JSLRS_NULL_MARK; |
1061 |
lrs->rootCount = 0; |
1062 |
lrs->topChunk = &lrs->firstChunk; |
1063 |
lrs->firstChunk.down = NULL; |
1064 |
cx->localRootStack = lrs; |
1065 |
} |
1066 |
|
1067 |
/* Push lrs->scopeMark to save it for restore when leaving. */ |
1068 |
mark = js_PushLocalRoot(cx, lrs, INT_TO_JSVAL(lrs->scopeMark)); |
1069 |
if (mark < 0) |
1070 |
return JS_FALSE; |
1071 |
lrs->scopeMark = (uint32) mark; |
1072 |
return JS_TRUE; |
1073 |
} |
1074 |
|
1075 |
void |
1076 |
js_LeaveLocalRootScopeWithResult(JSContext *cx, jsval rval) |
1077 |
{ |
1078 |
JSLocalRootStack *lrs; |
1079 |
uint32 mark, m, n; |
1080 |
JSLocalRootChunk *lrc; |
1081 |
|
1082 |
/* Defend against buggy native callers. */ |
1083 |
lrs = cx->localRootStack; |
1084 |
JS_ASSERT(lrs && lrs->rootCount != 0); |
1085 |
if (!lrs || lrs->rootCount == 0) |
1086 |
return; |
1087 |
|
1088 |
mark = lrs->scopeMark; |
1089 |
JS_ASSERT(mark != JSLRS_NULL_MARK); |
1090 |
if (mark == JSLRS_NULL_MARK) |
1091 |
return; |
1092 |
|
1093 |
/* Free any chunks being popped by this leave operation. */ |
1094 |
m = mark >> JSLRS_CHUNK_SHIFT; |
1095 |
n = (lrs->rootCount - 1) >> JSLRS_CHUNK_SHIFT; |
1096 |
while (n > m) { |
1097 |
lrc = lrs->topChunk; |
1098 |
JS_ASSERT(lrc != &lrs->firstChunk); |
1099 |
lrs->topChunk = lrc->down; |
1100 |
cx->free(lrc); |
1101 |
--n; |
1102 |
} |
1103 |
|
1104 |
/* |
1105 |
* Pop the scope, restoring lrs->scopeMark. If rval is a GC-thing, push |
1106 |
* it on the caller's scope, or store it in lastInternalResult if we are |
1107 |
* leaving the outermost scope. We don't need to allocate a new lrc |
1108 |
* because we can overwrite the old mark's slot with rval. |
1109 |
*/ |
1110 |
lrc = lrs->topChunk; |
1111 |
m = mark & JSLRS_CHUNK_MASK; |
1112 |
lrs->scopeMark = (uint32) JSVAL_TO_INT(lrc->roots[m]); |
1113 |
if (JSVAL_IS_GCTHING(rval) && !JSVAL_IS_NULL(rval)) { |
1114 |
if (mark == 0) { |
1115 |
cx->weakRoots.lastInternalResult = rval; |
1116 |
} else { |
1117 |
/* |
1118 |
* Increment m to avoid the "else if (m == 0)" case below. If |
1119 |
* rval is not a GC-thing, that case would take care of freeing |
1120 |
* any chunk that contained only the old mark. Since rval *is* |
1121 |
* a GC-thing here, we want to reuse that old mark's slot. |
1122 |
*/ |
1123 |
lrc->roots[m++] = rval; |
1124 |
++mark; |
1125 |
} |
1126 |
} |
1127 |
lrs->rootCount = (uint32) mark; |
1128 |
|
1129 |
/* |
1130 |
* Free the stack eagerly, risking malloc churn. The alternative would |
1131 |
* require an lrs->entryCount member, maintained by Enter and Leave, and |
1132 |
* tested by the GC in addition to the cx->localRootStack non-null test. |
1133 |
* |
1134 |
* That approach would risk hoarding 264 bytes (net) per context. Right |
1135 |
* now it seems better to give fresh (dirty in CPU write-back cache, and |
1136 |
* the data is no longer needed) memory back to the malloc heap. |
1137 |
*/ |
1138 |
if (mark == 0) { |
1139 |
cx->localRootStack = NULL; |
1140 |
cx->free(lrs); |
1141 |
} else if (m == 0) { |
1142 |
lrs->topChunk = lrc->down; |
1143 |
cx->free(lrc); |
1144 |
} |
1145 |
} |
1146 |
|
1147 |
void |
1148 |
js_ForgetLocalRoot(JSContext *cx, jsval v) |
1149 |
{ |
1150 |
JSLocalRootStack *lrs; |
1151 |
uint32 i, j, m, n, mark; |
1152 |
JSLocalRootChunk *lrc, *lrc2; |
1153 |
jsval top; |
1154 |
|
1155 |
lrs = cx->localRootStack; |
1156 |
JS_ASSERT(lrs && lrs->rootCount); |
1157 |
if (!lrs || lrs->rootCount == 0) |
1158 |
return; |
1159 |
|
1160 |
/* Prepare to pop the top-most value from the stack. */ |
1161 |
n = lrs->rootCount - 1; |
1162 |
m = n & JSLRS_CHUNK_MASK; |
1163 |
lrc = lrs->topChunk; |
1164 |
top = lrc->roots[m]; |
1165 |
|
1166 |
/* Be paranoid about calls on an empty scope. */ |
1167 |
mark = lrs->scopeMark; |
1168 |
JS_ASSERT(mark < n); |
1169 |
if (mark >= n) |
1170 |
return; |
1171 |
|
1172 |
/* If v was not the last root pushed in the top scope, find it. */ |
1173 |
if (top != v) { |
1174 |
/* Search downward in case v was recently pushed. */ |
1175 |
i = n; |
1176 |
j = m; |
1177 |
lrc2 = lrc; |
1178 |
while (--i > mark) { |
1179 |
if (j == 0) |
1180 |
lrc2 = lrc2->down; |
1181 |
j = i & JSLRS_CHUNK_MASK; |
1182 |
if (lrc2->roots[j] == v) |
1183 |
break; |
1184 |
} |
1185 |
|
1186 |
/* If we didn't find v in this scope, assert and bail out. */ |
1187 |
JS_ASSERT(i != mark); |
1188 |
if (i == mark) |
1189 |
return; |
1190 |
|
1191 |
/* Swap top and v so common tail code can pop v. */ |
1192 |
lrc2->roots[j] = top; |
1193 |
} |
1194 |
|
1195 |
/* Pop the last value from the stack. */ |
1196 |
lrc->roots[m] = JSVAL_NULL; |
1197 |
lrs->rootCount = n; |
1198 |
if (m == 0) { |
1199 |
JS_ASSERT(n != 0); |
1200 |
JS_ASSERT(lrc != &lrs->firstChunk); |
1201 |
lrs->topChunk = lrc->down; |
1202 |
cx->free(lrc); |
1203 |
} |
1204 |
} |
1205 |
|
1206 |
int |
1207 |
js_PushLocalRoot(JSContext *cx, JSLocalRootStack *lrs, jsval v) |
1208 |
{ |
1209 |
uint32 n, m; |
1210 |
JSLocalRootChunk *lrc; |
1211 |
|
1212 |
n = lrs->rootCount; |
1213 |
m = n & JSLRS_CHUNK_MASK; |
1214 |
if (n == 0 || m != 0) { |
1215 |
/* |
1216 |
* At start of first chunk, or not at start of a non-first top chunk. |
1217 |
* Check for lrs->rootCount overflow. |
1218 |
*/ |
1219 |
if ((uint32)(n + 1) == 0) { |
1220 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, |
1221 |
JSMSG_TOO_MANY_LOCAL_ROOTS); |
1222 |
return -1; |
1223 |
} |
1224 |
lrc = lrs->topChunk; |
1225 |
JS_ASSERT(n != 0 || lrc == &lrs->firstChunk); |
1226 |
} else { |
1227 |
/* |
1228 |
* After lrs->firstChunk, trying to index at a power-of-two chunk |
1229 |
* boundary: need a new chunk. |
1230 |
*/ |
1231 |
lrc = (JSLocalRootChunk *) cx->malloc(sizeof *lrc); |
1232 |
if (!lrc) |
1233 |
return -1; |
1234 |
lrc->down = lrs->topChunk; |
1235 |
lrs->topChunk = lrc; |
1236 |
} |
1237 |
lrs->rootCount = n + 1; |
1238 |
lrc->roots[m] = v; |
1239 |
return (int) n; |
1240 |
} |
1241 |
|
1242 |
void |
1243 |
js_TraceLocalRoots(JSTracer *trc, JSLocalRootStack *lrs) |
1244 |
{ |
1245 |
uint32 n, m, mark; |
1246 |
JSLocalRootChunk *lrc; |
1247 |
jsval v; |
1248 |
|
1249 |
n = lrs->rootCount; |
1250 |
if (n == 0) |
1251 |
return; |
1252 |
|
1253 |
mark = lrs->scopeMark; |
1254 |
lrc = lrs->topChunk; |
1255 |
do { |
1256 |
while (--n > mark) { |
1257 |
m = n & JSLRS_CHUNK_MASK; |
1258 |
v = lrc->roots[m]; |
1259 |
JS_ASSERT(JSVAL_IS_GCTHING(v) && v != JSVAL_NULL); |
1260 |
JS_SET_TRACING_INDEX(trc, "local_root", n); |
1261 |
js_CallValueTracerIfGCThing(trc, v); |
1262 |
if (m == 0) |
1263 |
lrc = lrc->down; |
1264 |
} |
1265 |
m = n & JSLRS_CHUNK_MASK; |
1266 |
mark = JSVAL_TO_INT(lrc->roots[m]); |
1267 |
if (m == 0) |
1268 |
lrc = lrc->down; |
1269 |
} while (n != 0); |
1270 |
JS_ASSERT(!lrc); |
1271 |
} |
1272 |
|
1273 |
static void |
1274 |
ReportError(JSContext *cx, const char *message, JSErrorReport *reportp) |
1275 |
{ |
1276 |
/* |
1277 |
* Check the error report, and set a JavaScript-catchable exception |
1278 |
* if the error is defined to have an associated exception. If an |
1279 |
* exception is thrown, then the JSREPORT_EXCEPTION flag will be set |
1280 |
* on the error report, and exception-aware hosts should ignore it. |
1281 |
*/ |
1282 |
JS_ASSERT(reportp); |
1283 |
if (reportp->errorNumber == JSMSG_UNCAUGHT_EXCEPTION) |
1284 |
reportp->flags |= JSREPORT_EXCEPTION; |
1285 |
|
1286 |
/* |
1287 |
* Call the error reporter only if an exception wasn't raised. |
1288 |
* |
1289 |
* If an exception was raised, then we call the debugErrorHook |
1290 |
* (if present) to give it a chance to see the error before it |
1291 |
* propagates out of scope. This is needed for compatability |
1292 |
* with the old scheme. |
1293 |
*/ |
1294 |
if (!JS_IsRunning(cx) || !js_ErrorToException(cx, message, reportp)) { |
1295 |
js_ReportErrorAgain(cx, message, reportp); |
1296 |
} else if (cx->debugHooks->debugErrorHook && cx->errorReporter) { |
1297 |
JSDebugErrorHook hook = cx->debugHooks->debugErrorHook; |
1298 |
/* test local in case debugErrorHook changed on another thread */ |
1299 |
if (hook) |
1300 |
hook(cx, message, reportp, cx->debugHooks->debugErrorHookData); |
1301 |
} |
1302 |
} |
1303 |
|
1304 |
/* The report must be initially zeroed. */ |
1305 |
static void |
1306 |
PopulateReportBlame(JSContext *cx, JSErrorReport *report) |
1307 |
{ |
1308 |
JSStackFrame *fp; |
1309 |
|
1310 |
/* |
1311 |
* Walk stack until we find a frame that is associated with some script |
1312 |
* rather than a native frame. |
1313 |
*/ |
1314 |
for (fp = js_GetTopStackFrame(cx); fp; fp = fp->down) { |
1315 |
if (fp->regs) { |
1316 |
report->filename = fp->script->filename; |
1317 |
report->lineno = js_FramePCToLineNumber(cx, fp); |
1318 |
break; |
1319 |
} |
1320 |
} |
1321 |
} |
1322 |
|
1323 |
/* |
1324 |
* We don't post an exception in this case, since doing so runs into |
1325 |
* complications of pre-allocating an exception object which required |
1326 |
* running the Exception class initializer early etc. |
1327 |
* Instead we just invoke the errorReporter with an "Out Of Memory" |
1328 |
* type message, and then hope the process ends swiftly. |
1329 |
*/ |
1330 |
void |
1331 |
js_ReportOutOfMemory(JSContext *cx) |
1332 |
{ |
1333 |
#ifdef JS_TRACER |
1334 |
/* |
1335 |
* If we are in a builtin called directly from trace, don't report an |
1336 |
* error. We will retry in the interpreter instead. |
1337 |
*/ |
1338 |
if (JS_ON_TRACE(cx) && !cx->bailExit) |
1339 |
return; |
1340 |
#endif |
1341 |
|
1342 |
JSErrorReport report; |
1343 |
JSErrorReporter onError = cx->errorReporter; |
1344 |
|
1345 |
/* Get the message for this error, but we won't expand any arguments. */ |
1346 |
const JSErrorFormatString *efs = |
1347 |
js_GetLocalizedErrorMessage(cx, NULL, NULL, JSMSG_OUT_OF_MEMORY); |
1348 |
const char *msg = efs ? efs->format : "Out of memory"; |
1349 |
|
1350 |
/* Fill out the report, but don't do anything that requires allocation. */ |
1351 |
memset(&report, 0, sizeof (struct JSErrorReport)); |
1352 |
report.flags = JSREPORT_ERROR; |
1353 |
report.errorNumber = JSMSG_OUT_OF_MEMORY; |
1354 |
PopulateReportBlame(cx, &report); |
1355 |
|
1356 |
/* |
1357 |
* If debugErrorHook is present then we give it a chance to veto sending |
1358 |
* the error on to the regular ErrorReporter. We also clear a pending |
1359 |
* exception if any now so the hooks can replace the out-of-memory error |
1360 |
* by a script-catchable exception. |
1361 |
*/ |
1362 |
cx->throwing = JS_FALSE; |
1363 |
if (onError) { |
1364 |
JSDebugErrorHook hook = cx->debugHooks->debugErrorHook; |
1365 |
if (hook && |
1366 |
!hook(cx, msg, &report, cx->debugHooks->debugErrorHookData)) { |
1367 |
onError = NULL; |
1368 |
} |
1369 |
} |
1370 |
|
1371 |
if (onError) |
1372 |
onError(cx, msg, &report); |
1373 |
} |
1374 |
|
1375 |
void |
1376 |
js_ReportOutOfScriptQuota(JSContext *cx) |
1377 |
{ |
1378 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, |
1379 |
JSMSG_SCRIPT_STACK_QUOTA); |
1380 |
} |
1381 |
|
1382 |
void |
1383 |
js_ReportOverRecursed(JSContext *cx) |
1384 |
{ |
1385 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_OVER_RECURSED); |
1386 |
} |
1387 |
|
1388 |
void |
1389 |
js_ReportAllocationOverflow(JSContext *cx) |
1390 |
{ |
1391 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_ALLOC_OVERFLOW); |
1392 |
} |
1393 |
|
1394 |
JSBool |
1395 |
js_ReportErrorVA(JSContext *cx, uintN flags, const char *format, va_list ap) |
1396 |
{ |
1397 |
char *message; |
1398 |
jschar *ucmessage; |
1399 |
size_t messagelen; |
1400 |
JSErrorReport report; |
1401 |
JSBool warning; |
1402 |
|
1403 |
if ((flags & JSREPORT_STRICT) && !JS_HAS_STRICT_OPTION(cx)) |
1404 |
return JS_TRUE; |
1405 |
|
1406 |
message = JS_vsmprintf(format, ap); |
1407 |
if (!message) |
1408 |
return JS_FALSE; |
1409 |
messagelen = strlen(message); |
1410 |
|
1411 |
memset(&report, 0, sizeof (struct JSErrorReport)); |
1412 |
report.flags = flags; |
1413 |
report.errorNumber = JSMSG_USER_DEFINED_ERROR; |
1414 |
report.ucmessage = ucmessage = js_InflateString(cx, message, &messagelen); |
1415 |
PopulateReportBlame(cx, &report); |
1416 |
|
1417 |
warning = JSREPORT_IS_WARNING(report.flags); |
1418 |
if (warning && JS_HAS_WERROR_OPTION(cx)) { |
1419 |
report.flags &= ~JSREPORT_WARNING; |
1420 |
warning = JS_FALSE; |
1421 |
} |
1422 |
|
1423 |
ReportError(cx, message, &report); |
1424 |
js_free(message); |
1425 |
cx->free(ucmessage); |
1426 |
return warning; |
1427 |
} |
1428 |
|
1429 |
/* |
1430 |
* The arguments from ap need to be packaged up into an array and stored |
1431 |
* into the report struct. |
1432 |
* |
1433 |
* The format string addressed by the error number may contain operands |
1434 |
* identified by the format {N}, where N is a decimal digit. Each of these |
1435 |
* is to be replaced by the Nth argument from the va_list. The complete |
1436 |
* message is placed into reportp->ucmessage converted to a JSString. |
1437 |
* |
1438 |
* Returns true if the expansion succeeds (can fail if out of memory). |
1439 |
*/ |
1440 |
JSBool |
1441 |
js_ExpandErrorArguments(JSContext *cx, JSErrorCallback callback, |
1442 |
void *userRef, const uintN errorNumber, |
1443 |
char **messagep, JSErrorReport *reportp, |
1444 |
JSBool *warningp, JSBool charArgs, va_list ap) |
1445 |
{ |
1446 |
const JSErrorFormatString *efs; |
1447 |
int i; |
1448 |
int argCount; |
1449 |
|
1450 |
*warningp = JSREPORT_IS_WARNING(reportp->flags); |
1451 |
if (*warningp && JS_HAS_WERROR_OPTION(cx)) { |
1452 |
reportp->flags &= ~JSREPORT_WARNING; |
1453 |
*warningp = JS_FALSE; |
1454 |
} |
1455 |
|
1456 |
*messagep = NULL; |
1457 |
|
1458 |
/* Most calls supply js_GetErrorMessage; if this is so, assume NULL. */ |
1459 |
if (!callback || callback == js_GetErrorMessage) |
1460 |
efs = js_GetLocalizedErrorMessage(cx, userRef, NULL, errorNumber); |
1461 |
else |
1462 |
efs = callback(userRef, NULL, errorNumber); |
1463 |
if (efs) { |
1464 |
size_t totalArgsLength = 0; |
1465 |
size_t argLengths[10]; /* only {0} thru {9} supported */ |
1466 |
argCount = efs->argCount; |
1467 |
JS_ASSERT(argCount <= 10); |
1468 |
if (argCount > 0) { |
1469 |
/* |
1470 |
* Gather the arguments into an array, and accumulate |
1471 |
* their sizes. We allocate 1 more than necessary and |
1472 |
* null it out to act as the caboose when we free the |
1473 |
* pointers later. |
1474 |
*/ |
1475 |
reportp->messageArgs = (const jschar **) |
1476 |
cx->malloc(sizeof(jschar *) * (argCount + 1)); |
1477 |
if (!reportp->messageArgs) |
1478 |
return JS_FALSE; |
1479 |
reportp->messageArgs[argCount] = NULL; |
1480 |
for (i = 0; i < argCount; i++) { |
1481 |
if (charArgs) { |
1482 |
char *charArg = va_arg(ap, char *); |
1483 |
size_t charArgLength = strlen(charArg); |
1484 |
reportp->messageArgs[i] |
1485 |
= js_InflateString(cx, charArg, &charArgLength); |
1486 |
if (!reportp->messageArgs[i]) |
1487 |
goto error; |
1488 |
} else { |
1489 |
reportp->messageArgs[i] = va_arg(ap, jschar *); |
1490 |
} |
1491 |
argLengths[i] = js_strlen(reportp->messageArgs[i]); |
1492 |
totalArgsLength += argLengths[i]; |
1493 |
} |
1494 |
/* NULL-terminate for easy copying. */ |
1495 |
reportp->messageArgs[i] = NULL; |
1496 |
} |
1497 |
/* |
1498 |
* Parse the error format, substituting the argument X |
1499 |
* for {X} in the format. |
1500 |
*/ |
1501 |
if (argCount > 0) { |
1502 |
if (efs->format) { |
1503 |
jschar *buffer, *fmt, *out; |
1504 |
int expandedArgs = 0; |
1505 |
size_t expandedLength; |
1506 |
size_t len = strlen(efs->format); |
1507 |
|
1508 |
buffer = fmt = js_InflateString (cx, efs->format, &len); |
1509 |
if (!buffer) |
1510 |
goto error; |
1511 |
expandedLength = len |
1512 |
- (3 * argCount) /* exclude the {n} */ |
1513 |
+ totalArgsLength; |
1514 |
|
1515 |
/* |
1516 |
* Note - the above calculation assumes that each argument |
1517 |
* is used once and only once in the expansion !!! |
1518 |
*/ |
1519 |
reportp->ucmessage = out = (jschar *) |
1520 |
cx->malloc((expandedLength + 1) * sizeof(jschar)); |
1521 |
if (!out) { |
1522 |
cx->free(buffer); |
1523 |
goto error; |
1524 |
} |
1525 |
while (*fmt) { |
1526 |
if (*fmt == '{') { |
1527 |
if (isdigit(fmt[1])) { |
1528 |
int d = JS7_UNDEC(fmt[1]); |
1529 |
JS_ASSERT(d < argCount); |
1530 |
js_strncpy(out, reportp->messageArgs[d], |
1531 |
argLengths[d]); |
1532 |
out += argLengths[d]; |
1533 |
fmt += 3; |
1534 |
expandedArgs++; |
1535 |
continue; |
1536 |
} |
1537 |
} |
1538 |
*out++ = *fmt++; |
1539 |
} |
1540 |
JS_ASSERT(expandedArgs == argCount); |
1541 |
*out = 0; |
1542 |
cx->free(buffer); |
1543 |
*messagep = |
1544 |
js_DeflateString(cx, reportp->ucmessage, |
1545 |
(size_t)(out - reportp->ucmessage)); |
1546 |
if (!*messagep) |
1547 |
goto error; |
1548 |
} |
1549 |
} else { |
1550 |
/* |
1551 |
* Zero arguments: the format string (if it exists) is the |
1552 |
* entire message. |
1553 |
*/ |
1554 |
if (efs->format) { |
1555 |
size_t len; |
1556 |
*messagep = JS_strdup(cx, efs->format); |
1557 |
if (!*messagep) |
1558 |
goto error; |
1559 |
len = strlen(*messagep); |
1560 |
reportp->ucmessage = js_InflateString(cx, *messagep, &len); |
1561 |
if (!reportp->ucmessage) |
1562 |
goto error; |
1563 |
} |
1564 |
} |
1565 |
} |
1566 |
if (*messagep == NULL) { |
1567 |
/* where's the right place for this ??? */ |
1568 |
const char *defaultErrorMessage |
1569 |
= "No error message available for error number %d"; |
1570 |
size_t nbytes = strlen(defaultErrorMessage) + 16; |
1571 |
*messagep = (char *)cx->malloc(nbytes); |
1572 |
if (!*messagep) |
1573 |
goto error; |
1574 |
JS_snprintf(*messagep, nbytes, defaultErrorMessage, errorNumber); |
1575 |
} |
1576 |
return JS_TRUE; |
1577 |
|
1578 |
error: |
1579 |
if (reportp->messageArgs) { |
1580 |
/* free the arguments only if we allocated them */ |
1581 |
if (charArgs) { |
1582 |
i = 0; |
1583 |
while (reportp->messageArgs[i]) |
1584 |
cx->free((void *)reportp->messageArgs[i++]); |
1585 |
} |
1586 |
cx->free((void *)reportp->messageArgs); |
1587 |
reportp->messageArgs = NULL; |
1588 |
} |
1589 |
if (reportp->ucmessage) { |
1590 |
cx->free((void *)reportp->ucmessage); |
1591 |
reportp->ucmessage = NULL; |
1592 |
} |
1593 |
if (*messagep) { |
1594 |
cx->free((void *)*messagep); |
1595 |
*messagep = NULL; |
1596 |
} |
1597 |
return JS_FALSE; |
1598 |
} |
1599 |
|
1600 |
JSBool |
1601 |
js_ReportErrorNumberVA(JSContext *cx, uintN flags, JSErrorCallback callback, |
1602 |
void *userRef, const uintN errorNumber, |
1603 |
JSBool charArgs, va_list ap) |
1604 |
{ |
1605 |
JSErrorReport report; |
1606 |
char *message; |
1607 |
JSBool warning; |
1608 |
|
1609 |
if ((flags & JSREPORT_STRICT) && !JS_HAS_STRICT_OPTION(cx)) |
1610 |
return JS_TRUE; |
1611 |
|
1612 |
memset(&report, 0, sizeof (struct JSErrorReport)); |
1613 |
report.flags = flags; |
1614 |
report.errorNumber = errorNumber; |
1615 |
PopulateReportBlame(cx, &report); |
1616 |
|
1617 |
if (!js_ExpandErrorArguments(cx, callback, userRef, errorNumber, |
1618 |
&message, &report, &warning, charArgs, ap)) { |
1619 |
return JS_FALSE; |
1620 |
} |
1621 |
|
1622 |
ReportError(cx, message, &report); |
1623 |
|
1624 |
if (message) |
1625 |
cx->free(message); |
1626 |
if (report.messageArgs) { |
1627 |
/* |
1628 |
* js_ExpandErrorArguments owns its messageArgs only if it had to |
1629 |
* inflate the arguments (from regular |char *|s). |
1630 |
*/ |
1631 |
if (charArgs) { |
1632 |
int i = 0; |
1633 |
while (report.messageArgs[i]) |
1634 |
cx->free((void *)report.messageArgs[i++]); |
1635 |
} |
1636 |
cx->free((void *)report.messageArgs); |
1637 |
} |
1638 |
if (report.ucmessage) |
1639 |
cx->free((void *)report.ucmessage); |
1640 |
|
1641 |
return warning; |
1642 |
} |
1643 |
|
1644 |
JS_FRIEND_API(void) |
1645 |
js_ReportErrorAgain(JSContext *cx, const char *message, JSErrorReport *reportp) |
1646 |
{ |
1647 |
JSErrorReporter onError; |
1648 |
|
1649 |
if (!message) |
1650 |
return; |
1651 |
|
1652 |
if (cx->lastMessage) |
1653 |
js_free(cx->lastMessage); |
1654 |
cx->lastMessage = JS_strdup(cx, message); |
1655 |
if (!cx->lastMessage) |
1656 |
return; |
1657 |
onError = cx->errorReporter; |
1658 |
|
1659 |
/* |
1660 |
* If debugErrorHook is present then we give it a chance to veto |
1661 |
* sending the error on to the regular ErrorReporter. |
1662 |
*/ |
1663 |
if (onError) { |
1664 |
JSDebugErrorHook hook = cx->debugHooks->debugErrorHook; |
1665 |
if (hook && |
1666 |
!hook(cx, cx->lastMessage, reportp, |
1667 |
cx->debugHooks->debugErrorHookData)) { |
1668 |
onError = NULL; |
1669 |
} |
1670 |
} |
1671 |
if (onError) |
1672 |
onError(cx, cx->lastMessage, reportp); |
1673 |
} |
1674 |
|
1675 |
void |
1676 |
js_ReportIsNotDefined(JSContext *cx, const char *name) |
1677 |
{ |
1678 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_DEFINED, name); |
1679 |
} |
1680 |
|
1681 |
JSBool |
1682 |
js_ReportIsNullOrUndefined(JSContext *cx, intN spindex, jsval v, |
1683 |
JSString *fallback) |
1684 |
{ |
1685 |
char *bytes; |
1686 |
JSBool ok; |
1687 |
|
1688 |
bytes = js_DecompileValueGenerator(cx, spindex, v, fallback); |
1689 |
if (!bytes) |
1690 |
return JS_FALSE; |
1691 |
|
1692 |
if (strcmp(bytes, js_undefined_str) == 0 || |
1693 |
strcmp(bytes, js_null_str) == 0) { |
1694 |
ok = JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, |
1695 |
js_GetErrorMessage, NULL, |
1696 |
JSMSG_NO_PROPERTIES, bytes, |
1697 |
NULL, NULL); |
1698 |
} else if (JSVAL_IS_VOID(v)) { |
1699 |
ok = JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, |
1700 |
js_GetErrorMessage, NULL, |
1701 |
JSMSG_NULL_OR_UNDEFINED, bytes, |
1702 |
js_undefined_str, NULL); |
1703 |
} else { |
1704 |
JS_ASSERT(JSVAL_IS_NULL(v)); |
1705 |
ok = JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, |
1706 |
js_GetErrorMessage, NULL, |
1707 |
JSMSG_NULL_OR_UNDEFINED, bytes, |
1708 |
js_null_str, NULL); |
1709 |
} |
1710 |
|
1711 |
cx->free(bytes); |
1712 |
return ok; |
1713 |
} |
1714 |
|
1715 |
void |
1716 |
js_ReportMissingArg(JSContext *cx, jsval *vp, uintN arg) |
1717 |
{ |
1718 |
char argbuf[11]; |
1719 |
char *bytes; |
1720 |
JSAtom *atom; |
1721 |
|
1722 |
JS_snprintf(argbuf, sizeof argbuf, "%u", arg); |
1723 |
bytes = NULL; |
1724 |
if (VALUE_IS_FUNCTION(cx, *vp)) { |
1725 |
atom = GET_FUNCTION_PRIVATE(cx, JSVAL_TO_OBJECT(*vp))->atom; |
1726 |
bytes = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, *vp, |
1727 |
ATOM_TO_STRING(atom)); |
1728 |
if (!bytes) |
1729 |
return; |
1730 |
} |
1731 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, |
1732 |
JSMSG_MISSING_FUN_ARG, argbuf, |
1733 |
bytes ? bytes : ""); |
1734 |
cx->free(bytes); |
1735 |
} |
1736 |
|
1737 |
JSBool |
1738 |
js_ReportValueErrorFlags(JSContext *cx, uintN flags, const uintN errorNumber, |
1739 |
intN spindex, jsval v, JSString *fallback, |
1740 |
const char *arg1, const char *arg2) |
1741 |
{ |
1742 |
char *bytes; |
1743 |
JSBool ok; |
1744 |
|
1745 |
JS_ASSERT(js_ErrorFormatString[errorNumber].argCount >= 1); |
1746 |
JS_ASSERT(js_ErrorFormatString[errorNumber].argCount <= 3); |
1747 |
bytes = js_DecompileValueGenerator(cx, spindex, v, fallback); |
1748 |
if (!bytes) |
1749 |
return JS_FALSE; |
1750 |
|
1751 |
ok = JS_ReportErrorFlagsAndNumber(cx, flags, js_GetErrorMessage, |
1752 |
NULL, errorNumber, bytes, arg1, arg2); |
1753 |
cx->free(bytes); |
1754 |
return ok; |
1755 |
} |
1756 |
|
1757 |
#if defined DEBUG && defined XP_UNIX |
1758 |
/* For gdb usage. */ |
1759 |
void js_traceon(JSContext *cx) { cx->tracefp = stderr; cx->tracePrevPc = NULL; } |
1760 |
void js_traceoff(JSContext *cx) { cx->tracefp = NULL; } |
1761 |
#endif |
1762 |
|
1763 |
JSErrorFormatString js_ErrorFormatString[JSErr_Limit] = { |
1764 |
#define MSG_DEF(name, number, count, exception, format) \ |
1765 |
{ format, count, exception } , |
1766 |
#include "js.msg" |
1767 |
#undef MSG_DEF |
1768 |
}; |
1769 |
|
1770 |
JS_FRIEND_API(const JSErrorFormatString *) |
1771 |
js_GetErrorMessage(void *userRef, const char *locale, const uintN errorNumber) |
1772 |
{ |
1773 |
if ((errorNumber > 0) && (errorNumber < JSErr_Limit)) |
1774 |
return &js_ErrorFormatString[errorNumber]; |
1775 |
return NULL; |
1776 |
} |
1777 |
|
1778 |
JSBool |
1779 |
js_InvokeOperationCallback(JSContext *cx) |
1780 |
{ |
1781 |
JS_ASSERT(cx->operationCallbackFlag); |
1782 |
|
1783 |
/* |
1784 |
* Reset the callback flag first, then yield. If another thread is racing |
1785 |
* us here we will accumulate another callback request which will be |
1786 |
* serviced at the next opportunity. |
1787 |
*/ |
1788 |
cx->operationCallbackFlag = 0; |
1789 |
|
1790 |
/* |
1791 |
* Unless we are going to run the GC, we automatically yield the current |
1792 |
* context every time the operation callback is hit since we might be |
1793 |
* called as a result of an impending GC, which would deadlock if we do |
1794 |
* not yield. Operation callbacks are supposed to happen rarely (seconds, |
1795 |
* not milliseconds) so it is acceptable to yield at every callback. |
1796 |
*/ |
1797 |
if (cx->runtime->gcIsNeeded) |
1798 |
js_GC(cx, GC_NORMAL); |
1799 |
#ifdef JS_THREADSAFE |
1800 |
else |
1801 |
JS_YieldRequest(cx); |
1802 |
#endif |
1803 |
|
1804 |
JSOperationCallback cb = cx->operationCallback; |
1805 |
|
1806 |
/* |
1807 |
* Important: Additional callbacks can occur inside the callback handler |
1808 |
* if it re-enters the JS engine. The embedding must ensure that the |
1809 |
* callback is disconnected before attempting such re-entry. |
1810 |
*/ |
1811 |
|
1812 |
return !cb || cb(cx); |
1813 |
} |
1814 |
|
1815 |
void |
1816 |
js_TriggerAllOperationCallbacks(JSRuntime *rt, JSBool gcLocked) |
1817 |
{ |
1818 |
JSContext *acx, *iter; |
1819 |
#ifdef JS_THREADSAFE |
1820 |
if (!gcLocked) |
1821 |
JS_LOCK_GC(rt); |
1822 |
#endif |
1823 |
iter = NULL; |
1824 |
while ((acx = js_ContextIterator(rt, JS_FALSE, &iter))) |
1825 |
JS_TriggerOperationCallback(acx); |
1826 |
#ifdef JS_THREADSAFE |
1827 |
if (!gcLocked) |
1828 |
JS_UNLOCK_GC(rt); |
1829 |
#endif |
1830 |
} |
1831 |
|
1832 |
JSStackFrame * |
1833 |
js_GetScriptedCaller(JSContext *cx, JSStackFrame *fp) |
1834 |
{ |
1835 |
if (!fp) |
1836 |
fp = js_GetTopStackFrame(cx); |
1837 |
while (fp) { |
1838 |
if (fp->script) |
1839 |
return fp; |
1840 |
fp = fp->down; |
1841 |
} |
1842 |
return NULL; |
1843 |
} |
1844 |
|
1845 |
jsbytecode* |
1846 |
js_GetCurrentBytecodePC(JSContext* cx) |
1847 |
{ |
1848 |
jsbytecode *pc, *imacpc; |
1849 |
|
1850 |
#ifdef JS_TRACER |
1851 |
if (JS_ON_TRACE(cx)) { |
1852 |
pc = cx->bailExit->pc; |
1853 |
imacpc = cx->bailExit->imacpc; |
1854 |
} else |
1855 |
#endif |
1856 |
{ |
1857 |
JS_ASSERT_NOT_ON_TRACE(cx); /* for static analysis */ |
1858 |
JSStackFrame* fp = cx->fp; |
1859 |
if (fp && fp->regs) { |
1860 |
pc = fp->regs->pc; |
1861 |
imacpc = fp->imacpc; |
1862 |
} else { |
1863 |
return NULL; |
1864 |
} |
1865 |
} |
1866 |
|
1867 |
/* |
1868 |
* If we are inside GetProperty_tn or similar, return a pointer to the |
1869 |
* current instruction in the script, not the CALL instruction in the |
1870 |
* imacro, for the benefit of callers doing bytecode inspection. |
1871 |
*/ |
1872 |
return (*pc == JSOP_CALL && imacpc) ? imacpc : pc; |
1873 |
} |
1874 |
|
1875 |
bool |
1876 |
js_CurrentPCIsInImacro(JSContext *cx) |
1877 |
{ |
1878 |
#ifdef JS_TRACER |
1879 |
VOUCH_DOES_NOT_REQUIRE_STACK(); |
1880 |
return (JS_ON_TRACE(cx) ? cx->bailExit->imacpc : cx->fp->imacpc) != NULL; |
1881 |
#else |
1882 |
return false; |
1883 |
#endif |
1884 |
} |