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, &copyReport, 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