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