Files
OxideJS-New/crates/oxide_builtins/tests/builtins_json_tests.rs
T

784 lines
26 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval(source: &str) -> Result<(Vm, 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))?;
let mut vm = Vm::new();
let result = vm.run(&Arc::new(module))?;
Ok((vm, result))
}
fn stringify_val(val: &JsValue) -> String {
if val.is_string() {
unsafe { (*val.as_string_ptr()).to_owned_string() }
} else {
format!("{:?}", val)
}
}
// --- replacer array ---
#[test]
fn replacer_array_filters_properties() {
let (_vm, result) = eval(r#"JSON.stringify({a:1,b:2,c:3}, ['a','c'])"#).unwrap();
assert_eq!(stringify_val(&result), r#"{"a":1,"c":3}"#);
}
#[test]
fn replacer_array_numeric_keys() {
let (_vm, result) = eval(r#"JSON.stringify({a:1,b:2}, ['b'])"#).unwrap();
assert_eq!(stringify_val(&result), r#"{"b":2}"#);
}
// --- replacer function ---
#[test]
fn replacer_function_skip_property() {
let (_vm, result) =
eval(r#"JSON.stringify({a:1,b:2}, function(k,v){if(k==='a')return undefined;return v})"#).unwrap();
assert_eq!(stringify_val(&result), r#"{"b":2}"#);
}
#[test]
fn replacer_function_array_null() {
let (_vm, result) =
eval(r#"JSON.stringify([1,2,3], function(k,v){if(k==='1')return undefined;return v})"#).unwrap();
assert_eq!(stringify_val(&result), "[1,null,3]");
}
#[test]
fn replacer_function_transform() {
let (_vm, result) =
eval(r#"JSON.stringify({a:1}, function(k,v){if(typeof v==='number')return v*2;return v})"#).unwrap();
assert_eq!(stringify_val(&result), r#"{"a":2}"#);
}
// --- space ---
#[test]
fn space_number_indent() {
let (_vm, result) = eval(r#"JSON.stringify({a:1}, null, 2)"#).unwrap();
let s = stringify_val(&result);
assert!(s.len() > 5, "expected indented output, got: {}", s);
assert!(s.starts_with('{'), "should start with brace");
}
#[test]
fn space_negative_clamped() {
let (_vm, result) = eval(r#"JSON.stringify({a:1}, null, -5)"#).unwrap();
assert_eq!(stringify_val(&result), r#"{"a":1}"#);
}
// --- toJSON ---
#[test]
fn tojson_called_before_serialize() {
let (_vm, result) = eval(r#"JSON.stringify({toJSON:function(){return {x:1}}})"#).unwrap();
assert_eq!(stringify_val(&result), r#"{"x":1}"#);
}
#[test]
fn tojson_non_callable_ignored() {
let (_vm, result) = eval(r#"JSON.stringify({toJSON:'not-a-function', a:1})"#).unwrap();
// toJSON 属性是字符串,应按普通属性序列化。
assert!(stringify_val(&result).contains("toJSON"));
assert!(stringify_val(&result).contains(r#""a":1"#));
}
// --- cycle detection ---
#[test]
fn cycle_throws_type_error() {
let result = eval("var a={};a.self=a;JSON.stringify(a)");
match result {
Err(e) => assert!(e.to_lowercase().contains("circular"), "error should mention 'circular', got: {}", e),
Ok(_) => panic!("expected cycle error, got ok"),
}
}
#[test]
fn no_cycle_distinct_objects_ok() {
let (_vm, result) = eval(r#"JSON.stringify([{a:1},{a:1}])"#).unwrap();
assert_eq!(stringify_val(&result), r#"[{"a":1},{"a":1}]"#);
}
// --- reviver ---
#[test]
fn reviver_transform_values() {
let (_vm, result) =
eval(r#"JSON.parse('{"a":1,"b":2}', function(k,v){if(typeof v==='number')return v*2;return v})"#).unwrap();
assert!(result.is_object());
}
#[test]
fn reviver_delete_property() {
let (_vm, result) =
eval(r#"JSON.parse('{"a":1,"b":2}', function(k,v){if(k==='a')return undefined;return v})"#).unwrap();
// 属性被软删除(置为 undefined)。
// 为 test262 兼容,stringify 应省略 undefined 属性。
assert!(result.is_object());
}
#[test]
fn reviver_root_key_empty_string() {
let (_vm, result) = eval(r#"JSON.parse('42', function(k,v){return v+1})"#).unwrap();
assert!(result.is_int() || result.is_double(), "expected number");
if result.is_int() {
assert_eq!(result.as_int(), 43);
} else {
assert_eq!(result.as_double() as i32, 43);
}
}
#[test]
fn reviver_no_reviver_works() {
let (_vm, result) = eval(r#"JSON.parse('{"a":1}')"#).unwrap();
assert!(result.is_object());
}
// --- accessor 自身属性值读(SerializeJSONProperty 步 2 Get)---
// reverse arraylike + length accessor:getter 被触发,length 键保留在输出。
#[test]
fn stringify_reverse_arraylike_length_accessor() {
let (_vm, result) = eval(
"(() => { const o = {0:'x',1:'y',length:2}; \
Object.defineProperty(o,'length',{get(){return 2;},set(v){},enumerable:true,configurable:true}); \
Array.prototype.reverse.call(o); return JSON.stringify(o); })()",
)
.unwrap();
assert_eq!(stringify_val(&result), r#"{"0":"y","1":"x","length":2}"#);
}
// 对象属性访问器:字面量 getter 与 defineProperty 重定义 accessor 两形态。
#[test]
fn stringify_object_accessor_getter() {
let (_vm, result) = eval(r#"JSON.stringify({get x(){return 5;}, y:1})"#).unwrap();
assert_eq!(stringify_val(&result), r#"{"x":5,"y":1}"#);
let (_vm, result) = eval(
"(() => { const o = {x:1}; \
Object.defineProperty(o,'x',{get(){return 2;}}); return JSON.stringify(o); })()",
)
.unwrap();
assert_eq!(stringify_val(&result), r#"{"x":2}"#);
}
// getter 的 this 绑定为属性所在对象。
#[test]
fn stringify_accessor_this_binding() {
let (_vm, result) = eval(r#"JSON.stringify({v:1, get p(){return this.v+10;}})"#).unwrap();
assert_eq!(stringify_val(&result), r#"{"v":1,"p":11}"#);
}
// 数组 replacer 白名单判定先于 Get:非白名单键的 getter 不触发。
#[test]
fn stringify_whitelist_before_accessor_get() {
let out = eval_str(
"(() => { let fired = false; const o = {a:1, get b(){fired=true;return 2;}}; \
return JSON.stringify(o,['a']) + '|' + fired; })()",
)
.unwrap();
assert_eq!(out, r#"{"a":1}|false"#);
}
// 数组元素访问器:元素位触发 getter,值为 getter 返回值。
#[test]
fn stringify_array_element_accessor() {
let out = eval_str(
"(() => { const a = [1,2]; \
Object.defineProperty(a,0,{get(){return 9;},configurable:true}); \
Object.defineProperty(a,1,{get(){return 2;},configurable:true}); \
return JSON.stringify(a); })()",
)
.unwrap();
assert_eq!(out, "[9,2]");
}
// getter 返回值形态:function/undefined 省略,带 toJSON 的对象走钩子,setter-only 省略。
#[test]
fn stringify_accessor_return_shapes() {
let out = eval_str(
"(() => { const o = {}; \
Object.defineProperty(o,'f',{get(){ return function g(){return 7;}; },enumerable:true,configurable:true}); \
Object.defineProperty(o,'u',{get(){ return undefined; },enumerable:true,configurable:true}); \
Object.defineProperty(o,'t',{get(){ return {toJSON(){return 7;}}; },enumerable:true,configurable:true}); \
Object.defineProperty(o,'s',{set(v){},enumerable:true,configurable:true}); \
return JSON.stringify(o); })()",
)
.unwrap();
assert_eq!(out, r#"{"t":7}"#);
}
// space 缩进 + 访问器:getter 值正常入输出(判别 p 键存在;对象开括号换行
// 位置是 space 格式面 pre-existing 偏差,不属本钉面)。
#[test]
fn stringify_space_with_accessor() {
let (_vm, result) = eval(
"(() => { const o = {}; \
Object.defineProperty(o,'p',{get(){return [1];},enumerable:true,configurable:true}); \
return JSON.stringify({o},null,2); })()",
)
.unwrap();
let s = stringify_val(&result);
assert!(s.contains(r#""p": ["#), "getter value missing from indented output: {}", s);
assert!(s.contains("1"), "getter value absent: {}", s);
}
// getter 抛非 Error 原始值:原值传播(捕获侧见 number 42,非 TypeError 对象)。
#[test]
fn stringify_accessor_throws_original_value() {
let out = eval_str(
"(() => { try { JSON.stringify({get x(){throw 42;}, y:1}); } \
catch (e) { return typeof e + '|' + e; } })()",
)
.unwrap();
assert_eq!(out, "number|42");
}
// toJSON 抛非 Error 原始值:原值传播。
#[test]
fn stringify_tojson_throws_original_value() {
let out = eval_str(
"(() => { try { JSON.stringify({toJSON(){throw 42;}}); } \
catch (e) { return typeof e + '|' + e; } })()",
)
.unwrap();
assert_eq!(out, "number|42");
}
// replacer 抛非 Error 原始值:原值传播。
#[test]
fn stringify_replacer_throws_original_value() {
let out = eval_str(
"(() => { try { JSON.stringify({a:1}, function(k,v){ if(k==='a') throw 'boom'; return v; }); } \
catch (e) { return typeof e + '|' + e; } })()",
)
.unwrap();
assert_eq!(out, "string|boom");
}
// 嵌套序:toJSON 先于 replacer 应用,replacer 见到 toJSON 结果。
#[test]
fn stringify_nested_tojson_before_replacer() {
let out = eval_str(
"(() => { let seen; const inner = {toJSON(){return 'T';}}; \
const s = JSON.stringify({a:inner}, function(k,v){ if(k==='a') seen = String(v); return v; }); \
return seen + '|' + s; })()",
)
.unwrap();
assert_eq!(out, r#"T|{"a":"T"}"#);
}
// 顶层 toJSON 结果自身带 toJSON:只应用一次。
#[test]
fn stringify_top_level_tojson_once() {
let (_vm, result) = eval(r#"JSON.stringify({toJSON(){return {toJSON(){return 'inner';}};}})"#).unwrap();
assert_eq!(stringify_val(&result), r#"{}"#);
}
// 访问器返回自身成环:getter 触发后环检抛 TypeError。
#[test]
fn stringify_accessor_self_cycle() {
let result = eval(
"(() => { const o = {n:1}; \
Object.defineProperty(o,'self',{get(){return o;},enumerable:true,configurable:true}); \
return JSON.stringify(o); })()",
);
match result {
Err(e) => assert!(e.to_lowercase().contains("circular"), "expected circular error, got: {}", e),
Ok(_) => panic!("expected cycle error"),
}
}
// 不可枚举访问器:省略(守卫,防修后翻红)。
#[test]
fn stringify_non_enumerable_accessor_skipped() {
let out = eval_str(
"(() => { const o = {v:2}; \
Object.defineProperty(o,'h',{get(){return 99;},enumerable:false,configurable:true}); \
return JSON.stringify(o); })()",
)
.unwrap();
assert_eq!(out, r#"{"v":2}"#);
}
// 嵌套 toJSON 结果为对象:单应用下正常展开(守卫)。
#[test]
fn stringify_nested_tojson_result_object() {
let (_vm, result) = eval(r#"JSON.stringify({a:{toJSON(){return {};}}})"#).unwrap();
assert_eq!(stringify_val(&result), r#"{"a":{}}"#);
}
// 数组附加命名属性:序列化省略(守卫)。
#[test]
fn stringify_array_ignores_named_props() {
let out = eval_str("(() => { const a = [1,2]; a.x = 'hello'; return JSON.stringify(a); })()").unwrap();
assert_eq!(out, "[1,2]");
}
// --- BigInt 值位(SerializeJSONProperty 步 10 抛 TypeError;步 2 查 toJSON)---
fn assert_bigint_serialize_err(source: &str) {
let result = eval(source);
match result {
Err(e) => {
assert!(
e.to_lowercase().contains("do not know how to serialize a bigint"),
"expected BigInt serialize TypeError, got: {}",
e
)
}
Ok(_) => panic!("expected BigInt serialize TypeError, got ok: {}", source),
}
}
// 顶层原语四值位:1n / -1n / 0n / -0n 恒抛,无文本产出。
#[test]
fn stringify_bigint_top_level_throws() {
assert_bigint_serialize_err("JSON.stringify(1n)");
assert_bigint_serialize_err("JSON.stringify(-1n)");
assert_bigint_serialize_err("JSON.stringify(0n)");
assert_bigint_serialize_err("JSON.stringify(-0n)");
}
// 大位宽 BigInt 同样抛错(不产十进制文本)。
#[test]
fn stringify_bigint_wide_bits_throws() {
assert_bigint_serialize_err("JSON.stringify(2n ** 32n + 1n)");
}
// 嵌套对象属性与数组元素位(含深嵌套、稀疏)均经值位臂抛错。
#[test]
fn stringify_bigint_nested_throws() {
assert_bigint_serialize_err("JSON.stringify({a:1n})");
assert_bigint_serialize_err("JSON.stringify([1n,2n,[3n]])");
assert_bigint_serialize_err("JSON.stringify({a:{b:1n}})");
assert_bigint_serialize_err("JSON.stringify([,1n])");
}
// 空格缩进不改变抛错路径。
#[test]
fn stringify_bigint_space_indent_throws() {
assert_bigint_serialize_err("JSON.stringify({a:1n}, null, 2)");
}
// replacer 白名单命中键后仍走值位抛错。
#[test]
fn stringify_bigint_whitelist_throws() {
assert_bigint_serialize_err("JSON.stringify({a:1n}, ['a'])");
}
// 装箱 BigInt(步 4d 无条件解包 [[BigIntData]])与裸值同抛。
#[test]
fn stringify_bigint_boxed_throws() {
assert_bigint_serialize_err("JSON.stringify(Object(0n))");
assert_bigint_serialize_err("JSON.stringify({a:Object(1n)})");
}
// BigInt64Array 元素位经对象臂读值后在值位臂抛错。
#[test]
fn stringify_bigint64array_throws() {
assert_bigint_serialize_err("JSON.stringify(new BigInt64Array([1n,2n]))");
}
// replacer 透传不改变值位;replacer 结果再入值位臂同样抛错。
#[test]
fn stringify_bigint_replacer_throws() {
assert_bigint_serialize_err("JSON.stringify(1n, (k,v) => v)");
assert_bigint_serialize_err("JSON.stringify({a:1n}, (k,v) => v)");
assert_bigint_serialize_err("JSON.stringify({a:1}, (k,v) => 2n)");
}
// BigInt.prototype 挂数据属性 toJSON:钩子被触发,返回串值正常序列化。
#[test]
fn stringify_bigint_tojson_hook_top_level() {
let out = eval_str(
"(() => { BigInt.prototype.toJSON = function(){ return this.toString(); }; \
const s = JSON.stringify(0n); \
delete BigInt.prototype.toJSON; \
return s; })()",
)
.unwrap();
assert_eq!(out, r#""0""#);
}
// toJSON 钩子嵌套对象属性位:receiver 为 BigInt 原语,this.toString() 生效。
#[test]
fn stringify_bigint_tojson_hook_nested() {
let out = eval_str(
"(() => { BigInt.prototype.toJSON = function(){ return this.toString(); }; \
const s = JSON.stringify({a:1n}); \
delete BigInt.prototype.toJSON; \
return s; })()",
)
.unwrap();
assert_eq!(out, r#"{"a":"1"}"#);
}
// 守卫:replacer 先于值位转换 BigInt → number,不抛错。
#[test]
fn stringify_bigint_replacer_transform_guard() {
let out = eval_str("JSON.stringify({b:1n}, function(k,v){ if(typeof v==='bigint') return Number(v); return v; })")
.unwrap();
assert_eq!(out, r#"{"b":1}"#);
}
// --- reviver 抛错原值传播(InternalizeJSONProperty 步 3)---
// reviver 抛非 Error 原语:原值传播(捕获侧见 number 42,非 TypeError 对象)。
#[test]
fn parse_reviver_throws_original_value() {
let out = eval_str(
"(() => { try { JSON.parse('0', function(){ throw 42; }); } \
catch (e) { return typeof e + '|' + e; } })()",
)
.unwrap();
assert_eq!(out, "number|42");
}
// reviver 抛字符串:原始串传播。
#[test]
fn parse_reviver_throws_string() {
let out = eval_str(
"(() => { try { JSON.parse('0', function(){ throw 'boom'; }); } \
catch (e) { return typeof e + '|' + e; } })()",
)
.unwrap();
assert_eq!(out, "string|boom");
}
// reviver 抛 Error 对象:原错误对象传播(种类与消息保真)。
#[test]
fn parse_reviver_throws_range_error() {
let out = eval_str(
"(() => { try { JSON.parse('0', function(){ throw new RangeError('re'); }); } \
catch (e) { return e.constructor.name + '|' + e.message; } })()",
)
.unwrap();
assert_eq!(out, "RangeError|re");
}
// --- reviver 读写臂(Get 读臂 / [[Delete]] + CreateDataProperty 写臂 / 根级无写回)---
// 读臂 Get:reviver 删自身 b 后读值走原型链,CreateDataProperty 重建自身 b(对象臂)。
#[test]
fn parse_reviver_object_get_prop_from_prototype() {
let out = eval_str(
"(() => { Object.prototype.b = 3; \
const obj = JSON.parse('{\"a\": 1, \"b\": 2}', function(key, value) { \
if (key === 'a') { delete this.b; } return value; }); \
delete Object.prototype.b; \
return [obj.a, obj.hasOwnProperty('b'), obj.b].join('|'); })()",
)
.unwrap();
assert_eq!(out, "1|true|3");
}
// 读臂 Get:数组臂同款(删自身 [1] 后原型读 3 重建)。
#[test]
fn parse_reviver_array_get_prop_from_prototype() {
let out = eval_str(
"(() => { Array.prototype[1] = 3; \
const arr = JSON.parse('[1, 2]', function(key, value) { \
if (key === '0') { delete this[1]; } return value; }); \
delete Array.prototype[1]; \
return [arr[0], arr.hasOwnProperty('1'), arr[1]].join('|'); })()",
)
.unwrap();
assert_eq!(out, "1|true|3");
}
// 读臂 Get:自身访问器 getter 抛错穿透,原值传播(非 TypeError 折叠)。
#[test]
fn parse_reviver_get_name_err_original_value() {
let out = eval_str(
"(() => { try { JSON.parse('[0,0]', function() { \
Object.defineProperty(this, '1', { get() { throw 42; } }); }); } \
catch (e) { return typeof e + '|' + e; } return 'nothrow'; })()",
)
.unwrap();
assert_eq!(out, "number|42");
}
// 写臂 CreateDataProperty:非可配置属性写败静默,保旧值(对象臂)。
#[test]
fn parse_reviver_object_non_configurable_prop_create() {
let out = eval_str(
"(() => { const obj = JSON.parse('{\"a\": 1, \"b\": 2}', function(key, value) { \
if (key === 'a') { Object.defineProperty(this, 'b', {configurable: false}); } \
if (key === 'b') return 22; return value; }); \
return [obj.a, obj.b].join('|'); })()",
)
.unwrap();
assert_eq!(out, "1|2");
}
// 写臂 [[Delete]]:非可配置属性删败静默,属性保留(对象臂)。
#[test]
fn parse_reviver_object_non_configurable_prop_delete() {
let out = eval_str(
"(() => { const obj = JSON.parse('{\"a\": 1, \"b\": 2}', function(key, value) { \
if (key === 'a') { Object.defineProperty(this, 'b', {configurable: false}); } \
if (key === 'b') return; return value; }); \
return [obj.a, obj.hasOwnProperty('b'), obj.b].join('|'); })()",
)
.unwrap();
assert_eq!(out, "1|true|2");
}
// 写臂同款两臂(数组臂)。
#[test]
fn parse_reviver_array_non_configurable_prop_create() {
let out = eval_str(
"(() => { const arr = JSON.parse('[1, 2]', function(key, value) { \
if (key === '0') { Object.defineProperty(this, '1', {configurable: false}); } \
if (key === '1') return 22; return value; }); \
return [arr[0], arr[1]].join('|'); })()",
)
.unwrap();
assert_eq!(out, "1|2");
}
#[test]
fn parse_reviver_array_non_configurable_prop_delete() {
let out = eval_str(
"(() => { const arr = JSON.parse('[1, 2]', function(key, value) { \
if (key === '0') { Object.defineProperty(this, '1', {configurable: false}); } \
if (key === '1') return; return value; }); \
return [arr[0], arr.hasOwnProperty('1'), arr[1]].join('|'); })()",
)
.unwrap();
assert_eq!(out, "1|true|2");
}
// 根级无写回:wrapper `''` 槽值/描述符恒保留(reviver 返 undefined 不抹槽)。
#[test]
fn parse_reviver_wrapper_slot_preserved() {
let out = eval_str(
"(() => { let w; JSON.parse('2', function() { w = this; }); \
const d = Object.getOwnPropertyDescriptor(w, ''); \
return [w[''], Object.getOwnPropertyNames(w).length, d.writable, d.enumerable, d.configurable].join('|'); })()",
)
.unwrap();
assert_eq!(out, "2|1|true|true|true");
}
// 根返回值 = 根级 reviver 返回值原样。
#[test]
fn parse_reviver_root_return_value() {
let (_vm, result) = eval(r#"JSON.parse('5', function(k,v){return v*2})"#).unwrap();
assert!(result.is_int() || result.is_double(), "expected number");
if result.is_int() {
assert_eq!(result.as_int(), 10);
} else {
assert_eq!(result.as_double() as i32, 10);
}
}
// this 面守卫:根级 this = wrapper(`''` 槽即结果对象)、子级 this = 容器,
// 前向改写 `this[1] = 99` 在子读点生效。
#[test]
fn parse_reviver_this_binding_guard() {
let out = eval_str(
"(() => { let rootThis, childThis; \
const arr = JSON.parse('[1, 2]', function(key, value) { \
if (key === '') rootThis = this; \
if (key === '0') childThis = this; \
if (key === '0') this[1] = 99; return value; }); \
return [rootThis[''] === arr, childThis === arr, rootThis === childThis, arr[1]].join('|'); })()",
)
.unwrap();
assert_eq!(out, "true|true|false|99");
}
// --- PropertyList 列表序(SerializeJSONObject 步 5 K = P)---
// 顶层对象:输出序 = 白名单列表序(非自身枚举序)。
#[test]
fn replacer_array_list_order() {
let out = eval_str(r#"JSON.stringify({b:1,a:2,c:3}, ['c','b','a'])"#).unwrap();
assert_eq!(out, r#"{"c":3,"b":1,"a":2}"#);
}
// 嵌套层级每级同用列表序。
#[test]
fn replacer_array_nested_list_order() {
let out = eval_str(r#"JSON.stringify({a:{b:2,c:3}}, ['c','b','a'])"#).unwrap();
assert_eq!(out, r#"{"a":{"c":3,"b":2}}"#);
}
// TypedArray:列表序定输出序,整数索引名读元素。
#[test]
fn replacer_array_typed_array_list_order() {
let out = eval_str(r#"JSON.stringify(new Float64Array([1,2,3]), ['2','0','1'])"#).unwrap();
assert_eq!(out, r#"{"2":3,"0":1,"1":2}"#);
}
// --- 白名单元素强转(Stringify 步 4:装箱 String/Number 走 ToPrimitive)---
// 装箱 Number 自定义 toString:string hint 调用结果作键。
#[test]
fn replacer_boxed_number_tostring() {
let out = eval_str(
"(() => { const n = new Number(10); \
n.toString = function(){ return 'toString'; }; \
return JSON.stringify({10:1,toString:2}, [n]); })()",
)
.unwrap();
assert_eq!(out, r#"{"toString":2}"#);
}
// 素形装箱 Number:盒值 → "10"。
#[test]
fn replacer_boxed_number_plain() {
let out = eval_str(r#"JSON.stringify({10:1}, [new Number(10)])"#).unwrap();
assert_eq!(out, r#"{"10":1}"#);
}
// 素形装箱 String:盒值 → "str"。
#[test]
fn replacer_boxed_string_plain() {
let out = eval_str(r#"JSON.stringify({str:4}, [new String('str')])"#).unwrap();
assert_eq!(out, r#"{"str":4}"#);
}
// --- 白名单键 Get 语义(原型链 + 不可枚举自身 + 访问器)---
// 原型链 getter 键在白名单给定时命中(getter 触发)。
#[test]
fn replacer_whitelist_proto_getter() {
let out = eval_str(
"(() => { const base = {get b(){ return 9; }}; \
const o = Object.create(base); o.a = 1; \
return JSON.stringify(o, ['a','b']); })()",
)
.unwrap();
assert_eq!(out, r#"{"a":1,"b":9}"#);
}
// 不可枚举自身键在白名单给定时同样序列化。
#[test]
fn replacer_whitelist_non_enumerable_own() {
let out = eval_str(
"(() => { const o = {a:1}; \
Object.defineProperty(o,'b',{value:2,enumerable:false,configurable:true}); \
return JSON.stringify(o, ['a','b']); })()",
)
.unwrap();
assert_eq!(out, r#"{"a":1,"b":2}"#);
}
// 嵌套层级原型链 getter 同样命中。
#[test]
fn replacer_whitelist_nested_proto_getter() {
let out = eval_str(
"(() => { const base = {get x(){ return 7; }}; \
const o = Object.create(base); o.y = 1; \
return JSON.stringify({o}, ['o','x','y']); })()",
)
.unwrap();
assert_eq!(out, r#"{"o":{"x":7,"y":1}}"#);
}
// --- toJSON 查表 Get 语义(accessor 形 toJSON 触发)---
// BigInt.prototype toJSON accessor 形:getter 触发,receiver = BigInt 原语。
#[test]
fn stringify_bigint_tojson_accessor() {
let out = eval_str(
"(() => { \"use strict\"; \
Object.defineProperty(BigInt.prototype, 'toJSON', \
{get(){ return () => typeof this; }, configurable:true}); \
const s = JSON.stringify(1n); \
delete BigInt.prototype.toJSON; \
return s; })()",
)
.unwrap();
assert_eq!(out, r#""bigint""#);
}
// --- 守卫(修后零漂移)---
// 白名单缺失名:空对象输出。
#[test]
fn replacer_whitelist_missing_name() {
let out = eval_str(r#"JSON.stringify({b:1}, ['zzz'])"#).unwrap();
assert_eq!(out, "{}");
}
// TA 白名单非索引名(length)经 Get 读命名键。
#[test]
fn replacer_whitelist_typed_array_length() {
let out = eval_str(r#"JSON.stringify(new Float64Array(2), ['length'])"#).unwrap();
assert_eq!(out, r#"{"length":2}"#);
}
// 数组容器不查白名单。
#[test]
fn replacer_whitelist_array_container_ignores() {
let out = eval_str(r#"JSON.stringify([1,2], ['1'])"#).unwrap();
assert_eq!(out, "[1,2]");
}
// 装箱 Boolean / 普通对象元素跳过(不产键)。
#[test]
fn replacer_whitelist_skip_object_elements() {
let out = eval_str(r#"JSON.stringify({b:5}, [new Boolean(true), {custom:1}])"#).unwrap();
assert_eq!(out, "{}");
}
// 白名单命中键值为 undefined:跳键。
#[test]
fn replacer_whitelist_undefined_value_skipped() {
let out = eval_str(r#"JSON.stringify({a:1,b:undefined}, ['a','b'])"#).unwrap();
assert_eq!(out, r#"{"a":1}"#);
}
// --- 嵌套深度上限(递归限制)---
// 深度 128 层恰好过:128 层嵌套数组正常解析。
#[test]
fn parse_depth_128_ok() {
let out = eval_str(
"(() => { const v = JSON.parse('['.repeat(128) + ']'.repeat(128)); \
return String(Array.isArray(v) && v.length === 1); })()",
)
.unwrap();
assert_eq!(out, "true");
}
// 深度 129 层超限:抛 SyntaxError(递归限制),而非进程 abort。
#[test]
fn parse_depth_129_throws() {
let out = eval_str(
"(() => { try { JSON.parse('['.repeat(129) + ']'.repeat(129)); return 'no-throw'; } \
catch (e) { return e.constructor.name + '|' + e.message; } })()",
)
.unwrap();
assert_eq!(out, "SyntaxError|recursion limit exceeded");
}
// 深度计数复位:深嵌套解析超限失败后,随后普通嵌套解析不受残留计数影响。
#[test]
fn parse_depth_count_resets() {
let out = eval_str(
"(() => { try { JSON.parse('['.repeat(129) + ']'.repeat(129)); } catch (e) {} \
const v = JSON.parse('{\"a\":{\"b\":[1]}}'); \
return String(v.a.b[0]); })()",
)
.unwrap();
assert_eq!(out, "1");
}
fn eval_str(source: &str) -> Result<String, String> {
let (vm, result) = eval(source)?;
vm.lookup_str(result)
.ok_or_else(|| "completion value is not a string".to_string())
}