fix: JSON.stringify 装箱字符串走完整 ToString 序列化

This commit is contained in:
2026-09-23 14:09:32 +00:00
parent ede3db8ad8
commit 1cf304bb0d
2 changed files with 159 additions and 11 deletions
+51 -11
View File
@@ -185,21 +185,39 @@ fn create_wrapper<H: VmHost>(vm: &mut H, value: JsValue) -> JsValue {
JsValue::from_js_object(obj_ptr)
}
fn process_space(val: JsValue) -> String {
/// space 参数文本化(Stringify 步 5):数字经 ToIntegerOrInfinity 钳 10,
/// 字符串取前 10 字符;装箱 String 走完整 ToString(步 4c 同款语义,
/// 抛出值原样上抛),其余形态维持占位行为。
fn process_space<H: VmHost>(vm: &mut H, val: JsValue) -> Result<String, JsValue> {
if val.is_int() || val.is_double() {
let n = oxide_runtime_api::to_integer_or_infinity(val);
if n.is_nan() || n.is_infinite() || n <= 0.0 {
return String::new();
return Ok(String::new());
}
let clamped = (n as usize).min(10);
" ".repeat(clamped)
} else if val.is_string() {
let s = unsafe { (*val.as_string_ptr()).to_owned_string() };
s.chars().take(10).collect()
} else {
let s = oxide_runtime_api::to_string(val);
s.chars().take(10).collect()
return Ok(" ".repeat(clamped));
}
if val.is_string() {
let s = unsafe { (*val.as_string_ptr()).to_owned_string() };
return Ok(s.chars().take(10).collect());
}
if val.is_object() {
let ptr = val.as_js_object_ptr();
if !ptr.is_null() && unsafe { (*ptr).is_string_obj() } {
let s = match oxide_runtime_api::to_string_value_full(val, vm) {
Ok(s) => s,
Err(msg) => {
return Err(vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg)));
}
};
let s = unsafe { (*s.as_string_ptr()).to_owned_string() };
return Ok(s.chars().take(10).collect());
}
}
let s = oxide_runtime_api::to_string(val);
Ok(s.chars().take(10).collect())
}
/// 键 si 物化为单元序列:整数键 → ASCII 数字串,字符串键 → 码表键经 `decode_key`
@@ -379,7 +397,14 @@ pub fn json_stringify<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
}
}
let space = if args.len() > 3 { process_space(vm.reg(args[3])) } else { String::new() };
let space = if args.len() > 3 {
match process_space(vm, vm.reg(args[3])) {
Ok(s) => s,
Err(exc) => return NativeResult::Err(exc),
}
} else {
String::new()
};
let holder = create_wrapper(vm, value);
@@ -467,7 +492,22 @@ fn jsvalue_to_json<H: VmHost>(
if obj.boxed_value().is_bigint() {
return Err(crate::error::create_type_error(vm, "Do not know how to serialize a BigInt"));
}
if obj.is_typed_array_obj() {
// 步 4c:装箱 String 走完整 ToString([[StringData]] 臂):尊重
// toString/Symbol.toPrimitive 覆盖、不直读载荷,抛出值原样传播。
if obj.is_string_obj() {
let s = match oxide_runtime_api::to_string_value_full(val, vm) {
Ok(s) => s,
Err(msg) => {
let exc = vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
visited.remove(&(obj_ptr as *const JsObject));
return Err(exc);
}
};
let units = vm.string_units(s);
stringify_string_units(&units, out);
} else if obj.is_typed_array_obj() {
stringify_typed_array(vm, obj, visited, out, replacer_fn, replacer_whitelist, space, indent_level)?;
} else if obj.is_array() {
stringify_array(vm, obj, visited, out, replacer_fn, replacer_whitelist, space, indent_level)?;
+108
View File
@@ -340,6 +340,114 @@ fn json_stringify_surrogate_pair_raw() {
assert!(r2.as_bool(), "unpaired units around a pair must each be escaped");
}
// -- JSON.stringify 装箱 String([[StringData]] 臂) --
#[test]
fn json_stringify_boxed_string_top_level() {
// 顶层装箱 String 序列化为载荷文本([[StringData]] 臂 ToString,非索引枚举)。
let mut vm = Vm::new();
let result = eval(&mut vm, "JSON.stringify(new String('x'))").unwrap();
assert_eq!(string_value(&vm, result), "\"x\"");
}
#[test]
fn json_stringify_boxed_string_object_property() {
// 对象属性位同臂:{"a":"x"}。
let mut vm = Vm::new();
let result = eval(&mut vm, "JSON.stringify({a: new String('x')})").unwrap();
assert_eq!(string_value(&vm, result), "{\"a\":\"x\"}");
}
#[test]
fn json_stringify_boxed_string_array_elements() {
// 数组元素位:["x","yz"]。
let mut vm = Vm::new();
let result = eval(&mut vm, "JSON.stringify([new String('x'), new String('yz')])").unwrap();
assert_eq!(string_value(&vm, result), "[\"x\",\"yz\"]");
}
#[test]
fn json_stringify_boxed_string_respects_tostring_override() {
// 完整 ToString 语义:toString 覆盖生效,valueOf 不在 string hint 路径被调用。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var s = new String('x'); s.toString = function() { return 'y'; }; \
s.valueOf = function() { throw new Error('no'); }; JSON.stringify(s)",
)
.unwrap();
assert_eq!(string_value(&vm, result), "\"y\"");
}
#[test]
fn json_stringify_boxed_string_abrupt_tostring_propagates() {
// 值位 abrupt toString 抛原始值(原样传播,不折叠为环检 TypeError)。
let mut vm = Vm::new();
let err = eval(
&mut vm,
"var s = new String('x'); s.toString = function() { throw new RangeError('SE'); }; \
JSON.stringify(s)",
);
assert!(err.is_err());
}
#[test]
fn json_stringify_string_prototype_is_empty_string() {
// String.prototype 是规范 String exotic 对象([[StringData]] 空串)→ ""。
let mut vm = Vm::new();
let result = eval(&mut vm, "JSON.stringify(String.prototype)").unwrap();
assert_eq!(string_value(&vm, result), "\"\"");
}
#[test]
fn json_stringify_boxed_string_space_argument() {
// space 位装箱 String 与同文本原语串缩进同形。
let mut vm = Vm::new();
let r1 = eval(&mut vm, "JSON.stringify({a: 1}, undefined, new String('xxx'))").unwrap();
let r2 = eval(&mut vm, "JSON.stringify({a: 1}, undefined, 'xxx')").unwrap();
assert_eq!(string_value(&vm, r1), string_value(&vm, r2));
}
#[test]
fn json_stringify_boxed_string_space_tostring_override() {
// space 位 toString 覆盖生效(完整 ToString,非直读载荷)。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var s = new String('zzz'); s.toString = function() { return '---'; }; \
JSON.stringify({a: 1}, undefined, s)",
)
.unwrap();
let plain = eval(&mut vm, "JSON.stringify({a: 1}, undefined, '---')").unwrap();
assert_eq!(string_value(&vm, result), string_value(&vm, plain));
}
#[test]
fn json_stringify_boxed_string_space_abrupt_throws() {
// space 位 abrupt toString 抛原始值。
let mut vm = Vm::new();
let err = eval(
&mut vm,
"var s = new String('x'); s.toString = function() { throw new RangeError('SE'); }; \
JSON.stringify({a: 1}, undefined, s)",
);
assert!(err.is_err());
}
#[test]
fn json_stringify_boxed_string_tostring_result_units_escaped() {
// 完整 ToString 产物经单元流序列化:孤立 surrogate → \uXXXX 转义(well-formed JSON)。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var s = new String('x'); \
s.toString = function() { return String.fromCharCode(0xD800); }; \
JSON.stringify(s) === '\"\\\\ud800\"'",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn json_stringify_key_units_real() {
// 键序列化用真实键串(孤立 surrogate 转义),toJSON/replacer 键参数同口径。