1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=8 sts=2 et sw=2 tw=80:
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/Assertions.h"
14 #include "mozilla/ScopeExit.h"
15 
16 #include <new>
17 #include <stdarg.h>
18 #include <stdint.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <utility>
22 
23 #include "jsapi.h"
24 #include "jsfriendapi.h"
25 #include "jstypes.h"
26 
27 #include "gc/Rooting.h"
28 #include "js/CharacterEncoding.h"
29 #include "js/Class.h"
30 #include "js/Conversions.h"
31 #include "js/ErrorReport.h"  // JS::PrintError
32 #include "js/Exception.h"    // JS::ExceptionStack
33 #include "js/SavedFrameAPI.h"
34 #include "js/UniquePtr.h"
35 #include "js/Value.h"
36 #include "js/Warnings.h"  // JS::{,Set}WarningReporter
37 #include "js/Wrapper.h"
38 #include "util/Memory.h"
39 #include "util/StringBuffer.h"
40 #include "vm/Compartment.h"
41 #include "vm/ErrorObject.h"
42 #include "vm/FrameIter.h"  // js::NonBuiltinFrameIter
43 #include "vm/JSAtom.h"
44 #include "vm/JSContext.h"
45 #include "vm/JSObject.h"
46 #include "vm/JSScript.h"
47 #include "vm/Realm.h"
48 #include "vm/SavedFrame.h"
49 #include "vm/SavedStacks.h"
50 #include "vm/SelfHosting.h"
51 #include "vm/Stack.h"
52 #include "vm/StringType.h"
53 #include "vm/SymbolType.h"
54 
55 #include "vm/ErrorObject-inl.h"
56 #include "vm/JSContext-inl.h"
57 #include "vm/JSObject-inl.h"
58 #include "vm/ObjectOperations-inl.h"  // js::GetProperty
59 #include "vm/SavedStacks-inl.h"
60 
61 using namespace js;
62 
63 using JS::SavedFrameSelfHosted;
64 
ExtraMallocSize(JSErrorReport * report)65 size_t ExtraMallocSize(JSErrorReport* report) {
66   if (report->linebuf()) {
67     /*
68      * Count with null terminator and alignment.
69      * See CopyExtraData for the details about alignment.
70      */
71     return (report->linebufLength() + 1) * sizeof(char16_t) + 1;
72   }
73 
74   return 0;
75 }
76 
ExtraMallocSize(JSErrorNotes::Note * note)77 size_t ExtraMallocSize(JSErrorNotes::Note* note) { return 0; }
78 
CopyExtraData(JSContext * cx,uint8_t ** cursor,JSErrorReport * copy,JSErrorReport * report)79 bool CopyExtraData(JSContext* cx, uint8_t** cursor, JSErrorReport* copy,
80                    JSErrorReport* report) {
81   if (report->linebuf()) {
82     /*
83      * Make sure cursor is properly aligned for char16_t for platforms
84      * which need it and it's at the end of the buffer on exit.
85      */
86     size_t alignment_backlog = 0;
87     if (size_t(*cursor) % 2) {
88       (*cursor)++;
89     } else {
90       alignment_backlog = 1;
91     }
92 
93     size_t linebufSize = (report->linebufLength() + 1) * sizeof(char16_t);
94     const char16_t* linebufCopy = (const char16_t*)(*cursor);
95     js_memcpy(*cursor, report->linebuf(), linebufSize);
96     *cursor += linebufSize + alignment_backlog;
97     copy->initBorrowedLinebuf(linebufCopy, report->linebufLength(),
98                               report->tokenOffset());
99   }
100 
101   /* Copy non-pointer members. */
102   copy->isMuted = report->isMuted;
103   copy->exnType = report->exnType;
104   copy->isWarning_ = report->isWarning_;
105 
106   /* Deep copy notes. */
107   if (report->notes) {
108     auto copiedNotes = report->notes->copy(cx);
109     if (!copiedNotes) {
110       return false;
111     }
112     copy->notes = std::move(copiedNotes);
113   } else {
114     copy->notes.reset(nullptr);
115   }
116 
117   return true;
118 }
119 
CopyExtraData(JSContext * cx,uint8_t ** cursor,JSErrorNotes::Note * copy,JSErrorNotes::Note * report)120 bool CopyExtraData(JSContext* cx, uint8_t** cursor, JSErrorNotes::Note* copy,
121                    JSErrorNotes::Note* report) {
122   return true;
123 }
124 
125 template <typename T>
CopyErrorHelper(JSContext * cx,T * report)126 static UniquePtr<T> CopyErrorHelper(JSContext* cx, T* report) {
127   /*
128    * We use a single malloc block to make a deep copy of JSErrorReport or
129    * JSErrorNotes::Note, except JSErrorNotes linked from JSErrorReport with
130    * the following layout:
131    *   JSErrorReport or JSErrorNotes::Note
132    *   char array with characters for message_
133    *   char array with characters for filename
134    *   char16_t array with characters for linebuf (only for JSErrorReport)
135    * Such layout together with the properties enforced by the following
136    * asserts does not need any extra alignment padding.
137    */
138   static_assert(sizeof(T) % sizeof(const char*) == 0);
139   static_assert(sizeof(const char*) % sizeof(char16_t) == 0);
140 
141   size_t filenameSize = report->filename ? strlen(report->filename) + 1 : 0;
142   size_t messageSize = 0;
143   if (report->message()) {
144     messageSize = strlen(report->message().c_str()) + 1;
145   }
146 
147   /*
148    * The mallocSize can not overflow since it represents the sum of the
149    * sizes of already allocated objects.
150    */
151   size_t mallocSize =
152       sizeof(T) + messageSize + filenameSize + ExtraMallocSize(report);
153   uint8_t* cursor = cx->pod_calloc<uint8_t>(mallocSize);
154   if (!cursor) {
155     return nullptr;
156   }
157 
158   UniquePtr<T> copy(new (cursor) T());
159   cursor += sizeof(T);
160 
161   if (report->message()) {
162     copy->initBorrowedMessage((const char*)cursor);
163     js_memcpy(cursor, report->message().c_str(), messageSize);
164     cursor += messageSize;
165   }
166 
167   if (report->filename) {
168     copy->filename = (const char*)cursor;
169     js_memcpy(cursor, report->filename, filenameSize);
170     cursor += filenameSize;
171   }
172 
173   if (!CopyExtraData(cx, &cursor, copy.get(), report)) {
174     return nullptr;
175   }
176 
177   MOZ_ASSERT(cursor == (uint8_t*)copy.get() + mallocSize);
178 
179   // errorMessageName should be static.
180   copy->errorMessageName = report->errorMessageName;
181 
182   /* Copy non-pointer members. */
183   copy->sourceId = report->sourceId;
184   copy->lineno = report->lineno;
185   copy->column = report->column;
186   copy->errorNumber = report->errorNumber;
187 
188   return copy;
189 }
190 
CopyErrorNote(JSContext * cx,JSErrorNotes::Note * note)191 UniquePtr<JSErrorNotes::Note> js::CopyErrorNote(JSContext* cx,
192                                                 JSErrorNotes::Note* note) {
193   return CopyErrorHelper(cx, note);
194 }
195 
CopyErrorReport(JSContext * cx,JSErrorReport * report)196 UniquePtr<JSErrorReport> js::CopyErrorReport(JSContext* cx,
197                                              JSErrorReport* report) {
198   return CopyErrorHelper(cx, report);
199 }
200 
201 struct SuppressErrorsGuard {
202   JSContext* cx;
203   JS::WarningReporter prevReporter;
204   JS::AutoSaveExceptionState prevState;
205 
SuppressErrorsGuardSuppressErrorsGuard206   explicit SuppressErrorsGuard(JSContext* cx)
207       : cx(cx),
208         prevReporter(JS::SetWarningReporter(cx, nullptr)),
209         prevState(cx) {}
210 
~SuppressErrorsGuardSuppressErrorsGuard211   ~SuppressErrorsGuard() { JS::SetWarningReporter(cx, prevReporter); }
212 };
213 
214 // Cut off the stack if it gets too deep (most commonly for infinite recursion
215 // errors).
216 static const size_t MAX_REPORTED_STACK_DEPTH = 1u << 7;
217 
CaptureStack(JSContext * cx,MutableHandleObject stack)218 bool js::CaptureStack(JSContext* cx, MutableHandleObject stack) {
219   return CaptureCurrentStack(
220       cx, stack, JS::StackCapture(JS::MaxFrames(MAX_REPORTED_STACK_DEPTH)));
221 }
222 
ComputeStackString(JSContext * cx)223 JSString* js::ComputeStackString(JSContext* cx) {
224   SuppressErrorsGuard seg(cx);
225 
226   RootedObject stack(cx);
227   if (!CaptureStack(cx, &stack)) {
228     return nullptr;
229   }
230 
231   RootedString str(cx);
232   if (!BuildStackString(cx, cx->realm()->principals(), stack, &str)) {
233     return nullptr;
234   }
235 
236   return str.get();
237 }
238 
ErrorFromException(JSContext * cx,HandleObject objArg)239 JSErrorReport* js::ErrorFromException(JSContext* cx, HandleObject objArg) {
240   // It's ok to UncheckedUnwrap here, since all we do is get the
241   // JSErrorReport, and consumers are careful with the information they get
242   // from that anyway.  Anyone doing things that would expose anything in the
243   // JSErrorReport to page script either does a security check on the
244   // JSErrorReport's principal or also tries to do toString on our object and
245   // will fail if they can't unwrap it.
246   RootedObject obj(cx, UncheckedUnwrap(objArg));
247   if (!obj->is<ErrorObject>()) {
248     return nullptr;
249   }
250 
251   JSErrorReport* report = obj->as<ErrorObject>().getOrCreateErrorReport(cx);
252   if (!report) {
253     MOZ_ASSERT(cx->isThrowingOutOfMemory());
254     cx->recoverFromOutOfMemory();
255   }
256 
257   return report;
258 }
259 
ExceptionStackOrNull(HandleObject objArg)260 JS_PUBLIC_API JSObject* JS::ExceptionStackOrNull(HandleObject objArg) {
261   ErrorObject* obj = objArg->maybeUnwrapIf<ErrorObject>();
262   if (!obj) {
263     return nullptr;
264   }
265 
266   return obj->stack();
267 }
268 
GetErrorTypeName(JSContext * cx,int16_t exnType)269 JS_FRIEND_API JSLinearString* js::GetErrorTypeName(JSContext* cx,
270                                                    int16_t exnType) {
271   /*
272    * JSEXN_INTERNALERR returns null to prevent that "InternalError: "
273    * is prepended before "uncaught exception: "
274    */
275   if (exnType < 0 || exnType >= JSEXN_LIMIT || exnType == JSEXN_INTERNALERR ||
276       exnType == JSEXN_WARN || exnType == JSEXN_NOTE) {
277     return nullptr;
278   }
279   JSProtoKey key = GetExceptionProtoKey(JSExnType(exnType));
280   return ClassName(key, cx);
281 }
282 
ErrorToException(JSContext * cx,JSErrorReport * reportp,JSErrorCallback callback,void * userRef)283 void js::ErrorToException(JSContext* cx, JSErrorReport* reportp,
284                           JSErrorCallback callback, void* userRef) {
285   MOZ_ASSERT(!reportp->isWarning());
286 
287   // We cannot throw a proper object inside the self-hosting realm, as we
288   // cannot construct the Error constructor without self-hosted code. Just
289   // print the error to stderr to help debugging.
290   if (cx->realm()->isSelfHostingRealm()) {
291     JS::PrintError(cx, stderr, reportp, true);
292     return;
293   }
294 
295   // Find the exception index associated with this error.
296   JSErrNum errorNumber = static_cast<JSErrNum>(reportp->errorNumber);
297   if (!callback) {
298     callback = GetErrorMessage;
299   }
300   const JSErrorFormatString* errorString = callback(userRef, errorNumber);
301   JSExnType exnType =
302       errorString ? static_cast<JSExnType>(errorString->exnType) : JSEXN_ERR;
303   MOZ_ASSERT(exnType < JSEXN_ERROR_LIMIT);
304 
305   // Prevent infinite recursion.
306   if (cx->generatingError) {
307     return;
308   }
309 
310   cx->generatingError = true;
311   auto restore = mozilla::MakeScopeExit([cx] { cx->generatingError = false; });
312 
313   // Create an exception object.
314   RootedString messageStr(cx, reportp->newMessageString(cx));
315   if (!messageStr) {
316     return;
317   }
318 
319   RootedString fileName(cx, JS_NewStringCopyZ(cx, reportp->filename));
320   if (!fileName) {
321     return;
322   }
323 
324   uint32_t sourceId = reportp->sourceId;
325   uint32_t lineNumber = reportp->lineno;
326   uint32_t columnNumber = reportp->column;
327 
328   RootedObject stack(cx);
329   if (!CaptureStack(cx, &stack)) {
330     return;
331   }
332 
333   UniquePtr<JSErrorReport> report = CopyErrorReport(cx, reportp);
334   if (!report) {
335     return;
336   }
337 
338   ErrorObject* errObject =
339       ErrorObject::create(cx, exnType, stack, fileName, sourceId, lineNumber,
340                           columnNumber, std::move(report), messageStr);
341   if (!errObject) {
342     return;
343   }
344 
345   // Throw it.
346   RootedValue errValue(cx, ObjectValue(*errObject));
347   RootedSavedFrame nstack(cx);
348   if (stack) {
349     nstack = &stack->as<SavedFrame>();
350   }
351   cx->setPendingException(errValue, nstack);
352 }
353 
354 using SniffingBehavior = JS::ErrorReportBuilder::SniffingBehavior;
355 
IsDuckTypedErrorObject(JSContext * cx,HandleObject exnObject,const char ** filename_strp)356 static bool IsDuckTypedErrorObject(JSContext* cx, HandleObject exnObject,
357                                    const char** filename_strp) {
358   /*
359    * This function is called from ErrorReport::init and so should not generate
360    * any new exceptions.
361    */
362   AutoClearPendingException acpe(cx);
363 
364   bool found;
365   if (!JS_HasProperty(cx, exnObject, js_message_str, &found) || !found) {
366     return false;
367   }
368 
369   // First try "filename".
370   const char* filename_str = *filename_strp;
371   if (!JS_HasProperty(cx, exnObject, filename_str, &found)) {
372     return false;
373   }
374   if (!found) {
375     // If that doesn't work, try "fileName".
376     filename_str = js_fileName_str;
377     if (!JS_HasProperty(cx, exnObject, filename_str, &found) || !found) {
378       return false;
379     }
380   }
381 
382   if (!JS_HasProperty(cx, exnObject, js_lineNumber_str, &found) || !found) {
383     return false;
384   }
385 
386   *filename_strp = filename_str;
387   return true;
388 }
389 
GetPropertyNoException(JSContext * cx,HandleObject obj,SniffingBehavior behavior,HandlePropertyName name,MutableHandleValue vp)390 static bool GetPropertyNoException(JSContext* cx, HandleObject obj,
391                                    SniffingBehavior behavior,
392                                    HandlePropertyName name,
393                                    MutableHandleValue vp) {
394   // This function has no side-effects so always use it.
395   if (GetPropertyPure(cx, obj, NameToId(name), vp.address())) {
396     return true;
397   }
398 
399   if (behavior == SniffingBehavior::WithSideEffects) {
400     AutoClearPendingException acpe(cx);
401     return GetProperty(cx, obj, obj, name, vp);
402   }
403 
404   return false;
405 }
406 
407 // Create a new error message similar to what Error.prototype.toString would
408 // produce when called on an object with those property values for name and
409 // message.
FormatErrorMessage(JSContext * cx,HandleString name,HandleString message)410 static JSString* FormatErrorMessage(JSContext* cx, HandleString name,
411                                     HandleString message) {
412   if (name && message) {
413     AutoClearPendingException acpe(cx);
414     JSStringBuilder sb(cx);
415 
416     // Prefix the message with the error type, if it exists.
417     if (!sb.append(name) || !sb.append(": ") || !sb.append(message)) {
418       return nullptr;
419     }
420 
421     return sb.finishString();
422   }
423 
424   return name ? name : message;
425 }
426 
ErrorReportToString(JSContext * cx,HandleObject exn,JSErrorReport * reportp,SniffingBehavior behavior)427 static JSString* ErrorReportToString(JSContext* cx, HandleObject exn,
428                                      JSErrorReport* reportp,
429                                      SniffingBehavior behavior) {
430   // The error object might have custom `name` overwriting the exnType in the
431   // error report. Try getting that property and use the exnType as a fallback.
432   RootedString name(cx);
433   RootedValue nameV(cx);
434   if (GetPropertyNoException(cx, exn, behavior, cx->names().name, &nameV) &&
435       nameV.isString()) {
436     name = nameV.toString();
437   }
438 
439   // We do NOT want to use GetErrorTypeName() here because it will not do the
440   // "right thing" for JSEXN_INTERNALERR.  That is, the caller of this API
441   // expects that "InternalError: " will be prepended but GetErrorTypeName
442   // goes out of its way to avoid this.
443   if (!name) {
444     JSExnType type = static_cast<JSExnType>(reportp->exnType);
445     if (type != JSEXN_WARN && type != JSEXN_NOTE) {
446       name = ClassName(GetExceptionProtoKey(type), cx);
447     }
448   }
449 
450   RootedString message(cx);
451   RootedValue messageV(cx);
452   if (GetPropertyNoException(cx, exn, behavior, cx->names().message,
453                              &messageV) &&
454       messageV.isString()) {
455     message = messageV.toString();
456   }
457 
458   if (!message) {
459     message = reportp->newMessageString(cx);
460     if (!message) {
461       return nullptr;
462     }
463   }
464 
465   return FormatErrorMessage(cx, name, message);
466 }
467 
ErrorReportBuilder(JSContext * cx)468 JS::ErrorReportBuilder::ErrorReportBuilder(JSContext* cx)
469     : reportp(nullptr), exnObject(cx) {}
470 
471 JS::ErrorReportBuilder::~ErrorReportBuilder() = default;
472 
init(JSContext * cx,const JS::ExceptionStack & exnStack,SniffingBehavior sniffingBehavior)473 bool JS::ErrorReportBuilder::init(JSContext* cx,
474                                   const JS::ExceptionStack& exnStack,
475                                   SniffingBehavior sniffingBehavior) {
476   MOZ_ASSERT(!cx->isExceptionPending());
477   MOZ_ASSERT(!reportp);
478 
479   if (exnStack.exception().isObject()) {
480     // Because ToString below could error and an exception object could become
481     // unrooted, we must root our exception object, if any.
482     exnObject = &exnStack.exception().toObject();
483     reportp = ErrorFromException(cx, exnObject);
484   }
485 
486   // Be careful not to invoke ToString if we've already successfully extracted
487   // an error report, since the exception might be wrapped in a security
488   // wrapper, and ToString-ing it might throw.
489   RootedString str(cx);
490   if (reportp) {
491     str = ErrorReportToString(cx, exnObject, reportp, sniffingBehavior);
492   } else if (exnStack.exception().isSymbol()) {
493     RootedValue strVal(cx);
494     if (js::SymbolDescriptiveString(cx, exnStack.exception().toSymbol(),
495                                     &strVal)) {
496       str = strVal.toString();
497     } else {
498       str = nullptr;
499     }
500   } else if (exnObject && sniffingBehavior == NoSideEffects) {
501     str = cx->names().Object;
502   } else {
503     str = js::ToString<CanGC>(cx, exnStack.exception());
504   }
505 
506   if (!str) {
507     cx->clearPendingException();
508   }
509 
510   // If ErrorFromException didn't get us a JSErrorReport, then the object
511   // was not an ErrorObject, security-wrapped or otherwise. However, it might
512   // still quack like one. Give duck-typing a chance.  We start by looking for
513   // "filename" (all lowercase), since that's where DOMExceptions store their
514   // filename.  Then we check "fileName", which is where Errors store it.  We
515   // have to do it in that order, because DOMExceptions have Error.prototype
516   // on their proto chain, and hence also have a "fileName" property, but its
517   // value is "".
518   const char* filename_str = "filename";
519   if (!reportp && exnObject && sniffingBehavior == WithSideEffects &&
520       IsDuckTypedErrorObject(cx, exnObject, &filename_str)) {
521     // Temporary value for pulling properties off of duck-typed objects.
522     RootedValue val(cx);
523 
524     RootedString name(cx);
525     if (JS_GetProperty(cx, exnObject, js_name_str, &val) && val.isString()) {
526       name = val.toString();
527     } else {
528       cx->clearPendingException();
529     }
530 
531     RootedString msg(cx);
532     if (JS_GetProperty(cx, exnObject, js_message_str, &val) && val.isString()) {
533       msg = val.toString();
534     } else {
535       cx->clearPendingException();
536     }
537 
538     // If we have the right fields, override the ToString we performed on
539     // the exception object above with something built out of its quacks
540     // (i.e. as much of |NameQuack: MessageQuack| as we can make).
541     str = FormatErrorMessage(cx, name, msg);
542 
543     {
544       AutoClearPendingException acpe(cx);
545       if (JS_GetProperty(cx, exnObject, filename_str, &val)) {
546         RootedString tmp(cx, js::ToString<CanGC>(cx, val));
547         if (tmp) {
548           filename = JS_EncodeStringToUTF8(cx, tmp);
549         }
550       }
551     }
552     if (!filename) {
553       filename = DuplicateString("");
554       if (!filename) {
555         ReportOutOfMemory(cx);
556         return false;
557       }
558     }
559 
560     uint32_t lineno;
561     if (!JS_GetProperty(cx, exnObject, js_lineNumber_str, &val) ||
562         !ToUint32(cx, val, &lineno)) {
563       cx->clearPendingException();
564       lineno = 0;
565     }
566 
567     uint32_t column;
568     if (!JS_GetProperty(cx, exnObject, js_columnNumber_str, &val) ||
569         !ToUint32(cx, val, &column)) {
570       cx->clearPendingException();
571       column = 0;
572     }
573 
574     reportp = &ownedReport;
575     new (reportp) JSErrorReport();
576     ownedReport.filename = filename.get();
577     ownedReport.lineno = lineno;
578     ownedReport.exnType = JSEXN_INTERNALERR;
579     ownedReport.column = column;
580 
581     if (str) {
582       // Note that using |str| for |message_| here is kind of wrong,
583       // because |str| is supposed to be of the format
584       // |ErrorName: ErrorMessage|, and |message_| is supposed to
585       // correspond to |ErrorMessage|. But this is what we've
586       // historically done for duck-typed error objects.
587       //
588       // If only this stuff could get specced one day...
589       if (auto utf8 = JS_EncodeStringToUTF8(cx, str)) {
590         ownedReport.initOwnedMessage(utf8.release());
591       } else {
592         cx->clearPendingException();
593         str = nullptr;
594       }
595     }
596   }
597 
598   const char* utf8Message = nullptr;
599   if (str) {
600     toStringResultBytesStorage = JS_EncodeStringToUTF8(cx, str);
601     utf8Message = toStringResultBytesStorage.get();
602   }
603   if (!utf8Message) {
604     utf8Message = "unknown (can't convert to string)";
605   }
606 
607   if (!reportp) {
608     // This is basically an inlined version of
609     //
610     //   JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
611     //                            JSMSG_UNCAUGHT_EXCEPTION, utf8Message);
612     //
613     // but without the reporting bits.  Instead it just puts all
614     // the stuff we care about in our ownedReport and message_.
615     if (!populateUncaughtExceptionReportUTF8(cx, exnStack.stack(),
616                                              utf8Message)) {
617       // Just give up.  We're out of memory or something; not much we can
618       // do here.
619       return false;
620     }
621   } else {
622     toStringResult_ = JS::ConstUTF8CharsZ(utf8Message, strlen(utf8Message));
623   }
624 
625   return true;
626 }
627 
populateUncaughtExceptionReportUTF8(JSContext * cx,HandleObject stack,...)628 bool JS::ErrorReportBuilder::populateUncaughtExceptionReportUTF8(
629     JSContext* cx, HandleObject stack, ...) {
630   va_list ap;
631   va_start(ap, stack);
632   bool ok = populateUncaughtExceptionReportUTF8VA(cx, stack, ap);
633   va_end(ap);
634   return ok;
635 }
636 
populateUncaughtExceptionReportUTF8VA(JSContext * cx,HandleObject stack,va_list ap)637 bool JS::ErrorReportBuilder::populateUncaughtExceptionReportUTF8VA(
638     JSContext* cx, HandleObject stack, va_list ap) {
639   new (&ownedReport) JSErrorReport();
640   ownedReport.isWarning_ = false;
641   ownedReport.errorNumber = JSMSG_UNCAUGHT_EXCEPTION;
642 
643   bool skippedAsync;
644   RootedSavedFrame frame(
645       cx, UnwrapSavedFrame(cx, cx->realm()->principals(), stack,
646                            SavedFrameSelfHosted::Exclude, skippedAsync));
647   if (frame) {
648     filename = StringToNewUTF8CharsZ(cx, *frame->getSource());
649     if (!filename) {
650       return false;
651     }
652 
653     // |ownedReport.filename| inherits the lifetime of |ErrorReport::filename|.
654     ownedReport.filename = filename.get();
655     ownedReport.sourceId = frame->getSourceId();
656     ownedReport.lineno = frame->getLine();
657     // Follow FixupColumnForDisplay and set column to 1 for WASM.
658     ownedReport.column = frame->isWasm() ? 1 : frame->getColumn();
659     ownedReport.isMuted = frame->getMutedErrors();
660   } else {
661     // XXXbz this assumes the stack we have right now is still
662     // related to our exception object.
663     NonBuiltinFrameIter iter(cx, cx->realm()->principals());
664     if (!iter.done()) {
665       ownedReport.filename = iter.filename();
666       uint32_t column;
667       ownedReport.sourceId =
668           iter.hasScript() ? iter.script()->scriptSource()->id() : 0;
669       ownedReport.lineno = iter.computeLine(&column);
670       ownedReport.column = FixupColumnForDisplay(column);
671       ownedReport.isMuted = iter.mutedErrors();
672     }
673   }
674 
675   if (!ExpandErrorArgumentsVA(cx, GetErrorMessage, nullptr,
676                               JSMSG_UNCAUGHT_EXCEPTION, ArgumentsAreUTF8,
677                               &ownedReport, ap)) {
678     return false;
679   }
680 
681   toStringResult_ = ownedReport.message();
682   reportp = &ownedReport;
683   return true;
684 }
685 
CopyErrorObject(JSContext * cx,Handle<ErrorObject * > err)686 JSObject* js::CopyErrorObject(JSContext* cx, Handle<ErrorObject*> err) {
687   UniquePtr<JSErrorReport> copyReport;
688   if (JSErrorReport* errorReport = err->getErrorReport()) {
689     copyReport = CopyErrorReport(cx, errorReport);
690     if (!copyReport) {
691       return nullptr;
692     }
693   }
694 
695   RootedString message(cx, err->getMessage());
696   if (message && !cx->compartment()->wrap(cx, &message)) {
697     return nullptr;
698   }
699   RootedString fileName(cx, err->fileName(cx));
700   if (!cx->compartment()->wrap(cx, &fileName)) {
701     return nullptr;
702   }
703   RootedObject stack(cx, err->stack());
704   if (!cx->compartment()->wrap(cx, &stack)) {
705     return nullptr;
706   }
707   uint32_t sourceId = err->sourceId();
708   uint32_t lineNumber = err->lineNumber();
709   uint32_t columnNumber = err->columnNumber();
710   JSExnType errorType = err->type();
711 
712   // Create the Error object.
713   return ErrorObject::create(cx, errorType, stack, fileName, sourceId,
714                              lineNumber, columnNumber, std::move(copyReport),
715                              message);
716 }
717 
CreateError(JSContext * cx,JSExnType type,HandleObject stack,HandleString fileName,uint32_t lineNumber,uint32_t columnNumber,JSErrorReport * report,HandleString message,MutableHandleValue rval)718 JS_PUBLIC_API bool JS::CreateError(JSContext* cx, JSExnType type,
719                                    HandleObject stack, HandleString fileName,
720                                    uint32_t lineNumber, uint32_t columnNumber,
721                                    JSErrorReport* report, HandleString message,
722                                    MutableHandleValue rval) {
723   cx->check(stack, fileName, message);
724   AssertObjectIsSavedFrameOrWrapper(cx, stack);
725 
726   js::UniquePtr<JSErrorReport> rep;
727   if (report) {
728     rep = CopyErrorReport(cx, report);
729     if (!rep) {
730       return false;
731     }
732   }
733 
734   JSObject* obj =
735       js::ErrorObject::create(cx, type, stack, fileName, 0, lineNumber,
736                               columnNumber, std::move(rep), message);
737   if (!obj) {
738     return false;
739   }
740 
741   rval.setObject(*obj);
742   return true;
743 }
744 
ValueToSourceForError(JSContext * cx,HandleValue val,UniqueChars & bytes)745 const char* js::ValueToSourceForError(JSContext* cx, HandleValue val,
746                                       UniqueChars& bytes) {
747   if (val.isUndefined()) {
748     return "undefined";
749   }
750 
751   if (val.isNull()) {
752     return "null";
753   }
754 
755   AutoClearPendingException acpe(cx);
756 
757   RootedString str(cx, JS_ValueToSource(cx, val));
758   if (!str) {
759     return "<<error converting value to string>>";
760   }
761 
762   JSStringBuilder sb(cx);
763   if (val.isObject()) {
764     RootedObject valObj(cx, val.toObjectOrNull());
765     ESClass cls;
766     if (!GetBuiltinClass(cx, valObj, &cls)) {
767       return "<<error determining class of value>>";
768     }
769     const char* s;
770     if (cls == ESClass::Array) {
771       s = "the array ";
772     } else if (cls == ESClass::ArrayBuffer) {
773       s = "the array buffer ";
774     } else if (JS_IsArrayBufferViewObject(valObj)) {
775       s = "the typed array ";
776     } else {
777       s = "the object ";
778     }
779     if (!sb.append(s, strlen(s))) {
780       return "<<error converting value to string>>";
781     }
782   } else if (val.isNumber()) {
783     if (!sb.append("the number ")) {
784       return "<<error converting value to string>>";
785     }
786   } else if (val.isString()) {
787     if (!sb.append("the string ")) {
788       return "<<error converting value to string>>";
789     }
790   } else if (val.isBigInt()) {
791     if (!sb.append("the BigInt ")) {
792       return "<<error converting value to string>>";
793     }
794   } else {
795     MOZ_ASSERT(val.isBoolean() || val.isSymbol());
796     bytes = StringToNewUTF8CharsZ(cx, *str);
797     return bytes.get();
798   }
799   if (!sb.append(str)) {
800     return "<<error converting value to string>>";
801   }
802   str = sb.finishString();
803   if (!str) {
804     return "<<error converting value to string>>";
805   }
806   bytes = StringToNewUTF8CharsZ(cx, *str);
807   return bytes.get();
808 }
809 
GetInternalError(JSContext * cx,unsigned errorNumber,MutableHandleValue error)810 bool js::GetInternalError(JSContext* cx, unsigned errorNumber,
811                           MutableHandleValue error) {
812   FixedInvokeArgs<1> args(cx);
813   args[0].set(Int32Value(errorNumber));
814   return CallSelfHostedFunction(cx, cx->names().GetInternalError,
815                                 NullHandleValue, args, error);
816 }
817 
GetTypeError(JSContext * cx,unsigned errorNumber,MutableHandleValue error)818 bool js::GetTypeError(JSContext* cx, unsigned errorNumber,
819                       MutableHandleValue error) {
820   FixedInvokeArgs<1> args(cx);
821   args[0].set(Int32Value(errorNumber));
822   return CallSelfHostedFunction(cx, cx->names().GetTypeError, NullHandleValue,
823                                 args, error);
824 }
825 
GetAggregateError(JSContext * cx,unsigned errorNumber,MutableHandleValue error)826 bool js::GetAggregateError(JSContext* cx, unsigned errorNumber,
827                            MutableHandleValue error) {
828   FixedInvokeArgs<1> args(cx);
829   args[0].set(Int32Value(errorNumber));
830   return CallSelfHostedFunction(cx, cx->names().GetAggregateError,
831                                 NullHandleValue, args, error);
832 }
833