Issue #2304 - Part 3 - Implement the error cause proposal. https://bugzilla.mozilla.org/show_bug.cgi?id=1679653

This commit is contained in:
Brian Smith 2026-03-07 00:41:42 -06:00 committed by OwnedByWuigi
commit 36285d0405
6 changed files with 81 additions and 12 deletions

View file

@ -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;

View file

@ -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<ErrorObject*> err)
RootedObject stack(cx, err->stack());
if (!cx->compartment()->wrap(cx, &stack))
return nullptr;
Rooted<mozilla::Maybe<Value>> 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, &copyReport, message);
lineNumber, columnNumber, &copyReport, 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;

View file

@ -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") \

View file

@ -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<mozilla::Maybe<Value>> 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<CanGC>(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<ErrorObject*> o
js::ErrorObject::init(JSContext* cx, Handle<ErrorObject*> obj, JSExnType type,
ScopedJSFreePtr<JSErrorReport>* errorReport, HandleString fileName,
HandleObject stack, uint32_t lineNumber, uint32_t columnNumber,
HandleString message)
HandleString message, Handle<mozilla::Maybe<JS::Value>> cause)
{
AssertObjectIsSavedFrameOrWrapper(cx, stack);
assertSameCompartment(cx, obj, stack);
@ -453,6 +477,9 @@ js::ErrorObject::init(JSContext* cx, Handle<ErrorObject*> 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<ErrorObject*> 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<ErrorObject*> obj, JSExnType type,
js::ErrorObject::create(JSContext* cx, JSExnType errorType, HandleObject stack,
HandleString fileName, uint32_t lineNumber, uint32_t columnNumber,
ScopedJSFreePtr<JSErrorReport>* report, HandleString message,
Handle<mozilla::Maybe<JS::Value>> 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;
}

View file

@ -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<ErrorObject*> obj, JSExnType type,
ScopedJSFreePtr<JSErrorReport>* errorReport, HandleString fileName, HandleObject stack,
uint32_t lineNumber, uint32_t columnNumber, HandleString message);
uint32_t lineNumber, uint32_t columnNumber, HandleString message, Handle<mozilla::Maybe<JS::Value>> 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<JSErrorReport>* report,
HandleString message, HandleObject proto = nullptr);
HandleString message, Handle<mozilla::Maybe<JS::Value>> 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<Value> 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);

View file

@ -1680,8 +1680,11 @@ Reject(JSContext* cx, const CompileArgs& args, UniqueChars error, Handle<Promise
if (!message)
return false;
// There's no error |cause| available here.
auto cause = JS::NothingHandleValue;
RootedObject errorObj(cx,
ErrorObject::create(cx, JSEXN_WASMCOMPILEERROR, stack, filename, line, column, nullptr, message));
ErrorObject::create(cx, JSEXN_WASMCOMPILEERROR, stack, filename, line, column, nullptr, message, cause));
if (!errorObj)
return false;