Bumping manifests a=b2g-bump
[gecko.git] / js / src / jsexn.cpp
blob35f1ddeb5ddfaf1e7946b91a828565bfebabe50e
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/. */
7 /*
8 * JS standard exception implementation.
9 */
11 #include "jsexn.h"
13 #include "mozilla/ArrayUtils.h"
14 #include "mozilla/PodOperations.h"
16 #include <string.h>
18 #include "jsapi.h"
19 #include "jscntxt.h"
20 #include "jsfun.h"
21 #include "jsnum.h"
22 #include "jsobj.h"
23 #include "jsprf.h"
24 #include "jsscript.h"
25 #include "jstypes.h"
26 #include "jsutil.h"
27 #include "jswrapper.h"
29 #include "gc/Marking.h"
30 #include "vm/ErrorObject.h"
31 #include "vm/GlobalObject.h"
32 #include "vm/StringBuffer.h"
34 #include "jsobjinlines.h"
36 #include "vm/ErrorObject-inl.h"
38 using namespace js;
39 using namespace js::gc;
40 using namespace js::types;
42 using mozilla::ArrayLength;
43 using mozilla::PodArrayZero;
45 static void
46 exn_finalize(FreeOp* fop, JSObject* obj);
48 bool
49 Error(JSContext* cx, unsigned argc, Value* vp);
51 static bool
52 exn_toSource(JSContext* cx, unsigned argc, Value* vp);
54 static const JSFunctionSpec exception_methods[] = {
55 #if JS_HAS_TOSOURCE
56 JS_FN(js_toSource_str, exn_toSource, 0, 0),
57 #endif
58 JS_SELF_HOSTED_FN(js_toString_str, "ErrorToString", 0,0),
59 JS_FS_END
62 #define IMPLEMENT_ERROR_SUBCLASS(name) \
63 { \
64 js_Error_str, /* yes, really */ \
65 JSCLASS_IMPLEMENTS_BARRIERS | \
66 JSCLASS_HAS_CACHED_PROTO(JSProto_##name) | \
67 JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS), \
68 nullptr, /* addProperty */ \
69 nullptr, /* delProperty */ \
70 nullptr, /* getProperty */ \
71 nullptr, /* setProperty */ \
72 nullptr, /* enumerate */ \
73 nullptr, /* resolve */ \
74 nullptr, /* convert */ \
75 exn_finalize, \
76 nullptr, /* call */ \
77 nullptr, /* hasInstance */ \
78 nullptr, /* construct */ \
79 nullptr, /* trace */ \
80 { \
81 ErrorObject::createConstructor, \
82 ErrorObject::createProto, \
83 nullptr, \
84 exception_methods, \
85 nullptr, \
86 nullptr, \
87 JSProto_Error \
88 } \
91 const Class
92 ErrorObject::classes[JSEXN_LIMIT] = {
94 js_Error_str,
95 JSCLASS_IMPLEMENTS_BARRIERS |
96 JSCLASS_HAS_CACHED_PROTO(JSProto_Error) |
97 JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS),
98 nullptr, /* addProperty */
99 nullptr, /* delProperty */
100 nullptr, /* getProperty */
101 nullptr, /* setProperty */
102 nullptr, /* enumerate */
103 nullptr, /* resolve */
104 nullptr, /* convert */
105 exn_finalize,
106 nullptr, /* call */
107 nullptr, /* hasInstance */
108 nullptr, /* construct */
109 nullptr, /* trace */
111 ErrorObject::createConstructor,
112 ErrorObject::createProto,
113 nullptr,
114 exception_methods,
118 IMPLEMENT_ERROR_SUBCLASS(InternalError),
119 IMPLEMENT_ERROR_SUBCLASS(EvalError),
120 IMPLEMENT_ERROR_SUBCLASS(RangeError),
121 IMPLEMENT_ERROR_SUBCLASS(ReferenceError),
122 IMPLEMENT_ERROR_SUBCLASS(SyntaxError),
123 IMPLEMENT_ERROR_SUBCLASS(TypeError),
124 IMPLEMENT_ERROR_SUBCLASS(URIError)
127 JSErrorReport*
128 js::CopyErrorReport(JSContext* cx, JSErrorReport* report)
131 * We use a single malloc block to make a deep copy of JSErrorReport with
132 * the following layout:
133 * JSErrorReport
134 * array of copies of report->messageArgs
135 * char16_t array with characters for all messageArgs
136 * char16_t array with characters for ucmessage
137 * char16_t array with characters for uclinebuf and uctokenptr
138 * char array with characters for linebuf and tokenptr
139 * char array with characters for filename
140 * Such layout together with the properties enforced by the following
141 * asserts does not need any extra alignment padding.
143 JS_STATIC_ASSERT(sizeof(JSErrorReport) % sizeof(const char*) == 0);
144 JS_STATIC_ASSERT(sizeof(const char*) % sizeof(char16_t) == 0);
146 size_t filenameSize;
147 size_t linebufSize;
148 size_t uclinebufSize;
149 size_t ucmessageSize;
150 size_t i, argsArraySize, argsCopySize, argSize;
151 size_t mallocSize;
152 JSErrorReport* copy;
153 uint8_t* cursor;
155 #define JS_CHARS_SIZE(chars) ((js_strlen(chars) + 1) * sizeof(char16_t))
157 filenameSize = report->filename ? strlen(report->filename) + 1 : 0;
158 linebufSize = report->linebuf ? strlen(report->linebuf) + 1 : 0;
159 uclinebufSize = report->uclinebuf ? JS_CHARS_SIZE(report->uclinebuf) : 0;
160 ucmessageSize = 0;
161 argsArraySize = 0;
162 argsCopySize = 0;
163 if (report->ucmessage) {
164 ucmessageSize = JS_CHARS_SIZE(report->ucmessage);
165 if (report->messageArgs) {
166 for (i = 0; report->messageArgs[i]; ++i)
167 argsCopySize += JS_CHARS_SIZE(report->messageArgs[i]);
169 /* Non-null messageArgs should have at least one non-null arg. */
170 MOZ_ASSERT(i != 0);
171 argsArraySize = (i + 1) * sizeof(const char16_t*);
176 * The mallocSize can not overflow since it represents the sum of the
177 * sizes of already allocated objects.
179 mallocSize = sizeof(JSErrorReport) + argsArraySize + argsCopySize +
180 ucmessageSize + uclinebufSize + linebufSize + filenameSize;
181 cursor = cx->pod_malloc<uint8_t>(mallocSize);
182 if (!cursor)
183 return nullptr;
185 copy = (JSErrorReport*)cursor;
186 memset(cursor, 0, sizeof(JSErrorReport));
187 cursor += sizeof(JSErrorReport);
189 if (argsArraySize != 0) {
190 copy->messageArgs = (const char16_t**)cursor;
191 cursor += argsArraySize;
192 for (i = 0; report->messageArgs[i]; ++i) {
193 copy->messageArgs[i] = (const char16_t*)cursor;
194 argSize = JS_CHARS_SIZE(report->messageArgs[i]);
195 js_memcpy(cursor, report->messageArgs[i], argSize);
196 cursor += argSize;
198 copy->messageArgs[i] = nullptr;
199 MOZ_ASSERT(cursor == (uint8_t*)copy->messageArgs[0] + argsCopySize);
202 if (report->ucmessage) {
203 copy->ucmessage = (const char16_t*)cursor;
204 js_memcpy(cursor, report->ucmessage, ucmessageSize);
205 cursor += ucmessageSize;
208 if (report->uclinebuf) {
209 copy->uclinebuf = (const char16_t*)cursor;
210 js_memcpy(cursor, report->uclinebuf, uclinebufSize);
211 cursor += uclinebufSize;
212 if (report->uctokenptr) {
213 copy->uctokenptr = copy->uclinebuf + (report->uctokenptr -
214 report->uclinebuf);
218 if (report->linebuf) {
219 copy->linebuf = (const char*)cursor;
220 js_memcpy(cursor, report->linebuf, linebufSize);
221 cursor += linebufSize;
222 if (report->tokenptr) {
223 copy->tokenptr = copy->linebuf + (report->tokenptr -
224 report->linebuf);
228 if (report->filename) {
229 copy->filename = (const char*)cursor;
230 js_memcpy(cursor, report->filename, filenameSize);
232 MOZ_ASSERT(cursor + filenameSize == (uint8_t*)copy + mallocSize);
234 /* Copy non-pointer members. */
235 copy->isMuted = report->isMuted;
236 copy->lineno = report->lineno;
237 copy->column = report->column;
238 copy->errorNumber = report->errorNumber;
239 copy->exnType = report->exnType;
241 /* Note that this is before it gets flagged with JSREPORT_EXCEPTION */
242 copy->flags = report->flags;
244 #undef JS_CHARS_SIZE
245 return copy;
248 struct SuppressErrorsGuard
250 JSContext* cx;
251 JSErrorReporter prevReporter;
252 JS::AutoSaveExceptionState prevState;
254 explicit SuppressErrorsGuard(JSContext* cx)
255 : cx(cx),
256 prevReporter(JS_SetErrorReporter(cx->runtime(), nullptr)),
257 prevState(cx)
260 ~SuppressErrorsGuard()
262 JS_SetErrorReporter(cx->runtime(), prevReporter);
266 JSString*
267 js::ComputeStackString(JSContext* cx)
269 StringBuffer sb(cx);
272 RootedAtom atom(cx);
273 SuppressErrorsGuard seg(cx);
274 for (NonBuiltinFrameIter i(cx, FrameIter::ALL_CONTEXTS, FrameIter::GO_THROUGH_SAVED,
275 cx->compartment()->principals);
276 !i.done();
277 ++i)
279 /* First append the function name, if any. */
280 if (i.isNonEvalFunctionFrame())
281 atom = i.functionDisplayAtom();
282 else
283 atom = nullptr;
284 if (atom && !sb.append(atom))
285 return nullptr;
287 /* Next a @ separating function name from source location. */
288 if (!sb.append('@'))
289 return nullptr;
291 /* Now the filename. */
293 /* First, try the `//# sourceURL=some-display-url.js` directive. */
294 if (const char16_t* display = i.scriptDisplayURL()) {
295 if (!sb.append(display, js_strlen(display)))
296 return nullptr;
298 /* Second, try the actual filename. */
299 else if (const char* filename = i.scriptFilename()) {
300 if (!sb.append(filename, strlen(filename)))
301 return nullptr;
304 uint32_t column = 0;
305 uint32_t line = i.computeLine(&column);
306 // Now the line number
307 if (!sb.append(':') || !NumberValueToStringBuffer(cx, NumberValue(line), sb))
308 return nullptr;
310 // Finally, : followed by the column number (1-based, as in other browsers)
311 // and a newline.
312 if (!sb.append(':') || !NumberValueToStringBuffer(cx, NumberValue(column + 1), sb) ||
313 !sb.append('\n'))
315 return nullptr;
319 * Cut off the stack if it gets too deep (most commonly for
320 * infinite recursion errors).
322 const size_t MaxReportedStackDepth = 1u << 20;
323 if (sb.length() > MaxReportedStackDepth)
324 break;
328 return sb.finishString();
331 static void
332 exn_finalize(FreeOp* fop, JSObject* obj)
334 if (JSErrorReport* report = obj->as<ErrorObject>().getErrorReport())
335 fop->free_(report);
338 JSErrorReport*
339 js_ErrorFromException(JSContext* cx, HandleObject objArg)
341 // It's ok to UncheckedUnwrap here, since all we do is get the
342 // JSErrorReport, and consumers are careful with the information they get
343 // from that anyway. Anyone doing things that would expose anything in the
344 // JSErrorReport to page script either does a security check on the
345 // JSErrorReport's principal or also tries to do toString on our object and
346 // will fail if they can't unwrap it.
347 RootedObject obj(cx, UncheckedUnwrap(objArg));
348 if (!obj->is<ErrorObject>())
349 return nullptr;
351 return obj->as<ErrorObject>().getOrCreateErrorReport(cx);
354 bool
355 Error(JSContext* cx, unsigned argc, Value* vp)
357 CallArgs args = CallArgsFromVp(argc, vp);
359 /* Compute the error message, if any. */
360 RootedString message(cx, nullptr);
361 if (args.hasDefined(0)) {
362 message = ToString<CanGC>(cx, args[0]);
363 if (!message)
364 return false;
367 /* Find the scripted caller. */
368 NonBuiltinFrameIter iter(cx);
370 /* Set the 'fileName' property. */
371 RootedString fileName(cx);
372 if (args.length() > 1) {
373 fileName = ToString<CanGC>(cx, args[1]);
374 } else {
375 fileName = cx->runtime()->emptyString;
376 if (!iter.done()) {
377 if (const char* cfilename = iter.scriptFilename())
378 fileName = JS_NewStringCopyZ(cx, cfilename);
381 if (!fileName)
382 return false;
384 /* Set the 'lineNumber' property. */
385 uint32_t lineNumber, columnNumber = 0;
386 if (args.length() > 2) {
387 if (!ToUint32(cx, args[2], &lineNumber))
388 return false;
389 } else {
390 lineNumber = iter.done() ? 0 : iter.computeLine(&columnNumber);
393 Rooted<JSString*> stack(cx, ComputeStackString(cx));
394 if (!stack)
395 return false;
398 * ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when
399 * called as functions, without operator new. But as we do not give
400 * each constructor a distinct JSClass, we must get the exception type
401 * ourselves.
403 JSExnType exnType = JSExnType(args.callee().as<JSFunction>().getExtendedSlot(0).toInt32());
405 RootedObject obj(cx, ErrorObject::create(cx, exnType, stack, fileName,
406 lineNumber, columnNumber, nullptr, message));
407 if (!obj)
408 return false;
410 args.rval().setObject(*obj);
411 return true;
414 #if JS_HAS_TOSOURCE
416 * Return a string that may eval to something similar to the original object.
418 static bool
419 exn_toSource(JSContext* cx, unsigned argc, Value* vp)
421 JS_CHECK_RECURSION(cx, return false);
422 CallArgs args = CallArgsFromVp(argc, vp);
424 RootedObject obj(cx, ToObject(cx, args.thisv()));
425 if (!obj)
426 return false;
428 RootedValue nameVal(cx);
429 RootedString name(cx);
430 if (!JSObject::getProperty(cx, obj, obj, cx->names().name, &nameVal) ||
431 !(name = ToString<CanGC>(cx, nameVal)))
433 return false;
436 RootedValue messageVal(cx);
437 RootedString message(cx);
438 if (!JSObject::getProperty(cx, obj, obj, cx->names().message, &messageVal) ||
439 !(message = ValueToSource(cx, messageVal)))
441 return false;
444 RootedValue filenameVal(cx);
445 RootedString filename(cx);
446 if (!JSObject::getProperty(cx, obj, obj, cx->names().fileName, &filenameVal) ||
447 !(filename = ValueToSource(cx, filenameVal)))
449 return false;
452 RootedValue linenoVal(cx);
453 uint32_t lineno;
454 if (!JSObject::getProperty(cx, obj, obj, cx->names().lineNumber, &linenoVal) ||
455 !ToUint32(cx, linenoVal, &lineno))
457 return false;
460 StringBuffer sb(cx);
461 if (!sb.append("(new ") || !sb.append(name) || !sb.append("("))
462 return false;
464 if (!sb.append(message))
465 return false;
467 if (!filename->empty()) {
468 if (!sb.append(", ") || !sb.append(filename))
469 return false;
471 if (lineno != 0) {
472 /* We have a line, but no filename, add empty string */
473 if (filename->empty() && !sb.append(", \"\""))
474 return false;
476 JSString* linenumber = ToString<CanGC>(cx, linenoVal);
477 if (!linenumber)
478 return false;
479 if (!sb.append(", ") || !sb.append(linenumber))
480 return false;
483 if (!sb.append("))"))
484 return false;
486 JSString* str = sb.finishString();
487 if (!str)
488 return false;
489 args.rval().setString(str);
490 return true;
492 #endif
494 /* static */ JSObject*
495 ErrorObject::createProto(JSContext* cx, JSProtoKey key)
497 RootedObject errorProto(cx, GenericCreatePrototype(cx, key));
498 if (!errorProto)
499 return nullptr;
501 Rooted<ErrorObject*> err(cx, &errorProto->as<ErrorObject>());
502 RootedString emptyStr(cx, cx->names().empty);
503 JSExnType type = ExnTypeFromProtoKey(key);
504 if (!ErrorObject::init(cx, err, type, nullptr, emptyStr, emptyStr, 0, 0, emptyStr))
505 return nullptr;
507 // The various prototypes also have .name in addition to the normal error
508 // instance properties.
509 RootedPropertyName name(cx, ClassName(key, cx));
510 RootedValue nameValue(cx, StringValue(name));
511 if (!JSObject::defineProperty(cx, err, cx->names().name, nameValue, nullptr, nullptr, 0))
512 return nullptr;
514 return errorProto;
517 /* static */ JSObject*
518 ErrorObject::createConstructor(JSContext* cx, JSProtoKey key)
520 RootedObject ctor(cx);
521 ctor = GenericCreateConstructor<Error, 1, JSFunction::ExtendedFinalizeKind>(cx, key);
522 if (!ctor)
523 return nullptr;
525 ctor->as<JSFunction>().setExtendedSlot(0, Int32Value(ExnTypeFromProtoKey(key)));
526 return ctor;
529 JS_FRIEND_API(JSFlatString*)
530 js::GetErrorTypeName(JSRuntime* rt, int16_t exnType)
533 * JSEXN_INTERNALERR returns null to prevent that "InternalError: "
534 * is prepended before "uncaught exception: "
536 if (exnType <= JSEXN_NONE || exnType >= JSEXN_LIMIT ||
537 exnType == JSEXN_INTERNALERR)
539 return nullptr;
541 JSProtoKey key = GetExceptionProtoKey(JSExnType(exnType));
542 return ClassName(key, rt);
545 bool
546 js_ErrorToException(JSContext* cx, const char* message, JSErrorReport* reportp,
547 JSErrorCallback callback, void* userRef)
549 // Tell our caller to report immediately if this report is just a warning.
550 MOZ_ASSERT(reportp);
551 if (JSREPORT_IS_WARNING(reportp->flags))
552 return false;
554 // Find the exception index associated with this error.
555 JSErrNum errorNumber = static_cast<JSErrNum>(reportp->errorNumber);
556 if (!callback)
557 callback = js_GetErrorMessage;
558 const JSErrorFormatString* errorString = callback(userRef, errorNumber);
559 JSExnType exnType = errorString ? static_cast<JSExnType>(errorString->exnType) : JSEXN_NONE;
560 MOZ_ASSERT(exnType < JSEXN_LIMIT);
562 // Return false (no exception raised) if no exception is associated
563 // with the given error number.
564 if (exnType == JSEXN_NONE)
565 return false;
567 // Prevent infinite recursion.
568 if (cx->generatingError)
569 return false;
570 AutoScopedAssign<bool> asa(&cx->generatingError, true);
572 // Create an exception object.
573 RootedString messageStr(cx, reportp->ucmessage ? JS_NewUCStringCopyZ(cx, reportp->ucmessage)
574 : JS_NewStringCopyZ(cx, message));
575 if (!messageStr)
576 return cx->isExceptionPending();
578 RootedString fileName(cx, JS_NewStringCopyZ(cx, reportp->filename));
579 if (!fileName)
580 return cx->isExceptionPending();
582 uint32_t lineNumber = reportp->lineno;
583 uint32_t columnNumber = reportp->column;
585 RootedString stack(cx, ComputeStackString(cx));
586 if (!stack)
587 return cx->isExceptionPending();
589 js::ScopedJSFreePtr<JSErrorReport> report(CopyErrorReport(cx, reportp));
590 if (!report)
591 return cx->isExceptionPending();
593 RootedObject errObject(cx, ErrorObject::create(cx, exnType, stack, fileName,
594 lineNumber, columnNumber, &report, messageStr));
595 if (!errObject)
596 return cx->isExceptionPending();
598 // Throw it.
599 RootedValue errValue(cx, ObjectValue(*errObject));
600 JS_SetPendingException(cx, errValue);
602 // Flag the error report passed in to indicate an exception was raised.
603 reportp->flags |= JSREPORT_EXCEPTION;
604 return true;
607 static bool
608 IsDuckTypedErrorObject(JSContext* cx, HandleObject exnObject, const char** filename_strp)
610 bool found;
611 if (!JS_HasProperty(cx, exnObject, js_message_str, &found) || !found)
612 return false;
614 const char* filename_str = *filename_strp;
615 if (!JS_HasProperty(cx, exnObject, filename_str, &found) || !found) {
616 /* Now try "fileName", in case this quacks like an Error */
617 filename_str = js_fileName_str;
618 if (!JS_HasProperty(cx, exnObject, filename_str, &found) || !found)
619 return false;
622 if (!JS_HasProperty(cx, exnObject, js_lineNumber_str, &found) || !found)
623 return false;
625 *filename_strp = filename_str;
626 return true;
629 JS_FRIEND_API(JSString*)
630 js::ErrorReportToString(JSContext* cx, JSErrorReport* reportp)
632 JSExnType type = static_cast<JSExnType>(reportp->exnType);
633 RootedString str(cx, cx->runtime()->emptyString);
634 if (type != JSEXN_NONE)
635 str = ClassName(GetExceptionProtoKey(type), cx);
636 RootedString toAppend(cx, JS_NewUCStringCopyN(cx, MOZ_UTF16(": "), 2));
637 if (!str || !toAppend)
638 return nullptr;
639 str = ConcatStrings<CanGC>(cx, str, toAppend);
640 if (!str)
641 return nullptr;
642 toAppend = JS_NewUCStringCopyZ(cx, reportp->ucmessage);
643 if (toAppend)
644 str = ConcatStrings<CanGC>(cx, str, toAppend);
645 return str;
648 bool
649 js_ReportUncaughtException(JSContext* cx)
651 if (!cx->isExceptionPending())
652 return true;
654 RootedValue exn(cx);
655 if (!cx->getPendingException(&exn))
656 return false;
658 cx->clearPendingException();
660 ErrorReport err(cx);
661 if (!err.init(cx, exn)) {
662 cx->clearPendingException();
663 return false;
666 cx->setPendingException(exn);
667 CallErrorReporter(cx, err.message(), err.report());
668 cx->clearPendingException();
669 return true;
672 ErrorReport::ErrorReport(JSContext* cx)
673 : reportp(nullptr),
674 message_(nullptr),
675 ownedMessage(nullptr),
676 str(cx),
677 strChars(cx),
678 exnObject(cx)
682 ErrorReport::~ErrorReport()
684 if (!ownedMessage)
685 return;
687 js_free(ownedMessage);
688 if (ownedReport.messageArgs) {
690 * js_ExpandErrorArguments owns its messageArgs only if it had to
691 * inflate the arguments (from regular |char*|s), which is always in
692 * our case.
694 size_t i = 0;
695 while (ownedReport.messageArgs[i])
696 js_free(const_cast<char16_t*>(ownedReport.messageArgs[i++]));
697 js_free(ownedReport.messageArgs);
699 js_free(const_cast<char16_t*>(ownedReport.ucmessage));
702 bool
703 ErrorReport::init(JSContext* cx, HandleValue exn)
705 MOZ_ASSERT(!cx->isExceptionPending());
708 * Because ToString below could error and an exception object could become
709 * unrooted, we must root our exception object, if any.
711 if (exn.isObject()) {
712 exnObject = &exn.toObject();
713 reportp = js_ErrorFromException(cx, exnObject);
715 JSCompartment* comp = exnObject->compartment();
716 JSAddonId* addonId = comp->addonId;
717 if (addonId) {
718 UniqueChars addonIdChars(JS_EncodeString(cx, addonId));
720 const char* filename = nullptr;
722 if (reportp && reportp->filename) {
723 filename = strrchr(reportp->filename, '/');
724 if (filename)
725 filename++;
727 if (!filename) {
728 filename = "FILE_NOT_FOUND";
730 char histogramKey[64];
731 JS_snprintf(histogramKey, sizeof(histogramKey),
732 "%s %s %u",
733 addonIdChars.get(),
734 filename,
735 (reportp ? reportp->lineno : 0) );
736 cx->runtime()->addTelemetry(JS_TELEMETRY_ADDON_EXCEPTIONS, 1, histogramKey);
739 // Be careful not to invoke ToString if we've already successfully extracted
740 // an error report, since the exception might be wrapped in a security
741 // wrapper, and ToString-ing it might throw.
742 if (reportp)
743 str = ErrorReportToString(cx, reportp);
744 else
745 str = ToString<CanGC>(cx, exn);
747 if (!str)
748 cx->clearPendingException();
750 // If js_ErrorFromException didn't get us a JSErrorReport, then the object
751 // was not an ErrorObject, security-wrapped or otherwise. However, it might
752 // still quack like one. Give duck-typing a chance. We start by looking for
753 // "filename" (all lowercase), since that's where DOMExceptions store their
754 // filename. Then we check "fileName", which is where Errors store it. We
755 // have to do it in that order, because DOMExceptions have Error.prototype
756 // on their proto chain, and hence also have a "fileName" property, but its
757 // value is "".
758 const char* filename_str = "filename";
759 if (!reportp && exnObject && IsDuckTypedErrorObject(cx, exnObject, &filename_str))
761 // Temporary value for pulling properties off of duck-typed objects.
762 RootedValue val(cx);
764 RootedString name(cx);
765 if (JS_GetProperty(cx, exnObject, js_name_str, &val) && val.isString())
766 name = val.toString();
767 else
768 cx->clearPendingException();
770 RootedString msg(cx);
771 if (JS_GetProperty(cx, exnObject, js_message_str, &val) && val.isString())
772 msg = val.toString();
773 else
774 cx->clearPendingException();
776 // If we have the right fields, override the ToString we performed on
777 // the exception object above with something built out of its quacks
778 // (i.e. as much of |NameQuack: MessageQuack| as we can make).
780 // It would be nice to use ErrorReportToString here, but we can't quite
781 // do it - mostly because we'd need to figure out what JSExnType |name|
782 // corresponds to, which may not be any JSExnType at all.
783 if (name && msg) {
784 RootedString colon(cx, JS_NewStringCopyZ(cx, ": "));
785 if (!colon)
786 return false;
787 RootedString nameColon(cx, ConcatStrings<CanGC>(cx, name, colon));
788 if (!nameColon)
789 return false;
790 str = ConcatStrings<CanGC>(cx, nameColon, msg);
791 if (!str)
792 return false;
793 } else if (name) {
794 str = name;
795 } else if (msg) {
796 str = msg;
799 if (JS_GetProperty(cx, exnObject, filename_str, &val)) {
800 JSString* tmp = ToString<CanGC>(cx, val);
801 if (tmp)
802 filename.encodeLatin1(cx, tmp);
803 else
804 cx->clearPendingException();
805 } else {
806 cx->clearPendingException();
809 uint32_t lineno;
810 if (!JS_GetProperty(cx, exnObject, js_lineNumber_str, &val) ||
811 !ToUint32(cx, val, &lineno))
813 cx->clearPendingException();
814 lineno = 0;
817 uint32_t column;
818 if (!JS_GetProperty(cx, exnObject, js_columnNumber_str, &val) ||
819 !ToUint32(cx, val, &column))
821 cx->clearPendingException();
822 column = 0;
825 reportp = &ownedReport;
826 new (reportp) JSErrorReport();
827 ownedReport.filename = filename.ptr();
828 ownedReport.lineno = lineno;
829 ownedReport.exnType = int16_t(JSEXN_NONE);
830 ownedReport.column = column;
831 if (str) {
832 // Note that using |str| for |ucmessage| here is kind of wrong,
833 // because |str| is supposed to be of the format
834 // |ErrorName: ErrorMessage|, and |ucmessage| is supposed to
835 // correspond to |ErrorMessage|. But this is what we've historically
836 // done for duck-typed error objects.
838 // If only this stuff could get specced one day...
839 if (str->ensureFlat(cx) && strChars.initTwoByte(cx, str))
840 ownedReport.ucmessage = strChars.twoByteChars();
844 if (str)
845 message_ = bytesStorage.encodeLatin1(cx, str);
846 if (!message_)
847 message_ = "unknown (can't convert to string)";
849 if (!reportp) {
850 // This is basically an inlined version of
852 // JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
853 // JSMSG_UNCAUGHT_EXCEPTION, message_);
855 // but without the reporting bits. Instead it just puts all
856 // the stuff we care about in our ownedReport and message_.
857 populateUncaughtExceptionReport(cx, message_);
858 } else {
859 /* Flag the error as an exception. */
860 reportp->flags |= JSREPORT_EXCEPTION;
863 return true;
866 void
867 ErrorReport::populateUncaughtExceptionReport(JSContext* cx, ...)
869 va_list ap;
870 va_start(ap, cx);
871 populateUncaughtExceptionReportVA(cx, ap);
872 va_end(ap);
875 void
876 ErrorReport::populateUncaughtExceptionReportVA(JSContext* cx, va_list ap)
878 new (&ownedReport) JSErrorReport();
879 ownedReport.flags = JSREPORT_ERROR;
880 ownedReport.errorNumber = JSMSG_UNCAUGHT_EXCEPTION;
881 // XXXbz this assumes the stack we have right now is still
882 // related to our exception object. It would be better if we
883 // could accept a passed-in stack of some sort instead.
884 NonBuiltinFrameIter iter(cx);
885 if (!iter.done()) {
886 ownedReport.filename = iter.scriptFilename();
887 ownedReport.lineno = iter.computeLine(&ownedReport.column);
888 ownedReport.isMuted = iter.mutedErrors();
891 if (!js_ExpandErrorArguments(cx, js_GetErrorMessage, nullptr,
892 JSMSG_UNCAUGHT_EXCEPTION, &ownedMessage,
893 &ownedReport, ArgumentsAreASCII, ap)) {
894 return;
897 reportp = &ownedReport;
898 message_ = ownedMessage;
899 ownsMessageAndReport = true;
902 JSObject*
903 js_CopyErrorObject(JSContext* cx, Handle<ErrorObject*> err)
905 js::ScopedJSFreePtr<JSErrorReport> copyReport;
906 if (JSErrorReport* errorReport = err->getErrorReport()) {
907 copyReport = CopyErrorReport(cx, errorReport);
908 if (!copyReport)
909 return nullptr;
912 RootedString message(cx, err->getMessage());
913 if (message && !cx->compartment()->wrap(cx, &message))
914 return nullptr;
915 RootedString fileName(cx, err->fileName(cx));
916 if (!cx->compartment()->wrap(cx, &fileName))
917 return nullptr;
918 RootedString stack(cx, err->stack(cx));
919 if (!cx->compartment()->wrap(cx, &stack))
920 return nullptr;
921 uint32_t lineNumber = err->lineNumber();
922 uint32_t columnNumber = err->columnNumber();
923 JSExnType errorType = err->type();
925 // Create the Error object.
926 return ErrorObject::create(cx, errorType, stack, fileName,
927 lineNumber, columnNumber, &copyReport, message);
930 JS_PUBLIC_API(bool)
931 JS::CreateError(JSContext* cx, JSExnType type, HandleString stack, HandleString fileName,
932 uint32_t lineNumber, uint32_t columnNumber, JSErrorReport* report,
933 HandleString message, MutableHandleValue rval)
935 assertSameCompartment(cx, stack, fileName, message);
936 js::ScopedJSFreePtr<JSErrorReport> rep;
937 if (report)
938 rep = CopyErrorReport(cx, report);
940 RootedObject obj(cx,
941 js::ErrorObject::create(cx, type, stack, fileName,
942 lineNumber, columnNumber, &rep, message));
943 if (!obj)
944 return false;
946 rval.setObject(*obj);
947 return true;