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