From d1195b0984a53baffa76373f2f7dad61f9d70e73 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Mon, 20 Jul 2020 23:58:25 +0000 Subject: [PATCH] Bug 1606568 - Add public BigInt API. r=jwalden Differential Revision: https://phabricator.services.mozilla.com/D82480 --- js/public/BigInt.h | 139 ++++++++++ js/src/jsapi-tests/moz.build | 1 + js/src/jsapi-tests/testBigInt.cpp | 441 ++++++++++++++++++++++++++++++ js/src/moz.build | 1 + js/src/vm/BigIntType.cpp | 98 ++++++- 5 files changed, 678 insertions(+), 2 deletions(-) create mode 100644 js/public/BigInt.h create mode 100644 js/src/jsapi-tests/testBigInt.cpp diff --git a/js/public/BigInt.h b/js/public/BigInt.h new file mode 100644 index 000000000000..d098d44f1487 --- /dev/null +++ b/js/public/BigInt.h @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* BigInt. */ + +#ifndef js_BigInt_h +#define js_BigInt_h + +#include "mozilla/Range.h" // mozilla::Range + +#include // std::numeric_limits +#include // int64_t, uint64_t +#include // std::enable_if_t, std::{true,false}_type, std::is_{integral,signed,unsigned}_v +#include // std::declval + +#include "jstypes.h" // JS_PUBLIC_API +#include "js/RootingAPI.h" // JS::Handle +#include "js/Value.h" // JS::Value + +struct JS_PUBLIC_API JSContext; + +namespace JS { + +class JS_PUBLIC_API BigInt; + +namespace detail { + +extern JS_PUBLIC_API BigInt* BigIntFromInt64(JSContext* cx, int64_t num); +extern JS_PUBLIC_API BigInt* BigIntFromUint64(JSContext* cx, uint64_t num); +extern JS_PUBLIC_API BigInt* BigIntFromBool(JSContext* cx, bool b); + +template +struct NumberToBigIntConverter; + +template +struct NumberToBigIntConverter< + SignedIntT, + std::enable_if_t && + std::is_signed_v && + std::numeric_limits::digits <= 64>> { + static BigInt* convert(JSContext* cx, SignedIntT num) { + return BigIntFromInt64(cx, num); + } +}; + +template +struct NumberToBigIntConverter< + UnsignedIntT, + std::enable_if_t && + std::is_unsigned_v && + std::numeric_limits::digits <= 64>> { + static BigInt* convert(JSContext* cx, UnsignedIntT num) { + return BigIntFromUint64(cx, num); + } +}; + +template <> +struct NumberToBigIntConverter { + static BigInt* convert(JSContext* cx, bool b) { + return BigIntFromBool(cx, b); + } +}; + +} // namespace detail + +/** + * Create a BigInt from an integer value. All integral types not larger than 64 + * bits in size are supported. + */ +template +extern JS_PUBLIC_API BigInt* NumberToBigInt(JSContext* cx, NumericT val) { + return detail::NumberToBigIntConverter::convert(cx, val); +} + +/** + * Create a BigInt from a floating-point value. If the number isn't integral + * (that is, if it's NaN, an infinity, or contains a fractional component), + * this function returns null and throws an exception. + * + * Passing -0.0 will produce the bigint 0n. + */ +extern JS_PUBLIC_API BigInt* NumberToBigInt(JSContext* cx, double num); + +/** + * Create a BigInt by parsing a string using the ECMAScript StringToBigInt + * algorithm (https://tc39.es/ecma262/#sec-stringtobigint). Latin1 and two-byte + * character ranges are supported. It may be convenient to use + * JS::ConstLatin1Chars or JS::ConstTwoByteChars. + * + * (StringToBigInt performs parsing similar to that performed by the |Number| + * global function when passed a string, but it doesn't allow infinities, + * decimal points, or exponential notation, and neither algorithm allows numeric + * separators or an 'n' suffix character. This fast-and-loose description is + * offered purely as a convenience to the reader: see the specification + * algorithm for exact behavior.) + * + * If parsing fails, this function returns null and throws an exception. + */ +extern JS_PUBLIC_API BigInt* StringToBigInt( + JSContext* cx, mozilla::Range chars); + +extern JS_PUBLIC_API BigInt* StringToBigInt( + JSContext* cx, mozilla::Range chars); + +/** + * Create a BigInt by parsing a string consisting of an optional sign character + * followed by one or more alphanumeric ASCII digits in the provided radix. + * + * If the radix is not in the range [2, 36], or the string fails to parse, this + * function returns null and throws an exception. + */ +extern JS_PUBLIC_API BigInt* SimpleStringToBigInt( + JSContext* cx, mozilla::Span chars, unsigned radix); + +/** + * Convert a JS::Value to a BigInt using the ECMAScript ToBigInt algorithm + * (https://tc39.es/ecma262/#sec-tobigint). + * + * (Note in particular that this will throw if passed a value whose type is + * 'number'. To convert a number to a BigInt, use one of the overloads of + * JS::NumberToBigInt().) + */ +extern JS_PUBLIC_API BigInt* ToBigInt(JSContext* cx, Handle val); + +/** + * Convert the given BigInt, modulo 2**64, to a signed 64-bit integer. + */ +extern JS_PUBLIC_API int64_t ToBigInt64(BigInt* bi); + +/** + * Convert the given BigInt, modulo 2**64, to an unsigned 64-bit integer. + */ +extern JS_PUBLIC_API uint64_t ToBigUint64(BigInt* bi); + +} // namespace JS + +#endif /* js_BigInt_h */ diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build index c8c88423423b..a1fad10ced74 100644 --- a/js/src/jsapi-tests/moz.build +++ b/js/src/jsapi-tests/moz.build @@ -18,6 +18,7 @@ UNIFIED_SOURCES += [ 'testArrayBufferWithUserOwnedContents.cpp', 'testAtomicOperations.cpp', 'testAtomizeUtf8NonAsciiLatin1CodePoint.cpp', + 'testBigInt.cpp', 'testBoundFunction.cpp', 'testBug604087.cpp', 'testCallArgs.cpp', diff --git a/js/src/jsapi-tests/testBigInt.cpp b/js/src/jsapi-tests/testBigInt.cpp new file mode 100644 index 000000000000..c07e52266abc --- /dev/null +++ b/js/src/jsapi-tests/testBigInt.cpp @@ -0,0 +1,441 @@ +/* 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 "mozilla/Range.h" // mozilla::Range +#include "mozilla/Span.h" // mozilla::MakeStringSpan + +#include + +#include "jsapi.h" // JS_IsExceptionPending, JS_StringEqualsLiteral +#include "jsfriendapi.h" // JSMSG_BIGINT_INVALID_SYNTAX + +#include "js/BigInt.h" // JS::{,Number,String,SimpleString}ToBigInt, JS::ToBig{I,Ui}nt64 +#include "js/CharacterEncoding.h" // JS::Const{Latin1,TwoByte}Chars +#include "js/Conversions.h" // JS::ToString +#include "js/ErrorReport.h" // JS::ErrorReportBuilder, JSEXN_SYNTAXERR +#include "js/Exception.h" // JS::StealPendingExceptionStack +#include "js/RootingAPI.h" // JS::Rooted +#include "js/Value.h" // JS::FalseValue, JS::Value + +#include "jsapi-tests/tests.h" +#include "util/Text.h" // js::InflateString + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSString; + +namespace JS { + +class JS_PUBLIC_API BigInt; + +} // namespace JS + +BEGIN_TEST(testToBigInt64) { + JS::Rooted v(cx); + + EVAL("0n", &v); + CHECK(v.isBigInt()); + CHECK(JS::ToBigInt64(v.toBigInt()) == 0); + + EVAL("9223372036854775807n", &v); + CHECK(v.isBigInt()); + CHECK(JS::ToBigInt64(v.toBigInt()) == 9223372036854775807L); + + EVAL("-9223372036854775808n", &v); + CHECK(v.isBigInt()); + CHECK(JS::ToBigInt64(v.toBigInt()) == -9223372036854775807L - 1L); + + return true; +} +END_TEST(testToBigInt64) + +BEGIN_TEST(testToBigUint64) { + JS::Rooted v(cx); + + EVAL("0n", &v); + CHECK(v.isBigInt()); + CHECK(JS::ToBigUint64(v.toBigInt()) == 0); + + EVAL("18446744073709551615n", &v); + CHECK(v.isBigInt()); + CHECK(JS::ToBigUint64(v.toBigInt()) == 18446744073709551615UL); + + return true; +} +END_TEST(testToBigUint64) + +#define GENERATE_INTTYPE_TEST(bits) \ + BEGIN_TEST(testNumberToBigInt_Int##bits) { \ + int##bits##_t i = INT##bits##_MIN; \ + JS::BigInt* bi = JS::NumberToBigInt(cx, i); \ + CHECK(bi); \ + CHECK(JS::ToBigInt64(bi) == i); \ + \ + i = INT##bits##_MAX; \ + bi = JS::NumberToBigInt(cx, i); \ + CHECK(bi); \ + CHECK(JS::ToBigInt64(bi) == i); \ + \ + uint##bits##_t u = 0; \ + bi = JS::NumberToBigInt(cx, u); \ + CHECK(bi); \ + CHECK(JS::ToBigUint64(bi) == 0); \ + \ + u = UINT##bits##_MAX; \ + bi = JS::NumberToBigInt(cx, u); \ + CHECK(bi); \ + CHECK(JS::ToBigUint64(bi) == u); \ + \ + return true; \ + } \ + END_TEST(testNumberToBigInt_Int##bits) + +GENERATE_INTTYPE_TEST(8); +GENERATE_INTTYPE_TEST(16); +GENERATE_INTTYPE_TEST(32); +GENERATE_INTTYPE_TEST(64); + +#undef GENERATE_INTTYPE_TEST + +#define GENERATE_SIGNED_VALUE_TEST(type, tag, val) \ + BEGIN_TEST(testNumberToBigInt_##type##_##tag) { \ + type v = val; \ + JS::BigInt* bi = JS::NumberToBigInt(cx, v); \ + CHECK(bi); \ + CHECK(JS::ToBigInt64(bi) == (val)); \ + return true; \ + } \ + END_TEST(testNumberToBigInt_##type##_##tag) + +#define GENERATE_UNSIGNED_VALUE_TEST(type, tag, val) \ + BEGIN_TEST(testNumberToBigInt_##type##_##tag) { \ + type v = val; \ + JS::BigInt* bi = JS::NumberToBigInt(cx, v); \ + CHECK(bi); \ + CHECK(JS::ToBigUint64(bi) == (val)); \ + return true; \ + } \ + END_TEST(testNumberToBigInt_##type##_##tag) + +GENERATE_SIGNED_VALUE_TEST(int, zero, 0); +GENERATE_SIGNED_VALUE_TEST(int, aValue, -42); +GENERATE_UNSIGNED_VALUE_TEST(unsigned, zero, 0); +GENERATE_UNSIGNED_VALUE_TEST(unsigned, aValue, 42); +GENERATE_SIGNED_VALUE_TEST(long, zero, 0); +GENERATE_SIGNED_VALUE_TEST(long, aValue, -42); +GENERATE_UNSIGNED_VALUE_TEST(uintptr_t, zero, 0); +GENERATE_UNSIGNED_VALUE_TEST(uintptr_t, aValue, 42); +GENERATE_UNSIGNED_VALUE_TEST(size_t, zero, 0); +GENERATE_UNSIGNED_VALUE_TEST(size_t, aValue, 42); +GENERATE_SIGNED_VALUE_TEST(double, zero, 0); +GENERATE_SIGNED_VALUE_TEST(double, aValue, -42); + +#undef GENERATE_SIGNED_VALUE_TEST +#undef GENERATE_UNSIGNED_VALUE_TEST + +BEGIN_TEST(testNumberToBigInt_bool) { + JS::BigInt* bi = JS::NumberToBigInt(cx, true); + CHECK(bi); + CHECK(JS::ToBigUint64(bi) == 1); + + bi = JS::NumberToBigInt(cx, false); + CHECK(bi); + CHECK(JS::ToBigUint64(bi) == 0); + + return true; +} +END_TEST(testNumberToBigInt_bool) + +BEGIN_TEST(testNumberToBigInt_NonIntegerValueFails) { + JS::BigInt* bi = JS::NumberToBigInt(cx, 3.1416); + CHECK_NULL(bi); + CHECK(JS_IsExceptionPending(cx)); + JS_ClearPendingException(cx); + return true; +} +END_TEST(testNumberToBigInt_NonIntegerValueFails) + +BEGIN_TEST(testStringToBigInt_FromTwoByteStringSpan) { + mozilla::Range input{mozilla::MakeStringSpan(u"18446744073709551616")}; + JS::BigInt* bi = JS::StringToBigInt(cx, input); + CHECK(bi); + JS::Rooted val(cx, JS::BigIntValue(bi)); + JS::Rooted str(cx, JS::ToString(cx, val)); + CHECK(str); + bool match; + CHECK(JS_StringEqualsLiteral(cx, str, "18446744073709551616", &match)); + CHECK(match); + return true; +} +END_TEST(testStringToBigInt_FromTwoByteStringSpan) + +BEGIN_TEST(testStringToBigInt_FromLatin1Range) { + const JS::Latin1Char string[] = "12345 and some junk at the end"; + JS::ConstLatin1Chars range(string, 5); + JS::BigInt* bi = JS::StringToBigInt(cx, range); + CHECK(bi); + CHECK(JS::ToBigInt64(bi) == 12345); + return true; +} +END_TEST(testStringToBigInt_FromLatin1Range) + +BEGIN_TEST(testStringToBigInt_FromTwoByteRange) { + const char16_t string[] = u"12345 and some junk at the end"; + JS::ConstTwoByteChars range(string, 5); + JS::BigInt* bi = JS::StringToBigInt(cx, range); + CHECK(bi); + CHECK(JS::ToBigInt64(bi) == 12345); + return true; +} +END_TEST(testStringToBigInt_FromTwoByteRange) + +BEGIN_TEST(testStringToBigInt_AcceptedInput) { + CHECK(Allowed(u"", 0)); + CHECK(Allowed(u"\n", 0)); + CHECK(Allowed(u" ", 0)); + CHECK(Allowed(u"0\n", 0)); + CHECK(Allowed(u"0 ", 0)); + CHECK(Allowed(u"\n1", 1)); + CHECK(Allowed(u" 1", 1)); + CHECK(Allowed(u"\n2 ", 2)); + CHECK(Allowed(u" 2\n", 2)); + CHECK(Allowed(u"0b11", 3)); + CHECK(Allowed(u"0x17", 23)); + CHECK(Allowed(u"-5", -5)); + CHECK(Allowed(u"+5", 5)); + CHECK(Allowed(u"-0", 0)); + + CHECK(Fails(u"!!!!!!111one1111one1!1!1!!")); + CHECK(Fails(u"3.1416")); + CHECK(Fails(u"6.022e23")); + CHECK(Fails(u"1e3")); + CHECK(Fails(u".25")); + CHECK(Fails(u".25e2")); + CHECK(Fails(u"1_000_000")); + CHECK(Fails(u"3n")); + CHECK(Fails(u"-0x3")); + CHECK(Fails(u"Infinity")); + + return true; +} + +template +inline bool Allowed(const char16_t (&str)[N], int64_t expected) { + JS::BigInt* bi = JS::StringToBigInt(cx, mozilla::MakeStringSpan(str)); + CHECK(bi); + CHECK(JS::ToBigInt64(bi) == expected); + return true; +} + +template +inline bool Fails(const char16_t (&str)[N]) { + JS::BigInt* bi = JS::StringToBigInt(cx, mozilla::MakeStringSpan(str)); + CHECK_NULL(bi); + CHECK(JS_IsExceptionPending(cx)); + + JS::ExceptionStack exnStack(cx); + CHECK(JS::StealPendingExceptionStack(cx, &exnStack)); + + JS::ErrorReportBuilder report(cx); + CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects)); + CHECK(report.report()->exnType == JSEXN_SYNTAXERR); + CHECK(report.report()->errorNumber == JSMSG_BIGINT_INVALID_SYNTAX); + + CHECK(!JS_IsExceptionPending(cx)); + + return true; +} +END_TEST(testStringToBigInt_AcceptedInput) + +BEGIN_TEST(testSimpleStringToBigInt_AcceptedInput) { + CHECK(Allowed("12345", 10, 12345)); + CHECK(Allowed("+12345", 10, 12345)); + CHECK(Allowed("-12345", 10, -12345)); + CHECK(Allowed("775", 8, 0775)); + CHECK(Allowed("+775", 8, 0775)); + CHECK(Allowed("-775", 8, -0775)); + CHECK(Allowed("cAfE", 16, 0xCAFE)); + CHECK(Allowed("+cAfE", 16, +0xCAFE)); + CHECK(Allowed("-cAfE", 16, -0xCAFE)); + CHECK(Allowed("-0", 10, 0)); + + CHECK(Fails("", 10)); + CHECK(Fails("\n", 10)); + CHECK(Fails(" ", 10)); + CHECK(Fails("0\n", 10)); + CHECK(Fails("0 ", 10)); + CHECK(Fails("\n1", 10)); + CHECK(Fails(" 1", 10)); + CHECK(Fails("\n2 ", 10)); + CHECK(Fails(" 2\n", 10)); + CHECK(Fails("0b11", 2)); + CHECK(Fails("0x17", 16)); + CHECK(Fails("!!!!!!111one1111one1!1!1!!", 10)); + CHECK(Fails("3.1416", 10)); + CHECK(Fails("6.022e23", 10)); + CHECK(Fails("1e3", 10)); + CHECK(Fails(".25", 10)); + CHECK(Fails(".25e2", 10)); + CHECK(Fails("1_000_000", 10)); + CHECK(Fails("3n", 10)); + CHECK(Fails("-0x3", 10)); + CHECK(Fails("Infinity", 10)); + CHECK(Fails("555", 4)); + CHECK(Fails("fff", 15)); + + return true; +} + +template +inline bool Allowed(const char (&str)[N], unsigned radix, int64_t expected) { + JS::BigInt* bi = JS::SimpleStringToBigInt(cx, {str, N - 1}, radix); + CHECK(bi); + CHECK(JS::ToBigInt64(bi) == expected); + return true; +} + +template +inline bool Fails(const char (&str)[N], unsigned radix) { + JS::BigInt* bi = JS::SimpleStringToBigInt(cx, {str, N - 1}, radix); + CHECK_NULL(bi); + CHECK(JS_IsExceptionPending(cx)); + + JS::ExceptionStack exnStack(cx); + CHECK(JS::StealPendingExceptionStack(cx, &exnStack)); + + JS::ErrorReportBuilder report(cx); + CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects)); + CHECK(report.report()->exnType == JSEXN_SYNTAXERR); + CHECK(report.report()->errorNumber == JSMSG_BIGINT_INVALID_SYNTAX); + + CHECK(!JS_IsExceptionPending(cx)); + + return true; +} +END_TEST(testSimpleStringToBigInt_AcceptedInput) + +BEGIN_TEST(testSimpleStringToBigInt_AllPossibleDigits) { + const char allPossible[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + JS::BigInt* bi = + JS::SimpleStringToBigInt(cx, mozilla::MakeStringSpan(allPossible), 36); + CHECK(bi); + JS::Rooted val(cx, JS::BigIntValue(bi)); + JS::Rooted str(cx, JS::ToString(cx, val)); + CHECK(str); + + // Answer calculated using Python: + // int('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', 36) + // Do not trust online base-36 calculators for values > UINT32_MAX! + bool match; + CHECK( + JS_StringEqualsLiteral(cx, str, + "8870050151210747660007771095260505028056221996735" + "67534007158336222790086855213834764150805438340", + &match)); + CHECK(match); + return true; +} +END_TEST(testSimpleStringToBigInt_AllPossibleDigits) + +BEGIN_TEST(testSimpleStringToBigInt_RadixOutOfRange) { + CHECK(RadixOutOfRange(1)); + CHECK(RadixOutOfRange(37)); + return true; +} + +inline bool RadixOutOfRange(unsigned radix) { + JS::BigInt* bi = + JS::SimpleStringToBigInt(cx, mozilla::MakeStringSpan("1"), radix); + CHECK_NULL(bi); + CHECK(JS_IsExceptionPending(cx)); + + JS::ExceptionStack exnStack(cx); + CHECK(JS::StealPendingExceptionStack(cx, &exnStack)); + + JS::ErrorReportBuilder report(cx); + CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects)); + CHECK(report.report()->exnType == JSEXN_RANGEERR); + CHECK(report.report()->errorNumber == JSMSG_BAD_RADIX); + + CHECK(!JS_IsExceptionPending(cx)); + + return true; +} +END_TEST(testSimpleStringToBigInt_RadixOutOfRange) + +BEGIN_TEST(testToBigInt_Undefined) { + JS::Rooted v(cx, JS::UndefinedValue()); + JS::BigInt* bi = JS::ToBigInt(cx, v); + CHECK_NULL(bi); + CHECK(JS_IsExceptionPending(cx)); + JS_ClearPendingException(cx); + return true; +} +END_TEST(testToBigInt_Undefined) + +BEGIN_TEST(testToBigInt_Null) { + JS::Rooted v(cx, JS::NullValue()); + JS::BigInt* bi = JS::ToBigInt(cx, v); + CHECK_NULL(bi); + CHECK(JS_IsExceptionPending(cx)); + JS_ClearPendingException(cx); + return true; +} +END_TEST(testToBigInt_Null) + +BEGIN_TEST(testToBigInt_Boolean) { + JS::Rooted v(cx, JS::TrueValue()); + JS::BigInt* bi = JS::ToBigInt(cx, v); + CHECK(bi); + CHECK(JS::ToBigInt64(bi) == 1); + + v = JS::FalseValue(); + bi = JS::ToBigInt(cx, v); + CHECK(bi); + CHECK(JS::ToBigInt64(bi) == 0); + + return true; +} +END_TEST(testToBigInt_Boolean) + +BEGIN_TEST(testToBigInt_BigInt) { + JS::Rooted v(cx); + EVAL("42n", &v); + JS::BigInt* bi = JS::ToBigInt(cx, v); + CHECK(bi); + CHECK(JS::ToBigInt64(bi) == 42); + return true; +} +END_TEST(testToBigInt_BigInt) + +BEGIN_TEST(testToBigInt_Number) { + JS::Rooted v(cx, JS::Int32Value(42)); + JS::BigInt* bi = JS::ToBigInt(cx, v); + CHECK_NULL(bi); + CHECK(JS_IsExceptionPending(cx)); + JS_ClearPendingException(cx); + return true; +} +END_TEST(testToBigInt_Number) + +BEGIN_TEST(testToBigInt_String) { + JS::Rooted v(cx); + EVAL("'42'", &v); + JS::BigInt* bi = JS::ToBigInt(cx, v); + CHECK(bi); + CHECK(JS::ToBigInt64(bi) == 42); + return true; +} +END_TEST(testToBigInt_String) + +BEGIN_TEST(testToBigInt_Symbol) { + JS::Rooted v(cx); + EVAL("Symbol.toStringTag", &v); + JS::BigInt* bi = JS::ToBigInt(cx, v); + CHECK_NULL(bi); + CHECK(JS_IsExceptionPending(cx)); + JS_ClearPendingException(cx); + return true; +} +END_TEST(testToBigInt_Symbol) diff --git a/js/src/moz.build b/js/src/moz.build index 8c2600d8b55a..0a8c390291c3 100755 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -126,6 +126,7 @@ EXPORTS.js += [ '../public/Array.h', '../public/ArrayBuffer.h', '../public/ArrayBufferMaybeShared.h', + '../public/BigInt.h', '../public/BuildId.h', '../public/CallArgs.h', '../public/CallNonGenericMethod.h', diff --git a/js/src/vm/BigIntType.cpp b/js/src/vm/BigIntType.cpp index ac3ef000bc1d..08fe5fb01824 100644 --- a/js/src/vm/BigIntType.cpp +++ b/js/src/vm/BigIntType.cpp @@ -86,6 +86,7 @@ #include "mozilla/MemoryChecking.h" #include "mozilla/Range.h" #include "mozilla/RangedPtr.h" +#include "mozilla/Span.h" // mozilla::Span #include "mozilla/WrappingOperations.h" #include @@ -99,6 +100,7 @@ #include "builtin/BigInt.h" #include "gc/Allocator.h" +#include "js/BigInt.h" #include "js/Conversions.h" #include "js/Initialization.h" #include "js/StableStringChars.h" @@ -2963,7 +2965,7 @@ BigInt* js::ToBigInt(JSContext* cx, HandleValue val) { } JS::Result js::ToBigInt64(JSContext* cx, HandleValue v) { - BigInt* bi = ToBigInt(cx, v); + BigInt* bi = js::ToBigInt(cx, v); if (!bi) { return cx->alreadyReportedError(); } @@ -2971,7 +2973,7 @@ JS::Result js::ToBigInt64(JSContext* cx, HandleValue v) { } JS::Result js::ToBigUint64(JSContext* cx, HandleValue v) { - BigInt* bi = ToBigInt(cx, v); + BigInt* bi = js::ToBigInt(cx, v); if (!bi) { return cx->alreadyReportedError(); } @@ -3725,3 +3727,95 @@ template XDRResult js::XDRBigInt(XDRState* xdr, template XDRResult js::XDRBigInt(XDRState* xdr, MutableHandleBigInt bi); + +// Public API + +BigInt* JS::NumberToBigInt(JSContext* cx, double num) { + return js::NumberToBigInt(cx, num); +} + +template +static inline BigInt* StringToBigIntHelper(JSContext* cx, + Range& chars) { + bool parseError; + BigInt* bi = ParseStringBigIntLiteral(cx, chars, &parseError); + if (parseError) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_BIGINT_INVALID_SYNTAX); + } + return bi; +} + +BigInt* JS::StringToBigInt(JSContext* cx, Range chars) { + return StringToBigIntHelper(cx, chars); +} + +BigInt* JS::StringToBigInt(JSContext* cx, Range chars) { + return StringToBigIntHelper(cx, chars); +} + +static inline BigInt* SimpleStringToBigIntHelper( + JSContext* cx, mozilla::Span chars, unsigned radix, + bool* haveParseError) { + if (chars.Length() > 1) { + if (chars[0] == '+') { + return BigInt::parseLiteralDigits( + cx, Range{chars.From(1)}, radix, + /* isNegative = */ false, haveParseError); + } + if (chars[0] == '-') { + return BigInt::parseLiteralDigits( + cx, Range{chars.From(1)}, radix, + /* isNegative = */ true, haveParseError); + } + } + + return BigInt::parseLiteralDigits(cx, Range{chars}, radix, + /* isNegative = */ false, haveParseError); +} + +BigInt* JS::SimpleStringToBigInt(JSContext* cx, mozilla::Span chars, + unsigned radix) { + if (chars.empty()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_BIGINT_INVALID_SYNTAX); + return nullptr; + } + if (radix < 2 || radix > 36) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_RADIX); + return nullptr; + } + + mozilla::Span latin1{ + reinterpret_cast(chars.data()), chars.size()}; + bool haveParseError = false; + BigInt* bi = SimpleStringToBigIntHelper(cx, latin1, radix, &haveParseError); + if (haveParseError) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_BIGINT_INVALID_SYNTAX); + } + + return bi; +} + +BigInt* JS::ToBigInt(JSContext* cx, HandleValue val) { + return js::ToBigInt(cx, val); +} + +int64_t JS::ToBigInt64(JS::BigInt* bi) { return BigInt::toInt64(bi); } + +uint64_t JS::ToBigUint64(JS::BigInt* bi) { return BigInt::toUint64(bi); } + +// Semi-public template details + +BigInt* JS::detail::BigIntFromInt64(JSContext* cx, int64_t num) { + return BigInt::createFromInt64(cx, num); +} + +BigInt* JS::detail::BigIntFromUint64(JSContext* cx, uint64_t num) { + return BigInt::createFromUint64(cx, num); +} + +BigInt* JS::detail::BigIntFromBool(JSContext* cx, bool b) { + return b ? BigInt::one(cx) : BigInt::zero(cx); +}