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