diff options
author | Slappy826 <summon123@hotmail.com> | 2022-08-26 11:14:04 -0500 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2022-08-30 01:00:48 +0100 |
commit | f4b3bb519f4dcf9aa79b68420da4e7250226ecb7 (patch) | |
tree | 472a4d313f0bd831f5623046f32bfad45e7bab90 /Userland/Libraries/LibJS/Runtime | |
parent | 583c0dc782d3c3fd821402350291e1ca85333d67 (diff) | |
download | serenity-f4b3bb519f4dcf9aa79b68420da4e7250226ecb7.zip |
LibJS: Handle non-decimal integer literals in Value::to_number
Implements support for parsing binary and octal literals, and fixes
instances where a hex literal is parsed in ways the spec doesn't
allow.
Diffstat (limited to 'Userland/Libraries/LibJS/Runtime')
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/Value.cpp | 107 |
1 files changed, 88 insertions, 19 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/Value.cpp b/Userland/Libraries/LibJS/Runtime/Value.cpp index a4997dc5ad..eddfe0b4bd 100644 --- a/Userland/Libraries/LibJS/Runtime/Value.cpp +++ b/Userland/Libraries/LibJS/Runtime/Value.cpp @@ -471,6 +471,92 @@ FLATTEN ThrowCompletionOr<Value> Value::to_numeric(VM& vm) const return primitive.to_number(vm); } +constexpr bool is_ascii_number(u32 code_point) +{ + return is_ascii_digit(code_point) || code_point == '.' || (code_point == 'e' || code_point == 'E') || code_point == '+' || code_point == '-'; +} + +struct NumberParseResult { + StringView literal; + u8 base; +}; + +static Optional<NumberParseResult> parse_number_text(StringView text) +{ + NumberParseResult result {}; + + auto check_prefix = [&](auto lower_prefix, auto upper_prefix) { + if (text.length() <= 2) + return false; + if (!text.starts_with(lower_prefix) && !text.starts_with(upper_prefix)) + return false; + return true; + }; + + // https://tc39.es/ecma262/#sec-tonumber-applied-to-the-string-type + if (check_prefix("0b"sv, "0B"sv)) { + if (!all_of(text.substring_view(2), is_ascii_binary_digit)) + return {}; + + result.literal = text.substring_view(2); + result.base = 2; + } else if (check_prefix("0o"sv, "0O"sv)) { + if (!all_of(text.substring_view(2), is_ascii_octal_digit)) + return {}; + + result.literal = text.substring_view(2); + result.base = 8; + } else if (check_prefix("0x"sv, "0X"sv)) { + if (!all_of(text.substring_view(2), is_ascii_hex_digit)) + return {}; + + result.literal = text.substring_view(2); + result.base = 16; + } else { + if (!all_of(text, is_ascii_number)) + return {}; + + result.literal = text; + result.base = 10; + } + + return result; +} + +// 7.1.4.1.1 StringToNumber ( str ), https://tc39.es/ecma262/#sec-stringtonumber +static Optional<Value> string_to_number(StringView string) +{ + // 1. Let text be StringToCodePoints(str). + String text = string.trim_whitespace(); + + // 2. Let literal be ParseText(text, StringNumericLiteral). + if (text.is_empty()) + return Value(0); + if (text == "Infinity" || text == "+Infinity") + return js_infinity(); + if (text == "-Infinity") + return js_negative_infinity(); + + auto result = parse_number_text(text); + + // 3. If literal is a List of errors, return NaN. + if (!result.has_value()) + return js_nan(); + + // 4. Return StringNumericValue of literal. + if (result->base != 10) { + auto bigint = Crypto::UnsignedBigInteger::from_base(result->base, result->literal); + return Value(bigint.to_double()); + } + + char* endptr; + auto parsed_double = strtod(text.characters(), &endptr); + if (*endptr) + return js_nan(); + + return Value(parsed_double); +} + // 7.1.4 ToNumber ( argument ), https://tc39.es/ecma262/#sec-tonumber ThrowCompletionOr<Value> Value::to_number(VM& vm) const { @@ -485,25 +571,8 @@ ThrowCompletionOr<Value> Value::to_number(VM& vm) const return Value(0); case BOOLEAN_TAG: return Value(as_bool() ? 1 : 0); - case STRING_TAG: { - String string = Utf8View(as_string().string()).trim(whitespace_characters, AK::TrimMode::Both).as_string(); - if (string.is_empty()) - return Value(0); - if (string == "Infinity" || string == "+Infinity") - return js_infinity(); - if (string == "-Infinity") - return js_negative_infinity(); - char* endptr; - auto parsed_double = strtod(string.characters(), &endptr); - if (*endptr) - return js_nan(); - // NOTE: Per the spec only exactly [+-]Infinity should result in infinity - // but strtod gives infinity for any case-insensitive 'infinity' or 'inf' string. - if (isinf(parsed_double) && string.contains('i', AK::CaseSensitivity::CaseInsensitive)) - return js_nan(); - - return Value(parsed_double); - } + case STRING_TAG: + return string_to_number(as_string().string().view()); case SYMBOL_TAG: return vm.throw_completion<TypeError>(ErrorType::Convert, "symbol", "number"); case BIGINT_TAG: |