fix: 内置属性读取触发访问器并透传原始异常值

This commit is contained in:
2026-09-20 05:51:45 +00:00
parent b876495c00
commit 2e6c83576b
5 changed files with 410 additions and 54 deletions
@@ -887,3 +887,56 @@ fn test_species_ctor_null_primitive_throws_four_mounts() {
.unwrap();
assert_eq!(out, "16");
}
// 引擎钉:reverse arraylike 单端缺失——(N,S) 臂 Delete 先于 Set,缺端不物化。
#[test]
fn test_reverse_arraylike_single_side_missing() {
let out = eval_str(
"(() => { const o = {0:'a', length:2}; Array.prototype.reverse.call(o); \
return (0 in o) + '|' + (1 in o) + '|' + o[1] + '|' + o.length; })()",
)
.unwrap();
assert_eq!(out, "false|true|a|2");
}
// 引擎钉:reverse arraylike 洞位保持——洞不物化、length 不变。
#[test]
fn test_reverse_arraylike_hole_kept() {
let out = eval_str(
"(() => { const o = {0:'a', 2:'b', length:3}; Array.prototype.reverse.call(o); \
return (0 in o) + '|' + (1 in o) + '|' + (2 in o) + '|' + o[0] + '|' + o[2] + '|' + o.length; })()",
)
.unwrap();
assert_eq!(out, "true|false|true|b|a|3");
}
// 引擎钉:reverse arraylike accessor length——getter 触发一次,抛错原值传播。
#[test]
fn test_reverse_arraylike_length_accessor_throws() {
let result = eval_value(
"(() => { const o = {0:'x', 1:'y', length:2}; \
Object.defineProperty(o,'length',{get(){throw 99;},set(v){},enumerable:true,configurable:true}); \
Array.prototype.reverse.call(o); return 'no-throw'; })()",
);
match result {
Err(e) => assert!(e.contains("99"), "error should carry thrown 99, got: {}", e),
Ok(_) => panic!("expected accessor length throw to propagate"),
}
}
// 引擎钉:reverse arraylike length 值形态——ToLength 取整定界、原值不写回。
#[test]
fn test_reverse_arraylike_length_not_written_back() {
let out = eval_str(
"(() => { const o = {0:'x', 1:'y', length:'2'}; Array.prototype.reverse.call(o); \
return o.length + '|' + o[0] + '|' + o[1]; })()",
)
.unwrap();
assert_eq!(out, "2|y|x");
let out = eval_str(
"(() => { const o = {0:'x', 1:'y', 2:'z', length:2.7}; Array.prototype.reverse.call(o); \
return o.length + '|' + o[0] + '|' + o[1]; })()",
)
.unwrap();
assert_eq!(out, "2.7|y|x");
}
@@ -141,3 +141,189 @@ 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]");
}
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())
}
@@ -223,6 +223,61 @@ fn test_values_returns_array() {
assert!(result.is_int() || result.is_double());
}
// Object.values 自身 accessor:getter 被触发,值为 getter 返回值。
#[test]
fn test_values_triggers_accessor_getter() {
let out = eval_str(
"(() => { const o = {}; \
Object.defineProperty(o,'a',{get(){return 1;},enumerable:true,configurable:true}); \
o.b = 2; return Object.values(o).join(','); })()",
);
assert_eq!(out, "1,2");
}
// Object.entries 自身 accessor:[key, getter值] 对。
#[test]
fn test_entries_triggers_accessor_getter() {
let out = eval_str(
"(() => { const o = {}; \
Object.defineProperty(o,'a',{get(){return 1;},enumerable:true,configurable:true}); \
o.b = 2; return JSON.stringify(Object.entries(o)); })()",
);
assert_eq!(out, r#"[["a",1],["b",2]]"#);
}
// 数组元素位 accessor:values 对元素触发 getter。
#[test]
fn test_values_array_element_accessor() {
let out = eval_str(
"(() => { const a = [3,4]; \
Object.defineProperty(a,2,{get(){return 9;},enumerable:true,configurable:true}); \
return Object.values(a).join(','); })()",
);
assert_eq!(out, "3,4,9");
}
// getter 的 this 绑定为属性所在对象。
#[test]
fn test_values_accessor_this_binding() {
let out = eval_str("(() => Object.values({v:1, get p(){return this.v;}}).join(','))()");
assert_eq!(out, "1,1");
}
// values/entries 的 getter 抛非 Error 原始值:原值传播(捕获侧见原类型与值)。
#[test]
fn test_values_entries_accessor_throws_original_value() {
let out = eval_str(
"(() => { try { Object.values({get a(){throw 'boom';}, b:1}); } \
catch (e) { return typeof e + '|' + e; } })()",
);
assert_eq!(out, "string|boom");
let out = eval_str(
"(() => { try { Object.entries({get a(){throw 42;}, b:1}); } \
catch (e) { return typeof e + '|' + e; } })()",
);
assert_eq!(out, "number|42");
}
#[test]
fn test_get_own_property_descriptor_no_args_type_error() {
assert!(eval("Object.getOwnPropertyDescriptor()").is_err());