Issue #2089 - In Promises, use a C++ version of SpeciesConstructor when calling from C++

Based-on: m-c 1344656,1386534
This commit is contained in:
Martok 2023-01-21 21:54:56 +01:00 committed by roytam1
commit 8322304fb3
7 changed files with 220 additions and 109 deletions

View file

@ -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<PromiseObject*> promise,
@ -2538,10 +2555,9 @@ js::OriginalPromiseThen(JSContext* cx, Handle<PromiseObject*> 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
};

View file

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

View file

@ -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<const char*>(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)

View file

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

View file

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

View file

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

View file

@ -1063,65 +1063,28 @@ TypedArrayObjectTemplate<T>::AllocateArrayBuffer(JSContext* cx, HandleValue ctor
}
static bool
IsArrayBufferConstructor(const Value& v)
IsArrayBufferSpecies(JSContext* cx, JSFunction* species)
{
return v.isObject() &&
v.toObject().is<JSFunction>() &&
v.toObject().as<JSFunction>().isNative() &&
v.toObject().as<JSFunction>().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<T>::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<T>::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<typename T>
/* static */ JSObject*
TypedArrayObjectTemplate<T>::fromTypedArray(JSContext* cx, HandleObject other, bool isWrapped,
@ -1229,61 +1194,62 @@ TypedArrayObjectTemplate<T>::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<ArrayBufferObject*> 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<TypedArrayObject*> 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<TypedArrayObject>::setFromTypedArray(cx, obj, srcArray))
return nullptr;
// Step 23.
// Step 22.
return obj;
}