builtins/vm: JSON.stringify 序列化与键面改单元域——孤立 surrogate 串值不再被 lossy 串值打碎

jsvalue_to_json 串值分支原经 to_owned_string(lossy)后按 char 流序列化:孤立
surrogate 单元被打成 U+FFFD 且输出裸非法码单元(非 well-formed JSON,
JSON.parse 无法复原同值)。现按单元流序列化(stringify_string_units):良配
代理对原样输出 astral 字符,非配对孤立 surrogate 输出 \\uXXXX(well-formed
JSON 要求,round-trip 复原同值),C0/C1 控制码输出 \\uXXXX,其余按 JSON
转义规则。

toJSON/replacer 键参数同改:键 si 物化为单元序列(整数键 → ASCII 数字串,
字符串键 → 码表键经 decode_key 还原含孤立 surrogate 单元),new_string_units_owned
建参——与 0c750fa 键物化面(Object.keys/ownKeys/entries/Reflect.ownKeys/json
reviver 键同源)口径一致,replace/reviver 键参数不再走 lossy 串值。

三并钉:json_tests 增补 stringify 孤立 surrogate round-trip / 代理对 astral
直出 / 键面单元传递三则;JSON 套件 37/0,node 13 用例对照一致。
This commit is contained in:
2026-09-14 07:33:51 +00:00
parent 6f70d7d09f
commit eb6bc412d9
2 changed files with 115 additions and 34 deletions
+50
View File
@@ -313,3 +313,53 @@ fn json_parse_numeric_keys_stringify_roundtrip() {
let s = string_value(&vm, result);
assert_eq!(s, "{\"5\":1}");
}
// -- JSON.stringify 单元语义(孤立 surrogate 转义 / 代理对原样 / 真实键串) --
#[test]
fn json_stringify_unpaired_surrogate_escaped() {
// 非配对孤立 surrogate 输出 \uXXXX(well-formed JSON 要求)。
let mut vm = Vm::new();
let r1 = eval(&mut vm, "JSON.stringify(String.fromCharCode(0xD800)) === '\"\\\\ud800\"'").unwrap();
assert!(r1.as_bool(), "unpaired high surrogate must be \\ud800-escaped");
let r2 = eval(&mut vm, "JSON.stringify(String.fromCharCode(0xDF06)) === '\"\\\\udf06\"'").unwrap();
assert!(r2.as_bool(), "unpaired low surrogate must be \\udf06-escaped");
}
#[test]
fn json_stringify_surrogate_pair_raw() {
// 配对 surrogate 原样输出 astral 字符;混串按 spec 逐单元处置。
let mut vm = Vm::new();
let r1 = eval(&mut vm, "JSON.stringify(String.fromCharCode(0xD834, 0xDF06)) === '\"\\u{1D306}\"'").unwrap();
assert!(r1.as_bool(), "paired surrogates must serialize as the raw astral char");
let r2 = eval(
&mut vm,
"JSON.stringify(String.fromCharCode(0xD834, 0xD834, 0xDF06, 0xD834)) === '\"\\\\ud834\\u{1D306}\\\\ud834\"'",
)
.unwrap();
assert!(r2.as_bool(), "unpaired units around a pair must each be escaped");
}
#[test]
fn json_stringify_key_units_real() {
// 键序列化用真实键串(孤立 surrogate 转义),toJSON/replacer 键参数同口径。
let mut vm = Vm::new();
let r1 = eval(
&mut vm,
"var o = {}; o[String.fromCharCode(0xD800)] = 1; JSON.stringify(o) === '{\"\\\\ud800\":1}'",
)
.unwrap();
assert!(r1.as_bool(), "object key with lone surrogate must serialize escaped");
let r2 = eval(
&mut vm,
"var ks = []; JSON.stringify({[String.fromCharCode(0xD800)]: 1}, function(k, v) { if (k !== '') ks.push(k.charCodeAt(0)); return v; }); ks.length === 1 && ks[0] === 0xD800",
)
.unwrap();
assert!(r2.as_bool(), "replacer must receive the real key string (lone unit, not FFFD)");
let r3 = eval(
&mut vm,
"var tk = -1; var inner = { toJSON: function(k) { tk = k.charCodeAt(0); return 'x'; } }; var outer = {}; outer[String.fromCharCode(0xD800)] = inner; JSON.stringify(outer); tk === 0xD800",
)
.unwrap();
assert!(r3.as_bool(), "toJSON must receive the real key string (lone unit, not FFFD)");
}