js/src/jsexn.cpp

branch
TOR_BUG_3246
changeset 7
129ffea94266
equal deleted inserted replaced
-1:000000000000 0:21979470e57a
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 /*
8 * JS standard exception implementation.
9 */
10
11 #include "jsexn.h"
12
13 #include "mozilla/ArrayUtils.h"
14 #include "mozilla/PodOperations.h"
15
16 #include <string.h>
17
18 #include "jsapi.h"
19 #include "jscntxt.h"
20 #include "jsfun.h"
21 #include "jsnum.h"
22 #include "jsobj.h"
23 #include "jsscript.h"
24 #include "jstypes.h"
25 #include "jsutil.h"
26 #include "jswrapper.h"
27
28 #include "gc/Marking.h"
29 #include "vm/ErrorObject.h"
30 #include "vm/GlobalObject.h"
31 #include "vm/StringBuffer.h"
32
33 #include "jsobjinlines.h"
34
35 #include "vm/ErrorObject-inl.h"
36
37 using namespace js;
38 using namespace js::gc;
39 using namespace js::types;
40
41 using mozilla::ArrayLength;
42 using mozilla::PodArrayZero;
43 using mozilla::PodZero;
44
45 static void
46 exn_finalize(FreeOp *fop, JSObject *obj);
47
48 const Class ErrorObject::class_ = {
49 js_Error_str,
50 JSCLASS_IMPLEMENTS_BARRIERS |
51 JSCLASS_HAS_CACHED_PROTO(JSProto_Error) |
52 JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS),
53 JS_PropertyStub, /* addProperty */
54 JS_DeletePropertyStub, /* delProperty */
55 JS_PropertyStub, /* getProperty */
56 JS_StrictPropertyStub, /* setProperty */
57 JS_EnumerateStub,
58 JS_ResolveStub,
59 JS_ConvertStub,
60 exn_finalize,
61 nullptr, /* call */
62 nullptr, /* hasInstance */
63 nullptr /* construct */
64 };
65
66 JSErrorReport *
67 js::CopyErrorReport(JSContext *cx, JSErrorReport *report)
68 {
69 /*
70 * We use a single malloc block to make a deep copy of JSErrorReport with
71 * the following layout:
72 * JSErrorReport
73 * array of copies of report->messageArgs
74 * jschar array with characters for all messageArgs
75 * jschar array with characters for ucmessage
76 * jschar array with characters for uclinebuf and uctokenptr
77 * char array with characters for linebuf and tokenptr
78 * char array with characters for filename
79 * Such layout together with the properties enforced by the following
80 * asserts does not need any extra alignment padding.
81 */
82 JS_STATIC_ASSERT(sizeof(JSErrorReport) % sizeof(const char *) == 0);
83 JS_STATIC_ASSERT(sizeof(const char *) % sizeof(jschar) == 0);
84
85 size_t filenameSize;
86 size_t linebufSize;
87 size_t uclinebufSize;
88 size_t ucmessageSize;
89 size_t i, argsArraySize, argsCopySize, argSize;
90 size_t mallocSize;
91 JSErrorReport *copy;
92 uint8_t *cursor;
93
94 #define JS_CHARS_SIZE(jschars) ((js_strlen(jschars) + 1) * sizeof(jschar))
95
96 filenameSize = report->filename ? strlen(report->filename) + 1 : 0;
97 linebufSize = report->linebuf ? strlen(report->linebuf) + 1 : 0;
98 uclinebufSize = report->uclinebuf ? JS_CHARS_SIZE(report->uclinebuf) : 0;
99 ucmessageSize = 0;
100 argsArraySize = 0;
101 argsCopySize = 0;
102 if (report->ucmessage) {
103 ucmessageSize = JS_CHARS_SIZE(report->ucmessage);
104 if (report->messageArgs) {
105 for (i = 0; report->messageArgs[i]; ++i)
106 argsCopySize += JS_CHARS_SIZE(report->messageArgs[i]);
107
108 /* Non-null messageArgs should have at least one non-null arg. */
109 JS_ASSERT(i != 0);
110 argsArraySize = (i + 1) * sizeof(const jschar *);
111 }
112 }
113
114 /*
115 * The mallocSize can not overflow since it represents the sum of the
116 * sizes of already allocated objects.
117 */
118 mallocSize = sizeof(JSErrorReport) + argsArraySize + argsCopySize +
119 ucmessageSize + uclinebufSize + linebufSize + filenameSize;
120 cursor = cx->pod_malloc<uint8_t>(mallocSize);
121 if (!cursor)
122 return nullptr;
123
124 copy = (JSErrorReport *)cursor;
125 memset(cursor, 0, sizeof(JSErrorReport));
126 cursor += sizeof(JSErrorReport);
127
128 if (argsArraySize != 0) {
129 copy->messageArgs = (const jschar **)cursor;
130 cursor += argsArraySize;
131 for (i = 0; report->messageArgs[i]; ++i) {
132 copy->messageArgs[i] = (const jschar *)cursor;
133 argSize = JS_CHARS_SIZE(report->messageArgs[i]);
134 js_memcpy(cursor, report->messageArgs[i], argSize);
135 cursor += argSize;
136 }
137 copy->messageArgs[i] = nullptr;
138 JS_ASSERT(cursor == (uint8_t *)copy->messageArgs[0] + argsCopySize);
139 }
140
141 if (report->ucmessage) {
142 copy->ucmessage = (const jschar *)cursor;
143 js_memcpy(cursor, report->ucmessage, ucmessageSize);
144 cursor += ucmessageSize;
145 }
146
147 if (report->uclinebuf) {
148 copy->uclinebuf = (const jschar *)cursor;
149 js_memcpy(cursor, report->uclinebuf, uclinebufSize);
150 cursor += uclinebufSize;
151 if (report->uctokenptr) {
152 copy->uctokenptr = copy->uclinebuf + (report->uctokenptr -
153 report->uclinebuf);
154 }
155 }
156
157 if (report->linebuf) {
158 copy->linebuf = (const char *)cursor;
159 js_memcpy(cursor, report->linebuf, linebufSize);
160 cursor += linebufSize;
161 if (report->tokenptr) {
162 copy->tokenptr = copy->linebuf + (report->tokenptr -
163 report->linebuf);
164 }
165 }
166
167 if (report->filename) {
168 copy->filename = (const char *)cursor;
169 js_memcpy(cursor, report->filename, filenameSize);
170 }
171 JS_ASSERT(cursor + filenameSize == (uint8_t *)copy + mallocSize);
172
173 /* HOLD called by the destination error object. */
174 copy->originPrincipals = report->originPrincipals;
175
176 /* Copy non-pointer members. */
177 copy->lineno = report->lineno;
178 copy->column = report->column;
179 copy->errorNumber = report->errorNumber;
180 copy->exnType = report->exnType;
181
182 /* Note that this is before it gets flagged with JSREPORT_EXCEPTION */
183 copy->flags = report->flags;
184
185 #undef JS_CHARS_SIZE
186 return copy;
187 }
188
189 struct SuppressErrorsGuard
190 {
191 JSContext *cx;
192 JSErrorReporter prevReporter;
193 JS::AutoSaveExceptionState prevState;
194
195 SuppressErrorsGuard(JSContext *cx)
196 : cx(cx),
197 prevReporter(JS_SetErrorReporter(cx, nullptr)),
198 prevState(cx)
199 {}
200
201 ~SuppressErrorsGuard()
202 {
203 JS_SetErrorReporter(cx, prevReporter);
204 }
205 };
206
207 JSString *
208 js::ComputeStackString(JSContext *cx)
209 {
210 StringBuffer sb(cx);
211
212 {
213 RootedAtom atom(cx);
214 SuppressErrorsGuard seg(cx);
215 for (NonBuiltinFrameIter i(cx, FrameIter::ALL_CONTEXTS, FrameIter::GO_THROUGH_SAVED,
216 cx->compartment()->principals);
217 !i.done();
218 ++i)
219 {
220 /* First append the function name, if any. */
221 if (i.isNonEvalFunctionFrame())
222 atom = i.functionDisplayAtom();
223 else
224 atom = nullptr;
225 if (atom && !sb.append(atom))
226 return nullptr;
227
228 /* Next a @ separating function name from source location. */
229 if (!sb.append('@'))
230 return nullptr;
231
232 /* Now the filename. */
233 const char *cfilename = i.scriptFilename();
234 if (!cfilename)
235 cfilename = "";
236 if (!sb.appendInflated(cfilename, strlen(cfilename)))
237 return nullptr;
238
239 uint32_t column = 0;
240 uint32_t line = i.computeLine(&column);
241 // Now the line number
242 if (!sb.append(':') || !NumberValueToStringBuffer(cx, NumberValue(line), sb))
243 return nullptr;
244
245 // Finally, : followed by the column number (1-based, as in other browsers)
246 // and a newline.
247 if (!sb.append(':') || !NumberValueToStringBuffer(cx, NumberValue(column + 1), sb) ||
248 !sb.append('\n'))
249 {
250 return nullptr;
251 }
252
253 /*
254 * Cut off the stack if it gets too deep (most commonly for
255 * infinite recursion errors).
256 */
257 const size_t MaxReportedStackDepth = 1u << 20;
258 if (sb.length() > MaxReportedStackDepth)
259 break;
260 }
261 }
262
263 return sb.finishString();
264 }
265
266 static void
267 exn_finalize(FreeOp *fop, JSObject *obj)
268 {
269 if (JSErrorReport *report = obj->as<ErrorObject>().getErrorReport()) {
270 /* These were held by ErrorObject::init. */
271 if (JSPrincipals *prin = report->originPrincipals)
272 JS_DropPrincipals(fop->runtime(), prin);
273 fop->free_(report);
274 }
275 }
276
277 JSErrorReport *
278 js_ErrorFromException(JSContext *cx, HandleObject objArg)
279 {
280 // It's ok to UncheckedUnwrap here, since all we do is get the
281 // JSErrorReport, and consumers are careful with the information they get
282 // from that anyway. Anyone doing things that would expose anything in the
283 // JSErrorReport to page script either does a security check on the
284 // JSErrorReport's principal or also tries to do toString on our object and
285 // will fail if they can't unwrap it.
286 RootedObject obj(cx, UncheckedUnwrap(objArg));
287 if (!obj->is<ErrorObject>())
288 return nullptr;
289
290 return obj->as<ErrorObject>().getOrCreateErrorReport(cx);
291 }
292
293 static bool
294 Error(JSContext *cx, unsigned argc, Value *vp)
295 {
296 CallArgs args = CallArgsFromVp(argc, vp);
297
298 /* Compute the error message, if any. */
299 RootedString message(cx, nullptr);
300 if (args.hasDefined(0)) {
301 message = ToString<CanGC>(cx, args[0]);
302 if (!message)
303 return false;
304 }
305
306 /* Find the scripted caller. */
307 NonBuiltinFrameIter iter(cx);
308
309 /* Set the 'fileName' property. */
310 RootedString fileName(cx);
311 if (args.length() > 1) {
312 fileName = ToString<CanGC>(cx, args[1]);
313 } else {
314 fileName = cx->runtime()->emptyString;
315 if (!iter.done()) {
316 if (const char *cfilename = iter.scriptFilename())
317 fileName = JS_NewStringCopyZ(cx, cfilename);
318 }
319 }
320 if (!fileName)
321 return false;
322
323 /* Set the 'lineNumber' property. */
324 uint32_t lineNumber, columnNumber = 0;
325 if (args.length() > 2) {
326 if (!ToUint32(cx, args[2], &lineNumber))
327 return false;
328 } else {
329 lineNumber = iter.done() ? 0 : iter.computeLine(&columnNumber);
330 }
331
332 Rooted<JSString*> stack(cx, ComputeStackString(cx));
333 if (!stack)
334 return false;
335
336 /*
337 * ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when
338 * called as functions, without operator new. But as we do not give
339 * each constructor a distinct JSClass, we must get the exception type
340 * ourselves.
341 */
342 JSExnType exnType = JSExnType(args.callee().as<JSFunction>().getExtendedSlot(0).toInt32());
343
344 RootedObject obj(cx, ErrorObject::create(cx, exnType, stack, fileName,
345 lineNumber, columnNumber, nullptr, message));
346 if (!obj)
347 return false;
348
349 args.rval().setObject(*obj);
350 return true;
351 }
352
353 /* ES5 15.11.4.4 (NB: with subsequent errata). */
354 static bool
355 exn_toString(JSContext *cx, unsigned argc, Value *vp)
356 {
357 JS_CHECK_RECURSION(cx, return false);
358 CallArgs args = CallArgsFromVp(argc, vp);
359
360 /* Step 2. */
361 if (!args.thisv().isObject()) {
362 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_PROTOTYPE, "Error");
363 return false;
364 }
365
366 /* Step 1. */
367 RootedObject obj(cx, &args.thisv().toObject());
368
369 /* Step 3. */
370 RootedValue nameVal(cx);
371 if (!JSObject::getProperty(cx, obj, obj, cx->names().name, &nameVal))
372 return false;
373
374 /* Step 4. */
375 RootedString name(cx);
376 if (nameVal.isUndefined()) {
377 name = cx->names().Error;
378 } else {
379 name = ToString<CanGC>(cx, nameVal);
380 if (!name)
381 return false;
382 }
383
384 /* Step 5. */
385 RootedValue msgVal(cx);
386 if (!JSObject::getProperty(cx, obj, obj, cx->names().message, &msgVal))
387 return false;
388
389 /* Step 6. */
390 RootedString message(cx);
391 if (msgVal.isUndefined()) {
392 message = cx->runtime()->emptyString;
393 } else {
394 message = ToString<CanGC>(cx, msgVal);
395 if (!message)
396 return false;
397 }
398
399 /* Step 7. */
400 if (name->empty() && message->empty()) {
401 args.rval().setString(cx->names().Error);
402 return true;
403 }
404
405 /* Step 8. */
406 if (name->empty()) {
407 args.rval().setString(message);
408 return true;
409 }
410
411 /* Step 9. */
412 if (message->empty()) {
413 args.rval().setString(name);
414 return true;
415 }
416
417 /* Step 10. */
418 StringBuffer sb(cx);
419 if (!sb.append(name) || !sb.append(": ") || !sb.append(message))
420 return false;
421
422 JSString *str = sb.finishString();
423 if (!str)
424 return false;
425 args.rval().setString(str);
426 return true;
427 }
428
429 #if JS_HAS_TOSOURCE
430 /*
431 * Return a string that may eval to something similar to the original object.
432 */
433 static bool
434 exn_toSource(JSContext *cx, unsigned argc, Value *vp)
435 {
436 JS_CHECK_RECURSION(cx, return false);
437 CallArgs args = CallArgsFromVp(argc, vp);
438
439 RootedObject obj(cx, ToObject(cx, args.thisv()));
440 if (!obj)
441 return false;
442
443 RootedValue nameVal(cx);
444 RootedString name(cx);
445 if (!JSObject::getProperty(cx, obj, obj, cx->names().name, &nameVal) ||
446 !(name = ToString<CanGC>(cx, nameVal)))
447 {
448 return false;
449 }
450
451 RootedValue messageVal(cx);
452 RootedString message(cx);
453 if (!JSObject::getProperty(cx, obj, obj, cx->names().message, &messageVal) ||
454 !(message = ValueToSource(cx, messageVal)))
455 {
456 return false;
457 }
458
459 RootedValue filenameVal(cx);
460 RootedString filename(cx);
461 if (!JSObject::getProperty(cx, obj, obj, cx->names().fileName, &filenameVal) ||
462 !(filename = ValueToSource(cx, filenameVal)))
463 {
464 return false;
465 }
466
467 RootedValue linenoVal(cx);
468 uint32_t lineno;
469 if (!JSObject::getProperty(cx, obj, obj, cx->names().lineNumber, &linenoVal) ||
470 !ToUint32(cx, linenoVal, &lineno))
471 {
472 return false;
473 }
474
475 StringBuffer sb(cx);
476 if (!sb.append("(new ") || !sb.append(name) || !sb.append("("))
477 return false;
478
479 if (!sb.append(message))
480 return false;
481
482 if (!filename->empty()) {
483 if (!sb.append(", ") || !sb.append(filename))
484 return false;
485 }
486 if (lineno != 0) {
487 /* We have a line, but no filename, add empty string */
488 if (filename->empty() && !sb.append(", \"\""))
489 return false;
490
491 JSString *linenumber = ToString<CanGC>(cx, linenoVal);
492 if (!linenumber)
493 return false;
494 if (!sb.append(", ") || !sb.append(linenumber))
495 return false;
496 }
497
498 if (!sb.append("))"))
499 return false;
500
501 JSString *str = sb.finishString();
502 if (!str)
503 return false;
504 args.rval().setString(str);
505 return true;
506 }
507 #endif
508
509 static const JSFunctionSpec exception_methods[] = {
510 #if JS_HAS_TOSOURCE
511 JS_FN(js_toSource_str, exn_toSource, 0,0),
512 #endif
513 JS_FN(js_toString_str, exn_toString, 0,0),
514 JS_FS_END
515 };
516
517 /* JSProto_ ordering for exceptions shall match JSEXN_ constants. */
518 JS_STATIC_ASSERT(JSEXN_ERR == 0);
519 JS_STATIC_ASSERT(JSProto_Error + JSEXN_INTERNALERR == JSProto_InternalError);
520 JS_STATIC_ASSERT(JSProto_Error + JSEXN_EVALERR == JSProto_EvalError);
521 JS_STATIC_ASSERT(JSProto_Error + JSEXN_RANGEERR == JSProto_RangeError);
522 JS_STATIC_ASSERT(JSProto_Error + JSEXN_REFERENCEERR == JSProto_ReferenceError);
523 JS_STATIC_ASSERT(JSProto_Error + JSEXN_SYNTAXERR == JSProto_SyntaxError);
524 JS_STATIC_ASSERT(JSProto_Error + JSEXN_TYPEERR == JSProto_TypeError);
525 JS_STATIC_ASSERT(JSProto_Error + JSEXN_URIERR == JSProto_URIError);
526
527 /* static */ ErrorObject *
528 ErrorObject::createProto(JSContext *cx, JS::Handle<GlobalObject*> global, JSExnType type,
529 JS::HandleObject proto)
530 {
531 RootedObject errorProto(cx);
532 errorProto = global->createBlankPrototypeInheriting(cx, &ErrorObject::class_, *proto);
533 if (!errorProto)
534 return nullptr;
535
536 Rooted<ErrorObject*> err(cx, &errorProto->as<ErrorObject>());
537 RootedString emptyStr(cx, cx->names().empty);
538 if (!ErrorObject::init(cx, err, type, nullptr, emptyStr, emptyStr, 0, 0, emptyStr))
539 return nullptr;
540
541 // The various prototypes also have .name in addition to the normal error
542 // instance properties.
543 JSProtoKey key = GetExceptionProtoKey(type);
544 RootedPropertyName name(cx, ClassName(key, cx));
545 RootedValue nameValue(cx, StringValue(name));
546 if (!JSObject::defineProperty(cx, err, cx->names().name, nameValue,
547 JS_PropertyStub, JS_StrictPropertyStub, 0))
548 {
549 return nullptr;
550 }
551
552 // Create the corresponding constructor.
553 RootedFunction ctor(cx, global->createConstructor(cx, Error, name, 1,
554 JSFunction::ExtendedFinalizeKind));
555 if (!ctor)
556 return nullptr;
557 ctor->setExtendedSlot(0, Int32Value(int32_t(type)));
558
559 if (!LinkConstructorAndPrototype(cx, ctor, err))
560 return nullptr;
561
562 if (!GlobalObject::initBuiltinConstructor(cx, global, key, ctor, err))
563 return nullptr;
564
565 return err;
566 }
567
568 JSObject *
569 js_InitExceptionClasses(JSContext *cx, HandleObject obj)
570 {
571 JS_ASSERT(obj->is<GlobalObject>());
572 JS_ASSERT(obj->isNative());
573
574 Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
575
576 RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
577 if (!objProto)
578 return nullptr;
579
580 /* Initialize the base Error class first. */
581 RootedObject errorProto(cx, ErrorObject::createProto(cx, global, JSEXN_ERR, objProto));
582 if (!errorProto)
583 return nullptr;
584
585 /* |Error.prototype| alone has method properties. */
586 if (!DefinePropertiesAndBrand(cx, errorProto, nullptr, exception_methods))
587 return nullptr;
588
589 /* Define all remaining *Error constructors. */
590 for (int i = JSEXN_ERR + 1; i < JSEXN_LIMIT; i++) {
591 if (!ErrorObject::createProto(cx, global, JSExnType(i), errorProto))
592 return nullptr;
593 }
594
595 return errorProto;
596 }
597
598 const JSErrorFormatString*
599 js_GetLocalizedErrorMessage(ExclusiveContext *cx, void *userRef, const char *locale,
600 const unsigned errorNumber)
601 {
602 const JSErrorFormatString *errorString = nullptr;
603
604 // The locale callbacks might not be thread safe, so don't call them if
605 // we're not on the main thread. When used with XPConnect,
606 // |localeGetErrorMessage| will be nullptr anyways.
607 if (cx->isJSContext() &&
608 cx->asJSContext()->runtime()->localeCallbacks &&
609 cx->asJSContext()->runtime()->localeCallbacks->localeGetErrorMessage)
610 {
611 JSLocaleCallbacks *callbacks = cx->asJSContext()->runtime()->localeCallbacks;
612 errorString = callbacks->localeGetErrorMessage(userRef, locale, errorNumber);
613 }
614
615 if (!errorString)
616 errorString = js_GetErrorMessage(userRef, locale, errorNumber);
617 return errorString;
618 }
619
620 JS_FRIEND_API(const jschar*)
621 js::GetErrorTypeName(JSRuntime* rt, int16_t exnType)
622 {
623 /*
624 * JSEXN_INTERNALERR returns null to prevent that "InternalError: "
625 * is prepended before "uncaught exception: "
626 */
627 if (exnType <= JSEXN_NONE || exnType >= JSEXN_LIMIT ||
628 exnType == JSEXN_INTERNALERR)
629 {
630 return nullptr;
631 }
632 JSProtoKey key = GetExceptionProtoKey(JSExnType(exnType));
633 return ClassName(key, rt)->chars();
634 }
635
636 bool
637 js_ErrorToException(JSContext *cx, const char *message, JSErrorReport *reportp,
638 JSErrorCallback callback, void *userRef)
639 {
640 // Tell our caller to report immediately if this report is just a warning.
641 JS_ASSERT(reportp);
642 if (JSREPORT_IS_WARNING(reportp->flags))
643 return false;
644
645 // Find the exception index associated with this error.
646 JSErrNum errorNumber = static_cast<JSErrNum>(reportp->errorNumber);
647 const JSErrorFormatString *errorString;
648 if (!callback || callback == js_GetErrorMessage)
649 errorString = js_GetLocalizedErrorMessage(cx, nullptr, nullptr, errorNumber);
650 else
651 errorString = callback(userRef, nullptr, errorNumber);
652 JSExnType exnType = errorString ? static_cast<JSExnType>(errorString->exnType) : JSEXN_NONE;
653 MOZ_ASSERT(exnType < JSEXN_LIMIT);
654
655 // Return false (no exception raised) if no exception is associated
656 // with the given error number.
657 if (exnType == JSEXN_NONE)
658 return false;
659
660 // Prevent infinite recursion.
661 if (cx->generatingError)
662 return false;
663 AutoScopedAssign<bool> asa(&cx->generatingError, true);
664
665 // Create an exception object.
666 RootedString messageStr(cx, reportp->ucmessage ? JS_NewUCStringCopyZ(cx, reportp->ucmessage)
667 : JS_NewStringCopyZ(cx, message));
668 if (!messageStr)
669 return cx->isExceptionPending();
670
671 RootedString fileName(cx, JS_NewStringCopyZ(cx, reportp->filename));
672 if (!fileName)
673 return cx->isExceptionPending();
674
675 uint32_t lineNumber = reportp->lineno;
676 uint32_t columnNumber = reportp->column;
677
678 RootedString stack(cx, ComputeStackString(cx));
679 if (!stack)
680 return cx->isExceptionPending();
681
682 js::ScopedJSFreePtr<JSErrorReport> report(CopyErrorReport(cx, reportp));
683 if (!report)
684 return cx->isExceptionPending();
685
686 RootedObject errObject(cx, ErrorObject::create(cx, exnType, stack, fileName,
687 lineNumber, columnNumber, &report, messageStr));
688 if (!errObject)
689 return cx->isExceptionPending();
690
691 // Throw it.
692 RootedValue errValue(cx, ObjectValue(*errObject));
693 JS_SetPendingException(cx, errValue);
694
695 // Flag the error report passed in to indicate an exception was raised.
696 reportp->flags |= JSREPORT_EXCEPTION;
697 return true;
698 }
699
700 static bool
701 IsDuckTypedErrorObject(JSContext *cx, HandleObject exnObject, const char **filename_strp)
702 {
703 bool found;
704 if (!JS_HasProperty(cx, exnObject, js_message_str, &found) || !found)
705 return false;
706
707 const char *filename_str = *filename_strp;
708 if (!JS_HasProperty(cx, exnObject, filename_str, &found) || !found) {
709 /* DOMException duck quacks "filename" (all lowercase) */
710 filename_str = "filename";
711 if (!JS_HasProperty(cx, exnObject, filename_str, &found) || !found)
712 return false;
713 }
714
715 if (!JS_HasProperty(cx, exnObject, js_lineNumber_str, &found) || !found)
716 return false;
717
718 *filename_strp = filename_str;
719 return true;
720 }
721
722 JS_FRIEND_API(JSString *)
723 js::ErrorReportToString(JSContext *cx, JSErrorReport *reportp)
724 {
725 JSExnType type = static_cast<JSExnType>(reportp->exnType);
726 RootedString str(cx, cx->runtime()->emptyString);
727 if (type != JSEXN_NONE)
728 str = ClassName(GetExceptionProtoKey(type), cx);
729 RootedString toAppend(cx, JS_NewUCStringCopyN(cx, MOZ_UTF16(": "), 2));
730 if (!str || !toAppend)
731 return nullptr;
732 str = ConcatStrings<CanGC>(cx, str, toAppend);
733 if (!str)
734 return nullptr;
735 toAppend = JS_NewUCStringCopyZ(cx, reportp->ucmessage);
736 if (toAppend)
737 str = ConcatStrings<CanGC>(cx, str, toAppend);
738 return str;
739 }
740
741 bool
742 js_ReportUncaughtException(JSContext *cx)
743 {
744 if (!cx->isExceptionPending())
745 return true;
746
747 RootedValue exn(cx);
748 if (!cx->getPendingException(&exn))
749 return false;
750
751 /*
752 * Because ToString below could error and an exception object could become
753 * unrooted, we must root exnObject. Later, if exnObject is non-null, we
754 * need to root other intermediates, so allocate an operand stack segment
755 * to protect all of these values.
756 */
757 RootedObject exnObject(cx);
758 if (JSVAL_IS_PRIMITIVE(exn)) {
759 exnObject = nullptr;
760 } else {
761 exnObject = JSVAL_TO_OBJECT(exn);
762 }
763
764 JS_ClearPendingException(cx);
765 JSErrorReport *reportp = exnObject ? js_ErrorFromException(cx, exnObject)
766 : nullptr;
767
768 // Be careful not to invoke ToString if we've already successfully extracted
769 // an error report, since the exception might be wrapped in a security
770 // wrapper, and ToString-ing it might throw.
771 RootedString str(cx);
772 if (reportp)
773 str = ErrorReportToString(cx, reportp);
774 else
775 str = ToString<CanGC>(cx, exn);
776
777 JSErrorReport report;
778
779 // If js_ErrorFromException didn't get us a JSErrorReport, then the object
780 // was not an ErrorObject, security-wrapped or otherwise. However, it might
781 // still quack like one. Give duck-typing a chance.
782 const char *filename_str = js_fileName_str;
783 JSAutoByteString filename;
784 if (!reportp && exnObject && IsDuckTypedErrorObject(cx, exnObject, &filename_str))
785 {
786 // Temporary value for pulling properties off of duck-typed objects.
787 RootedValue val(cx);
788
789 RootedString name(cx);
790 if (JS_GetProperty(cx, exnObject, js_name_str, &val) && val.isString())
791 name = val.toString();
792
793 RootedString msg(cx);
794 if (JS_GetProperty(cx, exnObject, js_message_str, &val) && val.isString())
795 msg = val.toString();
796
797 // If we have the right fields, override the ToString we performed on
798 // the exception object above with something built out of its quacks
799 // (i.e. as much of |NameQuack: MessageQuack| as we can make).
800 //
801 // It would be nice to use ErrorReportToString here, but we can't quite
802 // do it - mostly because we'd need to figure out what JSExnType |name|
803 // corresponds to, which may not be any JSExnType at all.
804 if (name && msg) {
805 RootedString colon(cx, JS_NewStringCopyZ(cx, ": "));
806 if (!colon)
807 return false;
808 RootedString nameColon(cx, ConcatStrings<CanGC>(cx, name, colon));
809 if (!nameColon)
810 return false;
811 str = ConcatStrings<CanGC>(cx, nameColon, msg);
812 if (!str)
813 return false;
814 } else if (name) {
815 str = name;
816 } else if (msg) {
817 str = msg;
818 }
819
820 if (JS_GetProperty(cx, exnObject, filename_str, &val)) {
821 JSString *tmp = ToString<CanGC>(cx, val);
822 if (tmp)
823 filename.encodeLatin1(cx, tmp);
824 }
825
826 uint32_t lineno;
827 if (!JS_GetProperty(cx, exnObject, js_lineNumber_str, &val) ||
828 !ToUint32(cx, val, &lineno))
829 {
830 lineno = 0;
831 }
832
833 uint32_t column;
834 if (!JS_GetProperty(cx, exnObject, js_columnNumber_str, &val) ||
835 !ToUint32(cx, val, &column))
836 {
837 column = 0;
838 }
839
840 reportp = &report;
841 PodZero(&report);
842 report.filename = filename.ptr();
843 report.lineno = (unsigned) lineno;
844 report.exnType = int16_t(JSEXN_NONE);
845 report.column = (unsigned) column;
846 if (str) {
847 // Note that using |str| for |ucmessage| here is kind of wrong,
848 // because |str| is supposed to be of the format
849 // |ErrorName: ErrorMessage|, and |ucmessage| is supposed to
850 // correspond to |ErrorMessage|. But this is what we've historically
851 // done for duck-typed error objects.
852 //
853 // If only this stuff could get specced one day...
854 if (JSFlatString *flat = str->ensureFlat(cx))
855 report.ucmessage = flat->chars();
856 }
857 }
858
859 JSAutoByteString bytesStorage;
860 const char *bytes = nullptr;
861 if (str)
862 bytes = bytesStorage.encodeLatin1(cx, str);
863 if (!bytes)
864 bytes = "unknown (can't convert to string)";
865
866 if (!reportp) {
867 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
868 JSMSG_UNCAUGHT_EXCEPTION, bytes);
869 } else {
870 /* Flag the error as an exception. */
871 reportp->flags |= JSREPORT_EXCEPTION;
872
873 /* Pass the exception object. */
874 JS_SetPendingException(cx, exn);
875 CallErrorReporter(cx, bytes, reportp);
876 }
877
878 JS_ClearPendingException(cx);
879 return true;
880 }
881
882 JSObject *
883 js_CopyErrorObject(JSContext *cx, Handle<ErrorObject*> err, HandleObject scope)
884 {
885 assertSameCompartment(cx, scope);
886
887 js::ScopedJSFreePtr<JSErrorReport> copyReport;
888 if (JSErrorReport *errorReport = err->getErrorReport()) {
889 copyReport = CopyErrorReport(cx, errorReport);
890 if (!copyReport)
891 return nullptr;
892 }
893
894 RootedString message(cx, err->getMessage());
895 if (message && !cx->compartment()->wrap(cx, message.address()))
896 return nullptr;
897 RootedString fileName(cx, err->fileName(cx));
898 if (!cx->compartment()->wrap(cx, fileName.address()))
899 return nullptr;
900 RootedString stack(cx, err->stack(cx));
901 if (!cx->compartment()->wrap(cx, stack.address()))
902 return nullptr;
903 uint32_t lineNumber = err->lineNumber();
904 uint32_t columnNumber = err->columnNumber();
905 JSExnType errorType = err->type();
906
907 // Create the Error object.
908 return ErrorObject::create(cx, errorType, stack, fileName,
909 lineNumber, columnNumber, &copyReport, message);
910 }
911
912 JS_PUBLIC_API(bool)
913 JS::CreateTypeError(JSContext *cx, HandleString stack, HandleString fileName,
914 uint32_t lineNumber, uint32_t columnNumber, JSErrorReport *report,
915 HandleString message, MutableHandleValue rval)
916 {
917 assertSameCompartment(cx, stack, fileName, message);
918 js::ScopedJSFreePtr<JSErrorReport> rep;
919 if (report)
920 rep = CopyErrorReport(cx, report);
921
922 RootedObject obj(cx,
923 js::ErrorObject::create(cx, JSEXN_TYPEERR, stack, fileName,
924 lineNumber, columnNumber, &rep, message));
925 if (!obj)
926 return false;
927
928 rval.setObject(*obj);
929 return true;
930 }

mercurial