diff options
author | Martok <martok@martoks-place.de> | 2023-01-27 19:45:13 +0100 |
---|---|---|
committer | Martok <martok@martoks-place.de> | 2023-02-01 00:02:44 +0100 |
commit | 0148ae5a60655a36d5664a961419defa34295fa9 (patch) | |
tree | 9849ad90afa93b50f8ec526e6ec96b81a6aac68b | |
parent | ec38c883c6f2ecda97b43cb585970f76e70d239a (diff) | |
download | uxp-0148ae5a60655a36d5664a961419defa34295fa9.tar.gz |
Issue #2089 - Implement AggregateError
Based-on: m-c 1568903/4,1641355(partial),1652148,1643397
-rw-r--r-- | js/src/jit-test/tests/auto-regress/bug1652148.js | 5 | ||||
-rw-r--r-- | js/src/jsapi.h | 1 | ||||
-rw-r--r-- | js/src/jsexn.h | 1 | ||||
-rw-r--r-- | js/src/jsprototypes.h | 1 | ||||
-rw-r--r-- | js/src/tests/non262/Error/AggregateError.js | 82 | ||||
-rw-r--r-- | js/src/vm/CommonPropertyNames.h | 1 | ||||
-rw-r--r-- | js/src/vm/ErrorObject.cpp | 211 | ||||
-rw-r--r-- | js/src/vm/ErrorObject.h | 1 | ||||
-rw-r--r-- | js/xpconnect/src/XPCJSContext.cpp | 1 | ||||
-rw-r--r-- | js/xpconnect/src/xpcprivate.h | 1 | ||||
-rw-r--r-- | js/xpconnect/wrappers/XrayWrapper.cpp | 9 |
11 files changed, 259 insertions, 55 deletions
diff --git a/js/src/jit-test/tests/auto-regress/bug1652148.js b/js/src/jit-test/tests/auto-regress/bug1652148.js new file mode 100644 index 0000000000..232957edb6 --- /dev/null +++ b/js/src/jit-test/tests/auto-regress/bug1652148.js @@ -0,0 +1,5 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(() => { + new AggregateError([]); +}); diff --git a/js/src/jsapi.h b/js/src/jsapi.h index b93353f223..42f80952e6 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -642,6 +642,7 @@ typedef enum JSExnType { JSEXN_ERR, JSEXN_FIRST = JSEXN_ERR, JSEXN_INTERNALERR, + JSEXN_AGGREGATEERR, JSEXN_EVALERR, JSEXN_RANGEERR, JSEXN_REFERENCEERR, diff --git a/js/src/jsexn.h b/js/src/jsexn.h index a1ed55b4a1..e3601e0276 100644 --- a/js/src/jsexn.h +++ b/js/src/jsexn.h @@ -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 && diff --git a/js/src/jsprototypes.h b/js/src/jsprototypes.h index 880fc1054e..7de6b0245a 100644 --- a/js/src/jsprototypes.h +++ b/js/src/jsprototypes.h @@ -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)) \ diff --git a/js/src/tests/non262/Error/AggregateError.js b/js/src/tests/non262/Error/AggregateError.js new file mode 100644 index 0000000000..81cf04e9d1 --- /dev/null +++ b/js/src/tests/non262/Error/AggregateError.js @@ -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); diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 57ec80669c..171920447e 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -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") \ diff --git a/js/src/vm/ErrorObject.cpp b/js/src/vm/ErrorObject.cpp index 192b0758db..2fa36089ed 100644 --- a/js/src/vm/ErrorObject.cpp +++ b/js/src/vm/ErrorObject.cpp @@ -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 \ - } - -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) -}; +#define IMPLEMENT_NATIVE_ERROR_PROPERTIES(name) \ + static const JSPropertySpec name##_properties[] = { \ + COMMON_ERROR_PROPERTIES(name), \ + JS_PS_END \ + }; + +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) diff --git a/js/src/vm/ErrorObject.h b/js/src/vm/ErrorObject.h index 6e99dfa2c6..7842bbfa09 100644 --- a/js/src/vm/ErrorObject.h +++ b/js/src/vm/ErrorObject.h @@ -13,6 +13,7 @@ #include "vm/Shape.h" namespace js { +class ArrayObject; class ErrorObject : public NativeObject { diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp index d918880a49..551bfb681f 100644 --- a/js/xpconnect/src/XPCJSContext.cpp +++ b/js/xpconnect/src/XPCJSContext.cpp @@ -108,6 +108,7 @@ const char* const XPCJSContext::mStrings[] = { "columnNumber", // IDX_COLUMNNUMBER "stack", // IDX_STACK "message", // IDX_MESSAGE + "errors", // IDX_ERRORS "lastIndex" // IDX_LASTINDEX }; diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h index c6b8cf0cc2..724c8db3a9 100644 --- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -495,6 +495,7 @@ public: IDX_COLUMNNUMBER , IDX_STACK , IDX_MESSAGE , + IDX_ERRORS , IDX_LASTINDEX , IDX_TOTAL_COUNT // just a count of the above }; diff --git a/js/xpconnect/wrappers/XrayWrapper.cpp b/js/xpconnect/wrappers/XrayWrapper.cpp index e63a1d6d4d..499e22d245 100644 --- a/js/xpconnect/wrappers/XrayWrapper.cpp +++ b/js/xpconnect/wrappers/XrayWrapper.cpp @@ -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); |