From 2db0386e651ae65e2d2a31c593f4ff640edf8b6d Mon Sep 17 00:00:00 2001 From: Martok Date: Thu, 19 Jan 2023 21:26:49 +0100 Subject: [PATCH 01/11] No issue - implement js::NativeDefineDataProperty helper --- js/src/vm/NativeObject.cpp | 35 +++++++++++++++++++++++++++++++++++ js/src/vm/NativeObject.h | 12 ++++++++++++ 2 files changed, 47 insertions(+) diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp index 53f7c0bfac..7418d81edf 100644 --- a/js/src/vm/NativeObject.cpp +++ b/js/src/vm/NativeObject.cpp @@ -1675,6 +1675,41 @@ js::NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, PropertyN return NativeDefineProperty(cx, obj, id, value, getter, setter, attrs); } +bool +js::NativeDefineDataProperty(JSContext* cx, Handle obj, HandleId id, HandleValue value, + unsigned attrs, ObjectOpResult& result) +{ + Rooted desc(cx); + desc.initFields(nullptr, value, attrs, nullptr, nullptr); + return NativeDefineProperty(cx, obj, id, desc, result); +} + +bool +js::NativeDefineDataProperty(JSContext* cx, Handle obj, HandleId id, HandleValue value, + unsigned attrs) +{ + ObjectOpResult result; + if (!NativeDefineDataProperty(cx, obj, id, value, attrs, result)) { + return false; + } + if (!result) { + // Off-thread callers should not get here: they must call this + // function only with known-valid arguments. Populating a new + // PlainObject with configurable properties is fine. + MOZ_ASSERT(!cx->isHelperThreadContext()); + result.reportError(cx, obj, id); + return false; + } + return true; +} + +bool +js::NativeDefineDataProperty(JSContext* cx, Handle obj, PropertyName* name, HandleValue value, + unsigned attrs) +{ + RootedId id(cx, NameToId(name)); + return NativeDefineDataProperty(cx, obj, id, value, attrs); +} /*** [[HasProperty]] *****************************************************************************/ diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h index abc84c9fd1..cf6d684ac0 100644 --- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -1399,6 +1399,18 @@ NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, PropertyName* HandleValue value, JSGetterOp getter, JSSetterOp setter, unsigned attrs); +bool +NativeDefineDataProperty(JSContext* cx, Handle obj, HandleId id, HandleValue value, + unsigned attrs, ObjectOpResult& result); + +extern bool +NativeDefineDataProperty(JSContext* cx, Handle obj, HandleId id, + HandleValue value, unsigned attrs); + +extern bool +NativeDefineDataProperty(JSContext* cx, Handle obj, PropertyName* name, + HandleValue value, unsigned attrs); + extern bool NativeHasProperty(JSContext* cx, HandleNativeObject obj, HandleId id, bool* foundp); From 7d2b83fafcffdd9e609c4edec62aa3efcdafcb68 Mon Sep 17 00:00:00 2001 From: Martok Date: Thu, 19 Jan 2023 21:45:32 +0100 Subject: [PATCH 02/11] No issue - reformat GlobalObject::skipDeselectedConstructor --- js/src/vm/GlobalObject.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index de7e247495..ce663cf907 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -93,21 +93,20 @@ js::GlobalObject::getTypedObjectModule() const { /* static */ bool GlobalObject::skipDeselectedConstructor(JSContext* cx, JSProtoKey key) { - if (key == JSProto_WebAssembly) + // Return true if the given constructor has been disabled at run-time. + switch (key) { + case JSProto_WebAssembly: return !wasm::HasSupport(cx); #ifdef ENABLE_SHARED_ARRAY_BUFFER - // Return true if the given constructor has been disabled at run-time. - switch (key) { case JSProto_Atomics: case JSProto_SharedArrayBuffer: return !cx->compartment()->creationOptions().getSharedMemoryAndAtomicsEnabled(); +#endif + default: return false; } -#else - return false; -#endif } /* static */ bool From 41c2b34542c33311c0a346bac012765f43a65673 Mon Sep 17 00:00:00 2001 From: Martok Date: Sat, 21 Jan 2023 00:55:40 +0100 Subject: [PATCH 03/11] No issue - Remove "code" from jsprototype.h macros Based-on: m-c 1394084 --- js/src/jsapi.cpp | 4 +- js/src/jsatom.cpp | 4 +- js/src/jsatom.h | 2 +- js/src/jsobj.cpp | 4 +- js/src/jsprototypes.h | 113 ++++++++++++++++++------------------- js/src/jspubtd.h | 2 +- js/src/vm/GlobalObject.cpp | 6 +- js/src/vm/Runtime.h | 2 +- 8 files changed, 68 insertions(+), 69 deletions(-) diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index d16df26ba7..ca6289344b 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -984,8 +984,8 @@ LookupStdName(const JSAtomState& names, JSAtom* name, const JSStdName* table) * JSProtoKey does not correspond to a class with a meaningful constructor, we * insert a null entry into the table. */ -#define STD_NAME_ENTRY(name, code, init, clasp) { EAGER_ATOM(name), static_cast(code) }, -#define STD_DUMMY_ENTRY(name, code, init, dummy) { 0, JSProto_Null }, +#define STD_NAME_ENTRY(name, init, clasp) { EAGER_ATOM(name), JSProto_##name }, +#define STD_DUMMY_ENTRY(name, init, dummy) { 0, JSProto_Null }, static const JSStdName standard_class_names[] = { JS_FOR_PROTOTYPES(STD_NAME_ENTRY, STD_DUMMY_ENTRY) { 0, JSProto_LIMIT } diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp index 7c90e19d25..6a1ca82011 100644 --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -44,7 +44,7 @@ js::AtomToPrintableString(ExclusiveContext* cx, JSAtom* atom, JSAutoByteString* return bytes->encodeLatin1(cx, str); } -#define DEFINE_PROTO_STRING(name,code,init,clasp) const char js_##name##_str[] = #name; +#define DEFINE_PROTO_STRING(name,init,clasp) const char js_##name##_str[] = #name; JS_FOR_EACH_PROTOTYPE(DEFINE_PROTO_STRING) #undef DEFINE_PROTO_STRING @@ -98,7 +98,7 @@ JSRuntime::initializeAtoms(JSContext* cx) #define COMMON_NAME_INFO(idpart, id, text) { js_##idpart##_str, sizeof(text) - 1 }, FOR_EACH_COMMON_PROPERTYNAME(COMMON_NAME_INFO) #undef COMMON_NAME_INFO -#define COMMON_NAME_INFO(name, code, init, clasp) { js_##name##_str, sizeof(#name) - 1 }, +#define COMMON_NAME_INFO(name, init, clasp) { js_##name##_str, sizeof(#name) - 1 }, JS_FOR_EACH_PROTOTYPE(COMMON_NAME_INFO) #undef COMMON_NAME_INFO #define COMMON_NAME_INFO(name) { #name, sizeof(#name) - 1 }, diff --git a/js/src/jsatom.h b/js/src/jsatom.h index 8f076171cd..eb43442e63 100644 --- a/js/src/jsatom.h +++ b/js/src/jsatom.h @@ -132,7 +132,7 @@ extern bool AtomIsPinned(JSContext* cx, JSAtom* atom); /* Well-known predefined C strings. */ -#define DECLARE_PROTO_STR(name,code,init,clasp) extern const char js_##name##_str[]; +#define DECLARE_PROTO_STR(name,init,clasp) extern const char js_##name##_str[]; JS_FOR_EACH_PROTOTYPE(DECLARE_PROTO_STR) #undef DECLARE_PROTO_STR diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index a23ac63366..4b3d5758d7 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -3196,8 +3196,8 @@ GetObjectSlotNameFunctor::operator()(JS::CallbackTracer* trc, char* buf, size_t pattern = "CLASS_OBJECT(%s)"; if (false) ; -#define TEST_SLOT_MATCHES_PROTOTYPE(name,code,init,clasp) \ - else if ((code) == slot) { slotname = js_##name##_str; } +#define TEST_SLOT_MATCHES_PROTOTYPE(name,init,clasp) \ + else if ((JSProto_##name) == slot) { slotname = js_##name##_str; } JS_FOR_EACH_PROTOTYPE(TEST_SLOT_MATCHES_PROTOTYPE) #undef TEST_SLOT_MATCHES_PROTOTYPE } else { diff --git a/js/src/jsprototypes.h b/js/src/jsprototypes.h index 615368a847..880fc1054e 100644 --- a/js/src/jsprototypes.h +++ b/js/src/jsprototypes.h @@ -9,9 +9,8 @@ /* A higher-order macro for enumerating all JSProtoKey values. */ /* * Consumers define macros as follows: - * macro(name, code, init, clasp) + * macro(name, init, clasp) * name: The canonical name of the class. - * code: The enumerator code. There are part of the XDR API, and must not change. * init: Initialization function. These are |extern "C";|, and clients should use * |extern "C" {}| as appropriate when using this macro. * clasp: The JSClass for this object, or "dummy" if it doesn't exist. @@ -57,61 +56,61 @@ #endif #define JS_FOR_PROTOTYPES(real,imaginary) \ - imaginary(Null, 0, InitNullClass, dummy) \ - real(Object, 1, InitViaClassSpec, OCLASP(Plain)) \ - real(Function, 2, InitViaClassSpec, &JSFunction::class_) \ - real(Array, 3, InitViaClassSpec, OCLASP(Array)) \ - real(Boolean, 4, InitBooleanClass, OCLASP(Boolean)) \ - real(JSON, 5, InitJSONClass, CLASP(JSON)) \ - real(Date, 6, InitViaClassSpec, OCLASP(Date)) \ - real(Math, 7, InitMathClass, CLASP(Math)) \ - real(Number, 8, InitNumberClass, OCLASP(Number)) \ - real(String, 9, InitStringClass, OCLASP(String)) \ - real(RegExp, 10, InitViaClassSpec, OCLASP(RegExp)) \ - real(Error, 11, InitViaClassSpec, ERROR_CLASP(JSEXN_ERR)) \ - real(InternalError, 12, InitViaClassSpec, ERROR_CLASP(JSEXN_INTERNALERR)) \ - real(EvalError, 13, InitViaClassSpec, ERROR_CLASP(JSEXN_EVALERR)) \ - real(RangeError, 14, InitViaClassSpec, ERROR_CLASP(JSEXN_RANGEERR)) \ - real(ReferenceError, 15, InitViaClassSpec, ERROR_CLASP(JSEXN_REFERENCEERR)) \ - real(SyntaxError, 16, InitViaClassSpec, ERROR_CLASP(JSEXN_SYNTAXERR)) \ - real(TypeError, 17, InitViaClassSpec, ERROR_CLASP(JSEXN_TYPEERR)) \ - real(URIError, 18, InitViaClassSpec, ERROR_CLASP(JSEXN_URIERR)) \ - real(DebuggeeWouldRun, 19, InitViaClassSpec, ERROR_CLASP(JSEXN_DEBUGGEEWOULDRUN)) \ - real(CompileError, 20, InitViaClassSpec, ERROR_CLASP(JSEXN_WASMCOMPILEERROR)) \ - real(RuntimeError, 21, InitViaClassSpec, ERROR_CLASP(JSEXN_WASMRUNTIMEERROR)) \ - real(Iterator, 22, InitLegacyIteratorClass,OCLASP(PropertyIterator)) \ - real(StopIteration, 23, InitStopIterationClass, OCLASP(StopIteration)) \ - real(ArrayBuffer, 24, InitViaClassSpec, OCLASP(ArrayBuffer)) \ - real(Int8Array, 25, InitViaClassSpec, TYPED_ARRAY_CLASP(Int8)) \ - real(Uint8Array, 26, InitViaClassSpec, TYPED_ARRAY_CLASP(Uint8)) \ - real(Int16Array, 27, InitViaClassSpec, TYPED_ARRAY_CLASP(Int16)) \ - real(Uint16Array, 28, InitViaClassSpec, TYPED_ARRAY_CLASP(Uint16)) \ - real(Int32Array, 29, InitViaClassSpec, TYPED_ARRAY_CLASP(Int32)) \ - real(Uint32Array, 30, InitViaClassSpec, TYPED_ARRAY_CLASP(Uint32)) \ - real(Float32Array, 31, InitViaClassSpec, TYPED_ARRAY_CLASP(Float32)) \ - real(Float64Array, 32, InitViaClassSpec, TYPED_ARRAY_CLASP(Float64)) \ - real(Uint8ClampedArray, 33, InitViaClassSpec, TYPED_ARRAY_CLASP(Uint8Clamped)) \ - real(Proxy, 34, InitProxyClass, js::ProxyClassPtr) \ - real(WeakMap, 35, InitWeakMapClass, OCLASP(WeakMap)) \ - real(Map, 36, InitMapClass, OCLASP(Map)) \ - real(Set, 37, InitSetClass, OCLASP(Set)) \ - real(DataView, 38, InitDataViewClass, OCLASP(DataView)) \ - real(Symbol, 39, InitSymbolClass, OCLASP(Symbol)) \ -IF_SAB(real,imaginary)(SharedArrayBuffer, 40, InitViaClassSpec, OCLASP(SharedArrayBuffer)) \ -IF_INTL(real,imaginary) (Intl, 41, InitIntlClass, CLASP(Intl)) \ -IF_BDATA(real,imaginary)(TypedObject, 42, InitTypedObjectModuleObject, OCLASP(TypedObjectModule)) \ - real(Reflect, 43, InitReflect, nullptr) \ -IF_SIMD(real,imaginary)(SIMD, 44, InitSimdClass, OCLASP(Simd)) \ - real(WeakSet, 45, InitWeakSetClass, OCLASP(WeakSet)) \ - real(TypedArray, 46, InitViaClassSpec, &js::TypedArrayObject::sharedTypedArrayPrototypeClass) \ -IF_SAB(real,imaginary)(Atomics, 47, InitAtomicsClass, OCLASP(Atomics)) \ - real(SavedFrame, 48, InitViaClassSpec, &js::SavedFrame::class_) \ - real(WebAssembly, 49, InitWebAssemblyClass, CLASP(WebAssembly)) \ - imaginary(WasmModule, 50, dummy, dummy) \ - imaginary(WasmInstance, 51, dummy, dummy) \ - imaginary(WasmMemory, 52, dummy, dummy) \ - imaginary(WasmTable, 53, dummy, dummy) \ - real(Promise, 54, InitViaClassSpec, OCLASP(Promise)) \ + imaginary(Null, InitNullClass, dummy) \ + real(Object, InitViaClassSpec, OCLASP(Plain)) \ + real(Function, InitViaClassSpec, &JSFunction::class_) \ + real(Array, InitViaClassSpec, OCLASP(Array)) \ + real(Boolean, InitBooleanClass, OCLASP(Boolean)) \ + real(JSON, InitJSONClass, CLASP(JSON)) \ + real(Date, InitViaClassSpec, OCLASP(Date)) \ + real(Math, InitMathClass, CLASP(Math)) \ + real(Number, InitNumberClass, OCLASP(Number)) \ + real(String, InitStringClass, OCLASP(String)) \ + real(RegExp, InitViaClassSpec, OCLASP(RegExp)) \ + real(Error, InitViaClassSpec, ERROR_CLASP(JSEXN_ERR)) \ + real(InternalError, InitViaClassSpec, ERROR_CLASP(JSEXN_INTERNALERR)) \ + real(EvalError, InitViaClassSpec, ERROR_CLASP(JSEXN_EVALERR)) \ + real(RangeError, InitViaClassSpec, ERROR_CLASP(JSEXN_RANGEERR)) \ + real(ReferenceError, InitViaClassSpec, ERROR_CLASP(JSEXN_REFERENCEERR)) \ + real(SyntaxError, InitViaClassSpec, ERROR_CLASP(JSEXN_SYNTAXERR)) \ + real(TypeError, InitViaClassSpec, ERROR_CLASP(JSEXN_TYPEERR)) \ + real(URIError, InitViaClassSpec, ERROR_CLASP(JSEXN_URIERR)) \ + real(DebuggeeWouldRun, InitViaClassSpec, ERROR_CLASP(JSEXN_DEBUGGEEWOULDRUN)) \ + real(CompileError, InitViaClassSpec, ERROR_CLASP(JSEXN_WASMCOMPILEERROR)) \ + real(RuntimeError, InitViaClassSpec, ERROR_CLASP(JSEXN_WASMRUNTIMEERROR)) \ + real(Iterator, InitLegacyIteratorClass,OCLASP(PropertyIterator)) \ + real(StopIteration, InitStopIterationClass, OCLASP(StopIteration)) \ + real(ArrayBuffer, InitViaClassSpec, OCLASP(ArrayBuffer)) \ + real(Int8Array, InitViaClassSpec, TYPED_ARRAY_CLASP(Int8)) \ + real(Uint8Array, InitViaClassSpec, TYPED_ARRAY_CLASP(Uint8)) \ + real(Int16Array, InitViaClassSpec, TYPED_ARRAY_CLASP(Int16)) \ + real(Uint16Array, InitViaClassSpec, TYPED_ARRAY_CLASP(Uint16)) \ + real(Int32Array, InitViaClassSpec, TYPED_ARRAY_CLASP(Int32)) \ + real(Uint32Array, InitViaClassSpec, TYPED_ARRAY_CLASP(Uint32)) \ + real(Float32Array, InitViaClassSpec, TYPED_ARRAY_CLASP(Float32)) \ + real(Float64Array, InitViaClassSpec, TYPED_ARRAY_CLASP(Float64)) \ + real(Uint8ClampedArray, InitViaClassSpec, TYPED_ARRAY_CLASP(Uint8Clamped)) \ + real(Proxy, InitProxyClass, js::ProxyClassPtr) \ + real(WeakMap, InitWeakMapClass, OCLASP(WeakMap)) \ + real(Map, InitMapClass, OCLASP(Map)) \ + real(Set, InitSetClass, OCLASP(Set)) \ + real(DataView, InitDataViewClass, OCLASP(DataView)) \ + real(Symbol, InitSymbolClass, OCLASP(Symbol)) \ +IF_SAB(real,imaginary)(SharedArrayBuffer, InitViaClassSpec, OCLASP(SharedArrayBuffer)) \ +IF_INTL(real,imaginary) (Intl, InitIntlClass, CLASP(Intl)) \ +IF_BDATA(real,imaginary)(TypedObject, InitTypedObjectModuleObject, OCLASP(TypedObjectModule)) \ + real(Reflect, InitReflect, nullptr) \ +IF_SIMD(real,imaginary)(SIMD, InitSimdClass, OCLASP(Simd)) \ + real(WeakSet, InitWeakSetClass, OCLASP(WeakSet)) \ + real(TypedArray, InitViaClassSpec, &js::TypedArrayObject::sharedTypedArrayPrototypeClass) \ +IF_SAB(real,imaginary)(Atomics, InitAtomicsClass, OCLASP(Atomics)) \ + real(SavedFrame, InitViaClassSpec, &js::SavedFrame::class_) \ + real(WebAssembly, InitWebAssemblyClass, CLASP(WebAssembly)) \ + imaginary(WasmModule, dummy, dummy) \ + imaginary(WasmInstance, dummy, dummy) \ + imaginary(WasmMemory, dummy, dummy) \ + imaginary(WasmTable, dummy, dummy) \ + real(Promise, InitViaClassSpec, OCLASP(Promise)) \ #define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro) diff --git a/js/src/jspubtd.h b/js/src/jspubtd.h index 7b57624637..065d2790ec 100644 --- a/js/src/jspubtd.h +++ b/js/src/jspubtd.h @@ -84,7 +84,7 @@ enum JSType { /* Dense index into cached prototypes and class atoms for standard objects. */ enum JSProtoKey { -#define PROTOKEY_AND_INITIALIZER(name,code,init,clasp) JSProto_##name = code, +#define PROTOKEY_AND_INITIALIZER(name,init,clasp) JSProto_##name, JS_FOR_EACH_PROTOTYPE(PROTOKEY_AND_INITIALIZER) #undef PROTOKEY_AND_INITIALIZER JSProto_LIMIT diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index ce663cf907..2c379eee89 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -51,7 +51,7 @@ struct ProtoTableEntry { namespace js { -#define DECLARE_PROTOTYPE_CLASS_INIT(name,code,init,clasp) \ +#define DECLARE_PROTOTYPE_CLASS_INIT(name,init,clasp) \ extern JSObject* init(JSContext* cx, Handle obj); JS_FOR_EACH_PROTOTYPE(DECLARE_PROTOTYPE_CLASS_INIT) #undef DECLARE_PROTOTYPE_CLASS_INIT @@ -65,8 +65,8 @@ js::InitViaClassSpec(JSContext* cx, Handle obj) } static const ProtoTableEntry protoTable[JSProto_LIMIT] = { -#define INIT_FUNC(name,code,init,clasp) { clasp, init }, -#define INIT_FUNC_DUMMY(name,code,init,clasp) { nullptr, nullptr }, +#define INIT_FUNC(name,init,clasp) { clasp, init }, +#define INIT_FUNC_DUMMY(name,init,clasp) { nullptr, nullptr }, JS_FOR_PROTOTYPES(INIT_FUNC, INIT_FUNC_DUMMY) #undef INIT_FUNC_DUMMY #undef INIT_FUNC diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index f1f2e07094..1bbe4658fb 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -169,7 +169,7 @@ struct JSAtomState #define PROPERTYNAME_FIELD(idpart, id, text) js::ImmutablePropertyNamePtr id; FOR_EACH_COMMON_PROPERTYNAME(PROPERTYNAME_FIELD) #undef PROPERTYNAME_FIELD -#define PROPERTYNAME_FIELD(name, code, init, clasp) js::ImmutablePropertyNamePtr name; +#define PROPERTYNAME_FIELD(name, init, clasp) js::ImmutablePropertyNamePtr name; JS_FOR_EACH_PROTOTYPE(PROPERTYNAME_FIELD) #undef PROPERTYNAME_FIELD #define PROPERTYNAME_FIELD(name) js::ImmutablePropertyNamePtr name; From 1230808583c0484678a4352d46586cc7f198dc3c Mon Sep 17 00:00:00 2001 From: Martok Date: Sat, 21 Jan 2023 22:44:26 +0100 Subject: [PATCH 04/11] No issue - Cleanup unused function exports to self-hosted global Based-on: m-c 1325696 --- js/src/vm/SelfHosting.cpp | 55 --------------------------------------- 1 file changed, 55 deletions(-) diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 686b2e9c28..0edf403726 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -601,23 +601,6 @@ intrinsic_FinishBoundFunctionInit(JSContext* cx, unsigned argc, Value* vp) return true; } -static bool -intrinsic_SetPrototype(JSContext *cx, unsigned argc, Value *vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 2); - MOZ_ASSERT(args[0].isObject()); - MOZ_ASSERT(args[1].isObjectOrNull()); - - RootedObject obj(cx, &args[0].toObject()); - RootedObject proto(cx, args[1].toObjectOrNull()); - if (!SetPrototype(cx, obj, proto)) - return false; - - args.rval().setUndefined(); - return true; -} - /* * Used to decompile values in the nearest non-builtin stack frame, falling * back to decompiling in the current frame. Helpful for printing higher-order @@ -768,34 +751,6 @@ intrinsic_UnsafeGetBooleanFromReservedSlot(JSContext* cx, unsigned argc, Value* return true; } -/** - * Intrinsic for creating an empty array in the compartment of the object - * passed as the first argument. - * - * Returns the array, wrapped in the default wrapper to use between the two - * compartments. - */ -static bool -intrinsic_NewArrayInCompartment(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 1); - RootedObject wrapped(cx, &args[0].toObject()); - MOZ_ASSERT(IsWrapper(wrapped)); - RootedObject obj(cx, UncheckedUnwrap(wrapped)); - - RootedArrayObject arr(cx); - { - AutoCompartment ac(cx, obj); - arr = NewDenseEmptyArray(cx); - if (!arr) - return false; - } - - args.rval().setObject(*arr); - return wrapped->compartment()->wrap(cx, args.rval()); -} - static bool intrinsic_IsPackedArray(JSContext* cx, unsigned argc, Value* vp) { @@ -2189,8 +2144,6 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_INLINABLE_FN("std_Math_max", math_max, 2,0, MathMax), JS_INLINABLE_FN("std_Math_min", math_min, 2,0, MathMin), JS_INLINABLE_FN("std_Math_abs", math_abs, 1,0, MathAbs), - JS_INLINABLE_FN("std_Math_imul", math_imul, 2,0, MathImul), - JS_INLINABLE_FN("std_Math_log2", math_log2, 1,0, MathLog2), JS_FN("std_Map_has", MapObject::has, 1,0), JS_FN("std_Map_iterator", MapObject::entries, 0,0), @@ -2203,7 +2156,6 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("std_Object_getOwnPropertyNames", obj_getOwnPropertyNames, 1,0), JS_FN("std_Object_getOwnPropertyDescriptor", obj_getOwnPropertyDescriptor, 2,0), JS_FN("std_Object_hasOwnProperty", obj_hasOwnProperty, 1,0), - JS_FN("std_Object_setPrototypeOf", intrinsic_SetPrototype, 2,0), JS_FN("std_Object_toString", obj_toString, 0,0), JS_FN("std_Reflect_getPrototypeOf", Reflect_getPrototypeOf, 1,0), @@ -2265,7 +2217,6 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_INLINABLE_FN("IsCallable", intrinsic_IsCallable, 1,0, IntrinsicIsCallable), JS_INLINABLE_FN("IsConstructor", intrinsic_IsConstructor, 1,0, IntrinsicIsConstructor), - JS_FN("IsFunctionObject",intrinsic_IsInstanceOfBuiltin, 1,0), JS_FN("GetBuiltinConstructorImpl", intrinsic_GetBuiltinConstructor, 1,0), JS_FN("MakeConstructible", intrinsic_MakeConstructible, 2,0), JS_FN("_ConstructFunction", intrinsic_ConstructFunction, 2,0), @@ -2306,8 +2257,6 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_INLINABLE_FN("UnsafeGetBooleanFromReservedSlot", intrinsic_UnsafeGetBooleanFromReservedSlot,2,0, IntrinsicUnsafeGetBooleanFromReservedSlot), - JS_FN("NewArrayInCompartment", intrinsic_NewArrayInCompartment, 1,0), - JS_FN("IsPackedArray", intrinsic_IsPackedArray, 1,0), JS_FN("GetIteratorPrototype", intrinsic_GetIteratorPrototype, 0,0), @@ -2444,10 +2393,6 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("CallWeakSetMethodIfWrapped", CallNonGenericSelfhostedMethod>, 2, 0), - JS_FN("Promise_static_resolve", Promise_static_resolve, 1, 0), - JS_FN("Promise_static_reject", Promise_reject, 1, 0), - JS_FN("Promise_then", Promise_then, 2, 0), - // See builtin/TypedObject.h for descriptors of the typedobj functions. JS_FN("NewOpaqueTypedObject", js::NewOpaqueTypedObject, 1, 0), JS_FN("NewDerivedTypedObject", js::NewDerivedTypedObject, 3, 0), From 531906eb8790c11e68f068e6df6816fcc5bb2517 Mon Sep 17 00:00:00 2001 From: Martok Date: Sat, 21 Jan 2023 22:48:18 +0100 Subject: [PATCH 05/11] No issue - add API to tell Profile Timeline Recording state to JS engine Based-on: m-c 1342070/5 --- docshell/base/timeline/TimelineConsumers.cpp | 7 +++++++ js/src/jsapi.cpp | 14 ++++++++++++++ js/src/jsapi.h | 16 ++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/docshell/base/timeline/TimelineConsumers.cpp b/docshell/base/timeline/TimelineConsumers.cpp index 92d589c5ba..3abe152881 100644 --- a/docshell/base/timeline/TimelineConsumers.cpp +++ b/docshell/base/timeline/TimelineConsumers.cpp @@ -6,6 +6,7 @@ #include "TimelineConsumers.h" #include "mozilla/ClearOnShutdown.h" +#include "jsapi.h" #include "nsAppRunner.h" // for XRE_IsContentProcess, XRE_IsParentProcess #include "nsDocShell.h" @@ -125,6 +126,9 @@ TimelineConsumers::AddConsumer(nsDocShell* aDocShell) UniquePtr& observed = aDocShell->mObserved; MOZ_ASSERT(!observed); + if (mActiveConsumers == 0) { + JS::SetProfileTimelineRecordingEnabled(true); + } mActiveConsumers++; ObservedDocShell* obsDocShell = new ObservedDocShell(aDocShell); @@ -144,6 +148,9 @@ TimelineConsumers::RemoveConsumer(nsDocShell* aDocShell) MOZ_ASSERT(observed); mActiveConsumers--; + if (mActiveConsumers == 0) { + JS::SetProfileTimelineRecordingEnabled(false); + } // Clear all markers from the `mTimelineMarkers` store. observed.get()->ClearMarkers(); diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index ca6289344b..7d32948a27 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -1276,6 +1276,20 @@ JS::detail::ComputeThis(JSContext* cx, Value* vp) return thisv; } +static bool gProfileTimelineRecordingEnabled = false; + +JS_PUBLIC_API(void) +JS::SetProfileTimelineRecordingEnabled(bool enabled) +{ + gProfileTimelineRecordingEnabled = enabled; +} + +JS_PUBLIC_API(bool) +JS::IsProfileTimelineRecordingEnabled() +{ + return gProfileTimelineRecordingEnabled; +} + JS_PUBLIC_API(void*) JS_malloc(JSContext* cx, size_t nbytes) { diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 0b865ff523..938fcb2a33 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -1545,6 +1545,22 @@ JS_DefineProfilingFunctions(JSContext* cx, JS::HandleObject obj); extern JS_PUBLIC_API(bool) JS_DefineDebuggerObject(JSContext* cx, JS::HandleObject obj); +namespace JS { + +/** + * Tell JS engine whether Profile Timeline Recording is enabled or not. + * If Profile Timeline Recording is enabled, data shown there like stack won't + * be optimized out. + * This is global state and not associated with specific runtime or context. + */ +extern JS_PUBLIC_API(void) +SetProfileTimelineRecordingEnabled(bool enabled); + +extern JS_PUBLIC_API(bool) +IsProfileTimelineRecordingEnabled(); + +} // namespace JS + #ifdef JS_HAS_CTYPES /** * Initialize the 'ctypes' object on a global variable 'obj'. The 'ctypes' From 0216108195b4d77a93387abd04c2d05fb21b733d Mon Sep 17 00:00:00 2001 From: Martok Date: Sun, 22 Jan 2023 00:10:50 +0100 Subject: [PATCH 06/11] No issue - Throw error when resolving or rejecting promise returned by async function with testing function Based-on: m-c 1418106 --- js/src/builtin/Promise.cpp | 7 +++++++ js/src/builtin/Promise.h | 3 +++ js/src/builtin/TestingFunctions.cpp | 10 ++++++++++ 3 files changed, 20 insertions(+) diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index faba010950..2cd26d87d6 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -2577,6 +2577,13 @@ js::CreatePromiseObjectForAsync(JSContext* cx, HandleValue generatorVal) return promise; } +bool +js::IsPromiseForAsync(JSObject* promise) +{ + return promise->is() && + PromiseHasAnyFlag(promise->as(), PROMISE_FLAG_ASYNC); +} + // ES 2018 draft 25.5.5.2 steps 3.f, 3.g. MOZ_MUST_USE bool js::AsyncFunctionThrown(JSContext* cx, Handle resultPromise) diff --git a/js/src/builtin/Promise.h b/js/src/builtin/Promise.h index 04cc46920f..d8d178891a 100644 --- a/js/src/builtin/Promise.h +++ b/js/src/builtin/Promise.h @@ -141,6 +141,9 @@ PromiseResolve(JSContext* cx, HandleObject constructor, HandleValue value); MOZ_MUST_USE PromiseObject* CreatePromiseObjectForAsync(JSContext* cx, HandleValue generatorVal); +MOZ_MUST_USE bool +IsPromiseForAsync(JSObject* promise); + MOZ_MUST_USE bool AsyncFunctionReturned(JSContext* cx, Handle resultPromise, HandleValue value); diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 48cdc827ba..914d3b989b 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -1398,6 +1398,11 @@ ResolvePromise(JSContext* cx, unsigned argc, Value* vp) return false; } + if (IsPromiseForAsync(promise)) { + JS_ReportErrorASCII(cx, "async function's promise shouldn't be manually resolved"); + return false; + } + bool result = JS::ResolvePromise(cx, promise, resolution); if (result) args.rval().setUndefined(); @@ -1425,6 +1430,11 @@ RejectPromise(JSContext* cx, unsigned argc, Value* vp) return false; } + if (IsPromiseForAsync(promise)) { + JS_ReportErrorASCII(cx, "async function's promise shouldn't be manually rejected"); + return false; + } + bool result = JS::RejectPromise(cx, promise, reason); if (result) args.rval().setUndefined(); From 6b50dd5d0659597673a3edaca19d90c65ab29614 Mon Sep 17 00:00:00 2001 From: Martok Date: Sat, 21 Jan 2023 02:13:01 +0100 Subject: [PATCH 07/11] Issue #2089 - Use JS engine stack if necessary when reporting errors Based-on: m-c 996060 --- dom/base/nsJSEnvironment.cpp | 31 ++++++++++++++++++------- dom/script/ScriptSettings.cpp | 14 +++++++++--- dom/script/ScriptSettings.h | 6 +++++ js/src/jit/VMFunctions.cpp | 2 +- js/src/jsapi.cpp | 35 ++++++++++++++++++++++++++--- js/src/jsapi.h | 36 ++++++++++++++++++++++++------ js/src/jscntxt.cpp | 31 +++++++++++++++++++++++-- js/src/jscntxt.h | 7 +++++- js/src/jscntxtinlines.h | 3 ++- js/src/jsexn.cpp | 15 ++++++++----- js/src/jsiter.cpp | 9 +++++--- js/src/proxy/Wrapper.cpp | 7 ++++-- js/src/vm/Debugger.cpp | 27 +++++++++------------- js/src/vm/ForOfIterator.cpp | 9 +++++--- js/src/vm/GeneratorObject.cpp | 5 +++-- js/src/vm/Interpreter.cpp | 22 +++++++++++++----- js/src/vm/Interpreter.h | 3 +++ js/src/vm/Runtime.cpp | 2 +- js/src/wasm/WasmJS.cpp | 3 ++- js/xpconnect/src/XPCComponents.cpp | 2 +- js/xpconnect/src/xpcpublic.h | 9 +++++--- 21 files changed, 209 insertions(+), 69 deletions(-) diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 5229948e01..580d6bfba3 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -224,16 +224,23 @@ ProcessNameForCollectorLog() namespace xpc { -// This handles JS Exceptions (via ExceptionStackOrNull), as well as DOM and XPC -// Exceptions. +// This handles JS Exceptions (via ExceptionStackOrNull), DOM and XPC +// Exceptions, and arbitrary values that were associated with a stack by the +// JS engine when they were thrown, as specified by exceptionStack. // // Note that the returned object is _not_ wrapped into the compartment of // exceptionValue. JSObject* FindExceptionStackForConsoleReport(nsPIDOMWindowInner* win, - JS::HandleValue exceptionValue) + JS::HandleValue exceptionValue, + JS::HandleObject exceptionStack) { if (!exceptionValue.isObject()) { + // Use the stack provided by the JS engine, if available. This will not be + // a wrapper. + if (exceptionStack) { + return exceptionStack; + } return nullptr; } @@ -257,6 +264,10 @@ FindExceptionStackForConsoleReport(nsPIDOMWindowInner* win, // Not a DOM Exception, try XPC Exception. UNWRAP_OBJECT(Exception, exceptionObject, exception); if (!exception) { + // As above, use the stack provided by the JS engine, if available. + if (exceptionStack) { + return exceptionStack; + } return nullptr; } } @@ -421,10 +432,12 @@ public: ScriptErrorEvent(nsPIDOMWindowInner* aWindow, JS::RootingContext* aRootingCx, xpc::ErrorReport* aReport, - JS::Handle aError) + JS::Handle aError, + JS::Handle aErrorStack) : mWindow(aWindow) , mReport(aReport) , mError(aRootingCx, aError) + , mErrorStack(aRootingCx, aErrorStack) {} NS_IMETHOD Run() override @@ -471,7 +484,7 @@ public: if (status != nsEventStatus_eConsumeNoDefault) { JS::Rooted stack(rootingCx, - xpc::FindExceptionStackForConsoleReport(win, mError)); + xpc::FindExceptionStackForConsoleReport(win, mError, mErrorStack)); mReport->LogToConsoleWithStack(stack); } @@ -481,7 +494,8 @@ public: private: nsCOMPtr mWindow; RefPtr mReport; - JS::PersistentRootedValue mError; + JS::PersistentRootedValue mError; + JS::PersistentRootedObject mErrorStack; static bool sHandlingScriptError; }; @@ -494,9 +508,10 @@ namespace xpc { void DispatchScriptErrorEvent(nsPIDOMWindowInner *win, JS::RootingContext* rootingCx, - xpc::ErrorReport *xpcReport, JS::Handle exception) + xpc::ErrorReport *xpcReport, JS::Handle exception, + JS::Handle exceptionStack) { - nsContentUtils::AddScriptRunner(new ScriptErrorEvent(win, rootingCx, xpcReport, exception)); + nsContentUtils::AddScriptRunner(new ScriptErrorEvent(win, rootingCx, xpcReport, exception, exceptionStack)); } } /* namespace xpc */ diff --git a/dom/script/ScriptSettings.cpp b/dom/script/ScriptSettings.cpp index 514b5cf858..790394de65 100644 --- a/dom/script/ScriptSettings.cpp +++ b/dom/script/ScriptSettings.cpp @@ -577,8 +577,9 @@ AutoJSAPI::ReportException() } JSAutoCompartment ac(cx(), errorGlobal); JS::Rooted exn(cx()); + JS::Rooted exnStack(cx()); js::ErrorReport jsReport(cx()); - if (StealException(&exn) && + if (StealExceptionAndStack(&exn, &exnStack) && jsReport.init(cx(), exn, js::ErrorReport::WithSideEffects)) { if (mIsMainThread) { RefPtr xpcReport = new xpc::ErrorReport(); @@ -595,10 +596,10 @@ AutoJSAPI::ReportException() inner ? inner->WindowID() : 0); if (inner && jsReport.report()->errorNumber != JSMSG_OUT_OF_MEMORY) { JS::RootingContext* rcx = JS::RootingContext::get(cx()); - DispatchScriptErrorEvent(inner, rcx, xpcReport, exn); + DispatchScriptErrorEvent(inner, rcx, xpcReport, exn, exnStack); } else { JS::Rooted stack(cx(), - xpc::FindExceptionStackForConsoleReport(inner, exn)); + xpc::FindExceptionStackForConsoleReport(inner, exn, exnStack)); xpcReport->LogToConsoleWithStack(stack); } } else { @@ -638,9 +639,16 @@ AutoJSAPI::PeekException(JS::MutableHandle aVal) bool AutoJSAPI::StealException(JS::MutableHandle aVal) { + JS::Rooted stack(cx()); + return StealExceptionAndStack(aVal, &stack); +} + +bool AutoJSAPI::StealExceptionAndStack(JS::MutableHandle aVal, + JS::MutableHandle aStack) { if (!PeekException(aVal)) { return false; } + aStack.set(JS::GetPendingExceptionStack(cx())); JS_ClearPendingException(cx()); return true; } diff --git a/dom/script/ScriptSettings.h b/dom/script/ScriptSettings.h index f6cfb6c3e4..f2e12f0be9 100644 --- a/dom/script/ScriptSettings.h +++ b/dom/script/ScriptSettings.h @@ -274,6 +274,12 @@ public: // into the current compartment. MOZ_MUST_USE bool StealException(JS::MutableHandle aVal); + // As for StealException(), but put the saved frames for any stack trace + // associated with the point the exception was thrown into aStack. + // aVal will be in the current compartment, but aStack might not be. + MOZ_MUST_USE bool StealExceptionAndStack(JS::MutableHandle aVal, + JS::MutableHandle aStack); + // Peek the current exception from the JS engine, without stealing it. // Callers must ensure that HasException() is true, and that cx() is in a // non-null compartment. diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index fbe6977bf9..01a22482eb 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -941,7 +941,7 @@ HandleDebugTrap(JSContext* cx, BaselineFrame* frame, uint8_t* retAddr, bool* mus return jit::DebugEpilogue(cx, frame, pc, true); case JSTRAP_THROW: - cx->setPendingException(rval); + cx->setPendingExceptionAndCaptureStack(rval); return false; default: diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 7d32948a27..0d23c96cb4 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -6219,12 +6219,16 @@ JS_GetPendingException(JSContext* cx, MutableHandleValue vp) } JS_PUBLIC_API(void) -JS_SetPendingException(JSContext* cx, HandleValue value) +JS_SetPendingException(JSContext* cx, HandleValue value, JS::ExceptionStackBehavior behavior) { AssertHeapIsIdle(cx); CHECK_REQUEST(cx); releaseAssertSameCompartment(cx, value); - cx->setPendingException(value); + if (behavior == JS::ExceptionStackBehavior::Capture) { + cx->setPendingExceptionAndCaptureStack(value); + } else { + cx->setPendingException(value, nullptr); + } } JS_PUBLIC_API(void) @@ -6234,12 +6238,20 @@ JS_ClearPendingException(JSContext* cx) cx->clearPendingException(); } +JS_PUBLIC_API(JSObject*) +JS::GetPendingExceptionStack(JSContext* cx) +{ + AssertHeapIsIdle(cx); + return cx->getPendingExceptionStack(); +} + JS::AutoSaveExceptionState::AutoSaveExceptionState(JSContext* cx) : context(cx), wasPropagatingForcedReturn(cx->propagatingForcedReturn_), wasOverRecursed(cx->overRecursed_), wasThrowing(cx->throwing), - exceptionValue(cx) + exceptionValue(cx), + exceptionStack(cx) { AssertHeapIsIdle(cx); CHECK_REQUEST(cx); @@ -6249,10 +6261,21 @@ JS::AutoSaveExceptionState::AutoSaveExceptionState(JSContext* cx) cx->overRecursed_ = false; if (wasThrowing) { exceptionValue = cx->unwrappedException_; + exceptionStack = cx->unwrappedExceptionStack_; cx->clearPendingException(); } } +void +JS::AutoSaveExceptionState::drop() +{ + wasPropagatingForcedReturn = false; + wasOverRecursed = false; + wasThrowing = false; + exceptionValue.setUndefined(); + exceptionStack = nullptr; +} + void JS::AutoSaveExceptionState::restore() { @@ -6260,6 +6283,9 @@ JS::AutoSaveExceptionState::restore() context->overRecursed_ = wasOverRecursed; context->throwing = wasThrowing; context->unwrappedException_ = exceptionValue; + if (exceptionStack) { + context->unwrappedExceptionStack_ = &exceptionStack->as(); + } drop(); } @@ -6272,6 +6298,9 @@ JS::AutoSaveExceptionState::~AutoSaveExceptionState() context->overRecursed_ = wasOverRecursed; context->throwing = true; context->unwrappedException_ = exceptionValue; + if (exceptionStack) { + context->unwrappedExceptionStack_ = &exceptionStack->as(); + } } } } diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 938fcb2a33..6002d86ad5 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -5762,8 +5762,22 @@ JS_IsExceptionPending(JSContext* cx); extern JS_PUBLIC_API(bool) JS_GetPendingException(JSContext* cx, JS::MutableHandleValue vp); +namespace JS { + +enum class ExceptionStackBehavior: bool { + // Do not capture any stack. + DoNotCapture, + + // Capture the current JS stack when setting the exception. It may be + // retrieved by JS::GetPendingExceptionStack. + Capture +}; + +} // namespace JS + extern JS_PUBLIC_API(void) -JS_SetPendingException(JSContext* cx, JS::HandleValue v); +JS_SetPendingException(JSContext* cx, JS::HandleValue v, + JS::ExceptionStackBehavior behavior = JS::ExceptionStackBehavior::Capture); extern JS_PUBLIC_API(void) JS_ClearPendingException(JSContext* cx); @@ -5790,6 +5804,7 @@ class JS_PUBLIC_API(AutoSaveExceptionState) bool wasOverRecursed; bool wasThrowing; RootedValue exceptionValue; + RootedObject exceptionStack; public: /* @@ -5808,12 +5823,7 @@ class JS_PUBLIC_API(AutoSaveExceptionState) * Discard any stored exception state. * If this is called, the destructor is a no-op. */ - void drop() { - wasPropagatingForcedReturn = false; - wasOverRecursed = false; - wasThrowing = false; - exceptionValue.setUndefined(); - } + void drop(); /* * Replace cx's exception state with the stored exception state. Then @@ -5823,6 +5833,18 @@ class JS_PUBLIC_API(AutoSaveExceptionState) void restore(); }; +/** + * Get the SavedFrame stack object captured when the pending exception was set + * on the JSContext. This fuzzily correlates with a `throw` statement in JS, + * although arbitrary JSAPI consumers or VM code may also set pending exceptions + * via `JS_SetPendingException`. + * + * This is not the same stack as `e.stack` when `e` is an `Error` object. (That + * would be JS::ExceptionStackOrNull). + */ +MOZ_MUST_USE JS_PUBLIC_API(JSObject*) +GetPendingExceptionStack(JSContext* cx); + } /* namespace JS */ /* Deprecated API. Use AutoSaveExceptionState instead. */ diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index baab36183a..c780b7feba 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -241,7 +241,8 @@ js::ReportOutOfMemory(ExclusiveContext* cxArg) if (JS::OutOfMemoryCallback oomCallback = cx->runtime()->oomCallback) oomCallback(cx, cx->runtime()->oomCallbackData); - cx->setPendingException(StringValue(cx->names().outOfMemory)); + RootedValue oomMessage(cx, StringValue(cx->names().outOfMemory)); + cx->setPendingException(oomMessage, nullptr); } void @@ -1013,6 +1014,7 @@ JSContext::JSContext(JSRuntime* parentRuntime) JSRuntime(parentRuntime), throwing(false), unwrappedException_(this), + unwrappedExceptionStack_(this), overRecursed_(false), propagatingForcedReturn_(false), liveVolatileJitFrameIterators_(nullptr), @@ -1038,6 +1040,24 @@ JSContext::~JSContext() MOZ_ASSERT(!resolvingList); } + +void +JSContext::setPendingExceptionAndCaptureStack(HandleValue value) +{ + static const size_t MAX_REPORTED_STACK_DEPTH = 1u << 7; + + RootedObject stack(this); + if (!CaptureCurrentStack(this, &stack, JS::StackCapture(JS::MaxFrames(MAX_REPORTED_STACK_DEPTH)))) { + clearPendingException(); + } + + RootedSavedFrame nstack(this); + if (stack) { + nstack = &stack->as(); + } + setPendingException(value, nstack); +} + bool JSContext::getPendingException(MutableHandleValue rval) { @@ -1045,16 +1065,23 @@ JSContext::getPendingException(MutableHandleValue rval) rval.set(unwrappedException_); if (IsAtomsCompartment(compartment())) return true; + RootedSavedFrame stack(this, unwrappedExceptionStack_); bool wasOverRecursed = overRecursed_; clearPendingException(); if (!compartment()->wrap(this, rval)) return false; assertSameCompartment(this, rval); - setPendingException(rval); + setPendingException(rval, stack); overRecursed_ = wasOverRecursed; return true; } +SavedFrame* +JSContext::getPendingExceptionStack() +{ + return unwrappedExceptionStack_; +} + bool JSContext::isThrowingOutOfMemory() { diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 935e6c59e5..93106d681e 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -369,6 +369,7 @@ struct JSContext : public js::ExclusiveContext, /* Exception state -- the exception member is a GC root by definition. */ bool throwing; /* is there a pending exception? */ JS::PersistentRooted unwrappedException_; /* most-recently-thrown exception */ + JS::PersistentRooted unwrappedExceptionStack_; /* stack when the exception was thrown */ // True if the exception currently being thrown is by result of // ReportOverRecursed. See Debugger::slowPathOnExceptionUnwind. @@ -495,17 +496,21 @@ struct JSContext : public js::ExclusiveContext, MOZ_MUST_USE bool getPendingException(JS::MutableHandleValue rval); + + js::SavedFrame* getPendingExceptionStack(); bool isThrowingOutOfMemory(); bool isThrowingDebuggeeWouldRun(); bool isClosingGenerator(); - void setPendingException(const js::Value& v); + void setPendingException(JS::HandleValue v, js::HandleSavedFrame stack); + void setPendingExceptionAndCaptureStack(JS::HandleValue v); void clearPendingException() { throwing = false; overRecursed_ = false; unwrappedException_.setUndefined(); + unwrappedExceptionStack_ = nullptr; } bool isThrowingOverRecursed() const { return throwing && overRecursed_; } diff --git a/js/src/jscntxtinlines.h b/js/src/jscntxtinlines.h index e08541e8da..c89ef86ec2 100644 --- a/js/src/jscntxtinlines.h +++ b/js/src/jscntxtinlines.h @@ -368,12 +368,13 @@ ExclusiveContext::typeLifoAlloc() } /* namespace js */ inline void -JSContext::setPendingException(const js::Value& v) +JSContext::setPendingException(JS::HandleValue v, js::HandleSavedFrame stack) { // overRecursed_ is set after the fact by ReportOverRecursed. this->overRecursed_ = false; this->throwing = true; this->unwrappedException_ = v; + this->unwrappedExceptionStack_ = stack; // We don't use assertSameCompartment here to allow // js::SetPendingExceptionCrossContext to work. MOZ_ASSERT_IF(v.isObject(), v.toObject().compartment() == compartment()); diff --git a/js/src/jsexn.cpp b/js/src/jsexn.cpp index 7f87c312d3..2eb8e7d105 100644 --- a/js/src/jsexn.cpp +++ b/js/src/jsexn.cpp @@ -361,13 +361,13 @@ struct SuppressErrorsGuard } }; -// Cut off the stack if it gets too deep (most commonly for infinite recursion -// errors). -static const size_t MAX_REPORTED_STACK_DEPTH = 1u << 7; - static bool CaptureStack(JSContext* cx, MutableHandleObject stack) { + // Cut off the stack if it gets too deep (most commonly for infinite recursion + // errors). + static const size_t MAX_REPORTED_STACK_DEPTH = 1u << 7; + return CaptureCurrentStack(cx, stack, JS::StackCapture(JS::MaxFrames(MAX_REPORTED_STACK_DEPTH))); } @@ -699,7 +699,12 @@ js::ErrorToException(JSContext* cx, JSErrorReport* reportp, return; // Throw it. - cx->setPendingException(ObjectValue(*errObject)); + RootedValue errValue(cx, ObjectValue(*errObject)); + RootedSavedFrame nstack(cx); + if (stack) { + nstack = &stack->as(); + } + cx->setPendingException(errValue, nstack); // Flag the error report passed in to indicate an exception was raised. reportp->flags |= JSREPORT_EXCEPTION; diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index 96285c1665..858e10a326 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -971,8 +971,10 @@ js::ThrowStopIteration(JSContext* cx) // StopIteration isn't a constructor, but it's stored in GlobalObject // as one, out of laziness. Hence the GetBuiltinConstructor call here. RootedObject ctor(cx); - if (GetBuiltinConstructor(cx, JSProto_StopIteration, &ctor)) - cx->setPendingException(ObjectValue(*ctor)); + if (GetBuiltinConstructor(cx, JSProto_StopIteration, &ctor)) { + RootedValue ctorval(cx, ObjectValue(*ctor)); + cx->setPendingExceptionAndCaptureStack(ctorval); + } return false; } @@ -1261,12 +1263,13 @@ js::UnwindIteratorForException(JSContext* cx, HandleObject obj) { RootedValue v(cx); bool getOk = cx->getPendingException(&v); + RootedSavedFrame stack(cx, cx->getPendingExceptionStack()); cx->clearPendingException(); if (!CloseIterator(cx, obj)) return false; if (!getOk) return false; - cx->setPendingException(v); + cx->setPendingException(v, stack); return true; } diff --git a/js/src/proxy/Wrapper.cpp b/js/src/proxy/Wrapper.cpp index 36cb1317f7..314409bba1 100644 --- a/js/src/proxy/Wrapper.cpp +++ b/js/src/proxy/Wrapper.cpp @@ -408,12 +408,15 @@ ErrorCopier::~ErrorCopier() { RootedValue exc(cx); if (cx->getPendingException(&exc) && exc.isObject() && exc.toObject().is()) { + RootedSavedFrame stack(cx, cx->getPendingExceptionStack()); cx->clearPendingException(); ac.reset(); Rooted errObj(cx, &exc.toObject().as()); JSObject* copyobj = CopyErrorObject(cx, errObj); - if (copyobj) - cx->setPendingException(ObjectValue(*copyobj)); + if (copyobj) { + RootedValue rootedCopy(cx, ObjectValue(*copyobj)); + cx->setPendingException(rootedCopy, stack); + } } } } diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index f665dcef9b..f844d1d482 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -848,7 +848,7 @@ Debugger::slowPathOnEnterFrame(JSContext* cx, AbstractFramePtr frame) break; case JSTRAP_THROW: - cx->setPendingException(rval); + cx->setPendingExceptionAndCaptureStack(rval); break; case JSTRAP_ERROR: @@ -960,7 +960,7 @@ Debugger::slowPathOnLeaveFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode return true; case JSTRAP_THROW: - cx->setPendingException(value); + cx->setPendingExceptionAndCaptureStack(value); return false; case JSTRAP_ERROR: @@ -993,7 +993,7 @@ Debugger::slowPathOnDebuggerStatement(JSContext* cx, AbstractFramePtr frame) break; case JSTRAP_THROW: - cx->setPendingException(rval); + cx->setPendingExceptionAndCaptureStack(rval); break; default: @@ -1028,7 +1028,7 @@ Debugger::slowPathOnExceptionUnwind(JSContext* cx, AbstractFramePtr frame) break; case JSTRAP_THROW: - cx->setPendingException(rval); + cx->setPendingExceptionAndCaptureStack(rval); break; case JSTRAP_ERROR: @@ -1321,7 +1321,7 @@ public: bool operator()(JSContext* cx) override { - cx->setPendingException(exn_); + cx->setPendingExceptionAndCaptureStack(exn_); return false; } @@ -1753,6 +1753,7 @@ Debugger::fireExceptionUnwind(JSContext* cx, MutableHandleValue vp) MOZ_ASSERT(hook->isCallable()); RootedValue exc(cx); + RootedSavedFrame stack(cx, cx->getPendingExceptionStack()); if (!cx->getPendingException(&exc)) return JSTRAP_ERROR; cx->clearPendingException(); @@ -1772,7 +1773,7 @@ Debugger::fireExceptionUnwind(JSContext* cx, MutableHandleValue vp) bool ok = js::Call(cx, fval, object, scriptFrame, wrappedExc, &rv); JSTrapStatus st = processHandlerResult(ac, ok, rv, iter.abstractFramePtr(), iter.pc(), vp); if (st == JSTRAP_CONTINUE) - cx->setPendingException(exc); + cx->setPendingException(exc, stack); return st; } @@ -2005,13 +2006,7 @@ Debugger::onSingleStep(JSContext* cx, MutableHandleValue vp) * onStep handlers mess with that (other than by returning a resumption * value). */ - RootedValue exception(cx, UndefinedValue()); - bool exceptionPending = cx->isExceptionPending(); - if (exceptionPending) { - if (!cx->getPendingException(&exception)) - return JSTRAP_ERROR; - cx->clearPendingException(); - } + JS::AutoSaveExceptionState savedExc(cx); /* * Build list of Debugger.Frame instances referring to this frame with @@ -2070,13 +2065,13 @@ Debugger::onSingleStep(JSContext* cx, MutableHandleValue vp) bool ok = js::Call(cx, fval, frame, &rval); JSTrapStatus st = dbg->processHandlerResult(ac, ok, rval, iter.abstractFramePtr(), iter.pc(), vp); - if (st != JSTRAP_CONTINUE) + if (st != JSTRAP_CONTINUE) { + savedExc.drop(); return st; + } } vp.setUndefined(); - if (exceptionPending) - cx->setPendingException(exception); return JSTRAP_CONTINUE; } diff --git a/js/src/vm/ForOfIterator.cpp b/js/src/vm/ForOfIterator.cpp index d616792697..abf27d0aee 100644 --- a/js/src/vm/ForOfIterator.cpp +++ b/js/src/vm/ForOfIterator.cpp @@ -158,9 +158,12 @@ ForOfIterator::closeThrow() MOZ_ASSERT(iterator); RootedValue completionException(cx_); + RootedSavedFrame completionExceptionStack(cx_); if (cx_->isExceptionPending()) { - if (!GetAndClearException(cx_, &completionException)) + if (!GetAndClearExceptionAndStack(cx_, &completionException, &completionExceptionStack)) { completionException.setUndefined(); + completionExceptionStack = nullptr; + } } // Steps 1-2 (implicit) @@ -172,7 +175,7 @@ ForOfIterator::closeThrow() // Step 4. if (returnVal.isUndefined()) { - cx_->setPendingException(completionException); + cx_->setPendingException(completionException, completionExceptionStack); return; } @@ -195,7 +198,7 @@ ForOfIterator::closeThrow() } // Step 6. - cx_->setPendingException(completionException); + cx_->setPendingException(completionException, completionExceptionStack); // Steps 7-9 (skipped). return; diff --git a/js/src/vm/GeneratorObject.cpp b/js/src/vm/GeneratorObject.cpp index 9265a1b628..df2869c89c 100644 --- a/js/src/vm/GeneratorObject.cpp +++ b/js/src/vm/GeneratorObject.cpp @@ -131,7 +131,7 @@ js::GeneratorThrowOrClose(JSContext* cx, AbstractFramePtr frame, HandlesetPendingException(arg); + cx->setPendingExceptionAndCaptureStack(arg); genObj->setRunning(); } else { MOZ_ASSERT(resumeKind == GeneratorObject::CLOSE); @@ -143,7 +143,8 @@ js::GeneratorThrowOrClose(JSContext* cx, AbstractFramePtr frame, HandlesetPendingException(MagicValue(JS_GENERATOR_CLOSING)); + RootedValue closing(cx, MagicValue(JS_GENERATOR_CLOSING)); + cx->setPendingException(closing, nullptr); genObj->setClosing(); } return false; diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index cf58e2d608..b95e533b6e 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1883,7 +1883,7 @@ CASE(EnableInterruptsPseudoOpcode) goto error; goto successful_return_continuation; case JSTRAP_THROW: - cx->setPendingException(rval); + cx->setPendingExceptionAndCaptureStack(rval); goto error; default:; } @@ -1905,7 +1905,7 @@ CASE(EnableInterruptsPseudoOpcode) goto error; goto successful_return_continuation; case JSTRAP_THROW: - cx->setPendingException(rval); + cx->setPendingExceptionAndCaptureStack(rval); goto error; default: break; @@ -3825,7 +3825,8 @@ CASE(JSOP_RETSUB) * be necessary, but it seems clearer. And it points out a FIXME: * 350509, due to Igor Bukanov. */ - cx->setPendingException(rval); + ReservedRooted v(&rootValue0, rval); + cx->setPendingExceptionAndCaptureStack(v); goto error; } MOZ_ASSERT(rval.isInt32()); @@ -4331,7 +4332,7 @@ bool js::Throw(JSContext* cx, HandleValue v) { MOZ_ASSERT(!cx->isExceptionPending()); - cx->setPendingException(v); + cx->setPendingExceptionAndCaptureStack(v); return false; } @@ -4342,7 +4343,7 @@ js::ThrowingOperation(JSContext* cx, HandleValue v) // execution instead of calling the (JIT) exception handler. MOZ_ASSERT(!cx->isExceptionPending()); - cx->setPendingException(v); + cx->setPendingExceptionAndCaptureStack(v); return true; } @@ -4548,16 +4549,25 @@ js::ThrowMsgOperation(JSContext* cx, const unsigned errorNum) } bool -js::GetAndClearException(JSContext* cx, MutableHandleValue res) +js::GetAndClearExceptionAndStack(JSContext* cx, MutableHandleValue res, + MutableHandleSavedFrame stack) { if (!cx->getPendingException(res)) return false; + stack.set(cx->getPendingExceptionStack()); cx->clearPendingException(); // Allow interrupting deeply nested exception handling. return CheckForInterrupt(cx); } +bool +js::GetAndClearException(JSContext* cx, MutableHandleValue res) +{ + RootedSavedFrame stack(cx); + return GetAndClearExceptionAndStack(cx, res, &stack); +} + template bool js::DeletePropertyJit(JSContext* cx, HandleValue v, HandlePropertyName name, bool* bp) diff --git a/js/src/vm/Interpreter.h b/js/src/vm/Interpreter.h index 6a908e115b..df9368e71c 100644 --- a/js/src/vm/Interpreter.h +++ b/js/src/vm/Interpreter.h @@ -489,6 +489,9 @@ ThrowMsgOperation(JSContext* cx, const unsigned errorNum); bool GetAndClearException(JSContext* cx, MutableHandleValue res); +bool +GetAndClearExceptionAndStack(JSContext* cx, MutableHandleValue res, MutableHandleSavedFrame stack); + bool DeleteNameOperation(JSContext* cx, HandlePropertyName name, HandleObject scopeObj, MutableHandleValue res); diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 251c8258cf..a12255c636 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -548,7 +548,7 @@ InvokeInterruptCallback(JSContext* cx) Debugger::propagateForcedReturn(cx, iter.abstractFramePtr(), rval); return false; case JSTRAP_THROW: - cx->setPendingException(rval); + cx->setPendingExceptionAndCaptureStack(rval); return false; default:; } diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp index fb292ac941..0479bda59f 100644 --- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -1729,7 +1729,8 @@ RejectWithPendingException(JSContext* cx, Handle promise) return false; RootedValue rejectionValue(cx); - if (!GetAndClearException(cx, &rejectionValue)) + RootedSavedFrame stack(cx); + if (!GetAndClearExceptionAndStack(cx, &rejectionValue, &stack)) return false; return PromiseObject::reject(cx, promise, rejectionValue); diff --git a/js/xpconnect/src/XPCComponents.cpp b/js/xpconnect/src/XPCComponents.cpp index 594753d64f..70103dfd17 100644 --- a/js/xpconnect/src/XPCComponents.cpp +++ b/js/xpconnect/src/XPCComponents.cpp @@ -2310,7 +2310,7 @@ nsXPCComponents_Utils::ReportError(HandleValue error, JSContext* cx) if (errorObj) { JS::RootedObject stackVal(cx, - FindExceptionStackForConsoleReport(win, error)); + FindExceptionStackForConsoleReport(win, error, nullptr)); if (stackVal) { scripterr = new nsScriptErrorWithStack(stackVal); } diff --git a/js/xpconnect/src/xpcpublic.h b/js/xpconnect/src/xpcpublic.h index 56468f0edd..5351be979b 100644 --- a/js/xpconnect/src/xpcpublic.h +++ b/js/xpconnect/src/xpcpublic.h @@ -590,11 +590,13 @@ class ErrorReport : public ErrorBase { void DispatchScriptErrorEvent(nsPIDOMWindowInner* win, JS::RootingContext* rootingCx, - xpc::ErrorReport* xpcReport, JS::Handle exception); + xpc::ErrorReport* xpcReport, JS::Handle exception, + JS::Handle exceptionStack); // Get a stack of the sort that can be passed to // xpc::ErrorReport::LogToConsoleWithStack from the given exception value. Can -// return null if the exception value doesn't have an associated stack. The +// be nullptr if the exception value doesn't have an associated stack, and if +// there is no stack supplied by the JS engine in exceptionStack. The // returned stack, if any, may also not be in the same compartment as // exceptionValue. // @@ -605,7 +607,8 @@ DispatchScriptErrorEvent(nsPIDOMWindowInner* win, JS::RootingContext* rootingCx, // the stack in the console message keeping the window alive. JSObject* FindExceptionStackForConsoleReport(nsPIDOMWindowInner* win, - JS::HandleValue exceptionValue); + JS::HandleValue exceptionValue, + JS::HandleObject exceptionStack); // Return a name for the compartment. // This function makes reasonable efforts to make this name both mostly human-readable From 8322304fb385175aa00cf1b63b45ffeb01d8dd5e Mon Sep 17 00:00:00 2001 From: Martok Date: Sat, 21 Jan 2023 21:54:56 +0100 Subject: [PATCH 08/11] Issue #2089 - In Promises, use a C++ version of SpeciesConstructor when calling from C++ Based-on: m-c 1344656,1386534 --- js/src/builtin/Promise.cpp | 33 ++++-- js/src/builtin/Promise.js | 7 -- js/src/jsapi.h | 3 + js/src/jsobj.cpp | 79 ++++++++++--- js/src/jsobj.h | 10 +- ...tructor-typedarray-species-other-global.js | 89 +++++++++++++++ js/src/vm/TypedArrayObject.cpp | 106 ++++++------------ 7 files changed, 219 insertions(+), 108 deletions(-) create mode 100644 js/src/tests/ecma_6/TypedArray/constructor-typedarray-species-other-global.js diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 2cd26d87d6..cd7b2c26b9 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -2479,7 +2479,18 @@ PromiseObject::unforgeableResolve(JSContext* cx, HandleValue value) return CommonStaticResolveRejectImpl(cx, cVal, value, ResolveMode); } -// ES2016, 25.4.4.6, implemented in Promise.js. +/** + * ES2016, 25.4.4.6 get Promise [ @@species ] + */ +static bool +Promise_static_species(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1: Return the this value. + args.rval().set(args.thisv()); + return true; +} // ES2016, 25.4.5.1, implemented in Promise.js. @@ -2520,6 +2531,12 @@ NewReactionRecord(JSContext* cx, HandleObject resultPromise, HandleValue onFulfi return reaction; } +static bool +IsPromiseSpecies(JSContext* cx, JSFunction* species) +{ + return species->maybeNative() == Promise_static_species; +} + // ES2016, 25.4.5.3., steps 3-5. MOZ_MUST_USE bool js::OriginalPromiseThen(JSContext* cx, Handle promise, @@ -2538,10 +2555,9 @@ js::OriginalPromiseThen(JSContext* cx, Handle promise, if (createDependent) { // Step 3. - RootedValue ctorVal(cx); - if (!SpeciesConstructor(cx, promiseObj, JSProto_Promise, &ctorVal)) + RootedObject C(cx, SpeciesConstructor(cx, promiseObj, JSProto_Promise, IsPromiseSpecies)); + if (!C) return false; - RootedObject C(cx, &ctorVal.toObject()); // Step 4. if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, true)) @@ -3193,11 +3209,10 @@ BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromis RootedObject PromiseCtor(cx); if (!GetBuiltinConstructor(cx, JSProto_Promise, &PromiseCtor)) return false; - RootedValue PromiseCtorVal(cx, ObjectValue(*PromiseCtor)); - RootedValue CVal(cx); - if (!SpeciesConstructor(cx, promiseObj, PromiseCtorVal, &CVal)) + + RootedObject C(cx, SpeciesConstructor(cx, PromiseCtor, JSProto_Promise, IsPromiseSpecies)); + if (!C) return false; - RootedObject C(cx, &CVal.toObject()); RootedObject resultPromise(cx, blockedPromise_); RootedObject resolveFun(cx); @@ -3579,7 +3594,7 @@ static const JSFunctionSpec promise_static_methods[] = { }; static const JSPropertySpec promise_static_properties[] = { - JS_SELF_HOSTED_SYM_GET(species, "Promise_static_get_species", 0), + JS_SYM_GET(species, Promise_static_species, 0), JS_PS_END }; diff --git a/js/src/builtin/Promise.js b/js/src/builtin/Promise.js index 91a1e1f562..94780a5f03 100644 --- a/js/src/builtin/Promise.js +++ b/js/src/builtin/Promise.js @@ -2,13 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// ES6, 25.4.4.6. -function Promise_static_get_species() { - // Step 1. - return this; -} -_SetCanonicalName(Promise_static_get_species, "get [Symbol.species]"); - // ES6, 25.4.5.1. function Promise_catch(onRejected) { // Steps 1-2. diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 6002d86ad5..b93353f223 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -2070,6 +2070,9 @@ inline int CheckIsSetterOp(JSSetterOp op); #define JS_PSGS(name, getter, setter, flags) \ JS_PS_ACCESSOR_SPEC(name, JSNATIVE_WRAPPER(getter), JSNATIVE_WRAPPER(setter), flags, \ JSPROP_SHARED) +#define JS_SYM_GET(symbol, getter, flags) \ + JS_PS_ACCESSOR_SPEC(reinterpret_cast(uint32_t(::JS::SymbolCode::symbol) + 1), \ + JSNATIVE_WRAPPER(getter), JSNATIVE_WRAPPER(nullptr), flags, JSPROP_SHARED) #define JS_SELF_HOSTED_GET(name, getterName, flags) \ JS_PS_ACCESSOR_SPEC(name, SELFHOSTED_WRAPPER(getterName), JSNATIVE_WRAPPER(nullptr), flags, \ JSPROP_SHARED | JSPROP_GETTER) diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 4b3d5758d7..3b38c20679 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -3864,34 +3864,77 @@ JSObject::maybeConstructorDisplayAtom() const return displayAtomFromObjectGroup(*group()); } -bool -js::SpeciesConstructor(JSContext* cx, HandleObject obj, HandleValue defaultCtor, MutableHandleValue pctor) +// ES 2016 7.3.20. +MOZ_MUST_USE JSObject* +js::SpeciesConstructor(JSContext* cx, HandleObject obj, HandleObject defaultCtor, + bool (*isDefaultSpecies)(JSContext*, JSFunction*)) { - HandlePropertyName shName = cx->names().SpeciesConstructor; - RootedValue func(cx); - if (!GlobalObject::getSelfHostedFunction(cx, cx->global(), shName, shName, 2, &func)) - return false; + // Step 1 (implicit). - FixedInvokeArgs<2> args(cx); + // Fast-path for steps 2 - 8. Applies if all of the following conditions + // are met: + // - obj.constructor can be retrieved without side-effects. + // - obj.constructor[[@@species]] can be retrieved without side-effects. + // - obj.constructor[[@@species]] is the builtin's original @@species + // getter. + RootedValue ctor(cx); + bool ctorGetSucceeded = GetPropertyPure(cx, obj, NameToId(cx->names().constructor), + ctor.address()); + if (ctorGetSucceeded && ctor.isObject() && &ctor.toObject() == defaultCtor) { + RootedObject ctorObj(cx, &ctor.toObject()); + RootedId speciesId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().species)); + JSFunction* getter; + if (GetGetterPure(cx, ctorObj, speciesId, &getter) && getter && + isDefaultSpecies(cx, getter)) + { + return defaultCtor; + } + } - args[0].setObject(*obj); - args[1].set(defaultCtor); + // Step 2. + if (!ctorGetSucceeded && !GetProperty(cx, obj, obj, cx->names().constructor, &ctor)) + return nullptr; - if (!Call(cx, func, UndefinedHandleValue, args, pctor)) - return false; + // Step 3. + if (ctor.isUndefined()) + return defaultCtor; - pctor.set(args.rval()); - return true; + // Step 4. + if (!ctor.isObject()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, + "object's 'constructor' property"); + return nullptr; + } + + // Step 5. + RootedObject ctorObj(cx, &ctor.toObject()); + RootedValue s(cx); + RootedId speciesId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().species)); + if (!GetProperty(cx, ctorObj, ctor, speciesId, &s)) + return nullptr; + + // Step 6. + if (s.isNullOrUndefined()) + return defaultCtor; + + // Step 7. + if (IsConstructor(s)) + return &s.toObject(); + + // Step 8. + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_CONSTRUCTOR, + "[Symbol.species] property of object's constructor"); + return nullptr; } -bool +MOZ_MUST_USE JSObject* js::SpeciesConstructor(JSContext* cx, HandleObject obj, JSProtoKey ctorKey, - MutableHandleValue pctor) + bool (*isDefaultSpecies)(JSContext*, JSFunction*)) { if (!GlobalObject::ensureConstructor(cx, cx->global(), ctorKey)) - return false; - RootedValue defaultCtor(cx, cx->global()->getConstructor(ctorKey)); - return SpeciesConstructor(cx, obj, defaultCtor, pctor); + return nullptr; + RootedObject defaultCtor(cx, &cx->global()->getConstructor(ctorKey).toObject()); + return SpeciesConstructor(cx, obj, defaultCtor, isDefaultSpecies); } bool diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 5e0cc347f9..49047192ed 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -1347,11 +1347,13 @@ FreezeObject(JSContext* cx, HandleObject obj) extern bool TestIntegrityLevel(JSContext* cx, HandleObject obj, IntegrityLevel level, bool* resultp); -extern bool -SpeciesConstructor(JSContext* cx, HandleObject obj, HandleValue defaultCtor, MutableHandleValue pctor); +extern MOZ_MUST_USE JSObject* +SpeciesConstructor(JSContext* cx, HandleObject obj, HandleObject defaultCtor, + bool (*isDefaultSpecies)(JSContext*, JSFunction*)); -extern bool -SpeciesConstructor(JSContext* cx, HandleObject obj, JSProtoKey ctorKey, MutableHandleValue pctor); +extern MOZ_MUST_USE JSObject* +SpeciesConstructor(JSContext* cx, HandleObject obj, JSProtoKey ctorKey, + bool (*isDefaultSpecies)(JSContext*, JSFunction*)); extern bool GetObjectFromIncumbentGlobal(JSContext* cx, MutableHandleObject obj); diff --git a/js/src/tests/ecma_6/TypedArray/constructor-typedarray-species-other-global.js b/js/src/tests/ecma_6/TypedArray/constructor-typedarray-species-other-global.js new file mode 100644 index 0000000000..a622d9c94b --- /dev/null +++ b/js/src/tests/ecma_6/TypedArray/constructor-typedarray-species-other-global.js @@ -0,0 +1,89 @@ +// 22.2.4.3 TypedArray ( typedArray ) + +// Test [[Prototype]] of newly created typed array and its array buffer, and +// ensure they are both created in the correct global. + +const thisGlobal = this; +const otherGlobal = newGlobal(); +const ta_i32 = otherGlobal.eval("new Int32Array(0)"); + +function assertBufferPrototypeFrom(newTypedArray, prototype) { + var typedArrayName = newTypedArray.constructor.name; + + assertEq(Object.getPrototypeOf(newTypedArray), thisGlobal[typedArrayName].prototype); + assertEq(Object.getPrototypeOf(newTypedArray.buffer), prototype); +} + +const EMPTY = {}; + +// Test SpeciesConstructor() implementation selects the correct (fallback) constructor. +const testCases = [ + // Create the array buffer from the species constructor. + { constructor: EMPTY, prototype: otherGlobal.ArrayBuffer.prototype }, + + // Use %ArrayBuffer% from this global if constructor is undefined. + { constructor: undefined, prototype: ArrayBuffer.prototype }, + + // Use %ArrayBuffer% from this global if species is undefined. + { constructor: {[Symbol.species]: undefined}, prototype: ArrayBuffer.prototype }, + + // Use %ArrayBuffer% from this global if species is null. + { constructor: {[Symbol.species]: null}, prototype: ArrayBuffer.prototype }, +]; + +for (let { constructor, prototype } of testCases) { + if (constructor !== EMPTY) { + ta_i32.buffer.constructor = constructor; + } + + // Same element type. + assertBufferPrototypeFrom(new Int32Array(ta_i32), prototype); + + // Different element type. + assertBufferPrototypeFrom(new Int16Array(ta_i32), prototype); +} + + +// Also ensure TypeErrors are thrown from the correct global. +const errorTestCases = [ + // Constructor property is neither undefined nor an object. + { constructor: null }, + { constructor: 123 }, + + // Species property is neither undefined/null nor a constructor function. + { constructor: { [Symbol.species]: 123 } }, + { constructor: { [Symbol.species]: [] } }, + { constructor: { [Symbol.species]: () => {} } }, +]; + +for (let { constructor } of errorTestCases) { + ta_i32.buffer.constructor = constructor; + + // Same element type. + assertThrowsInstanceOf(() => new Int32Array(ta_i32), TypeError); + + // Different element type. + assertThrowsInstanceOf(() => new Int32Array(ta_i32), TypeError); +} + + +// TypedArrays using SharedArrayBuffers never call the SpeciesConstructor operation. +if (this.SharedArrayBuffer) { + const ta_i32_shared = otherGlobal.eval("new Int32Array(new SharedArrayBuffer(0))"); + + Object.defineProperty(ta_i32_shared.buffer, "constructor", { + get() { + throw new Error("constructor property accessed"); + } + }); + + // Same element type. + assertBufferPrototypeFrom(new Int32Array(ta_i32_shared), ArrayBuffer.prototype); + + // Different element type. + assertBufferPrototypeFrom(new Int16Array(ta_i32_shared), ArrayBuffer.prototype); +} + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index 1b8a1089ae..9dca828a3a 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -1063,65 +1063,28 @@ TypedArrayObjectTemplate::AllocateArrayBuffer(JSContext* cx, HandleValue ctor } static bool -IsArrayBufferConstructor(const Value& v) +IsArrayBufferSpecies(JSContext* cx, JSFunction* species) { - return v.isObject() && - v.toObject().is() && - v.toObject().as().isNative() && - v.toObject().as().native() == ArrayBufferObject::class_constructor; + return IsSelfHostedFunctionWithName(species, cx->names().ArrayBufferSpecies); } -static bool -IsArrayBufferSpecies(JSContext* cx, HandleObject origBuffer) -{ - RootedValue ctor(cx); - if (!GetPropertyPure(cx, origBuffer, NameToId(cx->names().constructor), ctor.address())) - return false; - - if (!IsArrayBufferConstructor(ctor)) - return false; - - RootedObject ctorObj(cx, &ctor.toObject()); - RootedId speciesId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().species)); - JSFunction* getter; - if (!GetGetterPure(cx, ctorObj, speciesId, &getter)) - return false; - - if (!getter) - return false; - - return IsSelfHostedFunctionWithName(getter, cx->names().ArrayBufferSpecies); -} - -static bool +static JSObject* GetSpeciesConstructor(JSContext* cx, HandleObject obj, bool isWrapped, - SpeciesConstructorOverride override, MutableHandleValue ctor) + SpeciesConstructorOverride override) { - if (!isWrapped) { - if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_ArrayBuffer)) - return false; - RootedValue defaultCtor(cx, cx->global()->getConstructor(JSProto_ArrayBuffer)); - // The second disjunct is an optimization. - if (override == SpeciesConstructorOverride::ArrayBuffer || IsArrayBufferSpecies(cx, obj)) - ctor.set(defaultCtor); - else if (!SpeciesConstructor(cx, obj, defaultCtor, ctor)) - return false; + if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_ArrayBuffer)) + return nullptr; + RootedObject defaultCtor(cx, &cx->global()->getConstructor(JSProto_ArrayBuffer).toObject()); - return true; - } + // Use the current global's ArrayBuffer if the override is set. + if (override == SpeciesConstructorOverride::ArrayBuffer) + return defaultCtor; - { - JSAutoCompartment ac(cx, obj); - if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_ArrayBuffer)) - return false; - RootedValue defaultCtor(cx, cx->global()->getConstructor(JSProto_ArrayBuffer)); - if (override == SpeciesConstructorOverride::ArrayBuffer) - ctor.set(defaultCtor); - else if (!SpeciesConstructor(cx, obj, defaultCtor, ctor)) - return false; - } + RootedObject wrappedObj(cx, obj); + if (isWrapped && !cx->compartment()->wrap(cx, &wrappedObj)) + return nullptr; - return JS_WrapValue(cx, ctor); + return SpeciesConstructor(cx, wrappedObj, defaultCtor, IsArrayBufferSpecies); } // ES 2017 draft rev 8633ffd9394b203b8876bb23cb79aff13eb07310 24.1.1.4. @@ -1137,9 +1100,10 @@ TypedArrayObjectTemplate::CloneArrayBufferNoCopy(JSContext* cx, // Step 1 (skipped). // Step 2.a. - RootedValue cloneCtor(cx); - if (!GetSpeciesConstructor(cx, srcBuffer, isWrapped, override, &cloneCtor)) + JSObject* ctorObj = GetSpeciesConstructor(cx, srcBuffer, isWrapped, override); + if (!ctorObj) return false; + RootedValue cloneCtor(cx, ObjectValue(*ctorObj)); // Step 2.b. if (srcBuffer->isDetached()) { @@ -1181,7 +1145,8 @@ TypedArrayObjectTemplate::fromArray(JSContext* cx, HandleObject other, return fromObject(cx, other, newTarget); } -// ES 2017 draft rev 8633ffd9394b203b8876bb23cb79aff13eb07310 22.2.4.3. +// ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210 +// 22.2.4.3 TypedArray ( typedArray ) template /* static */ JSObject* TypedArrayObjectTemplate::fromTypedArray(JSContext* cx, HandleObject other, bool isWrapped, @@ -1229,61 +1194,62 @@ TypedArrayObjectTemplate::fromTypedArray(JSContext* cx, HandleObject other, b return nullptr; } - // Steps 10. + // Step 9. uint32_t elementLength = srcArray->length(); - // Steps 11-12. + // Steps 10-11. Scalar::Type srcType = srcArray->type(); - // Step 13 (skipped). + // Step 12 (skipped). - // Step 14. + // Step 13. uint32_t srcByteOffset = srcArray->byteOffset(); - // Step 17, modified for SharedArrayBuffer. + // Steps 16-17. bool isShared = srcArray->isSharedMemory(); SpeciesConstructorOverride override = isShared ? SpeciesConstructorOverride::ArrayBuffer : SpeciesConstructorOverride::None; - // Steps 8-9, 17. + // Steps 8, 16-17. Rooted buffer(cx); if (ArrayTypeID() == srcType) { - // Step 17.a. + // Step 16.a. uint32_t srcLength = srcArray->byteLength(); - // Step 17.b, modified for SharedArrayBuffer + // Steps 16.b-c. if (!CloneArrayBufferNoCopy(cx, srcData, isWrapped, srcByteOffset, srcLength, override, &buffer)) { return nullptr; } } else { - // Step 18.a, modified for SharedArrayBuffer - RootedValue bufferCtor(cx); - if (!GetSpeciesConstructor(cx, srcData, isWrapped, override, &bufferCtor)) + // Steps 17.a-b. + JSObject* ctorObj = GetSpeciesConstructor(cx, srcData, isWrapped, override); + if (!ctorObj) return nullptr; + RootedValue bufferCtor(cx, ObjectValue(*ctorObj)); - // Step 15-16, 18.b. + // Steps 14-15, 17.c. if (!AllocateArrayBuffer(cx, bufferCtor, elementLength, BYTES_PER_ELEMENT, &buffer)) return nullptr; - // Step 18.c. + // Step 17.d. if (srcArray->hasDetachedBuffer()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); return nullptr; } } - // Steps 3, 4 (remaining part), 19-22. + // Steps 3-4 (remaining part), 18-21. Rooted obj(cx, makeInstance(cx, buffer, 0, elementLength, proto)); if (!obj) return nullptr; - // Step 18.d-g or 24.1.1.4 step 11. + // Steps 17.e-h or 24.1.1.4 step 8. if (!TypedArrayMethods::setFromTypedArray(cx, obj, srcArray)) return nullptr; - // Step 23. + // Step 22. return obj; } From ece0496985800e17a853b66405232dd20b757142 Mon Sep 17 00:00:00 2001 From: Martok Date: Sat, 21 Jan 2023 22:21:45 +0100 Subject: [PATCH 09/11] Issue #2089 - Avoid copying/recreating iterator result, AsyncGeneratorRequest and GeneratorObject expression stacks Based-on: m-c 1394682,1410283,1396499 --- js/src/builtin/Promise.cpp | 7 +++- js/src/jit/BaselineCompiler.cpp | 4 +- js/src/jscompartment.cpp | 4 ++ js/src/jscompartment.h | 5 +++ js/src/jsiter.cpp | 73 +++++++++++++++++++++++++++++---- js/src/vm/AsyncIteration.cpp | 23 ++++++++--- js/src/vm/AsyncIteration.h | 63 +++++++++++++++++++++++----- js/src/vm/GeneratorObject.cpp | 39 +++++++++++++----- js/src/vm/GeneratorObject.h | 3 ++ js/src/vm/NativeObject-inl.h | 17 ++++++++ js/src/vm/NativeObject.h | 3 ++ js/src/vm/TypeInference.h | 5 +++ 12 files changed, 208 insertions(+), 38 deletions(-) diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index cd7b2c26b9..d1a92c6246 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -2861,6 +2861,8 @@ js::AsyncGeneratorResolve(JSContext* cx, Handle asyncGenO // Step 5. RootedObject resultPromise(cx, request->promise()); + asyncGenObj->cacheRequest(request); + // Step 6. RootedObject resultObj(cx, CreateIterResultObject(cx, value, done)); if (!resultObj) @@ -2899,6 +2901,8 @@ js::AsyncGeneratorReject(JSContext* cx, Handle asyncGenOb // Step 5. RootedObject resultPromise(cx, request->promise()); + asyncGenObj->cacheRequest(request); + // Step 6. if (!RejectMaybeWrappedPromise(cx, resultPromise, exception)) return false; @@ -3036,7 +3040,8 @@ js::AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal, // Step 5 (reordered). Rooted request( - cx, AsyncGeneratorRequest::create(cx, completionKind, completionValue, resultPromise)); + cx, AsyncGeneratorObject::createRequest(cx, asyncGenObj, completionKind, completionValue, + resultPromise)); if (!request) return false; diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 53254718c0..61de870cdb 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -4557,20 +4557,20 @@ BaselineCompiler::emit_JSOP_RESUME() Register initLength = regs.takeAny(); masm.loadPtr(Address(scratch2, NativeObject::offsetOfElements()), scratch2); masm.load32(Address(scratch2, ObjectElements::offsetOfInitializedLength()), initLength); + masm.store32(Imm32(0), Address(scratch2, ObjectElements::offsetOfInitializedLength())); Label loop, loopDone; masm.bind(&loop); masm.branchTest32(Assembler::Zero, initLength, initLength, &loopDone); { masm.pushValue(Address(scratch2, 0)); + masm.patchableCallPreBarrier(exprStackSlot, MIRType::Value); masm.addPtr(Imm32(sizeof(Value)), scratch2); masm.sub32(Imm32(1), initLength); masm.jump(&loop); } masm.bind(&loopDone); - masm.patchableCallPreBarrier(exprStackSlot, MIRType::Value); - masm.storeValue(NullValue(), exprStackSlot); regs.add(initLength); } diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 5e33f27f7d..8cf22026f3 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -85,6 +85,7 @@ JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options = jitCompartment_(nullptr), mappedArgumentsTemplate_(nullptr), unmappedArgumentsTemplate_(nullptr), + iterResultTemplate_(nullptr), lcovOutput() { runtime_->numCompartments++; @@ -846,6 +847,9 @@ JSCompartment::sweepTemplateObjects() if (unmappedArgumentsTemplate_ && IsAboutToBeFinalized(&unmappedArgumentsTemplate_)) unmappedArgumentsTemplate_.set(nullptr); + + if (iterResultTemplate_ && IsAboutToBeFinalized(&iterResultTemplate_)) + iterResultTemplate_.set(nullptr); } /* static */ void diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index d789c547fa..7723aeffa4 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -861,6 +861,7 @@ struct JSCompartment js::ReadBarriered mappedArgumentsTemplate_; js::ReadBarriered unmappedArgumentsTemplate_; + js::ReadBarriered iterResultTemplate_; public: bool ensureJitCompartmentExists(JSContext* cx); @@ -872,6 +873,10 @@ struct JSCompartment js::ArgumentsObject* maybeArgumentsTemplateObject(bool mapped) const; + static const size_t IterResultObjectValueSlot = 0; + static const size_t IterResultObjectDoneSlot = 1; + js::NativeObject* getOrCreateIterResultTemplateObject(JSContext* cx); + public: // Aggregated output used to collect JSScript hit counts when code coverage // is enabled. diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index 858e10a326..f7fb664b0c 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -8,6 +8,7 @@ #include "jsiter.h" #include "mozilla/ArrayUtils.h" +#include "mozilla/DebugOnly.h" #include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" #include "mozilla/PodOperations.h" @@ -45,6 +46,7 @@ using namespace js::gc; using JS::ForOfIterator; using mozilla::ArrayLength; +using mozilla::DebugOnly; using mozilla::Maybe; using mozilla::PodCopy; using mozilla::PodZero; @@ -944,25 +946,78 @@ js::CreateIterResultObject(JSContext* cx, HandleValue value, bool done) // Step 1 (implicit). // Step 2. - RootedObject resultObj(cx, NewBuiltinClassInstance(cx)); - if (!resultObj) + RootedObject templateObject(cx, cx->compartment()->getOrCreateIterResultTemplateObject(cx)); + if (!templateObject) return nullptr; + NativeObject* resultObj = NativeObject::createWithTemplate(cx, gc::DefaultHeap, templateObject); + if (!resultObj) + return nullptr; + // Step 3. - if (!DefineProperty(cx, resultObj, cx->names().value, value)) - return nullptr; + resultObj->setSlot(JSCompartment::IterResultObjectValueSlot, value); // Step 4. - if (!DefineProperty(cx, resultObj, cx->names().done, - done ? TrueHandleValue : FalseHandleValue)) - { - return nullptr; - } + resultObj->setSlot(JSCompartment::IterResultObjectDoneSlot, + done ? TrueHandleValue : FalseHandleValue); // Step 5. return resultObj; } +NativeObject* +JSCompartment::getOrCreateIterResultTemplateObject(JSContext* cx) +{ + if (iterResultTemplate_) + return iterResultTemplate_; + + // Create template plain object + RootedNativeObject templateObject(cx, NewBuiltinClassInstance(cx, TenuredObject)); + if (!templateObject) + return iterResultTemplate_; // = nullptr + + // Create a new group for the template. + Rooted proto(cx, templateObject->taggedProto()); + RootedObjectGroup group(cx, ObjectGroupCompartment::makeGroup(cx, templateObject->getClass(), + proto)); + if (!group) + return iterResultTemplate_; // = nullptr + templateObject->setGroup(group); + + // Set dummy `value` property + if (!NativeDefineDataProperty(cx, templateObject, cx->names().value, UndefinedHandleValue, + JSPROP_ENUMERATE)) + { + return iterResultTemplate_; // = nullptr + } + + // Set dummy `done` property + if (!NativeDefineDataProperty(cx, templateObject, cx->names().done, TrueHandleValue, + JSPROP_ENUMERATE)) + { + return iterResultTemplate_; // = nullptr + } + + // Update `value` property typeset, since it can be any value. + HeapTypeSet* types = group->maybeGetProperty(NameToId(cx->names().value)); + MOZ_ASSERT(types); + { + AutoEnterAnalysis enter(cx); + types->makeUnknown(cx); + } + + // Make sure that the properties are in the right slots. + DebugOnly shape = templateObject->lastProperty(); + MOZ_ASSERT(shape->previous()->slot() == JSCompartment::IterResultObjectValueSlot && + shape->previous()->propidRef() == NameToId(cx->names().value)); + MOZ_ASSERT(shape->slot() == JSCompartment::IterResultObjectDoneSlot && + shape->propidRef() == NameToId(cx->names().done)); + + iterResultTemplate_.set(templateObject); + + return iterResultTemplate_; +} + bool js::ThrowStopIteration(JSContext* cx) { diff --git a/js/src/vm/AsyncIteration.cpp b/js/src/vm/AsyncIteration.cpp index bcc1814cef..58ad09815c 100644 --- a/js/src/vm/AsyncIteration.cpp +++ b/js/src/vm/AsyncIteration.cpp @@ -311,9 +311,24 @@ AsyncGeneratorObject::create(JSContext* cx, HandleFunction asyncGen, HandleValue // Step 8. asyncGenObj->clearSingleQueueRequest(); + asyncGenObj->clearCachedRequest(); + return asyncGenObj; } +/* static */ AsyncGeneratorRequest* +AsyncGeneratorObject::createRequest(JSContext* cx, Handle asyncGenObj, + CompletionKind completionKind, HandleValue completionValue, + HandleObject promise) +{ + if (!asyncGenObj->hasCachedRequest()) + return AsyncGeneratorRequest::create(cx, completionKind, completionValue, promise); + + AsyncGeneratorRequest* request = asyncGenObj->takeCachedRequest(); + request->init(completionKind, completionValue, promise); + return request; +} + static MOZ_MUST_USE bool InternalEnqueue(JSContext* cx, HandleArrayObject queue, HandleValue val) { @@ -428,17 +443,15 @@ const Class AsyncGeneratorRequest::class_ = { // Async Iteration proposal 11.4.3.1. /* static */ AsyncGeneratorRequest* -AsyncGeneratorRequest::create(JSContext* cx, CompletionKind completionKind_, - HandleValue completionValue_, HandleObject promise_) +AsyncGeneratorRequest::create(JSContext* cx, CompletionKind completionKind, + HandleValue completionValue, HandleObject promise) { RootedObject obj(cx, NewNativeObjectWithGivenProto(cx, &class_, nullptr)); if (!obj) return nullptr; Handle request = obj.as(); - request->setCompletionKind(completionKind_); - request->setCompletionValue(completionValue_); - request->setPromise(promise_); + request->init(completionKind, completionValue, promise); return request; } diff --git a/js/src/vm/AsyncIteration.h b/js/src/vm/AsyncIteration.h index b712c233a1..66c9c091f1 100644 --- a/js/src/vm/AsyncIteration.h +++ b/js/src/vm/AsyncIteration.h @@ -50,6 +50,8 @@ AsyncGeneratorYieldReturnAwaitedRejected(JSContext* cx, Handle asyncGenObj, HandleValue reason); +class AsyncGeneratorObject; + class AsyncGeneratorRequest : public NativeObject { private: @@ -60,23 +62,26 @@ class AsyncGeneratorRequest : public NativeObject Slots, }; - void setCompletionKind(CompletionKind completionKind_) { + void init(CompletionKind completionKind, HandleValue completionValue, + HandleObject promise) { setFixedSlot(Slot_CompletionKind, - Int32Value(static_cast(completionKind_))); + Int32Value(static_cast(completionKind))); + setFixedSlot(Slot_CompletionValue, completionValue); + setFixedSlot(Slot_Promise, ObjectValue(*promise)); } - void setCompletionValue(HandleValue completionValue_) { - setFixedSlot(Slot_CompletionValue, completionValue_); - } - void setPromise(HandleObject promise_) { - setFixedSlot(Slot_Promise, ObjectValue(*promise_)); + + void clearData() { + setFixedSlot(Slot_CompletionValue, NullValue()); + setFixedSlot(Slot_Promise, NullValue()); } + friend AsyncGeneratorObject; + public: static const Class class_; - static AsyncGeneratorRequest* - create(JSContext* cx, CompletionKind completionKind, HandleValue completionValue, - HandleObject promise); + static AsyncGeneratorRequest* create(JSContext* cx, CompletionKind completionKind, + HandleValue completionValue, HandleObject promise); CompletionKind completionKind() const { return static_cast(getFixedSlot(Slot_CompletionKind).toInt32()); @@ -96,6 +101,7 @@ class AsyncGeneratorObject : public NativeObject Slot_State = 0, Slot_Generator, Slot_QueueOrRequest, + Slot_CachedRequest, Slots }; @@ -139,7 +145,7 @@ class AsyncGeneratorObject : public NativeObject setFixedSlot(Slot_QueueOrRequest, ObjectValue(*request)); } void clearSingleQueueRequest() { - setFixedSlot(Slot_QueueOrRequest, NullHandleValue); + setFixedSlot(Slot_QueueOrRequest, NullValue()); } AsyncGeneratorRequest* singleQueueRequest() const { return &getFixedSlot(Slot_QueueOrRequest).toObject().as(); @@ -218,6 +224,41 @@ class AsyncGeneratorObject : public NativeObject return isSingleQueueEmpty(); return queue()->length() == 0; } + + // This function does either of the following: + // * return a cached request object with the slots updated + // * create a new request object with the slots set + static AsyncGeneratorRequest* createRequest(JSContext* cx, + Handle asyncGenObj, + CompletionKind completionKind, + HandleValue completionValue, + HandleObject promise); + + // Stores the given request to the generator's cache after clearing its data + // slots. The cached request will be reused in the subsequent createRequest + // call. + void cacheRequest(AsyncGeneratorRequest* request) { + if (hasCachedRequest()) + return; + + request->clearData(); + setFixedSlot(Slot_CachedRequest, ObjectValue(*request)); + } + + private: + bool hasCachedRequest() const { + return getFixedSlot(Slot_CachedRequest).isObject(); + } + + AsyncGeneratorRequest* takeCachedRequest() { + auto request = &getFixedSlot(Slot_CachedRequest).toObject().as(); + clearCachedRequest(); + return request; + } + + void clearCachedRequest() { + setFixedSlot(Slot_CachedRequest, NullValue()); + } }; JSObject* diff --git a/js/src/vm/GeneratorObject.cpp b/js/src/vm/GeneratorObject.cpp index df2869c89c..aad29b910d 100644 --- a/js/src/vm/GeneratorObject.cpp +++ b/js/src/vm/GeneratorObject.cpp @@ -10,7 +10,9 @@ #include "jsatominlines.h" #include "jsscriptinlines.h" +#include "vm/ArrayObject-inl.h" #include "vm/NativeObject-inl.h" +#include "vm/UnboxedObject-inl.h" #include "vm/Stack-inl.h" using namespace js; @@ -66,7 +68,7 @@ GeneratorObject::suspend(JSContext* cx, HandleObject obj, AbstractFramePtr frame MOZ_ASSERT(*pc == JSOP_INITIALYIELD || *pc == JSOP_YIELD || *pc == JSOP_AWAIT); Rooted genObj(cx, &obj->as()); - MOZ_ASSERT(!genObj->hasExpressionStack()); + MOZ_ASSERT(!genObj->hasExpressionStack() || genObj->isExpressionStackEmpty()); MOZ_ASSERT_IF(*pc == JSOP_AWAIT, genObj->callee().isAsync()); MOZ_ASSERT_IF(*pc == JSOP_YIELD, genObj->callee().isStarGenerator() || @@ -78,16 +80,33 @@ GeneratorObject::suspend(JSContext* cx, HandleObject obj, AbstractFramePtr frame return false; } + ArrayObject* stack = nullptr; + if (nvalues) { + do { + if (genObj->hasExpressionStack()) { + MOZ_ASSERT(genObj->expressionStack().getDenseInitializedLength() == 0); + auto result = SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, + &genObj->expressionStack().as(), + 0, vp, nvalues, ShouldUpdateTypes::DontUpdate); + if (result == DenseElementResult::Success) { + MOZ_ASSERT(genObj->expressionStack().getDenseInitializedLength() == nvalues); + break; + } + if (result == DenseElementResult::Failure) + return false; + } + + stack = NewDenseCopiedArray(cx, nvalues, vp); + if (!stack) + return false; + } while (false); + } + uint32_t yieldAndAwaitIndex = GET_UINT24(pc); genObj->setYieldAndAwaitIndex(yieldAndAwaitIndex); genObj->setEnvironmentChain(*frame.environmentChain()); - - if (nvalues) { - ArrayObject* stack = NewDenseCopiedArray(cx, nvalues, vp); - if (!stack) - return false; + if (stack) genObj->setExpressionStack(*stack); - } return true; } @@ -167,13 +186,13 @@ GeneratorObject::resume(JSContext* cx, InterpreterActivation& activation, if (genObj->hasArgsObj()) activation.regs().fp()->initArgsObj(genObj->argsObj()); - if (genObj->hasExpressionStack()) { - uint32_t len = genObj->expressionStack().length(); + if (genObj->hasExpressionStack() && !genObj->isExpressionStackEmpty()) { + uint32_t len = genObj->expressionStack().getDenseInitializedLength(); MOZ_ASSERT(activation.regs().spForStackDepth(len)); const Value* src = genObj->expressionStack().getDenseElements(); mozilla::PodCopy(activation.regs().sp, src, len); activation.regs().sp += len; - genObj->clearExpressionStack(); + genObj->expressionStack().setDenseInitializedLength(0); } JSScript* script = callee->nonLazyScript(); diff --git a/js/src/vm/GeneratorObject.h b/js/src/vm/GeneratorObject.h index c33eb48d43..c717d25e66 100644 --- a/js/src/vm/GeneratorObject.h +++ b/js/src/vm/GeneratorObject.h @@ -99,6 +99,9 @@ class GeneratorObject : public NativeObject bool hasExpressionStack() const { return getFixedSlot(EXPRESSION_STACK_SLOT).isObject(); } + bool isExpressionStackEmpty() const { + return expressionStack().getDenseInitializedLength() == 0; + } ArrayObject& expressionStack() const { return getFixedSlot(EXPRESSION_STACK_SLOT).toObject().as(); } diff --git a/js/src/vm/NativeObject-inl.h b/js/src/vm/NativeObject-inl.h index 004b308f05..4d692f4a9f 100644 --- a/js/src/vm/NativeObject-inl.h +++ b/js/src/vm/NativeObject-inl.h @@ -242,6 +242,23 @@ NativeObject::getDenseOrTypedArrayElement(uint32_t idx) return getDenseElement(idx); } +/* static */ inline NativeObject* +NativeObject::createWithTemplate(JSContext* cx, gc::InitialHeap heap, + HandleObject templateObject) +{ + RootedObjectGroup group(cx, templateObject->group()); + RootedShape shape(cx, templateObject->as().lastProperty()); + + gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); + MOZ_ASSERT(CanBeFinalizedInBackground(kind, shape->getObjectClass())); + kind = gc::GetBackgroundAllocKind(kind); + + JSObject* baseObj = create(cx, kind, heap, shape, group); + if (!baseObj) + return nullptr; + return &baseObj->as(); +} + /* static */ inline NativeObject* NativeObject::copy(ExclusiveContext* cx, gc::AllocKind kind, gc::InitialHeap heap, HandleNativeObject templateObject) diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h index cf6d684ac0..030dcfb888 100644 --- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -485,6 +485,9 @@ class NativeObject : public ShapedObject return cells && cells->hasCell(cell); } + static inline NativeObject* + createWithTemplate(JSContext* cx, js::gc::InitialHeap heap, HandleObject templateObject); + protected: #ifdef DEBUG void checkShapeConsistency(); diff --git a/js/src/vm/TypeInference.h b/js/src/vm/TypeInference.h index d24a39e531..764f99ca07 100644 --- a/js/src/vm/TypeInference.h +++ b/js/src/vm/TypeInference.h @@ -620,6 +620,11 @@ class ConstraintTypeSet : public TypeSet */ void addType(ExclusiveContext* cx, Type type); + /* Generalize to any type. */ + void makeUnknown(ExclusiveContext* cx) { + addType(cx, UnknownType()); + } + // Trigger a post barrier when writing to this set, if necessary. // addType(cx, type) takes care of this automatically. void postWriteBarrier(ExclusiveContext* cx, Type type); From 8495231395a5a4297896594d47cb6ac3026ec569 Mon Sep 17 00:00:00 2001 From: Job Bautista Date: Wed, 25 Jan 2023 14:35:39 +0800 Subject: [PATCH 10/11] Issue #2091 - Parse \p{Extended_Pictographic} too. This is essentially a follow up to Issue #1286. We already have UCHAR_EXTENDED_PICTOGRAPHIC in our in-tree ICU, so there's no reason to comment this case out. --- js/src/irregexp/RegExpCharRanges.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/irregexp/RegExpCharRanges.cpp b/js/src/irregexp/RegExpCharRanges.cpp index 8266d351d3..99c671ebcd 100644 --- a/js/src/irregexp/RegExpCharRanges.cpp +++ b/js/src/irregexp/RegExpCharRanges.cpp @@ -550,7 +550,7 @@ bool IsSupportedBinaryProperty(UProperty property) { case UCHAR_EMOJI_MODIFIER_BASE: case UCHAR_EMOJI_MODIFIER: case UCHAR_EMOJI_PRESENTATION: - // case UCHAR_EXTENDED_PICTOGRAPHIC: + case UCHAR_EXTENDED_PICTOGRAPHIC: case UCHAR_EXTENDER: case UCHAR_GRAPHEME_BASE: case UCHAR_GRAPHEME_EXTEND: From 70279e1eb2a2c4e7764d84871be31cbc4649c1f4 Mon Sep 17 00:00:00 2001 From: Martok Date: Wed, 25 Jan 2023 23:22:07 +0100 Subject: [PATCH 11/11] Issue #2093 - Trace HeapPtr for GC move/compact events, regression from #2072 --- js/src/vm/RegExpObject.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index 2c6d66381f..151571085d 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -970,6 +970,7 @@ RegExpShared::trace(JSTracer* trc) TraceNullableEdge(trc, &source, "RegExpShared source"); for (auto& comp : compilationArray) TraceNullableEdge(trc, &comp.jitCode, "RegExpShared code"); + TraceNullableEdge(trc, &groupsTemplate_, "RegExpShared groupsTemplate"); } bool