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 standard exception implementation.
42  */
43
44 #include "jsstddef.h"
45 #include <stdlib.h>
46 #include <string.h>
47 #include "jstypes.h"
48 #include "jsbit.h"
49 #include "jsutil.h" /* Added by JSIFY */
50 #include "jsprf.h"
51 #include "jsapi.h"
52 #include "jscntxt.h"
53 #include "jsconfig.h"
54 #include "jsexn.h"
55 #include "jsfun.h"
56 #include "jsinterp.h"
57 #include "jsopcode.h"
58 #include "jsnum.h"
59 #include "jsscript.h"
60
61 #if JS_HAS_ERROR_EXCEPTIONS
62 #if !JS_HAS_EXCEPTIONS
63 # error "JS_HAS_EXCEPTIONS must be defined to use JS_HAS_ERROR_EXCEPTIONS"
64 #endif
65
66 /* XXX consider adding rt->atomState.messageAtom */
67 static char js_message_str[]  = "message";
68 static char js_filename_str[] = "fileName";
69 static char js_lineno_str[]   = "lineNumber";
70 static char js_stack_str[]    = "stack";
71
72 /* Forward declarations for ExceptionClass's initializer. */
73 static JSBool
74 Exception(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
75
76 static void
77 exn_finalize(JSContext *cx, JSObject *obj);
78
79 static JSClass ExceptionClass = {
80     "Error",
81     JSCLASS_HAS_PRIVATE,
82     JS_PropertyStub,  JS_PropertyStub,  JS_PropertyStub,  JS_PropertyStub,
83     JS_EnumerateStub, JS_ResolveStub,   JS_ConvertStub,   exn_finalize,
84     NULL,             NULL,             NULL,             Exception,
85     NULL,             NULL,             NULL,             0
86 };
87
88 /*
89  * A copy of the JSErrorReport originally generated.
90  */
91 typedef struct JSExnPrivate {
92     JSErrorReport *errorReport;
93 } JSExnPrivate;
94
95 /*
96  * Undo all the damage done by exn_newPrivate.
97  */
98 static void
99 exn_destroyPrivate(JSContext *cx, JSExnPrivate *privateData)
100 0 {
101 0     JSErrorReport *report;
102 0     const jschar **args;
103
104 0     if (!privateData)
105 0         return;
106 0     report = privateData->errorReport;
107 0     if (report) {
108 0         if (report->uclinebuf)
109 0             JS_free(cx, (void *)report->uclinebuf);
110 0         if (report->filename)
111 0             JS_free(cx, (void *)report->filename);
112 0         if (report->ucmessage)
113 0             JS_free(cx, (void *)report->ucmessage);
114 0         if (report->messageArgs) {
115 0             args = report->messageArgs;
116 0             while (*args != NULL)
117 0                 JS_free(cx, (void *)*args++);
118 0             JS_free(cx, (void *)report->messageArgs);
119         }
120 0         JS_free(cx, report);
121     }
122 0     JS_free(cx, privateData);
123 }
124
125 /*
126  * Copy everything interesting about an error into allocated memory.
127  */
128 static JSExnPrivate *
129 exn_newPrivate(JSContext *cx, JSErrorReport *report)
130 0 {
131 0     intN i;
132 0     JSExnPrivate *newPrivate;
133 0     JSErrorReport *newReport;
134 0     size_t capacity;
135
136 0     newPrivate = (JSExnPrivate *)JS_malloc(cx, sizeof (JSExnPrivate));
137 0     if (!newPrivate)
138 0         return NULL;
139 0     memset(newPrivate, 0, sizeof (JSExnPrivate));
140
141     /* Copy the error report */
142 0     newReport = (JSErrorReport *)JS_malloc(cx, sizeof (JSErrorReport));
143 0     if (!newReport)
144 0         goto error;
145 0     memset(newReport, 0, sizeof (JSErrorReport));
146 0     newPrivate->errorReport = newReport;
147
148 0     if (report->filename != NULL) {
149 0         newReport->filename = JS_strdup(cx, report->filename);
150 0         if (!newReport->filename)
151 0             goto error;
152     } else {
153 0         newReport->filename = NULL;
154     }
155
156 0     newReport->lineno = report->lineno;
157
158     /*
159      * We don't need to copy linebuf and tokenptr, because they
160      * point into the deflated string cache.  (currently?)
161      */
162 0     newReport->linebuf = report->linebuf;
163 0     newReport->tokenptr = report->tokenptr;
164
165     /*
166      * But we do need to copy uclinebuf, uctokenptr, because they're
167      * pointers into internal tokenstream structs, and may go away.
168      */
169 0     if (report->uclinebuf != NULL) {
170 0         capacity = js_strlen(report->uclinebuf) + 1;
171 0         newReport->uclinebuf =
172             (const jschar *)JS_malloc(cx, capacity * sizeof(jschar));
173 0         if (!newReport->uclinebuf)
174 0             goto error;
175 0         js_strncpy((jschar *)newReport->uclinebuf, report->uclinebuf, capacity);
176 0         newReport->uctokenptr = newReport->uclinebuf + (report->uctokenptr -
177                                                         report->uclinebuf);
178     } else {
179 0         newReport->uclinebuf = newReport->uctokenptr = NULL;
180     }
181
182 0     if (report->ucmessage != NULL) {
183 0         capacity = js_strlen(report->ucmessage) + 1;
184 0         newReport->ucmessage = (const jschar *)
185             JS_malloc(cx, capacity * sizeof(jschar));
186 0         if (!newReport->ucmessage)
187 0             goto error;
188 0         js_strncpy((jschar *)newReport->ucmessage, report->ucmessage, capacity);
189
190 0         if (report->messageArgs) {
191 0             for (i = 0; report->messageArgs[i] != NULL; i++)
192 0                 continue;
193 0             JS_ASSERT(i);
194 0             newReport->messageArgs =
195                 (const jschar **)JS_malloc(cx, (i + 1) * sizeof(jschar *));
196 0             if (!newReport->messageArgs)
197 0                 goto error;
198 0             for (i = 0; report->messageArgs[i] != NULL; i++) {
199 0                 capacity = js_strlen(report->messageArgs[i]) + 1;
200 0                 newReport->messageArgs[i] =
201                     (const jschar *)JS_malloc(cx, capacity * sizeof(jschar));
202 0                 if (!newReport->messageArgs[i])
203 0                     goto error;
204 0                 js_strncpy((jschar *)(newReport->messageArgs[i]),
205                            report->messageArgs[i], capacity);
206             }
207 0             newReport->messageArgs[i] = NULL;
208         } else {
209 0             newReport->messageArgs = NULL;
210         }
211     } else {
212 0         newReport->ucmessage = NULL;
213 0         newReport->messageArgs = NULL;
214     }
215 0     newReport->errorNumber = report->errorNumber;
216
217     /* Note that this is before it gets flagged with JSREPORT_EXCEPTION */
218 0     newReport->flags = report->flags;
219
220 0     return newPrivate;
221 error:
222 0     exn_destroyPrivate(cx, newPrivate);
223 0     return NULL;
224 }
225
226 static void
227 exn_finalize(JSContext *cx, JSObject *obj)
228 136 {
229 136     JSExnPrivate *privateData;
230 136     jsval privateValue;
231
232 136     privateValue = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE);
233
234 136     if (!JSVAL_IS_VOID(privateValue)) {
235 0         privateData = (JSExnPrivate*) JSVAL_TO_PRIVATE(privateValue);
236 0         if (privateData)
237 0             exn_destroyPrivate(cx, privateData);
238     }
239 }
240
241 JSErrorReport *
242 js_ErrorFromException(JSContext *cx, jsval exn)
243 0 {
244 0     JSObject *obj;
245 0     JSExnPrivate *privateData;
246 0     jsval privateValue;
247
248 0     if (JSVAL_IS_PRIMITIVE(exn))
249 0         return NULL;
250 0     obj = JSVAL_TO_OBJECT(exn);
251 0     if (OBJ_GET_CLASS(cx, obj) != &ExceptionClass)
252 0         return NULL;
253 0     privateValue = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE);
254 0     if (JSVAL_IS_VOID(privateValue))
255 0         return NULL;
256 0     privateData = (JSExnPrivate*) JSVAL_TO_PRIVATE(privateValue);
257 0     if (!privateData)
258 0         return NULL;
259
260 0     JS_ASSERT(privateData->errorReport);
261 0     return privateData->errorReport;
262 }
263
264 /*
265  * This must be kept in synch with the exceptions array below.
266  * XXX use a jsexn.tbl file a la jsopcode.tbl
267  */
268 typedef enum JSExnType {
269     JSEXN_NONE = -1,
270       JSEXN_ERR,
271         JSEXN_INTERNALERR,
272         JSEXN_EVALERR,
273         JSEXN_RANGEERR,
274         JSEXN_REFERENCEERR,
275         JSEXN_SYNTAXERR,
276         JSEXN_TYPEERR,
277         JSEXN_URIERR,
278         JSEXN_LIMIT
279 } JSExnType;
280
281 struct JSExnSpec {
282     int protoIndex;
283     const char *name;
284     JSNative native;
285 };
286
287 /*
288  * All *Error constructors share the same JSClass, ExceptionClass.  But each
289  * constructor function for an *Error class must have a distinct native 'call'
290  * function pointer, in order for instanceof to work properly across multiple
291  * standard class sets.  See jsfun.c:fun_hasInstance.
292  */
293 #define MAKE_EXCEPTION_CTOR(name)                                             \
294 const char js_##name##_str[] = #name;                                         \
295 static JSBool                                                                 \
296 name(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)      \
297 {                                                                             \
298     return Exception(cx, obj, argc, argv, rval);                              \
299 }
300
301 0 MAKE_EXCEPTION_CTOR(Error)
302 0 MAKE_EXCEPTION_CTOR(InternalError)
303 0 MAKE_EXCEPTION_CTOR(EvalError)
304 0 MAKE_EXCEPTION_CTOR(RangeError)
305 0 MAKE_EXCEPTION_CTOR(ReferenceError)
306 0 MAKE_EXCEPTION_CTOR(SyntaxError)
307 0 MAKE_EXCEPTION_CTOR(TypeError)
308 0 MAKE_EXCEPTION_CTOR(URIError)
309
310 #undef MAKE_EXCEPTION_CTOR
311
312 static struct JSExnSpec exceptions[] = {
313     { JSEXN_NONE,       js_Error_str,           Error },
314     { JSEXN_ERR,        js_InternalError_str,   InternalError },
315     { JSEXN_ERR,        js_EvalError_str,       EvalError },
316     { JSEXN_ERR,        js_RangeError_str,      RangeError },
317     { JSEXN_ERR,        js_ReferenceError_str,  ReferenceError },
318     { JSEXN_ERR,        js_SyntaxError_str,     SyntaxError },
319     { JSEXN_ERR,        js_TypeError_str,       TypeError },
320     { JSEXN_ERR,        js_URIError_str,        URIError },
321     {0,NULL,NULL}
322 };
323
324 static JSBool
325 InitExceptionObject(JSContext *cx, JSObject *obj, JSString *message,
326                     JSString *filename, uintN lineno)
327 {
328 0     JSCheckAccessOp checkAccess;
329 0     JSErrorReporter older;
330 0     JSExceptionState *state;
331 0     jschar *stackbuf;
332 0     size_t stacklen, stackmax;
333 0     JSStackFrame *fp;
334 0     jsval callerid, v;
335 0     JSBool ok;
336 0     JSString *argsrc, *stack;
337 0     uintN i, ulineno;
338 0     const char *cp;
339 0     char ulnbuf[11];
340
341 0     if (!JS_DefineProperty(cx, obj, js_message_str, STRING_TO_JSVAL(message),
342                            NULL, NULL, JSPROP_ENUMERATE)) {
343 0         return JS_FALSE;
344     }
345
346 0     if (!JS_DefineProperty(cx, obj, js_filename_str,
347                            STRING_TO_JSVAL(filename),
348                            NULL, NULL, JSPROP_ENUMERATE)) {
349 0         return JS_FALSE;
350     }
351
352 0     if (!JS_DefineProperty(cx, obj, js_lineno_str,
353                            INT_TO_JSVAL(lineno),
354                            NULL, NULL, JSPROP_ENUMERATE)) {
355 0         return JS_FALSE;
356     }
357
358     /*
359      * Set the 'stack' property.
360      *
361      * First, set aside any error reporter for cx and save its exception state
362      * so we can suppress any checkAccess failures.  Such failures should stop
363      * the backtrace procedure, not result in a failure of this constructor.
364      */
365 0     checkAccess = cx->runtime->checkObjectAccess;
366 0     if (checkAccess) {
367 0         older = JS_SetErrorReporter(cx, NULL);
368 0         state = JS_SaveExceptionState(cx);
369     }
370 #ifdef __GNUC__         /* suppress bogus gcc warnings */
371     else {
372 0         older = NULL;
373 0         state = NULL;
374     }
375 #endif
376 0     callerid = ATOM_KEY(cx->runtime->atomState.callerAtom);
377
378     /*
379      * Prepare to allocate a jschar buffer at stackbuf, where stacklen indexes
380      * the next free jschar slot, and with room for at most stackmax non-null
381      * jschars.  If stackbuf is non-null, it always contains an extra slot for
382      * the null terminator we'll store at the end, as a backstop.
383      *
384      * All early returns must goto done after this point, till the after-loop
385      * cleanup code has run!
386      */
387 0     stackbuf = NULL;
388 0     stacklen = stackmax = 0;
389 0     ok = JS_TRUE;
390
391 #define APPEND_CHAR_TO_STACK(c)                                               \
392     JS_BEGIN_MACRO                                                            \
393         if (stacklen == stackmax) {                                           \
394             void *ptr_;                                                       \
395             stackmax = stackmax ? 2 * stackmax : 64;                          \
396             ptr_ = JS_realloc(cx, stackbuf, (stackmax+1) * sizeof(jschar));   \
397             if (!ptr_) {                                                      \
398                 ok = JS_FALSE;                                                \
399                 goto done;                                                    \
400             }                                                                 \
401             stackbuf = ptr_;                                                  \
402         }                                                                     \
403         stackbuf[stacklen++] = (c);                                           \
404     JS_END_MACRO
405
406 #define APPEND_STRING_TO_STACK(str)                                           \
407     JS_BEGIN_MACRO                                                            \
408         JSString *str_ = str;                                                 \
409         size_t length_ = JSSTRING_LENGTH(str_);                               \
410         if (stacklen + length_ > stackmax) {                                  \
411             void *ptr_;                                                       \
412             stackmax = JS_BIT(JS_CeilingLog2(stacklen + length_));            \
413             ptr_ = JS_realloc(cx, stackbuf, (stackmax+1) * sizeof(jschar));   \
414             if (!ptr_) {                                                      \
415                 ok = JS_FALSE;                                                \
416                 goto done;                                                    \
417             }                                                                 \
418             stackbuf = ptr_;                                                  \
419         }                                                                     \
420         js_strncpy(stackbuf + stacklen, JSSTRING_CHARS(str_), length_);       \
421         stacklen += length_;                                                  \
422     JS_END_MACRO
423
424 0     for (fp = cx->fp; fp; fp = fp->down) {
425 0         if (checkAccess) {
426 0             v = (fp->fun && fp->argv) ? fp->argv[-2] : JSVAL_NULL;
427 0             if (!JSVAL_IS_PRIMITIVE(v)) {
428 0                 ok = checkAccess(cx, fp->fun->object, callerid, JSACC_READ, &v);
429 0                 if (!ok) {
430 0                     ok = JS_TRUE;
431 0                     break;
432                 }
433             }
434         }
435
436 0         if (fp->fun) {
437 0             if (fp->fun->atom)
438 0                 APPEND_STRING_TO_STACK(ATOM_TO_STRING(fp->fun->atom));
439
440 0             APPEND_CHAR_TO_STACK('(');
441 0             for (i = 0; i < fp->argc; i++) {
442                 /* Avoid toSource bloat and fallibility for object types. */
443 0                 v = fp->argv[i];
444 0                 if (JSVAL_IS_PRIMITIVE(v)) {
445 0                     argsrc = js_ValueToSource(cx, v);
446 0                 } else if (JSVAL_IS_FUNCTION(cx, v)) {
447                     /* XXX Avoid function decompilation bloat for now. */
448 0                     argsrc = JS_GetFunctionId(JS_ValueToFunction(cx, v));
449 0                     if (!argsrc)
450 0                         argsrc = js_ValueToSource(cx, v);
451                 } else {
452                     /* XXX Avoid toString on objects, it takes too long and
453                            uses too much memory, for too many classes (see
454                            Mozilla bug 166743). */
455 0                     char buf[100];
456 0                     JS_snprintf(buf, sizeof buf, "[object %s]",
457                                 OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(v))->name);
458 0                     argsrc = JS_NewStringCopyZ(cx, buf);
459                 }
460 0                 if (!argsrc) {
461 0                     ok = JS_FALSE;
462 0                     goto done;
463                 }
464 0                 if (i > 0)
465 0                     APPEND_CHAR_TO_STACK(',');
466 0                 APPEND_STRING_TO_STACK(argsrc);
467             }
468 0             APPEND_CHAR_TO_STACK(')');
469         }
470
471 0         APPEND_CHAR_TO_STACK('@');
472 0         if (fp->script && fp->script->filename) {
473 0             for (cp = fp->script->filename; *cp; cp++)
474 0                 APPEND_CHAR_TO_STACK(*cp);
475         }
476 0         APPEND_CHAR_TO_STACK(':');
477 0         if (fp->script && fp->pc) {
478 0             ulineno = js_PCToLineNumber(cx, fp->script, fp->pc);
479 0             JS_snprintf(ulnbuf, sizeof ulnbuf, "%u", ulineno);
480 0             for (cp = ulnbuf; *cp; cp++)
481 0                 APPEND_CHAR_TO_STACK(*cp);
482         } else {
483 0             APPEND_CHAR_TO_STACK('0');
484         }
485 0         APPEND_CHAR_TO_STACK('\n');
486     }
487
488 #undef APPEND_CHAR_TO_STACK
489 #undef APPEND_STRING_TO_STACK
490
491 done:
492 0     if (checkAccess) {
493 0         if (ok)
494 0             JS_RestoreExceptionState(cx, state);
495         else
496 0             JS_DropExceptionState(cx, state);
497 0         JS_SetErrorReporter(cx, older);
498     }
499 0     if (!ok) {
500 0         JS_free(cx, stackbuf);
501 0         return JS_FALSE;
502     }
503
504 0     if (!stackbuf) {
505 0         stack = cx->runtime->emptyString;
506     } else {
507         /* NB: if stackbuf was allocated, it has room for the terminator. */
508 0         JS_ASSERT(stacklen <= stackmax);
509 0         if (stacklen < stackmax) {
510             /*
511              * Realloc can fail when shrinking on some FreeBSD versions, so
512              * don't use JS_realloc here; simply let the oversized allocation
513              * be owned by the string in that rare case.
514              */
515 0             void *shrunk = realloc(stackbuf, (stacklen+1) * sizeof(jschar));
516 0             if (shrunk)
517 0                 stackbuf = shrunk;
518         }
519 0         stackbuf[stacklen] = 0;
520 0         stack = js_NewString(cx, stackbuf, stacklen, 0);
521 0         if (!stack) {
522 0             JS_free(cx, stackbuf);
523 0             return JS_FALSE;
524         }
525     }
526 0     return JS_DefineProperty(cx, obj, js_stack_str,
527                              STRING_TO_JSVAL(stack),
528                              NULL, NULL, JSPROP_ENUMERATE);
529 }
530
531 static JSBool
532 Exception(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
533 0 {
534 0     JSBool ok;
535 0     jsval pval;
536 0     int32 lineno;
537 0     JSString *message, *filename;
538
539 0     if (cx->creatingException)
540 0         return JS_FALSE;
541 0     cx->creatingException = JS_TRUE;
542
543 0     if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) {
544         /*
545          * ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when
546          * called as functions, without operator new.  But as we do not give
547          * each constructor a distinct JSClass, whose .name member is used by
548          * js_NewObject to find the class prototype, we must get the class
549          * prototype ourselves.
550          */
551 0         ok = OBJ_GET_PROPERTY(cx, JSVAL_TO_OBJECT(argv[-2]),
552                               (jsid)cx->runtime->atomState.classPrototypeAtom,
553                               &pval);
554 0         if (!ok)
555 0             goto out;
556 0         obj = js_NewObject(cx, &ExceptionClass, JSVAL_TO_OBJECT(pval), NULL);
557 0         if (!obj) {
558 0             ok = JS_FALSE;
559 0             goto out;
560         }
561 0         *rval = OBJECT_TO_JSVAL(obj);
562     }
563
564     /*
565      * If it's a new object of class Exception, then null out the private
566      * data so that the finalizer doesn't attempt to free it.
567      */
568 0     if (OBJ_GET_CLASS(cx, obj) == &ExceptionClass)
569 0         OBJ_SET_SLOT(cx, obj, JSSLOT_PRIVATE, JSVAL_VOID);
570
571     /* Set the 'message' property. */
572 0     if (argc != 0) {
573 0         message = js_ValueToString(cx, argv[0]);
574 0         if (!message) {
575 0             ok = JS_FALSE;
576 0             goto out;
577         }
578     } else {
579 0         message = cx->runtime->emptyString;
580     }
581
582     /* Set the 'fileName' property. */
583 0     if (argc > 1) {
584 0         filename = js_ValueToString(cx, argv[1]);
585 0         if (!filename) {
586 0             ok = JS_FALSE;
587 0             goto out;
588         }
589     } else {
590 0         filename = cx->runtime->emptyString;
591     }
592
593     /* Set the 'lineNumber' property. */
594 0     if (argc > 2) {
595 0         ok = js_ValueToInt32(cx, argv[2], &lineno);
596 0         if (!ok)
597 0             goto out;
598     } else {
599 0         lineno = 0;
600     }
601
602 0     ok = InitExceptionObject(cx, obj, message, filename, lineno);
603
604 out:
605 0     cx->creatingException = JS_FALSE;
606 0     return ok;
607 }
608
609 /*
610  * Convert to string.
611  *
612  * This method only uses JavaScript-modifiable properties name, message.  It
613  * is left to the host to check for private data and report filename and line
614  * number information along with this message.
615  */
616 static JSBool
617 exn_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
618 0 {
619 0     jsval v;
620 0     JSString *name, *message, *result;
621 0     jschar *chars, *cp;
622 0     size_t name_length, message_length, length;
623
624 0     if (!OBJ_GET_PROPERTY(cx, obj, (jsid)cx->runtime->atomState.nameAtom, &v))
625 0         return JS_FALSE;
626 0     name = js_ValueToString(cx, v);
627 0     if (!name)
628 0         return JS_FALSE;
629
630 0     if (!JS_GetProperty(cx, obj, js_message_str, &v) ||
631         !(message = js_ValueToString(cx, v))) {
632 0         return JS_FALSE;
633     }
634
635 0     if (JSSTRING_LENGTH(message) != 0) {
636 0         name_length = JSSTRING_LENGTH(name);
637 0         message_length = JSSTRING_LENGTH(message);
638 0         length = name_length + message_length + 2;
639 0         cp = chars = (jschar*) JS_malloc(cx, (length + 1) * sizeof(jschar));
640 0         if (!chars)
641 0             return JS_FALSE;
642
643 0         js_strncpy(cp, JSSTRING_CHARS(name), name_length);
644 0         cp += name_length;
645 0         *cp++ = ':'; *cp++ = ' ';
646 0         js_strncpy(cp, JSSTRING_CHARS(message), message_length);
647 0         cp += message_length;
648 0         *cp = 0;
649
650 0         result = js_NewString(cx, chars, length, 0);
651 0         if (!result) {
652 0             JS_free(cx, chars);
653 0             return JS_FALSE;
654         }
655     } else {
656 0         result = name;
657     }
658
659 0     *rval = STRING_TO_JSVAL(result);
660 0     return JS_TRUE;
661 }
662
663 #if JS_HAS_TOSOURCE
664 /*
665  * Return a string that may eval to something similar to the original object.
666  */
667 static JSBool
668 exn_toSource(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
669 0 {
670 0     jsval v;
671 0     JSString *name, *message, *filename, *lineno_as_str, *result;
672 0     int32 lineno;
673 0     size_t lineno_length, name_length, message_length, filename_length, length;
674 0     jschar *chars, *cp;
675
676 0     if (!OBJ_GET_PROPERTY(cx, obj, (jsid)cx->runtime->atomState.nameAtom, &v))
677 0         return JS_FALSE;
678 0     name = js_ValueToString(cx, v);
679 0     if (!name)
680 0         return JS_FALSE;
681
682 0     if (!JS_GetProperty(cx, obj, js_message_str, &v) ||
683         !(message = js_ValueToSource(cx, v))) {
684 0         return JS_FALSE;
685     }
686
687 0     if (!JS_GetProperty(cx, obj, js_filename_str, &v) ||
688         !(filename = js_ValueToSource(cx, v))) {
689 0         return JS_FALSE;
690     }
691
692 0     if (!JS_GetProperty(cx, obj, js_lineno_str, &v) ||
693         !js_ValueToInt32 (cx, v, &lineno)) {
694 0         return JS_FALSE;
695     }
696
697 0     if (lineno != 0) {
698 0         if (!(lineno_as_str = js_ValueToString(cx, v))) {
699 0             return JS_FALSE;
700         }
701 0         lineno_length = JSSTRING_LENGTH(lineno_as_str);
702     } else {
703 0         lineno_as_str = NULL;
704 0         lineno_length = 0;
705     }
706
707     /* Magic 8, for the characters in ``(new ())''. */
708 0     name_length = JSSTRING_LENGTH(name);
709 0     message_length = JSSTRING_LENGTH(message);
710 0     length = 8 + name_length + message_length;
711
712 0     filename_length = JSSTRING_LENGTH(filename);
713 0     if (filename_length != 0) {
714         /* append filename as ``, {filename}'' */
715 0         length += 2 + filename_length;
716 0         if (lineno_as_str) {
717             /* append lineno as ``, {lineno_as_str}'' */
718 0             length += 2 + lineno_length;
719         }
720     } else {
721 0         if (lineno_as_str) {
722             /*
723              * no filename, but have line number,
724              * need to append ``, "", {lineno_as_str}''
725              */
726 0             length += 6 + lineno_length;
727         }
728     }
729
730 0     cp = chars = (jschar*) JS_malloc(cx, (length + 1) * sizeof(jschar));
731 0     if (!chars)
732 0         return JS_FALSE;
733
734 0     *cp++ = '('; *cp++ = 'n'; *cp++ = 'e'; *cp++ = 'w'; *cp++ = ' ';
735 0     js_strncpy(cp, JSSTRING_CHARS(name), name_length);
736 0     cp += name_length;
737 0     *cp++ = '(';
738 0     if (message_length != 0) {
739 0         js_strncpy(cp, JSSTRING_CHARS(message), message_length);
740 0         cp += message_length;
741     }
742
743 0     if (filename_length != 0) {
744         /* append filename as ``, {filename}'' */
745 0         *cp++ = ','; *cp++ = ' ';
746 0         js_strncpy(cp, JSSTRING_CHARS(filename), filename_length);
747 0         cp += filename_length;
748     } else {
749 0         if (lineno_as_str) {
750             /*
751              * no filename, but have line number,
752              * need to append ``, "", {lineno_as_str}''
753              */
754 0             *cp++ = ','; *cp++ = ' '; *cp++ = '"'; *cp++ = '"';
755         }
756     }
757 0     if (lineno_as_str) {
758         /* append lineno as ``, {lineno_as_str}'' */
759 0         *cp++ = ','; *cp++ = ' ';
760 0         js_strncpy(cp, JSSTRING_CHARS(lineno_as_str), lineno_length);
761 0         cp += lineno_length;
762     }
763
764 0     *cp++ = ')'; *cp++ = ')'; *cp = 0;
765
766 0     result = js_NewString(cx, chars, length, 0);
767 0     if (!result) {
768 0         JS_free(cx, chars);
769 0         return JS_FALSE;
770     }
771 0     *rval = STRING_TO_JSVAL(result);
772 0     return JS_TRUE;
773 }
774 #endif
775
776 static JSFunctionSpec exception_methods[] = {
777 #if JS_HAS_TOSOURCE
778     {js_toSource_str,   exn_toSource,           0,0,0},
779 #endif
780     {js_toString_str,   exn_toString,           0,0,0},
781     {0,0,0,0,0}
782 };
783
784 JSObject *
785 js_InitExceptionClasses(JSContext *cx, JSObject *obj)
786 17 {
787 17     int i;
788 17     JSObject *protos[JSEXN_LIMIT];
789
790     /* Initialize the prototypes first. */
791 153     for (i = 0; exceptions[i].name != 0; i++) {
792 136         JSAtom *atom;
793 136         JSFunction *fun;
794 136         JSString *nameString;
795 136         int protoIndex = exceptions[i].protoIndex;
796
797         /* Make the prototype for the current constructor name. */
798 136         protos[i] = js_NewObject(cx, &ExceptionClass,
799                                  (protoIndex != JSEXN_NONE)
800                                  ? protos[protoIndex]
801                                  : NULL,
802                                  obj);
803 136         if (!protos[i])
804 0             return NULL;
805
806         /* So exn_finalize knows whether to destroy private data. */
807 136         OBJ_SET_SLOT(cx, protos[i], JSSLOT_PRIVATE, JSVAL_VOID);
808
809 136         atom = js_Atomize(cx, exceptions[i].name, strlen(exceptions[i].name), 0);
810 136         if (!atom)
811 0             return NULL;
812
813         /* Make a constructor function for the current name. */
814 136         fun = js_DefineFunction(cx, obj, atom, exceptions[i].native, 3, 0);
815 136         if (!fun)
816 0             return NULL;
817
818         /* Make this constructor make objects of class Exception. */
819 136         fun->clasp = &ExceptionClass;
820
821         /* Make the prototype and constructor links. */
822 136         if (!js_SetClassPrototype(cx, fun->object, protos[i],
823                                   JSPROP_READONLY | JSPROP_PERMANENT)) {
824 0             return NULL;
825         }
826
827         /* proto bootstrap bit from JS_InitClass omitted. */
828 136         nameString = JS_NewStringCopyZ(cx, exceptions[i].name);
829 136         if (!nameString)
830 0             return NULL;
831
832         /* Add the name property to the prototype. */
833 136         if (!JS_DefineProperty(cx, protos[i], js_name_str,
834                                STRING_TO_JSVAL(nameString),
835                                NULL, NULL,
836                                JSPROP_ENUMERATE)) {
837 0             return NULL;
838         }
839     }
840
841     /*
842      * Add an empty message property.  (To Exception.prototype only,
843      * because this property will be the same for all the exception
844      * protos.)
845      */
846 17     if (!JS_DefineProperty(cx, protos[0], js_message_str,
847                            STRING_TO_JSVAL(cx->runtime->emptyString),
848                            NULL, NULL, JSPROP_ENUMERATE)) {
849 0         return NULL;
850     }
851 17     if (!JS_DefineProperty(cx, protos[0], js_filename_str,
852                            STRING_TO_JSVAL(cx->runtime->emptyString),
853                            NULL, NULL, JSPROP_ENUMERATE)) {
854 0         return NULL;
855     }
856 17     if (!JS_DefineProperty(cx, protos[0], js_lineno_str,
857                            INT_TO_JSVAL(0),
858                            NULL, NULL, JSPROP_ENUMERATE)) {
859 0         return NULL;
860     }
861
862     /*
863      * Add methods only to Exception.prototype, because ostensibly all
864      * exception types delegate to that.
865      */
866 17     if (!JS_DefineFunctions(cx, protos[0], exception_methods))
867 0         return NULL;
868
869 17     return protos[0];
870 }
871
872 static JSExnType errorToExceptionNum[] = {
873 #define MSG_DEF(name, number, count, exception, format) \
874     exception,
875 #include "js.msg"
876 #undef MSG_DEF
877 };
878
879 #if defined ( DEBUG_mccabe ) && defined ( PRINTNAMES )
880 /* For use below... get character strings for error name and exception name */
881 static struct exnname { char *name; char *exception; } errortoexnname[] = {
882 #define MSG_DEF(name, number, count, exception, format) \
883     {#name, #exception},
884 #include "js.msg"
885 #undef MSG_DEF
886 };
887 #endif /* DEBUG */
888
889 JSBool
890 js_ErrorToException(JSContext *cx, const char *message, JSErrorReport *reportp)
891 0 {
892 0     JSErrNum errorNumber;
893 0     JSExnType exn;
894 0     JSBool ok;
895 0     JSObject *errProto, *errObject;
896 0     JSString *messageStr, *filenameStr;
897 0     uintN lineno;
898 0     JSExnPrivate *privateData;
899
900     /*
901      * Tell our caller to report immediately if cx has no active frames, or if
902      * this report is just a warning.
903      */
904 0     JS_ASSERT(reportp);
905 0     if (!cx->fp || JSREPORT_IS_WARNING(reportp->flags))
906 0         return JS_FALSE;
907
908     /* Find the exception index associated with this error. */
909 0     errorNumber = (JSErrNum) reportp->errorNumber;
910 0     exn = errorToExceptionNum[errorNumber];
911 0     JS_ASSERT(exn < JSEXN_LIMIT);
912
913 #if defined( DEBUG_mccabe ) && defined ( PRINTNAMES )
914     /* Print the error name and the associated exception name to stderr */
915     fprintf(stderr, "%s\t%s\n",
916             errortoexnname[errorNumber].name,
917             errortoexnname[errorNumber].exception);
918 #endif
919
920     /*
921      * Return false (no exception raised) if no exception is associated
922      * with the given error number.
923      */
924 0     if (exn == JSEXN_NONE)
925 0         return JS_FALSE;
926
927     /*
928      * Prevent runaway recursion, just as the Exception native constructor
929      * must do, via cx->creatingException.  If an out-of-memory error occurs,
930      * no exception object will be created, but we don't assume that OOM is
931      * the only kind of error that subroutines of this function called below
932      * might raise.
933      */
934 0     if (cx->creatingException)
935 0         return JS_FALSE;
936 0     cx->creatingException = JS_TRUE;
937
938     /*
939      * Try to get an appropriate prototype by looking up the corresponding
940      * exception constructor name in the scope chain of the current context's
941      * top stack frame, or in the global object if no frame is active.
942      *
943      * XXXbe hack around JSCLASS_NEW_RESOLVE code in js_LookupProperty that
944      *       checks cx->fp, cx->fp->pc, and js_CodeSpec[*cx->fp->pc] in order
945      *       to compute resolve flags such as JSRESOLVE_ASSIGNING.  The bug
946      *       is that this "internal" js_GetClassPrototype call may trigger a
947      *       resolve of exceptions[exn].name if the global object uses a lazy
948      *       standard class resolver (see JS_ResolveStandardClass), but the
949      *       current frame and bytecode end up affecting the resolve flags.
950      */
951     {
952 0         JSStackFrame *fp = cx->fp;
953 0         jsbytecode *pc = NULL;
954
955 0         if (fp) {
956 0             pc = fp->pc;
957 0             fp->pc = NULL;
958         }
959 0         ok = js_GetClassPrototype(cx, exceptions[exn].name, &errProto);
960 0         if (pc)
961 0             fp->pc = pc;
962 0         if (!ok)
963 0             goto out;
964     }
965
966 0     errObject = js_NewObject(cx, &ExceptionClass, errProto, NULL);
967 0     if (!errObject) {
968 0         ok = JS_FALSE;
969 0         goto out;
970     }
971
972     /*
973      * Set the generated Exception object early, so it won't be GC'd by a last
974      * ditch attempt to collect garbage, or a GC that otherwise nests or races
975      * under any of the following calls.  If one of the following calls fails,
976      * it will overwrite this exception object with one of its own (except in
977      * case of OOM errors, of course).
978      */
979 0     JS_SetPendingException(cx, OBJECT_TO_JSVAL(errObject));
980
981 0     messageStr = JS_NewStringCopyZ(cx, message);
982 0     if (!messageStr) {
983 0         ok = JS_FALSE;
984 0         goto out;
985     }
986
987 0     if (reportp) {
988 0         filenameStr = JS_NewStringCopyZ(cx, reportp->filename);
989 0         if (!filenameStr) {
990 0             ok = JS_FALSE;
991 0             goto out;
992         }
993 0         lineno = reportp->lineno;
994     } else {
995 0         filenameStr = cx->runtime->emptyString;
996 0         lineno = 0;
997     }
998 0     ok = InitExceptionObject(cx, errObject, messageStr, filenameStr, lineno);
999 0     if (!ok)
1000 0         goto out;
1001
1002     /*
1003      * Construct a new copy of the error report struct, and store it in the
1004      * exception object's private data.  We can't use the error report struct
1005      * that was passed in, because it's stack-allocated, and also because it
1006      * may point to transient data in the JSTokenStream.
1007      */
1008 0     privateData = exn_newPrivate(cx, reportp);
1009 0     if (!privateData) {
1010 0         ok = JS_FALSE;
1011 0         goto out;
1012     }
1013 0     OBJ_SET_SLOT(cx, errObject, JSSLOT_PRIVATE, PRIVATE_TO_JSVAL(privateData));
1014
1015     /* Flag the error report passed in to indicate an exception was raised. */
1016 0     reportp->flags |= JSREPORT_EXCEPTION;
1017
1018 out:
1019 0     cx->creatingException = JS_FALSE;
1020 0     return ok;
1021 }
1022 #endif /* JS_HAS_ERROR_EXCEPTIONS */
1023
1024 #if JS_HAS_EXCEPTIONS
1025
1026 JSBool
1027 js_ReportUncaughtException(JSContext *cx)
1028 0 {
1029 0     JSObject *exnObject;
1030 0     JSString *str;
1031 0     jsval exn;
1032 0     JSErrorReport *reportp;
1033 0     const char *bytes;
1034
1035 0     if (!JS_IsExceptionPending(cx))
1036 0         return JS_FALSE;
1037
1038 0     if (!JS_GetPendingException(cx, &exn))
1039 0         return JS_FALSE;
1040
1041     /*
1042      * Because js_ValueToString below could error and an exception object
1043      * could become unrooted, we root it here.
1044      */
1045 0     if (JSVAL_IS_OBJECT(exn) && exn != JSVAL_NULL) {
1046 0         exnObject = JSVAL_TO_OBJECT(exn);
1047 0         if (!js_AddRoot(cx, &exnObject, "exn.report.root"))
1048 0             return JS_FALSE;
1049     } else {
1050 0         exnObject = NULL;
1051     }
1052
1053 #if JS_HAS_ERROR_EXCEPTIONS
1054 0     reportp = js_ErrorFromException(cx, exn);
1055 #else
1056     reportp = NULL;
1057 #endif
1058
1059 0     str = js_ValueToString(cx, exn);
1060 0     bytes = str ? js_GetStringBytes(str) : "null";
1061
1062 0     if (reportp == NULL) {
1063         /*
1064          * XXXmccabe todo: Instead of doing this, synthesize an error report
1065          * struct that includes the filename, lineno where the exception was
1066          * originally thrown.
1067          */
1068 0         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
1069                              JSMSG_UNCAUGHT_EXCEPTION, bytes);
1070     } else {
1071         /* Flag the error as an exception. */
1072 0         reportp->flags |= JSREPORT_EXCEPTION;
1073 0         js_ReportErrorAgain(cx, bytes, reportp);
1074     }
1075
1076 0     if (exnObject != NULL)
1077 0         js_RemoveRoot(cx->runtime, &exnObject);
1078 0     return JS_TRUE;
1079 }
1080