ErrorObject.cpp (35243B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sw=2 et tw=80: 3 * 4 * This Source Code Form is subject to the terms of the Mozilla Public 5 * License, v. 2.0. If a copy of the MPL was not distributed with this 6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 7 8 #include "vm/ErrorObject-inl.h" 9 10 #include "mozilla/Assertions.h" 11 #include "mozilla/Attributes.h" 12 #include "mozilla/DebugOnly.h" 13 #include "mozilla/Maybe.h" 14 15 #include <utility> 16 17 #include "jsexn.h" 18 #include "jspubtd.h" 19 #include "NamespaceImports.h" 20 21 #include "gc/AllocKind.h" 22 #include "gc/GCContext.h" 23 #include "js/CallArgs.h" 24 #include "js/CallNonGenericMethod.h" 25 #include "js/CharacterEncoding.h" // JS::ConstUTF8CharsZ 26 #include "js/Class.h" 27 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin 28 #include "js/Conversions.h" 29 #include "js/ErrorReport.h" 30 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 31 #include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit 32 #include "js/PropertySpec.h" 33 #include "js/RootingAPI.h" 34 #include "js/Stack.h" 35 #include "js/TypeDecls.h" 36 #include "js/Utility.h" 37 #include "js/Value.h" 38 #include "js/Wrapper.h" 39 #include "util/StringBuilder.h" 40 #include "vm/ErrorReporting.h" 41 #include "vm/GlobalObject.h" 42 #include "vm/Iteration.h" 43 #include "vm/JSAtomUtils.h" // ClassName 44 #include "vm/JSFunction.h" 45 #include "vm/JSObject.h" 46 #include "vm/NativeObject.h" 47 #include "vm/ObjectOperations.h" 48 #include "vm/SavedStacks.h" 49 #include "vm/SelfHosting.h" 50 #include "vm/Shape.h" 51 #include "vm/Stack.h" 52 #include "vm/StringType.h" 53 #include "vm/ToSource.h" // js::ValueToSource 54 55 #include "vm/JSContext-inl.h" 56 #include "vm/JSObject-inl.h" 57 #include "vm/ObjectOperations-inl.h" 58 #include "vm/Realm-inl.h" 59 #include "vm/SavedStacks-inl.h" 60 #include "vm/Shape-inl.h" 61 62 using namespace js; 63 64 #define IMPLEMENT_ERROR_PROTO_CLASS(name) \ 65 {#name ".prototype", JSCLASS_HAS_CACHED_PROTO(JSProto_##name), \ 66 JS_NULL_CLASS_OPS, \ 67 &ErrorObject::classSpecs[JSProto_##name - JSProto_Error]} 68 69 const JSClass ErrorObject::protoClasses[JSEXN_ERROR_LIMIT] = { 70 IMPLEMENT_ERROR_PROTO_CLASS(Error), 71 72 IMPLEMENT_ERROR_PROTO_CLASS(InternalError), 73 IMPLEMENT_ERROR_PROTO_CLASS(AggregateError), 74 IMPLEMENT_ERROR_PROTO_CLASS(EvalError), 75 IMPLEMENT_ERROR_PROTO_CLASS(RangeError), 76 IMPLEMENT_ERROR_PROTO_CLASS(ReferenceError), 77 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 78 IMPLEMENT_ERROR_PROTO_CLASS(SuppressedError), 79 #endif 80 IMPLEMENT_ERROR_PROTO_CLASS(SyntaxError), 81 IMPLEMENT_ERROR_PROTO_CLASS(TypeError), 82 IMPLEMENT_ERROR_PROTO_CLASS(URIError), 83 84 IMPLEMENT_ERROR_PROTO_CLASS(DebuggeeWouldRun), 85 IMPLEMENT_ERROR_PROTO_CLASS(CompileError), 86 IMPLEMENT_ERROR_PROTO_CLASS(LinkError), 87 IMPLEMENT_ERROR_PROTO_CLASS(RuntimeError), 88 #ifdef ENABLE_WASM_JSPI 89 IMPLEMENT_ERROR_PROTO_CLASS(SuspendError), 90 #endif 91 }; 92 93 static bool exn_toSource(JSContext* cx, unsigned argc, Value* vp); 94 95 static const JSFunctionSpec error_methods[] = { 96 JS_FN("toSource", exn_toSource, 0, 0), 97 JS_SELF_HOSTED_FN("toString", "ErrorToString", 0, 0), 98 JS_FS_END, 99 }; 100 101 static bool exn_isError(JSContext* cx, unsigned argc, Value* vp); 102 103 static bool exn_captureStackTrace(JSContext* cx, unsigned argc, Value* vp); 104 105 static const JSFunctionSpec error_static_methods[] = { 106 JS_FN("isError", exn_isError, 1, 0), 107 JS_FN("captureStackTrace", exn_captureStackTrace, 2, 0), 108 JS_FS_END, 109 }; 110 111 // Error.prototype and NativeError.prototype have own .message and .name 112 // properties. 113 #define COMMON_ERROR_PROPERTIES(name) \ 114 JS_STRING_PS("message", "", 0), JS_STRING_PS("name", #name, 0) 115 116 static const JSPropertySpec error_properties[] = { 117 COMMON_ERROR_PROPERTIES(Error), 118 // Only Error.prototype has .stack! 119 JS_PSGS("stack", ErrorObject::getStack, ErrorObject::setStack, 0), 120 JS_PS_END, 121 }; 122 123 #define IMPLEMENT_NATIVE_ERROR_PROPERTIES(name) \ 124 static const JSPropertySpec name##_properties[] = { \ 125 COMMON_ERROR_PROPERTIES(name), JS_PS_END}; 126 127 IMPLEMENT_NATIVE_ERROR_PROPERTIES(InternalError) 128 IMPLEMENT_NATIVE_ERROR_PROPERTIES(AggregateError) 129 IMPLEMENT_NATIVE_ERROR_PROPERTIES(EvalError) 130 IMPLEMENT_NATIVE_ERROR_PROPERTIES(RangeError) 131 IMPLEMENT_NATIVE_ERROR_PROPERTIES(ReferenceError) 132 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 133 IMPLEMENT_NATIVE_ERROR_PROPERTIES(SuppressedError) 134 #endif 135 IMPLEMENT_NATIVE_ERROR_PROPERTIES(SyntaxError) 136 IMPLEMENT_NATIVE_ERROR_PROPERTIES(TypeError) 137 IMPLEMENT_NATIVE_ERROR_PROPERTIES(URIError) 138 IMPLEMENT_NATIVE_ERROR_PROPERTIES(DebuggeeWouldRun) 139 IMPLEMENT_NATIVE_ERROR_PROPERTIES(CompileError) 140 IMPLEMENT_NATIVE_ERROR_PROPERTIES(LinkError) 141 IMPLEMENT_NATIVE_ERROR_PROPERTIES(RuntimeError) 142 #ifdef ENABLE_WASM_JSPI 143 IMPLEMENT_NATIVE_ERROR_PROPERTIES(SuspendError) 144 #endif 145 146 #define IMPLEMENT_NATIVE_ERROR_SPEC(name) \ 147 {ErrorObject::createConstructor, \ 148 ErrorObject::createProto, \ 149 nullptr, \ 150 nullptr, \ 151 nullptr, \ 152 name##_properties, \ 153 nullptr, \ 154 JSProto_Error} 155 156 #define IMPLEMENT_NONGLOBAL_ERROR_SPEC(name) \ 157 {ErrorObject::createConstructor, \ 158 ErrorObject::createProto, \ 159 nullptr, \ 160 nullptr, \ 161 nullptr, \ 162 name##_properties, \ 163 nullptr, \ 164 JSProto_Error | ClassSpec::DontDefineConstructor} 165 166 const ClassSpec ErrorObject::classSpecs[JSEXN_ERROR_LIMIT] = { 167 {ErrorObject::createConstructor, ErrorObject::createProto, 168 error_static_methods, nullptr, error_methods, error_properties}, 169 170 IMPLEMENT_NATIVE_ERROR_SPEC(InternalError), 171 IMPLEMENT_NATIVE_ERROR_SPEC(AggregateError), 172 IMPLEMENT_NATIVE_ERROR_SPEC(EvalError), 173 IMPLEMENT_NATIVE_ERROR_SPEC(RangeError), 174 IMPLEMENT_NATIVE_ERROR_SPEC(ReferenceError), 175 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 176 IMPLEMENT_NATIVE_ERROR_SPEC(SuppressedError), 177 #endif 178 IMPLEMENT_NATIVE_ERROR_SPEC(SyntaxError), 179 IMPLEMENT_NATIVE_ERROR_SPEC(TypeError), 180 IMPLEMENT_NATIVE_ERROR_SPEC(URIError), 181 182 IMPLEMENT_NONGLOBAL_ERROR_SPEC(DebuggeeWouldRun), 183 IMPLEMENT_NONGLOBAL_ERROR_SPEC(CompileError), 184 IMPLEMENT_NONGLOBAL_ERROR_SPEC(LinkError), 185 IMPLEMENT_NONGLOBAL_ERROR_SPEC(RuntimeError), 186 #ifdef ENABLE_WASM_JSPI 187 IMPLEMENT_NONGLOBAL_ERROR_SPEC(SuspendError), 188 #endif 189 }; 190 191 #define IMPLEMENT_ERROR_CLASS_CORE(name, reserved_slots) \ 192 {#name, \ 193 JSCLASS_HAS_CACHED_PROTO(JSProto_##name) | \ 194 JSCLASS_HAS_RESERVED_SLOTS(reserved_slots) | \ 195 JSCLASS_BACKGROUND_FINALIZE, \ 196 &ErrorObjectClassOps, \ 197 &ErrorObject::classSpecs[JSProto_##name - JSProto_Error]} 198 199 #define IMPLEMENT_ERROR_CLASS(name) \ 200 IMPLEMENT_ERROR_CLASS_CORE(name, ErrorObject::RESERVED_SLOTS) 201 202 // Only used for classes that could be a Wasm trap. Classes that use this 203 // macro should be kept in sync with the exception types that mightBeWasmTrap() 204 // will return true for. 205 #define IMPLEMENT_ERROR_CLASS_MAYBE_WASM_TRAP(name) \ 206 IMPLEMENT_ERROR_CLASS_CORE(name, ErrorObject::RESERVED_SLOTS_MAYBE_WASM_TRAP) 207 208 static void exn_finalize(JS::GCContext* gcx, JSObject* obj); 209 210 static const JSClassOps ErrorObjectClassOps = { 211 nullptr, // addProperty 212 nullptr, // delProperty 213 nullptr, // enumerate 214 nullptr, // newEnumerate 215 nullptr, // resolve 216 nullptr, // mayResolve 217 exn_finalize, // finalize 218 nullptr, // call 219 nullptr, // construct 220 nullptr, // trace 221 }; 222 223 const JSClass ErrorObject::classes[JSEXN_ERROR_LIMIT] = { 224 IMPLEMENT_ERROR_CLASS(Error), 225 IMPLEMENT_ERROR_CLASS_MAYBE_WASM_TRAP(InternalError), 226 IMPLEMENT_ERROR_CLASS(AggregateError), 227 IMPLEMENT_ERROR_CLASS(EvalError), 228 IMPLEMENT_ERROR_CLASS(RangeError), 229 IMPLEMENT_ERROR_CLASS(ReferenceError), 230 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 231 IMPLEMENT_ERROR_CLASS(SuppressedError), 232 #endif 233 IMPLEMENT_ERROR_CLASS(SyntaxError), 234 IMPLEMENT_ERROR_CLASS(TypeError), 235 IMPLEMENT_ERROR_CLASS(URIError), 236 // These Error subclasses are not accessible via the global object: 237 IMPLEMENT_ERROR_CLASS(DebuggeeWouldRun), 238 IMPLEMENT_ERROR_CLASS(CompileError), 239 IMPLEMENT_ERROR_CLASS(LinkError), 240 IMPLEMENT_ERROR_CLASS_MAYBE_WASM_TRAP(RuntimeError), 241 #ifdef ENABLE_WASM_JSPI 242 IMPLEMENT_ERROR_CLASS(SuspendError), 243 #endif 244 }; 245 246 static void exn_finalize(JS::GCContext* gcx, JSObject* obj) { 247 if (JSErrorReport* report = obj->as<ErrorObject>().getErrorReport()) { 248 // Bug 1560019: This allocation is not currently tracked. 249 gcx->deleteUntracked(report); 250 } 251 } 252 253 static ErrorObject* CreateErrorObject(JSContext* cx, const CallArgs& args, 254 unsigned messageArg, JSExnType exnType, 255 HandleObject proto) { 256 // Compute the error message, if any. 257 RootedString message(cx, nullptr); 258 if (args.hasDefined(messageArg)) { 259 message = ToString<CanGC>(cx, args[messageArg]); 260 if (!message) { 261 return nullptr; 262 } 263 } 264 265 // Don't interpret the two parameters following the message parameter as the 266 // non-standard fileName and lineNumber arguments when we have an options 267 // object argument and the exception type is not SuppressedError. 268 bool hasOptions = 269 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 270 args.get(messageArg + 1).isObject() && exnType != JSEXN_SUPPRESSEDERR; 271 #else 272 args.get(messageArg + 1).isObject(); 273 #endif 274 275 Rooted<mozilla::Maybe<Value>> cause(cx, mozilla::Nothing()); 276 if (hasOptions) { 277 RootedObject options(cx, &args[messageArg + 1].toObject()); 278 279 bool hasCause = false; 280 if (!HasProperty(cx, options, cx->names().cause, &hasCause)) { 281 return nullptr; 282 } 283 284 if (hasCause) { 285 RootedValue causeValue(cx); 286 if (!GetProperty(cx, options, options, cx->names().cause, &causeValue)) { 287 return nullptr; 288 } 289 cause = mozilla::Some(causeValue.get()); 290 } 291 } 292 293 // Find the scripted caller, but only ones we're allowed to know about. 294 NonBuiltinFrameIter iter(cx, cx->realm()->principals()); 295 296 RootedString fileName(cx); 297 uint32_t sourceId = 0; 298 if (!hasOptions && args.length() > messageArg + 1) { 299 fileName = ToString<CanGC>(cx, args[messageArg + 1]); 300 } else { 301 fileName = cx->runtime()->emptyString; 302 if (!iter.done()) { 303 if (const char* cfilename = iter.filename()) { 304 fileName = JS_NewStringCopyUTF8Z( 305 cx, JS::ConstUTF8CharsZ(cfilename, strlen(cfilename))); 306 } 307 if (iter.hasScript()) { 308 sourceId = iter.script()->scriptSource()->id(); 309 } 310 } 311 } 312 if (!fileName) { 313 return nullptr; 314 } 315 316 uint32_t lineNumber; 317 JS::ColumnNumberOneOrigin columnNumber; 318 if (!hasOptions && args.length() > messageArg + 2) { 319 if (!ToUint32(cx, args[messageArg + 2], &lineNumber)) { 320 return nullptr; 321 } 322 } else { 323 JS::TaggedColumnNumberOneOrigin tmp; 324 lineNumber = iter.done() ? 0 : iter.computeLine(&tmp); 325 columnNumber = JS::ColumnNumberOneOrigin(tmp.oneOriginValue()); 326 } 327 328 RootedObject stack(cx); 329 if (!CaptureStack(cx, &stack)) { 330 return nullptr; 331 } 332 333 return ErrorObject::create(cx, exnType, stack, fileName, sourceId, lineNumber, 334 columnNumber, nullptr, message, cause, proto); 335 } 336 337 static bool Error(JSContext* cx, unsigned argc, Value* vp) { 338 CallArgs args = CallArgsFromVp(argc, vp); 339 340 // ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when 341 // called as functions, without operator new. But as we do not give 342 // each constructor a distinct JSClass, we must get the exception type 343 // ourselves. 344 JSExnType exnType = 345 JSExnType(args.callee().as<JSFunction>().getExtendedSlot(0).toInt32()); 346 347 MOZ_ASSERT(exnType != JSEXN_AGGREGATEERR, 348 "AggregateError has its own constructor function"); 349 350 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 351 MOZ_ASSERT(exnType != JSEXN_SUPPRESSEDERR, 352 "SuppressedError has its own constuctor function"); 353 #endif 354 355 JSProtoKey protoKey = 356 JSCLASS_CACHED_PROTO_KEY(&ErrorObject::classes[exnType]); 357 358 // ES6 19.5.1.1 mandates the .prototype lookup happens before the toString 359 RootedObject proto(cx); 360 if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey, &proto)) { 361 return false; 362 } 363 364 auto* obj = CreateErrorObject(cx, args, 0, exnType, proto); 365 if (!obj) { 366 return false; 367 } 368 369 args.rval().setObject(*obj); 370 return true; 371 } 372 373 // AggregateError ( errors, message ) 374 static bool AggregateError(JSContext* cx, unsigned argc, Value* vp) { 375 CallArgs args = CallArgsFromVp(argc, vp); 376 377 mozilla::DebugOnly<JSExnType> exnType = 378 JSExnType(args.callee().as<JSFunction>().getExtendedSlot(0).toInt32()); 379 380 MOZ_ASSERT(exnType == JSEXN_AGGREGATEERR); 381 382 // Steps 1-2. (9.1.13 OrdinaryCreateFromConstructor, steps 1-2). 383 RootedObject proto(cx); 384 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_AggregateError, 385 &proto)) { 386 return false; 387 } 388 389 // TypeError anyway, but this gives a better error message. 390 if (!args.requireAtLeast(cx, "AggregateError", 1)) { 391 return false; 392 } 393 394 // 9.1.13 OrdinaryCreateFromConstructor, step 3. 395 // Step 3. 396 Rooted<ErrorObject*> obj( 397 cx, CreateErrorObject(cx, args, 1, JSEXN_AGGREGATEERR, proto)); 398 if (!obj) { 399 return false; 400 } 401 402 // Step 4. 403 404 Rooted<ArrayObject*> errorsList(cx); 405 if (!IterableToArray(cx, args.get(0), &errorsList)) { 406 return false; 407 } 408 409 // Step 5. 410 RootedValue errorsVal(cx, JS::ObjectValue(*errorsList)); 411 if (!NativeDefineDataProperty(cx, obj, cx->names().errors, errorsVal, 0)) { 412 return false; 413 } 414 415 // Step 6. 416 args.rval().setObject(*obj); 417 return true; 418 } 419 420 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 421 // Explicit Resource Management Proposal 422 // SuppressedError ( error, suppressed, message ) 423 // https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-suppressederror 424 static bool SuppressedError(JSContext* cx, unsigned argc, Value* vp) { 425 CallArgs args = CallArgsFromVp(argc, vp); 426 427 mozilla::DebugOnly<JSExnType> exnType = 428 JSExnType(args.callee().as<JSFunction>().getExtendedSlot(0).toInt32()); 429 430 MOZ_ASSERT(exnType == JSEXN_SUPPRESSEDERR); 431 432 // Step 1. If NewTarget is undefined, let newTarget be the active function 433 // object; else let newTarget be NewTarget. 434 // Step 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, 435 // "%SuppressedError.prototype%", « [[ErrorData]] »). 436 JS::Rooted<JSObject*> proto(cx); 437 438 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_SuppressedError, 439 &proto)) { 440 return false; 441 } 442 443 // Step 3. If message is not undefined, then 444 // Step 3.a. Let messageString be ? ToString(message). 445 // Step 3.b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", 446 // messageString). 447 JS::Rooted<ErrorObject*> obj( 448 cx, CreateErrorObject(cx, args, 2, JSEXN_SUPPRESSEDERR, proto)); 449 450 if (!obj) { 451 return false; 452 } 453 454 // Step 4. Perform CreateNonEnumerableDataPropertyOrThrow(O, "error", error). 455 JS::Rooted<JS::Value> errorVal(cx, args.get(0)); 456 if (!NativeDefineDataProperty(cx, obj, cx->names().error, errorVal, 0)) { 457 return false; 458 } 459 460 // Step 5. Perform CreateNonEnumerableDataPropertyOrThrow(O, "suppressed", 461 // suppressed). 462 JS::Rooted<JS::Value> suppressedVal(cx, args.get(1)); 463 if (!NativeDefineDataProperty(cx, obj, cx->names().suppressed, suppressedVal, 464 0)) { 465 return false; 466 } 467 468 // Step 6. Return O. 469 args.rval().setObject(*obj); 470 return true; 471 } 472 #endif 473 474 /* static */ 475 JSObject* ErrorObject::createProto(JSContext* cx, JSProtoKey key) { 476 JSExnType type = ExnTypeFromProtoKey(key); 477 478 if (type == JSEXN_ERR) { 479 return GlobalObject::createBlankPrototype( 480 cx, cx->global(), &ErrorObject::protoClasses[JSEXN_ERR]); 481 } 482 483 RootedObject protoProto( 484 cx, GlobalObject::getOrCreateErrorPrototype(cx, cx->global())); 485 if (!protoProto) { 486 return nullptr; 487 } 488 489 return GlobalObject::createBlankPrototypeInheriting( 490 cx, &ErrorObject::protoClasses[type], protoProto); 491 } 492 493 /* static */ 494 JSObject* ErrorObject::createConstructor(JSContext* cx, JSProtoKey key) { 495 JSExnType type = ExnTypeFromProtoKey(key); 496 RootedObject ctor(cx); 497 498 if (type == JSEXN_ERR) { 499 ctor = GenericCreateConstructor<Error, 1, gc::AllocKind::FUNCTION_EXTENDED>( 500 cx, key); 501 } else { 502 RootedFunction proto( 503 cx, GlobalObject::getOrCreateErrorConstructor(cx, cx->global())); 504 if (!proto) { 505 return nullptr; 506 } 507 508 Native native; 509 unsigned nargs; 510 if (type == JSEXN_AGGREGATEERR) { 511 native = AggregateError; 512 nargs = 2; 513 } 514 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 515 else if (type == JSEXN_SUPPRESSEDERR) { 516 native = SuppressedError; 517 nargs = 3; 518 } 519 #endif 520 else { 521 native = Error; 522 nargs = 1; 523 } 524 525 ctor = 526 NewFunctionWithProto(cx, native, nargs, FunctionFlags::NATIVE_CTOR, 527 nullptr, ClassName(key, cx), proto, 528 gc::AllocKind::FUNCTION_EXTENDED, TenuredObject); 529 } 530 531 if (!ctor) { 532 return nullptr; 533 } 534 535 ctor->as<JSFunction>().setExtendedSlot(0, Int32Value(type)); 536 return ctor; 537 } 538 539 /* static */ 540 SharedShape* js::ErrorObject::assignInitialShape(JSContext* cx, 541 Handle<ErrorObject*> obj) { 542 MOZ_ASSERT(obj->empty()); 543 544 constexpr PropertyFlags propFlags = {PropertyFlag::Configurable, 545 PropertyFlag::Writable}; 546 547 if (!NativeObject::addPropertyInReservedSlot(cx, obj, cx->names().fileName, 548 FILENAME_SLOT, propFlags)) { 549 return nullptr; 550 } 551 552 if (!NativeObject::addPropertyInReservedSlot(cx, obj, cx->names().lineNumber, 553 LINENUMBER_SLOT, propFlags)) { 554 return nullptr; 555 } 556 557 if (!NativeObject::addPropertyInReservedSlot( 558 cx, obj, cx->names().columnNumber, COLUMNNUMBER_SLOT, propFlags)) { 559 return nullptr; 560 } 561 562 return obj->sharedShape(); 563 } 564 565 /* static */ 566 bool js::ErrorObject::init(JSContext* cx, Handle<ErrorObject*> obj, 567 JSExnType type, UniquePtr<JSErrorReport> errorReport, 568 HandleString fileName, HandleObject stack, 569 uint32_t sourceId, uint32_t lineNumber, 570 JS::ColumnNumberOneOrigin columnNumber, 571 HandleString message, 572 Handle<mozilla::Maybe<JS::Value>> cause) { 573 MOZ_ASSERT(JSEXN_ERR <= type && type < JSEXN_ERROR_LIMIT); 574 AssertObjectIsSavedFrameOrWrapper(cx, stack); 575 cx->check(obj, stack); 576 577 // Null out early in case of error, for exn_finalize's sake. 578 obj->initReservedSlot(ERROR_REPORT_SLOT, PrivateValue(nullptr)); 579 580 if (!SharedShape::ensureInitialCustomShape<ErrorObject>(cx, obj)) { 581 return false; 582 } 583 584 // The .message property isn't part of the initial shape because it's 585 // present in some error objects -- |Error.prototype|, |new Error("f")|, 586 // |new Error("")| -- but not in others -- |new Error(undefined)|, 587 // |new Error()|. 588 if (message) { 589 constexpr PropertyFlags propFlags = {PropertyFlag::Configurable, 590 PropertyFlag::Writable}; 591 if (!NativeObject::addPropertyInReservedSlot(cx, obj, cx->names().message, 592 MESSAGE_SLOT, propFlags)) { 593 return false; 594 } 595 } 596 597 // Similar to the .message property, .cause is present only in some error 598 // objects -- |new Error("f", {cause: cause})| -- but not in other -- 599 // |Error.prototype|, |new Error()|, |new Error("f")|. 600 if (cause.isSome()) { 601 constexpr PropertyFlags propFlags = {PropertyFlag::Configurable, 602 PropertyFlag::Writable}; 603 if (!NativeObject::addPropertyInReservedSlot(cx, obj, cx->names().cause, 604 CAUSE_SLOT, propFlags)) { 605 return false; 606 } 607 } 608 609 MOZ_ASSERT(obj->lookupPure(NameToId(cx->names().fileName))->slot() == 610 FILENAME_SLOT); 611 MOZ_ASSERT(obj->lookupPure(NameToId(cx->names().lineNumber))->slot() == 612 LINENUMBER_SLOT); 613 MOZ_ASSERT(obj->lookupPure(NameToId(cx->names().columnNumber))->slot() == 614 COLUMNNUMBER_SLOT); 615 MOZ_ASSERT_IF( 616 message, 617 obj->lookupPure(NameToId(cx->names().message))->slot() == MESSAGE_SLOT); 618 MOZ_ASSERT_IF( 619 cause.isSome(), 620 obj->lookupPure(NameToId(cx->names().cause))->slot() == CAUSE_SLOT); 621 622 JSErrorReport* report = errorReport.release(); 623 obj->initReservedSlot(STACK_SLOT, ObjectOrNullValue(stack)); 624 obj->setReservedSlot(ERROR_REPORT_SLOT, PrivateValue(report)); 625 obj->initReservedSlot(FILENAME_SLOT, StringValue(fileName)); 626 obj->initReservedSlot(LINENUMBER_SLOT, Int32Value(lineNumber)); 627 obj->initReservedSlot(COLUMNNUMBER_SLOT, 628 Int32Value(columnNumber.oneOriginValue())); 629 if (message) { 630 obj->initReservedSlot(MESSAGE_SLOT, StringValue(message)); 631 } 632 if (cause.isSome()) { 633 obj->initReservedSlot(CAUSE_SLOT, *cause.get()); 634 } else { 635 obj->initReservedSlot(CAUSE_SLOT, MagicValue(JS_ERROR_WITHOUT_CAUSE)); 636 } 637 obj->initReservedSlot(SOURCEID_SLOT, Int32Value(sourceId)); 638 if (obj->mightBeWasmTrap()) { 639 MOZ_ASSERT(JSCLASS_RESERVED_SLOTS(obj->getClass()) > WASM_TRAP_SLOT); 640 obj->initReservedSlot(WASM_TRAP_SLOT, BooleanValue(false)); 641 } 642 643 return true; 644 } 645 646 /* static */ 647 ErrorObject* js::ErrorObject::create(JSContext* cx, JSExnType errorType, 648 HandleObject stack, HandleString fileName, 649 uint32_t sourceId, uint32_t lineNumber, 650 JS::ColumnNumberOneOrigin columnNumber, 651 UniquePtr<JSErrorReport> report, 652 HandleString message, 653 Handle<mozilla::Maybe<JS::Value>> cause, 654 HandleObject protoArg /* = nullptr */) { 655 AssertObjectIsSavedFrameOrWrapper(cx, stack); 656 657 RootedObject proto(cx, protoArg); 658 if (!proto) { 659 proto = GlobalObject::getOrCreateCustomErrorPrototype(cx, cx->global(), 660 errorType); 661 if (!proto) { 662 return nullptr; 663 } 664 } 665 666 Rooted<ErrorObject*> errObject(cx); 667 { 668 const JSClass* clasp = ErrorObject::classForType(errorType); 669 JSObject* obj = NewObjectWithGivenProto(cx, clasp, proto); 670 if (!obj) { 671 return nullptr; 672 } 673 errObject = &obj->as<ErrorObject>(); 674 } 675 676 if (!ErrorObject::init(cx, errObject, errorType, std::move(report), fileName, 677 stack, sourceId, lineNumber, columnNumber, message, 678 cause)) { 679 return nullptr; 680 } 681 682 return errObject; 683 } 684 685 JSErrorReport* js::ErrorObject::getOrCreateErrorReport(JSContext* cx) { 686 if (JSErrorReport* r = getErrorReport()) { 687 return r; 688 } 689 690 // We build an error report on the stack and then use CopyErrorReport to do 691 // the nitty-gritty malloc stuff. 692 JSErrorReport report; 693 694 // Type. 695 JSExnType type_ = type(); 696 report.exnType = type_; 697 698 // Filename. 699 RootedString filename(cx, fileName(cx)); 700 UniqueChars filenameStr = JS_EncodeStringToUTF8(cx, filename); 701 if (!filenameStr) { 702 return nullptr; 703 } 704 report.filename = JS::ConstUTF8CharsZ(filenameStr.get()); 705 706 // Coordinates. 707 report.sourceId = sourceId(); 708 report.lineno = lineNumber(); 709 report.column = columnNumber(); 710 711 // Message. Note that |new Error()| will result in an undefined |message| 712 // slot, so we need to explicitly substitute the empty string in that case. 713 RootedString message(cx, getMessage()); 714 if (!message) { 715 message = cx->runtime()->emptyString; 716 } 717 718 UniqueChars utf8 = StringToNewUTF8CharsZ(cx, *message); 719 if (!utf8) { 720 return nullptr; 721 } 722 report.initOwnedMessage(utf8.release()); 723 724 // Cache and return. 725 UniquePtr<JSErrorReport> copy = CopyErrorReport(cx, &report); 726 if (!copy) { 727 return nullptr; 728 } 729 setReservedSlot(ERROR_REPORT_SLOT, PrivateValue(copy.get())); 730 return copy.release(); 731 } 732 733 static bool FindErrorInstanceOrPrototype(JSContext* cx, HandleObject obj, 734 MutableHandleObject result) { 735 // Walk up the prototype chain until we find an error object instance or 736 // prototype object. This allows code like: 737 // Object.create(Error.prototype).stack 738 // or 739 // function NYI() { } 740 // NYI.prototype = new Error; 741 // (new NYI).stack 742 // to continue returning stacks that are useless, but at least don't throw. 743 744 RootedObject curr(cx, obj); 745 RootedObject target(cx); 746 do { 747 target = CheckedUnwrapStatic(curr); 748 if (!target) { 749 ReportAccessDenied(cx); 750 return false; 751 } 752 if (IsErrorProtoKey(StandardProtoKeyOrNull(target))) { 753 result.set(target); 754 return true; 755 } 756 757 if (!GetPrototype(cx, curr, &curr)) { 758 return false; 759 } 760 } while (curr); 761 762 // We walked the whole prototype chain and did not find an Error 763 // object. 764 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 765 JSMSG_INCOMPATIBLE_PROTO, "Error", "(get stack)", 766 obj->getClass()->name); 767 return false; 768 } 769 770 static MOZ_ALWAYS_INLINE bool IsObject(HandleValue v) { return v.isObject(); } 771 772 /* static */ 773 bool js::ErrorObject::getStack(JSContext* cx, unsigned argc, Value* vp) { 774 CallArgs args = CallArgsFromVp(argc, vp); 775 // We accept any object here, because of poor-man's subclassing of Error. 776 return CallNonGenericMethod<IsObject, getStack_impl>(cx, args); 777 } 778 779 /* static */ 780 bool js::ErrorObject::getStack_impl(JSContext* cx, const CallArgs& args) { 781 RootedObject thisObj(cx, &args.thisv().toObject()); 782 783 RootedObject obj(cx); 784 if (!FindErrorInstanceOrPrototype(cx, thisObj, &obj)) { 785 return false; 786 } 787 788 if (!obj->is<ErrorObject>()) { 789 args.rval().setString(cx->runtime()->emptyString); 790 return true; 791 } 792 793 // Do frame filtering based on the ErrorObject's principals. This ensures we 794 // don't see chrome frames when chrome code accesses .stack over Xrays. 795 JSPrincipals* principals = obj->as<ErrorObject>().realm()->principals(); 796 797 RootedObject savedFrameObj(cx, obj->as<ErrorObject>().stack()); 798 RootedString stackString(cx); 799 if (!BuildStackString(cx, principals, savedFrameObj, &stackString)) { 800 return false; 801 } 802 803 if (cx->runtime()->stackFormat() == js::StackFormat::V8) { 804 // When emulating V8 stack frames, we also need to prepend the 805 // stringified Error to the stack string. 806 Handle<PropertyName*> name = cx->names().ErrorToStringWithTrailingNewline; 807 FixedInvokeArgs<0> args2(cx); 808 RootedValue rval(cx); 809 if (!CallSelfHostedFunction(cx, name, args.thisv(), args2, &rval)) { 810 return false; 811 } 812 813 if (!rval.isString()) { 814 args.rval().setString(cx->runtime()->emptyString); 815 return true; 816 } 817 818 RootedString stringified(cx, rval.toString()); 819 stackString = ConcatStrings<CanGC>(cx, stringified, stackString); 820 } 821 822 args.rval().setString(stackString); 823 return true; 824 } 825 826 /* static */ 827 bool js::ErrorObject::setStack(JSContext* cx, unsigned argc, Value* vp) { 828 CallArgs args = CallArgsFromVp(argc, vp); 829 // We accept any object here, because of poor-man's subclassing of Error. 830 return CallNonGenericMethod<IsObject, setStack_impl>(cx, args); 831 } 832 833 /* static */ 834 bool js::ErrorObject::setStack_impl(JSContext* cx, const CallArgs& args) { 835 RootedObject thisObj(cx, &args.thisv().toObject()); 836 837 if (!args.requireAtLeast(cx, "(set stack)", 1)) { 838 return false; 839 } 840 841 return DefineDataProperty(cx, thisObj, cx->names().stack, args[0]); 842 } 843 844 void js::ErrorObject::setFromWasmTrap() { 845 MOZ_ASSERT(mightBeWasmTrap()); 846 MOZ_ASSERT(JSCLASS_RESERVED_SLOTS(getClass()) > WASM_TRAP_SLOT); 847 setReservedSlot(WASM_TRAP_SLOT, BooleanValue(true)); 848 } 849 850 JSString* js::ErrorToSource(JSContext* cx, HandleObject obj) { 851 AutoCycleDetector detector(cx, obj); 852 if (!detector.init()) { 853 return nullptr; 854 } 855 if (detector.foundCycle()) { 856 return NewStringCopyZ<CanGC>(cx, "{}"); 857 } 858 859 RootedValue nameVal(cx); 860 RootedString name(cx); 861 if (!GetProperty(cx, obj, obj, cx->names().name, &nameVal) || 862 !(name = ToString<CanGC>(cx, nameVal))) { 863 return nullptr; 864 } 865 866 RootedValue messageVal(cx); 867 RootedString message(cx); 868 if (!GetProperty(cx, obj, obj, cx->names().message, &messageVal) || 869 !(message = ValueToSource(cx, messageVal))) { 870 return nullptr; 871 } 872 873 RootedValue filenameVal(cx); 874 RootedString filename(cx); 875 if (!GetProperty(cx, obj, obj, cx->names().fileName, &filenameVal) || 876 !(filename = ValueToSource(cx, filenameVal))) { 877 return nullptr; 878 } 879 880 RootedValue errorsVal(cx); 881 RootedString errors(cx); 882 bool isAggregateError = obj->is<ErrorObject>() && 883 obj->as<ErrorObject>().type() == JSEXN_AGGREGATEERR; 884 if (isAggregateError) { 885 if (!GetProperty(cx, obj, obj, cx->names().errors, &errorsVal) || 886 !(errors = ValueToSource(cx, errorsVal))) { 887 return nullptr; 888 } 889 } 890 891 RootedValue linenoVal(cx); 892 uint32_t lineno; 893 if (!GetProperty(cx, obj, obj, cx->names().lineNumber, &linenoVal) || 894 !ToUint32(cx, linenoVal, &lineno)) { 895 return nullptr; 896 } 897 898 JSStringBuilder sb(cx); 899 if (!sb.append("(new ") || !sb.append(name) || !sb.append("(")) { 900 return nullptr; 901 } 902 903 if (isAggregateError) { 904 if (!sb.append(errors) || !sb.append(", ")) { 905 return nullptr; 906 } 907 } 908 909 if (!sb.append(message)) { 910 return nullptr; 911 } 912 913 if (!filename->empty()) { 914 if (!sb.append(", ") || !sb.append(filename)) { 915 return nullptr; 916 } 917 } 918 if (lineno != 0) { 919 /* We have a line, but no filename, add empty string */ 920 if (filename->empty() && !sb.append(", \"\"")) { 921 return nullptr; 922 } 923 924 JSString* linenumber = ToString<CanGC>(cx, linenoVal); 925 if (!linenumber) { 926 return nullptr; 927 } 928 if (!sb.append(", ") || !sb.append(linenumber)) { 929 return nullptr; 930 } 931 } 932 933 if (!sb.append("))")) { 934 return nullptr; 935 } 936 937 return sb.finishString(); 938 } 939 940 /* 941 * Return a string that may eval to something similar to the original object. 942 */ 943 static bool exn_toSource(JSContext* cx, unsigned argc, Value* vp) { 944 AutoCheckRecursionLimit recursion(cx); 945 if (!recursion.check(cx)) { 946 return false; 947 } 948 CallArgs args = CallArgsFromVp(argc, vp); 949 950 RootedObject obj(cx, ToObject(cx, args.thisv())); 951 if (!obj) { 952 return false; 953 } 954 955 JSString* str = ErrorToSource(cx, obj); 956 if (!str) { 957 return false; 958 } 959 960 args.rval().setString(str); 961 return true; 962 } 963 964 /** 965 * Error.isError Proposal 966 * Error.isError ( arg ) 967 * https://tc39.es/proposal-is-error/#sec-error.iserror 968 * IsError ( argument ) 969 * https://tc39.es/proposal-is-error/#sec-iserror 970 */ 971 static bool exn_isError(JSContext* cx, unsigned argc, Value* vp) { 972 CallArgs args = CallArgsFromVp(argc, vp); 973 974 // Error.isError ( arg ) 975 // Step 1. Return IsError(arg). 976 977 // IsError ( argument ) 978 // Step 1. If argument is not an Object, return false. 979 if (!args.get(0).isObject()) { 980 args.rval().setBoolean(false); 981 return true; 982 } 983 984 JSObject* unwrappedObject = CheckedUnwrapStatic(&args.get(0).toObject()); 985 if (!unwrappedObject) { 986 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 987 JSMSG_OBJECT_ACCESS_DENIED); 988 return false; 989 } 990 991 if (JS_IsDeadWrapper(unwrappedObject)) { 992 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); 993 return false; 994 } 995 996 // Step 2. If argument has an [[ErrorData]] internal slot, return true. 997 if (unwrappedObject->is<ErrorObject>()) { 998 args.rval().setBoolean(true); 999 return true; 1000 } 1001 if (unwrappedObject->getClass()->isDOMClass()) { 1002 args.rval().setBoolean(cx->runtime()->DOMcallbacks->instanceClassIsError( 1003 unwrappedObject->getClass())); 1004 return true; 1005 } 1006 1007 // Step 3. Return false 1008 args.rval().setBoolean(false); 1009 return true; 1010 } 1011 1012 // The below is the "documentation" from https://v8.dev/docs/stack-trace-api 1013 // 1014 // ## Stack trace collection for custom exceptions 1015 // 1016 // The stack trace mechanism used for built-in errors is implemented using a 1017 // general stack trace collection API that is also available to user scripts. 1018 // The function 1019 // 1020 // Error.captureStackTrace(error, constructorOpt) 1021 // 1022 // adds a stack property to the given error object that yields the stack trace 1023 // at the time captureStackTrace was called. Stack traces collected through 1024 // Error.captureStackTrace are immediately collected, formatted, and attached 1025 // to the given error object. 1026 // 1027 // The optional constructorOpt parameter allows you to pass in a function 1028 // value. When collecting the stack trace all frames above the topmost call to 1029 // this function, including that call, are left out of the stack trace. This 1030 // can be useful to hide implementation details that won’t be useful to the 1031 // user. The usual way of defining a custom error that captures a stack trace 1032 // would be: 1033 // 1034 // function MyError() { 1035 // Error.captureStackTrace(this, MyError); 1036 // // Any other initialization goes here. 1037 // } 1038 // 1039 // Passing in MyError as a second argument means that the constructor call to 1040 // MyError won’t show up in the stack trace. 1041 1042 static bool exn_captureStackTrace(JSContext* cx, unsigned argc, Value* vp) { 1043 CallArgs args = CallArgsFromVp(argc, vp); 1044 const char* callerName = "Error.captureStackTrace"; 1045 1046 if (!args.requireAtLeast(cx, callerName, 1)) { 1047 return false; 1048 } 1049 1050 Rooted<JSObject*> obj(cx, 1051 RequireObjectArg(cx, "`target`", callerName, args[0])); 1052 if (!obj) { 1053 return false; 1054 } 1055 1056 Rooted<JSObject*> caller(cx, nullptr); 1057 if (args.length() > 1 && args[1].isObject() && 1058 args[1].toObject().isCallable()) { 1059 caller = CheckedUnwrapStatic(&args[1].toObject()); 1060 if (!caller) { 1061 ReportAccessDenied(cx); 1062 return false; 1063 } 1064 } 1065 1066 RootedObject stack(cx); 1067 if (!CaptureCurrentStack( 1068 cx, &stack, JS::StackCapture(JS::MaxFrames(MAX_REPORTED_STACK_DEPTH)), 1069 caller)) { 1070 return false; 1071 } 1072 1073 RootedString stackString(cx); 1074 1075 // Do frame filtering based on the current realm, to filter out any 1076 // chrome frames which could exist on the stack. 1077 JSPrincipals* principals = cx->realm()->principals(); 1078 if (!BuildStackString(cx, principals, stack, &stackString)) { 1079 return false; 1080 } 1081 1082 // V8 installs a non-enumerable, configurable getter-setter on the object. 1083 // JSC installs a non-enumerable, configurable, writable value on the 1084 // object. We are following JSC here, not V8. 1085 RootedValue string(cx, StringValue(stackString)); 1086 if (!DefineDataProperty(cx, obj, cx->names().stack, string, 0)) { 1087 return false; 1088 } 1089 1090 args.rval().setUndefined(); 1091 return true; 1092 }