Imported Upstream version 1.0.0
[platform/upstream/js.git] / js / src / jsexn.cpp
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=8 sw=4 et tw=78:
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 standard exception implementation.
43  */
44 #include <stdlib.h>
45 #include <string.h>
46 #include "jstypes.h"
47 #include "jsstdint.h"
48 #include "jsbit.h"
49 #include "jsutil.h"
50 #include "jsprf.h"
51 #include "jsapi.h"
52 #include "jscntxt.h"
53 #include "jsversion.h"
54 #include "jsexn.h"
55 #include "jsfun.h"
56 #include "jsinterp.h"
57 #include "jsnum.h"
58 #include "jsobj.h"
59 #include "jsopcode.h"
60 #include "jsscope.h"
61 #include "jsscript.h"
62 #include "jsstaticcheck.h"
63 #include "jswrapper.h"
64
65 #include "jscntxtinlines.h"
66 #include "jsinterpinlines.h"
67 #include "jsobjinlines.h"
68
69 using namespace js;
70 using namespace js::gc;
71
72 /* Forward declarations for js_ErrorClass's initializer. */
73 static JSBool
74 Exception(JSContext *cx, uintN argc, Value *vp);
75
76 static void
77 exn_trace(JSTracer *trc, JSObject *obj);
78
79 static void
80 exn_finalize(JSContext *cx, JSObject *obj);
81
82 static JSBool
83 exn_enumerate(JSContext *cx, JSObject *obj);
84
85 static JSBool
86 exn_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags,
87             JSObject **objp);
88
89 Class js_ErrorClass = {
90     js_Error_str,
91     JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | JSCLASS_MARK_IS_TRACE |
92     JSCLASS_HAS_CACHED_PROTO(JSProto_Error),
93     PropertyStub,         /* addProperty */
94     PropertyStub,         /* delProperty */
95     PropertyStub,         /* getProperty */
96     StrictPropertyStub,   /* setProperty */
97     exn_enumerate,
98     (JSResolveOp)exn_resolve,
99     ConvertStub,
100     exn_finalize,
101     NULL,                 /* reserved0   */
102     NULL,                 /* checkAccess */
103     NULL,                 /* call        */
104     NULL,                 /* construct   */
105     NULL,                 /* xdrObject   */
106     NULL,                 /* hasInstance */
107     JS_CLASS_TRACE(exn_trace)
108 };
109
110 typedef struct JSStackTraceElem {
111     JSString            *funName;
112     size_t              argc;
113     const char          *filename;
114     uintN               ulineno;
115 } JSStackTraceElem;
116
117 typedef struct JSExnPrivate {
118     /* A copy of the JSErrorReport originally generated. */
119     JSErrorReport       *errorReport;
120     JSString            *message;
121     JSString            *filename;
122     uintN               lineno;
123     size_t              stackDepth;
124     JSStackTraceElem    stackElems[1];
125 } JSExnPrivate;
126
127 static JSString *
128 StackTraceToString(JSContext *cx, JSExnPrivate *priv);
129
130 static JSErrorReport *
131 CopyErrorReport(JSContext *cx, JSErrorReport *report)
132 {
133     /*
134      * We use a single malloc block to make a deep copy of JSErrorReport with
135      * the following layout:
136      *   JSErrorReport
137      *   array of copies of report->messageArgs
138      *   jschar array with characters for all messageArgs
139      *   jschar array with characters for ucmessage
140      *   jschar array with characters for uclinebuf and uctokenptr
141      *   char array with characters for linebuf and tokenptr
142      *   char array with characters for filename
143      * Such layout together with the properties enforced by the following
144      * asserts does not need any extra alignment padding.
145      */
146     JS_STATIC_ASSERT(sizeof(JSErrorReport) % sizeof(const char *) == 0);
147     JS_STATIC_ASSERT(sizeof(const char *) % sizeof(jschar) == 0);
148
149     size_t filenameSize;
150     size_t linebufSize;
151     size_t uclinebufSize;
152     size_t ucmessageSize;
153     size_t i, argsArraySize, argsCopySize, argSize;
154     size_t mallocSize;
155     JSErrorReport *copy;
156     uint8 *cursor;
157
158 #define JS_CHARS_SIZE(jschars) ((js_strlen(jschars) + 1) * sizeof(jschar))
159
160     filenameSize = report->filename ? strlen(report->filename) + 1 : 0;
161     linebufSize = report->linebuf ? strlen(report->linebuf) + 1 : 0;
162     uclinebufSize = report->uclinebuf ? JS_CHARS_SIZE(report->uclinebuf) : 0;
163     ucmessageSize = 0;
164     argsArraySize = 0;
165     argsCopySize = 0;
166     if (report->ucmessage) {
167         ucmessageSize = JS_CHARS_SIZE(report->ucmessage);
168         if (report->messageArgs) {
169             for (i = 0; report->messageArgs[i]; ++i)
170                 argsCopySize += JS_CHARS_SIZE(report->messageArgs[i]);
171
172             /* Non-null messageArgs should have at least one non-null arg. */
173             JS_ASSERT(i != 0);
174             argsArraySize = (i + 1) * sizeof(const jschar *);
175         }
176     }
177
178     /*
179      * The mallocSize can not overflow since it represents the sum of the
180      * sizes of already allocated objects.
181      */
182     mallocSize = sizeof(JSErrorReport) + argsArraySize + argsCopySize +
183                  ucmessageSize + uclinebufSize + linebufSize + filenameSize;
184     cursor = (uint8 *)cx->malloc(mallocSize);
185     if (!cursor)
186         return NULL;
187
188     copy = (JSErrorReport *)cursor;
189     memset(cursor, 0, sizeof(JSErrorReport));
190     cursor += sizeof(JSErrorReport);
191
192     if (argsArraySize != 0) {
193         copy->messageArgs = (const jschar **)cursor;
194         cursor += argsArraySize;
195         for (i = 0; report->messageArgs[i]; ++i) {
196             copy->messageArgs[i] = (const jschar *)cursor;
197             argSize = JS_CHARS_SIZE(report->messageArgs[i]);
198             memcpy(cursor, report->messageArgs[i], argSize);
199             cursor += argSize;
200         }
201         copy->messageArgs[i] = NULL;
202         JS_ASSERT(cursor == (uint8 *)copy->messageArgs[0] + argsCopySize);
203     }
204
205     if (report->ucmessage) {
206         copy->ucmessage = (const jschar *)cursor;
207         memcpy(cursor, report->ucmessage, ucmessageSize);
208         cursor += ucmessageSize;
209     }
210
211     if (report->uclinebuf) {
212         copy->uclinebuf = (const jschar *)cursor;
213         memcpy(cursor, report->uclinebuf, uclinebufSize);
214         cursor += uclinebufSize;
215         if (report->uctokenptr) {
216             copy->uctokenptr = copy->uclinebuf + (report->uctokenptr -
217                                                   report->uclinebuf);
218         }
219     }
220
221     if (report->linebuf) {
222         copy->linebuf = (const char *)cursor;
223         memcpy(cursor, report->linebuf, linebufSize);
224         cursor += linebufSize;
225         if (report->tokenptr) {
226             copy->tokenptr = copy->linebuf + (report->tokenptr -
227                                               report->linebuf);
228         }
229     }
230
231     if (report->filename) {
232         copy->filename = (const char *)cursor;
233         memcpy(cursor, report->filename, filenameSize);
234     }
235     JS_ASSERT(cursor + filenameSize == (uint8 *)copy + mallocSize);
236
237     /* Copy non-pointer members. */
238     copy->lineno = report->lineno;
239     copy->errorNumber = report->errorNumber;
240
241     /* Note that this is before it gets flagged with JSREPORT_EXCEPTION */
242     copy->flags = report->flags;
243
244 #undef JS_CHARS_SIZE
245     return copy;
246 }
247
248 static jsval *
249 GetStackTraceValueBuffer(JSExnPrivate *priv)
250 {
251     /*
252      * We use extra memory after JSExnPrivateInfo.stackElems to store jsvals
253      * that helps to produce more informative stack traces. The following
254      * assert allows us to assume that no gap after stackElems is necessary to
255      * align the buffer properly.
256      */
257     JS_STATIC_ASSERT(sizeof(JSStackTraceElem) % sizeof(jsval) == 0);
258
259     return (jsval *)(priv->stackElems + priv->stackDepth);
260 }
261
262 static JSBool
263 InitExnPrivate(JSContext *cx, JSObject *exnObject, JSString *message,
264                JSString *filename, uintN lineno, JSErrorReport *report)
265 {
266     JSSecurityCallbacks *callbacks;
267     CheckAccessOp checkAccess;
268     JSErrorReporter older;
269     JSExceptionState *state;
270     jsid callerid;
271     JSStackFrame *fp, *fpstop;
272     size_t stackDepth, valueCount, size;
273     JSBool overflow;
274     JSExnPrivate *priv;
275     JSStackTraceElem *elem;
276     jsval *values;
277
278     JS_ASSERT(exnObject->getClass() == &js_ErrorClass);
279
280     /*
281      * Prepare stack trace data.
282      *
283      * Set aside any error reporter for cx and save its exception state
284      * so we can suppress any checkAccess failures.  Such failures should stop
285      * the backtrace procedure, not result in a failure of this constructor.
286      */
287     callbacks = JS_GetSecurityCallbacks(cx);
288     checkAccess = callbacks
289                   ? Valueify(callbacks->checkObjectAccess)
290                   : NULL;
291     older = JS_SetErrorReporter(cx, NULL);
292     state = JS_SaveExceptionState(cx);
293
294     callerid = ATOM_TO_JSID(cx->runtime->atomState.callerAtom);
295     stackDepth = 0;
296     valueCount = 0;
297     for (fp = js_GetTopStackFrame(cx); fp; fp = fp->prev()) {
298         if (fp->isFunctionFrame() && !fp->isEvalFrame()) {
299             Value v = NullValue();
300             if (checkAccess &&
301                 !checkAccess(cx, &fp->callee(), callerid, JSACC_READ, &v)) {
302                 break;
303             }
304             valueCount += fp->numActualArgs();
305         }
306         ++stackDepth;
307     }
308     JS_RestoreExceptionState(cx, state);
309     JS_SetErrorReporter(cx, older);
310     fpstop = fp;
311
312     size = offsetof(JSExnPrivate, stackElems);
313     overflow = (stackDepth > ((size_t)-1 - size) / sizeof(JSStackTraceElem));
314     size += stackDepth * sizeof(JSStackTraceElem);
315     overflow |= (valueCount > ((size_t)-1 - size) / sizeof(jsval));
316     size += valueCount * sizeof(jsval);
317     if (overflow) {
318         js_ReportAllocationOverflow(cx);
319         return JS_FALSE;
320     }
321     priv = (JSExnPrivate *)cx->malloc(size);
322     if (!priv)
323         return JS_FALSE;
324
325     /*
326      * We initialize errorReport with a copy of report after setting the
327      * private slot, to prevent GC accessing a junk value we clear the field
328      * here.
329      */
330     priv->errorReport = NULL;
331     priv->message = message;
332     priv->filename = filename;
333     priv->lineno = lineno;
334     priv->stackDepth = stackDepth;
335
336     values = GetStackTraceValueBuffer(priv);
337     elem = priv->stackElems;
338     for (fp = js_GetTopStackFrame(cx); fp != fpstop; fp = fp->prev()) {
339         if (!fp->isFunctionFrame() || fp->isEvalFrame()) {
340             elem->funName = NULL;
341             elem->argc = 0;
342         } else {
343             elem->funName = fp->fun()->atom
344                             ? ATOM_TO_STRING(fp->fun()->atom)
345                             : cx->runtime->emptyString;
346             elem->argc = fp->numActualArgs();
347             fp->forEachCanonicalActualArg(CopyTo(Valueify(values)));
348             values += elem->argc;
349         }
350         elem->ulineno = 0;
351         elem->filename = NULL;
352         if (fp->isScriptFrame()) {
353             elem->filename = fp->script()->filename;
354             if (fp->pc(cx))
355                 elem->ulineno = js_FramePCToLineNumber(cx, fp);
356         }
357         ++elem;
358     }
359     JS_ASSERT(priv->stackElems + stackDepth == elem);
360     JS_ASSERT(GetStackTraceValueBuffer(priv) + valueCount == values);
361
362     exnObject->setPrivate(priv);
363
364     if (report) {
365         /*
366          * Construct a new copy of the error report struct. We can't use the
367          * error report struct that was passed in, because it's allocated on
368          * the stack, and also because it may point to transient data in the
369          * TokenStream.
370          */
371         priv->errorReport = CopyErrorReport(cx, report);
372         if (!priv->errorReport) {
373             /* The finalizer realeases priv since it is in the private slot. */
374             return JS_FALSE;
375         }
376     }
377
378     return JS_TRUE;
379 }
380
381 static inline JSExnPrivate *
382 GetExnPrivate(JSContext *cx, JSObject *obj)
383 {
384     return (JSExnPrivate *) obj->getPrivate();
385 }
386
387 static void
388 exn_trace(JSTracer *trc, JSObject *obj)
389 {
390     JSExnPrivate *priv;
391     JSStackTraceElem *elem;
392     size_t vcount, i;
393     jsval *vp, v;
394
395     priv = GetExnPrivate(trc->context, obj);
396     if (priv) {
397         if (priv->message)
398             MarkString(trc, priv->message, "exception message");
399         if (priv->filename)
400             MarkString(trc, priv->filename, "exception filename");
401
402         elem = priv->stackElems;
403         for (vcount = i = 0; i != priv->stackDepth; ++i, ++elem) {
404             if (elem->funName)
405                 MarkString(trc, elem->funName, "stack trace function name");
406             if (IS_GC_MARKING_TRACER(trc) && elem->filename)
407                 js_MarkScriptFilename(elem->filename);
408             vcount += elem->argc;
409         }
410         vp = GetStackTraceValueBuffer(priv);
411         for (i = 0; i != vcount; ++i, ++vp) {
412             v = *vp;
413             JS_CALL_VALUE_TRACER(trc, v, "stack trace argument");
414         }
415     }
416 }
417
418 static void
419 exn_finalize(JSContext *cx, JSObject *obj)
420 {
421     JSExnPrivate *priv;
422
423     priv = GetExnPrivate(cx, obj);
424     if (priv) {
425         if (priv->errorReport)
426             cx->free(priv->errorReport);
427         cx->free(priv);
428     }
429 }
430
431 static JSBool
432 exn_enumerate(JSContext *cx, JSObject *obj)
433 {
434     JSAtomState *atomState;
435     uintN i;
436     JSAtom *atom;
437     JSObject *pobj;
438     JSProperty *prop;
439
440     JS_STATIC_ASSERT(sizeof(JSAtomState) <= (size_t)(uint16)-1);
441     static const uint16 offsets[] = {
442         (uint16)offsetof(JSAtomState, messageAtom),
443         (uint16)offsetof(JSAtomState, fileNameAtom),
444         (uint16)offsetof(JSAtomState, lineNumberAtom),
445         (uint16)offsetof(JSAtomState, stackAtom),
446     };
447
448     atomState = &cx->runtime->atomState;
449     for (i = 0; i != JS_ARRAY_LENGTH(offsets); ++i) {
450         atom = *(JSAtom **)((uint8 *)atomState + offsets[i]);
451         if (!js_LookupProperty(cx, obj, ATOM_TO_JSID(atom), &pobj, &prop))
452             return JS_FALSE;
453     }
454     return JS_TRUE;
455 }
456
457 static JSBool
458 exn_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags,
459             JSObject **objp)
460 {
461     JSExnPrivate *priv;
462     JSString *str;
463     JSAtom *atom;
464     JSString *stack;
465     const char *prop;
466     jsval v;
467
468     *objp = NULL;
469     priv = GetExnPrivate(cx, obj);
470     if (priv && JSID_IS_ATOM(id)) {
471         str = JSID_TO_STRING(id);
472
473         atom = cx->runtime->atomState.messageAtom;
474         if (str == ATOM_TO_STRING(atom)) {
475             prop = js_message_str;
476             v = STRING_TO_JSVAL(priv->message);
477             goto define;
478         }
479
480         atom = cx->runtime->atomState.fileNameAtom;
481         if (str == ATOM_TO_STRING(atom)) {
482             prop = js_fileName_str;
483             v = STRING_TO_JSVAL(priv->filename);
484             goto define;
485         }
486
487         atom = cx->runtime->atomState.lineNumberAtom;
488         if (str == ATOM_TO_STRING(atom)) {
489             prop = js_lineNumber_str;
490             v = INT_TO_JSVAL(priv->lineno);
491             goto define;
492         }
493
494         atom = cx->runtime->atomState.stackAtom;
495         if (str == ATOM_TO_STRING(atom)) {
496             stack = StackTraceToString(cx, priv);
497             if (!stack)
498                 return JS_FALSE;
499
500             /* Allow to GC all things that were used to build stack trace. */
501             priv->stackDepth = 0;
502             prop = js_stack_str;
503             v = STRING_TO_JSVAL(stack);
504             goto define;
505         }
506     }
507     return JS_TRUE;
508
509   define:
510     if (!JS_DefineProperty(cx, obj, prop, v, NULL, NULL, JSPROP_ENUMERATE))
511         return JS_FALSE;
512     *objp = obj;
513     return JS_TRUE;
514 }
515
516 JSErrorReport *
517 js_ErrorFromException(JSContext *cx, jsval exn)
518 {
519     JSObject *obj;
520     JSExnPrivate *priv;
521
522     if (JSVAL_IS_PRIMITIVE(exn))
523         return NULL;
524     obj = JSVAL_TO_OBJECT(exn);
525     if (obj->getClass() != &js_ErrorClass)
526         return NULL;
527     priv = GetExnPrivate(cx, obj);
528     if (!priv)
529         return NULL;
530     return priv->errorReport;
531 }
532
533 static JSString *
534 ValueToShortSource(JSContext *cx, jsval v)
535 {
536     JSString *str;
537
538     /* Avoid toSource bloat and fallibility for object types. */
539     if (JSVAL_IS_PRIMITIVE(v))
540         return js_ValueToSource(cx, Valueify(v));
541
542     AutoCompartment ac(cx, JSVAL_TO_OBJECT(v));
543     if (!ac.enter())
544         return NULL;
545
546     if (VALUE_IS_FUNCTION(cx, v)) {
547         /*
548          * XXX Avoid function decompilation bloat for now.
549          */
550         str = JS_GetFunctionId(JS_ValueToFunction(cx, v));
551         if (!str && !(str = js_ValueToSource(cx, Valueify(v)))) {
552             /*
553              * Continue to soldier on if the function couldn't be
554              * converted into a string.
555              */
556             JS_ClearPendingException(cx);
557             str = JS_NewStringCopyZ(cx, "[unknown function]");
558         }
559     } else {
560         /*
561          * XXX Avoid toString on objects, it takes too long and uses too much
562          * memory, for too many classes (see Mozilla bug 166743).
563          */
564         char buf[100];
565         JS_snprintf(buf, sizeof buf, "[object %s]",
566                     JSVAL_TO_OBJECT(v)->getClass()->name);
567         str = JS_NewStringCopyZ(cx, buf);
568     }
569
570     ac.leave();
571
572     if (!str || !cx->compartment->wrap(cx, &str))
573         return NULL;
574     return str;
575 }
576
577 static JSString *
578 StackTraceToString(JSContext *cx, JSExnPrivate *priv)
579 {
580     jschar *stackbuf;
581     size_t stacklen, stackmax;
582     JSStackTraceElem *elem, *endElem;
583     jsval *values;
584     size_t i;
585     JSString *str;
586     const char *cp;
587     char ulnbuf[11];
588
589     /* After this point, failing control flow must goto bad. */
590     stackbuf = NULL;
591     stacklen = stackmax = 0;
592
593 /* Limit the stackbuf length to a reasonable value to avoid overflow checks. */
594 #define STACK_LENGTH_LIMIT JS_BIT(20)
595
596 #define APPEND_CHAR_TO_STACK(c)                                               \
597     JS_BEGIN_MACRO                                                            \
598         if (stacklen == stackmax) {                                           \
599             void *ptr_;                                                       \
600             if (stackmax >= STACK_LENGTH_LIMIT)                               \
601                 goto done;                                                    \
602             stackmax = stackmax ? 2 * stackmax : 64;                          \
603             ptr_ = cx->realloc(stackbuf, (stackmax+1) * sizeof(jschar));      \
604             if (!ptr_)                                                        \
605                 goto bad;                                                     \
606             stackbuf = (jschar *) ptr_;                                       \
607         }                                                                     \
608         stackbuf[stacklen++] = (c);                                           \
609     JS_END_MACRO
610
611 #define APPEND_STRING_TO_STACK(str)                                           \
612     JS_BEGIN_MACRO                                                            \
613         JSString *str_ = str;                                                 \
614         size_t length_ = str_->length();                                      \
615         const jschar *chars_ = str_->getChars(cx);                            \
616         if (!chars_)                                                          \
617             goto bad;                                                         \
618                                                                               \
619         if (length_ > stackmax - stacklen) {                                  \
620             void *ptr_;                                                       \
621             if (stackmax >= STACK_LENGTH_LIMIT ||                             \
622                 length_ >= STACK_LENGTH_LIMIT - stacklen) {                   \
623                 goto done;                                                    \
624             }                                                                 \
625             stackmax = JS_BIT(JS_CeilingLog2(stacklen + length_));            \
626             ptr_ = cx->realloc(stackbuf, (stackmax+1) * sizeof(jschar));      \
627             if (!ptr_)                                                        \
628                 goto bad;                                                     \
629             stackbuf = (jschar *) ptr_;                                       \
630         }                                                                     \
631         js_strncpy(stackbuf + stacklen, chars_, length_);                     \
632         stacklen += length_;                                                  \
633     JS_END_MACRO
634
635     values = GetStackTraceValueBuffer(priv);
636     elem = priv->stackElems;
637     for (endElem = elem + priv->stackDepth; elem != endElem; elem++) {
638         if (elem->funName) {
639             APPEND_STRING_TO_STACK(elem->funName);
640             APPEND_CHAR_TO_STACK('(');
641             for (i = 0; i != elem->argc; i++, values++) {
642                 if (i > 0)
643                     APPEND_CHAR_TO_STACK(',');
644                 str = ValueToShortSource(cx, *values);
645                 if (!str)
646                     goto bad;
647                 APPEND_STRING_TO_STACK(str);
648             }
649             APPEND_CHAR_TO_STACK(')');
650         }
651         APPEND_CHAR_TO_STACK('@');
652         if (elem->filename) {
653             for (cp = elem->filename; *cp; cp++)
654                 APPEND_CHAR_TO_STACK(*cp);
655         }
656         APPEND_CHAR_TO_STACK(':');
657         JS_snprintf(ulnbuf, sizeof ulnbuf, "%u", elem->ulineno);
658         for (cp = ulnbuf; *cp; cp++)
659             APPEND_CHAR_TO_STACK(*cp);
660         APPEND_CHAR_TO_STACK('\n');
661     }
662 #undef APPEND_CHAR_TO_STACK
663 #undef APPEND_STRING_TO_STACK
664 #undef STACK_LENGTH_LIMIT
665
666   done:
667     if (stacklen == 0) {
668         JS_ASSERT(!stackbuf);
669         return cx->runtime->emptyString;
670     }
671     if (stacklen < stackmax) {
672         /*
673          * Realloc can fail when shrinking on some FreeBSD versions, so
674          * don't use JS_realloc here; simply let the oversized allocation
675          * be owned by the string in that rare case.
676          */
677         void *shrunk = cx->realloc(stackbuf, (stacklen+1) * sizeof(jschar));
678         if (shrunk)
679             stackbuf = (jschar *) shrunk;
680     }
681
682     stackbuf[stacklen] = 0;
683     str = js_NewString(cx, stackbuf, stacklen);
684     if (str)
685         return str;
686
687   bad:
688     if (stackbuf)
689         cx->free(stackbuf);
690     return NULL;
691 }
692
693 /* XXXbe Consolidate the ugly truth that we don't treat filename as UTF-8
694          with these two functions. */
695 static JSString *
696 FilenameToString(JSContext *cx, const char *filename)
697 {
698     return JS_NewStringCopyZ(cx, filename);
699 }
700
701 static JSBool
702 Exception(JSContext *cx, uintN argc, Value *vp)
703 {
704     JSString *message, *filename;
705     JSStackFrame *fp;
706
707     /*
708      * ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when
709      * called as functions, without operator new.  But as we do not give
710      * each constructor a distinct JSClass, whose .name member is used by
711      * NewNativeClassInstance to find the class prototype, we must get the
712      * class prototype ourselves.
713      */
714     JSObject &callee = vp[0].toObject();
715     Value protov;
716     if (!callee.getProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom), &protov))
717         return JS_FALSE;
718
719     if (!protov.isObject()) {
720         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_PROTOTYPE, "Error");
721         return JS_FALSE;
722     }
723
724     JSObject *errProto = &protov.toObject();
725     JSObject *obj = NewNativeClassInstance(cx, &js_ErrorClass, errProto, errProto->getParent());
726     if (!obj)
727         return JS_FALSE;
728
729     /*
730      * If it's a new object of class Exception, then null out the private
731      * data so that the finalizer doesn't attempt to free it.
732      */
733     if (obj->getClass() == &js_ErrorClass)
734         obj->setPrivate(NULL);
735
736     /* Set the 'message' property. */
737     Value *argv = vp + 2;
738     if (argc != 0) {
739         message = js_ValueToString(cx, argv[0]);
740         if (!message)
741             return JS_FALSE;
742         argv[0].setString(message);
743     } else {
744         message = cx->runtime->emptyString;
745     }
746
747     /* Set the 'fileName' property. */
748     if (argc > 1) {
749         filename = js_ValueToString(cx, argv[1]);
750         if (!filename)
751             return JS_FALSE;
752         argv[1].setString(filename);
753         fp = NULL;
754     } else {
755         fp = js_GetScriptedCaller(cx, NULL);
756         if (fp) {
757             filename = FilenameToString(cx, fp->script()->filename);
758             if (!filename)
759                 return JS_FALSE;
760         } else {
761             filename = cx->runtime->emptyString;
762         }
763     }
764
765     /* Set the 'lineNumber' property. */
766     uint32_t lineno;
767     if (argc > 2) {
768         if (!ValueToECMAUint32(cx, argv[2], &lineno))
769             return JS_FALSE;
770     } else {
771         if (!fp)
772             fp = js_GetScriptedCaller(cx, NULL);
773         lineno = (fp && fp->pc(cx)) ? js_FramePCToLineNumber(cx, fp) : 0;
774     }
775
776     if (obj->getClass() == &js_ErrorClass &&
777         !InitExnPrivate(cx, obj, message, filename, lineno, NULL)) {
778         return JS_FALSE;
779     }
780
781     vp->setObject(*obj);
782     return JS_TRUE;
783 }
784
785 /*
786  * Convert to string.
787  *
788  * This method only uses JavaScript-modifiable properties name, message.  It
789  * is left to the host to check for private data and report filename and line
790  * number information along with this message.
791  */
792 static JSBool
793 exn_toString(JSContext *cx, uintN argc, Value *vp)
794 {
795     jsval v;
796     JSString *name, *message, *result;
797     jschar *chars, *cp;
798     size_t name_length, message_length, length;
799
800     JSObject *obj = ToObject(cx, &vp[1]);
801     if (!obj)
802         return JS_FALSE;
803     if (!obj->getProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.nameAtom), Valueify(&v)))
804         return JS_FALSE;
805     name = JSVAL_IS_STRING(v) ? JSVAL_TO_STRING(v) : cx->runtime->emptyString;
806     vp->setString(name);
807
808     if (!JS_GetProperty(cx, obj, js_message_str, &v))
809         return JS_FALSE;
810     message = JSVAL_IS_STRING(v) ? JSVAL_TO_STRING(v)
811                                  : cx->runtime->emptyString;
812
813     if (message->length() != 0) {
814         name_length = name->length();
815         message_length = message->length();
816         length = (name_length ? name_length + 2 : 0) + message_length;
817         cp = chars = (jschar *) cx->malloc((length + 1) * sizeof(jschar));
818         if (!chars)
819             return JS_FALSE;
820
821         if (name_length) {
822             const jschar *name_chars = name->getChars(cx);
823             if (!name_chars)
824                 return JS_FALSE;
825             js_strncpy(cp, name_chars, name_length);
826             cp += name_length;
827             *cp++ = ':'; *cp++ = ' ';
828         }
829         const jschar *message_chars = message->getChars(cx);
830         if (!message_chars)
831             return JS_FALSE;
832         js_strncpy(cp, message_chars, message_length);
833         cp += message_length;
834         *cp = 0;
835
836         result = js_NewString(cx, chars, length);
837         if (!result) {
838             cx->free(chars);
839             return JS_FALSE;
840         }
841     } else {
842         result = name;
843     }
844
845     vp->setString(result);
846     return JS_TRUE;
847 }
848
849 #if JS_HAS_TOSOURCE
850 /*
851  * Return a string that may eval to something similar to the original object.
852  */
853 static JSBool
854 exn_toSource(JSContext *cx, uintN argc, Value *vp)
855 {
856     JSString *name, *message, *filename, *lineno_as_str, *result;
857     jsval localroots[3] = {JSVAL_NULL, JSVAL_NULL, JSVAL_NULL};
858     size_t lineno_length, name_length, message_length, filename_length, length;
859     jschar *chars, *cp;
860
861     JSObject *obj = ToObject(cx, &vp[1]);
862     if (!obj)
863         return false;
864     if (!obj->getProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.nameAtom), vp))
865         return false;
866     name = js_ValueToString(cx, *vp);
867     if (!name)
868         return false;
869     vp->setString(name);
870
871     {
872         AutoArrayRooter tvr(cx, JS_ARRAY_LENGTH(localroots), Valueify(localroots));
873
874 #ifdef __GNUC__
875         message = filename = NULL;
876 #endif
877         if (!JS_GetProperty(cx, obj, js_message_str, &localroots[0]) ||
878             !(message = js_ValueToSource(cx, Valueify(localroots[0])))) {
879             return false;
880         }
881         localroots[0] = STRING_TO_JSVAL(message);
882
883         if (!JS_GetProperty(cx, obj, js_fileName_str, &localroots[1]) ||
884             !(filename = js_ValueToSource(cx, Valueify(localroots[1])))) {
885             return false;
886         }
887         localroots[1] = STRING_TO_JSVAL(filename);
888
889         if (!JS_GetProperty(cx, obj, js_lineNumber_str, &localroots[2]))
890             return false;
891         uint32_t lineno;
892         if (!ValueToECMAUint32(cx, Valueify(localroots[2]), &lineno))
893             return false;
894
895         if (lineno != 0) {
896             lineno_as_str = js_ValueToString(cx, Valueify(localroots[2]));
897             if (!lineno_as_str)
898                 return false;
899             lineno_length = lineno_as_str->length();
900         } else {
901             lineno_as_str = NULL;
902             lineno_length = 0;
903         }
904
905         /* Magic 8, for the characters in ``(new ())''. */
906         name_length = name->length();
907         message_length = message->length();
908         length = 8 + name_length + message_length;
909
910         filename_length = filename->length();
911         if (filename_length != 0) {
912             /* append filename as ``, {filename}'' */
913             length += 2 + filename_length;
914             if (lineno_as_str) {
915                 /* append lineno as ``, {lineno_as_str}'' */
916                 length += 2 + lineno_length;
917             }
918         } else {
919             if (lineno_as_str) {
920                 /*
921                  * no filename, but have line number,
922                  * need to append ``, "", {lineno_as_str}''
923                  */
924                 length += 6 + lineno_length;
925             }
926         }
927
928         cp = chars = (jschar *) cx->malloc((length + 1) * sizeof(jschar));
929         if (!chars)
930             return false;
931
932         *cp++ = '('; *cp++ = 'n'; *cp++ = 'e'; *cp++ = 'w'; *cp++ = ' ';
933         const jschar *name_chars = name->getChars(cx);
934         if (!name_chars)
935             return false;
936         js_strncpy(cp, name_chars, name_length);
937         cp += name_length;
938         *cp++ = '(';
939         const jschar *message_chars = message->getChars(cx);
940         if (!message_chars)
941             return false;
942         if (message_length != 0) {
943             js_strncpy(cp, message_chars, message_length);
944             cp += message_length;
945         }
946
947         if (filename_length != 0) {
948             /* append filename as ``, {filename}'' */
949             *cp++ = ','; *cp++ = ' ';
950             const jschar *filename_chars = filename->getChars(cx);
951             if (!filename_chars)
952                 return false;
953             js_strncpy(cp, filename_chars, filename_length);
954             cp += filename_length;
955         } else {
956             if (lineno_as_str) {
957                 /*
958                  * no filename, but have line number,
959                  * need to append ``, "", {lineno_as_str}''
960                  */
961                 *cp++ = ','; *cp++ = ' '; *cp++ = '"'; *cp++ = '"';
962             }
963         }
964         if (lineno_as_str) {
965             /* append lineno as ``, {lineno_as_str}'' */
966             *cp++ = ','; *cp++ = ' ';
967             const jschar *lineno_chars = lineno_as_str->getChars(cx);
968             if (!lineno_chars)
969                 return false;
970             js_strncpy(cp, lineno_chars, lineno_length);
971             cp += lineno_length;
972         }
973
974         *cp++ = ')'; *cp++ = ')'; *cp = 0;
975
976         result = js_NewString(cx, chars, length);
977         if (!result) {
978             cx->free(chars);
979             return false;
980         }
981         vp->setString(result);
982         return true;
983     }
984 }
985 #endif
986
987 static JSFunctionSpec exception_methods[] = {
988 #if JS_HAS_TOSOURCE
989     JS_FN(js_toSource_str,   exn_toSource,           0,0),
990 #endif
991     JS_FN(js_toString_str,   exn_toString,           0,0),
992     JS_FS_END
993 };
994
995 /* JSProto_ ordering for exceptions shall match JSEXN_ constants. */
996 JS_STATIC_ASSERT(JSEXN_ERR == 0);
997 JS_STATIC_ASSERT(JSProto_Error + JSEXN_INTERNALERR  == JSProto_InternalError);
998 JS_STATIC_ASSERT(JSProto_Error + JSEXN_EVALERR      == JSProto_EvalError);
999 JS_STATIC_ASSERT(JSProto_Error + JSEXN_RANGEERR     == JSProto_RangeError);
1000 JS_STATIC_ASSERT(JSProto_Error + JSEXN_REFERENCEERR == JSProto_ReferenceError);
1001 JS_STATIC_ASSERT(JSProto_Error + JSEXN_SYNTAXERR    == JSProto_SyntaxError);
1002 JS_STATIC_ASSERT(JSProto_Error + JSEXN_TYPEERR      == JSProto_TypeError);
1003 JS_STATIC_ASSERT(JSProto_Error + JSEXN_URIERR       == JSProto_URIError);
1004
1005 static JS_INLINE JSProtoKey
1006 GetExceptionProtoKey(intN exn)
1007 {
1008     JS_ASSERT(JSEXN_ERR <= exn);
1009     JS_ASSERT(exn < JSEXN_LIMIT);
1010     return (JSProtoKey) (JSProto_Error + exn);
1011 }
1012
1013 JSObject *
1014 js_InitExceptionClasses(JSContext *cx, JSObject *obj)
1015 {
1016     /*
1017      * If lazy class initialization occurs for any Error subclass, then all
1018      * classes are initialized, starting with Error.  To avoid reentry and
1019      * redundant initialization, we must not pass a null proto parameter to
1020      * NewNonFunction below, when called for the Error superclass.  We need to
1021      * ensure that Object.prototype is the proto of Error.prototype.
1022      *
1023      * See the equivalent code to ensure that parent_proto is non-null when
1024      * js_InitClass calls NewObject, in jsobj.cpp.
1025      */
1026     JSObject *obj_proto;
1027     if (!js_GetClassPrototype(cx, obj, JSProto_Object, &obj_proto))
1028         return NULL;
1029
1030     /* Define all error constructors. */
1031     Value empty = StringValue(cx->runtime->emptyString);
1032     jsid nameId = ATOM_TO_JSID(cx->runtime->atomState.nameAtom);
1033     jsid messageId = ATOM_TO_JSID(cx->runtime->atomState.messageAtom);
1034     jsid fileNameId = ATOM_TO_JSID(cx->runtime->atomState.fileNameAtom);
1035     jsid lineNumberId = ATOM_TO_JSID(cx->runtime->atomState.lineNumberAtom);
1036     JSObject *error_proto = NULL;
1037     for (intN i = JSEXN_ERR; i != JSEXN_LIMIT; i++) {
1038         JSProtoKey protoKey = GetExceptionProtoKey(i);
1039         JSAtom *atom = cx->runtime->atomState.classAtoms[protoKey];
1040         JSObject *proto =
1041             DefineConstructorAndPrototype(cx, obj, protoKey, atom,
1042                                           (i == JSEXN_ERR) ? obj_proto : error_proto,
1043                                           &js_ErrorClass, Exception, 1,
1044                                           NULL, (i == JSEXN_ERR) ? exception_methods : NULL,
1045                                           NULL, NULL);
1046         if (!proto)
1047             return NULL;
1048         JS_ASSERT(proto->privateData == NULL);
1049
1050         if (i == JSEXN_ERR)
1051             error_proto = proto;
1052
1053         /* Add properties to the prototype. */
1054         JSAutoResolveFlags rf(cx, JSRESOLVE_QUALIFIED | JSRESOLVE_DECLARING);
1055         if (!js_DefineNativeProperty(cx, proto, nameId, StringValue(atom),
1056                                      PropertyStub, StrictPropertyStub, 
1057                                      JSPROP_ENUMERATE, 0, 0, NULL) ||
1058             !js_DefineNativeProperty(cx, proto, messageId, empty,
1059                                      PropertyStub, StrictPropertyStub,
1060                                      JSPROP_ENUMERATE, 0, 0, NULL) ||
1061             !js_DefineNativeProperty(cx, proto, fileNameId, empty,
1062                                      PropertyStub, StrictPropertyStub,
1063                                      JSPROP_ENUMERATE, 0, 0, NULL) ||
1064             !js_DefineNativeProperty(cx, proto, lineNumberId, Valueify(JSVAL_ZERO),
1065                                      PropertyStub, StrictPropertyStub,
1066                                      JSPROP_ENUMERATE, 0, 0, NULL)) {
1067             return NULL;
1068         }
1069     }
1070
1071     return error_proto;
1072 }
1073
1074 const JSErrorFormatString*
1075 js_GetLocalizedErrorMessage(JSContext* cx, void *userRef, const char *locale,
1076                             const uintN errorNumber)
1077 {
1078     const JSErrorFormatString *errorString = NULL;
1079
1080     if (cx->localeCallbacks && cx->localeCallbacks->localeGetErrorMessage) {
1081         errorString = cx->localeCallbacks
1082                         ->localeGetErrorMessage(userRef, locale, errorNumber);
1083     }
1084     if (!errorString)
1085         errorString = js_GetErrorMessage(userRef, locale, errorNumber);
1086     return errorString;
1087 }
1088
1089 #if defined ( DEBUG_mccabe ) && defined ( PRINTNAMES )
1090 /* For use below... get character strings for error name and exception name */
1091 static struct exnname { char *name; char *exception; } errortoexnname[] = {
1092 #define MSG_DEF(name, number, count, exception, format) \
1093     {#name, #exception},
1094 #include "js.msg"
1095 #undef MSG_DEF
1096 };
1097 #endif /* DEBUG */
1098
1099 JSBool
1100 js_ErrorToException(JSContext *cx, const char *message, JSErrorReport *reportp,
1101                     JSErrorCallback callback, void *userRef)
1102 {
1103     JSErrNum errorNumber;
1104     const JSErrorFormatString *errorString;
1105     JSExnType exn;
1106     jsval tv[4];
1107     JSBool ok;
1108     JSObject *errProto, *errObject;
1109     JSString *messageStr, *filenameStr;
1110
1111     /*
1112      * Tell our caller to report immediately if this report is just a warning.
1113      */
1114     JS_ASSERT(reportp);
1115     if (JSREPORT_IS_WARNING(reportp->flags))
1116         return JS_FALSE;
1117
1118     /* Find the exception index associated with this error. */
1119     errorNumber = (JSErrNum) reportp->errorNumber;
1120     if (!callback || callback == js_GetErrorMessage)
1121         errorString = js_GetLocalizedErrorMessage(cx, NULL, NULL, errorNumber);
1122     else
1123         errorString = callback(userRef, NULL, errorNumber);
1124     exn = errorString ? (JSExnType) errorString->exnType : JSEXN_NONE;
1125     JS_ASSERT(exn < JSEXN_LIMIT);
1126
1127 #if defined( DEBUG_mccabe ) && defined ( PRINTNAMES )
1128     /* Print the error name and the associated exception name to stderr */
1129     fprintf(stderr, "%s\t%s\n",
1130             errortoexnname[errorNumber].name,
1131             errortoexnname[errorNumber].exception);
1132 #endif
1133
1134     /*
1135      * Return false (no exception raised) if no exception is associated
1136      * with the given error number.
1137      */
1138     if (exn == JSEXN_NONE)
1139         return JS_FALSE;
1140
1141     /*
1142      * Prevent runaway recursion, via cx->generatingError.  If an out-of-memory
1143      * error occurs, no exception object will be created, but we don't assume
1144      * that OOM is the only kind of error that subroutines of this function
1145      * called below might raise.
1146      */
1147     if (cx->generatingError)
1148         return JS_FALSE;
1149
1150     MUST_FLOW_THROUGH("out");
1151     cx->generatingError = JS_TRUE;
1152
1153     /* Protect the newly-created strings below from nesting GCs. */
1154     PodArrayZero(tv);
1155     AutoArrayRooter tvr(cx, JS_ARRAY_LENGTH(tv), Valueify(tv));
1156
1157     /*
1158      * Try to get an appropriate prototype by looking up the corresponding
1159      * exception constructor name in the scope chain of the current context's
1160      * top stack frame, or in the global object if no frame is active.
1161      */
1162     ok = js_GetClassPrototype(cx, NULL, GetExceptionProtoKey(exn), &errProto);
1163     if (!ok)
1164         goto out;
1165     tv[0] = OBJECT_TO_JSVAL(errProto);
1166
1167     errObject = NewNativeClassInstance(cx, &js_ErrorClass, errProto, errProto->getParent());
1168     if (!errObject) {
1169         ok = JS_FALSE;
1170         goto out;
1171     }
1172     tv[1] = OBJECT_TO_JSVAL(errObject);
1173
1174     messageStr = JS_NewStringCopyZ(cx, message);
1175     if (!messageStr) {
1176         ok = JS_FALSE;
1177         goto out;
1178     }
1179     tv[2] = STRING_TO_JSVAL(messageStr);
1180
1181     filenameStr = JS_NewStringCopyZ(cx, reportp->filename);
1182     if (!filenameStr) {
1183         ok = JS_FALSE;
1184         goto out;
1185     }
1186     tv[3] = STRING_TO_JSVAL(filenameStr);
1187
1188     ok = InitExnPrivate(cx, errObject, messageStr, filenameStr,
1189                         reportp->lineno, reportp);
1190     if (!ok)
1191         goto out;
1192
1193     JS_SetPendingException(cx, OBJECT_TO_JSVAL(errObject));
1194
1195     /* Flag the error report passed in to indicate an exception was raised. */
1196     reportp->flags |= JSREPORT_EXCEPTION;
1197
1198 out:
1199     cx->generatingError = JS_FALSE;
1200     return ok;
1201 }
1202
1203 JSBool
1204 js_ReportUncaughtException(JSContext *cx)
1205 {
1206     jsval exn;
1207     JSObject *exnObject;
1208     jsval roots[5];
1209     JSErrorReport *reportp, report;
1210     JSString *str;
1211     const char *bytes;
1212
1213     if (!JS_IsExceptionPending(cx))
1214         return true;
1215
1216     if (!JS_GetPendingException(cx, &exn))
1217         return false;
1218
1219     PodArrayZero(roots);
1220     AutoArrayRooter tvr(cx, JS_ARRAY_LENGTH(roots), Valueify(roots));
1221
1222     /*
1223      * Because js_ValueToString below could error and an exception object
1224      * could become unrooted, we must root exnObject.  Later, if exnObject is
1225      * non-null, we need to root other intermediates, so allocate an operand
1226      * stack segment to protect all of these values.
1227      */
1228     if (JSVAL_IS_PRIMITIVE(exn)) {
1229         exnObject = NULL;
1230     } else {
1231         exnObject = JSVAL_TO_OBJECT(exn);
1232         roots[0] = exn;
1233     }
1234
1235     JS_ClearPendingException(cx);
1236     reportp = js_ErrorFromException(cx, exn);
1237
1238     /* XXX L10N angels cry once again (see also jsemit.c, /L10N gaffes/) */
1239     str = js_ValueToString(cx, Valueify(exn));
1240     JSAutoByteString bytesStorage;
1241     if (!str) {
1242         bytes = "unknown (can't convert to string)";
1243     } else {
1244         roots[1] = STRING_TO_JSVAL(str);
1245         if (!bytesStorage.encode(cx, str))
1246             return false;
1247         bytes = bytesStorage.ptr();
1248     }
1249
1250     JSAutoByteString filename;
1251     if (!reportp && exnObject && exnObject->getClass() == &js_ErrorClass) {
1252         if (!JS_GetProperty(cx, exnObject, js_message_str, &roots[2]))
1253             return false;
1254         if (JSVAL_IS_STRING(roots[2])) {
1255             bytesStorage.clear();
1256             if (!bytesStorage.encode(cx, str))
1257                 return false;
1258             bytes = bytesStorage.ptr();
1259         }
1260
1261         if (!JS_GetProperty(cx, exnObject, js_fileName_str, &roots[3]))
1262             return false;
1263         str = js_ValueToString(cx, Valueify(roots[3]));
1264         if (!str || !filename.encode(cx, str))
1265             return false;
1266
1267         if (!JS_GetProperty(cx, exnObject, js_lineNumber_str, &roots[4]))
1268             return false;
1269         uint32_t lineno;
1270         if (!ValueToECMAUint32(cx, Valueify(roots[4]), &lineno))
1271             return false;
1272
1273         reportp = &report;
1274         PodZero(&report);
1275         report.filename = filename.ptr();
1276         report.lineno = (uintN) lineno;
1277         if (JSVAL_IS_STRING(roots[2])) {
1278             report.ucmessage = js_GetStringChars(cx, JSVAL_TO_STRING(roots[2]));
1279             if (!report.ucmessage)
1280                 return false;
1281         }
1282     }
1283
1284     if (!reportp) {
1285         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
1286                              JSMSG_UNCAUGHT_EXCEPTION, bytes);
1287     } else {
1288         /* Flag the error as an exception. */
1289         reportp->flags |= JSREPORT_EXCEPTION;
1290
1291         /* Pass the exception object. */
1292         JS_SetPendingException(cx, exn);
1293         js_ReportErrorAgain(cx, bytes, reportp);
1294         JS_ClearPendingException(cx);
1295     }
1296
1297     return true;
1298 }