1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 *
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 *
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
10 *
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
15 *
16 * The Original Code is Mozilla Communicator client code, released
17 * March 31, 1998.
18 *
19 * The Initial Developer of the Original Code is
20 * Netscape Communications Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 1998
22 * the Initial Developer. All Rights Reserved.
23 *
24 * Contributor(s):
25 *
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
37 *
38 * ***** END LICENSE BLOCK ***** */
40 /*
41 * JS standard exception implementation.
42 */
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"
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
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";
72 /* Forward declarations for ExceptionClass's initializer. */
73 static JSBool
74 Exception(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
76 static void
77 exn_finalize(JSContext *cx, JSObject *obj);
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 };
88 /*
89 * A copy of the JSErrorReport originally generated.
90 */
91 typedef struct JSExnPrivate {
92 JSErrorReport *errorReport;
93 } JSExnPrivate;
95 /*
96 * Undo all the damage done by exn_newPrivate.
97 */
98 static void
99 exn_destroyPrivate(JSContext *cx, JSExnPrivate *privateData)
100 {
101 JSErrorReport *report;
102 const jschar **args;
104 if (!privateData)
105 return;
106 report = privateData->errorReport;
107 if (report) {
108 if (report->uclinebuf)
109 JS_free(cx, (void *)report->uclinebuf);
110 if (report->filename)
111 JS_free(cx, (void *)report->filename);
112 if (report->ucmessage)
113 JS_free(cx, (void *)report->ucmessage);
114 if (report->messageArgs) {
115 args = report->messageArgs;
116 while (*args != NULL)
117 JS_free(cx, (void *)*args++);
118 JS_free(cx, (void *)report->messageArgs);
119 }
120 JS_free(cx, report);
121 }
122 JS_free(cx, privateData);
123 }
125 /*
126 * Copy everything interesting about an error into allocated memory.
127 */
128 static JSExnPrivate *
129 exn_newPrivate(JSContext *cx, JSErrorReport *report)
130 {
131 intN i;
132 JSExnPrivate *newPrivate;
133 JSErrorReport *newReport;
134 size_t capacity;
136 newPrivate = (JSExnPrivate *)JS_malloc(cx, sizeof (JSExnPrivate));
137 if (!newPrivate)
138 return NULL;
139 memset(newPrivate, 0, sizeof (JSExnPrivate));
141 /* Copy the error report */
142 newReport = (JSErrorReport *)JS_malloc(cx, sizeof (JSErrorReport));
143 if (!newReport)
144 goto error;
145 memset(newReport, 0, sizeof (JSErrorReport));
146 newPrivate->errorReport = newReport;
148 if (report->filename != NULL) {
149 newReport->filename = JS_strdup(cx, report->filename);
150 if (!newReport->filename)
151 goto error;
152 } else {
153 newReport->filename = NULL;
154 }
156 newReport->lineno = report->lineno;
158 /*
159 * We don't need to copy linebuf and tokenptr, because they
160 * point into the deflated string cache. (currently?)
161 */
162 newReport->linebuf = report->linebuf;
163 newReport->tokenptr = report->tokenptr;
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 if (report->uclinebuf != NULL) {
170 capacity = js_strlen(report->uclinebuf) + 1;
171 newReport->uclinebuf =
172 (const jschar *)JS_malloc(cx, capacity * sizeof(jschar));
173 if (!newReport->uclinebuf)
174 goto error;
175 js_strncpy((jschar *)newReport->uclinebuf, report->uclinebuf, capacity);
176 newReport->uctokenptr = newReport->uclinebuf + (report->uctokenptr -
177 report->uclinebuf);
178 } else {
179 newReport->uclinebuf = newReport->uctokenptr = NULL;
180 }
182 if (report->ucmessage != NULL) {
183 capacity = js_strlen(report->ucmessage) + 1;
184 newReport->ucmessage = (const jschar *)
185 JS_malloc(cx, capacity * sizeof(jschar));
186 if (!newReport->ucmessage)
187 goto error;
188 js_strncpy((jschar *)newReport->ucmessage, report->ucmessage, capacity);
190 if (report->messageArgs) {
191 for (i = 0; report->messageArgs[i] != NULL; i++)
192 continue;
193 JS_ASSERT(i);
194 newReport->messageArgs =
195 (const jschar **)JS_malloc(cx, (i + 1) * sizeof(jschar *));
196 if (!newReport->messageArgs)
197 goto error;
198 for (i = 0; report->messageArgs[i] != NULL; i++) {
199 capacity = js_strlen(report->messageArgs[i]) + 1;
200 newReport->messageArgs[i] =
201 (const jschar *)JS_malloc(cx, capacity * sizeof(jschar));
202 if (!newReport->messageArgs[i])
203 goto error;
204 js_strncpy((jschar *)(newReport->messageArgs[i]),
205 report->messageArgs[i], capacity);
206 }
207 newReport->messageArgs[i] = NULL;
208 } else {
209 newReport->messageArgs = NULL;
210 }
211 } else {
212 newReport->ucmessage = NULL;
213 newReport->messageArgs = NULL;
214 }
215 newReport->errorNumber = report->errorNumber;
217 /* Note that this is before it gets flagged with JSREPORT_EXCEPTION */
218 newReport->flags = report->flags;
220 return newPrivate;
221 error:
222 exn_destroyPrivate(cx, newPrivate);
223 return NULL;
224 }
226 static void
227 exn_finalize(JSContext *cx, JSObject *obj)
228 {
229 JSExnPrivate *privateData;
230 jsval privateValue;
232 privateValue = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE);
234 if (!JSVAL_IS_VOID(privateValue)) {
235 privateData = (JSExnPrivate*) JSVAL_TO_PRIVATE(privateValue);
236 if (privateData)
237 exn_destroyPrivate(cx, privateData);
238 }
239 }
241 JSErrorReport *
242 js_ErrorFromException(JSContext *cx, jsval exn)
243 {
244 JSObject *obj;
245 JSExnPrivate *privateData;
246 jsval privateValue;
248 if (JSVAL_IS_PRIMITIVE(exn))
249 return NULL;
250 obj = JSVAL_TO_OBJECT(exn);
251 if (OBJ_GET_CLASS(cx, obj) != &ExceptionClass)
252 return NULL;
253 privateValue = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE);
254 if (JSVAL_IS_VOID(privateValue))
255 return NULL;
256 privateData = (JSExnPrivate*) JSVAL_TO_PRIVATE(privateValue);
257 if (!privateData)
258 return NULL;
260 JS_ASSERT(privateData->errorReport);
261 return privateData->errorReport;
262 }
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;
281 struct JSExnSpec {
282 int protoIndex;
283 const char *name;
284 JSNative native;
285 };
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 }
301 MAKE_EXCEPTION_CTOR(Error)
302 MAKE_EXCEPTION_CTOR(InternalError)
303 MAKE_EXCEPTION_CTOR(EvalError)
304 MAKE_EXCEPTION_CTOR(RangeError)
305 MAKE_EXCEPTION_CTOR(ReferenceError)
306 MAKE_EXCEPTION_CTOR(SyntaxError)
307 MAKE_EXCEPTION_CTOR(TypeError)
308 MAKE_EXCEPTION_CTOR(URIError)
310 #undef MAKE_EXCEPTION_CTOR
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 };
324 static JSBool
325 InitExceptionObject(JSContext *cx, JSObject *obj, JSString *message,
326 JSString *filename, uintN lineno)
327 {
328 JSCheckAccessOp checkAccess;
329 JSErrorReporter older;
330 JSExceptionState *state;
331 jschar *stackbuf;
332 size_t stacklen, stackmax;
333 JSStackFrame *fp;
334 jsval callerid, v;
335 JSBool ok;
336 JSString *argsrc, *stack;
337 uintN i, ulineno;
338 const char *cp;
339 char ulnbuf[11];
341 if (!JS_DefineProperty(cx, obj, js_message_str, STRING_TO_JSVAL(message),
342 NULL, NULL, JSPROP_ENUMERATE)) {
343 return JS_FALSE;
344 }
346 if (!JS_DefineProperty(cx, obj, js_filename_str,
347 STRING_TO_JSVAL(filename),
348 NULL, NULL, JSPROP_ENUMERATE)) {
349 return JS_FALSE;
350 }
352 if (!JS_DefineProperty(cx, obj, js_lineno_str,
353 INT_TO_JSVAL(lineno),
354 NULL, NULL, JSPROP_ENUMERATE)) {
355 return JS_FALSE;
356 }
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 checkAccess = cx->runtime->checkObjectAccess;
366 if (checkAccess) {
367 older = JS_SetErrorReporter(cx, NULL);
368 state = JS_SaveExceptionState(cx);
369 }
370 #ifdef __GNUC__ /* suppress bogus gcc warnings */
371 else {
372 older = NULL;
373 state = NULL;
374 }
375 #endif
376 callerid = ATOM_KEY(cx->runtime->atomState.callerAtom);
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 stackbuf = NULL;
388 stacklen = stackmax = 0;
389 ok = JS_TRUE;
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
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
424 for (fp = cx->fp; fp; fp = fp->down) {
425 if (checkAccess) {
426 v = (fp->fun && fp->argv) ? fp->argv[-2] : JSVAL_NULL;
427 if (!JSVAL_IS_PRIMITIVE(v)) {
428 ok = checkAccess(cx, fp->fun->object, callerid, JSACC_READ, &v);
429 if (!ok) {
430 ok = JS_TRUE;
431 break;
432 }
433 }
434 }
436 if (fp->fun) {
437 if (fp->fun->atom)
438 APPEND_STRING_TO_STACK(ATOM_TO_STRING(fp->fun->atom));
440 APPEND_CHAR_TO_STACK('(');
441 for (i = 0; i < fp->argc; i++) {
442 /* Avoid toSource bloat and fallibility for object types. */
443 v = fp->argv[i];
444 if (JSVAL_IS_PRIMITIVE(v)) {
445 argsrc = js_ValueToSource(cx, v);
446 } else if (JSVAL_IS_FUNCTION(cx, v)) {
447 /* XXX Avoid function decompilation bloat for now. */
448 argsrc = JS_GetFunctionId(JS_ValueToFunction(cx, v));
449 if (!argsrc)
450 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 char buf[100];
456 JS_snprintf(buf, sizeof buf, "[object %s]",
457 OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(v))->name);
458 argsrc = JS_NewStringCopyZ(cx, buf);
459 }
460 if (!argsrc) {
461 ok = JS_FALSE;
462 goto done;
463 }
464 if (i > 0)
465 APPEND_CHAR_TO_STACK(',');
466 APPEND_STRING_TO_STACK(argsrc);
467 }
468 APPEND_CHAR_TO_STACK(')');
469 }
471 APPEND_CHAR_TO_STACK('@');
472 if (fp->script && fp->script->filename) {
473 for (cp = fp->script->filename; *cp; cp++)
474 APPEND_CHAR_TO_STACK(*cp);
475 }
476 APPEND_CHAR_TO_STACK(':');
477 if (fp->script && fp->pc) {
478 ulineno = js_PCToLineNumber(cx, fp->script, fp->pc);
479 JS_snprintf(ulnbuf, sizeof ulnbuf, "%u", ulineno);
480 for (cp = ulnbuf; *cp; cp++)
481 APPEND_CHAR_TO_STACK(*cp);
482 } else {
483 APPEND_CHAR_TO_STACK('0');
484 }
485 APPEND_CHAR_TO_STACK('\n');
486 }
488 #undef APPEND_CHAR_TO_STACK
489 #undef APPEND_STRING_TO_STACK
491 done:
492 if (checkAccess) {
493 if (ok)
494 JS_RestoreExceptionState(cx, state);
495 else
496 JS_DropExceptionState(cx, state);
497 JS_SetErrorReporter(cx, older);
498 }
499 if (!ok) {
500 JS_free(cx, stackbuf);
501 return JS_FALSE;
502 }
504 if (!stackbuf) {
505 stack = cx->runtime->emptyString;
506 } else {
507 /* NB: if stackbuf was allocated, it has room for the terminator. */
508 JS_ASSERT(stacklen <= stackmax);
509 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 void *shrunk = realloc(stackbuf, (stacklen+1) * sizeof(jschar));
516 if (shrunk)
517 stackbuf = shrunk;
518 }
519 stackbuf[stacklen] = 0;
520 stack = js_NewString(cx, stackbuf, stacklen, 0);
521 if (!stack) {
522 JS_free(cx, stackbuf);
523 return JS_FALSE;
524 }
525 }
526 return JS_DefineProperty(cx, obj, js_stack_str,
527 STRING_TO_JSVAL(stack),
528 NULL, NULL, JSPROP_ENUMERATE);
529 }
531 static JSBool
532 Exception(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
533 {
534 JSBool ok;
535 jsval pval;
536 int32 lineno;
537 JSString *message, *filename;
539 if (cx->creatingException)
540 return JS_FALSE;
541 cx->creatingException = JS_TRUE;
543 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 ok = OBJ_GET_PROPERTY(cx, JSVAL_TO_OBJECT(argv[-2]),
552 (jsid)cx->runtime->atomState.classPrototypeAtom,
553 &pval);
554 if (!ok)
555 goto out;
556 obj = js_NewObject(cx, &ExceptionClass, JSVAL_TO_OBJECT(pval), NULL);
557 if (!obj) {
558 ok = JS_FALSE;
559 goto out;
560 }
561 *rval = OBJECT_TO_JSVAL(obj);
562 }
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 if (OBJ_GET_CLASS(cx, obj) == &ExceptionClass)
569 OBJ_SET_SLOT(cx, obj, JSSLOT_PRIVATE, JSVAL_VOID);
571 /* Set the 'message' property. */
572 if (argc != 0) {
573 message = js_ValueToString(cx, argv[0]);
574 if (!message) {
575 ok = JS_FALSE;
576 goto out;
577 }
578 argv[0] = STRING_TO_JSVAL(message);
579 } else {
580 message = cx->runtime->emptyString;
581 }
583 /* Set the 'fileName' property. */
584 if (argc > 1) {
585 filename = js_ValueToString(cx, argv[1]);
586 if (!filename) {
587 ok = JS_FALSE;
588 goto out;
589 }
590 argv[1] = STRING_TO_JSVAL(filename);
591 } else {
592 filename = cx->runtime->emptyString;
593 }
595 /* Set the 'lineNumber' property. */
596 if (argc > 2) {
597 ok = js_ValueToInt32(cx, argv[2], &lineno);
598 if (!ok)
599 goto out;
600 } else {
601 lineno = 0;
602 }
604 ok = InitExceptionObject(cx, obj, message, filename, lineno);
606 out:
607 cx->creatingException = JS_FALSE;
608 return ok;
609 }
611 /*
612 * Convert to string.
613 *
614 * This method only uses JavaScript-modifiable properties name, message. It
615 * is left to the host to check for private data and report filename and line
616 * number information along with this message.
617 */
618 static JSBool
619 exn_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
620 {
621 jsval v;
622 JSString *name, *message, *result;
623 jschar *chars, *cp;
624 size_t name_length, message_length, length;
626 if (!OBJ_GET_PROPERTY(cx, obj, (jsid)cx->runtime->atomState.nameAtom, &v))
627 return JS_FALSE;
628 name = JSVAL_IS_STRING(v) ? JSVAL_TO_STRING(v) : cx->runtime->emptyString;
630 if (!JS_GetProperty(cx, obj, js_message_str, &v))
631 return JS_FALSE;
632 message = JSVAL_IS_STRING(v) ? JSVAL_TO_STRING(v)
633 : cx->runtime->emptyString;
635 if (JSSTRING_LENGTH(message) != 0) {
636 name_length = JSSTRING_LENGTH(name);
637 message_length = JSSTRING_LENGTH(message);
638 length = (name_length ? name_length + 2 : 0) + message_length;
639 cp = chars = (jschar*) JS_malloc(cx, (length + 1) * sizeof(jschar));
640 if (!chars)
641 return JS_FALSE;
643 if (name_length) {
644 js_strncpy(cp, JSSTRING_CHARS(name), name_length);
645 cp += name_length;
646 *cp++ = ':'; *cp++ = ' ';
647 }
648 js_strncpy(cp, JSSTRING_CHARS(message), message_length);
649 cp += message_length;
650 *cp = 0;
652 result = js_NewString(cx, chars, length, 0);
653 if (!result) {
654 JS_free(cx, chars);
655 return JS_FALSE;
656 }
657 } else {
658 result = name;
659 }
661 *rval = STRING_TO_JSVAL(result);
662 return JS_TRUE;
663 }
665 #if JS_HAS_TOSOURCE
666 /*
667 * Return a string that may eval to something similar to the original object.
668 */
669 static JSBool
670 exn_toSource(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
671 {
672 jsval v;
673 JSString *name, *message, *filename, *lineno_as_str, *result;
674 int32 lineno;
675 size_t lineno_length, name_length, message_length, filename_length, length;
676 jschar *chars, *cp;
678 if (!OBJ_GET_PROPERTY(cx, obj, (jsid)cx->runtime->atomState.nameAtom, &v))
679 return JS_FALSE;
680 name = js_ValueToString(cx, v);
681 if (!name)
682 return JS_FALSE;
684 if (!JS_GetProperty(cx, obj, js_message_str, &v) ||
685 !(message = js_ValueToSource(cx, v))) {
686 return JS_FALSE;
687 }
689 if (!JS_GetProperty(cx, obj, js_filename_str, &v) ||
690 !(filename = js_ValueToSource(cx, v))) {
691 return JS_FALSE;
692 }
694 if (!JS_GetProperty(cx, obj, js_lineno_str, &v) ||
695 !js_ValueToInt32 (cx, v, &lineno)) {
696 return JS_FALSE;
697 }
699 if (lineno != 0) {
700 if (!(lineno_as_str = js_ValueToString(cx, v))) {
701 return JS_FALSE;
702 }
703 lineno_length = JSSTRING_LENGTH(lineno_as_str);
704 } else {
705 lineno_as_str = NULL;
706 lineno_length = 0;
707 }
709 /* Magic 8, for the characters in ``(new ())''. */
710 name_length = JSSTRING_LENGTH(name);
711 message_length = JSSTRING_LENGTH(message);
712 length = 8 + name_length + message_length;
714 filename_length = JSSTRING_LENGTH(filename);
715 if (filename_length != 0) {
716 /* append filename as ``, {filename}'' */
717 length += 2 + filename_length;
718 if (lineno_as_str) {
719 /* append lineno as ``, {lineno_as_str}'' */
720 length += 2 + lineno_length;
721 }
722 } else {
723 if (lineno_as_str) {
724 /*
725 * no filename, but have line number,
726 * need to append ``, "", {lineno_as_str}''
727 */
728 length += 6 + lineno_length;
729 }
730 }
732 cp = chars = (jschar*) JS_malloc(cx, (length + 1) * sizeof(jschar));
733 if (!chars)
734 return JS_FALSE;
736 *cp++ = '('; *cp++ = 'n'; *cp++ = 'e'; *cp++ = 'w'; *cp++ = ' ';
737 js_strncpy(cp, JSSTRING_CHARS(name), name_length);
738 cp += name_length;
739 *cp++ = '(';
740 if (message_length != 0) {
741 js_strncpy(cp, JSSTRING_CHARS(message), message_length);
742 cp += message_length;
743 }
745 if (filename_length != 0) {
746 /* append filename as ``, {filename}'' */
747 *cp++ = ','; *cp++ = ' ';
748 js_strncpy(cp, JSSTRING_CHARS(filename), filename_length);
749 cp += filename_length;
750 } else {
751 if (lineno_as_str) {
752 /*
753 * no filename, but have line number,
754 * need to append ``, "", {lineno_as_str}''
755 */
756 *cp++ = ','; *cp++ = ' '; *cp++ = '"'; *cp++ = '"';
757 }
758 }
759 if (lineno_as_str) {
760 /* append lineno as ``, {lineno_as_str}'' */
761 *cp++ = ','; *cp++ = ' ';
762 js_strncpy(cp, JSSTRING_CHARS(lineno_as_str), lineno_length);
763 cp += lineno_length;
764 }
766 *cp++ = ')'; *cp++ = ')'; *cp = 0;
768 result = js_NewString(cx, chars, length, 0);
769 if (!result) {
770 JS_free(cx, chars);
771 return JS_FALSE;
772 }
773 *rval = STRING_TO_JSVAL(result);
774 return JS_TRUE;
775 }
776 #endif
778 static JSFunctionSpec exception_methods[] = {
779 #if JS_HAS_TOSOURCE
780 {js_toSource_str, exn_toSource, 0,0,0},
781 #endif
782 {js_toString_str, exn_toString, 0,0,0},
783 {0,0,0,0,0}
784 };
786 JSObject *
787 js_InitExceptionClasses(JSContext *cx, JSObject *obj)
788 {
789 int i;
790 JSObject *protos[JSEXN_LIMIT];
792 /* Initialize the prototypes first. */
793 for (i = 0; exceptions[i].name != 0; i++) {
794 JSAtom *atom;
795 JSFunction *fun;
796 JSString *nameString;
797 int protoIndex = exceptions[i].protoIndex;
799 /* Make the prototype for the current constructor name. */
800 protos[i] = js_NewObject(cx, &ExceptionClass,
801 (protoIndex != JSEXN_NONE)
802 ? protos[protoIndex]
803 : NULL,
804 obj);
805 if (!protos[i])
806 return NULL;
808 /* So exn_finalize knows whether to destroy private data. */
809 OBJ_SET_SLOT(cx, protos[i], JSSLOT_PRIVATE, JSVAL_VOID);
811 atom = js_Atomize(cx, exceptions[i].name, strlen(exceptions[i].name), 0);
812 if (!atom)
813 return NULL;
815 /* Make a constructor function for the current name. */
816 fun = js_DefineFunction(cx, obj, atom, exceptions[i].native, 3, 0);
817 if (!fun)
818 return NULL;
820 /* Make this constructor make objects of class Exception. */
821 fun->clasp = &ExceptionClass;
823 /* Make the prototype and constructor links. */
824 if (!js_SetClassPrototype(cx, fun->object, protos[i],
825 JSPROP_READONLY | JSPROP_PERMANENT)) {
826 return NULL;
827 }
829 /* proto bootstrap bit from JS_InitClass omitted. */
830 nameString = JS_NewStringCopyZ(cx, exceptions[i].name);
831 if (!nameString)
832 return NULL;
834 /* Add the name property to the prototype. */
835 if (!JS_DefineProperty(cx, protos[i], js_name_str,
836 STRING_TO_JSVAL(nameString),
837 NULL, NULL,
838 JSPROP_ENUMERATE)) {
839 return NULL;
840 }
841 }
843 /*
844 * Add an empty message property. (To Exception.prototype only,
845 * because this property will be the same for all the exception
846 * protos.)
847 */
848 if (!JS_DefineProperty(cx, protos[0], js_message_str,
849 STRING_TO_JSVAL(cx->runtime->emptyString),
850 NULL, NULL, JSPROP_ENUMERATE)) {
851 return NULL;
852 }
853 if (!JS_DefineProperty(cx, protos[0], js_filename_str,
854 STRING_TO_JSVAL(cx->runtime->emptyString),
855 NULL, NULL, JSPROP_ENUMERATE)) {
856 return NULL;
857 }
858 if (!JS_DefineProperty(cx, protos[0], js_lineno_str,
859 INT_TO_JSVAL(0),
860 NULL, NULL, JSPROP_ENUMERATE)) {
861 return NULL;
862 }
864 /*
865 * Add methods only to Exception.prototype, because ostensibly all
866 * exception types delegate to that.
867 */
868 if (!JS_DefineFunctions(cx, protos[0], exception_methods))
869 return NULL;
871 return protos[0];
872 }
874 static JSExnType errorToExceptionNum[] = {
875 #define MSG_DEF(name, number, count, exception, format) \
876 exception,
877 #include "js.msg"
878 #undef MSG_DEF
879 };
881 #if defined ( DEBUG_mccabe ) && defined ( PRINTNAMES )
882 /* For use below... get character strings for error name and exception name */
883 static struct exnname { char *name; char *exception; } errortoexnname[] = {
884 #define MSG_DEF(name, number, count, exception, format) \
885 {#name, #exception},
886 #include "js.msg"
887 #undef MSG_DEF
888 };
889 #endif /* DEBUG */
891 JSBool
892 js_ErrorToException(JSContext *cx, const char *message, JSErrorReport *reportp)
893 {
894 JSErrNum errorNumber;
895 JSExnType exn;
896 JSBool ok;
897 JSObject *errProto, *errObject;
898 JSString *messageStr, *filenameStr;
899 uintN lineno;
900 JSExnPrivate *privateData;
902 /*
903 * Tell our caller to report immediately if cx has no active frames, or if
904 * this report is just a warning.
905 */
906 JS_ASSERT(reportp);
907 if (!cx->fp || JSREPORT_IS_WARNING(reportp->flags))
908 return JS_FALSE;
910 /* Find the exception index associated with this error. */
911 errorNumber = (JSErrNum) reportp->errorNumber;
912 exn = errorToExceptionNum[errorNumber];
913 JS_ASSERT(exn < JSEXN_LIMIT);
915 #if defined( DEBUG_mccabe ) && defined ( PRINTNAMES )
916 /* Print the error name and the associated exception name to stderr */
917 fprintf(stderr, "%s\t%s\n",
918 errortoexnname[errorNumber].name,
919 errortoexnname[errorNumber].exception);
920 #endif
922 /*
923 * Return false (no exception raised) if no exception is associated
924 * with the given error number.
925 */
926 if (exn == JSEXN_NONE)
927 return JS_FALSE;
929 /*
930 * Prevent runaway recursion, just as the Exception native constructor
931 * must do, via cx->creatingException. If an out-of-memory error occurs,
932 * no exception object will be created, but we don't assume that OOM is
933 * the only kind of error that subroutines of this function called below
934 * might raise.
935 */
936 if (cx->creatingException)
937 return JS_FALSE;
938 cx->creatingException = JS_TRUE;
940 /*
941 * Try to get an appropriate prototype by looking up the corresponding
942 * exception constructor name in the scope chain of the current context's
943 * top stack frame, or in the global object if no frame is active.
944 *
945 * XXXbe hack around JSCLASS_NEW_RESOLVE code in js_LookupProperty that
946 * checks cx->fp, cx->fp->pc, and js_CodeSpec[*cx->fp->pc] in order
947 * to compute resolve flags such as JSRESOLVE_ASSIGNING. The bug
948 * is that this "internal" js_GetClassPrototype call may trigger a
949 * resolve of exceptions[exn].name if the global object uses a lazy
950 * standard class resolver (see JS_ResolveStandardClass), but the
951 * current frame and bytecode end up affecting the resolve flags.
952 */
953 {
954 JSStackFrame *fp = cx->fp;
955 jsbytecode *pc = NULL;
957 if (fp) {
958 pc = fp->pc;
959 fp->pc = NULL;
960 }
961 ok = js_GetClassPrototype(cx, exceptions[exn].name, &errProto);
962 if (pc)
963 fp->pc = pc;
964 if (!ok)
965 goto out;
966 }
968 errObject = js_NewObject(cx, &ExceptionClass, errProto, NULL);
969 if (!errObject) {
970 ok = JS_FALSE;
971 goto out;
972 }
974 /*
975 * Set the generated Exception object early, so it won't be GC'd by a last
976 * ditch attempt to collect garbage, or a GC that otherwise nests or races
977 * under any of the following calls. If one of the following calls fails,
978 * it will overwrite this exception object with one of its own (except in
979 * case of OOM errors, of course).
980 */
981 JS_SetPendingException(cx, OBJECT_TO_JSVAL(errObject));
983 messageStr = JS_NewStringCopyZ(cx, message);
984 if (!messageStr) {
985 ok = JS_FALSE;
986 goto out;
987 }
989 if (reportp) {
990 filenameStr = JS_NewStringCopyZ(cx, reportp->filename);
991 if (!filenameStr) {
992 ok = JS_FALSE;
993 goto out;
994 }
995 lineno = reportp->lineno;
996 } else {
997 filenameStr = cx->runtime->emptyString;
998 lineno = 0;
999 }
1000 ok = InitExceptionObject(cx, errObject, messageStr, filenameStr, lineno);
1001 if (!ok)
1002 goto out;
1004 /*
1005 * Construct a new copy of the error report struct, and store it in the
1006 * exception object's private data. We can't use the error report struct
1007 * that was passed in, because it's stack-allocated, and also because it
1008 * may point to transient data in the JSTokenStream.
1009 */
1010 privateData = exn_newPrivate(cx, reportp);
1011 if (!privateData) {
1012 ok = JS_FALSE;
1013 goto out;
1014 }
1015 OBJ_SET_SLOT(cx, errObject, JSSLOT_PRIVATE, PRIVATE_TO_JSVAL(privateData));
1017 /* Flag the error report passed in to indicate an exception was raised. */
1018 reportp->flags |= JSREPORT_EXCEPTION;
1020 out:
1021 cx->creatingException = JS_FALSE;
1022 return ok;
1023 }
1024 #endif /* JS_HAS_ERROR_EXCEPTIONS */
1026 #if JS_HAS_EXCEPTIONS
1028 JSBool
1029 js_ReportUncaughtException(JSContext *cx)
1030 {
1031 JSObject *exnObject;
1032 JSString *str;
1033 jsval exn;
1034 JSErrorReport *reportp;
1035 const char *bytes;
1037 if (!JS_IsExceptionPending(cx))
1038 return JS_TRUE;
1040 if (!JS_GetPendingException(cx, &exn))
1041 return JS_FALSE;
1043 /*
1044 * Because js_ValueToString below could error and an exception object
1045 * could become unrooted, we must root exnObject.
1046 */
1047 if (JSVAL_IS_PRIMITIVE(exn)) {
1048 exnObject = NULL;
1049 } else {
1050 exnObject = JSVAL_TO_OBJECT(exn);
1051 if (!js_AddRoot(cx, &exnObject, "exn.report.root"))
1052 return JS_FALSE;
1053 }
1055 #if JS_HAS_ERROR_EXCEPTIONS
1056 reportp = js_ErrorFromException(cx, exn);
1057 #else
1058 reportp = NULL;
1059 #endif
1061 str = js_ValueToString(cx, exn);
1062 bytes = str ? js_GetStringBytes(str) : "null";
1064 if (reportp == NULL) {
1065 /*
1066 * XXXmccabe todo: Instead of doing this, synthesize an error report
1067 * struct that includes the filename, lineno where the exception was
1068 * originally thrown.
1069 */
1070 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
1071 JSMSG_UNCAUGHT_EXCEPTION, bytes);
1072 } else {
1073 /* Flag the error as an exception. */
1074 reportp->flags |= JSREPORT_EXCEPTION;
1075 js_ReportErrorAgain(cx, bytes, reportp);
1076 }
1078 if (exnObject != NULL)
1079 js_RemoveRoot(cx->runtime, &exnObject);
1080 JS_ClearPendingException(cx);
1081 return JS_TRUE;
1082 }
1084 #endif /* JS_HAS_EXCEPTIONS */