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