mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: JSON.stringify 装箱字符串走完整 ToString 序列化
This commit is contained in:
@@ -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)?;
|
||||
|
||||
@@ -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 键参数同口径。
|
||||
|
||||
Reference in New Issue
Block a user