diff --git a/js/public/Value.h b/js/public/Value.h index 46c3995f24..0f5cbae8b0 100644 --- a/js/public/Value.h +++ b/js/public/Value.h @@ -221,6 +221,12 @@ typedef enum JSWhyMagic /** for local use */ JS_GENERIC_MAGIC, + /** + * When an error object is created without the error cause argument, we set + * the error's cause slot to this magic value. + */ + JS_ERROR_WITHOUT_CAUSE, + JS_WHY_MAGIC_COUNT } JSWhyMagic; diff --git a/js/src/jsexn.cpp b/js/src/jsexn.cpp index 29c76333a1..33cd6b928d 100644 --- a/js/src/jsexn.cpp +++ b/js/src/jsexn.cpp @@ -10,6 +10,7 @@ #include "jsexn.h" #include "mozilla/ArrayUtils.h" +#include "mozilla/Maybe.h" #include "mozilla/PodOperations.h" #include "mozilla/Sprintf.h" @@ -329,6 +330,9 @@ js::ErrorToException(JSContext* cx, JSErrorReport* reportp, uint32_t lineNumber = reportp->lineno; uint32_t columnNumber = reportp->column; + // Error reports don't provide a |cause|, so we default to |Nothing| here. + auto cause = JS::NothingHandleValue; + RootedObject stack(cx); if (!CaptureStack(cx, &stack)) return; @@ -338,7 +342,7 @@ js::ErrorToException(JSContext* cx, JSErrorReport* reportp, return; RootedObject errObject(cx, ErrorObject::create(cx, exnType, stack, fileName, - lineNumber, columnNumber, &report, messageStr)); + lineNumber, columnNumber, &report, messageStr, cause)); if (!errObject) return; @@ -660,13 +664,21 @@ js::CopyErrorObject(JSContext* cx, Handle err) RootedObject stack(cx, err->stack()); if (!cx->compartment()->wrap(cx, &stack)) return nullptr; + Rooted> cause(cx, mozilla::Nothing()); + if (auto maybeCause = err->getCause()) { + RootedValue errorCause(cx, maybeCause.value()); + if (!cx->compartment()->wrap(cx, &errorCause)) { + return nullptr; + } + cause = mozilla::Some(errorCause.get()); + } uint32_t lineNumber = err->lineNumber(); uint32_t columnNumber = err->columnNumber(); JSExnType errorType = err->type(); // Create the Error object. return ErrorObject::create(cx, errorType, stack, fileName, - lineNumber, columnNumber, ©Report, message); + lineNumber, columnNumber, ©Report, message, cause); } JS_PUBLIC_API(bool) @@ -681,9 +693,13 @@ JS::CreateError(JSContext* cx, JSExnType type, HandleObject stack, HandleString if (report) rep = CopyErrorReport(cx, report); + // The public API doesn't (yet) support a |cause| argument, so we default to + // |Nothing()| here. + auto cause = JS::NothingHandleValue; + RootedObject obj(cx, js::ErrorObject::create(cx, type, stack, fileName, - lineNumber, columnNumber, &rep, message)); + lineNumber, columnNumber, &rep, message, cause)); if (!obj) return false; diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 8489648d6b..13af5c237d 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -67,6 +67,7 @@ macro(case, case_, "case") \ macro(caseFirst, caseFirst, "caseFirst") \ macro(catch, catch_, "catch") \ + macro(cause, cause, "cause") \ macro(class, class_, "class") \ macro(close, close, "close") \ macro(collation, collation, "collation") \ diff --git a/js/src/vm/ErrorObject.cpp b/js/src/vm/ErrorObject.cpp index 7c966e0fe5..ce7ea2cdbc 100644 --- a/js/src/vm/ErrorObject.cpp +++ b/js/src/vm/ErrorObject.cpp @@ -7,6 +7,7 @@ #include "vm/ErrorObject-inl.h" #include "mozilla/DebugOnly.h" +#include "mozilla/Maybe.h" #include "mozilla/Range.h" #include "jsapi.h" @@ -211,12 +212,35 @@ static ErrorObject* CreateErrorObject(JSContext* cx, const CallArgs& args, return nullptr; } + // Don't interpret the two parameters following the message parameter as the + // non-standard fileName and lineNumber arguments when we have an options + // object argument. + bool hasOptions = args.get(messageArg + 1).isObject(); + + Rooted> cause(cx, mozilla::Nothing()); + if (hasOptions) { + RootedObject options(cx, &args[messageArg + 1].toObject()); + + bool hasCause = false; + if (!HasProperty(cx, options, cx->names().cause, &hasCause)) { + return nullptr; + } + + if (hasCause) { + RootedValue causeValue(cx); + if (!GetProperty(cx, options, options, cx->names().cause, &causeValue)) { + return nullptr; + } + cause = mozilla::Some(causeValue.get()); + } + } + /* Find the scripted caller, but only ones we're allowed to know about. */ NonBuiltinFrameIter iter(cx, cx->compartment()->principals()); /* Set the 'fileName' property. */ RootedString fileName(cx); - if (args.length() > messageArg + 1) { + if (!hasOptions && args.length() > messageArg + 1) { fileName = ToString(cx, args[messageArg + 1]); } else { fileName = cx->runtime()->emptyString; @@ -230,7 +254,7 @@ static ErrorObject* CreateErrorObject(JSContext* cx, const CallArgs& args, /* Set the 'lineNumber' property. */ uint32_t lineNumber, columnNumber = 0; - if (args.length() > messageArg + 2) { + if (!hasOptions && args.length() > messageArg + 2) { if (!ToUint32(cx, args[messageArg + 2], &lineNumber)) return nullptr; } else { @@ -246,7 +270,7 @@ static ErrorObject* CreateErrorObject(JSContext* cx, const CallArgs& args, return nullptr; return ErrorObject::create(cx, exnType, stack, fileName, lineNumber, - columnNumber, nullptr, message, proto); + columnNumber, nullptr, message, cause, proto); } static bool Error(JSContext* cx, unsigned argc, Value* vp) @@ -422,7 +446,7 @@ js::ErrorObject::assignInitialShape(ExclusiveContext* cx, Handle o js::ErrorObject::init(JSContext* cx, Handle obj, JSExnType type, ScopedJSFreePtr* errorReport, HandleString fileName, HandleObject stack, uint32_t lineNumber, uint32_t columnNumber, - HandleString message) + HandleString message, Handle> cause) { AssertObjectIsSavedFrameOrWrapper(cx, stack); assertSameCompartment(cx, obj, stack); @@ -453,6 +477,9 @@ js::ErrorObject::init(JSContext* cx, Handle obj, JSExnType type, obj->lookupPure(NameToId(cx->names().message))->slot() == MESSAGE_SLOT); MOZ_ASSERT(JSEXN_ERR <= type && type < JSEXN_LIMIT); + MOZ_ASSERT_IF( + cause.isSome(), + obj->lookupPure(NameToId(cx->names().cause))->slot() == CAUSE_SLOT); JSErrorReport* report = errorReport ? errorReport->forget() : nullptr; obj->initReservedSlot(EXNTYPE_SLOT, Int32Value(type)); @@ -463,6 +490,11 @@ js::ErrorObject::init(JSContext* cx, Handle obj, JSExnType type, obj->initReservedSlot(COLUMNNUMBER_SLOT, Int32Value(columnNumber)); if (message) obj->setSlotWithType(cx, messageShape, StringValue(message)); + if (cause.isSome()) { + obj->initReservedSlot(CAUSE_SLOT, *cause.get()); + } else { + obj->initReservedSlot(CAUSE_SLOT, MagicValue(JS_ERROR_WITHOUT_CAUSE)); + } return true; } @@ -471,6 +503,7 @@ js::ErrorObject::init(JSContext* cx, Handle obj, JSExnType type, js::ErrorObject::create(JSContext* cx, JSExnType errorType, HandleObject stack, HandleString fileName, uint32_t lineNumber, uint32_t columnNumber, ScopedJSFreePtr* report, HandleString message, + Handle> cause, HandleObject protoArg /* = nullptr */) { AssertObjectIsSavedFrameOrWrapper(cx, stack); @@ -492,7 +525,7 @@ js::ErrorObject::create(JSContext* cx, JSExnType errorType, HandleObject stack, } if (!ErrorObject::init(cx, errObject, errorType, report, fileName, stack, - lineNumber, columnNumber, message)) + lineNumber, columnNumber, message, cause)) { return nullptr; } diff --git a/js/src/vm/ErrorObject.h b/js/src/vm/ErrorObject.h index 7842bbfa09..f216c573f7 100644 --- a/js/src/vm/ErrorObject.h +++ b/js/src/vm/ErrorObject.h @@ -7,6 +7,7 @@ #define vm_ErrorObject_h_ #include "mozilla/ArrayUtils.h" +#include "mozilla/Maybe.h" #include "vm/NativeObject.h" #include "vm/SavedStacks.h" @@ -26,7 +27,7 @@ class ErrorObject : public NativeObject static bool init(JSContext* cx, Handle obj, JSExnType type, ScopedJSFreePtr* errorReport, HandleString fileName, HandleObject stack, - uint32_t lineNumber, uint32_t columnNumber, HandleString message); + uint32_t lineNumber, uint32_t columnNumber, HandleString message, Handle> cause); static const ClassSpec classSpecs[JSEXN_ERROR_LIMIT]; static const Class protoClasses[JSEXN_ERROR_LIMIT]; @@ -39,8 +40,9 @@ class ErrorObject : public NativeObject static const uint32_t LINENUMBER_SLOT = FILENAME_SLOT + 1; static const uint32_t COLUMNNUMBER_SLOT = LINENUMBER_SLOT + 1; static const uint32_t MESSAGE_SLOT = COLUMNNUMBER_SLOT + 1; + static const uint32_t CAUSE_SLOT = MESSAGE_SLOT + 1; - static const uint32_t RESERVED_SLOTS = MESSAGE_SLOT + 1; + static const uint32_t RESERVED_SLOTS = CAUSE_SLOT + 1; public: static const Class classes[JSEXN_ERROR_LIMIT]; @@ -61,7 +63,7 @@ class ErrorObject : public NativeObject static ErrorObject* create(JSContext* cx, JSExnType type, HandleObject stack, HandleString fileName, uint32_t lineNumber, uint32_t columnNumber, ScopedJSFreePtr* report, - HandleString message, HandleObject proto = nullptr); + HandleString message, Handle> cause, HandleObject proto = nullptr); /* * Assign the initial error shape to the empty object. (This shape does @@ -94,6 +96,14 @@ class ErrorObject : public NativeObject return slot.isString() ? slot.toString() : nullptr; } + mozilla::Maybe getCause() const { + const auto& value = getReservedSlot(CAUSE_SLOT); + if (value.isMagic(JS_ERROR_WITHOUT_CAUSE)) { + return mozilla::Nothing(); + } + return mozilla::Some(value); + } + // Getter and setter for the Error.prototype.stack accessor. static bool getStack(JSContext* cx, unsigned argc, Value* vp); static bool getStack_impl(JSContext* cx, const CallArgs& args); diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp index d5e1c2acd3..dea2bddc46 100644 --- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -1680,8 +1680,11 @@ Reject(JSContext* cx, const CompileArgs& args, UniqueChars error, Handle