diff --git a/js/src/builtin/Number.js b/js/src/builtin/Number.js index ef1b37b08e4f..dc81820389ec 100644 --- a/js/src/builtin/Number.js +++ b/js/src/builtin/Number.js @@ -13,8 +13,8 @@ var numberFormatCache = new Record(); * Spec: ECMAScript Internationalization API Specification, 13.2.1. */ function Number_toLocaleString() { - // Steps 1-2. Note that valueOf enforces "this Number value" restrictions. - var x = callFunction(std_Number_valueOf, this); + // Steps 1-2. + var x = callFunction(ThisNumberValueForToLocaleString, this); // Steps 2-3. var locales = arguments.length > 0 ? arguments[0] : undefined; diff --git a/js/src/jit-test/tests/basic/number-methods-this-error.js b/js/src/jit-test/tests/basic/number-methods-this-error.js new file mode 100644 index 000000000000..6d5a710bb141 --- /dev/null +++ b/js/src/jit-test/tests/basic/number-methods-this-error.js @@ -0,0 +1,12 @@ +load(libdir + "asserts.js"); + +let methods = Object.getOwnPropertyNames(Number.prototype) + .filter(n => n != "constructor"); + +for (let method of methods) { + assertTypeErrorMessage(() => Number.prototype[method].call(new Map), + `Number.prototype.${method} called on incompatible Map`); + + assertTypeErrorMessage(() => Number.prototype[method].call(false), + `Number.prototype.${method} called on incompatible boolean`); +} diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index acfc2c188c48..425e7a482a98 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -41,6 +41,7 @@ #include "vm/JSContext.h" #include "vm/JSObject.h" +#include "vm/Compartment-inl.h" // For js::UnwrapAndTypeCheckThis #include "vm/NativeObject-inl.h" #include "vm/NumberObject-inl.h" #include "vm/StringType-inl.h" @@ -670,19 +671,51 @@ static bool Number(JSContext* cx, unsigned argc, Value* vp) { return true; } -MOZ_ALWAYS_INLINE bool IsNumber(HandleValue v) { - return v.isNumber() || (v.isObject() && v.toObject().is()); -} +// ES2020 draft rev e08b018785606bc6465a0456a79604b149007932 +// 20.1.3 Properties of the Number Prototype Object, thisNumberValue. +MOZ_ALWAYS_INLINE +static bool ThisNumberValue(JSContext* cx, const CallArgs& args, + const char* methodName, double* number) { + HandleValue thisv = args.thisv(); -static inline double Extract(const Value& v) { - if (v.isNumber()) { - return v.toNumber(); + // Step 1. + if (thisv.isNumber()) { + *number = thisv.toNumber(); + return true; } - return v.toObject().as().unbox(); + + // Steps 2-3. + auto* obj = UnwrapAndTypeCheckThis(cx, args, methodName); + if (!obj) { + return false; + } + + *number = obj->unbox(); + return true; } -MOZ_ALWAYS_INLINE bool num_toSource_impl(JSContext* cx, const CallArgs& args) { - double d = Extract(args.thisv()); +// On-off helper function for the self-hosted Number_toLocaleString method. +// This only exists to produce an error message with the right method name. +bool js::ThisNumberValueForToLocaleString(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + double d; + if (!ThisNumberValue(cx, args, "toLocaleString", &d)) { + return false; + } + + args.rval().setNumber(d); + return true; +} + +static bool num_toSource(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + double d; + if (!ThisNumberValue(cx, args, "toSource", &d)) { + return false; + } JSStringBuilder sb(cx); if (!sb.append("(new Number(") || @@ -698,11 +731,6 @@ MOZ_ALWAYS_INLINE bool num_toSource_impl(JSContext* cx, const CallArgs& args) { return true; } -static bool num_toSource(JSContext* cx, unsigned argc, Value* vp) { - CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, args); -} - ToCStringBuf::ToCStringBuf() : dbuf(nullptr) { static_assert(sbufSize >= DTOSTR_STANDARD_BUFFER_SIZE, "builtin space must be large enough to store even the " @@ -859,10 +887,13 @@ static char* Int32ToCString(ToCStringBuf* cbuf, int32_t i, size_t* len, template static JSString* NumberToStringWithBase(JSContext* cx, double d, int base); -MOZ_ALWAYS_INLINE bool num_toString_impl(JSContext* cx, const CallArgs& args) { - MOZ_ASSERT(IsNumber(args.thisv())); +static bool num_toString(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); - double d = Extract(args.thisv()); + double d; + if (!ThisNumberValue(cx, args, "toString", &d)) { + return false; + } int32_t base = 10; if (args.hasDefined(0)) { @@ -887,17 +918,14 @@ MOZ_ALWAYS_INLINE bool num_toString_impl(JSContext* cx, const CallArgs& args) { return true; } -bool js::num_toString(JSContext* cx, unsigned argc, Value* vp) { - CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, args); -} - #if !JS_HAS_INTL_API -MOZ_ALWAYS_INLINE bool num_toLocaleString_impl(JSContext* cx, - const CallArgs& args) { - MOZ_ASSERT(IsNumber(args.thisv())); +static bool num_toLocaleString(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); - double d = Extract(args.thisv()); + double d; + if (!ThisNumberValue(cx, args, "toLocaleString", &d)) { + return false; + } RootedString str(cx, NumberToStringWithBase(cx, d, 10)); if (!str) { @@ -1029,22 +1057,18 @@ MOZ_ALWAYS_INLINE bool num_toLocaleString_impl(JSContext* cx, args.rval().setString(str); return true; } - -static bool num_toLocaleString(JSContext* cx, unsigned argc, Value* vp) { - CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, args); -} #endif /* !JS_HAS_INTL_API */ -MOZ_ALWAYS_INLINE bool num_valueOf_impl(JSContext* cx, const CallArgs& args) { - MOZ_ASSERT(IsNumber(args.thisv())); - args.rval().setNumber(Extract(args.thisv())); - return true; -} - bool js::num_valueOf(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, args); + + double d; + if (!ThisNumberValue(cx, args, "valueOf", &d)) { + return false; + } + + args.rval().setNumber(d); + return true; } static const unsigned MAX_PRECISION = 100; @@ -1090,10 +1114,14 @@ static bool DToStrResult(JSContext* cx, double d, JSDToStrMode mode, * than ECMA requires; this is permitted by ECMA-262. */ // ES 2017 draft rev f8a9be8ea4bd97237d176907a1e3080dce20c68f 20.1.3.3. -MOZ_ALWAYS_INLINE bool num_toFixed_impl(JSContext* cx, const CallArgs& args) { +static bool num_toFixed(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + // Step 1. - MOZ_ASSERT(IsNumber(args.thisv())); - double d = Extract(args.thisv()); + double d; + if (!ThisNumberValue(cx, args, "toFixed", &d)) { + return false; + } // Steps 2-3. int precision; @@ -1128,20 +1156,18 @@ MOZ_ALWAYS_INLINE bool num_toFixed_impl(JSContext* cx, const CallArgs& args) { } // Steps 5-9. - return DToStrResult(cx, Extract(args.thisv()), DTOSTR_FIXED, precision, args); -} - -static bool num_toFixed(JSContext* cx, unsigned argc, Value* vp) { - CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, args); + return DToStrResult(cx, d, DTOSTR_FIXED, precision, args); } // ES 2017 draft rev f8a9be8ea4bd97237d176907a1e3080dce20c68f 20.1.3.2. -MOZ_ALWAYS_INLINE bool num_toExponential_impl(JSContext* cx, - const CallArgs& args) { +static bool num_toExponential(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + // Step 1. - MOZ_ASSERT(IsNumber(args.thisv())); - double d = Extract(args.thisv()); + double d; + if (!ThisNumberValue(cx, args, "toExponential", &d)) { + return false; + } // Step 2. double prec = 0; @@ -1184,17 +1210,15 @@ MOZ_ALWAYS_INLINE bool num_toExponential_impl(JSContext* cx, return DToStrResult(cx, d, mode, precision + 1, args); } -static bool num_toExponential(JSContext* cx, unsigned argc, Value* vp) { - CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, args); -} - // ES 2017 draft rev f8a9be8ea4bd97237d176907a1e3080dce20c68f 20.1.3.5. -MOZ_ALWAYS_INLINE bool num_toPrecision_impl(JSContext* cx, - const CallArgs& args) { +static bool num_toPrecision(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + // Step 1. - MOZ_ASSERT(IsNumber(args.thisv())); - double d = Extract(args.thisv()); + double d; + if (!ThisNumberValue(cx, args, "toPrecision", &d)) { + return false; + } // Step 2. if (!args.hasDefined(0)) { @@ -1239,11 +1263,6 @@ MOZ_ALWAYS_INLINE bool num_toPrecision_impl(JSContext* cx, return DToStrResult(cx, d, DTOSTR_PRECISION, precision, args); } -static bool num_toPrecision(JSContext* cx, unsigned argc, Value* vp) { - CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, args); -} - static const JSFunctionSpec number_methods[] = { JS_FN(js_toSource_str, num_toSource, 0, 0), JS_FN(js_toString_str, num_toString, 1, 0), diff --git a/js/src/jsnum.h b/js/src/jsnum.h index 27ad712178eb..223985c80ab2 100644 --- a/js/src/jsnum.h +++ b/js/src/jsnum.h @@ -263,7 +263,9 @@ extern MOZ_MUST_USE bool FullStringToDouble(JSContext* cx, const CharT* begin, return false; } -extern MOZ_MUST_USE bool num_toString(JSContext* cx, unsigned argc, Value* vp); +extern MOZ_MUST_USE bool ThisNumberValueForToLocaleString(JSContext* cx, + unsigned argc, + Value* vp); extern MOZ_MUST_USE bool num_valueOf(JSContext* cx, unsigned argc, Value* vp); diff --git a/js/src/vm/Compartment-inl.h b/js/src/vm/Compartment-inl.h index a60db87a380b..3d2a37205bef 100644 --- a/js/src/vm/Compartment-inl.h +++ b/js/src/vm/Compartment-inl.h @@ -185,7 +185,8 @@ inline MOZ_MUST_USE T* UnwrapAndTypeCheckValue(JSContext* cx, HandleValue value, * or isn't an instance of the expected type. */ template -inline MOZ_MUST_USE T* UnwrapAndTypeCheckThis(JSContext* cx, CallArgs& args, +inline MOZ_MUST_USE T* UnwrapAndTypeCheckThis(JSContext* cx, + const CallArgs& args, const char* methodName) { HandleValue thisv = args.thisv(); return UnwrapAndTypeCheckValue(cx, thisv, [cx, methodName, thisv] { diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index fb85b482fc04..e2f40dd374c4 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -2143,8 +2143,6 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("std_Map_entries", MapObject::entries, 0, 0), - JS_FN("std_Number_valueOf", num_valueOf, 0, 0), - JS_INLINABLE_FN("std_Object_create", obj_create, 2, 0, ObjectCreate), JS_FN("std_Object_propertyIsEnumerable", obj_propertyIsEnumerable, 1, 0), JS_FN("std_Object_toString", obj_toString, 0, 0), @@ -2232,6 +2230,9 @@ static const JSFunctionSpec intrinsic_functions[] = { intrinsic_UnsafeGetBooleanFromReservedSlot, 2, 0, IntrinsicUnsafeGetBooleanFromReservedSlot), + JS_FN("ThisNumberValueForToLocaleString", ThisNumberValueForToLocaleString, + 0, 0), + JS_INLINABLE_FN("IsPackedArray", intrinsic_IsPackedArray, 1, 0, IntrinsicIsPackedArray),