From 28d4e4a5fa5ba7a22d3497769fbb5a9d11db7a9e Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Fri, 16 Mar 2018 09:04:55 +0100 Subject: Bug 1318017: Call GetPrototypeFromConstructor for generator/async function and Intl constructors [Depends on] Bug 755821: Function() should use the parser's argument parsing code --- js/src/builtin/Intl.cpp | 102 +++++++++++++-------- js/src/jsfun.cpp | 50 +++++++--- js/src/tests/Intl/Collator/construct-newtarget.js | 81 ++++++++++++++++ .../Intl/DateTimeFormat/construct-newtarget.js | 81 ++++++++++++++++ .../tests/Intl/NumberFormat/construct-newtarget.js | 81 ++++++++++++++++ .../AsyncFunctions/construct-newtarget.js | 79 ++++++++++++++++ js/src/tests/ecma_2017/AsyncFunctions/subclass.js | 31 +++++++ .../tests/ecma_6/Generators/construct-newtarget.js | 79 ++++++++++++++++ js/src/tests/ecma_6/Generators/subclass.js | 33 +++++++ js/src/vm/AsyncFunction.cpp | 19 ++-- js/src/vm/AsyncFunction.h | 3 + 11 files changed, 586 insertions(+), 53 deletions(-) create mode 100644 js/src/tests/Intl/Collator/construct-newtarget.js create mode 100644 js/src/tests/Intl/DateTimeFormat/construct-newtarget.js create mode 100644 js/src/tests/Intl/NumberFormat/construct-newtarget.js create mode 100644 js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js create mode 100644 js/src/tests/ecma_2017/AsyncFunctions/subclass.js create mode 100644 js/src/tests/ecma_6/Generators/construct-newtarget.js create mode 100644 js/src/tests/ecma_6/Generators/subclass.js diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp index 990a4acdb..3a20c487b 100644 --- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -765,42 +765,53 @@ static const JSFunctionSpec collator_methods[] = { }; /** - * Collator constructor. - * Spec: ECMAScript Internationalization API Specification, 10.1 + * 10.1.2 Intl.Collator([ locales [, options]]) + * + * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b */ static bool Collator(JSContext* cx, const CallArgs& args, bool construct) { RootedObject obj(cx); + // We're following ECMA-402 1st Edition when Collator is called because of + // backward compatibility issues. + // See https://github.com/tc39/ecma402/issues/57 if (!construct) { - // 10.1.2.1 step 3 + // ES Intl 1st ed., 10.1.2.1 step 3 JSObject* intl = cx->global()->getOrCreateIntlObject(cx); if (!intl) return false; RootedValue self(cx, args.thisv()); if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { - // 10.1.2.1 step 4 + // ES Intl 1st ed., 10.1.2.1 step 4 obj = ToObject(cx, self); if (!obj) return false; - // 10.1.2.1 step 5 + // ES Intl 1st ed., 10.1.2.1 step 5 bool extensible; if (!IsExtensible(cx, obj, &extensible)) return false; if (!extensible) return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); } else { - // 10.1.2.1 step 3.a + // ES Intl 1st ed., 10.1.2.1 step 3.a construct = true; } } if (construct) { - // 10.1.3.1 paragraph 2 - RootedObject proto(cx, cx->global()->getOrCreateCollatorPrototype(cx)); - if (!proto) + // Steps 2-5 (Inlined 9.1.14, OrdinaryCreateFromConstructor). + RootedObject proto(cx); + if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto)) return false; + + if (!proto) { + proto = cx->global()->getOrCreateCollatorPrototype(cx); + if (!proto) + return false; + } + obj = NewObjectWithGivenProto(cx, &CollatorClass, proto); if (!obj) return false; @@ -808,15 +819,13 @@ Collator(JSContext* cx, const CallArgs& args, bool construct) obj->as().setReservedSlot(UCOLLATOR_SLOT, PrivateValue(nullptr)); } - // 10.1.2.1 steps 1 and 2; 10.1.3.1 steps 1 and 2 RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); - // 10.1.2.1 step 6; 10.1.3.1 step 3 + // Step 6. if (!IntlInitialize(cx, obj, cx->names().InitializeCollator, locales, options)) return false; - // 10.1.2.1 steps 3.a and 7 args.rval().setObject(*obj); return true; } @@ -833,6 +842,7 @@ js::intl_Collator(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(!args.isConstructing()); // intl_Collator is an intrinsic for self-hosted JavaScript, so it cannot // be used with "new", but it still has to be treated as a constructor. return Collator(cx, args, true); @@ -1257,42 +1267,53 @@ static const JSFunctionSpec numberFormat_methods[] = { }; /** - * NumberFormat constructor. - * Spec: ECMAScript Internationalization API Specification, 11.1 + * 11.2.1 Intl.NumberFormat([ locales [, options]]) + * + * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b */ static bool NumberFormat(JSContext* cx, const CallArgs& args, bool construct) { RootedObject obj(cx); + // We're following ECMA-402 1st Edition when NumberFormat is called + // because of backward compatibility issues. + // See https://github.com/tc39/ecma402/issues/57 if (!construct) { - // 11.1.2.1 step 3 + // ES Intl 1st ed., 11.1.2.1 step 3 JSObject* intl = cx->global()->getOrCreateIntlObject(cx); if (!intl) return false; RootedValue self(cx, args.thisv()); if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { - // 11.1.2.1 step 4 + // ES Intl 1st ed., 11.1.2.1 step 4 obj = ToObject(cx, self); if (!obj) return false; - // 11.1.2.1 step 5 + // ES Intl 1st ed., 11.1.2.1 step 5 bool extensible; if (!IsExtensible(cx, obj, &extensible)) return false; if (!extensible) return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); } else { - // 11.1.2.1 step 3.a + // ES Intl 1st ed., 11.1.2.1 step 3.a construct = true; } } if (construct) { - // 11.1.3.1 paragraph 2 - RootedObject proto(cx, cx->global()->getOrCreateNumberFormatPrototype(cx)); - if (!proto) + // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). + RootedObject proto(cx); + if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto)) return false; + + if (!proto) { + proto = cx->global()->getOrCreateNumberFormatPrototype(cx); + if (!proto) + return false; + } + obj = NewObjectWithGivenProto(cx, &NumberFormatClass, proto); if (!obj) return false; @@ -1300,15 +1321,13 @@ NumberFormat(JSContext* cx, const CallArgs& args, bool construct) obj->as().setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nullptr)); } - // 11.1.2.1 steps 1 and 2; 11.1.3.1 steps 1 and 2 RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); - // 11.1.2.1 step 6; 11.1.3.1 step 3 + // Step 3. if (!IntlInitialize(cx, obj, cx->names().InitializeNumberFormat, locales, options)) return false; - // 11.1.2.1 steps 3.a and 7 args.rval().setObject(*obj); return true; } @@ -1325,6 +1344,7 @@ js::intl_NumberFormat(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(!args.isConstructing()); // intl_NumberFormat is an intrinsic for self-hosted JavaScript, so it // cannot be used with "new", but it still has to be treated as a // constructor. @@ -1725,42 +1745,53 @@ static const JSFunctionSpec dateTimeFormat_methods[] = { }; /** - * DateTimeFormat constructor. - * Spec: ECMAScript Internationalization API Specification, 12.1 + * 12.2.1 Intl.DateTimeFormat([ locales [, options]]) + * + * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b */ static bool DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct) { RootedObject obj(cx); + // We're following ECMA-402 1st Edition when DateTimeFormat is called + // because of backward compatibility issues. + // See https://github.com/tc39/ecma402/issues/57 if (!construct) { - // 12.1.2.1 step 3 + // ES Intl 1st ed., 12.1.2.1 step 3 JSObject* intl = cx->global()->getOrCreateIntlObject(cx); if (!intl) return false; RootedValue self(cx, args.thisv()); if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { - // 12.1.2.1 step 4 + // ES Intl 1st ed., 12.1.2.1 step 4 obj = ToObject(cx, self); if (!obj) return false; - // 12.1.2.1 step 5 + // ES Intl 1st ed., 12.1.2.1 step 5 bool extensible; if (!IsExtensible(cx, obj, &extensible)) return false; if (!extensible) return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); } else { - // 12.1.2.1 step 3.a + // ES Intl 1st ed., 12.1.2.1 step 3.a construct = true; } } if (construct) { - // 12.1.3.1 paragraph 2 - RootedObject proto(cx, cx->global()->getOrCreateDateTimeFormatPrototype(cx)); - if (!proto) + // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). + RootedObject proto(cx); + if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto)) return false; + + if (!proto) { + proto = cx->global()->getOrCreateDateTimeFormatPrototype(cx); + if (!proto) + return false; + } + obj = NewObjectWithGivenProto(cx, &DateTimeFormatClass, proto); if (!obj) return false; @@ -1768,15 +1799,13 @@ DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct) obj->as().setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(nullptr)); } - // 12.1.2.1 steps 1 and 2; 12.1.3.1 steps 1 and 2 RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); - // 12.1.2.1 step 6; 12.1.3.1 step 3 + // Step 3. if (!IntlInitialize(cx, obj, cx->names().InitializeDateTimeFormat, locales, options)) return false; - // 12.1.2.1 steps 3.a and 7 args.rval().setObject(*obj); return true; } @@ -1793,6 +1822,7 @@ js::intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(!args.isConstructing()); // intl_DateTimeFormat is an intrinsic for self-hosted JavaScript, so it // cannot be used with "new", but it still has to be treated as a // constructor. diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index c952441ad..9be02b217 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -1682,11 +1682,9 @@ const JSFunctionSpec js::function_methods[] = { }; static bool -FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind generatorKind, +FunctionConstructor(JSContext* cx, const CallArgs& args, GeneratorKind generatorKind, FunctionAsyncKind asyncKind) { - CallArgs args = CallArgsFromVp(argc, vp); - /* Block this call if security callbacks forbid it. */ Rooted global(cx, &args.callee().global()); if (!GlobalObject::isRuntimeCodeGenEnabled(cx, global)) { @@ -1803,16 +1801,23 @@ FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind gener * and so would a call to f from another top-level's script or function. */ RootedAtom anonymousAtom(cx, cx->names().anonymous); + + // ES2017, draft rev 0f10dba4ad18de92d47d421f378233a2eae8f077 + // 19.2.1.1.1 Runtime Semantics: CreateDynamicFunction, step 24. RootedObject proto(cx); - if (isStarGenerator) { + if (!isAsync) { + if (!GetPrototypeFromCallableConstructor(cx, args, &proto)) + return false; + } + + // 19.2.1.1.1, step 4.d, use %Generator% as the fallback prototype. + // Also use %Generator% for the unwrapped function of async functions. + if (!proto && isStarGenerator) { // Unwrapped function of async function should use GeneratorFunction, // while wrapped function isn't generator. proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, global); if (!proto) return false; - } else { - if (!GetPrototypeFromCallableConstructor(cx, args, &proto)) - return false; } RootedObject globalLexical(cx, &global->lexicalEnvironment()); @@ -1921,24 +1926,47 @@ FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind gener bool js::Function(JSContext* cx, unsigned argc, Value* vp) { - return FunctionConstructor(cx, argc, vp, NotGenerator, SyncFunction); + CallArgs args = CallArgsFromVp(argc, vp); + return FunctionConstructor(cx, args, NotGenerator, SyncFunction); } bool js::Generator(JSContext* cx, unsigned argc, Value* vp) { - return FunctionConstructor(cx, argc, vp, StarGenerator, SyncFunction); + CallArgs args = CallArgsFromVp(argc, vp); + return FunctionConstructor(cx, args, StarGenerator, SyncFunction); } bool js::AsyncFunctionConstructor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - if (!FunctionConstructor(cx, argc, vp, StarGenerator, AsyncFunction)) + + // Save the callee before its reset in FunctionConstructor(). + RootedObject newTarget(cx); + if (args.isConstructing()) + newTarget = &args.newTarget().toObject(); + else + newTarget = &args.callee(); + + if (!FunctionConstructor(cx, args, StarGenerator, AsyncFunction)) return false; + // ES2017, draft rev 0f10dba4ad18de92d47d421f378233a2eae8f077 + // 19.2.1.1.1 Runtime Semantics: CreateDynamicFunction, step 24. + RootedObject proto(cx); + if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) + return false; + + // 19.2.1.1.1, step 4.d, use %AsyncFunctionPrototype% as the fallback. + if (!proto) { + proto = GlobalObject::getOrCreateAsyncFunctionPrototype(cx, cx->global()); + if (!proto) + return false; + } + RootedFunction unwrapped(cx, &args.rval().toObject().as()); - RootedObject wrapped(cx, WrapAsyncFunction(cx, unwrapped)); + RootedObject wrapped(cx, WrapAsyncFunctionWithProto(cx, unwrapped, proto)); if (!wrapped) return false; diff --git a/js/src/tests/Intl/Collator/construct-newtarget.js b/js/src/tests/Intl/Collator/construct-newtarget.js new file mode 100644 index 000000000..5db1abf37 --- /dev/null +++ b/js/src/tests/Intl/Collator/construct-newtarget.js @@ -0,0 +1,81 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + + +// Test subclassing %Intl.Collator% works correctly. +class MyCollator extends Intl.Collator {} + +var obj = new MyCollator(); +assertEq(obj instanceof MyCollator, true); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), MyCollator.prototype); + +obj = Reflect.construct(MyCollator, []); +assertEq(obj instanceof MyCollator, true); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), MyCollator.prototype); + +obj = Reflect.construct(MyCollator, [], MyCollator); +assertEq(obj instanceof MyCollator, true); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), MyCollator.prototype); + +obj = Reflect.construct(MyCollator, [], Intl.Collator); +assertEq(obj instanceof MyCollator, false); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype); + + +// Set a different constructor as NewTarget. +obj = Reflect.construct(MyCollator, [], Array); +assertEq(obj instanceof MyCollator, false); +assertEq(obj instanceof Intl.Collator, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + +obj = Reflect.construct(Intl.Collator, [], Array); +assertEq(obj instanceof Intl.Collator, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + + +// The prototype defaults to %CollatorPrototype% if null. +function NewTargetNullPrototype() {} +NewTargetNullPrototype.prototype = null; + +obj = Reflect.construct(Intl.Collator, [], NewTargetNullPrototype); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype); + +obj = Reflect.construct(MyCollator, [], NewTargetNullPrototype); +assertEq(obj instanceof MyCollator, false); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype); + + +// "prototype" property is retrieved exactly once. +var trapLog = [], getLog = []; +var ProxiedConstructor = new Proxy(Intl.Collator, new Proxy({ + get(target, propertyKey, receiver) { + getLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +}, { + get(target, propertyKey, receiver) { + trapLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +})); + +obj = Reflect.construct(Intl.Collator, [], ProxiedConstructor); +assertEqArray(trapLog, ["get"]); +assertEqArray(getLog, ["prototype"]); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype); + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/Intl/DateTimeFormat/construct-newtarget.js b/js/src/tests/Intl/DateTimeFormat/construct-newtarget.js new file mode 100644 index 000000000..bbc08c79a --- /dev/null +++ b/js/src/tests/Intl/DateTimeFormat/construct-newtarget.js @@ -0,0 +1,81 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + + +// Test subclassing %Intl.DateTimeFormat% works correctly. +class MyDateTimeFormat extends Intl.DateTimeFormat {} + +var obj = new MyDateTimeFormat(); +assertEq(obj instanceof MyDateTimeFormat, true); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), MyDateTimeFormat.prototype); + +obj = Reflect.construct(MyDateTimeFormat, []); +assertEq(obj instanceof MyDateTimeFormat, true); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), MyDateTimeFormat.prototype); + +obj = Reflect.construct(MyDateTimeFormat, [], MyDateTimeFormat); +assertEq(obj instanceof MyDateTimeFormat, true); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), MyDateTimeFormat.prototype); + +obj = Reflect.construct(MyDateTimeFormat, [], Intl.DateTimeFormat); +assertEq(obj instanceof MyDateTimeFormat, false); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype); + + +// Set a different constructor as NewTarget. +obj = Reflect.construct(MyDateTimeFormat, [], Array); +assertEq(obj instanceof MyDateTimeFormat, false); +assertEq(obj instanceof Intl.DateTimeFormat, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + +obj = Reflect.construct(Intl.DateTimeFormat, [], Array); +assertEq(obj instanceof Intl.DateTimeFormat, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + + +// The prototype defaults to %DateTimeFormatPrototype% if null. +function NewTargetNullPrototype() {} +NewTargetNullPrototype.prototype = null; + +obj = Reflect.construct(Intl.DateTimeFormat, [], NewTargetNullPrototype); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype); + +obj = Reflect.construct(MyDateTimeFormat, [], NewTargetNullPrototype); +assertEq(obj instanceof MyDateTimeFormat, false); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype); + + +// "prototype" property is retrieved exactly once. +var trapLog = [], getLog = []; +var ProxiedConstructor = new Proxy(Intl.DateTimeFormat, new Proxy({ + get(target, propertyKey, receiver) { + getLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +}, { + get(target, propertyKey, receiver) { + trapLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +})); + +obj = Reflect.construct(Intl.DateTimeFormat, [], ProxiedConstructor); +assertEqArray(trapLog, ["get"]); +assertEqArray(getLog, ["prototype"]); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype); + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/Intl/NumberFormat/construct-newtarget.js b/js/src/tests/Intl/NumberFormat/construct-newtarget.js new file mode 100644 index 000000000..0053b2737 --- /dev/null +++ b/js/src/tests/Intl/NumberFormat/construct-newtarget.js @@ -0,0 +1,81 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + + +// Test subclassing %Intl.NumberFormat% works correctly. +class MyNumberFormat extends Intl.NumberFormat {} + +var obj = new MyNumberFormat(); +assertEq(obj instanceof MyNumberFormat, true); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), MyNumberFormat.prototype); + +obj = Reflect.construct(MyNumberFormat, []); +assertEq(obj instanceof MyNumberFormat, true); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), MyNumberFormat.prototype); + +obj = Reflect.construct(MyNumberFormat, [], MyNumberFormat); +assertEq(obj instanceof MyNumberFormat, true); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), MyNumberFormat.prototype); + +obj = Reflect.construct(MyNumberFormat, [], Intl.NumberFormat); +assertEq(obj instanceof MyNumberFormat, false); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype); + + +// Set a different constructor as NewTarget. +obj = Reflect.construct(MyNumberFormat, [], Array); +assertEq(obj instanceof MyNumberFormat, false); +assertEq(obj instanceof Intl.NumberFormat, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + +obj = Reflect.construct(Intl.NumberFormat, [], Array); +assertEq(obj instanceof Intl.NumberFormat, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + + +// The prototype defaults to %NumberFormatPrototype% if null. +function NewTargetNullPrototype() {} +NewTargetNullPrototype.prototype = null; + +obj = Reflect.construct(Intl.NumberFormat, [], NewTargetNullPrototype); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype); + +obj = Reflect.construct(MyNumberFormat, [], NewTargetNullPrototype); +assertEq(obj instanceof MyNumberFormat, false); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype); + + +// "prototype" property is retrieved exactly once. +var trapLog = [], getLog = []; +var ProxiedConstructor = new Proxy(Intl.NumberFormat, new Proxy({ + get(target, propertyKey, receiver) { + getLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +}, { + get(target, propertyKey, receiver) { + trapLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +})); + +obj = Reflect.construct(Intl.NumberFormat, [], ProxiedConstructor); +assertEqArray(trapLog, ["get"]); +assertEqArray(getLog, ["prototype"]); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype); + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js b/js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js new file mode 100644 index 000000000..7d75d0c93 --- /dev/null +++ b/js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +const AsyncFunction = async function(){}.constructor; + + +// Test subclassing %AsyncFunction% works correctly. +class MyAsync extends AsyncFunction {} + +var fn = new MyAsync(); +assertEq(fn instanceof MyAsync, true); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), MyAsync.prototype); + +fn = Reflect.construct(MyAsync, []); +assertEq(fn instanceof MyAsync, true); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), MyAsync.prototype); + +fn = Reflect.construct(MyAsync, [], MyAsync); +assertEq(fn instanceof MyAsync, true); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), MyAsync.prototype); + +fn = Reflect.construct(MyAsync, [], AsyncFunction); +assertEq(fn instanceof MyAsync, false); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype); + + +// Set a different constructor as NewTarget. +fn = Reflect.construct(MyAsync, [], Array); +assertEq(fn instanceof MyAsync, false); +assertEq(fn instanceof AsyncFunction, false); +assertEq(Object.getPrototypeOf(fn), Array.prototype); + +fn = Reflect.construct(AsyncFunction, [], Array); +assertEq(fn instanceof AsyncFunction, false); +assertEq(Object.getPrototypeOf(fn), Array.prototype); + + +// The prototype defaults to %AsyncFunctionPrototype% if null. +function NewTargetNullPrototype() {} +NewTargetNullPrototype.prototype = null; + +fn = Reflect.construct(AsyncFunction, [], NewTargetNullPrototype); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype); + +fn = Reflect.construct(MyAsync, [], NewTargetNullPrototype); +assertEq(fn instanceof MyAsync, false); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype); + + +// "prototype" property is retrieved exactly once. +var trapLog = [], getLog = []; +var ProxiedConstructor = new Proxy(AsyncFunction, new Proxy({ + get(target, propertyKey, receiver) { + getLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +}, { + get(target, propertyKey, receiver) { + trapLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +})); + +fn = Reflect.construct(AsyncFunction, [], ProxiedConstructor); +assertEqArray(trapLog, ["get"]); +assertEqArray(getLog, ["prototype"]); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype); + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_2017/AsyncFunctions/subclass.js b/js/src/tests/ecma_2017/AsyncFunctions/subclass.js new file mode 100644 index 000000000..da20ab19b --- /dev/null +++ b/js/src/tests/ecma_2017/AsyncFunctions/subclass.js @@ -0,0 +1,31 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +const AsyncFunction = async function(){}.constructor; + +class MyAsync extends AsyncFunction {} + +// MyGen inherits from %AsyncFunction%. +assertEq(Object.getPrototypeOf(MyAsync), AsyncFunction); + +// MyGen.prototype inherits from %AsyncFunctionPrototype%. +assertEq(Object.getPrototypeOf(MyAsync.prototype), AsyncFunction.prototype); + +var fn = new MyAsync("return await 'ok';"); + +// fn inherits from MyAsync.prototype. +assertEq(Object.getPrototypeOf(fn), MyAsync.prototype); + +// Ensure the new async function can be executed. +var promise = fn(); + +// promise inherits from %Promise.prototype%. +assertEq(Object.getPrototypeOf(promise), Promise.prototype); + +// Computes the expected result. +assertEventuallyEq(promise, "ok"); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Generators/construct-newtarget.js b/js/src/tests/ecma_6/Generators/construct-newtarget.js new file mode 100644 index 000000000..f2087852b --- /dev/null +++ b/js/src/tests/ecma_6/Generators/construct-newtarget.js @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +const GeneratorFunction = function*(){}.constructor; + + +// Test subclassing %GeneratorFunction% works correctly. +class MyGenerator extends GeneratorFunction {} + +var fn = new MyGenerator(); +assertEq(fn instanceof MyGenerator, true); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), MyGenerator.prototype); + +fn = Reflect.construct(MyGenerator, []); +assertEq(fn instanceof MyGenerator, true); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), MyGenerator.prototype); + +fn = Reflect.construct(MyGenerator, [], MyGenerator); +assertEq(fn instanceof MyGenerator, true); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), MyGenerator.prototype); + +fn = Reflect.construct(MyGenerator, [], GeneratorFunction); +assertEq(fn instanceof MyGenerator, false); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), GeneratorFunction.prototype); + + +// Set a different constructor as NewTarget. +fn = Reflect.construct(MyGenerator, [], Array); +assertEq(fn instanceof MyGenerator, false); +assertEq(fn instanceof GeneratorFunction, false); +assertEq(Object.getPrototypeOf(fn), Array.prototype); + +fn = Reflect.construct(GeneratorFunction, [], Array); +assertEq(fn instanceof GeneratorFunction, false); +assertEq(Object.getPrototypeOf(fn), Array.prototype); + + +// The prototype defaults to %GeneratorFunctionPrototype% if null. +function NewTargetNullPrototype() {} +NewTargetNullPrototype.prototype = null; + +fn = Reflect.construct(GeneratorFunction, [], NewTargetNullPrototype); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), GeneratorFunction.prototype); + +fn = Reflect.construct(MyGenerator, [], NewTargetNullPrototype); +assertEq(fn instanceof MyGenerator, false); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), GeneratorFunction.prototype); + + +// "prototype" property is retrieved exactly once. +var trapLog = [], getLog = []; +var ProxiedConstructor = new Proxy(GeneratorFunction, new Proxy({ + get(target, propertyKey, receiver) { + getLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +}, { + get(target, propertyKey, receiver) { + trapLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +})); + +fn = Reflect.construct(GeneratorFunction, [], ProxiedConstructor); +assertEqArray(trapLog, ["get"]); +assertEqArray(getLog, ["prototype"]); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), GeneratorFunction.prototype); + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Generators/subclass.js b/js/src/tests/ecma_6/Generators/subclass.js new file mode 100644 index 000000000..f93f4df4d --- /dev/null +++ b/js/src/tests/ecma_6/Generators/subclass.js @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +const GeneratorFunction = function*(){}.constructor; + +class MyGen extends GeneratorFunction {} + +// MyGen inherits from %GeneratorFunction%. +assertEq(Object.getPrototypeOf(MyGen), GeneratorFunction); + +// MyGen.prototype inherits from %Generator%. +assertEq(Object.getPrototypeOf(MyGen.prototype), GeneratorFunction.prototype); + +var fn = new MyGen("yield* [1, 2, 3]"); + +// fn inherits from MyGen.prototype. +assertEq(Object.getPrototypeOf(fn), MyGen.prototype); + +// fn.prototype inherits from %GeneratorPrototype%. +assertEq(Object.getPrototypeOf(fn.prototype), GeneratorFunction.prototype.prototype); + +// Ensure the new generator function can be executed. +var it = fn(); + +// it inherits from fn.prototype. +assertEq(Object.getPrototypeOf(it), fn.prototype); + +// Computes the expected result. +assertEqArray([...it], [1, 2, 3]); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/vm/AsyncFunction.cpp b/js/src/vm/AsyncFunction.cpp index bd0b4f32a..1e0c7d7c2 100644 --- a/js/src/vm/AsyncFunction.cpp +++ b/js/src/vm/AsyncFunction.cpp @@ -107,18 +107,15 @@ WrappedAsyncFunction(JSContext* cx, unsigned argc, Value* vp) // the async function's body, replacing `await` with `yield`. `wrapped` is a // function that is visible to the outside, and handles yielded values. JSObject* -js::WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped) +js::WrapAsyncFunctionWithProto(JSContext* cx, HandleFunction unwrapped, HandleObject proto) { MOZ_ASSERT(unwrapped->isStarGenerator()); + MOZ_ASSERT(proto, "We need an explicit prototype to avoid the default" + "%FunctionPrototype% fallback in NewFunctionWithProto()."); // Create a new function with AsyncFunctionPrototype, reusing the name and // the length of `unwrapped`. - // Step 1. - RootedObject proto(cx, GlobalObject::getOrCreateAsyncFunctionPrototype(cx, cx->global())); - if (!proto) - return nullptr; - RootedAtom funName(cx, unwrapped->name()); uint16_t length; if (!unwrapped->getLength(cx, &length)) @@ -141,6 +138,16 @@ js::WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped) return wrapped; } +JSObject* +js::WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped) +{ + RootedObject proto(cx, GlobalObject::getOrCreateAsyncFunctionPrototype(cx, cx->global())); + if (!proto) + return nullptr; + + return WrapAsyncFunctionWithProto(cx, unwrapped, proto); +} + enum class ResumeKind { Normal, Throw diff --git a/js/src/vm/AsyncFunction.h b/js/src/vm/AsyncFunction.h index ddf81a177..d7f2c1311 100644 --- a/js/src/vm/AsyncFunction.h +++ b/js/src/vm/AsyncFunction.h @@ -21,6 +21,9 @@ GetUnwrappedAsyncFunction(JSFunction* wrapped); bool IsWrappedAsyncFunction(JSFunction* fun); +JSObject* +WrapAsyncFunctionWithProto(JSContext* cx, HandleFunction unwrapped, HandleObject proto); + JSObject* WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped); -- cgit v1.2.3