mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 22:02:44 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									583c0dc782
								
							
						
					
					
						commit
						f4b3bb519f
					
				
					 2 changed files with 172 additions and 19 deletions
				
			
		|  | @ -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: | ||||
|  |  | |||
|  | @ -11,6 +11,9 @@ test("constructor without new", () => { | |||
|     expect(Number(-123)).toBe(-123); | ||||
|     expect(Number(123n)).toBe(123); | ||||
|     expect(Number(-123n)).toBe(-123); | ||||
|     expect(Number("1_23")).toBeNaN(); | ||||
|     expect(Number("00123")).toBe(123); | ||||
|     expect(Number("123n")).toBeNaN(); | ||||
|     expect(Number("42")).toBe(42); | ||||
|     expect(Number(null)).toBe(0); | ||||
|     expect(Number(true)).toBe(1); | ||||
|  | @ -29,6 +32,45 @@ test("constructor without new", () => { | |||
|     expect(Number("foo")).toBeNaN(); | ||||
|     expect(Number("10e10000")).toBe(Infinity); | ||||
|     expect(Number("-10e10000")).toBe(-Infinity); | ||||
|     expect(Number("0b1")).toBe(1); | ||||
|     expect(Number("0B1")).toBe(1); | ||||
|     expect(Number("0b01")).toBe(1); | ||||
|     expect(Number("0b11")).toBe(3); | ||||
|     expect(Number("0b")).toBeNaN(); | ||||
|     expect(Number("0B")).toBeNaN(); | ||||
|     expect(Number("-0b1")).toBeNaN(); | ||||
|     expect(Number("+0b1")).toBeNaN(); | ||||
|     expect(Number("0b1.1")).toBeNaN(); | ||||
|     expect(Number("0b1e10")).toBeNaN(); | ||||
|     expect(Number("0b1e+10")).toBeNaN(); | ||||
|     expect(Number("0b1e-10")).toBeNaN(); | ||||
|     expect(Number("0b1_1")).toBeNaN(); | ||||
|     expect(Number("0o7")).toBe(7); | ||||
|     expect(Number("0O7")).toBe(7); | ||||
|     expect(Number("0o07")).toBe(7); | ||||
|     expect(Number("0o77")).toBe(63); | ||||
|     expect(Number("0o")).toBeNaN(); | ||||
|     expect(Number("0O")).toBeNaN(); | ||||
|     expect(Number("-0o1")).toBeNaN(); | ||||
|     expect(Number("+0o1")).toBeNaN(); | ||||
|     expect(Number("0o1.1")).toBeNaN(); | ||||
|     expect(Number("0o1e10")).toBeNaN(); | ||||
|     expect(Number("0o1e+10")).toBeNaN(); | ||||
|     expect(Number("0o1e-10")).toBeNaN(); | ||||
|     expect(Number("0o1_1")).toBeNaN(); | ||||
|     expect(Number("0x1")).toBe(1); | ||||
|     expect(Number("0X1")).toBe(1); | ||||
|     expect(Number("0x01")).toBe(1); | ||||
|     expect(Number("0x11")).toBe(17); | ||||
|     expect(Number("0x")).toBeNaN(); | ||||
|     expect(Number("0X")).toBeNaN(); | ||||
|     expect(Number("-0x1")).toBeNaN(); | ||||
|     expect(Number("+0x1")).toBeNaN(); | ||||
|     expect(Number("0x1.1")).toBeNaN(); | ||||
|     expect(Number("0x1e10")).toBe(7696); | ||||
|     expect(Number("0x1e+10")).toBeNaN(); | ||||
|     expect(Number("0x1e-10")).toBeNaN(); | ||||
|     expect(Number("0x1_1")).toBeNaN(); | ||||
| }); | ||||
| 
 | ||||
| test("constructor with new", () => { | ||||
|  | @ -39,6 +81,9 @@ test("constructor with new", () => { | |||
|     expect(new Number(-123).valueOf()).toBe(-123); | ||||
|     expect(new Number(123n).valueOf()).toBe(123); | ||||
|     expect(new Number(-123n).valueOf()).toBe(-123); | ||||
|     expect(new Number("1_23").valueOf()).toBeNaN(); | ||||
|     expect(new Number("00123").valueOf()).toBe(123); | ||||
|     expect(new Number("123n").valueOf()).toBeNaN(); | ||||
|     expect(new Number("42").valueOf()).toBe(42); | ||||
|     expect(new Number(null).valueOf()).toBe(0); | ||||
|     expect(new Number(true).valueOf()).toBe(1); | ||||
|  | @ -57,4 +102,43 @@ test("constructor with new", () => { | |||
|     expect(new Number("foo").valueOf()).toBeNaN(); | ||||
|     expect(new Number("10e10000").valueOf()).toBe(Infinity); | ||||
|     expect(new Number("-10e10000").valueOf()).toBe(-Infinity); | ||||
|     expect(new Number("0b1").valueOf()).toBe(1); | ||||
|     expect(new Number("0B1").valueOf()).toBe(1); | ||||
|     expect(new Number("0b01").valueOf()).toBe(1); | ||||
|     expect(new Number("0b11").valueOf()).toBe(3); | ||||
|     expect(new Number("0b").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0B").valueOf()).toBeNaN(); | ||||
|     expect(new Number("-0b1").valueOf()).toBeNaN(); | ||||
|     expect(new Number("+0b1").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0b1.1").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0b1e10").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0b1e+10").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0b1e-10").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0b1_1").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0o7").valueOf()).toBe(7); | ||||
|     expect(new Number("0O7").valueOf()).toBe(7); | ||||
|     expect(new Number("0o07").valueOf()).toBe(7); | ||||
|     expect(new Number("0o77").valueOf()).toBe(63); | ||||
|     expect(new Number("0o").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0O").valueOf()).toBeNaN(); | ||||
|     expect(new Number("-0o1").valueOf()).toBeNaN(); | ||||
|     expect(new Number("+0o1").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0o1.1").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0o1e7").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0o1e+10").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0o1e-10").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0o1_1").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0x1").valueOf()).toBe(1); | ||||
|     expect(new Number("0X1").valueOf()).toBe(1); | ||||
|     expect(new Number("0x01").valueOf()).toBe(1); | ||||
|     expect(new Number("0x11").valueOf()).toBe(17); | ||||
|     expect(new Number("0x").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0X").valueOf()).toBeNaN(); | ||||
|     expect(new Number("-0x1").valueOf()).toBeNaN(); | ||||
|     expect(new Number("+0x1").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0x1.1").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0x1e10").valueOf()).toBe(7696); | ||||
|     expect(new Number("0x1e+10").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0x1e-10").valueOf()).toBeNaN(); | ||||
|     expect(new Number("0x1_1").valueOf()).toBeNaN(); | ||||
| }); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Slappy826
						Slappy826