Files
OxideJS-New/crates/oxide_vm/tests/array_length_define_tests.rs
T

251 lines
11 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.
//! 数组 length 虚拟属性的 define 侧回归:`Object.defineProperty` / `Reflect.defineProperty`
//! 的截断、增长、可写位收窄、非法长度 RangeError 与访问器拒绝。
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_parser::Allocator;
use oxide_vm::vm::Vm;
/// 求值并取字符串结果内容(字符串值走 lookup_str,非字符串走 Display)。
fn eval_str(source: &str) -> String {
let allocator = Allocator::default();
let program = oxide_parser::parse(&allocator, source).expect("parse ok");
let module = Compiler::new().compile(&program).expect("compile ok");
let mut vm = Vm::new();
let val = vm.run(&Arc::new(module)).expect("run ok");
if val.is_string() {
vm.lookup_str(val).unwrap_or_default()
} else {
format!("{val}")
}
}
#[test]
fn define_length_value_truncates_elements() {
// value 收缩:元素区被截断,越界元素不可见。
assert_eq!(
eval_str("var a=[1,2,3]; Object.defineProperty(a,'length',{value:1}); ''+a.length+':'+a[2]+':'+(2 in a)"),
"1:undefined:false"
);
// value 增长:新增槽为稀疏空洞。
assert_eq!(
eval_str("var a=[1]; Object.defineProperty(a,'length',{value:3}); ''+a.length+':'+(1 in a)+':'+(2 in a)"),
"3:false:false"
);
}
#[test]
fn define_length_value_writable_defaults_to_current() {
// 只给 value:writable 缺省保持当前值 true。
assert_eq!(
eval_str("var a=[1,2]; Object.defineProperty(a,'length',{value:1}); ''+Object.getOwnPropertyDescriptor(a,'length').writable"),
"true"
);
}
#[test]
fn define_length_non_writable_blocks_sloppy_assignment() {
assert_eq!(
eval_str("var a=[1,2,3]; Object.defineProperty(a,'length',{writable:false}); (function(){a.length=1;})(); ''+a.length"),
"3"
);
}
#[test]
fn define_length_non_writable_throws_strict_assignment() {
assert_eq!(
eval_str("var a=[1,2,3]; Object.defineProperty(a,'length',{writable:false}); (function(){'use strict'; try{a.length=1;return 'no-throw';}catch(e){return e.name+':'+a.length;}})()"),
"TypeError:3"
);
}
#[test]
fn define_length_non_writable_same_value_succeeds() {
assert_eq!(
eval_str("var a=[1,2]; Object.defineProperty(a,'length',{writable:false}); Object.defineProperty(a,'length',{value:2}); ''+a.length"),
"2"
);
}
#[test]
fn define_length_invalid_values_throw_range_error() {
let src = "var r=[]; [-1,NaN,4294967296,1.5].forEach(function(v){try{Object.defineProperty([],'length',{value:v});r.push('no-throw');}catch(e){r.push(e.name);}}); r.join(',')";
assert_eq!(eval_str(src), "RangeError,RangeError,RangeError,RangeError");
// 非法值检查早于 configurable 收窄校验。
assert_eq!(
eval_str("try{Object.defineProperty([],'length',{value:-1,configurable:true});'no-throw';}catch(e){e.name}"),
"RangeError"
);
}
#[test]
fn define_length_descriptor_validation_throws_type_error() {
// configurable/enumerable 不得放宽。
assert_eq!(
eval_str("try{Object.defineProperty([],'length',{configurable:true});'no-throw';}catch(e){e.name}"),
"TypeError"
);
// 非可写后不得再请求 writable。
assert_eq!(
eval_str("var a=[1]; Object.defineProperty(a,'length',{writable:false}); try{Object.defineProperty(a,'length',{writable:true});'no-throw';}catch(e){e.name}"),
"TypeError"
);
}
#[test]
fn define_length_accessor_descriptor_rejected() {
assert_eq!(
eval_str("var r=[]; try{Object.defineProperty([],'length',{get:function(){return 1;}});r.push('no-throw');}catch(e){r.push(e.name);} r.push(Reflect.defineProperty([],'length',{set:function(v){}})); r.join(',')"),
"TypeError,false"
);
}
#[test]
fn define_length_shrink_blocked_by_non_configurable_element() {
assert_eq!(
eval_str("var a=[1,2,3]; Object.defineProperty(a,'2',{configurable:false}); var r; try{Object.defineProperty(a,'length',{value:1});r='no-throw';}catch(e){r=e.name;} r+':'+a.length"),
"TypeError:3"
);
}
#[test]
fn define_length_shrink_partial_truncation_at_blocker() {
// 阻挡索引 = newLen(index 1):length 收敛到 2,其上元素被删除。
assert_eq!(
eval_str("var a=[0,1,2,3,4]; Object.defineProperty(a,'1',{configurable:false}); var r; try{Object.defineProperty(a,'length',{value:1});r='no-throw';}catch(e){r=e.name;} r+':'+a.length+':'+a.hasOwnProperty('1')+':'+a.hasOwnProperty('2')+':'+a.hasOwnProperty('4')"),
"TypeError:2:true:false:false"
);
// 阻挡索引 = newLen+1(index 2):length 收敛到 3,index 1/2 保留、index 3/4 删除。
assert_eq!(
eval_str("var a=[0,1,2,3,4]; Object.defineProperty(a,'2',{configurable:false}); var r; try{Object.defineProperty(a,'length',{value:1});r='no-throw';}catch(e){r=e.name;} r+':'+a.length+':'+a.hasOwnProperty('1')+':'+a.hasOwnProperty('3')"),
"TypeError:3:true:false"
);
// 阻挡索引 = oldLen-2(index 3):length 收敛到 4,仅 index 4 删除。
assert_eq!(
eval_str("var a=[0,1,2,3,4]; Object.defineProperty(a,'3',{configurable:false}); var r; try{Object.defineProperty(a,'length',{value:1});r='no-throw';}catch(e){r=e.name;} r+':'+a.length+':'+a.hasOwnProperty('3')+':'+a.hasOwnProperty('4')"),
"TypeError:4:true:false"
);
}
#[test]
fn define_length_shrink_blocker_is_highest_non_configurable() {
// 规范删除循环自高向低:多个不可配置索引时以最高者确定最终长度。
assert_eq!(
eval_str("var a=[0,1,2,3,4]; Object.defineProperty(a,'1',{configurable:false}); Object.defineProperty(a,'3',{configurable:false}); var r; try{Object.defineProperty(a,'length',{value:1});r='no-throw';}catch(e){r=e.name;} r+':'+a.length+':'+a.hasOwnProperty('1')+':'+a.hasOwnProperty('2')+':'+a.hasOwnProperty('3')+':'+a.hasOwnProperty('4')"),
"TypeError:4:true:true:true:false"
);
}
#[test]
fn define_length_shrink_partial_truncation_sets_writable() {
// 部分截断失败时 writable 收窄仍生效:length 收敛到阻挡索引 + 1 且不可写。
assert_eq!(
eval_str("var a=[0,1,2,3]; Object.defineProperty(a,'1',{configurable:false}); var r; try{Object.defineProperty(a,'length',{value:0,writable:false});r='no-throw';}catch(e){r=e.name;} var d=Object.getOwnPropertyDescriptor(a,'length'); r+':'+a.length+':'+d.writable+':'+a.hasOwnProperty('2')"),
"TypeError:2:false:false"
);
}
#[test]
fn define_length_coercion_exception_propagates_original_value() {
// 强转期 valueOf 抛出的自定义 Error 在两个入口都须原值重抛(identity 不改写)。
let obj_src = "var err=new Error('boom'); var v={valueOf:function(){throw err;}}; var c=null; try{Object.defineProperty([],'length',{value:v});}catch(e){c=e;} (c===err)+':'+c.name";
let ref_src = "var err=new TypeError('boom'); var v={valueOf:function(){throw err;}}; var c=null; try{Reflect.defineProperty([],'length',{value:v});}catch(e){c=e;} (c===err)+':'+c.name";
assert_eq!(eval_str(obj_src), "true:Error");
assert_eq!(eval_str(ref_src), "true:TypeError");
// Symbol.toPrimitive 抛出的异常同样原值重抛。
assert_eq!(
eval_str("var err=new RangeError('sym'); var v={}; v[Symbol.toPrimitive]=function(h){throw err;}; var c=null; try{Reflect.defineProperty([],'length',{value:v});}catch(e){c=e;} (c===err)+':'+c.name"),
"true:RangeError"
);
}
#[test]
fn define_length_symbol_value_throws_type_error() {
// value=Symbol() 无 ToNumber 语义:两入口均抛 TypeError(Reflect 不得吞成 false)。
assert_eq!(
eval_str("var r=[]; try{Object.defineProperty([],'length',{value:Symbol()});r.push('no-throw');}catch(e){r.push(e.name);} try{Reflect.defineProperty([],'length',{value:Symbol()});r.push('no-throw');}catch(e){r.push(e.name);} r.join(',')"),
"TypeError,TypeError"
);
}
#[test]
fn define_length_bigint_value_throws_type_error() {
// ToNumber(BigInt) 抛 TypeError:define 路径不得把 1n 近似为 1.0 接受。
assert_eq!(
eval_str("var r=[]; try{Object.defineProperty([],'length',{value:1n});r.push('no-throw');}catch(e){r.push(e.name);} try{Reflect.defineProperty([],'length',{value:1n});r.push('no-throw');}catch(e){r.push(e.name);} r.join(',')"),
"TypeError,TypeError"
);
}
#[test]
fn define_properties_length_range_error_kind() {
// Object.defineProperties 须与单属性入口一致地保留 RangeError kind。
assert_eq!(
eval_str("var r=[]; try{Object.defineProperties([],{length:{value:-1}});r.push('no-throw');}catch(e){r.push(e.name);} try{Object.defineProperties([],{length:{value:1.5}});r.push('no-throw');}catch(e){r.push(e.name);} r.join(',')"),
"RangeError,RangeError"
);
}
#[test]
fn define_length_configurable_true_error_leaves_elements() {
// TypeError 后元素区不截断。
assert_eq!(
eval_str("var a=[1,2,3]; try{Object.defineProperty(a,'length',{value:1,configurable:true});}catch(e){} ''+a.length+':'+a[2]"),
"3:3"
);
}
#[test]
fn define_length_coerces_value_twice_before_validation() {
// ToUint32 + ToNumber 各触发一次 valueOf;第二次强转的副作用收窄可写位后
// 由描述符校验拒绝。
assert_eq!(
eval_str("var a=[1,2], n=0; var v={valueOf:function(){n++;return a.length;}}; Object.defineProperty(a,'length',{value:v}); ''+a.length+':'+n"),
"2:2"
);
assert_eq!(
eval_str("var a=[1,2,3], n=0; var v={valueOf:function(){n++;Object.defineProperty(a,'length',{writable:false});return a.length;}}; var r; try{Object.defineProperty(a,'length',{value:v,writable:true});r='no-throw';}catch(e){r=e.name;} r+':'+n"),
"TypeError:2"
);
}
#[test]
fn reflect_define_length_error_channel() {
// 非法长度保留 RangeError kind(抛而非 false);普通失败投影 false。
assert_eq!(
eval_str("var r=[]; try{Reflect.defineProperty([],'length',{value:-1});r.push('no-throw');}catch(e){r.push(e.name);} r.push(Reflect.defineProperty([],'length',{configurable:true})); r.join(',')"),
"RangeError,false"
);
}
#[test]
fn non_array_length_stays_ordinary_property() {
assert_eq!(
eval_str("var o={}; Object.defineProperty(o,'length',{value:1}); ''+o.length+':'+Object.getOwnPropertyDescriptor(o,'length').writable"),
"1:false"
);
}
#[test]
fn define_length_non_writable_reported_by_descriptor() {
assert_eq!(
eval_str("var a=[1]; Object.defineProperty(a,'length',{writable:false}); ''+Object.getOwnPropertyDescriptor(a,'length').writable+':'+Object.getOwnPropertyDescriptor(a,'length').value"),
"false:1"
);
}
#[test]
fn define_index_at_length_requires_writable_length() {
// 索引达到当前 length 需增长 length,length 不可写则拒绝。
assert_eq!(
eval_str("var a=[1,2,3]; Object.defineProperty(a,'length',{writable:false}); var r; try{Object.defineProperty(a,3,{value:'x'});r='no-throw';}catch(e){r=e.name;} r+':'+a.length"),
"TypeError:3"
);
// 索引小于 length 的已有元素重定义不受 length 可写性限制。
assert_eq!(
eval_str("var a=[1,2,3]; Object.defineProperty(a,'length',{writable:false}); Object.defineProperty(a,1,{value:9}); ''+a[1]"),
"9"
);
}