From c3d181bd32e8ad811de473a93dcbb0d4c02ff521 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 15 Aug 2026 22:32:14 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20parseInt/parseFloat=20=E5=AF=B9=E8=B1=A1?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E6=8C=89=20ToString=20=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E8=BD=AC=E6=8D=A2=E5=B9=B6=E4=BC=A0=E6=92=AD=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 对象参数走 ToPrimitive(string hint) 后取串解析(toString 结果可见), Symbol 抛 TypeError,对象方法抛出的原始异常原样传播。 --- crates/oxide_builtins/src/number.rs | 24 +++++++++++++++++-- .../oxide_vm/tests/builtins_number_tests.rs | 20 ++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/crates/oxide_builtins/src/number.rs b/crates/oxide_builtins/src/number.rs index 55e83287..784b1ed4 100644 --- a/crates/oxide_builtins/src/number.rs +++ b/crates/oxide_builtins/src/number.rs @@ -117,7 +117,17 @@ pub fn number_parse_int(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::float(f64::NAN)); } - let s = oxide_runtime_api::to_string(vm.reg(args[1])); + // 参数按 ToString 完整转换:对象经 ToPrimitive(string hint),Symbol 抛 + // TypeError;对象方法抛出的原生异常原样传播。 + let s = match oxide_runtime_api::to_string_full(vm.reg(args[1]), vm) { + Ok(s) => s, + Err(_) => { + if let Some(exc) = vm.take_uncaught_value() { + return NativeResult::Err(exc); + } + return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string")); + } + }; let s = s.trim_start_matches(is_js_ws).trim_end_matches(is_js_ws); // radix 经 ToInt32:缺省参数视为 undefined(ToInt32 → 0)。 @@ -212,7 +222,17 @@ pub fn number_parse_float(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::float(f64::NAN)); } - let s = oxide_runtime_api::to_string(vm.reg(args[1])); + // 参数按 ToString 完整转换:对象经 ToPrimitive(string hint),Symbol 抛 + // TypeError;对象方法抛出的原生异常原样传播。 + let s = match oxide_runtime_api::to_string_full(vm.reg(args[1]), vm) { + Ok(s) => s, + Err(_) => { + if let Some(exc) = vm.take_uncaught_value() { + return NativeResult::Err(exc); + } + return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string")); + } + }; let s = s.trim_start_matches(is_js_ws).trim_end_matches(is_js_ws); // 读符号;`Infinity` 大小写敏感,其后可带任意后缀(取最长合法前缀)。 diff --git a/crates/oxide_vm/tests/builtins_number_tests.rs b/crates/oxide_vm/tests/builtins_number_tests.rs index 9cbcaa24..426a31a2 100644 --- a/crates/oxide_vm/tests/builtins_number_tests.rs +++ b/crates/oxide_vm/tests/builtins_number_tests.rs @@ -145,6 +145,26 @@ fn parse_float_prefix_semantics() { } } +#[test] +fn parse_parse_int_object_arg_tostring() { + let mut vm = Vm::new(); + // 对象参数经 ToString 完整转换:取 toString 结果再解析。 + let result = eval(&mut vm, "parseInt({toString: () => '42'})").unwrap(); + assert_eq!(result.as_int(), 42); + #[allow(clippy::approx_constant)] + let expected = 3.14; + let result = eval(&mut vm, "parseFloat({toString: () => '3.14abc'})").unwrap(); + assert!((result.as_double() - expected).abs() < 0.001); + // Symbol 参数按规范抛 TypeError。 + let err = eval(&mut vm, "parseInt(Symbol())").unwrap_err(); + assert!(err.contains("TypeError"), "got: {}", err); + let err = eval(&mut vm, "parseFloat(Symbol('x'))").unwrap_err(); + assert!(err.contains("TypeError"), "got: {}", err); + // 对象 toString 抛出的原始异常原样传播(保留异常对象)。 + let err = eval(&mut vm, "try { parseInt({toString(){ throw 42 }}) } catch (e) { e }").unwrap(); + assert_eq!(err.as_int(), 42); +} + #[test] fn number_constructor() { let mut vm = Vm::new();