mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
eb6bc412d9
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 用例对照一致。
366 lines
11 KiB
Rust
366 lines
11 KiB
Rust
use std::sync::Arc;
|
||
|
||
use oxide_compiler::compiler::Compiler;
|
||
use oxide_types::value::JsValue;
|
||
use oxide_vm::vm::Vm;
|
||
|
||
fn eval(vm: &mut Vm, source: &str) -> Result<JsValue, String> {
|
||
let allocator = oxide_parser::Allocator::default();
|
||
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
|
||
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
|
||
vm.run(&Arc::new(module))
|
||
}
|
||
|
||
fn string_value(vm: &Vm, val: JsValue) -> String {
|
||
vm.lookup_str(val).unwrap_or_default()
|
||
}
|
||
|
||
// -- JSON.parse --
|
||
|
||
#[test]
|
||
fn json_parse_object() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.parse('{\"a\":1,\"b\":2}')").unwrap();
|
||
assert!(result.is_object());
|
||
let obj = unsafe { &*result.as_js_object_ptr() };
|
||
assert_eq!(obj.prop_count(), 2);
|
||
}
|
||
|
||
#[test]
|
||
fn json_parse_array() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.parse('[1,2,3]')").unwrap();
|
||
assert!(result.is_object());
|
||
let obj = unsafe { &*result.as_js_object_ptr() };
|
||
assert!(obj.is_array());
|
||
assert_eq!(obj.prop_count(), 3);
|
||
assert_eq!(obj.get_prop_at(2).as_double(), 3.0);
|
||
}
|
||
|
||
#[test]
|
||
fn json_parse_string() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.parse('\"hello\"')").unwrap();
|
||
assert!(result.is_string());
|
||
let s = string_value(&vm, result);
|
||
assert_eq!(s, "hello");
|
||
}
|
||
|
||
#[test]
|
||
fn json_parse_number() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.parse('42')").unwrap();
|
||
assert!(result.is_double());
|
||
}
|
||
|
||
#[test]
|
||
fn json_parse_float() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.parse('3.14')").unwrap();
|
||
assert!(result.is_double());
|
||
}
|
||
|
||
#[test]
|
||
fn json_parse_true() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.parse('true')").unwrap();
|
||
assert!(result.is_bool());
|
||
assert!(result.as_bool());
|
||
}
|
||
|
||
#[test]
|
||
fn json_parse_false() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.parse('false')").unwrap();
|
||
assert!(result.is_bool());
|
||
assert!(!result.as_bool());
|
||
}
|
||
|
||
#[test]
|
||
fn json_parse_null() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.parse('null')").unwrap();
|
||
assert!(result.is_null());
|
||
}
|
||
|
||
#[test]
|
||
fn json_parse_nested() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.parse('{\"a\":1,\"b\":[1,2],\"c\":{\"d\":\"hello\"}}')").unwrap();
|
||
assert!(result.is_object());
|
||
}
|
||
|
||
#[test]
|
||
fn json_parse_invalid_throws() {
|
||
let mut vm = Vm::new();
|
||
let err = eval(&mut vm, "JSON.parse('not json')");
|
||
assert!(err.is_err());
|
||
}
|
||
|
||
// -- JSON.stringify --
|
||
|
||
#[test]
|
||
fn json_stringify_object() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.stringify({a:1})").unwrap();
|
||
assert!(result.is_string());
|
||
let s = string_value(&vm, result);
|
||
assert_eq!(s, "{\"a\":1}");
|
||
}
|
||
|
||
#[test]
|
||
fn json_stringify_array() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.stringify([1,2,3])").unwrap();
|
||
assert!(result.is_string());
|
||
let s = string_value(&vm, result);
|
||
assert_eq!(s, "[1,2,3]");
|
||
}
|
||
|
||
#[test]
|
||
fn json_stringify_array_with_negative_double() {
|
||
// 数组内负 double:out 已含 "[1,",负号必须追加在当前数前(ryu 路径)。
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.stringify([1,-1.5])").unwrap();
|
||
assert!(result.is_string());
|
||
let s = string_value(&vm, result);
|
||
assert_eq!(s, "[1,-1.5]");
|
||
}
|
||
|
||
#[test]
|
||
fn json_stringify_string() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.stringify('hello')").unwrap();
|
||
assert!(result.is_string());
|
||
let s = string_value(&vm, result);
|
||
assert_eq!(s, "\"hello\"");
|
||
}
|
||
|
||
#[test]
|
||
fn json_stringify_number() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.stringify(42)").unwrap();
|
||
assert!(result.is_string());
|
||
let s = string_value(&vm, result);
|
||
assert_eq!(s, "42");
|
||
}
|
||
|
||
#[test]
|
||
fn json_stringify_float() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.stringify(3.14)").unwrap();
|
||
assert!(result.is_string());
|
||
let s = string_value(&vm, result);
|
||
assert!(s.starts_with("3.14"));
|
||
}
|
||
|
||
#[test]
|
||
fn json_stringify_true() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.stringify(true)").unwrap();
|
||
assert!(result.is_string());
|
||
let s = string_value(&vm, result);
|
||
assert_eq!(s, "true");
|
||
}
|
||
|
||
#[test]
|
||
fn json_stringify_false() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.stringify(false)").unwrap();
|
||
assert!(result.is_string());
|
||
let s = string_value(&vm, result);
|
||
assert_eq!(s, "false");
|
||
}
|
||
|
||
#[test]
|
||
fn json_stringify_null() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.stringify(null)").unwrap();
|
||
assert!(result.is_string());
|
||
let s = string_value(&vm, result);
|
||
assert_eq!(s, "null");
|
||
}
|
||
|
||
#[test]
|
||
fn json_stringify_undefined() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.stringify(undefined)").unwrap();
|
||
assert!(result.is_undefined());
|
||
}
|
||
|
||
// -- Roundtrip --
|
||
|
||
#[test]
|
||
fn json_parse_stringify_roundtrip() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.parse(JSON.stringify({a:1,b:2}))").unwrap();
|
||
assert!(result.is_object());
|
||
let obj = unsafe { &*result.as_js_object_ptr() };
|
||
assert_eq!(obj.prop_vec_len(), 2);
|
||
}
|
||
|
||
#[test]
|
||
fn json_roundtrip_array() {
|
||
let mut vm = Vm::new();
|
||
let _ = eval(&mut vm, "var arr = JSON.parse(JSON.stringify([1,2,3])); arr").unwrap();
|
||
}
|
||
|
||
// -- Edge cases --
|
||
|
||
#[test]
|
||
fn json_stringify_empty_object() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.stringify({})").unwrap();
|
||
assert!(result.is_string());
|
||
let s = string_value(&vm, result);
|
||
assert_eq!(s, "{}");
|
||
}
|
||
|
||
#[test]
|
||
fn json_stringify_empty_array() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.stringify([])").unwrap();
|
||
assert!(result.is_string());
|
||
let s = string_value(&vm, result);
|
||
assert_eq!(s, "[]");
|
||
}
|
||
|
||
#[test]
|
||
fn json_stringify_escape_quotes() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.stringify('he\"llo')").unwrap();
|
||
assert!(result.is_string());
|
||
let s = string_value(&vm, result);
|
||
assert_eq!(s, "\"he\\\"llo\"");
|
||
}
|
||
|
||
#[test]
|
||
fn json_stringify_escape_backslash() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.stringify('a\\\\b')").unwrap();
|
||
assert!(result.is_string());
|
||
let s = string_value(&vm, result);
|
||
assert_eq!(s, "\"a\\\\b\"");
|
||
}
|
||
|
||
#[test]
|
||
fn json_stringify_nested_objects() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.stringify({a: {b: 1}})").unwrap();
|
||
assert!(result.is_string());
|
||
let s = string_value(&vm, result);
|
||
assert_eq!(s, "{\"a\":{\"b\":1}}");
|
||
}
|
||
|
||
#[test]
|
||
fn json_parse_empty_object() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.parse('{}')").unwrap();
|
||
assert!(result.is_object());
|
||
let obj = unsafe { &*result.as_js_object_ptr() };
|
||
assert_eq!(obj.prop_vec_len(), 0);
|
||
}
|
||
|
||
#[test]
|
||
fn json_parse_empty_array() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.parse('[]')").unwrap();
|
||
assert!(result.is_object());
|
||
let obj = unsafe { &*result.as_js_object_ptr() };
|
||
assert!(obj.is_array());
|
||
assert_eq!(obj.prop_vec_len(), 0);
|
||
}
|
||
|
||
#[test]
|
||
fn json_parse_int_is_double() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.parse('123')").unwrap();
|
||
assert!((result.as_double() - 123.0).abs() < 0.0001);
|
||
}
|
||
|
||
// -- Cycle detection --
|
||
|
||
#[test]
|
||
fn json_stringify_cycle_throws_type_error() {
|
||
let mut vm = Vm::new();
|
||
let err = eval(&mut vm, "var a = {}; a.self = a; JSON.stringify(a)");
|
||
assert!(err.is_err());
|
||
}
|
||
|
||
// -- 数字键对象(P1-2 回归防线) --
|
||
|
||
#[test]
|
||
fn json_parse_numeric_object_key_reads_via_index() {
|
||
// JSON 键 "5" 经字符串规范化映射整数键:o[5] 与 o["5"] 命中同一键。
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.parse('{\"5\":1}')[5]").unwrap();
|
||
assert!((result.as_double() - 1.0).abs() < 0.0001);
|
||
}
|
||
|
||
#[test]
|
||
fn json_parse_numeric_keys_do_not_clash_between_keys() {
|
||
// 多个数字键各自独立:{"0":7,"2024":8} 的 0 与 2024 不互相覆盖。
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "var o = JSON.parse('{\"0\":7,\"2024\":8}'); o[0] + o[2024]").unwrap();
|
||
assert!((result.as_double() - 15.0).abs() < 0.0001);
|
||
}
|
||
|
||
#[test]
|
||
fn json_parse_numeric_keys_stringify_roundtrip() {
|
||
// 数字键对象经 stringify 后键名保持 "5"(键字符串化语义)。
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "JSON.stringify(JSON.parse('{\"5\":1}'))").unwrap();
|
||
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)");
|
||
}
|