| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * 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/double-conversion.h" |
| #include "mozilla/FloatingPoint.h" |
| #include "mozilla/PodOperations.h" |
| #include "mozilla/RangedPtr.h" |
| |
| #ifdef HAVE_LOCALECONV |
| #include <locale.h> |
| #endif |
| #include <math.h> |
| #include <string.h> |
| |
| #include "jsatom.h" |
| #include "jscntxt.h" |
| #include "jsdtoa.h" |
| #include "jsobj.h" |
| #include "jsstr.h" |
| #include "jstypes.h" |
| |
| #include "js/Conversions.h" |
| #include "vm/GlobalObject.h" |
| #include "vm/StringBuffer.h" |
| |
| #include "jsatominlines.h" |
| |
| #include "vm/NativeObject-inl.h" |
| #include "vm/NumberObject-inl.h" |
| #include "vm/String-inl.h" |
| |
| using namespace js; |
| |
| using mozilla::Abs; |
| using mozilla::ArrayLength; |
| using mozilla::MinNumberValue; |
| using mozilla::NegativeInfinity; |
| using mozilla::PodCopy; |
| using mozilla::PositiveInfinity; |
| using mozilla::RangedPtr; |
| |
| using JS::AutoCheckCannotGC; |
| using JS::GenericNaN; |
| using JS::ToInt8; |
| using JS::ToInt16; |
| using JS::ToInt32; |
| using JS::ToInt64; |
| using JS::ToUint32; |
| using JS::ToUint64; |
| |
| /* |
| * If we're accumulating a decimal number and the number is >= 2^53, then the |
| * fast result from the loop in Get{Prefix,Decimal}Integer may be inaccurate. |
| * Call js_strtod_harder to get the correct answer. |
| */ |
| template <typename CharT> |
| static bool |
| ComputeAccurateDecimalInteger(ExclusiveContext* cx, const CharT* start, const CharT* end, |
| double* dp) |
| { |
| size_t length = end - start; |
| ScopedJSFreePtr<char> cstr(cx->pod_malloc<char>(length + 1)); |
| if (!cstr) |
| return false; |
| |
| for (size_t i = 0; i < length; i++) { |
| char c = char(start[i]); |
| MOZ_ASSERT(('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')); |
| cstr[i] = c; |
| } |
| cstr[length] = 0; |
| |
| char* estr; |
| int err = 0; |
| *dp = js_strtod_harder(cx->dtoaState(), cstr, &estr, &err); |
| if (err == JS_DTOA_ENOMEM) { |
| ReportOutOfMemory(cx); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| 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* start; /* Pointer to the remaining digits */ |
| const CharT* end; /* Pointer to first non-digit */ |
| |
| public: |
| BinaryDigitReader(int base, const CharT* start, const CharT* end) |
| : base(base), digit(0), digitMask(0), start(start), end(end) |
| { |
| } |
| |
| /* Return the next binary digit from the number, or -1 if done. */ |
| int nextDigit() { |
| if (digitMask == 0) { |
| if (start == end) |
| return -1; |
| |
| int c = *start++; |
| MOZ_ASSERT(('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')); |
| if ('0' <= c && c <= '9') |
| digit = c - '0'; |
| else if ('a' <= c && c <= 'z') |
| digit = c - 'a' + 10; |
| else |
| digit = c - 'A' + 10; |
| 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.start(), 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> |
| bool |
| js::GetPrefixInteger(ExclusiveContext* cx, const CharT* start, const CharT* end, int base, |
| 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++) { |
| int digit; |
| CharT c = *s; |
| if ('0' <= c && c <= '9') |
| digit = c - '0'; |
| else if ('a' <= c && c <= 'z') |
| digit = c - 'a' + 10; |
| else if ('A' <= c && c <= 'Z') |
| digit = c - 'A' + 10; |
| else |
| break; |
| 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 15.1.2.2 step 13. |
| */ |
| if (base == 10) |
| return ComputeAccurateDecimalInteger(cx, start, s, dp); |
| |
| if ((base & (base - 1)) == 0) |
| *dp = ComputeAccurateBinaryBaseInteger(start, s, base); |
| |
| return true; |
| } |
| |
| template bool |
| js::GetPrefixInteger(ExclusiveContext* cx, const char16_t* start, const char16_t* end, int base, |
| const char16_t** endp, double* dp); |
| |
| template bool |
| js::GetPrefixInteger(ExclusiveContext* cx, const Latin1Char* start, const Latin1Char* end, |
| int base, const Latin1Char** endp, double* dp); |
| |
| bool |
| js::GetDecimalInteger(ExclusiveContext* cx, const char16_t* start, const char16_t* end, double* dp) |
| { |
| MOZ_ASSERT(start <= end); |
| |
| const char16_t* s = start; |
| double d = 0.0; |
| for (; s < end; s++) { |
| char16_t c = *s; |
| MOZ_ASSERT('0' <= c && c <= '9'); |
| int digit = c - '0'; |
| d = d * 10 + digit; |
| } |
| |
| *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. |
| return ComputeAccurateDecimalInteger(cx, start, s, dp); |
| } |
| |
| static bool |
| num_parseFloat(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() == 0) { |
| args.rval().setNaN(); |
| return true; |
| } |
| |
| JSString* str = ToString<CanGC>(cx, args[0]); |
| if (!str) |
| return false; |
| |
| 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; |
| if (!js_strtod(cx, begin, begin + linear->length(), &end, &d)) |
| return false; |
| if (end == begin) |
| d = GenericNaN(); |
| } else { |
| const char16_t* begin = linear->twoByteChars(nogc); |
| const char16_t* end; |
| if (!js_strtod(cx, begin, begin + linear->length(), &end, &d)) |
| return false; |
| if (end == begin) |
| d = GenericNaN(); |
| } |
| |
| args.rval().setDouble(d); |
| return true; |
| } |
| |
| 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 (!GetPrefixInteger(cx, s, end, radix, &actualEnd, &d)) |
| return false; |
| |
| if (s == actualEnd) |
| *res = GenericNaN(); |
| else |
| *res = negative ? -d : d; |
| return true; |
| } |
| |
| /* ES5 15.1.2.2. */ |
| bool |
| js::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 (1.0e-6 < d && d < 1.0e21) { |
| args.rval().setNumber(floor(d)); |
| return true; |
| } |
| if (-1.0e21 < d && d < -1.0e-6) { |
| args.rval().setNumber(-floor(-d)); |
| return true; |
| } |
| if (d == 0.0) { |
| args.rval().setInt32(0); |
| return true; |
| } |
| } |
| } |
| |
| /* Step 1. */ |
| RootedString inputString(cx, ToString<CanGC>(cx, args[0])); |
| if (!inputString) |
| return false; |
| args[0].setString(inputString); |
| |
| /* Steps 6-9. */ |
| bool stripPrefix = true; |
| int32_t radix; |
| if (!args.hasDefined(1)) { |
| radix = 10; |
| } else { |
| if (!ToInt32(cx, args[1], &radix)) |
| return false; |
| if (radix == 0) { |
| radix = 10; |
| } else { |
| if (radix < 2 || radix > 36) { |
| args.rval().setNaN(); |
| return true; |
| } |
| if (radix != 16) |
| stripPrefix = false; |
| } |
| } |
| |
| JSLinearString* linear = inputString->ensureLinear(cx); |
| if (!linear) |
| return false; |
| |
| AutoCheckCannotGC nogc; |
| size_t length = inputString->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; |
| } |
| |
| args.rval().setNumber(number); |
| return true; |
| } |
| |
| static const JSFunctionSpec number_functions[] = { |
| JS_SELF_HOSTED_FN(js_isNaN_str, "Global_isNaN", 1, JSPROP_RESOLVING), |
| JS_SELF_HOSTED_FN(js_isFinite_str, "Global_isFinite", 1, JSPROP_RESOLVING), |
| JS_FS_END |
| }; |
| |
| const Class NumberObject::class_ = { |
| js_Number_str, |
| JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_HAS_CACHED_PROTO(JSProto_Number) |
| }; |
| |
| static bool |
| Number(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| /* Sample JS_CALLEE before clobbering. */ |
| bool isConstructing = args.isConstructing(); |
| |
| if (args.length() > 0) { |
| if (!ToNumber(cx, args[0])) |
| return false; |
| args.rval().set(args[0]); |
| } else { |
| args.rval().setInt32(0); |
| } |
| |
| if (!isConstructing) |
| return true; |
| |
| RootedObject newTarget(cx, &args.newTarget().toObject()); |
| RootedObject proto(cx); |
| if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) |
| return false; |
| JSObject* obj = NumberObject::create(cx, args.rval().toNumber(), proto); |
| if (!obj) |
| return false; |
| args.rval().setObject(*obj); |
| return true; |
| } |
| |
| MOZ_ALWAYS_INLINE bool |
| IsNumber(HandleValue v) |
| { |
| return v.isNumber() || (v.isObject() && v.toObject().is<NumberObject>()); |
| } |
| |
| static inline double |
| Extract(const Value& v) |
| { |
| if (v.isNumber()) |
| return v.toNumber(); |
| return v.toObject().as<NumberObject>().unbox(); |
| } |
| |
| #if JS_HAS_TOSOURCE |
| MOZ_ALWAYS_INLINE bool |
| num_toSource_impl(JSContext* cx, const CallArgs& args) |
| { |
| double d = Extract(args.thisv()); |
| |
| StringBuffer sb(cx); |
| if (!sb.append("(new Number(") || |
| !NumberValueToStringBuffer(cx, NumberValue(d), sb) || |
| !sb.append("))")) |
| { |
| return false; |
| } |
| |
| JSString* str = sb.finishString(); |
| if (!str) |
| return false; |
| args.rval().setString(str); |
| return true; |
| } |
| |
| static bool |
| num_toSource(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<IsNumber, num_toSource_impl>(cx, args); |
| } |
| #endif |
| |
| ToCStringBuf::ToCStringBuf() : dbuf(nullptr) |
| { |
| static_assert(sbufSize >= DTOSTR_STANDARD_BUFFER_SIZE, |
| "builtin space must be large enough to store even the " |
| "longest string produced by a conversion"); |
| } |
| |
| ToCStringBuf::~ToCStringBuf() |
| { |
| js_free(dbuf); |
| } |
| |
| MOZ_ALWAYS_INLINE |
| static JSFlatString* |
| LookupDtoaCache(ExclusiveContext* cx, double d) |
| { |
| if (JSCompartment* comp = cx->compartment()) { |
| if (JSFlatString* str = comp->dtoaCache.lookup(10, d)) |
| return str; |
| } |
| |
| return nullptr; |
| } |
| |
| MOZ_ALWAYS_INLINE |
| static void |
| CacheNumber(ExclusiveContext* cx, double d, JSFlatString* str) |
| { |
| if (JSCompartment* comp = cx->compartment()) |
| comp->dtoaCache.cache(10, d, str); |
| } |
| |
| MOZ_ALWAYS_INLINE |
| static JSFlatString* |
| LookupInt32ToString(ExclusiveContext* cx, int32_t si) |
| { |
| if (si >= 0 && StaticStrings::hasInt(si)) |
| return cx->staticStrings().getInt(si); |
| |
| return LookupDtoaCache(cx, si); |
| } |
| |
| template <typename T> |
| 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<T> end(buffer + size - 1, buffer, size); |
| *end = '\0'; |
| RangedPtr<T> start = BackfillIndexInCharBuffer(ui, end); |
| if (si < 0) |
| *--start = '-'; |
| |
| *length = end - start; |
| return start.get(); |
| } |
| |
| template <AllowGC allowGC> |
| JSFlatString* |
| js::Int32ToString(ExclusiveContext* cx, int32_t si) |
| { |
| if (JSFlatString* str = LookupInt32ToString(cx, si)) |
| return str; |
| |
| Latin1Char buffer[JSFatInlineString::MAX_LENGTH_LATIN1 + 1]; |
| size_t length; |
| Latin1Char* start = BackfillInt32InBuffer(si, buffer, ArrayLength(buffer), &length); |
| |
| mozilla::Range<const Latin1Char> chars(start, length); |
| JSInlineString* str = NewInlineString<allowGC>(cx, chars); |
| if (!str) |
| return nullptr; |
| |
| CacheNumber(cx, si, str); |
| return str; |
| } |
| |
| template JSFlatString* |
| js::Int32ToString<CanGC>(ExclusiveContext* cx, int32_t si); |
| |
| template JSFlatString* |
| js::Int32ToString<NoGC>(ExclusiveContext* cx, int32_t si); |
| |
| JSAtom* |
| js::Int32ToAtom(ExclusiveContext* cx, int32_t si) |
| { |
| if (JSFlatString* 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); |
| |
| JSAtom* atom = Atomize(cx, start, length); |
| if (!atom) |
| return nullptr; |
| |
| CacheNumber(cx, si, atom); |
| return atom; |
| } |
| |
| /* Returns a non-nullptr pointer to inside cbuf. */ |
| static char* |
| Int32ToCString(ToCStringBuf* cbuf, int32_t i, size_t* len, int base = 10) |
| { |
| uint32_t u = Abs(i); |
| |
| RangedPtr<char> cp(cbuf->sbuf + ToCStringBuf::sbufSize - 1, cbuf->sbuf, ToCStringBuf::sbufSize); |
| 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 (i < 0) |
| *--cp = '-'; |
| |
| *len = end - cp.get(); |
| return cp.get(); |
| } |
| |
| template <AllowGC allowGC> |
| static JSString* |
| NumberToStringWithBase(ExclusiveContext* cx, double d, int base); |
| |
| MOZ_ALWAYS_INLINE bool |
| num_toString_impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(IsNumber(args.thisv())); |
| |
| double d = Extract(args.thisv()); |
| |
| int32_t base = 10; |
| if (args.hasDefined(0)) { |
| double d2; |
| if (!ToInteger(cx, args[0], &d2)) |
| return false; |
| |
| if (d2 < 2 || d2 > 36) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_RADIX); |
| return false; |
| } |
| |
| base = int32_t(d2); |
| } |
| JSString* str = NumberToStringWithBase<CanGC>(cx, d, base); |
| if (!str) { |
| JS_ReportOutOfMemory(cx); |
| return false; |
| } |
| args.rval().setString(str); |
| return true; |
| } |
| |
| bool |
| js::num_toString(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<IsNumber, num_toString_impl>(cx, args); |
| } |
| |
| #if !EXPOSE_INTL_API |
| MOZ_ALWAYS_INLINE bool |
| num_toLocaleString_impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(IsNumber(args.thisv())); |
| |
| double d = Extract(args.thisv()); |
| |
| RootedString str(cx, NumberToStringWithBase<CanGC>(cx, d, 10)); |
| if (!str) { |
| JS_ReportOutOfMemory(cx); |
| return false; |
| } |
| |
| /* |
| * Create the string, move back to bytes to make string twiddling |
| * a bit easier and so we can insert platform charset seperators. |
| */ |
| JSAutoByteString numBytes(cx, str); |
| if (!numBytes) |
| return false; |
| const char* num = numBytes.ptr(); |
| 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; |
| } |
| |
| static bool |
| num_toLocaleString(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<IsNumber, num_toLocaleString_impl>(cx, args); |
| } |
| #endif /* !EXPOSE_INTL_API */ |
| |
| MOZ_ALWAYS_INLINE bool |
| num_valueOf_impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(IsNumber(args.thisv())); |
| args.rval().setNumber(Extract(args.thisv())); |
| return true; |
| } |
| |
| bool |
| js::num_valueOf(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<IsNumber, num_valueOf_impl>(cx, args); |
| } |
| |
| static const unsigned MAX_PRECISION = 100; |
| |
| static bool |
| ComputePrecisionInRange(JSContext* cx, int minPrecision, int maxPrecision, HandleValue v, |
| int* precision) |
| { |
| double prec; |
| if (!ToInteger(cx, v, &prec)) |
| return false; |
| if (minPrecision <= prec && prec <= maxPrecision) { |
| *precision = int(prec); |
| return true; |
| } |
| |
| ToCStringBuf cbuf; |
| if (char* numStr = NumberToCString(cx, &cbuf, prec, 10)) |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_PRECISION_RANGE, numStr); |
| return false; |
| } |
| |
| static bool |
| DToStrResult(JSContext* cx, double d, JSDToStrMode mode, int precision, const CallArgs& args) |
| { |
| char buf[DTOSTR_VARIABLE_BUFFER_SIZE(MAX_PRECISION + 1)]; |
| char* numStr = js_dtostr(cx->mainThread().dtoaState, buf, sizeof buf, mode, precision, d); |
| if (!numStr) { |
| JS_ReportOutOfMemory(cx); |
| return false; |
| } |
| JSString* str = NewStringCopyZ<CanGC>(cx, numStr); |
| if (!str) |
| return false; |
| args.rval().setString(str); |
| return true; |
| } |
| |
| /* |
| * In the following three implementations, we allow a larger range of precision |
| * than ECMA requires; this is permitted by ECMA-262. |
| */ |
| MOZ_ALWAYS_INLINE bool |
| num_toFixed_impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(IsNumber(args.thisv())); |
| |
| int precision; |
| if (args.length() == 0) { |
| precision = 0; |
| } else { |
| if (!ComputePrecisionInRange(cx, -20, MAX_PRECISION, args[0], &precision)) |
| return false; |
| } |
| |
| return DToStrResult(cx, Extract(args.thisv()), DTOSTR_FIXED, precision, args); |
| } |
| |
| static bool |
| num_toFixed(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<IsNumber, num_toFixed_impl>(cx, args); |
| } |
| |
| MOZ_ALWAYS_INLINE bool |
| num_toExponential_impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(IsNumber(args.thisv())); |
| |
| JSDToStrMode mode; |
| int precision; |
| if (!args.hasDefined(0)) { |
| mode = DTOSTR_STANDARD_EXPONENTIAL; |
| precision = 0; |
| } else { |
| mode = DTOSTR_EXPONENTIAL; |
| if (!ComputePrecisionInRange(cx, 0, MAX_PRECISION, args[0], &precision)) |
| return false; |
| } |
| |
| return DToStrResult(cx, Extract(args.thisv()), mode, precision + 1, args); |
| } |
| |
| static bool |
| num_toExponential(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<IsNumber, num_toExponential_impl>(cx, args); |
| } |
| |
| MOZ_ALWAYS_INLINE bool |
| num_toPrecision_impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(IsNumber(args.thisv())); |
| |
| double d = Extract(args.thisv()); |
| |
| if (!args.hasDefined(0)) { |
| JSString* str = NumberToStringWithBase<CanGC>(cx, d, 10); |
| if (!str) { |
| JS_ReportOutOfMemory(cx); |
| return false; |
| } |
| args.rval().setString(str); |
| return true; |
| } |
| |
| int precision; |
| if (!ComputePrecisionInRange(cx, 1, MAX_PRECISION, args[0], &precision)) |
| return false; |
| |
| return DToStrResult(cx, d, DTOSTR_PRECISION, precision, args); |
| } |
| |
| static bool |
| num_toPrecision(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<IsNumber, num_toPrecision_impl>(cx, args); |
| } |
| |
| static const JSFunctionSpec number_methods[] = { |
| #if JS_HAS_TOSOURCE |
| JS_FN(js_toSource_str, num_toSource, 0, 0), |
| #endif |
| JS_FN(js_toString_str, num_toString, 1, 0), |
| #if EXPOSE_INTL_API |
| JS_SELF_HOSTED_FN(js_toLocaleString_str, "Number_toLocaleString", 0,0), |
| #else |
| JS_FN(js_toLocaleString_str, num_toLocaleString, 0,0), |
| #endif |
| JS_FN(js_valueOf_str, 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 |
| }; |
| |
| // ES6 draft ES6 15.7.3.12 |
| static bool |
| Number_isInteger(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() < 1 || !args[0].isNumber()) { |
| args.rval().setBoolean(false); |
| return true; |
| } |
| Value val = args[0]; |
| args.rval().setBoolean(val.isInt32() || |
| (mozilla::IsFinite(val.toDouble()) && |
| JS::ToInteger(val.toDouble()) == val.toDouble())); |
| return true; |
| } |
| |
| |
| static const JSFunctionSpec number_static_methods[] = { |
| JS_SELF_HOSTED_FN("isFinite", "Number_isFinite", 1,0), |
| JS_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 |
| }; |
| |
| |
| /* |
| * Set the exception mask to mask all exceptions and set the FPU precision |
| * to 53 bit mantissa (64 bit doubles). |
| */ |
| void |
| js::FIX_FPU() |
| { |
| #if (defined __GNUC__ && defined __i386__) || \ |
| (defined __SUNPRO_CC && defined __i386) |
| short control; |
| asm("fstcw %0" : "=m" (control) : ); |
| control &= ~0x300; // Lower bits 8 and 9 (precision control). |
| control |= 0x2f3; // Raise bits 0-5 (exception masks) and 9 (64-bit precision). |
| asm("fldcw %0" : : "m" (control) ); |
| #endif |
| } |
| |
| bool |
| js::InitRuntimeNumberState(JSRuntime* rt) |
| { |
| FIX_FPU(); |
| |
| // XXX If EXPOSE_INTL_API becomes true all the time at some point, |
| // js::InitRuntimeNumberState is no longer fallible, and we should |
| // change its return type. |
| #if !EXPOSE_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 = js_sb_getenv("LOCALE_THOUSANDS_SEP"); |
| decimalPoint = js_sb_getenv("LOCALE_DECIMAL_POINT"); |
| grouping = js_sb_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 /* !EXPOSE_INTL_API */ |
| return true; |
| } |
| |
| #if !EXPOSE_INTL_API |
| void |
| js::FinishRuntimeNumberState(JSRuntime* rt) |
| { |
| /* |
| * The free also releases the memory for decimalSeparator and numGrouping |
| * strings. |
| */ |
| char* storage = const_cast<char*>(rt->thousandsSeparator); |
| js_free(storage); |
| } |
| #endif |
| |
| JSObject* |
| js::InitNumberClass(JSContext* cx, HandleObject obj) |
| { |
| MOZ_ASSERT(obj->isNative()); |
| |
| /* XXX must do at least once per new thread, so do it per JSContext... */ |
| FIX_FPU(); |
| |
| Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>()); |
| |
| RootedObject numberProto(cx, global->createBlankPrototype(cx, &NumberObject::class_)); |
| if (!numberProto) |
| return nullptr; |
| numberProto->as<NumberObject>().setPrimitiveValue(0); |
| |
| RootedFunction ctor(cx); |
| ctor = global->createConstructor(cx, Number, cx->names().Number, 1); |
| if (!ctor) |
| return nullptr; |
| |
| if (!LinkConstructorAndPrototype(cx, ctor, numberProto)) |
| return nullptr; |
| |
| /* |
| * Our NaN must be one particular canonical value, because we rely on NaN |
| * encoding for our value representation. See Value.h. |
| */ |
| static JSConstDoubleSpec number_constants[] = { |
| {"NaN", GenericNaN() }, |
| {"POSITIVE_INFINITY", mozilla::PositiveInfinity<double>() }, |
| {"NEGATIVE_INFINITY", mozilla::NegativeInfinity<double>() }, |
| {"MAX_VALUE", 1.7976931348623157E+308 }, |
| {"MIN_VALUE", MinNumberValue<double>() }, |
| /* ES6 (April 2014 draft) 20.1.2.6 */ |
| {"MAX_SAFE_INTEGER", 9007199254740991 }, |
| /* ES6 (April 2014 draft) 20.1.2.10 */ |
| {"MIN_SAFE_INTEGER", -9007199254740991, }, |
| /* ES6 (May 2013 draft) 15.7.3.7 */ |
| {"EPSILON", 2.2204460492503130808472633361816e-16}, |
| {0,0} |
| }; |
| |
| /* Add numeric constants (MAX_VALUE, NaN, &c.) to the Number constructor. */ |
| if (!JS_DefineConstDoubles(cx, ctor, number_constants)) |
| return nullptr; |
| |
| if (!DefinePropertiesAndFunctions(cx, ctor, nullptr, number_static_methods)) |
| return nullptr; |
| |
| if (!DefinePropertiesAndFunctions(cx, numberProto, nullptr, number_methods)) |
| return nullptr; |
| |
| if (!JS_DefineFunctions(cx, global, number_functions)) |
| return nullptr; |
| |
| /* 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 nullptr; |
| RootedValue parseIntValue(cx, ObjectValue(*parseInt)); |
| if (!DefineProperty(cx, ctor, parseIntId, parseIntValue, nullptr, nullptr, 0)) |
| return nullptr; |
| |
| /* 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 nullptr; |
| RootedValue parseFloatValue(cx, ObjectValue(*parseFloat)); |
| if (!DefineProperty(cx, ctor, parseFloatId, parseFloatValue, nullptr, nullptr, 0)) |
| return nullptr; |
| |
| RootedValue valueNaN(cx, cx->runtime()->NaNValue); |
| RootedValue valueInfinity(cx, cx->runtime()->positiveInfinityValue); |
| |
| /* ES5 15.1.1.1, 15.1.1.2 */ |
| if (!NativeDefineProperty(cx, global, cx->names().NaN, valueNaN, nullptr, nullptr, |
| JSPROP_PERMANENT | JSPROP_READONLY | JSPROP_RESOLVING) || |
| !NativeDefineProperty(cx, global, cx->names().Infinity, valueInfinity, nullptr, nullptr, |
| JSPROP_PERMANENT | JSPROP_READONLY | JSPROP_RESOLVING)) |
| { |
| return nullptr; |
| } |
| |
| if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_Number, ctor, numberProto)) |
| return nullptr; |
| |
| return numberProto; |
| } |
| |
| static char* |
| FracNumberToCString(ExclusiveContext* cx, ToCStringBuf* cbuf, double d, int base = 10) |
| { |
| #ifdef DEBUG |
| { |
| int32_t _; |
| MOZ_ASSERT(!mozilla::NumberIsInt32(d, &_)); |
| } |
| #endif |
| |
| char* numStr; |
| if (base == 10) { |
| /* |
| * 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, cbuf->sbufSize); |
| converter.ToShortest(d, &builder); |
| numStr = builder.Finalize(); |
| } else { |
| numStr = cbuf->dbuf = js_dtobasestr(cx->dtoaState(), base, d); |
| } |
| return numStr; |
| } |
| |
| char* |
| js::NumberToCString(JSContext* cx, ToCStringBuf* cbuf, double d, int base/* = 10*/) |
| { |
| int32_t i; |
| size_t len; |
| return mozilla::NumberIsInt32(d, &i) |
| ? Int32ToCString(cbuf, i, &len, base) |
| : FracNumberToCString(cx, cbuf, d, base); |
| } |
| |
| template <AllowGC allowGC> |
| static JSString* |
| NumberToStringWithBase(ExclusiveContext* cx, double d, int base) |
| { |
| ToCStringBuf cbuf; |
| char* numStr; |
| |
| /* |
| * Caller is responsible for error reporting. When called from trace, |
| * returning nullptr here will cause us to fall of trace and then retry |
| * from the interpreter (which will report the error). |
| */ |
| if (base < 2 || base > 36) |
| return nullptr; |
| |
| JSCompartment* comp = cx->compartment(); |
| |
| int32_t i; |
| if (mozilla::NumberIsInt32(d, &i)) { |
| if (base == 10 && StaticStrings::hasInt(i)) |
| return cx->staticStrings().getInt(i); |
| 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); |
| } |
| |
| if (JSFlatString* str = comp->dtoaCache.lookup(base, d)) |
| return str; |
| |
| size_t len; |
| numStr = Int32ToCString(&cbuf, i, &len, base); |
| MOZ_ASSERT(!cbuf.dbuf && numStr >= cbuf.sbuf && numStr < cbuf.sbuf + cbuf.sbufSize); |
| } else { |
| if (JSFlatString* str = comp->dtoaCache.lookup(base, d)) |
| return str; |
| |
| numStr = FracNumberToCString(cx, &cbuf, d, base); |
| if (!numStr) { |
| ReportOutOfMemory(cx); |
| return nullptr; |
| } |
| MOZ_ASSERT_IF(base == 10, |
| !cbuf.dbuf && numStr >= cbuf.sbuf && numStr < cbuf.sbuf + cbuf.sbufSize); |
| MOZ_ASSERT_IF(base != 10, |
| cbuf.dbuf && cbuf.dbuf == numStr); |
| } |
| |
| JSFlatString* s = NewStringCopyZ<allowGC>(cx, numStr); |
| |
| comp->dtoaCache.cache(base, d, s); |
| return s; |
| } |
| |
| template <AllowGC allowGC> |
| JSString* |
| js::NumberToString(ExclusiveContext* cx, double d) |
| { |
| return NumberToStringWithBase<allowGC>(cx, d, 10); |
| } |
| |
| template JSString* |
| js::NumberToString<CanGC>(ExclusiveContext* cx, double d); |
| |
| template JSString* |
| js::NumberToString<NoGC>(ExclusiveContext* cx, double d); |
| |
| JSAtom* |
| js::NumberToAtom(ExclusiveContext* cx, double d) |
| { |
| int32_t si; |
| if (mozilla::NumberIsInt32(d, &si)) |
| return Int32ToAtom(cx, si); |
| |
| if (JSFlatString* str = LookupDtoaCache(cx, d)) |
| return AtomizeString(cx, str); |
| |
| ToCStringBuf cbuf; |
| char* numStr = FracNumberToCString(cx, &cbuf, d); |
| if (!numStr) { |
| ReportOutOfMemory(cx); |
| return nullptr; |
| } |
| MOZ_ASSERT(!cbuf.dbuf && numStr >= cbuf.sbuf && numStr < cbuf.sbuf + cbuf.sbufSize); |
| |
| size_t length = strlen(numStr); |
| JSAtom* atom = Atomize(cx, numStr, length); |
| if (!atom) |
| return nullptr; |
| |
| CacheNumber(cx, d, atom); |
| |
| return atom; |
| } |
| |
| JSFlatString* |
| js::NumberToString(JSContext* cx, double d) |
| { |
| if (JSString* str = NumberToStringWithBase<CanGC>(cx, d, 10)) |
| return &str->asFlat(); |
| return nullptr; |
| } |
| |
| JSFlatString* |
| js::IndexToString(JSContext* cx, uint32_t index) |
| { |
| if (StaticStrings::hasUint(index)) |
| return cx->staticStrings().getUint(index); |
| |
| JSCompartment* c = cx->compartment(); |
| if (JSFlatString* str = c->dtoaCache.lookup(10, index)) |
| return str; |
| |
| Latin1Char buffer[JSFatInlineString::MAX_LENGTH_LATIN1 + 1]; |
| RangedPtr<Latin1Char> end(buffer + JSFatInlineString::MAX_LENGTH_LATIN1, |
| buffer, JSFatInlineString::MAX_LENGTH_LATIN1 + 1); |
| *end = '\0'; |
| RangedPtr<Latin1Char> start = BackfillIndexInCharBuffer(index, end); |
| |
| mozilla::Range<const Latin1Char> chars(start.get(), end - start); |
| JSInlineString* str = NewInlineString<CanGC>(cx, chars); |
| if (!str) |
| return nullptr; |
| |
| c->dtoaCache.cache(10, index, str); |
| return str; |
| } |
| |
| bool JS_FASTCALL |
| js::NumberValueToStringBuffer(JSContext* cx, 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); |
| MOZ_ASSERT(cstrlen == strlen(cstr)); |
| } else { |
| cstr = NumberToCString(cx, &cbuf, v.toDouble()); |
| if (!cstr) { |
| JS_ReportOutOfMemory(cx); |
| return false; |
| } |
| cstrlen = strlen(cstr); |
| } |
| |
| /* |
| * Inflate to char16_t string. The input C-string characters are < 127, so |
| * even if char16_t units are UTF-8, all chars should map to one char16_t. |
| */ |
| MOZ_ASSERT(!cbuf.dbuf && cstrlen < cbuf.sbufSize); |
| return sb.append(cstr, cstrlen); |
| } |
| |
| template <typename CharT> |
| static bool |
| CharsToNumber(ExclusiveContext* cx, const CharT* chars, size_t length, double* result) |
| { |
| if (length == 1) { |
| CharT c = chars[0]; |
| if ('0' <= c && c <= '9') |
| *result = c - '0'; |
| else if (unicode::IsSpace(c)) |
| *result = 0.0; |
| else |
| *result = GenericNaN(); |
| return true; |
| } |
| |
| const CharT* end = chars + length; |
| const CharT* bp = SkipSpace(chars, end); |
| |
| /* ECMA doesn't allow signed non-decimal numbers (bug 273467). */ |
| if (end - bp >= 2 && bp[0] == '0') { |
| int radix = 0; |
| if (bp[1] == 'b' || bp[1] == 'B') |
| radix = 2; |
| else if (bp[1] == 'o' || bp[1] == 'O') |
| radix = 8; |
| else if (bp[1] == 'x' || bp[1] == 'X') |
| radix = 16; |
| |
| if (radix != 0) { |
| /* |
| * 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; |
| if (!GetPrefixInteger(cx, bp + 2, end, radix, &endptr, &d) || |
| endptr == bp + 2 || |
| SkipSpace(endptr, end) != end) |
| { |
| *result = GenericNaN(); |
| } else { |
| *result = d; |
| } |
| return true; |
| } |
| } |
| |
| /* |
| * 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; |
| if (!js_strtod(cx, bp, end, &ep, &d)) { |
| *result = GenericNaN(); |
| return false; |
| } |
| |
| if (SkipSpace(ep, end) != end) |
| *result = GenericNaN(); |
| else |
| *result = d; |
| |
| return true; |
| } |
| |
| bool |
| js::StringToNumber(ExclusiveContext* cx, JSString* str, double* result) |
| { |
| AutoCheckCannotGC nogc; |
| JSLinearString* linearStr = str->ensureLinear(cx); |
| if (!linearStr) |
| return false; |
| |
| return linearStr->hasLatin1Chars() |
| ? CharsToNumber(cx, linearStr->latin1Chars(nogc), str->length(), result) |
| : CharsToNumber(cx, linearStr->twoByteChars(nogc), str->length(), result); |
| } |
| |
| bool |
| js::ToNumberSlow(ExclusiveContext* cx, Value v, double* out) |
| { |
| MOZ_ASSERT(!v.isNumber()); |
| goto skip_int_double; |
| for (;;) { |
| if (v.isNumber()) { |
| *out = v.toNumber(); |
| return true; |
| } |
| |
| skip_int_double: |
| if (!v.isObject()) { |
| 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.isSymbol()) { |
| if (cx->isJSContext()) { |
| JS_ReportErrorNumber(cx->asJSContext(), GetErrorMessage, nullptr, |
| JSMSG_SYMBOL_TO_NUMBER); |
| } |
| return false; |
| } |
| |
| MOZ_ASSERT(v.isUndefined()); |
| *out = GenericNaN(); |
| return true; |
| } |
| |
| if (!cx->isJSContext()) |
| return false; |
| |
| RootedValue v2(cx, v); |
| if (!ToPrimitive(cx->asJSContext(), JSTYPE_NUMBER, &v2)) |
| return false; |
| v = v2; |
| if (v.isObject()) |
| break; |
| } |
| |
| *out = GenericNaN(); |
| return true; |
| } |
| |
| JS_PUBLIC_API(bool) |
| js::ToNumberSlow(JSContext* cx, Value v, double* out) |
| { |
| return ToNumberSlow(static_cast<ExclusiveContext*>(cx), v, out); |
| } |
| |
| /* |
| * 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 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; |
| } |
| |
| 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; |
| } |
| |
| if (d == 0 || !mozilla::IsFinite(d)) { |
| *out = 0; |
| return true; |
| } |
| |
| uint16_t u = (uint16_t) d; |
| if ((double)u == d) { |
| *out = u; |
| return true; |
| } |
| |
| bool neg = (d < 0); |
| d = floor(neg ? -d : d); |
| d = neg ? -d : d; |
| unsigned m = JS_BIT(16); |
| d = fmod(d, (double) m); |
| if (d < 0) |
| d += m; |
| *out = (uint16_t) d; |
| return true; |
| } |
| |
| template<typename T> |
| bool |
| js::ToLengthClamped(T* cx, HandleValue v, uint32_t* out, bool* overflow) |
| { |
| if (v.isInt32()) { |
| int32_t i = v.toInt32(); |
| *out = i < 0 ? 0 : i; |
| return true; |
| } |
| double d; |
| if (v.isDouble()) { |
| d = v.toDouble(); |
| } else { |
| if (!ToNumber(cx, v, &d)) { |
| *overflow = false; |
| return false; |
| } |
| } |
| d = JS::ToInteger(d); |
| if (d <= 0.0) { |
| *out = 0; |
| return true; |
| } |
| if (d >= (double)0xFFFFFFFEU) { |
| *overflow = true; |
| return false; |
| } |
| *out = (uint32_t)d; |
| return true; |
| } |
| |
| template bool |
| js::ToLengthClamped<JSContext>(JSContext*, HandleValue, uint32_t*, bool*); |
| template bool |
| js::ToLengthClamped<ExclusiveContext>(ExclusiveContext*, HandleValue, uint32_t*, bool*); |
| |
| template <typename CharT> |
| bool |
| js_strtod(ExclusiveContext* cx, const CharT* begin, const CharT* end, const CharT** dEnd, |
| double* d) |
| { |
| const CharT* s = SkipSpace(begin, end); |
| size_t length = end - s; |
| |
| Vector<char, 32> chars(cx); |
| if (!chars.growByUninitialized(length + 1)) |
| return false; |
| |
| size_t i = 0; |
| for (; i < length; i++) { |
| char16_t c = s[i]; |
| if (c >> 8) |
| break; |
| chars[i] = char(c); |
| } |
| chars[i] = 0; |
| |
| /* Try to parse +Infinity, -Infinity or Infinity. */ |
| { |
| char* afterSign = chars.begin(); |
| bool negative = (*afterSign == '-'); |
| if (negative || *afterSign == '+') |
| afterSign++; |
| |
| if (*afterSign == 'I' && !strncmp(afterSign, "Infinity", 8)) { |
| *d = negative ? NegativeInfinity<double>() : PositiveInfinity<double>(); |
| *dEnd = s + (afterSign - chars.begin()) + 8; |
| return true; |
| } |
| } |
| |
| /* Everything else. */ |
| int err; |
| char* ep; |
| *d = js_strtod_harder(cx->dtoaState(), chars.begin(), &ep, &err); |
| |
| MOZ_ASSERT(ep >= chars.begin()); |
| |
| if (ep == chars.begin()) |
| *dEnd = begin; |
| else |
| *dEnd = s + (ep - chars.begin()); |
| |
| return true; |
| } |
| |
| template bool |
| js_strtod(ExclusiveContext* cx, const char16_t* begin, const char16_t* end, const char16_t** dEnd, |
| double* d); |
| |
| template bool |
| js_strtod(ExclusiveContext* cx, const Latin1Char* begin, const Latin1Char* end, |
| const Latin1Char** dEnd, double* d); |