/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * 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/. */ /* * JS number type and wrapper class. */ #include "jsnum.h" #include "mozilla/Casting.h" #include "mozilla/FloatingPoint.h" #include "mozilla/Maybe.h" #include "mozilla/RangedPtr.h" #include "mozilla/TextUtils.h" #include "mozilla/Utf8.h" #include #include #include #ifdef HAVE_LOCALECONV # include #endif #include #include // memmove #include #include "jstypes.h" #include "builtin/String.h" #include "double-conversion/double-conversion.h" #include "frontend/ParserAtom.h" // frontend::{ParserAtomsTable, TaggedParserAtomIndex} #include "jit/InlinableNatives.h" #include "js/CharacterEncoding.h" #include "js/Conversions.h" #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/GCAPI.h" #if !JS_HAS_INTL_API # include "js/LocaleSensitive.h" #endif #include "js/PropertyAndElement.h" // JS_DefineFunctions #include "js/PropertySpec.h" #include "util/DoubleToString.h" #include "util/Memory.h" #include "util/StringBuffer.h" #include "vm/BigIntType.h" #include "vm/GlobalObject.h" #include "vm/JSAtomUtils.h" // Atomize, AtomizeString #include "vm/JSContext.h" #include "vm/JSObject.h" #include "vm/StaticStrings.h" #include "vm/Compartment-inl.h" // For js::UnwrapAndTypeCheckThis #include "vm/GeckoProfiler-inl.h" #include "vm/JSAtomUtils-inl.h" // BackfillIndexInCharBuffer #include "vm/NativeObject-inl.h" #include "vm/NumberObject-inl.h" #include "vm/StringType-inl.h" using namespace js; using mozilla::Abs; using mozilla::AsciiAlphanumericToNumber; using mozilla::IsAsciiAlphanumeric; using mozilla::IsAsciiDigit; using mozilla::Maybe; using mozilla::MinNumberValue; using mozilla::NegativeInfinity; using mozilla::NumberEqualsInt32; using mozilla::PositiveInfinity; using mozilla::RangedPtr; using mozilla::Utf8AsUnsignedChars; using mozilla::Utf8Unit; using JS::AutoCheckCannotGC; using JS::GenericNaN; using JS::ToInt16; using JS::ToInt32; using JS::ToInt64; using JS::ToInt8; using JS::ToUint16; using JS::ToUint32; using JS::ToUint64; using JS::ToUint8; static bool EnsureDtoaState(JSContext* cx) { if (!cx->dtoaState) { cx->dtoaState = NewDtoaState(); if (!cx->dtoaState) { return false; } } return true; } template static inline void AssertWellPlacedNumericSeparator(const CharT* s, const CharT* start, const CharT* end) { MOZ_ASSERT(start < end, "string is non-empty"); MOZ_ASSERT(s > start, "number can't start with a separator"); MOZ_ASSERT(s + 1 < end, "final character in a numeric literal can't be a separator"); MOZ_ASSERT(*(s + 1) != '_', "separator can't be followed by another separator"); MOZ_ASSERT(*(s - 1) != '_', "separator can't be preceded by another separator"); } namespace { template class BinaryDigitReader { const int base; /* Base of number; must be a power of 2 */ int digit; /* Current digit value in radix given by base */ int digitMask; /* Mask to extract the next bit from digit */ const CharT* cur; /* Pointer to the remaining digits */ const CharT* start; /* Pointer to the start of the string */ const CharT* end; /* Pointer to first non-digit */ public: BinaryDigitReader(int base, const CharT* start, const CharT* end) : base(base), digit(0), digitMask(0), cur(start), start(start), end(end) {} /* Return the next binary digit from the number, or -1 if done. */ int nextDigit() { if (digitMask == 0) { if (cur == end) { return -1; } int c = *cur++; if (c == '_') { AssertWellPlacedNumericSeparator(cur - 1, start, end); c = *cur++; } MOZ_ASSERT(IsAsciiAlphanumeric(c)); digit = AsciiAlphanumericToNumber(c); digitMask = base >> 1; } int bit = (digit & digitMask) != 0; digitMask >>= 1; return bit; } }; } /* anonymous namespace */ /* * The fast result might also have been inaccurate for power-of-two bases. This * happens if the addition in value * 2 + digit causes a round-down to an even * least significant mantissa bit when the first dropped bit is a one. If any * of the following digits in the number (which haven't been added in yet) are * nonzero, then the correct action would have been to round up instead of * down. An example occurs when reading the number 0x1000000000000081, which * rounds to 0x1000000000000000 instead of 0x1000000000000100. */ template static double ComputeAccurateBinaryBaseInteger(const CharT* start, const CharT* end, int base) { BinaryDigitReader bdr(base, start, end); /* Skip leading zeroes. */ int bit; do { bit = bdr.nextDigit(); } while (bit == 0); MOZ_ASSERT(bit == 1); // guaranteed by Get{Prefix,Decimal}Integer /* Gather the 53 significant bits (including the leading 1). */ double value = 1.0; for (int j = 52; j > 0; j--) { bit = bdr.nextDigit(); if (bit < 0) { return value; } value = value * 2 + bit; } /* bit2 is the 54th bit (the first dropped from the mantissa). */ int bit2 = bdr.nextDigit(); if (bit2 >= 0) { double factor = 2.0; int sticky = 0; /* sticky is 1 if any bit beyond the 54th is 1 */ int bit3; while ((bit3 = bdr.nextDigit()) >= 0) { sticky |= bit3; factor *= 2; } value += bit2 & (bit | sticky); value *= factor; } return value; } template double js::ParseDecimalNumber(const mozilla::Range chars) { MOZ_ASSERT(chars.length() > 0); uint64_t dec = 0; RangedPtr s = chars.begin(), end = chars.end(); do { CharT c = *s; MOZ_ASSERT('0' <= c && c <= '9'); uint8_t digit = c - '0'; uint64_t next = dec * 10 + digit; MOZ_ASSERT(next < DOUBLE_INTEGRAL_PRECISION_LIMIT, "next value won't be an integrally-precise double"); dec = next; } while (++s < end); return static_cast(dec); } template double js::ParseDecimalNumber( const mozilla::Range chars); template double js::ParseDecimalNumber( const mozilla::Range chars); template static bool GetPrefixIntegerImpl(const CharT* start, const CharT* end, int base, IntegerSeparatorHandling separatorHandling, const CharT** endp, double* dp) { MOZ_ASSERT(start <= end); MOZ_ASSERT(2 <= base && base <= 36); const CharT* s = start; double d = 0.0; for (; s < end; s++) { CharT c = *s; if (!IsAsciiAlphanumeric(c)) { if (c == '_' && separatorHandling == IntegerSeparatorHandling::SkipUnderscore) { AssertWellPlacedNumericSeparator(s, start, end); continue; } break; } uint8_t digit = AsciiAlphanumericToNumber(c); if (digit >= base) { break; } d = d * base + digit; } *endp = s; *dp = d; /* If we haven't reached the limit of integer precision, we're done. */ if (d < DOUBLE_INTEGRAL_PRECISION_LIMIT) { return true; } /* * Otherwise compute the correct integer from the prefix of valid digits * if we're computing for base ten or a power of two. Don't worry about * other bases; see ES2018, 18.2.5 `parseInt(string, radix)`, step 13. */ if (base == 10) { return false; } if ((base & (base - 1)) == 0) { *dp = ComputeAccurateBinaryBaseInteger(start, s, base); } return true; } template bool js::GetPrefixInteger(const CharT* start, const CharT* end, int base, IntegerSeparatorHandling separatorHandling, const CharT** endp, double* dp) { if (GetPrefixIntegerImpl(start, end, base, separatorHandling, endp, dp)) { return true; } // Can only fail for base 10. MOZ_ASSERT(base == 10); // If we're accumulating a decimal number and the number is >= 2^53, then the // fast result from the loop in GetPrefixIntegerImpl may be inaccurate. Call // GetDecimal to get the correct answer. return GetDecimal(start, *endp, dp); } namespace js { template bool GetPrefixInteger(const char16_t* start, const char16_t* end, int base, IntegerSeparatorHandling separatorHandling, const char16_t** endp, double* dp); template bool GetPrefixInteger(const Latin1Char* start, const Latin1Char* end, int base, IntegerSeparatorHandling separatorHandling, const Latin1Char** endp, double* dp); } // namespace js template bool js::GetDecimalInteger(const CharT* start, const CharT* end, double* dp) { MOZ_ASSERT(start <= end); double d = 0.0; for (const CharT* s = start; s < end; s++) { CharT c = *s; if (c == '_') { AssertWellPlacedNumericSeparator(s, start, end); continue; } MOZ_ASSERT(IsAsciiDigit(c)); int digit = c - '0'; d = d * 10 + digit; } // If we haven't reached the limit of integer precision, we're done. if (d < DOUBLE_INTEGRAL_PRECISION_LIMIT) { *dp = d; return true; } // Otherwise compute the correct integer using GetDecimal. return GetDecimal(start, end, dp); } namespace js { template bool GetDecimalInteger(const char16_t* start, const char16_t* end, double* dp); template bool GetDecimalInteger(const Latin1Char* start, const Latin1Char* end, double* dp); template <> bool GetDecimalInteger(const Utf8Unit* start, const Utf8Unit* end, double* dp) { return GetDecimalInteger(Utf8AsUnsignedChars(start), Utf8AsUnsignedChars(end), dp); } } // namespace js template bool js::GetDecimal(const CharT* start, const CharT* end, double* dp) { MOZ_ASSERT(start <= end); size_t length = end - start; auto convert = [](auto* chars, size_t length) -> double { using SToDConverter = double_conversion::StringToDoubleConverter; SToDConverter converter(/* flags = */ 0, /* empty_string_value = */ 0.0, /* junk_string_value = */ 0.0, /* infinity_symbol = */ nullptr, /* nan_symbol = */ nullptr); int lengthInt = mozilla::AssertedCast(length); int processed = 0; double d = converter.StringToDouble(chars, lengthInt, &processed); MOZ_ASSERT(processed >= 0); MOZ_ASSERT(size_t(processed) == length); return d; }; // If there are no underscores, we don't need to copy the chars. bool hasUnderscore = std::any_of(start, end, [](auto c) { return c == '_'; }); if (!hasUnderscore) { if constexpr (std::is_same_v) { *dp = convert(reinterpret_cast(start), length); } else { static_assert(std::is_same_v); *dp = convert(reinterpret_cast(start), length); } return true; } Vector chars; if (!chars.growByUninitialized(length)) { return false; } const CharT* s = start; size_t i = 0; for (; s < end; s++) { CharT c = *s; if (c == '_') { AssertWellPlacedNumericSeparator(s, start, end); continue; } MOZ_ASSERT(IsAsciiDigit(c) || c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-'); chars[i++] = char(c); } *dp = convert(chars.begin(), i); return true; } namespace js { template bool GetDecimal(const char16_t* start, const char16_t* end, double* dp); template bool GetDecimal(const Latin1Char* start, const Latin1Char* end, double* dp); template <> bool GetDecimal(const Utf8Unit* start, const Utf8Unit* end, double* dp) { return GetDecimal(Utf8AsUnsignedChars(start), Utf8AsUnsignedChars(end), dp); } } // namespace js static bool num_parseFloat(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() == 0) { args.rval().setNaN(); return true; } if (args[0].isNumber()) { // ToString(-0) is "0", handle it accordingly. if (args[0].isDouble() && args[0].toDouble() == 0.0) { args.rval().setInt32(0); } else { args.rval().set(args[0]); } return true; } JSString* str = ToString(cx, args[0]); if (!str) { return false; } if (str->hasIndexValue()) { args.rval().setNumber(str->getIndexValue()); return true; } JSLinearString* linear = str->ensureLinear(cx); if (!linear) { return false; } double d; AutoCheckCannotGC nogc; if (linear->hasLatin1Chars()) { const Latin1Char* begin = linear->latin1Chars(nogc); const Latin1Char* end; d = js_strtod(begin, begin + linear->length(), &end); if (end == begin) { d = GenericNaN(); } } else { const char16_t* begin = linear->twoByteChars(nogc); const char16_t* end; d = js_strtod(begin, begin + linear->length(), &end); if (end == begin) { d = GenericNaN(); } } args.rval().setDouble(d); return true; } // ES2023 draft rev 053d34c87b14d9234d6f7f45bd61074b72ca9d69 // 19.2.5 parseInt ( string, radix ) template static bool ParseIntImpl(JSContext* cx, const CharT* chars, size_t length, bool stripPrefix, int32_t radix, double* res) { // Step 2. const CharT* end = chars + length; const CharT* s = SkipSpace(chars, end); MOZ_ASSERT(chars <= s); MOZ_ASSERT(s <= end); // Steps 3-4. bool negative = (s != end && s[0] == '-'); // Step 5. */ if (s != end && (s[0] == '-' || s[0] == '+')) { s++; } // Step 10. if (stripPrefix) { if (end - s >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { s += 2; radix = 16; } } // Steps 11-15. const CharT* actualEnd; double d; if (!js::GetPrefixInteger(s, end, radix, IntegerSeparatorHandling::None, &actualEnd, &d)) { ReportOutOfMemory(cx); return false; } if (s == actualEnd) { *res = GenericNaN(); } else { *res = negative ? -d : d; } return true; } // ES2023 draft rev 053d34c87b14d9234d6f7f45bd61074b72ca9d69 // 19.2.5 parseInt ( string, radix ) bool js::NumberParseInt(JSContext* cx, HandleString str, int32_t radix, MutableHandleValue result) { // Step 7. bool stripPrefix = true; // Steps 8-9. if (radix != 0) { if (radix < 2 || radix > 36) { result.setNaN(); return true; } if (radix != 16) { stripPrefix = false; } } else { radix = 10; } MOZ_ASSERT(2 <= radix && radix <= 36); JSLinearString* linear = str->ensureLinear(cx); if (!linear) { return false; } // Steps 2-5, 10-16. AutoCheckCannotGC nogc; size_t length = linear->length(); double number; if (linear->hasLatin1Chars()) { if (!ParseIntImpl(cx, linear->latin1Chars(nogc), length, stripPrefix, radix, &number)) { return false; } } else { if (!ParseIntImpl(cx, linear->twoByteChars(nogc), length, stripPrefix, radix, &number)) { return false; } } result.setNumber(number); return true; } // ES2023 draft rev 053d34c87b14d9234d6f7f45bd61074b72ca9d69 // 19.2.5 parseInt ( string, radix ) static bool num_parseInt(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); /* Fast paths and exceptional cases. */ if (args.length() == 0) { args.rval().setNaN(); return true; } if (args.length() == 1 || (args[1].isInt32() && (args[1].toInt32() == 0 || args[1].toInt32() == 10))) { if (args[0].isInt32()) { args.rval().set(args[0]); return true; } /* * Step 1 is |inputString = ToString(string)|. When string >= * 1e21, ToString(string) is in the form "NeM". 'e' marks the end of * the word, which would mean the result of parseInt(string) should be |N|. * * To preserve this behaviour, we can't use the fast-path when string >= * 1e21, or else the result would be |NeM|. * * The same goes for values smaller than 1.0e-6, because the string would be * in the form of "Ne-M". */ if (args[0].isDouble()) { double d = args[0].toDouble(); if (DOUBLE_DECIMAL_IN_SHORTEST_LOW <= d && d < DOUBLE_DECIMAL_IN_SHORTEST_HIGH) { args.rval().setNumber(floor(d)); return true; } if (-DOUBLE_DECIMAL_IN_SHORTEST_HIGH < d && d <= -DOUBLE_DECIMAL_IN_SHORTEST_LOW) { args.rval().setNumber(-floor(-d)); return true; } if (d == 0.0) { args.rval().setInt32(0); return true; } } if (args[0].isString()) { JSString* str = args[0].toString(); if (str->hasIndexValue()) { args.rval().setNumber(str->getIndexValue()); return true; } } } // Step 1. RootedString inputString(cx, ToString(cx, args[0])); if (!inputString) { return false; } // Step 6. int32_t radix = 0; if (args.hasDefined(1)) { if (!ToInt32(cx, args[1], &radix)) { return false; } } // Steps 2-5, 7-16. return NumberParseInt(cx, inputString, radix, args.rval()); } static const JSFunctionSpec number_functions[] = { JS_SELF_HOSTED_FN("isNaN", "Global_isNaN", 1, JSPROP_RESOLVING), JS_SELF_HOSTED_FN("isFinite", "Global_isFinite", 1, JSPROP_RESOLVING), JS_FS_END}; const JSClass NumberObject::class_ = { "Number", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_HAS_CACHED_PROTO(JSProto_Number), JS_NULL_CLASS_OPS, &NumberObject::classSpec_}; static bool Number(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() > 0) { // BigInt proposal section 6.2, steps 2a-c. if (!ToNumeric(cx, args[0])) { return false; } if (args[0].isBigInt()) { args[0].setNumber(BigInt::numberValue(args[0].toBigInt())); } MOZ_ASSERT(args[0].isNumber()); } if (!args.isConstructing()) { if (args.length() > 0) { args.rval().set(args[0]); } else { args.rval().setInt32(0); } return true; } RootedObject proto(cx); if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Number, &proto)) { return false; } double d = args.length() > 0 ? args[0].toNumber() : 0; JSObject* obj = NumberObject::create(cx, d, proto); if (!obj) { return false; } args.rval().setObject(*obj); return true; } // 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(); // Step 1. if (thisv.isNumber()) { *number = thisv.toNumber(); return true; } // Steps 2-3. auto* obj = UnwrapAndTypeCheckThis(cx, args, methodName); if (!obj) { return false; } *number = obj->unbox(); return true; } // 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(") || !NumberValueToStringBuffer(NumberValue(d), sb) || !sb.append("))")) { return false; } JSString* str = sb.finishString(); if (!str) { return false; } args.rval().setString(str); return true; } // Subtract one from DTOSTR_STANDARD_BUFFER_SIZE to exclude the null-character. static_assert( double_conversion::DoubleToStringConverter::kMaxCharsEcmaScriptShortest == DTOSTR_STANDARD_BUFFER_SIZE - 1, "double_conversion and dtoa both agree how large the longest string " "can be"); static_assert(DTOSTR_STANDARD_BUFFER_SIZE <= JS::MaximumNumberToStringLength, "MaximumNumberToStringLength is large enough to hold the longest " "string produced by a conversion"); MOZ_ALWAYS_INLINE static JSLinearString* LookupDtoaCache(JSContext* cx, double d) { if (Realm* realm = cx->realm()) { if (JSLinearString* str = realm->dtoaCache.lookup(10, d)) { return str; } } return nullptr; } MOZ_ALWAYS_INLINE static void CacheNumber(JSContext* cx, double d, JSLinearString* str) { if (Realm* realm = cx->realm()) { realm->dtoaCache.cache(10, d, str); } } MOZ_ALWAYS_INLINE static JSLinearString* LookupInt32ToString(JSContext* cx, int32_t si) { if (si >= 0 && StaticStrings::hasInt(si)) { return cx->staticStrings().getInt(si); } return LookupDtoaCache(cx, si); } template MOZ_ALWAYS_INLINE static T* BackfillInt32InBuffer(int32_t si, T* buffer, size_t size, size_t* length) { uint32_t ui = Abs(si); MOZ_ASSERT_IF(si == INT32_MIN, ui == uint32_t(INT32_MAX) + 1); RangedPtr end(buffer + size - 1, buffer, size); *end = '\0'; RangedPtr start = BackfillIndexInCharBuffer(ui, end); if (si < 0) { *--start = '-'; } *length = end - start; return start.get(); } template JSLinearString* js::Int32ToString(JSContext* cx, int32_t si) { return js::Int32ToStringWithHeap(cx, si, gc::Heap::Default); } template JSLinearString* js::Int32ToString(JSContext* cx, int32_t si); template JSLinearString* js::Int32ToString(JSContext* cx, int32_t si); template JSLinearString* js::Int32ToStringWithHeap(JSContext* cx, int32_t si, gc::Heap heap) { if (JSLinearString* str = LookupInt32ToString(cx, si)) { return str; } Latin1Char buffer[JSFatInlineString::MAX_LENGTH_LATIN1 + 1]; size_t length; Latin1Char* start = BackfillInt32InBuffer(si, buffer, std::size(buffer), &length); mozilla::Range chars(start, length); JSInlineString* str = NewInlineString(cx, chars, heap); if (!str) { return nullptr; } if (si >= 0) { str->maybeInitializeIndexValue(si); } CacheNumber(cx, si, str); return str; } template JSLinearString* js::Int32ToStringWithHeap(JSContext* cx, int32_t si, gc::Heap heap); template JSLinearString* js::Int32ToStringWithHeap(JSContext* cx, int32_t si, gc::Heap heap); JSLinearString* js::Int32ToStringPure(JSContext* cx, int32_t si) { AutoUnsafeCallWithABI unsafe; return Int32ToString(cx, si); } JSAtom* js::Int32ToAtom(JSContext* cx, int32_t si) { if (JSLinearString* str = LookupInt32ToString(cx, si)) { return js::AtomizeString(cx, str); } char buffer[JSFatInlineString::MAX_LENGTH_TWO_BYTE + 1]; size_t length; char* start = BackfillInt32InBuffer( si, buffer, JSFatInlineString::MAX_LENGTH_TWO_BYTE + 1, &length); Maybe indexValue; if (si >= 0) { indexValue.emplace(si); } JSAtom* atom = Atomize(cx, start, length, indexValue); if (!atom) { return nullptr; } CacheNumber(cx, si, atom); return atom; } frontend::TaggedParserAtomIndex js::Int32ToParserAtom( FrontendContext* fc, frontend::ParserAtomsTable& parserAtoms, int32_t si) { char buffer[JSFatInlineString::MAX_LENGTH_TWO_BYTE + 1]; size_t length; char* start = BackfillInt32InBuffer( si, buffer, JSFatInlineString::MAX_LENGTH_TWO_BYTE + 1, &length); Maybe indexValue; if (si >= 0) { indexValue.emplace(si); } return parserAtoms.internAscii(fc, start, length); } /* Returns a non-nullptr pointer to inside `buf`. */ template static char* Int32ToCStringWithBase(mozilla::Range buf, T i, size_t* len, int base) { uint32_t u; if constexpr (std::is_signed_v) { u = Abs(i); } else { u = i; } RangedPtr cp = buf.end() - 1; char* end = cp.get(); *cp = '\0'; /* Build the string from behind. */ switch (base) { case 10: cp = BackfillIndexInCharBuffer(u, cp); break; case 16: do { unsigned newu = u / 16; *--cp = "0123456789abcdef"[u - newu * 16]; u = newu; } while (u != 0); break; default: MOZ_ASSERT(base >= 2 && base <= 36); do { unsigned newu = u / base; *--cp = "0123456789abcdefghijklmnopqrstuvwxyz"[u - newu * base]; u = newu; } while (u != 0); break; } if constexpr (std::is_signed_v) { if (i < 0) { *--cp = '-'; } } *len = end - cp.get(); return cp.get(); } /* Returns a non-nullptr pointer to inside `out`. */ template static char* Int32ToCStringWithBase(char (&out)[Length], T i, size_t* len, int base) { // The buffer needs to be large enough to hold the largest number, including // the sign and the terminating null-character. static_assert(std::numeric_limits::digits + (2 * std::is_signed_v) < Length); mozilla::Range buf(out, Length); return Int32ToCStringWithBase(buf, i, len, base); } /* Returns a non-nullptr pointer to inside `out`. */ template static char* Int32ToCString(char (&out)[Length], T i, size_t* len) { // The buffer needs to be large enough to hold the largest number, including // the sign and the terminating null-character. if constexpr (Base == 10) { static_assert(std::numeric_limits::digits10 + 1 + std::is_signed_v < Length); } else { // Compute digits16 analog to std::numeric_limits::digits10, which is // defined as |std::numeric_limits::digits * std::log10(2)| for integer // types. // Note: log16(2) is 1/4. static_assert(Base == 16); static_assert(((std::numeric_limits::digits + std::is_signed_v) / 4 + std::is_signed_v) < Length); } mozilla::Range buf(out, Length); return Int32ToCStringWithBase(buf, i, len, Base); } /* Returns a non-nullptr pointer to inside `cbuf`. */ template static char* Int32ToCString(ToCStringBuf* cbuf, T i, size_t* len) { return Int32ToCString(cbuf->sbuf, i, len); } /* Returns a non-nullptr pointer to inside `cbuf`. */ template static char* Int32ToCString(Int32ToCStringBuf* cbuf, T i, size_t* len) { return Int32ToCString(cbuf->sbuf, i, len); } template static JSString* NumberToStringWithBase(JSContext* cx, double d, int base); static bool num_toString(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); double d; if (!ThisNumberValue(cx, args, "toString", &d)) { return false; } int32_t base = 10; if (args.hasDefined(0)) { double d2; if (!ToInteger(cx, args[0], &d2)) { return false; } if (d2 < 2 || d2 > 36) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_RADIX); return false; } base = int32_t(d2); } JSString* str = NumberToStringWithBase(cx, d, base); if (!str) { return false; } args.rval().setString(str); return true; } #if !JS_HAS_INTL_API static bool num_toLocaleString(JSContext* cx, unsigned argc, Value* vp) { AutoJSMethodProfilerEntry pseudoFrame(cx, "Number.prototype", "toLocaleString"); CallArgs args = CallArgsFromVp(argc, vp); double d; if (!ThisNumberValue(cx, args, "toLocaleString", &d)) { return false; } RootedString str(cx, NumberToStringWithBase(cx, d, 10)); if (!str) { return false; } /* * Create the string, move back to bytes to make string twiddling * a bit easier and so we can insert platform charset seperators. */ UniqueChars numBytes = EncodeAscii(cx, str); if (!numBytes) { return false; } const char* num = numBytes.get(); if (!num) { return false; } /* * Find the first non-integer value, whether it be a letter as in * 'Infinity', a decimal point, or an 'e' from exponential notation. */ const char* nint = num; if (*nint == '-') { nint++; } while (*nint >= '0' && *nint <= '9') { nint++; } int digits = nint - num; const char* end = num + digits; if (!digits) { args.rval().setString(str); return true; } JSRuntime* rt = cx->runtime(); size_t thousandsLength = strlen(rt->thousandsSeparator); size_t decimalLength = strlen(rt->decimalSeparator); /* Figure out how long resulting string will be. */ int buflen = strlen(num); if (*nint == '.') { buflen += decimalLength - 1; /* -1 to account for existing '.' */ } const char* numGrouping; const char* tmpGroup; numGrouping = tmpGroup = rt->numGrouping; int remainder = digits; if (*num == '-') { remainder--; } while (*tmpGroup != CHAR_MAX && *tmpGroup != '\0') { if (*tmpGroup >= remainder) { break; } buflen += thousandsLength; remainder -= *tmpGroup; tmpGroup++; } int nrepeat; if (*tmpGroup == '\0' && *numGrouping != '\0') { nrepeat = (remainder - 1) / tmpGroup[-1]; buflen += thousandsLength * nrepeat; remainder -= nrepeat * tmpGroup[-1]; } else { nrepeat = 0; } tmpGroup--; char* buf = cx->pod_malloc(buflen + 1); if (!buf) { return false; } char* tmpDest = buf; const char* tmpSrc = num; while (*tmpSrc == '-' || remainder--) { MOZ_ASSERT(tmpDest - buf < buflen); *tmpDest++ = *tmpSrc++; } while (tmpSrc < end) { MOZ_ASSERT(tmpDest - buf + ptrdiff_t(thousandsLength) <= buflen); strcpy(tmpDest, rt->thousandsSeparator); tmpDest += thousandsLength; MOZ_ASSERT(tmpDest - buf + *tmpGroup <= buflen); js_memcpy(tmpDest, tmpSrc, *tmpGroup); tmpDest += *tmpGroup; tmpSrc += *tmpGroup; if (--nrepeat < 0) { tmpGroup--; } } if (*nint == '.') { MOZ_ASSERT(tmpDest - buf + ptrdiff_t(decimalLength) <= buflen); strcpy(tmpDest, rt->decimalSeparator); tmpDest += decimalLength; MOZ_ASSERT(tmpDest - buf + ptrdiff_t(strlen(nint + 1)) <= buflen); strcpy(tmpDest, nint + 1); } else { MOZ_ASSERT(tmpDest - buf + ptrdiff_t(strlen(nint)) <= buflen); strcpy(tmpDest, nint); } if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToUnicode) { Rooted v(cx, StringValue(str)); bool ok = !!cx->runtime()->localeCallbacks->localeToUnicode(cx, buf, &v); if (ok) { args.rval().set(v); } js_free(buf); return ok; } str = NewStringCopyN(cx, buf, buflen); js_free(buf); if (!str) { return false; } args.rval().setString(str); return true; } #endif /* !JS_HAS_INTL_API */ bool js::num_valueOf(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); double d; if (!ThisNumberValue(cx, args, "valueOf", &d)) { return false; } args.rval().setNumber(d); return true; } static const unsigned MAX_PRECISION = 100; static bool ComputePrecisionInRange(JSContext* cx, int minPrecision, int maxPrecision, double prec, int* precision) { if (minPrecision <= prec && prec <= maxPrecision) { *precision = int(prec); return true; } ToCStringBuf cbuf; char* numStr = NumberToCString(&cbuf, prec); MOZ_ASSERT(numStr); JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PRECISION_RANGE, numStr); return false; } static constexpr size_t DoubleToStrResultBufSize = 128; template [[nodiscard]] static bool DoubleToStrResult(JSContext* cx, const CallArgs& args, Op op) { char buf[DoubleToStrResultBufSize]; const auto& converter = double_conversion::DoubleToStringConverter::EcmaScriptConverter(); double_conversion::StringBuilder builder(buf, sizeof(buf)); bool ok = op(converter, builder); MOZ_RELEASE_ASSERT(ok); size_t numStrLen = builder.position(); const char* numStr = builder.Finalize(); MOZ_ASSERT(numStr == buf); MOZ_ASSERT(numStrLen == strlen(numStr)); JSString* str = NewStringCopyN(cx, numStr, numStrLen); if (!str) { return false; } args.rval().setString(str); return true; } // ES 2021 draft 21.1.3.3. static bool num_toFixed(JSContext* cx, unsigned argc, Value* vp) { AutoJSMethodProfilerEntry pseudoFrame(cx, "Number.prototype", "toFixed"); CallArgs args = CallArgsFromVp(argc, vp); // Step 1. double d; if (!ThisNumberValue(cx, args, "toFixed", &d)) { return false; } // Steps 2-5. int precision; if (args.length() == 0) { precision = 0; } else { double prec = 0; if (!ToInteger(cx, args[0], &prec)) { return false; } if (!ComputePrecisionInRange(cx, 0, MAX_PRECISION, prec, &precision)) { return false; } } // Step 6. if (std::isnan(d)) { args.rval().setString(cx->names().NaN); return true; } if (std::isinf(d)) { if (d > 0) { args.rval().setString(cx->names().Infinity); return true; } args.rval().setString(cx->names().NegativeInfinity_); return true; } // Steps 7-10 for very large numbers. if (d <= -1e21 || d >= 1e+21) { JSString* s = NumberToString(cx, d); if (!s) { return false; } args.rval().setString(s); return true; } // Steps 7-12. // DoubleToStringConverter::ToFixed is documented as requiring a buffer size // of: // // 1 + kMaxFixedDigitsBeforePoint + 1 + kMaxFixedDigitsAfterPoint + 1 // (one additional character for the sign, one for the decimal point, // and one for the null terminator) // // We already ensured there are at most 21 digits before the point, and // MAX_PRECISION digits after the point. static_assert(1 + 21 + 1 + MAX_PRECISION + 1 <= DoubleToStrResultBufSize); // The double-conversion library by default has a kMaxFixedDigitsAfterPoint of // 60. Assert our modified version supports at least MAX_PRECISION (100). using DToSConverter = double_conversion::DoubleToStringConverter; static_assert(DToSConverter::kMaxFixedDigitsAfterPoint >= MAX_PRECISION); return DoubleToStrResult(cx, args, [&](auto& converter, auto& builder) { return converter.ToFixed(d, precision, &builder); }); } // ES 2021 draft 21.1.3.2. static bool num_toExponential(JSContext* cx, unsigned argc, Value* vp) { AutoJSMethodProfilerEntry pseudoFrame(cx, "Number.prototype", "toExponential"); CallArgs args = CallArgsFromVp(argc, vp); // Step 1. double d; if (!ThisNumberValue(cx, args, "toExponential", &d)) { return false; } // Step 2. double prec = 0; if (args.hasDefined(0)) { if (!ToInteger(cx, args[0], &prec)) { return false; } } // Step 3. MOZ_ASSERT_IF(!args.hasDefined(0), prec == 0); // Step 4. if (std::isnan(d)) { args.rval().setString(cx->names().NaN); return true; } if (std::isinf(d)) { if (d > 0) { args.rval().setString(cx->names().Infinity); return true; } args.rval().setString(cx->names().NegativeInfinity_); return true; } // Step 5. int precision = 0; if (!ComputePrecisionInRange(cx, 0, MAX_PRECISION, prec, &precision)) { return false; } // Steps 6-15. // DoubleToStringConverter::ToExponential is documented as adding at most 8 // characters on top of the requested digits: "the sign, the digit before the // decimal point, the decimal point, the exponent character, the exponent's // sign, and at most 3 exponent digits". In addition, the buffer must be able // to hold the trailing '\0' character. static_assert(MAX_PRECISION + 8 + 1 <= DoubleToStrResultBufSize); return DoubleToStrResult(cx, args, [&](auto& converter, auto& builder) { int requestedDigits = args.hasDefined(0) ? precision : -1; return converter.ToExponential(d, requestedDigits, &builder); }); } // ES 2021 draft 21.1.3.5. static bool num_toPrecision(JSContext* cx, unsigned argc, Value* vp) { AutoJSMethodProfilerEntry pseudoFrame(cx, "Number.prototype", "toPrecision"); CallArgs args = CallArgsFromVp(argc, vp); // Step 1. double d; if (!ThisNumberValue(cx, args, "toPrecision", &d)) { return false; } // Step 2. if (!args.hasDefined(0)) { JSString* str = NumberToStringWithBase(cx, d, 10); if (!str) { return false; } args.rval().setString(str); return true; } // Step 3. double prec = 0; if (!ToInteger(cx, args[0], &prec)) { return false; } // Step 4. if (std::isnan(d)) { args.rval().setString(cx->names().NaN); return true; } if (std::isinf(d)) { if (d > 0) { args.rval().setString(cx->names().Infinity); return true; } args.rval().setString(cx->names().NegativeInfinity_); return true; } // Step 5. int precision = 0; if (!ComputePrecisionInRange(cx, 1, MAX_PRECISION, prec, &precision)) { return false; } // Steps 6-14. // DoubleToStringConverter::ToPrecision is documented as adding at most 7 // characters on top of the requested digits: "the sign, the decimal point, // the exponent character, the exponent's sign, and at most 3 exponent // digits". In addition, the buffer must be able to hold the trailing '\0' // character. static_assert(MAX_PRECISION + 7 + 1 <= DoubleToStrResultBufSize); return DoubleToStrResult(cx, args, [&](auto& converter, auto& builder) { return converter.ToPrecision(d, precision, &builder); }); } static const JSFunctionSpec number_methods[] = { JS_FN("toSource", num_toSource, 0, 0), JS_INLINABLE_FN("toString", num_toString, 1, 0, NumberToString), #if JS_HAS_INTL_API JS_SELF_HOSTED_FN("toLocaleString", "Number_toLocaleString", 0, 0), #else JS_FN("toLocaleString", num_toLocaleString, 0, 0), #endif JS_FN("valueOf", num_valueOf, 0, 0), JS_FN("toFixed", num_toFixed, 1, 0), JS_FN("toExponential", num_toExponential, 1, 0), JS_FN("toPrecision", num_toPrecision, 1, 0), JS_FS_END}; bool js::IsInteger(double d) { return std::isfinite(d) && JS::ToInteger(d) == d; } static const JSFunctionSpec number_static_methods[] = { JS_SELF_HOSTED_FN("isFinite", "Number_isFinite", 1, 0), JS_SELF_HOSTED_FN("isInteger", "Number_isInteger", 1, 0), JS_SELF_HOSTED_FN("isNaN", "Number_isNaN", 1, 0), JS_SELF_HOSTED_FN("isSafeInteger", "Number_isSafeInteger", 1, 0), JS_FS_END}; static const JSPropertySpec number_static_properties[] = { JS_DOUBLE_PS("POSITIVE_INFINITY", mozilla::PositiveInfinity(), JSPROP_READONLY | JSPROP_PERMANENT), JS_DOUBLE_PS("NEGATIVE_INFINITY", mozilla::NegativeInfinity(), JSPROP_READONLY | JSPROP_PERMANENT), JS_DOUBLE_PS("MAX_VALUE", 1.7976931348623157E+308, JSPROP_READONLY | JSPROP_PERMANENT), JS_DOUBLE_PS("MIN_VALUE", MinNumberValue(), JSPROP_READONLY | JSPROP_PERMANENT), /* ES6 (April 2014 draft) 20.1.2.6 */ JS_DOUBLE_PS("MAX_SAFE_INTEGER", 9007199254740991, JSPROP_READONLY | JSPROP_PERMANENT), /* ES6 (April 2014 draft) 20.1.2.10 */ JS_DOUBLE_PS("MIN_SAFE_INTEGER", -9007199254740991, JSPROP_READONLY | JSPROP_PERMANENT), /* ES6 (May 2013 draft) 15.7.3.7 */ JS_DOUBLE_PS("EPSILON", 2.2204460492503130808472633361816e-16, JSPROP_READONLY | JSPROP_PERMANENT), JS_PS_END}; bool js::InitRuntimeNumberState(JSRuntime* rt) { // XXX If JS_HAS_INTL_API becomes true all the time at some point, // js::InitRuntimeNumberState is no longer fallible, and we should // change its return type. #if !JS_HAS_INTL_API /* Copy locale-specific separators into the runtime strings. */ const char* thousandsSeparator; const char* decimalPoint; const char* grouping; # ifdef HAVE_LOCALECONV struct lconv* locale = localeconv(); thousandsSeparator = locale->thousands_sep; decimalPoint = locale->decimal_point; grouping = locale->grouping; # else thousandsSeparator = getenv("LOCALE_THOUSANDS_SEP"); decimalPoint = getenv("LOCALE_DECIMAL_POINT"); grouping = getenv("LOCALE_GROUPING"); # endif if (!thousandsSeparator) { thousandsSeparator = "'"; } if (!decimalPoint) { decimalPoint = "."; } if (!grouping) { grouping = "\3\0"; } /* * We use single malloc to get the memory for all separator and grouping * strings. */ size_t thousandsSeparatorSize = strlen(thousandsSeparator) + 1; size_t decimalPointSize = strlen(decimalPoint) + 1; size_t groupingSize = strlen(grouping) + 1; char* storage = js_pod_malloc(thousandsSeparatorSize + decimalPointSize + groupingSize); if (!storage) { return false; } js_memcpy(storage, thousandsSeparator, thousandsSeparatorSize); rt->thousandsSeparator = storage; storage += thousandsSeparatorSize; js_memcpy(storage, decimalPoint, decimalPointSize); rt->decimalSeparator = storage; storage += decimalPointSize; js_memcpy(storage, grouping, groupingSize); rt->numGrouping = grouping; #endif /* !JS_HAS_INTL_API */ return true; } void js::FinishRuntimeNumberState(JSRuntime* rt) { #if !JS_HAS_INTL_API /* * The free also releases the memory for decimalSeparator and numGrouping * strings. */ char* storage = const_cast(rt->thousandsSeparator.ref()); js_free(storage); #endif // !JS_HAS_INTL_API } JSObject* NumberObject::createPrototype(JSContext* cx, JSProtoKey key) { NumberObject* numberProto = GlobalObject::createBlankPrototype(cx, cx->global()); if (!numberProto) { return nullptr; } numberProto->setPrimitiveValue(0); return numberProto; } static bool NumberClassFinish(JSContext* cx, HandleObject ctor, HandleObject proto) { Handle global = cx->global(); if (!JS_DefineFunctions(cx, global, number_functions)) { return false; } // Number.parseInt should be the same function object as global parseInt. RootedId parseIntId(cx, NameToId(cx->names().parseInt)); JSFunction* parseInt = DefineFunction(cx, global, parseIntId, num_parseInt, 2, JSPROP_RESOLVING); if (!parseInt) { return false; } parseInt->setJitInfo(&jit::JitInfo_NumberParseInt); RootedValue parseIntValue(cx, ObjectValue(*parseInt)); if (!DefineDataProperty(cx, ctor, parseIntId, parseIntValue, 0)) { return false; } // Number.parseFloat should be the same function object as global // parseFloat. RootedId parseFloatId(cx, NameToId(cx->names().parseFloat)); JSFunction* parseFloat = DefineFunction(cx, global, parseFloatId, num_parseFloat, 1, JSPROP_RESOLVING); if (!parseFloat) { return false; } RootedValue parseFloatValue(cx, ObjectValue(*parseFloat)); if (!DefineDataProperty(cx, ctor, parseFloatId, parseFloatValue, 0)) { return false; } RootedValue valueNaN(cx, JS::NaNValue()); RootedValue valueInfinity(cx, JS::InfinityValue()); if (!DefineDataProperty( cx, ctor, cx->names().NaN, valueNaN, JSPROP_PERMANENT | JSPROP_READONLY | JSPROP_RESOLVING)) { return false; } // ES5 15.1.1.1, 15.1.1.2 if (!NativeDefineDataProperty( cx, global, cx->names().NaN, valueNaN, JSPROP_PERMANENT | JSPROP_READONLY | JSPROP_RESOLVING) || !NativeDefineDataProperty( cx, global, cx->names().Infinity, valueInfinity, JSPROP_PERMANENT | JSPROP_READONLY | JSPROP_RESOLVING)) { return false; } return true; } const ClassSpec NumberObject::classSpec_ = { GenericCreateConstructor, NumberObject::createPrototype, number_static_methods, number_static_properties, number_methods, nullptr, NumberClassFinish}; static char* FracNumberToCString(ToCStringBuf* cbuf, double d, size_t* len) { #ifdef DEBUG { int32_t _; MOZ_ASSERT(!NumberEqualsInt32(d, &_)); } #endif /* * This is V8's implementation of the algorithm described in the * following paper: * * Printing floating-point numbers quickly and accurately with integers. * Florian Loitsch, PLDI 2010. */ const double_conversion::DoubleToStringConverter& converter = double_conversion::DoubleToStringConverter::EcmaScriptConverter(); double_conversion::StringBuilder builder(cbuf->sbuf, std::size(cbuf->sbuf)); converter.ToShortest(d, &builder); *len = builder.position(); return builder.Finalize(); } void JS::NumberToString(double d, char (&out)[MaximumNumberToStringLength]) { int32_t i; if (NumberEqualsInt32(d, &i)) { Int32ToCStringBuf cbuf; size_t len; char* loc = ::Int32ToCString(&cbuf, i, &len); memmove(out, loc, len); out[len] = '\0'; } else { const double_conversion::DoubleToStringConverter& converter = double_conversion::DoubleToStringConverter::EcmaScriptConverter(); double_conversion::StringBuilder builder(out, sizeof(out)); converter.ToShortest(d, &builder); #ifdef DEBUG char* result = #endif builder.Finalize(); MOZ_ASSERT(out == result); } } char* js::NumberToCString(ToCStringBuf* cbuf, double d, size_t* length) { int32_t i; size_t len; char* s = NumberEqualsInt32(d, &i) ? ::Int32ToCString(cbuf, i, &len) : FracNumberToCString(cbuf, d, &len); MOZ_ASSERT(s); if (length) { *length = len; } return s; } char* js::Int32ToCString(Int32ToCStringBuf* cbuf, int32_t value, size_t* length) { size_t len; char* s = ::Int32ToCString(cbuf, value, &len); MOZ_ASSERT(s); if (length) { *length = len; } return s; } char* js::Uint32ToCString(Int32ToCStringBuf* cbuf, uint32_t value, size_t* length) { size_t len; char* s = ::Int32ToCString(cbuf, value, &len); MOZ_ASSERT(s); if (length) { *length = len; } return s; } char* js::Uint32ToHexCString(Int32ToCStringBuf* cbuf, uint32_t value, size_t* length) { size_t len; char* s = ::Int32ToCString(cbuf, value, &len); MOZ_ASSERT(s); if (length) { *length = len; } return s; } template static JSString* NumberToStringWithBase(JSContext* cx, double d, int base) { MOZ_ASSERT(2 <= base && base <= 36); Realm* realm = cx->realm(); int32_t i; if (NumberEqualsInt32(d, &i)) { bool isBase10Int = (base == 10); if (isBase10Int) { static_assert(StaticStrings::INT_STATIC_LIMIT > 10 * 10); if (StaticStrings::hasInt(i)) { return cx->staticStrings().getInt(i); } } else if (unsigned(i) < unsigned(base)) { if (i < 10) { return cx->staticStrings().getInt(i); } char16_t c = 'a' + i - 10; MOZ_ASSERT(StaticStrings::hasUnit(c)); return cx->staticStrings().getUnit(c); } else if (unsigned(i) < unsigned(base * base)) { static constexpr char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; char chars[] = {digits[i / base], digits[i % base]}; JSString* str = cx->staticStrings().lookup(chars, 2); MOZ_ASSERT(str); return str; } if (JSLinearString* str = realm->dtoaCache.lookup(base, d)) { return str; } // Plus three to include the largest number, the sign, and the terminating // null character. constexpr size_t MaximumLength = std::numeric_limits::digits + 3; char buf[MaximumLength] = {}; size_t numStrLen; char* numStr = Int32ToCStringWithBase(buf, i, &numStrLen, base); MOZ_ASSERT(numStrLen == strlen(numStr)); JSLinearString* s = NewStringCopyN(cx, numStr, numStrLen); if (!s) { return nullptr; } if (isBase10Int && i >= 0) { s->maybeInitializeIndexValue(i); } realm->dtoaCache.cache(base, d, s); return s; } if (JSLinearString* str = realm->dtoaCache.lookup(base, d)) { return str; } JSLinearString* s; if (base == 10) { // We use a faster algorithm for base 10. ToCStringBuf cbuf; size_t numStrLen; char* numStr = FracNumberToCString(&cbuf, d, &numStrLen); MOZ_ASSERT(numStr); MOZ_ASSERT(numStrLen == strlen(numStr)); s = NewStringCopyN(cx, numStr, numStrLen); if (!s) { return nullptr; } } else { if (!EnsureDtoaState(cx)) { if constexpr (allowGC) { ReportOutOfMemory(cx); } return nullptr; } UniqueChars numStr(js_dtobasestr(cx->dtoaState, base, d)); if (!numStr) { if constexpr (allowGC) { ReportOutOfMemory(cx); } return nullptr; } s = NewStringCopyZ(cx, numStr.get()); if (!s) { return nullptr; } } realm->dtoaCache.cache(base, d, s); return s; } template JSString* js::NumberToString(JSContext* cx, double d) { return NumberToStringWithBase(cx, d, 10); } template JSString* js::NumberToString(JSContext* cx, double d); template JSString* js::NumberToString(JSContext* cx, double d); JSString* js::NumberToStringPure(JSContext* cx, double d) { AutoUnsafeCallWithABI unsafe; return NumberToString(cx, d); } JSAtom* js::NumberToAtom(JSContext* cx, double d) { int32_t si; if (NumberEqualsInt32(d, &si)) { return Int32ToAtom(cx, si); } if (JSLinearString* str = LookupDtoaCache(cx, d)) { return AtomizeString(cx, str); } ToCStringBuf cbuf; size_t length; char* numStr = FracNumberToCString(&cbuf, d, &length); MOZ_ASSERT(numStr); MOZ_ASSERT(std::begin(cbuf.sbuf) <= numStr && numStr < std::end(cbuf.sbuf)); MOZ_ASSERT(length == strlen(numStr)); JSAtom* atom = Atomize(cx, numStr, length); if (!atom) { return nullptr; } CacheNumber(cx, d, atom); return atom; } frontend::TaggedParserAtomIndex js::NumberToParserAtom( FrontendContext* fc, frontend::ParserAtomsTable& parserAtoms, double d) { int32_t si; if (NumberEqualsInt32(d, &si)) { return Int32ToParserAtom(fc, parserAtoms, si); } ToCStringBuf cbuf; size_t length; char* numStr = FracNumberToCString(&cbuf, d, &length); MOZ_ASSERT(numStr); MOZ_ASSERT(std::begin(cbuf.sbuf) <= numStr && numStr < std::end(cbuf.sbuf)); MOZ_ASSERT(length == strlen(numStr)); return parserAtoms.internAscii(fc, numStr, length); } JSLinearString* js::IndexToString(JSContext* cx, uint32_t index) { if (StaticStrings::hasUint(index)) { return cx->staticStrings().getUint(index); } Realm* realm = cx->realm(); if (JSLinearString* str = realm->dtoaCache.lookup(10, index)) { return str; } Latin1Char buffer[JSFatInlineString::MAX_LENGTH_LATIN1 + 1]; RangedPtr end(buffer + JSFatInlineString::MAX_LENGTH_LATIN1, buffer, JSFatInlineString::MAX_LENGTH_LATIN1 + 1); *end = '\0'; RangedPtr start = BackfillIndexInCharBuffer(index, end); mozilla::Range chars(start.get(), end - start); JSInlineString* str = NewInlineString(cx, chars, js::gc::Heap::Default); if (!str) { return nullptr; } realm->dtoaCache.cache(10, index, str); return str; } JSString* js::Int32ToStringWithBase(JSContext* cx, int32_t i, int32_t base, bool lowerCase) { Rooted str(cx, NumberToStringWithBase(cx, double(i), base)); if (!str) { return nullptr; } if (lowerCase) { return str; } return StringToUpperCase(cx, str); } bool js::NumberValueToStringBuffer(const Value& v, StringBuffer& sb) { /* Convert to C-string. */ ToCStringBuf cbuf; const char* cstr; size_t cstrlen; if (v.isInt32()) { cstr = ::Int32ToCString(&cbuf, v.toInt32(), &cstrlen); } else { cstr = NumberToCString(&cbuf, v.toDouble(), &cstrlen); } MOZ_ASSERT(cstr); MOZ_ASSERT(cstrlen == strlen(cstr)); MOZ_ASSERT(cstrlen < std::size(cbuf.sbuf)); return sb.append(cstr, cstrlen); } template inline double CharToNumber(CharT c) { if ('0' <= c && c <= '9') { return c - '0'; } if (unicode::IsSpace(c)) { return 0.0; } return GenericNaN(); } template inline bool CharsToNonDecimalNumber(const CharT* start, const CharT* end, double* result) { MOZ_ASSERT(end - start >= 2); MOZ_ASSERT(start[0] == '0'); int radix = 0; if (start[1] == 'b' || start[1] == 'B') { radix = 2; } else if (start[1] == 'o' || start[1] == 'O') { radix = 8; } else if (start[1] == 'x' || start[1] == 'X') { radix = 16; } else { return false; } // It's probably a non-decimal number. Accept if there's at least one digit // after the 0b|0o|0x, and if no non-whitespace characters follow all the // digits. const CharT* endptr; double d; MOZ_ALWAYS_TRUE(GetPrefixIntegerImpl( start + 2, end, radix, IntegerSeparatorHandling::None, &endptr, &d)); if (endptr == start + 2 || SkipSpace(endptr, end) != end) { *result = GenericNaN(); } else { *result = d; } return true; } template double js::CharsToNumber(const CharT* chars, size_t length) { if (length == 1) { return CharToNumber(chars[0]); } const CharT* end = chars + length; const CharT* start = SkipSpace(chars, end); // ECMA doesn't allow signed non-decimal numbers (bug 273467). if (end - start >= 2 && start[0] == '0') { double d; if (CharsToNonDecimalNumber(start, end, &d)) { return d; } } /* * Note that ECMA doesn't treat a string beginning with a '0' as * an octal number here. This works because all such numbers will * be interpreted as decimal by js_strtod. Also, any hex numbers * that have made it here (which can only be negative ones) will * be treated as 0 without consuming the 'x' by js_strtod. */ const CharT* ep; double d = js_strtod(start, end, &ep); if (SkipSpace(ep, end) != end) { return GenericNaN(); } return d; } template double js::CharsToNumber(const Latin1Char* chars, size_t length); template double js::CharsToNumber(const char16_t* chars, size_t length); double js::LinearStringToNumber(JSLinearString* str) { if (str->hasIndexValue()) { return str->getIndexValue(); } AutoCheckCannotGC nogc; return str->hasLatin1Chars() ? CharsToNumber(str->latin1Chars(nogc), str->length()) : CharsToNumber(str->twoByteChars(nogc), str->length()); } bool js::StringToNumber(JSContext* cx, JSString* str, double* result) { JSLinearString* linearStr = str->ensureLinear(cx); if (!linearStr) { return false; } *result = LinearStringToNumber(linearStr); return true; } bool js::StringToNumberPure(JSContext* cx, JSString* str, double* result) { // IC Code calls this directly. AutoUnsafeCallWithABI unsafe; if (!StringToNumber(cx, str, result)) { cx->recoverFromOutOfMemory(); return false; } return true; } JS_PUBLIC_API bool js::ToNumberSlow(JSContext* cx, HandleValue v_, double* out) { RootedValue v(cx, v_); MOZ_ASSERT(!v.isNumber()); if (!v.isPrimitive()) { if (!ToPrimitive(cx, JSTYPE_NUMBER, &v)) { return false; } if (v.isNumber()) { *out = v.toNumber(); return true; } } if (v.isString()) { return StringToNumber(cx, v.toString(), out); } if (v.isBoolean()) { *out = v.toBoolean() ? 1.0 : 0.0; return true; } if (v.isNull()) { *out = 0.0; return true; } if (v.isUndefined()) { *out = GenericNaN(); return true; } #ifdef ENABLE_RECORD_TUPLE if (v.isExtendedPrimitive()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_RECORD_TUPLE_TO_NUMBER); return false; } #endif MOZ_ASSERT(v.isSymbol() || v.isBigInt()); unsigned errnum = JSMSG_SYMBOL_TO_NUMBER; if (v.isBigInt()) { errnum = JSMSG_BIGINT_TO_NUMBER; } JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errnum); return false; } // BigInt proposal section 3.1.6 bool js::ToNumericSlow(JSContext* cx, MutableHandleValue vp) { MOZ_ASSERT(!vp.isNumeric()); // Step 1. if (!vp.isPrimitive()) { if (!ToPrimitive(cx, JSTYPE_NUMBER, vp)) { return false; } } // Step 2. if (vp.isBigInt()) { return true; } // Step 3. return ToNumber(cx, vp); } /* * Convert a value to an int8_t, according to the WebIDL rules for byte * conversion. Return converted value in *out on success, false on failure. */ JS_PUBLIC_API bool js::ToInt8Slow(JSContext* cx, const HandleValue v, int8_t* out) { MOZ_ASSERT(!v.isInt32()); double d; if (v.isDouble()) { d = v.toDouble(); } else { if (!ToNumberSlow(cx, v, &d)) { return false; } } *out = ToInt8(d); return true; } /* * Convert a value to an uint8_t, according to the ToUInt8() function in ES6 * ECMA-262, 7.1.10. Return converted value in *out on success, false on * failure. */ JS_PUBLIC_API bool js::ToUint8Slow(JSContext* cx, const HandleValue v, uint8_t* out) { MOZ_ASSERT(!v.isInt32()); double d; if (v.isDouble()) { d = v.toDouble(); } else { if (!ToNumberSlow(cx, v, &d)) { return false; } } *out = ToUint8(d); return true; } /* * Convert a value to an int16_t, according to the WebIDL rules for short * conversion. Return converted value in *out on success, false on failure. */ JS_PUBLIC_API bool js::ToInt16Slow(JSContext* cx, const HandleValue v, int16_t* out) { MOZ_ASSERT(!v.isInt32()); double d; if (v.isDouble()) { d = v.toDouble(); } else { if (!ToNumberSlow(cx, v, &d)) { return false; } } *out = ToInt16(d); return true; } /* * Convert a value to an int64_t, according to the WebIDL rules for long long * conversion. Return converted value in *out on success, false on failure. */ JS_PUBLIC_API bool js::ToInt64Slow(JSContext* cx, const HandleValue v, int64_t* out) { MOZ_ASSERT(!v.isInt32()); double d; if (v.isDouble()) { d = v.toDouble(); } else { if (!ToNumberSlow(cx, v, &d)) { return false; } } *out = ToInt64(d); return true; } /* * Convert a value to an uint64_t, according to the WebIDL rules for unsigned * long long conversion. Return converted value in *out on success, false on * failure. */ JS_PUBLIC_API bool js::ToUint64Slow(JSContext* cx, const HandleValue v, uint64_t* out) { MOZ_ASSERT(!v.isInt32()); double d; if (v.isDouble()) { d = v.toDouble(); } else { if (!ToNumberSlow(cx, v, &d)) { return false; } } *out = ToUint64(d); return true; } JS_PUBLIC_API bool js::ToInt32Slow(JSContext* cx, const HandleValue v, int32_t* out) { MOZ_ASSERT(!v.isInt32()); double d; if (v.isDouble()) { d = v.toDouble(); } else { if (!ToNumberSlow(cx, v, &d)) { return false; } } *out = ToInt32(d); return true; } bool js::ToInt32OrBigIntSlow(JSContext* cx, MutableHandleValue vp) { MOZ_ASSERT(!vp.isInt32()); if (vp.isDouble()) { vp.setInt32(ToInt32(vp.toDouble())); return true; } if (!ToNumeric(cx, vp)) { return false; } if (vp.isBigInt()) { return true; } vp.setInt32(ToInt32(vp.toNumber())); return true; } JS_PUBLIC_API bool js::ToUint32Slow(JSContext* cx, const HandleValue v, uint32_t* out) { MOZ_ASSERT(!v.isInt32()); double d; if (v.isDouble()) { d = v.toDouble(); } else { if (!ToNumberSlow(cx, v, &d)) { return false; } } *out = ToUint32(d); return true; } JS_PUBLIC_API bool js::ToUint16Slow(JSContext* cx, const HandleValue v, uint16_t* out) { MOZ_ASSERT(!v.isInt32()); double d; if (v.isDouble()) { d = v.toDouble(); } else if (!ToNumberSlow(cx, v, &d)) { return false; } *out = ToUint16(d); return true; } // ES2017 draft 7.1.17 ToIndex bool js::ToIndexSlow(JSContext* cx, JS::HandleValue v, const unsigned errorNumber, uint64_t* index) { MOZ_ASSERT_IF(v.isInt32(), v.toInt32() < 0); // Step 1. if (v.isUndefined()) { *index = 0; return true; } // Step 2.a. double integerIndex; if (!ToInteger(cx, v, &integerIndex)) { return false; } // Inlined version of ToLength. // 1. Already an integer. // 2. Step eliminates < 0, +0 == -0 with SameValueZero. // 3/4. Limit to <= 2^53-1, so everything above should fail. if (integerIndex < 0 || integerIndex >= DOUBLE_INTEGRAL_PRECISION_LIMIT) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber); return false; } // Step 3. *index = uint64_t(integerIndex); return true; } template double js_strtod(const CharT* begin, const CharT* end, const CharT** dEnd) { const CharT* s = SkipSpace(begin, end); size_t length = end - s; { // StringToDouble can make indirect calls but can't trigger a GC. JS::AutoSuppressGCAnalysis nogc; using SToDConverter = double_conversion::StringToDoubleConverter; SToDConverter converter(SToDConverter::ALLOW_TRAILING_JUNK, /* empty_string_value = */ 0.0, /* junk_string_value = */ GenericNaN(), /* infinity_symbol = */ nullptr, /* nan_symbol = */ nullptr); int lengthInt = mozilla::AssertedCast(length); double d; int processed = 0; if constexpr (std::is_same_v) { d = converter.StringToDouble(reinterpret_cast(s), lengthInt, &processed); } else { static_assert(std::is_same_v); d = converter.StringToDouble(reinterpret_cast(s), lengthInt, &processed); } MOZ_ASSERT(processed >= 0); MOZ_ASSERT(processed <= lengthInt); if (processed > 0) { *dEnd = s + processed; return d; } } // Try to parse +Infinity, -Infinity or Infinity. Note that we do this here // instead of using StringToDoubleConverter's infinity_symbol because it's // faster: the code below is less generic and not on the fast path for regular // doubles. static constexpr std::string_view Infinity = "Infinity"; if (length >= Infinity.length()) { const CharT* afterSign = s; bool negative = (*afterSign == '-'); if (negative || *afterSign == '+') { afterSign++; } MOZ_ASSERT(afterSign < end); if (*afterSign == 'I' && size_t(end - afterSign) >= Infinity.length() && EqualChars(afterSign, Infinity.data(), Infinity.length())) { *dEnd = afterSign + Infinity.length(); return negative ? NegativeInfinity() : PositiveInfinity(); } } *dEnd = begin; return 0.0; } template double js_strtod(const char16_t* begin, const char16_t* end, const char16_t** dEnd); template double js_strtod(const Latin1Char* begin, const Latin1Char* end, const Latin1Char** dEnd);