summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibJS/Runtime
diff options
context:
space:
mode:
authorSlappy826 <summon123@hotmail.com>2022-08-26 11:14:04 -0500
committerLinus Groh <mail@linusgroh.de>2022-08-30 01:00:48 +0100
commitf4b3bb519f4dcf9aa79b68420da4e7250226ecb7 (patch)
tree472a4d313f0bd831f5623046f32bfad45e7bab90 /Userland/Libraries/LibJS/Runtime
parent583c0dc782d3c3fd821402350291e1ca85333d67 (diff)
downloadserenity-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.cpp107
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: