/* -*- 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/. */ #include "builtin/intl/IntlMathematicalValue.h" #include "mozilla/Assertions.h" #include "mozilla/FloatingPoint.h" #include "mozilla/Range.h" #include "mozilla/Span.h" #include "mozilla/TextUtils.h" #include #include #include "jspubtd.h" #include "builtin/Number.h" #include "js/CharacterEncoding.h" #include "js/GCAPI.h" #include "js/TracingAPI.h" #include "util/Text.h" #include "vm/BigIntType.h" #include "vm/JSContext.h" #include "vm/JSObject.h" #include "vm/StringType.h" using namespace js; using namespace js::intl; void js::intl::IntlMathematicalValue::trace(JSTracer* trc) { JS::TraceRoot(trc, &value_, "IntlMathematicalValue::value"); } void js::intl::IntlMathematicalValueString::trace(JSTracer* trc) { TraceNullableRoot(trc, &string_, "IntlMathematicalValueString::string"); } bool js::intl::IntlMathematicalValue::isRepresentableAsDouble( double* result) const { if (value_.isNumber()) { *result = value_.toNumber(); return true; } if (value_.isBigInt()) { int64_t i64; if (JS::BigInt::isInt64(value_.toBigInt(), &i64) && i64 < int64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT) && i64 > -int64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT)) { *result = double(i64); return true; } } return false; } JSLinearString* js::intl::IntlMathematicalValue::toLinearString( JSContext* cx) const { if (value_.isInt32()) { return Int32ToString(cx, value_.toInt32()); } if (value_.isDouble()) { // Special case to preserve negative zero. if (mozilla::IsNegativeZero(value_.toDouble())) { constexpr std::string_view negativeZero = "-0"; return NewStringCopy(cx, negativeZero); } auto* str = NumberToString(cx, value_.toNumber()); if (!str) { return nullptr; } return str->ensureLinear(cx); } if (value_.isBigInt()) { Rooted bigInt(cx, value_.toBigInt()); return BigInt::toString(cx, bigInt, 10); } return value_.toString()->ensureLinear(cx); }; // Return the number part of the input by removing leading and trailing // whitespace. template static mozilla::Span NumberPart(mozilla::Span chars) { const CharT* start = chars.data(); const CharT* end = chars.data() + chars.size(); start = SkipSpace(start, end); // |SkipSpace| only supports forward iteration, so inline the backwards // iteration here. MOZ_ASSERT(start <= end); while (end > start && unicode::IsSpace(end[-1])) { end--; } // The number part is a non-empty, ASCII-only substring. MOZ_ASSERT(start < end); MOZ_ASSERT(mozilla::IsAscii(mozilla::Span(start, end))); return {start, end}; } IntlMathematicalValueStringView js::intl::IntlMathematicalValueString::asView( JSContext* cx, const JS::AutoCheckCannotGC& nogc) const { MOZ_ASSERT(string_ != nullptr); if (string_->hasLatin1Chars()) { auto span = NumberPart(mozilla::AsChars(string_->latin1Range(nogc))); auto view = std::string_view{span.data(), span.size()}; return IntlMathematicalValueStringView{view}; } auto span = NumberPart(mozilla::Span{string_->twoByteRange(nogc)}); JS::UniqueChars latin1{ JS::LossyTwoByteCharsToNewLatin1CharsZ(cx, span).c_str()}; if (!latin1) { return IntlMathematicalValueStringView{}; } auto view = std::string_view{latin1.get(), span.size()}; return IntlMathematicalValueStringView{view, std::move(latin1)}; } // Return true if the string starts with "0[bBoOxX]", possibly skipping over // leading whitespace. template static bool IsNonDecimalNumber(mozilla::Range chars) { const CharT* end = chars.end().get(); const CharT* start = SkipSpace(chars.begin().get(), end); if (end - start >= 2 && start[0] == '0') { CharT ch = start[1]; return ch == 'b' || ch == 'B' || ch == 'o' || ch == 'O' || ch == 'x' || ch == 'X'; } return false; } static bool IsNonDecimalNumber(const JSLinearString* str) { JS::AutoCheckCannotGC nogc; return str->hasLatin1Chars() ? IsNonDecimalNumber(str->latin1Range(nogc)) : IsNonDecimalNumber(str->twoByteRange(nogc)); } /** * 15.5.16 ToIntlMathematicalValue ( value ) * * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6 */ static bool ToIntlMathematicalValue(JSContext* cx, JS::MutableHandle value) { // Step 1. if (!ToPrimitive(cx, JSTYPE_NUMBER, value)) { return false; } // Step 2. if (value.isBigInt()) { return true; } // Step 4. if (!value.isString()) { // Step 4.a. (Steps 4.b-10 not applicable in our implementation.) return ToNumber(cx, value); } // Step 3. auto* str = value.toString()->ensureLinear(cx); if (!str) { return false; } // Steps 5-6, 8, and 9.a. double number = LinearStringToNumber(str); // Step 7. if (std::isnan(number)) { // Set to NaN if the input can't be parsed as a number. value.setNaN(); return true; } // Step 9. if (number == 0.0 || std::isinf(number)) { // Step 9.a. (Reordered) // Steps 9.b-e. value.setDouble(number); return true; } // Step 10. if (IsNonDecimalNumber(str)) { // ICU doesn't accept non-decimal numbers, so we have to convert the input // into a base-10 string. MOZ_ASSERT(!mozilla::IsNegative(number), "non-decimal numbers can't be negative"); if (number < DOUBLE_INTEGRAL_PRECISION_LIMIT) { // Fast-path if we can guarantee there was no loss of precision. value.setDouble(number); } else { // For the slow-path convert the string into a BigInt. // StringToBigInt can't fail (other than OOM) when StringToNumber already // succeeded. JS::Rooted rooted(cx, str); JS::BigInt* bi; JS_TRY_VAR_OR_RETURN_FALSE(cx, bi, StringToBigInt(cx, rooted)); MOZ_ASSERT(bi); value.setBigInt(bi); } } return true; } bool js::intl::ToIntlMathematicalValue( JSContext* cx, JS::Handle value, JS::MutableHandle result) { JS::Rooted val(cx, value); if (!::ToIntlMathematicalValue(cx, &val)) { return false; } result.set(IntlMathematicalValue{val}); return true; }