2313 lines
64 KiB
C++
2313 lines
64 KiB
C++
/* -*- 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 <algorithm>
|
|
#include <charconv>
|
|
#include <iterator>
|
|
#include <limits>
|
|
#ifdef HAVE_LOCALECONV
|
|
# include <locale.h>
|
|
#endif
|
|
#include <math.h>
|
|
#include <string.h> // memmove
|
|
#include <string_view>
|
|
|
|
#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/StringBuilder.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/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::MaxNumberValue;
|
|
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 <typename CharT>
|
|
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 <typename CharT>
|
|
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 <typename CharT>
|
|
static double ComputeAccurateBinaryBaseInteger(const CharT* start,
|
|
const CharT* end, int base) {
|
|
BinaryDigitReader<CharT> 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 <typename CharT>
|
|
double js::ParseDecimalNumber(const mozilla::Range<const CharT> chars) {
|
|
MOZ_ASSERT(chars.length() > 0);
|
|
uint64_t dec = 0;
|
|
RangedPtr<const CharT> 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<double>(dec);
|
|
}
|
|
|
|
template double js::ParseDecimalNumber(
|
|
const mozilla::Range<const Latin1Char> chars);
|
|
|
|
template double js::ParseDecimalNumber(
|
|
const mozilla::Range<const char16_t> chars);
|
|
|
|
template <typename CharT>
|
|
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 <typename CharT>
|
|
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 <typename CharT>
|
|
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<Utf8Unit>(const Utf8Unit* start, const Utf8Unit* end,
|
|
double* dp) {
|
|
return GetDecimalInteger(Utf8AsUnsignedChars(start), Utf8AsUnsignedChars(end),
|
|
dp);
|
|
}
|
|
|
|
} // namespace js
|
|
|
|
template <typename CharT>
|
|
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<int>(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<CharT, char16_t>) {
|
|
*dp = convert(reinterpret_cast<const uc16*>(start), length);
|
|
} else {
|
|
static_assert(std::is_same_v<CharT, Latin1Char>);
|
|
*dp = convert(reinterpret_cast<const char*>(start), length);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Vector<char, 32, SystemAllocPolicy> 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<Utf8Unit>(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<CanGC>(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 <typename CharT>
|
|
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<CanGC>(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 constexpr 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<NumberObject>(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(") ||
|
|
!NumberValueToStringBuilder(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* LookupInt32ToString(JSContext* cx, int32_t si) {
|
|
if (StaticStrings::hasInt(si)) {
|
|
return cx->staticStrings().getInt(si);
|
|
}
|
|
return cx->realm()->dtoaCache.lookup(10, si);
|
|
}
|
|
|
|
template <AllowGC allowGC>
|
|
JSLinearString* js::Int32ToString(JSContext* cx, int32_t si) {
|
|
return js::Int32ToStringWithHeap<allowGC>(cx, si, gc::Heap::Default);
|
|
}
|
|
template JSLinearString* js::Int32ToString<CanGC>(JSContext* cx, int32_t si);
|
|
template JSLinearString* js::Int32ToString<NoGC>(JSContext* cx, int32_t si);
|
|
|
|
template <AllowGC allowGC>
|
|
JSLinearString* js::Int32ToStringWithHeap(JSContext* cx, int32_t si,
|
|
gc::Heap heap) {
|
|
if (JSLinearString* str = LookupInt32ToString(cx, si)) {
|
|
return str;
|
|
}
|
|
|
|
char buffer[JSFatInlineString::MAX_LENGTH_LATIN1];
|
|
|
|
auto result = std::to_chars(buffer, std::end(buffer), si, 10);
|
|
MOZ_ASSERT(result.ec == std::errc());
|
|
|
|
size_t length = result.ptr - buffer;
|
|
const auto& latin1Chars =
|
|
reinterpret_cast<const JS::Latin1Char(&)[std::size(buffer)]>(buffer);
|
|
JSInlineString* str = NewInlineString<allowGC>(cx, latin1Chars, length, heap);
|
|
if (!str) {
|
|
return nullptr;
|
|
}
|
|
if (si >= 0) {
|
|
str->maybeInitializeIndexValue(si);
|
|
}
|
|
|
|
cx->realm()->dtoaCache.cache(10, si, str);
|
|
return str;
|
|
}
|
|
template JSLinearString* js::Int32ToStringWithHeap<CanGC>(JSContext* cx,
|
|
int32_t si,
|
|
gc::Heap heap);
|
|
template JSLinearString* js::Int32ToStringWithHeap<NoGC>(JSContext* cx,
|
|
int32_t si,
|
|
gc::Heap heap);
|
|
|
|
JSLinearString* js::Int32ToStringPure(JSContext* cx, int32_t si) {
|
|
AutoUnsafeCallWithABI unsafe;
|
|
return Int32ToString<NoGC>(cx, si);
|
|
}
|
|
|
|
JSAtom* js::Int32ToAtom(JSContext* cx, int32_t si) {
|
|
if (JSLinearString* str = LookupInt32ToString(cx, si)) {
|
|
return js::AtomizeString(cx, str);
|
|
}
|
|
|
|
Int32ToCStringBuf cbuf;
|
|
auto result = std::to_chars(cbuf.sbuf, std::end(cbuf.sbuf), si, 10);
|
|
MOZ_ASSERT(result.ec == std::errc());
|
|
|
|
Maybe<uint32_t> indexValue;
|
|
if (si >= 0) {
|
|
indexValue.emplace(si);
|
|
}
|
|
|
|
size_t length = result.ptr - cbuf.sbuf;
|
|
JSAtom* atom = Atomize(cx, cbuf.sbuf, length, indexValue);
|
|
if (!atom) {
|
|
return nullptr;
|
|
}
|
|
|
|
cx->realm()->dtoaCache.cache(10, si, atom);
|
|
return atom;
|
|
}
|
|
|
|
frontend::TaggedParserAtomIndex js::Int32ToParserAtom(
|
|
FrontendContext* fc, frontend::ParserAtomsTable& parserAtoms, int32_t si) {
|
|
Int32ToCStringBuf cbuf;
|
|
auto result = std::to_chars(cbuf.sbuf, std::end(cbuf.sbuf), si, 10);
|
|
MOZ_ASSERT(result.ec == std::errc());
|
|
|
|
size_t length = result.ptr - cbuf.sbuf;
|
|
return parserAtoms.internAscii(fc, cbuf.sbuf, length);
|
|
}
|
|
|
|
/* Returns the number of digits written. */
|
|
template <typename T, size_t Base, size_t Length>
|
|
static size_t Int32ToCString(char (&out)[Length], T i) {
|
|
// 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<T>::digits10 + 1 + std::is_signed_v<T> <
|
|
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<T>::digits + std::is_signed_v<T>) / 4 +
|
|
std::is_signed_v<T>) < Length);
|
|
}
|
|
|
|
// -1 to leave space for the terminating null-character.
|
|
auto result = std::to_chars(out, std::end(out) - 1, i, Base);
|
|
MOZ_ASSERT(result.ec == std::errc());
|
|
|
|
// Null-terminate the result.
|
|
*result.ptr = '\0';
|
|
|
|
return result.ptr - out;
|
|
}
|
|
|
|
/* Returns the number of digits written. */
|
|
template <typename T, size_t Base = 10>
|
|
static size_t Int32ToCString(ToCStringBuf* cbuf, T i) {
|
|
return Int32ToCString<T, Base>(cbuf->sbuf, i);
|
|
}
|
|
|
|
/* Returns the number of digits written. */
|
|
template <typename T, size_t Base = 10>
|
|
static size_t Int32ToCString(Int32ToCStringBuf* cbuf, T i) {
|
|
return Int32ToCString<T, Base>(cbuf->sbuf, i);
|
|
}
|
|
|
|
template <AllowGC allowGC>
|
|
static JSString* NumberToStringWithBase(JSContext* cx, double d, int32_t 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<CanGC>(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<CanGC>(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<char>(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<Value> 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<CanGC>(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 <typename Op>
|
|
[[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<CanGC>(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<CanGC>(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<CanGC>(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 constexpr 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 constexpr 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 constexpr JSPropertySpec number_static_properties[] = {
|
|
JS_DOUBLE_PS("POSITIVE_INFINITY", mozilla::PositiveInfinity<double>(),
|
|
JSPROP_READONLY | JSPROP_PERMANENT),
|
|
JS_DOUBLE_PS("NEGATIVE_INFINITY", mozilla::NegativeInfinity<double>(),
|
|
JSPROP_READONLY | JSPROP_PERMANENT),
|
|
JS_DOUBLE_PS("MAX_VALUE", MaxNumberValue<double>(),
|
|
JSPROP_READONLY | JSPROP_PERMANENT),
|
|
JS_DOUBLE_PS("MIN_VALUE", MinNumberValue<double>(),
|
|
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<char>(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<char*>(rt->thousandsSeparator.ref());
|
|
js_free(storage);
|
|
#endif // !JS_HAS_INTL_API
|
|
}
|
|
|
|
JSObject* NumberObject::createPrototype(JSContext* cx, JSProtoKey key) {
|
|
NumberObject* numberProto =
|
|
GlobalObject::createBlankPrototype<NumberObject>(cx, cx->global());
|
|
if (!numberProto) {
|
|
return nullptr;
|
|
}
|
|
numberProto->setPrimitiveValue(0);
|
|
return numberProto;
|
|
}
|
|
|
|
static bool NumberClassFinish(JSContext* cx, HandleObject ctor,
|
|
HandleObject proto) {
|
|
Handle<GlobalObject*> 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<Number, 1, gc::AllocKind::FUNCTION,
|
|
&jit::JitInfo_Number>,
|
|
NumberObject::createPrototype,
|
|
number_static_methods,
|
|
number_static_properties,
|
|
number_methods,
|
|
nullptr,
|
|
NumberClassFinish,
|
|
};
|
|
|
|
static size_t FracNumberToCString(ToCStringBuf* cbuf, double d) {
|
|
#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));
|
|
MOZ_ALWAYS_TRUE(converter.ToShortest(d, &builder));
|
|
|
|
size_t len = builder.position();
|
|
#ifdef DEBUG
|
|
char* result =
|
|
#endif
|
|
builder.Finalize();
|
|
MOZ_ASSERT(cbuf->sbuf == result);
|
|
return len;
|
|
}
|
|
|
|
void JS::NumberToString(double d, char (&out)[MaximumNumberToStringLength]) {
|
|
int32_t i;
|
|
if (NumberEqualsInt32(d, &i)) {
|
|
Int32ToCStringBuf cbuf;
|
|
size_t len = ::Int32ToCString(&cbuf, i);
|
|
memmove(out, cbuf.sbuf, len);
|
|
out[len] = '\0';
|
|
} else {
|
|
const double_conversion::DoubleToStringConverter& converter =
|
|
double_conversion::DoubleToStringConverter::EcmaScriptConverter();
|
|
|
|
double_conversion::StringBuilder builder(out, sizeof(out));
|
|
MOZ_ALWAYS_TRUE(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 = NumberEqualsInt32(d, &i) ? ::Int32ToCString(cbuf, i)
|
|
: FracNumberToCString(cbuf, d);
|
|
if (length) {
|
|
*length = len;
|
|
}
|
|
return cbuf->sbuf;
|
|
}
|
|
|
|
char* js::Int32ToCString(Int32ToCStringBuf* cbuf, int32_t value,
|
|
size_t* length) {
|
|
size_t len = ::Int32ToCString(cbuf, value);
|
|
if (length) {
|
|
*length = len;
|
|
}
|
|
return cbuf->sbuf;
|
|
}
|
|
|
|
char* js::Uint32ToCString(Int32ToCStringBuf* cbuf, uint32_t value,
|
|
size_t* length) {
|
|
size_t len = ::Int32ToCString(cbuf, value);
|
|
if (length) {
|
|
*length = len;
|
|
}
|
|
return cbuf->sbuf;
|
|
}
|
|
|
|
char* js::Uint32ToHexCString(Int32ToCStringBuf* cbuf, uint32_t value,
|
|
size_t* length) {
|
|
size_t len = ::Int32ToCString<uint32_t, 16>(cbuf, value);
|
|
if (length) {
|
|
*length = len;
|
|
}
|
|
return cbuf->sbuf;
|
|
}
|
|
|
|
template <AllowGC allowGC>
|
|
static JSLinearString* Int32ToStringWithBase(JSContext* cx, int32_t i,
|
|
int32_t base) {
|
|
MOZ_ASSERT(2 <= base && base <= 36);
|
|
|
|
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]};
|
|
JSLinearString* str = cx->staticStrings().lookup(chars, 2);
|
|
MOZ_ASSERT(str);
|
|
return str;
|
|
}
|
|
|
|
auto& dtoaCache = cx->realm()->dtoaCache;
|
|
double d = i;
|
|
if (JSLinearString* str = dtoaCache.lookup(base, d)) {
|
|
return str;
|
|
}
|
|
|
|
// Plus two to include the largest number and the sign.
|
|
constexpr size_t MaximumLength = std::numeric_limits<int32_t>::digits + 2;
|
|
|
|
char buf[MaximumLength] = {};
|
|
|
|
// Use explicit cases for base 10 and base 16 to make it more likely the
|
|
// compiler will generate optimized code for these two common bases.
|
|
std::to_chars_result result;
|
|
switch (base) {
|
|
case 10: {
|
|
result = std::to_chars(buf, std::end(buf), i, 10);
|
|
break;
|
|
}
|
|
case 16: {
|
|
result = std::to_chars(buf, std::end(buf), i, 16);
|
|
break;
|
|
}
|
|
default: {
|
|
MOZ_ASSERT(base >= 2 && base <= 36);
|
|
result = std::to_chars(buf, std::end(buf), i, base);
|
|
break;
|
|
}
|
|
}
|
|
MOZ_ASSERT(result.ec == std::errc());
|
|
|
|
size_t length = result.ptr - buf;
|
|
MOZ_ASSERT(i < 0 || length > 2, "small static strings are handled above");
|
|
|
|
auto* latin1Chars = reinterpret_cast<JS::Latin1Char*>(buf);
|
|
JSLinearString* s = NewStringCopyNDontDeflateNonStaticValidLength<allowGC>(
|
|
cx, latin1Chars, length);
|
|
if (!s) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (isBase10Int && i >= 0) {
|
|
s->maybeInitializeIndexValue(i);
|
|
}
|
|
|
|
dtoaCache.cache(base, d, s);
|
|
return s;
|
|
}
|
|
|
|
template <AllowGC allowGC>
|
|
static JSString* NumberToStringWithBase(JSContext* cx, double d, int32_t base) {
|
|
MOZ_ASSERT(2 <= base && base <= 36);
|
|
|
|
int32_t i;
|
|
if (NumberEqualsInt32(d, &i)) {
|
|
return ::Int32ToStringWithBase<allowGC>(cx, i, base);
|
|
}
|
|
|
|
auto& dtoaCache = cx->realm()->dtoaCache;
|
|
if (JSLinearString* str = dtoaCache.lookup(base, d)) {
|
|
return str;
|
|
}
|
|
|
|
JSLinearString* s;
|
|
if (base == 10) {
|
|
// We use a faster algorithm for base 10.
|
|
ToCStringBuf cbuf;
|
|
size_t numStrLen = FracNumberToCString(&cbuf, d);
|
|
MOZ_ASSERT(numStrLen == strlen(cbuf.sbuf));
|
|
|
|
s = NewStringCopyN<allowGC>(cx, cbuf.sbuf, 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<allowGC>(cx, numStr.get());
|
|
if (!s) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
dtoaCache.cache(base, d, s);
|
|
return s;
|
|
}
|
|
|
|
template <AllowGC allowGC>
|
|
JSString* js::NumberToString(JSContext* cx, double d) {
|
|
return NumberToStringWithBase<allowGC>(cx, d, 10);
|
|
}
|
|
|
|
template JSString* js::NumberToString<CanGC>(JSContext* cx, double d);
|
|
|
|
template JSString* js::NumberToString<NoGC>(JSContext* cx, double d);
|
|
|
|
JSString* js::NumberToStringPure(JSContext* cx, double d) {
|
|
AutoUnsafeCallWithABI unsafe;
|
|
return NumberToString<NoGC>(cx, d);
|
|
}
|
|
|
|
JSAtom* js::NumberToAtom(JSContext* cx, double d) {
|
|
int32_t si;
|
|
if (NumberEqualsInt32(d, &si)) {
|
|
return Int32ToAtom(cx, si);
|
|
}
|
|
|
|
auto& dtoaCache = cx->realm()->dtoaCache;
|
|
if (JSLinearString* str = dtoaCache.lookup(10, d)) {
|
|
return AtomizeString(cx, str);
|
|
}
|
|
|
|
ToCStringBuf cbuf;
|
|
size_t length = FracNumberToCString(&cbuf, d);
|
|
MOZ_ASSERT(length == strlen(cbuf.sbuf));
|
|
|
|
JSAtom* atom = Atomize(cx, cbuf.sbuf, length);
|
|
if (!atom) {
|
|
return nullptr;
|
|
}
|
|
|
|
dtoaCache.cache(10, 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 = FracNumberToCString(&cbuf, d);
|
|
MOZ_ASSERT(length == strlen(cbuf.sbuf));
|
|
|
|
return parserAtoms.internAscii(fc, cbuf.sbuf, length);
|
|
}
|
|
|
|
JSLinearString* js::IndexToString(JSContext* cx, uint32_t index) {
|
|
if (StaticStrings::hasUint(index)) {
|
|
return cx->staticStrings().getUint(index);
|
|
}
|
|
|
|
char buffer[JSFatInlineString::MAX_LENGTH_LATIN1];
|
|
|
|
auto result = std::to_chars(buffer, std::end(buffer), index, 10);
|
|
MOZ_ASSERT(result.ec == std::errc());
|
|
|
|
size_t length = result.ptr - buffer;
|
|
const auto& latin1Chars =
|
|
reinterpret_cast<const JS::Latin1Char(&)[std::size(buffer)]>(buffer);
|
|
return NewInlineString<CanGC>(cx, latin1Chars, length);
|
|
}
|
|
|
|
template <AllowGC allowGC>
|
|
JSLinearString* js::Int32ToStringWithBase(JSContext* cx, int32_t i,
|
|
int32_t base, bool lowerCase) {
|
|
JSLinearString* str = ::Int32ToStringWithBase<allowGC>(cx, i, base);
|
|
if (!str) {
|
|
return nullptr;
|
|
}
|
|
|
|
if constexpr (allowGC == NoGC) {
|
|
MOZ_ASSERT(lowerCase, "upper case conversion not allowed for NoGC");
|
|
return str;
|
|
} else {
|
|
if (lowerCase) {
|
|
return str;
|
|
}
|
|
return StringToUpperCase(cx, str);
|
|
}
|
|
}
|
|
template JSLinearString* js::Int32ToStringWithBase<CanGC>(JSContext* cx,
|
|
int32_t i,
|
|
int32_t base,
|
|
bool lowerCase);
|
|
template JSLinearString* js::Int32ToStringWithBase<NoGC>(JSContext* cx,
|
|
int32_t i,
|
|
int32_t base,
|
|
bool lowerCase);
|
|
|
|
bool js::NumberValueToStringBuilder(const Value& v, StringBuilder& sb) {
|
|
/* Convert to C-string. */
|
|
ToCStringBuf cbuf;
|
|
const char* cstr;
|
|
size_t cstrlen;
|
|
if (v.isInt32()) {
|
|
cstrlen = ::Int32ToCString(&cbuf, v.toInt32());
|
|
cstr = cbuf.sbuf;
|
|
} 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 <typename CharT>
|
|
inline double CharToNumber(CharT c) {
|
|
if ('0' <= c && c <= '9') {
|
|
return c - '0';
|
|
}
|
|
if (unicode::IsSpace(c)) {
|
|
return 0.0;
|
|
}
|
|
return GenericNaN();
|
|
}
|
|
|
|
template <typename CharT>
|
|
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 <typename CharT>
|
|
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(const 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Convert |value| to an integer and clamp it to a valid integer index within
|
|
* the range `[0..length]`.
|
|
*/
|
|
template <typename ArrayLength>
|
|
bool js::ToIntegerIndexSlow(JSContext* cx, Handle<Value> value,
|
|
ArrayLength length, ArrayLength* result) {
|
|
MOZ_ASSERT(!value.isInt32());
|
|
|
|
double relative;
|
|
if (!ToInteger(cx, value, &relative)) {
|
|
return false;
|
|
}
|
|
|
|
if (relative >= 0) {
|
|
*result = ArrayLength(std::min(relative, double(length)));
|
|
} else {
|
|
*result = ArrayLength(std::max(relative + double(length), 0.0));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Dummy type used when `size_t` is the same type as `uint64_t`. Implements
|
|
// constructor and conversion methods called in ToIntegerIndexSlow.
|
|
struct Dummy {
|
|
explicit Dummy(double) {}
|
|
explicit operator double() { return 0; }
|
|
};
|
|
|
|
// Instantiate ToIntegerIndexSlow for `size_t` and `uint64_t`, but avoid a
|
|
// duplicate instantiation error for the case when `size_t` is the same type as
|
|
// `uint64_t`.
|
|
|
|
using Uint64OrDummy =
|
|
std::conditional_t<!std::is_same_v<uint64_t, size_t>, uint64_t, Dummy>;
|
|
|
|
template bool js::ToIntegerIndexSlow<size_t>(JSContext*, Handle<Value>, size_t,
|
|
size_t*);
|
|
|
|
template bool js::ToIntegerIndexSlow<Uint64OrDummy>(JSContext*, Handle<Value>,
|
|
Uint64OrDummy,
|
|
Uint64OrDummy*);
|
|
|
|
template <typename CharT>
|
|
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<int>(length);
|
|
double d;
|
|
int processed = 0;
|
|
if constexpr (std::is_same_v<CharT, char16_t>) {
|
|
d = converter.StringToDouble(reinterpret_cast<const uc16*>(s), lengthInt,
|
|
&processed);
|
|
} else {
|
|
static_assert(std::is_same_v<CharT, Latin1Char>);
|
|
d = converter.StringToDouble(reinterpret_cast<const char*>(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<double>() : PositiveInfinity<double>();
|
|
}
|
|
}
|
|
|
|
*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);
|