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 "jsprf.h"
24 #include "jsscript.h"
25 #include "jstypes.h"
26 #include "jsutil.h"
27 #include "jswrapper.h"
28
29 #include "gc/Marking.h"
30 #include "vm/ErrorObject.h"
31 #include "vm/GlobalObject.h"
32 #include "vm/SavedStacks.h"
33 #include "vm/StringBuffer.h"
34
35 #include "jsobjinlines.h"
36
37 #include "vm/ErrorObject-inl.h"
38 #include "vm/SavedStacks-inl.h"
39
40 using namespace js;
41 using namespace js::gc;
42
43 using mozilla::ArrayLength;
44 using mozilla::PodArrayZero;
45
46 static void
47 exn_finalize(FreeOp* fop, JSObject* obj);
48
49 bool
50 Error(JSContext* cx, unsigned argc, Value* vp);
51
52 static bool
53 exn_toSource(JSContext* cx, unsigned argc, Value* vp);
54
55 static const JSPropertySpec exception_properties[] = {
56 JS_PSGS("stack", ErrorObject::getStack, ErrorObject::setStack, 0),
57 JS_PS_END
58 };
59
60 static const JSFunctionSpec exception_methods[] = {
61 #if JS_HAS_TOSOURCE
62 JS_FN(js_toSource_str, exn_toSource, 0, 0),
63 #endif
64 JS_SELF_HOSTED_FN(js_toString_str, "ErrorToString", 0,0),
65 JS_FS_END
66 };
67
68 #define IMPLEMENT_ERROR_SUBCLASS(name) \
69 { \
70 js_Error_str, /* yes, really */ \
71 JSCLASS_HAS_CACHED_PROTO(JSProto_##name) | \
72 JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS), \
73 nullptr, /* addProperty */ \
74 nullptr, /* delProperty */ \
75 nullptr, /* getProperty */ \
76 nullptr, /* setProperty */ \
77 nullptr, /* enumerate */ \
78 nullptr, /* resolve */ \
79 nullptr, /* mayResolve */ \
80 exn_finalize, \
81 nullptr, /* call */ \
82 nullptr, /* hasInstance */ \
83 nullptr, /* construct */ \
84 nullptr, /* trace */ \
85 { \
86 ErrorObject::createConstructor, \
87 ErrorObject::createProto, \
88 nullptr, \
89 nullptr, \
90 exception_methods, \
91 exception_properties, \
92 nullptr, \
93 JSProto_Error \
94 } \
95 }
96
97 const Class
98 ErrorObject::classes[JSEXN_LIMIT] = {
99 {
100 js_Error_str,
101 JSCLASS_HAS_CACHED_PROTO(JSProto_Error) |
102 JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS),
103 nullptr, /* addProperty */
104 nullptr, /* delProperty */
105 nullptr, /* getProperty */
106 nullptr, /* setProperty */
107 nullptr, /* enumerate */
108 nullptr, /* resolve */
109 nullptr, /* mayResolve */
110 exn_finalize,
111 nullptr, /* call */
112 nullptr, /* hasInstance */
113 nullptr, /* construct */
114 nullptr, /* trace */
115 {
116 ErrorObject::createConstructor,
117 ErrorObject::createProto,
118 nullptr,
119 nullptr,
120 exception_methods,
121 exception_properties,
122 nullptr
123 }
124 },
125 IMPLEMENT_ERROR_SUBCLASS(InternalError),
126 IMPLEMENT_ERROR_SUBCLASS(EvalError),
127 IMPLEMENT_ERROR_SUBCLASS(RangeError),
128 IMPLEMENT_ERROR_SUBCLASS(ReferenceError),
129 IMPLEMENT_ERROR_SUBCLASS(SyntaxError),
130 IMPLEMENT_ERROR_SUBCLASS(TypeError),
131 IMPLEMENT_ERROR_SUBCLASS(URIError)
132 };
133
134 JSErrorReport*
CopyErrorReport(JSContext * cx,JSErrorReport * report)135 js::CopyErrorReport(JSContext* cx, JSErrorReport* report)
136 {
137 /*
138 * We use a single malloc block to make a deep copy of JSErrorReport with
139 * the following layout:
140 * JSErrorReport
141 * array of copies of report->messageArgs
142 * char16_t array with characters for all messageArgs
143 * char16_t array with characters for ucmessage
144 * char16_t array with characters for linebuf
145 * char array with characters for filename
146 * Such layout together with the properties enforced by the following
147 * asserts does not need any extra alignment padding.
148 */
149 JS_STATIC_ASSERT(sizeof(JSErrorReport) % sizeof(const char*) == 0);
150 JS_STATIC_ASSERT(sizeof(const char*) % sizeof(char16_t) == 0);
151
152 #define JS_CHARS_SIZE(chars) ((js_strlen(chars) + 1) * sizeof(char16_t))
153
154 size_t filenameSize = report->filename ? strlen(report->filename) + 1 : 0;
155 size_t linebufSize = 0;
156 if (report->linebuf())
157 linebufSize = (report->linebufLength() + 1) * sizeof(char16_t);
158 size_t ucmessageSize = 0;
159 size_t argsArraySize = 0;
160 size_t argsCopySize = 0;
161 if (report->ucmessage) {
162 ucmessageSize = JS_CHARS_SIZE(report->ucmessage);
163 if (report->messageArgs) {
164 size_t i = 0;
165 for (; report->messageArgs[i]; ++i)
166 argsCopySize += JS_CHARS_SIZE(report->messageArgs[i]);
167
168 /* Non-null messageArgs should have at least one non-null arg. */
169 MOZ_ASSERT(i != 0);
170 argsArraySize = (i + 1) * sizeof(const char16_t*);
171 }
172 }
173
174 /*
175 * The mallocSize can not overflow since it represents the sum of the
176 * sizes of already allocated objects.
177 */
178 size_t mallocSize = sizeof(JSErrorReport) + argsArraySize + argsCopySize +
179 ucmessageSize + linebufSize + filenameSize;
180 uint8_t* cursor = cx->pod_calloc<uint8_t>(mallocSize);
181 if (!cursor)
182 return nullptr;
183
184 JSErrorReport* copy = (JSErrorReport*)cursor;
185 cursor += sizeof(JSErrorReport);
186
187 if (argsArraySize != 0) {
188 copy->messageArgs = (const char16_t**)cursor;
189 cursor += argsArraySize;
190 size_t i = 0;
191 for (; report->messageArgs[i]; ++i) {
192 copy->messageArgs[i] = (const char16_t*)cursor;
193 size_t argSize = JS_CHARS_SIZE(report->messageArgs[i]);
194 js_memcpy(cursor, report->messageArgs[i], argSize);
195 cursor += argSize;
196 }
197 copy->messageArgs[i] = nullptr;
198 MOZ_ASSERT(cursor == (uint8_t*)copy->messageArgs[0] + argsCopySize);
199 }
200
201 if (report->ucmessage) {
202 copy->ucmessage = (const char16_t*)cursor;
203 js_memcpy(cursor, report->ucmessage, ucmessageSize);
204 cursor += ucmessageSize;
205 }
206
207 if (report->linebuf()) {
208 const char16_t* linebufCopy = (const char16_t*)cursor;
209 js_memcpy(cursor, report->linebuf(), linebufSize);
210 cursor += linebufSize;
211 copy->initLinebuf(linebufCopy, report->linebufLength(), report->tokenOffset());
212 }
213
214 if (report->filename) {
215 copy->filename = (const char*)cursor;
216 js_memcpy(cursor, report->filename, filenameSize);
217 }
218 MOZ_ASSERT(cursor + filenameSize == (uint8_t*)copy + mallocSize);
219
220 /* Copy non-pointer members. */
221 copy->isMuted = report->isMuted;
222 copy->lineno = report->lineno;
223 copy->column = report->column;
224 copy->errorNumber = report->errorNumber;
225 copy->exnType = report->exnType;
226
227 /* Note that this is before it gets flagged with JSREPORT_EXCEPTION */
228 copy->flags = report->flags;
229
230 #undef JS_CHARS_SIZE
231 return copy;
232 }
233
234 struct SuppressErrorsGuard
235 {
236 JSContext* cx;
237 JSErrorReporter prevReporter;
238 JS::AutoSaveExceptionState prevState;
239
SuppressErrorsGuardSuppressErrorsGuard240 explicit SuppressErrorsGuard(JSContext* cx)
241 : cx(cx),
242 prevReporter(JS_SetErrorReporter(cx->runtime(), nullptr)),
243 prevState(cx)
244 {}
245
~SuppressErrorsGuardSuppressErrorsGuard246 ~SuppressErrorsGuard()
247 {
248 JS_SetErrorReporter(cx->runtime(), prevReporter);
249 }
250 };
251
252 // Cut off the stack if it gets too deep (most commonly for infinite recursion
253 // errors).
254 static const size_t MAX_REPORTED_STACK_DEPTH = 1u << 7;
255
256 static bool
CaptureStack(JSContext * cx,MutableHandleObject stack)257 CaptureStack(JSContext* cx, MutableHandleObject stack)
258 {
259 return CaptureCurrentStack(cx, stack, MAX_REPORTED_STACK_DEPTH);
260 }
261
262 JSString*
ComputeStackString(JSContext * cx)263 js::ComputeStackString(JSContext* cx)
264 {
265 SuppressErrorsGuard seg(cx);
266
267 RootedObject stack(cx);
268 if (!CaptureStack(cx, &stack))
269 return nullptr;
270
271 RootedString str(cx);
272 if (!BuildStackString(cx, stack, &str))
273 return nullptr;
274
275 return str.get();
276 }
277
278 static void
exn_finalize(FreeOp * fop,JSObject * obj)279 exn_finalize(FreeOp* fop, JSObject* obj)
280 {
281 if (JSErrorReport* report = obj->as<ErrorObject>().getErrorReport())
282 fop->free_(report);
283 }
284
285 JSErrorReport*
ErrorFromException(JSContext * cx,HandleObject objArg)286 js::ErrorFromException(JSContext* cx, HandleObject objArg)
287 {
288 // It's ok to UncheckedUnwrap here, since all we do is get the
289 // JSErrorReport, and consumers are careful with the information they get
290 // from that anyway. Anyone doing things that would expose anything in the
291 // JSErrorReport to page script either does a security check on the
292 // JSErrorReport's principal or also tries to do toString on our object and
293 // will fail if they can't unwrap it.
294 RootedObject obj(cx, UncheckedUnwrap(objArg));
295 if (!obj->is<ErrorObject>())
296 return nullptr;
297
298 return obj->as<ErrorObject>().getOrCreateErrorReport(cx);
299 }
300
301 JS_PUBLIC_API(JSObject*)
ExceptionStackOrNull(JSContext * cx,HandleObject objArg)302 ExceptionStackOrNull(JSContext* cx, HandleObject objArg)
303 {
304 AssertHeapIsIdle(cx);
305 CHECK_REQUEST(cx);
306 assertSameCompartment(cx, objArg);
307 RootedObject obj(cx, CheckedUnwrap(objArg));
308 if (!obj || !obj->is<ErrorObject>()) {
309 return nullptr;
310 }
311
312 return obj->as<ErrorObject>().stack();
313 }
314
315 bool
Error(JSContext * cx,unsigned argc,Value * vp)316 Error(JSContext* cx, unsigned argc, Value* vp)
317 {
318 CallArgs args = CallArgsFromVp(argc, vp);
319
320 // ES6 19.5.1.1 mandates the .prototype lookup happens before the toString
321 RootedObject proto(cx);
322 if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
323 return false;
324
325 /* Compute the error message, if any. */
326 RootedString message(cx, nullptr);
327 if (args.hasDefined(0)) {
328 message = ToString<CanGC>(cx, args[0]);
329 if (!message)
330 return false;
331 }
332
333 /* Find the scripted caller, but only ones we're allowed to know about. */
334 NonBuiltinFrameIter iter(cx, cx->compartment()->principals());
335
336 /* Set the 'fileName' property. */
337 RootedString fileName(cx);
338 if (args.length() > 1) {
339 fileName = ToString<CanGC>(cx, args[1]);
340 } else {
341 fileName = cx->runtime()->emptyString;
342 if (!iter.done()) {
343 if (const char* cfilename = iter.scriptFilename())
344 fileName = JS_NewStringCopyZ(cx, cfilename);
345 }
346 }
347 if (!fileName)
348 return false;
349
350 /* Set the 'lineNumber' property. */
351 uint32_t lineNumber, columnNumber = 0;
352 if (args.length() > 2) {
353 if (!ToUint32(cx, args[2], &lineNumber))
354 return false;
355 } else {
356 lineNumber = iter.done() ? 0 : iter.computeLine(&columnNumber);
357 // XXX: Make the column 1-based as in other browsers, instead of 0-based
358 // which is how SpiderMonkey stores it internally. This will be
359 // unnecessary once bug 1144340 is fixed.
360 ++columnNumber;
361 }
362
363 RootedObject stack(cx);
364 if (!CaptureStack(cx, &stack))
365 return false;
366
367 /*
368 * ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when
369 * called as functions, without operator new. But as we do not give
370 * each constructor a distinct JSClass, we must get the exception type
371 * ourselves.
372 */
373 JSExnType exnType = JSExnType(args.callee().as<JSFunction>().getExtendedSlot(0).toInt32());
374
375 RootedObject obj(cx, ErrorObject::create(cx, exnType, stack, fileName,
376 lineNumber, columnNumber, nullptr, message, proto));
377 if (!obj)
378 return false;
379
380 args.rval().setObject(*obj);
381 return true;
382 }
383
384 #if JS_HAS_TOSOURCE
385 /*
386 * Return a string that may eval to something similar to the original object.
387 */
388 static bool
exn_toSource(JSContext * cx,unsigned argc,Value * vp)389 exn_toSource(JSContext* cx, unsigned argc, Value* vp)
390 {
391 JS_CHECK_RECURSION(cx, return false);
392 CallArgs args = CallArgsFromVp(argc, vp);
393
394 RootedObject obj(cx, ToObject(cx, args.thisv()));
395 if (!obj)
396 return false;
397
398 RootedValue nameVal(cx);
399 RootedString name(cx);
400 if (!GetProperty(cx, obj, obj, cx->names().name, &nameVal) ||
401 !(name = ToString<CanGC>(cx, nameVal)))
402 {
403 return false;
404 }
405
406 RootedValue messageVal(cx);
407 RootedString message(cx);
408 if (!GetProperty(cx, obj, obj, cx->names().message, &messageVal) ||
409 !(message = ValueToSource(cx, messageVal)))
410 {
411 return false;
412 }
413
414 RootedValue filenameVal(cx);
415 RootedString filename(cx);
416 if (!GetProperty(cx, obj, obj, cx->names().fileName, &filenameVal) ||
417 !(filename = ValueToSource(cx, filenameVal)))
418 {
419 return false;
420 }
421
422 RootedValue linenoVal(cx);
423 uint32_t lineno;
424 if (!GetProperty(cx, obj, obj, cx->names().lineNumber, &linenoVal) ||
425 !ToUint32(cx, linenoVal, &lineno))
426 {
427 return false;
428 }
429
430 StringBuffer sb(cx);
431 if (!sb.append("(new ") || !sb.append(name) || !sb.append("("))
432 return false;
433
434 if (!sb.append(message))
435 return false;
436
437 if (!filename->empty()) {
438 if (!sb.append(", ") || !sb.append(filename))
439 return false;
440 }
441 if (lineno != 0) {
442 /* We have a line, but no filename, add empty string */
443 if (filename->empty() && !sb.append(", \"\""))
444 return false;
445
446 JSString* linenumber = ToString<CanGC>(cx, linenoVal);
447 if (!linenumber)
448 return false;
449 if (!sb.append(", ") || !sb.append(linenumber))
450 return false;
451 }
452
453 if (!sb.append("))"))
454 return false;
455
456 JSString* str = sb.finishString();
457 if (!str)
458 return false;
459 args.rval().setString(str);
460 return true;
461 }
462 #endif
463
464 /* static */ JSObject*
createProto(JSContext * cx,JSProtoKey key)465 ErrorObject::createProto(JSContext* cx, JSProtoKey key)
466 {
467 RootedObject errorProto(cx, GenericCreatePrototype(cx, key));
468 if (!errorProto)
469 return nullptr;
470
471 Rooted<ErrorObject*> err(cx, &errorProto->as<ErrorObject>());
472 RootedString emptyStr(cx, cx->names().empty);
473 JSExnType type = ExnTypeFromProtoKey(key);
474 if (!ErrorObject::init(cx, err, type, nullptr, emptyStr, nullptr, 0, 0, emptyStr))
475 return nullptr;
476
477 // The various prototypes also have .name in addition to the normal error
478 // instance properties.
479 RootedPropertyName name(cx, ClassName(key, cx));
480 RootedValue nameValue(cx, StringValue(name));
481 if (!DefineProperty(cx, err, cx->names().name, nameValue, nullptr, nullptr, 0))
482 return nullptr;
483
484 return errorProto;
485 }
486
487 /* static */ JSObject*
createConstructor(JSContext * cx,JSProtoKey key)488 ErrorObject::createConstructor(JSContext* cx, JSProtoKey key)
489 {
490 RootedObject ctor(cx);
491 ctor = GenericCreateConstructor<Error, 1, gc::AllocKind::FUNCTION_EXTENDED>(cx, key);
492 if (!ctor)
493 return nullptr;
494
495 ctor->as<JSFunction>().setExtendedSlot(0, Int32Value(ExnTypeFromProtoKey(key)));
496 return ctor;
497 }
498
JS_FRIEND_API(JSFlatString *)499 JS_FRIEND_API(JSFlatString*)
500 js::GetErrorTypeName(JSRuntime* rt, int16_t exnType)
501 {
502 /*
503 * JSEXN_INTERNALERR returns null to prevent that "InternalError: "
504 * is prepended before "uncaught exception: "
505 */
506 if (exnType <= JSEXN_NONE || exnType >= JSEXN_LIMIT ||
507 exnType == JSEXN_INTERNALERR)
508 {
509 return nullptr;
510 }
511 JSProtoKey key = GetExceptionProtoKey(JSExnType(exnType));
512 return ClassName(key, rt);
513 }
514
515 bool
ErrorToException(JSContext * cx,const char * message,JSErrorReport * reportp,JSErrorCallback callback,void * userRef)516 js::ErrorToException(JSContext* cx, const char* message, JSErrorReport* reportp,
517 JSErrorCallback callback, void* userRef)
518 {
519 // Tell our caller to report immediately if this report is just a warning.
520 MOZ_ASSERT(reportp);
521 if (JSREPORT_IS_WARNING(reportp->flags))
522 return false;
523
524 // Similarly, we cannot throw a proper object inside the self-hosting
525 // compartment, as we cannot construct the Error constructor without
526 // self-hosted code. Tell our caller to report immediately.
527 // Without self-hosted code, we cannot get started anyway.
528 if (cx->runtime()->isSelfHostingCompartment(cx->compartment()))
529 return false;
530
531 // Find the exception index associated with this error.
532 JSErrNum errorNumber = static_cast<JSErrNum>(reportp->errorNumber);
533 if (!callback)
534 callback = GetErrorMessage;
535 const JSErrorFormatString* errorString = callback(userRef, errorNumber);
536 JSExnType exnType = errorString ? static_cast<JSExnType>(errorString->exnType) : JSEXN_NONE;
537 MOZ_ASSERT(exnType < JSEXN_LIMIT);
538
539 // Return false (no exception raised) if no exception is associated
540 // with the given error number.
541 if (exnType == JSEXN_NONE)
542 return false;
543
544 // Prevent infinite recursion.
545 if (cx->generatingError)
546 return false;
547 AutoScopedAssign<bool> asa(&cx->generatingError, true);
548
549 // Create an exception object.
550 RootedString messageStr(cx, reportp->ucmessage ? JS_NewUCStringCopyZ(cx, reportp->ucmessage)
551 : JS_NewStringCopyZ(cx, message));
552 if (!messageStr)
553 return cx->isExceptionPending();
554
555 RootedString fileName(cx, JS_NewStringCopyZ(cx, reportp->filename));
556 if (!fileName)
557 return cx->isExceptionPending();
558
559 uint32_t lineNumber = reportp->lineno;
560 uint32_t columnNumber = reportp->column;
561
562 RootedObject stack(cx);
563 if (!CaptureStack(cx, &stack))
564 return cx->isExceptionPending();
565
566 js::ScopedJSFreePtr<JSErrorReport> report(CopyErrorReport(cx, reportp));
567 if (!report)
568 return cx->isExceptionPending();
569
570 RootedObject errObject(cx, ErrorObject::create(cx, exnType, stack, fileName,
571 lineNumber, columnNumber, &report, messageStr));
572 if (!errObject)
573 return cx->isExceptionPending();
574
575 // Throw it.
576 RootedValue errValue(cx, ObjectValue(*errObject));
577 JS_SetPendingException(cx, errValue);
578
579 // Flag the error report passed in to indicate an exception was raised.
580 reportp->flags |= JSREPORT_EXCEPTION;
581 return true;
582 }
583
584 static bool
IsDuckTypedErrorObject(JSContext * cx,HandleObject exnObject,const char ** filename_strp)585 IsDuckTypedErrorObject(JSContext* cx, HandleObject exnObject, const char** filename_strp)
586 {
587 /*
588 * This function is called from ErrorReport::init and so should not generate
589 * any new exceptions.
590 */
591 AutoClearPendingException acpe(cx);
592
593 bool found;
594 if (!JS_HasProperty(cx, exnObject, js_message_str, &found) || !found)
595 return false;
596
597 const char* filename_str = *filename_strp;
598 if (!JS_HasProperty(cx, exnObject, filename_str, &found) || !found) {
599 /* Now try "fileName", in case this quacks like an Error */
600 filename_str = js_fileName_str;
601 if (!JS_HasProperty(cx, exnObject, filename_str, &found) || !found)
602 return false;
603 }
604
605 if (!JS_HasProperty(cx, exnObject, js_lineNumber_str, &found) || !found)
606 return false;
607
608 *filename_strp = filename_str;
609 return true;
610 }
611
JS_FRIEND_API(JSString *)612 JS_FRIEND_API(JSString*)
613 js::ErrorReportToString(JSContext* cx, JSErrorReport* reportp)
614 {
615 JSExnType type = static_cast<JSExnType>(reportp->exnType);
616 RootedString str(cx, cx->runtime()->emptyString);
617 if (type != JSEXN_NONE)
618 str = ClassName(GetExceptionProtoKey(type), cx);
619 RootedString toAppend(cx, JS_NewUCStringCopyN(cx, MOZ_UTF16(": "), 2));
620 if (!str || !toAppend)
621 return nullptr;
622 str = ConcatStrings<CanGC>(cx, str, toAppend);
623 if (!str)
624 return nullptr;
625 toAppend = JS_NewUCStringCopyZ(cx, reportp->ucmessage);
626 if (toAppend)
627 str = ConcatStrings<CanGC>(cx, str, toAppend);
628 return str;
629 }
630
631 bool
ReportUncaughtException(JSContext * cx)632 js::ReportUncaughtException(JSContext* cx)
633 {
634 if (!cx->isExceptionPending())
635 return true;
636
637 RootedValue exn(cx);
638 if (!cx->getPendingException(&exn)) {
639 cx->clearPendingException();
640 return false;
641 }
642
643 cx->clearPendingException();
644
645 ErrorReport err(cx);
646 if (!err.init(cx, exn)) {
647 cx->clearPendingException();
648 return false;
649 }
650
651 cx->setPendingException(exn);
652 CallErrorReporter(cx, err.message(), err.report());
653 cx->clearPendingException();
654 return true;
655 }
656
ErrorReport(JSContext * cx)657 ErrorReport::ErrorReport(JSContext* cx)
658 : reportp(nullptr),
659 message_(nullptr),
660 ownedMessage(nullptr),
661 str(cx),
662 strChars(cx),
663 exnObject(cx)
664 {
665 }
666
~ErrorReport()667 ErrorReport::~ErrorReport()
668 {
669 if (!ownedMessage)
670 return;
671
672 js_free(ownedMessage);
673 if (ownedReport.messageArgs) {
674 /*
675 * ExpandErrorArgumentsVA owns its messageArgs only if it had to
676 * inflate the arguments (from regular |char*|s), which is always in
677 * our case.
678 */
679 size_t i = 0;
680 while (ownedReport.messageArgs[i])
681 js_free(const_cast<char16_t*>(ownedReport.messageArgs[i++]));
682 js_free(ownedReport.messageArgs);
683 }
684 js_free(const_cast<char16_t*>(ownedReport.ucmessage));
685 }
686
687 void
ReportAddonExceptionToTelementry(JSContext * cx)688 ErrorReport::ReportAddonExceptionToTelementry(JSContext* cx)
689 {
690 MOZ_ASSERT(exnObject);
691 RootedObject unwrapped(cx, UncheckedUnwrap(exnObject));
692 MOZ_ASSERT(unwrapped, "UncheckedUnwrap failed?");
693
694 // There is not much we can report if the exception is not an ErrorObject, let's ignore those.
695 if (!unwrapped->is<ErrorObject>())
696 return;
697
698 Rooted<ErrorObject*> errObj(cx, &unwrapped->as<ErrorObject>());
699 RootedObject stack(cx, errObj->stack());
700
701 // Let's ignore TOP level exceptions. For regular add-ons those will not be reported anyway,
702 // for SDK based once it should not be a valid case either.
703 // At this point the frame stack is unwound but the exception object stored the stack so let's
704 // use that for getting the function name.
705 if (!stack)
706 return;
707
708 JSCompartment* comp = stack->compartment();
709 JSAddonId* addonId = comp->addonId;
710
711 // We only want to send the report if the scope that just have thrown belongs to an add-on.
712 // Let's check the compartment of the youngest function on the stack, to determine that.
713 if (!addonId)
714 return;
715
716 RootedString funnameString(cx);
717 JS::SavedFrameResult result = GetSavedFrameFunctionDisplayName(cx, stack, &funnameString);
718 // AccessDenied should never be the case here for add-ons but let's not risk it.
719 JSAutoByteString bytes;
720 const char* funname = nullptr;
721 bool denied = result == JS::SavedFrameResult::AccessDenied;
722 funname = denied ? "unknown"
723 : funnameString ? AtomToPrintableString(cx,
724 &funnameString->asAtom(),
725 &bytes)
726 : "anonymous";
727
728 UniqueChars addonIdChars(JS_EncodeString(cx, addonId));
729
730 const char* filename = nullptr;
731 if (reportp && reportp->filename) {
732 filename = strrchr(reportp->filename, '/');
733 if (filename)
734 filename++;
735 }
736 if (!filename) {
737 filename = "FILE_NOT_FOUND";
738 }
739 char histogramKey[64];
740 JS_snprintf(histogramKey, sizeof(histogramKey),
741 "%s %s %s %u",
742 addonIdChars.get(),
743 funname,
744 filename,
745 (reportp ? reportp->lineno : 0) );
746 cx->runtime()->addTelemetry(JS_TELEMETRY_ADDON_EXCEPTIONS, 1, histogramKey);
747 }
748
749 bool
init(JSContext * cx,HandleValue exn)750 ErrorReport::init(JSContext* cx, HandleValue exn)
751 {
752 MOZ_ASSERT(!cx->isExceptionPending());
753
754 if (exn.isObject()) {
755 // Because ToString below could error and an exception object could become
756 // unrooted, we must root our exception object, if any.
757 exnObject = &exn.toObject();
758 reportp = ErrorFromException(cx, exnObject);
759
760 // Let's see if the exception is from add-on code, if so, it should be reported
761 // to telementry.
762 ReportAddonExceptionToTelementry(cx);
763 }
764
765
766 // Be careful not to invoke ToString if we've already successfully extracted
767 // an error report, since the exception might be wrapped in a security
768 // wrapper, and ToString-ing it might throw.
769 if (reportp)
770 str = ErrorReportToString(cx, reportp);
771 else
772 str = ToString<CanGC>(cx, exn);
773
774 if (!str)
775 cx->clearPendingException();
776
777 // If ErrorFromException didn't get us a JSErrorReport, then the object
778 // was not an ErrorObject, security-wrapped or otherwise. However, it might
779 // still quack like one. Give duck-typing a chance. We start by looking for
780 // "filename" (all lowercase), since that's where DOMExceptions store their
781 // filename. Then we check "fileName", which is where Errors store it. We
782 // have to do it in that order, because DOMExceptions have Error.prototype
783 // on their proto chain, and hence also have a "fileName" property, but its
784 // value is "".
785 const char* filename_str = "filename";
786 if (!reportp && exnObject && IsDuckTypedErrorObject(cx, exnObject, &filename_str))
787 {
788 // Temporary value for pulling properties off of duck-typed objects.
789 RootedValue val(cx);
790
791 RootedString name(cx);
792 if (JS_GetProperty(cx, exnObject, js_name_str, &val) && val.isString())
793 name = val.toString();
794 else
795 cx->clearPendingException();
796
797 RootedString msg(cx);
798 if (JS_GetProperty(cx, exnObject, js_message_str, &val) && val.isString())
799 msg = val.toString();
800 else
801 cx->clearPendingException();
802
803 // If we have the right fields, override the ToString we performed on
804 // the exception object above with something built out of its quacks
805 // (i.e. as much of |NameQuack: MessageQuack| as we can make).
806 //
807 // It would be nice to use ErrorReportToString here, but we can't quite
808 // do it - mostly because we'd need to figure out what JSExnType |name|
809 // corresponds to, which may not be any JSExnType at all.
810 if (name && msg) {
811 RootedString colon(cx, JS_NewStringCopyZ(cx, ": "));
812 if (!colon)
813 return false;
814 RootedString nameColon(cx, ConcatStrings<CanGC>(cx, name, colon));
815 if (!nameColon)
816 return false;
817 str = ConcatStrings<CanGC>(cx, nameColon, msg);
818 if (!str)
819 return false;
820 } else if (name) {
821 str = name;
822 } else if (msg) {
823 str = msg;
824 }
825
826 if (JS_GetProperty(cx, exnObject, filename_str, &val)) {
827 RootedString tmp(cx, ToString<CanGC>(cx, val));
828 if (tmp)
829 filename.encodeUtf8(cx, tmp);
830 else
831 cx->clearPendingException();
832 } else {
833 cx->clearPendingException();
834 }
835
836 uint32_t lineno;
837 if (!JS_GetProperty(cx, exnObject, js_lineNumber_str, &val) ||
838 !ToUint32(cx, val, &lineno))
839 {
840 cx->clearPendingException();
841 lineno = 0;
842 }
843
844 uint32_t column;
845 if (!JS_GetProperty(cx, exnObject, js_columnNumber_str, &val) ||
846 !ToUint32(cx, val, &column))
847 {
848 cx->clearPendingException();
849 column = 0;
850 }
851
852 reportp = &ownedReport;
853 new (reportp) JSErrorReport();
854 ownedReport.filename = filename.ptr();
855 ownedReport.lineno = lineno;
856 ownedReport.exnType = int16_t(JSEXN_NONE);
857 ownedReport.column = column;
858 if (str) {
859 // Note that using |str| for |ucmessage| here is kind of wrong,
860 // because |str| is supposed to be of the format
861 // |ErrorName: ErrorMessage|, and |ucmessage| is supposed to
862 // correspond to |ErrorMessage|. But this is what we've historically
863 // done for duck-typed error objects.
864 //
865 // If only this stuff could get specced one day...
866 if (str->ensureFlat(cx) && strChars.initTwoByte(cx, str))
867 ownedReport.ucmessage = strChars.twoByteChars();
868 }
869 }
870
871 if (str)
872 message_ = bytesStorage.encodeUtf8(cx, str);
873 if (!message_)
874 message_ = "unknown (can't convert to string)";
875
876 if (!reportp) {
877 // This is basically an inlined version of
878 //
879 // JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
880 // JSMSG_UNCAUGHT_EXCEPTION, message_);
881 //
882 // but without the reporting bits. Instead it just puts all
883 // the stuff we care about in our ownedReport and message_.
884 if (!populateUncaughtExceptionReport(cx, message_)) {
885 // Just give up. We're out of memory or something; not much we can
886 // do here.
887 return false;
888 }
889 } else {
890 /* Flag the error as an exception. */
891 reportp->flags |= JSREPORT_EXCEPTION;
892 }
893
894 return true;
895 }
896
897 bool
populateUncaughtExceptionReport(JSContext * cx,...)898 ErrorReport::populateUncaughtExceptionReport(JSContext* cx, ...)
899 {
900 va_list ap;
901 va_start(ap, cx);
902 bool ok = populateUncaughtExceptionReportVA(cx, ap);
903 va_end(ap);
904 return ok;
905 }
906
907 bool
populateUncaughtExceptionReportVA(JSContext * cx,va_list ap)908 ErrorReport::populateUncaughtExceptionReportVA(JSContext* cx, va_list ap)
909 {
910 new (&ownedReport) JSErrorReport();
911 ownedReport.flags = JSREPORT_ERROR;
912 ownedReport.errorNumber = JSMSG_UNCAUGHT_EXCEPTION;
913 // XXXbz this assumes the stack we have right now is still
914 // related to our exception object. It would be better if we
915 // could accept a passed-in stack of some sort instead.
916 NonBuiltinFrameIter iter(cx, cx->compartment()->principals());
917 if (!iter.done()) {
918 ownedReport.filename = iter.scriptFilename();
919 ownedReport.lineno = iter.computeLine(&ownedReport.column);
920 // XXX: Make the column 1-based as in other browsers, instead of 0-based
921 // which is how SpiderMonkey stores it internally. This will be
922 // unnecessary once bug 1144340 is fixed.
923 ++ownedReport.column;
924 ownedReport.isMuted = iter.mutedErrors();
925 }
926
927 if (!ExpandErrorArgumentsVA(cx, GetErrorMessage, nullptr,
928 JSMSG_UNCAUGHT_EXCEPTION, &ownedMessage,
929 &ownedReport, ArgumentsAreASCII, ap)) {
930 return false;
931 }
932
933 reportp = &ownedReport;
934 message_ = ownedMessage;
935 ownsMessageAndReport = true;
936 return true;
937 }
938
939 JSObject*
CopyErrorObject(JSContext * cx,Handle<ErrorObject * > err)940 js::CopyErrorObject(JSContext* cx, Handle<ErrorObject*> err)
941 {
942 js::ScopedJSFreePtr<JSErrorReport> copyReport;
943 if (JSErrorReport* errorReport = err->getErrorReport()) {
944 copyReport = CopyErrorReport(cx, errorReport);
945 if (!copyReport)
946 return nullptr;
947 }
948
949 RootedString message(cx, err->getMessage());
950 if (message && !cx->compartment()->wrap(cx, &message))
951 return nullptr;
952 RootedString fileName(cx, err->fileName(cx));
953 if (!cx->compartment()->wrap(cx, &fileName))
954 return nullptr;
955 RootedObject stack(cx, err->stack());
956 if (!cx->compartment()->wrap(cx, &stack))
957 return nullptr;
958 uint32_t lineNumber = err->lineNumber();
959 uint32_t columnNumber = err->columnNumber();
960 JSExnType errorType = err->type();
961
962 // Create the Error object.
963 return ErrorObject::create(cx, errorType, stack, fileName,
964 lineNumber, columnNumber, ©Report, message);
965 }
966
JS_PUBLIC_API(bool)967 JS_PUBLIC_API(bool)
968 JS::CreateError(JSContext* cx, JSExnType type, HandleObject stack, HandleString fileName,
969 uint32_t lineNumber, uint32_t columnNumber, JSErrorReport* report,
970 HandleString message, MutableHandleValue rval)
971 {
972 assertSameCompartment(cx, stack, fileName, message);
973 AssertObjectIsSavedFrameOrWrapper(cx, stack);
974
975 js::ScopedJSFreePtr<JSErrorReport> rep;
976 if (report)
977 rep = CopyErrorReport(cx, report);
978
979 RootedObject obj(cx,
980 js::ErrorObject::create(cx, type, stack, fileName,
981 lineNumber, columnNumber, &rep, message));
982 if (!obj)
983 return false;
984
985 rval.setObject(*obj);
986 return true;
987 }
988
989 const char*
ValueToSourceForError(JSContext * cx,HandleValue val,JSAutoByteString & bytes)990 js::ValueToSourceForError(JSContext* cx, HandleValue val, JSAutoByteString& bytes)
991 {
992 if (val.isUndefined()) {
993 return "undefined";
994 }
995 if (val.isNull()) {
996 return "null";
997 }
998
999 RootedString str(cx, JS_ValueToSource(cx, val));
1000 if (!str) {
1001 JS_ClearPendingException(cx);
1002 return "<<error converting value to string>>";
1003 }
1004
1005 StringBuffer sb(cx);
1006 if (val.isObject()) {
1007 RootedObject valObj(cx, val.toObjectOrNull());
1008 ESClassValue cls;
1009 if (!GetBuiltinClass(cx, valObj, &cls)) {
1010 JS_ClearPendingException(cx);
1011 return "<<error determining class of value>>";
1012 }
1013 if (cls == ESClass_Array) {
1014 sb.append("the array ");
1015 } else if (cls == ESClass_ArrayBuffer) {
1016 sb.append("the array buffer ");
1017 } else if (JS_IsArrayBufferViewObject(valObj)) {
1018 sb.append("the typed array ");
1019 } else {
1020 sb.append("the object ");
1021 }
1022 } else if (val.isNumber()) {
1023 sb.append("the number ");
1024 } else if (val.isString()) {
1025 sb.append("the string ");
1026 } else {
1027 MOZ_ASSERT(val.isBoolean() || val.isSymbol());
1028 return bytes.encodeLatin1(cx, str);
1029 }
1030 sb.append(str);
1031 return bytes.encodeLatin1(cx, sb.finishString());
1032 }
1033