Issue #2089 - Implement AggregateError

Based-on: m-c 1568903/4,1641355(partial),1652148,1643397
This commit is contained in:
Martok 2023-01-27 19:45:13 +01:00 committed by roytam1
commit 61c99bf6cd
11 changed files with 259 additions and 55 deletions

View file

@ -0,0 +1,5 @@
// |jit-test| skip-if: !('oomTest' in this)
oomTest(() => {
new AggregateError([]);
});

View file

@ -642,6 +642,7 @@ typedef enum JSExnType {
JSEXN_ERR,
JSEXN_FIRST = JSEXN_ERR,
JSEXN_INTERNALERR,
JSEXN_AGGREGATEERR,
JSEXN_EVALERR,
JSEXN_RANGEERR,
JSEXN_REFERENCEERR,

View file

@ -62,6 +62,7 @@ CopyErrorObject(JSContext* cx, JS::Handle<ErrorObject*> errobj);
static_assert(JSEXN_ERR == 0 &&
JSProto_Error + JSEXN_INTERNALERR == JSProto_InternalError &&
JSProto_Error + JSEXN_AGGREGATEERR == JSProto_AggregateError &&
JSProto_Error + JSEXN_EVALERR == JSProto_EvalError &&
JSProto_Error + JSEXN_RANGEERR == JSProto_RangeError &&
JSProto_Error + JSEXN_REFERENCEERR == JSProto_ReferenceError &&

View file

@ -69,6 +69,7 @@
real(RegExp, InitViaClassSpec, OCLASP(RegExp)) \
real(Error, InitViaClassSpec, ERROR_CLASP(JSEXN_ERR)) \
real(InternalError, InitViaClassSpec, ERROR_CLASP(JSEXN_INTERNALERR)) \
real(AggregateError, InitViaClassSpec, ERROR_CLASP(JSEXN_AGGREGATEERR)) \
real(EvalError, InitViaClassSpec, ERROR_CLASP(JSEXN_EVALERR)) \
real(RangeError, InitViaClassSpec, ERROR_CLASP(JSEXN_RANGEERR)) \
real(ReferenceError, InitViaClassSpec, ERROR_CLASP(JSEXN_REFERENCEERR)) \

View file

@ -0,0 +1,82 @@
// |reftest| skip-if(release_or_beta)
assertEq(typeof AggregateError, "function");
assertEq(Object.getPrototypeOf(AggregateError), Error);
assertEq(AggregateError.name, "AggregateError");
assertEq(AggregateError.length, 2);
assertEq(Object.getPrototypeOf(AggregateError.prototype), Error.prototype);
assertEq(AggregateError.prototype.name, "AggregateError");
assertEq(AggregateError.prototype.message, "");
// The |errors| argument is mandatory.
assertThrowsInstanceOf(() => new AggregateError(), TypeError);
assertThrowsInstanceOf(() => AggregateError(), TypeError);
// The .errors data property is an array object.
{
let err = new AggregateError([]);
let {errors} = err;
assertEq(Array.isArray(errors), true);
assertEq(errors.length, 0);
// The errors object is modifiable.
errors.push(123);
assertEq(errors.length, 1);
assertEq(errors[0], 123);
assertEq(err.errors[0], 123);
// The property is writable.
err.errors = undefined;
assertEq(err.errors, undefined);
}
// The errors argument can be any iterable.
{
function* g() { yield* [1, 2, 3]; }
let {errors} = new AggregateError(g());
assertEqArray(errors, [1, 2, 3]);
}
// The message property is populated by the second argument.
{
let err;
err = new AggregateError([]);
assertEq(err.message, "");
err = new AggregateError([], "my message");
assertEq(err.message, "my message");
}
{
assertEq("errors" in AggregateError.prototype, false);
const {
configurable,
enumerable,
value,
writable
} = Object.getOwnPropertyDescriptor(new AggregateError([]), "errors");
assertEq(configurable, true);
assertEq(enumerable, false);
assertEq(writable, true);
assertEq(value.length, 0);
const g = newGlobal();
let obj = {};
let errors = new g.AggregateError([obj]).errors;
assertEq(errors.length, 1);
assertEq(errors[0], obj);
// The prototype is |g.Array.prototype| in the cross-compartment case.
let proto = Object.getPrototypeOf(errors);
assertEq(proto === g.Array.prototype, true);
}
if (typeof reportCompare === "function")
reportCompare(0, 0);

View file

@ -116,6 +116,7 @@
macro(enumerate, enumerate, "enumerate") \
macro(era, era, "era") \
macro(ErrorToStringWithTrailingNewline, ErrorToStringWithTrailingNewline, "ErrorToStringWithTrailingNewline") \
macro(errors, errors, "errors") \
macro(escape, escape, "escape") \
macro(eval, eval, "eval") \
macro(exec, exec, "exec") \

View file

@ -6,8 +6,11 @@
#include "vm/ErrorObject-inl.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Range.h"
#include "jsapi.h"
#include "jsarray.h"
#include "jsexn.h"
#include "js/CallArgs.h"
@ -18,6 +21,7 @@
#include "jsobjinlines.h"
#include "vm/ArrayObject-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/SavedStacks-inl.h"
#include "vm/Shape-inl.h"
@ -37,6 +41,7 @@ ErrorObject::protoClasses[JSEXN_ERROR_LIMIT] = {
IMPLEMENT_ERROR_PROTO_CLASS(Error),
IMPLEMENT_ERROR_PROTO_CLASS(InternalError),
IMPLEMENT_ERROR_PROTO_CLASS(AggregateError),
IMPLEMENT_ERROR_PROTO_CLASS(EvalError),
IMPLEMENT_ERROR_PROTO_CLASS(RangeError),
IMPLEMENT_ERROR_PROTO_CLASS(ReferenceError),
@ -60,33 +65,37 @@ static const JSFunctionSpec error_methods[] = {
JS_FS_END
};
// Error.prototype and NativeError.prototype have own .message and .name
// properties.
#define COMMON_ERROR_PROPERTIES(name) \
JS_STRING_PS("message", "", 0), \
JS_STRING_PS("name", #name, 0)
static const JSPropertySpec error_properties[] = {
JS_STRING_PS("message", "", 0),
JS_STRING_PS("name", "Error", 0),
COMMON_ERROR_PROPERTIES(Error),
// Only Error.prototype has .stack!
JS_PSGS("stack", ErrorObject::getStack, ErrorObject::setStack, 0),
JS_PS_END
};
#define IMPLEMENT_ERROR_PROPERTIES(name) \
{ \
JS_STRING_PS("message", "", 0), \
JS_STRING_PS("name", #name, 0), \
JS_PS_END \
}
#define IMPLEMENT_NATIVE_ERROR_PROPERTIES(name) \
static const JSPropertySpec name##_properties[] = { \
COMMON_ERROR_PROPERTIES(name), \
JS_PS_END \
};
static const JSPropertySpec other_error_properties[JSEXN_ERROR_LIMIT - 1][3] = {
IMPLEMENT_ERROR_PROPERTIES(InternalError),
IMPLEMENT_ERROR_PROPERTIES(EvalError),
IMPLEMENT_ERROR_PROPERTIES(RangeError),
IMPLEMENT_ERROR_PROPERTIES(ReferenceError),
IMPLEMENT_ERROR_PROPERTIES(SyntaxError),
IMPLEMENT_ERROR_PROPERTIES(TypeError),
IMPLEMENT_ERROR_PROPERTIES(URIError),
IMPLEMENT_ERROR_PROPERTIES(DebuggeeWouldRun),
IMPLEMENT_ERROR_PROPERTIES(CompileError),
IMPLEMENT_ERROR_PROPERTIES(RuntimeError)
};
IMPLEMENT_NATIVE_ERROR_PROPERTIES(InternalError)
IMPLEMENT_NATIVE_ERROR_PROPERTIES(AggregateError)
IMPLEMENT_NATIVE_ERROR_PROPERTIES(EvalError)
IMPLEMENT_NATIVE_ERROR_PROPERTIES(RangeError)
IMPLEMENT_NATIVE_ERROR_PROPERTIES(ReferenceError)
IMPLEMENT_NATIVE_ERROR_PROPERTIES(SyntaxError)
IMPLEMENT_NATIVE_ERROR_PROPERTIES(TypeError)
IMPLEMENT_NATIVE_ERROR_PROPERTIES(URIError)
IMPLEMENT_NATIVE_ERROR_PROPERTIES(DebuggeeWouldRun)
IMPLEMENT_NATIVE_ERROR_PROPERTIES(CompileError)
IMPLEMENT_NATIVE_ERROR_PROPERTIES(LinkError)
IMPLEMENT_NATIVE_ERROR_PROPERTIES(RuntimeError)
#define IMPLEMENT_NATIVE_ERROR_SPEC(name) \
{ \
@ -95,7 +104,7 @@ static const JSPropertySpec other_error_properties[JSEXN_ERROR_LIMIT - 1][3] = {
nullptr, \
nullptr, \
nullptr, \
other_error_properties[JSProto_##name - JSProto_Error - 1], \
name##_properties, \
nullptr, \
JSProto_Error \
}
@ -107,7 +116,7 @@ static const JSPropertySpec other_error_properties[JSEXN_ERROR_LIMIT - 1][3] = {
nullptr, \
nullptr, \
nullptr, \
other_error_properties[JSProto_##name - JSProto_Error - 1], \
name##_properties, \
nullptr, \
JSProto_Error | ClassSpec::DontDefineConstructor \
}
@ -124,6 +133,7 @@ ErrorObject::classSpecs[JSEXN_ERROR_LIMIT] = {
},
IMPLEMENT_NATIVE_ERROR_SPEC(InternalError),
IMPLEMENT_NATIVE_ERROR_SPEC(AggregateError),
IMPLEMENT_NATIVE_ERROR_SPEC(EvalError),
IMPLEMENT_NATIVE_ERROR_SPEC(RangeError),
IMPLEMENT_NATIVE_ERROR_SPEC(ReferenceError),
@ -136,13 +146,13 @@ ErrorObject::classSpecs[JSEXN_ERROR_LIMIT] = {
IMPLEMENT_NONGLOBAL_ERROR_SPEC(RuntimeError)
};
#define IMPLEMENT_ERROR_CLASS(name) \
{ \
js_Error_str, /* yes, really */ \
JSCLASS_HAS_CACHED_PROTO(JSProto_##name) | \
#define IMPLEMENT_ERROR_CLASS(name) \
{ \
js_Error_str, /* yes, really */ \
JSCLASS_HAS_CACHED_PROTO(JSProto_##name) | \
JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS) | \
JSCLASS_BACKGROUND_FINALIZE, \
&ErrorObjectClassOps, \
JSCLASS_BACKGROUND_FINALIZE, \
&ErrorObjectClassOps, \
&ErrorObject::classSpecs[JSProto_##name - JSProto_Error ] \
}
@ -168,6 +178,7 @@ const Class
ErrorObject::classes[JSEXN_ERROR_LIMIT] = {
IMPLEMENT_ERROR_CLASS(Error),
IMPLEMENT_ERROR_CLASS(InternalError),
IMPLEMENT_ERROR_CLASS(AggregateError),
IMPLEMENT_ERROR_CLASS(EvalError),
IMPLEMENT_ERROR_CLASS(RangeError),
IMPLEMENT_ERROR_CLASS(ReferenceError),
@ -188,22 +199,16 @@ exn_finalize(FreeOp* fop, JSObject* obj)
fop->delete_(report);
}
bool
Error(JSContext* cx, unsigned argc, Value* vp)
static ErrorObject* CreateErrorObject(JSContext* cx, const CallArgs& args,
unsigned messageArg, JSExnType exnType,
HandleObject proto)
{
CallArgs args = CallArgsFromVp(argc, vp);
// ES6 19.5.1.1 mandates the .prototype lookup happens before the toString
RootedObject proto(cx);
if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
return false;
/* Compute the error message, if any. */
RootedString message(cx, nullptr);
if (args.hasDefined(0)) {
message = ToString<CanGC>(cx, args[0]);
if (args.hasDefined(messageArg)) {
message = ToString<CanGC>(cx, args[messageArg]);
if (!message)
return false;
return nullptr;
}
/* Find the scripted caller, but only ones we're allowed to know about. */
@ -211,8 +216,8 @@ Error(JSContext* cx, unsigned argc, Value* vp)
/* Set the 'fileName' property. */
RootedString fileName(cx);
if (args.length() > 1) {
fileName = ToString<CanGC>(cx, args[1]);
if (args.length() > messageArg + 1) {
fileName = ToString<CanGC>(cx, args[messageArg + 1]);
} else {
fileName = cx->runtime()->emptyString;
if (!iter.done()) {
@ -221,13 +226,13 @@ Error(JSContext* cx, unsigned argc, Value* vp)
}
}
if (!fileName)
return false;
return nullptr;
/* Set the 'lineNumber' property. */
uint32_t lineNumber, columnNumber = 0;
if (args.length() > 2) {
if (!ToUint32(cx, args[2], &lineNumber))
return false;
if (args.length() > messageArg + 2) {
if (!ToUint32(cx, args[messageArg + 2], &lineNumber))
return nullptr;
} else {
lineNumber = iter.done() ? 0 : iter.computeLine(&columnNumber);
// XXX: Make the column 1-based as in other browsers, instead of 0-based
@ -238,7 +243,15 @@ Error(JSContext* cx, unsigned argc, Value* vp)
RootedObject stack(cx);
if (!CaptureStack(cx, &stack))
return false;
return nullptr;
return ErrorObject::create(cx, exnType, stack, fileName, lineNumber,
columnNumber, nullptr, message, proto);
}
static bool Error(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
/*
* ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when
@ -248,8 +261,15 @@ Error(JSContext* cx, unsigned argc, Value* vp)
*/
JSExnType exnType = JSExnType(args.callee().as<JSFunction>().getExtendedSlot(0).toInt32());
RootedObject obj(cx, ErrorObject::create(cx, exnType, stack, fileName,
lineNumber, columnNumber, nullptr, message, proto));
MOZ_ASSERT(exnType != JSEXN_AGGREGATEERR,
"AggregateError has its own constructor function");
// ES6 19.5.1.1 mandates the .prototype lookup happens before the toString
RootedObject proto(cx);
if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
return false;
auto* obj = CreateErrorObject(cx, args, 0, exnType, proto);
if (!obj)
return false;
@ -257,6 +277,80 @@ Error(JSContext* cx, unsigned argc, Value* vp)
return true;
}
static ArrayObject* IterableToArray(JSContext* cx, HandleValue iterable)
{
JS::ForOfIterator iterator(cx);
if (!iterator.init(iterable, JS::ForOfIterator::ThrowOnNonIterable)) {
return nullptr;
}
RootedArrayObject array(cx, NewDenseEmptyArray(cx));
if (!array) {
return nullptr;
}
RootedValue nextValue(cx);
while (true) {
bool done;
if (!iterator.next(&nextValue, &done)) {
return nullptr;
}
if (done) {
return array;
}
if (!NewbornArrayPush(cx, array, nextValue)) {
return nullptr;
}
}
}
// AggregateError ( errors, message )
static bool AggregateError(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
mozilla::DebugOnly<JSExnType> exnType =
JSExnType(args.callee().as<JSFunction>().getExtendedSlot(0).toInt32());
MOZ_ASSERT(exnType == JSEXN_AGGREGATEERR);
// Steps 1-2. (9.1.13 OrdinaryCreateFromConstructor, steps 1-2).
RootedObject proto(cx);
if (!GetPrototypeFromCallableConstructor(cx, args, &proto)) {
return false;
}
// TypeError anyway, but this gives a better error message.
if (!args.requireAtLeast(cx, "AggregateError", 1)) {
return false;
}
// 9.1.13 OrdinaryCreateFromConstructor, step 3.
// Step 3.
Rooted<ErrorObject*> obj(
cx, CreateErrorObject(cx, args, 1, JSEXN_AGGREGATEERR, proto));
if (!obj) {
return false;
}
// Step 4.
RootedArrayObject errorsList(cx, IterableToArray(cx, args.get(0)));
if (!errorsList) {
return false;
}
// Step 5.
RootedValue errorsVal(cx, JS::ObjectValue(*errorsList));
if (!NativeDefineDataProperty(cx, obj, cx->names().errors, errorsVal, 0)) {
return false;
}
// Step 6.
args.rval().setObject(*obj);
return true;
}
/* static */ JSObject*
ErrorObject::createProto(JSContext* cx, JSProtoKey key)
{
@ -289,9 +383,20 @@ ErrorObject::createConstructor(JSContext* cx, JSProtoKey key)
if (!proto)
return nullptr;
ctor = NewFunctionWithProto(cx, Error, 1, JSFunction::NATIVE_CTOR, nullptr,
ClassName(key, cx), proto, gc::AllocKind::FUNCTION_EXTENDED,
SingletonObject);
Native native;
unsigned nargs;
if (type == JSEXN_AGGREGATEERR) {
native = AggregateError;
nargs = 2;
} else {
native = Error;
nargs = 1;
}
ctor =
NewFunctionWithProto(cx, native, nargs, JSFunction::NATIVE_CTOR,
nullptr, ClassName(key, cx), proto,
gc::AllocKind::FUNCTION_EXTENDED, SingletonObject);
}
if (!ctor)

View file

@ -13,6 +13,7 @@
#include "vm/Shape.h"
namespace js {
class ArrayObject;
class ErrorObject : public NativeObject
{

View file

@ -108,6 +108,7 @@ const char* const XPCJSContext::mStrings[] = {
"columnNumber", // IDX_COLUMNNUMBER
"stack", // IDX_STACK
"message", // IDX_MESSAGE
"errors", // IDX_ERRORS
"lastIndex" // IDX_LASTINDEX
};

View file

@ -495,6 +495,7 @@ public:
IDX_COLUMNNUMBER ,
IDX_STACK ,
IDX_MESSAGE ,
IDX_ERRORS ,
IDX_LASTINDEX ,
IDX_TOTAL_COUNT // just a count of the above
};

View file

@ -41,11 +41,11 @@ using namespace XrayUtils;
#define Between(x, a, b) (a <= x && x <= b)
static_assert(JSProto_URIError - JSProto_Error == 7, "New prototype added in error object range");
static_assert(JSProto_URIError - JSProto_Error == 8, "New prototype added in error object range");
#define AssertErrorObjectKeyInBounds(key) \
static_assert(Between(key, JSProto_Error, JSProto_URIError), "We depend on jsprototypes.h ordering here");
MOZ_FOR_EACH(AssertErrorObjectKeyInBounds, (),
(JSProto_Error, JSProto_InternalError, JSProto_EvalError, JSProto_RangeError,
(JSProto_Error, JSProto_InternalError, JSProto_AggregateError, JSProto_EvalError, JSProto_RangeError,
JSProto_ReferenceError, JSProto_SyntaxError, JSProto_TypeError, JSProto_URIError));
static_assert(JSProto_Uint8ClampedArray - JSProto_Int8Array == 8, "New prototype added in typed array range");
@ -608,6 +608,11 @@ JSXrayTraits::resolveOwnProperty(JSContext* cx, const Wrapper& jsWrapper,
FillPropertyDescriptor(desc, nullptr, 0, UndefinedValue());
return true;
}
if (key == JSProto_AggregateError &&
id == GetJSIDByIndex(cx, XPCJSContext::IDX_ERRORS)) {
return getOwnPropertyFromWrapperIfSafe(cx, wrapper, id, desc);
}
} else if (key == JSProto_RegExp) {
if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_LASTINDEX))
return getOwnPropertyFromWrapperIfSafe(cx, wrapper, id, desc);