1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 *
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 *
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
10 *
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
15 *
16 * The Original Code is Mozilla Communicator client code, released
17 * March 31, 1998.
18 *
19 * The Initial Developer of the Original Code is
20 * Netscape Communications Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 1998
22 * the Initial Developer. All Rights Reserved.
23 *
24 * Contributor(s):
25 *
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
37 *
38 * ***** END LICENSE BLOCK ***** */
40 /*
41 * JS execution context.
42 */
43 #include "jsstddef.h"
44 #include <stdarg.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include "jstypes.h"
48 #include "jsarena.h" /* Added by JSIFY */
49 #include "jsutil.h" /* Added by JSIFY */
50 #include "jsclist.h"
51 #include "jsprf.h"
52 #include "jsatom.h"
53 #include "jscntxt.h"
54 #include "jsconfig.h"
55 #include "jsdbgapi.h"
56 #include "jsexn.h"
57 #include "jsgc.h"
58 #include "jslock.h"
59 #include "jsnum.h"
60 #include "jsobj.h"
61 #include "jsopcode.h"
62 #include "jsscan.h"
63 #include "jsscript.h"
64 #include "jsstr.h"
66 void
67 js_OnVersionChange(JSContext *cx)
68 {
69 #if !JS_BUG_FALLIBLE_EQOPS
70 if (JS_VERSION_IS_1_2(cx)) {
71 cx->jsop_eq = JSOP_NEW_EQ;
72 cx->jsop_ne = JSOP_NEW_NE;
73 } else {
74 cx->jsop_eq = JSOP_EQ;
75 cx->jsop_ne = JSOP_NE;
76 }
77 #endif /* !JS_BUG_FALLIBLE_EQOPS */
78 }
80 void
81 js_SetVersion(JSContext *cx, JSVersion version)
82 {
83 cx->version = version;
84 js_OnVersionChange(cx);
85 }
87 JSContext *
88 js_NewContext(JSRuntime *rt, size_t stackChunkSize)
89 {
90 JSContext *cx;
91 JSBool ok, first;
93 cx = (JSContext *) malloc(sizeof *cx);
94 if (!cx)
95 return NULL;
96 memset(cx, 0, sizeof *cx);
98 cx->runtime = rt;
99 #if JS_STACK_GROWTH_DIRECTION > 0
100 cx->stackLimit = (jsuword)-1;
101 #endif
102 #ifdef JS_THREADSAFE
103 js_InitContextForLocking(cx);
104 #endif
106 JS_LOCK_GC(rt);
107 for (;;) {
108 first = (rt->contextList.next == &rt->contextList);
109 if (rt->state == JSRTS_UP) {
110 JS_ASSERT(!first);
111 break;
112 }
113 if (rt->state == JSRTS_DOWN) {
114 JS_ASSERT(first);
115 rt->state = JSRTS_LAUNCHING;
116 break;
117 }
118 JS_WAIT_CONDVAR(rt->stateChange, JS_NO_TIMEOUT);
119 }
120 JS_APPEND_LINK(&cx->links, &rt->contextList);
121 JS_UNLOCK_GC(rt);
123 /*
124 * First we do the infallible, every-time per-context initializations.
125 * Should a later, fallible initialization (js_InitRegExpStatics, e.g.,
126 * or the stuff under 'if (first)' below) fail, at least the version
127 * and arena-pools will be valid and safe to use (say, from the last GC
128 * done by js_DestroyContext).
129 */
130 cx->version = JSVERSION_DEFAULT;
131 cx->jsop_eq = JSOP_EQ;
132 cx->jsop_ne = JSOP_NE;
133 JS_InitArenaPool(&cx->stackPool, "stack", stackChunkSize, sizeof(jsval));
134 JS_InitArenaPool(&cx->tempPool, "temp", 1024, sizeof(jsdouble));
136 #if JS_HAS_REGEXPS
137 if (!js_InitRegExpStatics(cx, &cx->regExpStatics)) {
138 js_DestroyContext(cx, JS_NO_GC);
139 return NULL;
140 }
141 #endif
142 #if JS_HAS_EXCEPTIONS
143 cx->throwing = JS_FALSE;
144 #endif
146 /*
147 * If cx is the first context on this runtime, initialize well-known atoms,
148 * keywords, numbers, and strings. If one of these steps should fail, the
149 * runtime will be left in a partially initialized state, with zeroes and
150 * nulls stored in the default-initialized remainder of the struct. We'll
151 * clean the runtime up under js_DestroyContext, because cx will be "last"
152 * as well as "first".
153 */
154 if (first) {
155 /*
156 * Both atomState and the scriptFilenameTable may be left over from a
157 * previous episode of non-zero contexts alive in rt, so don't re-init
158 * either table if it's not necessary. Just repopulate atomState with
159 * well-known internal atoms, and with the reserved identifiers added
160 * by the scanner.
161 */
162 ok = (rt->atomState.liveAtoms == 0)
163 ? js_InitAtomState(cx, &rt->atomState)
164 : js_InitPinnedAtoms(cx, &rt->atomState);
165 if (ok)
166 ok = js_InitScanner(cx);
167 if (ok && !rt->scriptFilenameTable)
168 ok = js_InitRuntimeScriptState(rt);
169 if (ok)
170 ok = js_InitRuntimeNumberState(cx);
171 if (ok)
172 ok = js_InitRuntimeStringState(cx);
173 if (!ok) {
174 js_DestroyContext(cx, JS_NO_GC);
175 return NULL;
176 }
178 JS_LOCK_GC(rt);
179 rt->state = JSRTS_UP;
180 JS_NOTIFY_ALL_CONDVAR(rt->stateChange);
181 JS_UNLOCK_GC(rt);
182 }
184 return cx;
185 }
187 void
188 js_DestroyContext(JSContext *cx, JSGCMode gcmode)
189 {
190 JSRuntime *rt;
191 JSBool last;
192 JSArgumentFormatMap *map;
193 JSLocalRootStack *lrs;
194 JSLocalRootChunk *lrc;
196 rt = cx->runtime;
198 /* Remove cx from context list first. */
199 JS_LOCK_GC(rt);
200 JS_ASSERT(rt->state == JSRTS_UP || rt->state == JSRTS_LAUNCHING);
201 JS_REMOVE_LINK(&cx->links);
202 last = (rt->contextList.next == &rt->contextList);
203 if (last)
204 rt->state = JSRTS_LANDING;
205 JS_UNLOCK_GC(rt);
207 if (last) {
208 #ifdef JS_THREADSAFE
209 /*
210 * If cx is not in a request already, begin one now so that we wait
211 * for any racing GC started on a not-last context to finish, before
212 * we plow ahead and unpin atoms. Note that even though we begin a
213 * request here if necessary, we end all requests on cx below before
214 * forcing a final GC. This lets any not-last context destruction
215 * racing in another thread try to force or maybe run the GC, but by
216 * that point, rt->state will not be JSRTS_UP, and that GC attempt
217 * will return early.
218 */
219 if (cx->requestDepth == 0)
220 JS_BeginRequest(cx);
221 #endif
223 /* Unpin all pinned atoms before final GC. */
224 js_UnpinPinnedAtoms(&rt->atomState);
226 /* Unlock and clear GC things held by runtime pointers. */
227 js_FinishRuntimeNumberState(cx);
228 js_FinishRuntimeStringState(cx);
230 /* Clear debugging state to remove GC roots. */
231 JS_ClearAllTraps(cx);
232 JS_ClearAllWatchPoints(cx);
233 }
235 #if JS_HAS_REGEXPS
236 /*
237 * Remove more GC roots in regExpStatics, then collect garbage.
238 * XXX anti-modularity alert: we rely on the call to js_RemoveRoot within
239 * XXX this function call to wait for any racing GC to complete, in the
240 * XXX case where JS_DestroyContext is called outside of a request on cx
241 */
242 js_FreeRegExpStatics(cx, &cx->regExpStatics);
243 #endif
245 #ifdef JS_THREADSAFE
246 /*
247 * Destroying a context implicitly calls JS_EndRequest(). Also, we must
248 * end our request here in case we are "last" -- in that event, another
249 * js_DestroyContext that was not last might be waiting in the GC for our
250 * request to end. We'll let it run below, just before we do the truly
251 * final GC and then free atom state.
252 *
253 * At this point, cx must be inaccessible to other threads. It's off the
254 * rt->contextList, and it should not be reachable via any object private
255 * data structure.
256 */
257 while (cx->requestDepth != 0)
258 JS_EndRequest(cx);
259 #endif
261 if (last) {
262 /* Always force, so we wait for any racing GC to finish. */
263 js_ForceGC(cx, GC_LAST_CONTEXT);
265 /* Iterate until no finalizer removes a GC root or lock. */
266 while (rt->gcPoke)
267 js_GC(cx, GC_LAST_CONTEXT);
269 /* Try to free atom state, now that no unrooted scripts survive. */
270 if (rt->atomState.liveAtoms == 0)
271 js_FreeAtomState(cx, &rt->atomState);
273 /* Also free the script filename table if it exists and is empty. */
274 if (rt->scriptFilenameTable && rt->scriptFilenameTable->nentries == 0)
275 js_FinishRuntimeScriptState(rt);
277 /* Take the runtime down, now that it has no contexts or atoms. */
278 JS_LOCK_GC(rt);
279 rt->state = JSRTS_DOWN;
280 JS_NOTIFY_ALL_CONDVAR(rt->stateChange);
281 JS_UNLOCK_GC(rt);
282 } else {
283 if (gcmode == JS_FORCE_GC)
284 js_ForceGC(cx, 0);
285 else if (gcmode == JS_MAYBE_GC)
286 JS_MaybeGC(cx);
287 }
289 /* Free the stuff hanging off of cx. */
290 JS_FinishArenaPool(&cx->stackPool);
291 JS_FinishArenaPool(&cx->tempPool);
292 if (cx->lastMessage)
293 free(cx->lastMessage);
295 /* Remove any argument formatters. */
296 map = cx->argumentFormatMap;
297 while (map) {
298 JSArgumentFormatMap *temp = map;
299 map = map->next;
300 JS_free(cx, temp);
301 }
303 /* Destroy the resolve recursion damper. */
304 if (cx->resolvingTable) {
305 JS_DHashTableDestroy(cx->resolvingTable);
306 cx->resolvingTable = NULL;
307 }
309 lrs = cx->localRootStack;
310 if (lrs) {
311 while ((lrc = lrs->topChunk) != &lrs->firstChunk) {
312 lrs->topChunk = lrc->down;
313 JS_free(cx, lrc);
314 }
315 JS_free(cx, lrs);
316 }
318 /* Finally, free cx itself. */
319 free(cx);
320 }
322 JSBool
323 js_ValidContextPointer(JSRuntime *rt, JSContext *cx)
324 {
325 JSCList *cl;
327 for (cl = rt->contextList.next; cl != &rt->contextList; cl = cl->next) {
328 if (cl == &cx->links)
329 return JS_TRUE;
330 }
331 JS_RUNTIME_METER(rt, deadContexts);
332 return JS_FALSE;
333 }
335 JSContext *
336 js_ContextIterator(JSRuntime *rt, JSBool unlocked, JSContext **iterp)
337 {
338 JSContext *cx = *iterp;
340 if (unlocked)
341 JS_LOCK_GC(rt);
342 if (!cx)
343 cx = (JSContext *)&rt->contextList;
344 cx = (JSContext *)cx->links.next;
345 if (&cx->links == &rt->contextList)
346 cx = NULL;
347 *iterp = cx;
348 if (unlocked)
349 JS_UNLOCK_GC(rt);
350 return cx;
351 }
353 JS_STATIC_DLL_CALLBACK(const void *)
354 resolving_GetKey(JSDHashTable *table, JSDHashEntryHdr *hdr)
355 {
356 JSResolvingEntry *entry = (JSResolvingEntry *)hdr;
358 return &entry->key;
359 }
361 JS_STATIC_DLL_CALLBACK(JSDHashNumber)
362 resolving_HashKey(JSDHashTable *table, const void *ptr)
363 {
364 const JSResolvingKey *key = (const JSResolvingKey *)ptr;
366 return ((JSDHashNumber)JS_PTR_TO_UINT32(key->obj) >> JSVAL_TAGBITS) ^ key->id;
367 }
369 JS_PUBLIC_API(JSBool)
370 resolving_MatchEntry(JSDHashTable *table,
371 const JSDHashEntryHdr *hdr,
372 const void *ptr)
373 {
374 const JSResolvingEntry *entry = (const JSResolvingEntry *)hdr;
375 const JSResolvingKey *key = (const JSResolvingKey *)ptr;
377 return entry->key.obj == key->obj && entry->key.id == key->id;
378 }
380 static const JSDHashTableOps resolving_dhash_ops = {
381 JS_DHashAllocTable,
382 JS_DHashFreeTable,
383 resolving_GetKey,
384 resolving_HashKey,
385 resolving_MatchEntry,
386 JS_DHashMoveEntryStub,
387 JS_DHashClearEntryStub,
388 JS_DHashFinalizeStub,
389 NULL
390 };
392 JSBool
393 js_StartResolving(JSContext *cx, JSResolvingKey *key, uint32 flag,
394 JSResolvingEntry **entryp)
395 {
396 JSDHashTable *table;
397 JSResolvingEntry *entry;
399 table = cx->resolvingTable;
400 if (!table) {
401 table = JS_NewDHashTable(&resolving_dhash_ops, NULL,
402 sizeof(JSResolvingEntry),
403 JS_DHASH_MIN_SIZE);
404 if (!table)
405 goto outofmem;
406 cx->resolvingTable = table;
407 }
409 entry = (JSResolvingEntry *)
410 JS_DHashTableOperate(table, key, JS_DHASH_ADD);
411 if (!entry)
412 goto outofmem;
414 if (entry->flags & flag) {
415 /* An entry for (key, flag) exists already -- dampen recursion. */
416 entry = NULL;
417 } else {
418 /* Fill in key if we were the first to add entry, then set flag. */
419 if (!entry->key.obj)
420 entry->key = *key;
421 entry->flags |= flag;
422 }
423 *entryp = entry;
424 return JS_TRUE;
426 outofmem:
427 JS_ReportOutOfMemory(cx);
428 return JS_FALSE;
429 }
431 void
432 js_StopResolving(JSContext *cx, JSResolvingKey *key, uint32 flag,
433 JSResolvingEntry *entry, uint32 generation)
434 {
435 JSDHashTable *table;
437 /*
438 * Clear flag from entry->flags and return early if other flags remain.
439 * We must take care to re-lookup entry if the table has changed since
440 * it was found by js_StartResolving.
441 */
442 table = cx->resolvingTable;
443 if (!entry || table->generation != generation) {
444 entry = (JSResolvingEntry *)
445 JS_DHashTableOperate(table, key, JS_DHASH_LOOKUP);
446 }
447 JS_ASSERT(JS_DHASH_ENTRY_IS_BUSY(&entry->hdr));
448 entry->flags &= ~flag;
449 if (entry->flags)
450 return;
452 /*
453 * Do a raw remove only if fewer entries were removed than would cause
454 * alpha to be less than .5 (alpha is at most .75). Otherwise, we just
455 * call JS_DHashTableOperate to re-lookup the key and remove its entry,
456 * compressing or shrinking the table as needed.
457 */
458 if (table->removedCount < JS_DHASH_TABLE_SIZE(table) >> 2)
459 JS_DHashTableRawRemove(table, &entry->hdr);
460 else
461 JS_DHashTableOperate(table, key, JS_DHASH_REMOVE);
462 }
464 JSBool
465 js_EnterLocalRootScope(JSContext *cx)
466 {
467 JSLocalRootStack *lrs;
468 int mark;
470 lrs = cx->localRootStack;
471 if (!lrs) {
472 lrs = (JSLocalRootStack *) JS_malloc(cx, sizeof *lrs);
473 if (!lrs)
474 return JS_FALSE;
475 lrs->scopeMark = JSLRS_NULL_MARK;
476 lrs->rootCount = 0;
477 lrs->topChunk = &lrs->firstChunk;
478 lrs->firstChunk.down = NULL;
479 cx->localRootStack = lrs;
480 }
482 /* Push lrs->scopeMark to save it for restore when leaving. */
483 mark = js_PushLocalRoot(cx, lrs, INT_TO_JSVAL(lrs->scopeMark));
484 if (mark < 0)
485 return JS_FALSE;
486 lrs->scopeMark = (uint32) mark;
487 return JS_TRUE;
488 }
490 void
491 js_LeaveLocalRootScope(JSContext *cx)
492 {
493 JSLocalRootStack *lrs;
494 unsigned mark, m, n;
495 JSLocalRootChunk *lrc;
497 /* Defend against buggy native callers. */
498 lrs = cx->localRootStack;
499 JS_ASSERT(lrs && lrs->rootCount != 0);
500 if (!lrs || lrs->rootCount == 0)
501 return;
503 mark = lrs->scopeMark;
504 JS_ASSERT(mark != JSLRS_NULL_MARK);
505 if (mark == JSLRS_NULL_MARK)
506 return;
508 /* Free any chunks being popped by this leave operation. */
509 m = mark >> JSLRS_CHUNK_SHIFT;
510 n = (lrs->rootCount - 1) >> JSLRS_CHUNK_SHIFT;
511 while (n > m) {
512 lrc = lrs->topChunk;
513 JS_ASSERT(lrc != &lrs->firstChunk);
514 lrs->topChunk = lrc->down;
515 JS_free(cx, lrc);
516 --n;
517 }
519 /* Pop the scope, restoring lrs->scopeMark. */
520 lrc = lrs->topChunk;
521 m = mark & JSLRS_CHUNK_MASK;
522 lrs->scopeMark = (uint32) JSVAL_TO_INT(lrc->roots[m]);
523 lrc->roots[m] = JSVAL_NULL;
524 lrs->rootCount = (uint32) mark;
526 /*
527 * Free the stack eagerly, risking malloc churn. The alternative would
528 * require an lrs->entryCount member, maintained by Enter and Leave, and
529 * tested by the GC in addition to the cx->localRootStack non-null test.
530 *
531 * That approach would risk hoarding 264 bytes (net) per context. Right
532 * now it seems better to give fresh (dirty in CPU write-back cache, and
533 * the data is no longer needed) memory back to the malloc heap.
534 */
535 if (mark == 0) {
536 cx->localRootStack = NULL;
537 JS_free(cx, lrs);
538 } else if (m == 0) {
539 lrs->topChunk = lrc->down;
540 JS_free(cx, lrc);
541 }
542 }
544 void
545 js_ForgetLocalRoot(JSContext *cx, jsval v)
546 {
547 JSLocalRootStack *lrs;
548 unsigned i, j, m, n, mark;
549 JSLocalRootChunk *lrc, *lrc2;
550 jsval top;
552 lrs = cx->localRootStack;
553 JS_ASSERT(lrs && lrs->rootCount);
554 if (!lrs || lrs->rootCount == 0)
555 return;
557 /* Prepare to pop the top-most value from the stack. */
558 n = lrs->rootCount - 1;
559 m = n & JSLRS_CHUNK_MASK;
560 lrc = lrs->topChunk;
561 top = lrc->roots[m];
563 /* Be paranoid about calls on an empty scope. */
564 mark = lrs->scopeMark;
565 JS_ASSERT(mark < n);
566 if (mark >= n)
567 return;
569 /* If v was not the last root pushed in the top scope, find it. */
570 if (top != v) {
571 /* Search downward in case v was recently pushed. */
572 i = n;
573 j = m;
574 lrc2 = lrc;
575 while (--i > mark) {
576 if (j == 0)
577 lrc2 = lrc2->down;
578 j = i & JSLRS_CHUNK_MASK;
579 if (lrc2->roots[j] == v)
580 break;
581 }
583 /* If we didn't find v in this scope, assert and bail out. */
584 JS_ASSERT(i != mark);
585 if (i == mark)
586 return;
588 /* Swap top and v so common tail code can pop v. */
589 lrc2->roots[j] = top;
590 }
592 /* Pop the last value from the stack. */
593 lrc->roots[m] = JSVAL_NULL;
594 lrs->rootCount = n;
595 if (m == 0) {
596 JS_ASSERT(n != 0);
597 JS_ASSERT(lrc != &lrs->firstChunk);
598 lrs->topChunk = lrc->down;
599 JS_free(cx, lrc);
600 }
601 }
603 int
604 js_PushLocalRoot(JSContext *cx, JSLocalRootStack *lrs, jsval v)
605 {
606 unsigned n, m;
607 JSLocalRootChunk *lrc;
609 n = lrs->rootCount;
610 m = n & JSLRS_CHUNK_MASK;
611 if (n == 0 || m != 0) {
612 /*
613 * At start of first chunk, or not at start of a non-first top chunk.
614 * Check for lrs->rootCount overflow.
615 */
616 if ((uint32)(n + 1) == 0) {
617 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
618 JSMSG_TOO_MANY_LOCAL_ROOTS);
619 return -1;
620 }
621 lrc = lrs->topChunk;
622 JS_ASSERT(n != 0 || lrc == &lrs->firstChunk);
623 } else {
624 /*
625 * After lrs->firstChunk, trying to index at a power-of-two chunk
626 * boundary: need a new chunk.
627 */
628 lrc = (JSLocalRootChunk *) JS_malloc(cx, sizeof *lrc);
629 if (!lrc)
630 return -1;
631 lrc->down = lrs->topChunk;
632 lrs->topChunk = lrc;
633 }
634 lrs->rootCount = n + 1;
635 lrc->roots[m] = v;
636 return (int) n;
637 }
639 void
640 js_MarkLocalRoots(JSContext *cx, JSLocalRootStack *lrs)
641 {
642 unsigned n, m, mark;
643 JSLocalRootChunk *lrc;
645 n = lrs->rootCount;
646 if (n == 0)
647 return;
649 mark = lrs->scopeMark;
650 lrc = lrs->topChunk;
651 do {
652 while (--n > mark) {
653 #ifdef GC_MARK_DEBUG
654 char name[22];
655 JS_snprintf(name, sizeof name, "<local root %u>", n);
656 #else
657 const char *name = NULL;
658 #endif
659 m = n & JSLRS_CHUNK_MASK;
660 JS_ASSERT(JSVAL_IS_GCTHING(lrc->roots[m]));
661 JS_MarkGCThing(cx, JSVAL_TO_GCTHING(lrc->roots[m]), name, NULL);
662 if (m == 0)
663 lrc = lrc->down;
664 }
665 m = n & JSLRS_CHUNK_MASK;
666 mark = JSVAL_TO_INT(lrc->roots[m]);
667 if (m == 0)
668 lrc = lrc->down;
669 } while (n != 0);
670 JS_ASSERT(!lrc);
671 }
673 static void
674 ReportError(JSContext *cx, const char *message, JSErrorReport *reportp)
675 {
676 /*
677 * Check the error report, and set a JavaScript-catchable exception
678 * if the error is defined to have an associated exception. If an
679 * exception is thrown, then the JSREPORT_EXCEPTION flag will be set
680 * on the error report, and exception-aware hosts should ignore it.
681 */
682 if (reportp && reportp->errorNumber == JSMSG_UNCAUGHT_EXCEPTION)
683 reportp->flags |= JSREPORT_EXCEPTION;
685 #if JS_HAS_ERROR_EXCEPTIONS
686 /*
687 * Call the error reporter only if an exception wasn't raised.
688 *
689 * If an exception was raised, then we call the debugErrorHook
690 * (if present) to give it a chance to see the error before it
691 * propagates out of scope. This is needed for compatability
692 * with the old scheme.
693 */
694 if (!js_ErrorToException(cx, message, reportp)) {
695 js_ReportErrorAgain(cx, message, reportp);
696 } else if (cx->runtime->debugErrorHook && cx->errorReporter) {
697 JSDebugErrorHook hook = cx->runtime->debugErrorHook;
698 /* test local in case debugErrorHook changed on another thread */
699 if (hook)
700 hook(cx, message, reportp, cx->runtime->debugErrorHookData);
701 }
702 #else
703 js_ReportErrorAgain(cx, message, reportp);
704 #endif
705 }
707 /*
708 * We don't post an exception in this case, since doing so runs into
709 * complications of pre-allocating an exception object which required
710 * running the Exception class initializer early etc.
711 * Instead we just invoke the errorReporter with an "Out Of Memory"
712 * type message, and then hope the process ends swiftly.
713 */
714 void
715 js_ReportOutOfMemory(JSContext *cx, JSErrorCallback callback)
716 {
717 JSStackFrame *fp;
718 JSErrorReport report;
719 JSErrorReporter onError = cx->errorReporter;
721 /* Get the message for this error, but we won't expand any arguments. */
722 const JSErrorFormatString *efs = callback(NULL, NULL, JSMSG_OUT_OF_MEMORY);
723 const char *msg = efs ? efs->format : "Out of memory";
725 /* Fill out the report, but don't do anything that requires allocation. */
726 memset(&report, 0, sizeof (struct JSErrorReport));
727 report.flags = JSREPORT_ERROR;
728 report.errorNumber = JSMSG_OUT_OF_MEMORY;
730 /*
731 * Walk stack until we find a frame that is associated with some script
732 * rather than a native frame.
733 */
734 for (fp = cx->fp; fp; fp = fp->down) {
735 if (fp->script && fp->pc) {
736 report.filename = fp->script->filename;
737 report.lineno = js_PCToLineNumber(cx, fp->script, fp->pc);
738 break;
739 }
740 }
742 /*
743 * If debugErrorHook is present then we give it a chance to veto
744 * sending the error on to the regular ErrorReporter.
745 */
746 if (onError) {
747 JSDebugErrorHook hook = cx->runtime->debugErrorHook;
748 if (hook &&
749 !hook(cx, msg, &report, cx->runtime->debugErrorHookData)) {
750 onError = NULL;
751 }
752 }
754 if (onError)
755 onError(cx, msg, &report);
756 }
758 JSBool
759 js_ReportErrorVA(JSContext *cx, uintN flags, const char *format, va_list ap)
760 {
761 char *last;
762 JSStackFrame *fp;
763 JSErrorReport report;
764 JSBool warning;
766 if ((flags & JSREPORT_STRICT) && !JS_HAS_STRICT_OPTION(cx))
767 return JS_TRUE;
769 last = JS_vsmprintf(format, ap);
770 if (!last)
771 return JS_FALSE;
773 memset(&report, 0, sizeof (struct JSErrorReport));
774 report.flags = flags;
776 /* Find the top-most active script frame, for best line number blame. */
777 for (fp = cx->fp; fp; fp = fp->down) {
778 if (fp->script && fp->pc) {
779 report.filename = fp->script->filename;
780 report.lineno = js_PCToLineNumber(cx, fp->script, fp->pc);
781 break;
782 }
783 }
785 warning = JSREPORT_IS_WARNING(report.flags);
786 if (warning && JS_HAS_WERROR_OPTION(cx)) {
787 report.flags &= ~JSREPORT_WARNING;
788 warning = JS_FALSE;
789 }
791 ReportError(cx, last, &report);
792 free(last);
793 return warning;
794 }
796 /*
797 * The arguments from ap need to be packaged up into an array and stored
798 * into the report struct.
799 *
800 * The format string addressed by the error number may contain operands
801 * identified by the format {N}, where N is a decimal digit. Each of these
802 * is to be replaced by the Nth argument from the va_list. The complete
803 * message is placed into reportp->ucmessage converted to a JSString.
804 *
805 * Returns true if the expansion succeeds (can fail if out of memory).
806 */
807 JSBool
808 js_ExpandErrorArguments(JSContext *cx, JSErrorCallback callback,
809 void *userRef, const uintN errorNumber,
810 char **messagep, JSErrorReport *reportp,
811 JSBool *warningp, JSBool charArgs, va_list ap)
812 {
813 const JSErrorFormatString *efs;
814 int i;
815 int argCount;
817 *warningp = JSREPORT_IS_WARNING(reportp->flags);
818 if (*warningp && JS_HAS_WERROR_OPTION(cx)) {
819 reportp->flags &= ~JSREPORT_WARNING;
820 *warningp = JS_FALSE;
821 }
823 *messagep = NULL;
824 if (callback) {
825 efs = callback(userRef, NULL, errorNumber);
826 if (efs) {
827 size_t totalArgsLength = 0;
828 size_t argLengths[10]; /* only {0} thru {9} supported */
829 argCount = efs->argCount;
830 JS_ASSERT(argCount <= 10);
831 if (argCount > 0) {
832 /*
833 * Gather the arguments into an array, and accumulate
834 * their sizes. We allocate 1 more than necessary and
835 * null it out to act as the caboose when we free the
836 * pointers later.
837 */
838 reportp->messageArgs = (const jschar **)
839 JS_malloc(cx, sizeof(jschar *) * (argCount + 1));
840 if (!reportp->messageArgs)
841 return JS_FALSE;
842 reportp->messageArgs[argCount] = NULL;
843 for (i = 0; i < argCount; i++) {
844 if (charArgs) {
845 char *charArg = va_arg(ap, char *);
846 size_t charArgLength = strlen(charArg);
847 reportp->messageArgs[i]
848 = js_InflateString(cx, charArg, &charArgLength);
849 if (!reportp->messageArgs[i])
850 goto error;
851 }
852 else
853 reportp->messageArgs[i] = va_arg(ap, jschar *);
854 argLengths[i] = js_strlen(reportp->messageArgs[i]);
855 totalArgsLength += argLengths[i];
856 }
857 /* NULL-terminate for easy copying. */
858 reportp->messageArgs[i] = NULL;
859 }
860 /*
861 * Parse the error format, substituting the argument X
862 * for {X} in the format.
863 */
864 if (argCount > 0) {
865 if (efs->format) {
866 jschar *buffer, *fmt, *out;
867 const jschar *arg;
868 int expandedArgs = 0;
869 size_t expandedLength;
870 size_t len = strlen (efs->format);
871 buffer = fmt = js_InflateString (cx, efs->format, &len);
872 if (!buffer)
873 goto error;
874 expandedLength
875 = len
876 - (3 * argCount) /* exclude the {n} */
877 + totalArgsLength;
878 /*
879 * Note - the above calculation assumes that each argument
880 * is used once and only once in the expansion !!!
881 */
882 reportp->ucmessage = out = (jschar *)
883 JS_malloc(cx, (expandedLength + 1) * sizeof(jschar));
884 if (!out) {
885 JS_free (cx, buffer);
886 goto error;
887 }
888 while (*fmt) {
889 if (*fmt == '{') {
890 if (isdigit(fmt[1])) {
891 int d = JS7_UNDEC(fmt[1]);
892 JS_ASSERT(d < argCount);
893 arg = reportp->messageArgs[d];
894 js_strncpy(out, arg, argLengths[d]);
895 out += argLengths[d];
896 fmt += 3;
897 expandedArgs++;
898 continue;
899 }
900 }
901 *out++ = *fmt++;
902 }
903 JS_ASSERT(expandedArgs == argCount);
904 *out = 0;
905 JS_free (cx, buffer);
906 *messagep =
907 js_DeflateString(cx, reportp->ucmessage,
908 (size_t)(out - reportp->ucmessage));
909 if (!*messagep)
910 goto error;
911 }
912 } else {
913 /*
914 * Zero arguments: the format string (if it exists) is the
915 * entire message.
916 */
917 if (efs->format) {
918 size_t len;
919 *messagep = JS_strdup(cx, efs->format);
920 if (!*messagep)
921 goto error;
922 len = strlen(*messagep);
923 reportp->ucmessage
924 = js_InflateString(cx, *messagep, &len);
925 if (!reportp->ucmessage)
926 goto error;
927 }
928 }
929 }
930 }
931 if (*messagep == NULL) {
932 /* where's the right place for this ??? */
933 const char *defaultErrorMessage
934 = "No error message available for error number %d";
935 size_t nbytes = strlen(defaultErrorMessage) + 16;
936 *messagep = (char *)JS_malloc(cx, nbytes);
937 if (!*messagep)
938 goto error;
939 JS_snprintf(*messagep, nbytes, defaultErrorMessage, errorNumber);
940 }
941 return JS_TRUE;
943 error:
944 if (reportp->messageArgs) {
945 i = 0;
946 while (reportp->messageArgs[i])
947 JS_free(cx, (void *)reportp->messageArgs[i++]);
948 JS_free(cx, (void *)reportp->messageArgs);
949 reportp->messageArgs = NULL;
950 }
951 if (reportp->ucmessage) {
952 JS_free(cx, (void *)reportp->ucmessage);
953 reportp->ucmessage = NULL;
954 }
955 if (*messagep) {
956 JS_free(cx, (void *)*messagep);
957 *messagep = NULL;
958 }
959 return JS_FALSE;
960 }
962 JSBool
963 js_ReportErrorNumberVA(JSContext *cx, uintN flags, JSErrorCallback callback,
964 void *userRef, const uintN errorNumber,
965 JSBool charArgs, va_list ap)
966 {
967 JSStackFrame *fp;
968 JSErrorReport report;
969 char *message;
970 JSBool warning;
972 if ((flags & JSREPORT_STRICT) && !JS_HAS_STRICT_OPTION(cx))
973 return JS_TRUE;
975 memset(&report, 0, sizeof (struct JSErrorReport));
976 report.flags = flags;
977 report.errorNumber = errorNumber;
979 /*
980 * If we can't find out where the error was based on the current frame,
981 * see if the next frame has a script/pc combo we can use.
982 */
983 for (fp = cx->fp; fp; fp = fp->down) {
984 if (fp->script && fp->pc) {
985 report.filename = fp->script->filename;
986 report.lineno = js_PCToLineNumber(cx, fp->script, fp->pc);
987 break;
988 }
989 }
991 if (!js_ExpandErrorArguments(cx, callback, userRef, errorNumber,
992 &message, &report, &warning, charArgs, ap)) {
993 return JS_FALSE;
994 }
996 ReportError(cx, message, &report);
998 if (message)
999 JS_free(cx, message);
1000 if (report.messageArgs) {
1001 int i = 0;
1002 while (report.messageArgs[i])
1003 JS_free(cx, (void *)report.messageArgs[i++]);
1004 JS_free(cx, (void *)report.messageArgs);
1005 }
1006 if (report.ucmessage)
1007 JS_free(cx, (void *)report.ucmessage);
1009 return warning;
1010 }
1012 JS_FRIEND_API(void)
1013 js_ReportErrorAgain(JSContext *cx, const char *message, JSErrorReport *reportp)
1014 {
1015 JSErrorReporter onError;
1017 if (!message)
1018 return;
1020 if (cx->lastMessage)
1021 free(cx->lastMessage);
1022 cx->lastMessage = JS_strdup(cx, message);
1023 if (!cx->lastMessage)
1024 return;
1025 onError = cx->errorReporter;
1027 /*
1028 * If debugErrorHook is present then we give it a chance to veto
1029 * sending the error on to the regular ErrorReporter.
1030 */
1031 if (onError) {
1032 JSDebugErrorHook hook = cx->runtime->debugErrorHook;
1033 if (hook &&
1034 !hook(cx, cx->lastMessage, reportp,
1035 cx->runtime->debugErrorHookData)) {
1036 onError = NULL;
1037 }
1038 }
1039 if (onError)
1040 onError(cx, cx->lastMessage, reportp);
1041 }
1043 void
1044 js_ReportIsNotDefined(JSContext *cx, const char *name)
1045 {
1046 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_DEFINED, name);
1047 }
1049 #if defined DEBUG && defined XP_UNIX
1050 /* For gdb usage. */
1051 void js_traceon(JSContext *cx) { cx->tracefp = stderr; }
1052 void js_traceoff(JSContext *cx) { cx->tracefp = NULL; }
1053 #endif
1055 JSErrorFormatString js_ErrorFormatString[JSErr_Limit] = {
1056 #if JS_HAS_DFLT_MSG_STRINGS
1057 #define MSG_DEF(name, number, count, exception, format) \
1058 { format, count } ,
1059 #else
1060 #define MSG_DEF(name, number, count, exception, format) \
1061 { NULL, count } ,
1062 #endif
1063 #include "js.msg"
1064 #undef MSG_DEF
1065 };
1067 const JSErrorFormatString *
1068 js_GetErrorMessage(void *userRef, const char *locale, const uintN errorNumber)
1069 {
1070 if ((errorNumber > 0) && (errorNumber < JSErr_Limit))
1071 return &js_ErrorFormatString[errorNumber];
1072 return NULL;
1073 }