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

190 lines
8.2 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 虚拟属性的赋值侧回归:`a.length = v` 的两次数值强转、strict/sloppy
//! 可写位语义、部分截断、增长、异常原值传播,以及 push/pop/shift/unshift 与
//! `a[i] = v` 字节码路径对不可写 length 的行为。
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 set_length_truncates_and_grows() {
assert_eq!(eval_str("var a=[1,2,3]; a.length=1; ''+a.length+':'+(2 in a)"), "1:false");
assert_eq!(eval_str("var a=[1]; a.length=3; ''+a.length+':'+(1 in a)"), "3:false");
}
#[test]
fn set_length_non_writable_strict_throws_sloppy_silent() {
// sloppy:静默 no-op,length 不变。
assert_eq!(
eval_str("var a=[1,2,3]; Object.defineProperty(a,'length',{writable:false}); a.length=1; ''+a.length"),
"3"
);
// strict:TypeError,length 不变。
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"
);
// 同值赋值同样失败:不可写数据描述符先于 ArraySetLength 返回 false。
assert_eq!(
eval_str("var a=[1,2,3]; Object.defineProperty(a,'length',{writable:false}); (function(){'use strict';try{a.length=3;return 'no-throw';}catch(e){return e.name;}})()+':'+a.length"),
"TypeError:3"
);
}
#[test]
fn set_length_frozen_strict_throws_sloppy_silent() {
// 冻结数组的 length 同样不可写:sloppy 静默而非恒抛。
assert_eq!(eval_str("var a=Object.freeze([1,2,3]); a.length=1; ''+a.length"), "3");
assert_eq!(
eval_str("var a=Object.freeze([1,2,3]); (function(){'use strict';try{a.length=1;return 'no-throw';}catch(e){return e.name;}})()+':'+a.length"),
"TypeError:3"
);
}
#[test]
fn set_length_shrink_partial_truncation_at_blocker() {
// strict:阻挡索引 1,length 收敛到 2,index 2 起删除,并抛 TypeError。
assert_eq!(
eval_str("var a=[0,1,2,3,4]; Object.defineProperty(a,'1',{configurable:false}); var r; (function(){'use strict';try{a.length=1;r='no-throw';}catch(e){r=e.name;}})(); r+':'+a.length+':'+a.hasOwnProperty('1')+':'+a.hasOwnProperty('2')"),
"TypeError:2:true:false"
);
// sloppy:同样部分截断,静默失败。
assert_eq!(
eval_str("var a=[0,1,2,3,4]; Object.defineProperty(a,'1',{configurable:false}); a.length=0; ''+a.length+':'+a.hasOwnProperty('1')+':'+a.hasOwnProperty('2')"),
"2:true:false"
);
}
#[test]
fn set_length_growth_ignores_extensibility() {
// ArraySetLength 的增长不要求对象可扩展:preventExtensions 后仍成功。
assert_eq!(
eval_str("var a=[1]; Object.preventExtensions(a); a.length=3; ''+a.length+':'+(1 in a)+':'+(2 in a)"),
"3:false:false"
);
}
#[test]
fn set_length_invalid_values_throw_range_error() {
assert_eq!(
eval_str("var a=[1,2,3]; var r=[]; [1.5,-1,4294967296,NaN].forEach(function(v){try{a.length=v;r.push('no-throw');}catch(e){r.push(e.name);}}); r.join(',')"),
"RangeError,RangeError,RangeError,RangeError"
);
}
#[test]
fn set_length_coerces_twice_before_writable_check() {
// ToUint32 + ToNumber 各触发一次 ToPrimitive(number);第二次强转收窄可写位后
// 由可写位判定拒绝,且不做任何截断。
assert_eq!(
eval_str("var a=[1,2,3], n=0; var v={}; v[Symbol.toPrimitive]=function(h){n++;Object.defineProperty(a,'length',{writable:false});return 0;}; var r; (function(){'use strict';try{a.length=v;r='no-throw';}catch(e){r=e.name;}})(); r+':'+n+':'+a.length"),
"TypeError:2:3"
);
// Reflect.set 以严格模式调用并投影为 false,两次强转同样执行。
assert_eq!(
eval_str("var a=[1,2,3], n=0; var v={}; v[Symbol.toPrimitive]=function(h){n++;Object.defineProperty(a,'length',{writable:false});return 0;}; Reflect.set(a,'length',v)+':'+n+':'+a.length"),
"false:2:3"
);
}
#[test]
fn set_length_coercion_exception_propagates_original_value() {
// 强转期 valueOf / Symbol.toPrimitive 抛出的自定义错误须原值重抛(identity 保留)。
assert_eq!(
eval_str("var err=new Error('boom'); var v={valueOf:function(){throw err;}}; var a=[]; var c=null; try{a.length=v;}catch(e){c=e;} (c===err)+':'+c.name"),
"true:Error"
);
assert_eq!(
eval_str("var err=new RangeError('sym'); var v={}; v[Symbol.toPrimitive]=function(h){throw err;}; var a=[]; var c=null; try{a.length=v;}catch(e){c=e;} (c===err)+':'+c.name"),
"true:RangeError"
);
}
#[test]
fn set_length_bigint_and_symbol_throw_type_error() {
assert_eq!(
eval_str("var a=[]; var r=[]; try{a.length=1n;r.push('no-throw');}catch(e){r.push(e.name);} try{a.length=Symbol();r.push('no-throw');}catch(e){r.push(e.name);} r.join(',')"),
"TypeError,TypeError"
);
}
#[test]
fn set_elem_beyond_non_writable_length_respects_strict() {
// `a[i] = v` 字节码路径:越界写须过 length 可写性检查,sloppy 静默、strict 抛。
assert_eq!(
eval_str(
"var a=[]; Object.defineProperty(a,'length',{writable:false}); var i=3; a[i]=9; ''+a.length+':'+(3 in a)"
),
"0:false"
);
assert_eq!(
eval_str("var a=[]; Object.defineProperty(a,'length',{writable:false}); var i=3; var r; (function(){'use strict';try{a[i]=9;r='no-throw';}catch(e){r=e.name;}})(); r+':'+a.length+':'+(3 in a)"),
"TypeError:0:false"
);
}
#[test]
fn mutators_on_non_writable_length_throw() {
// 空数组 + 不可写 length:四个变更方法收尾的 Set length 均抛 TypeError。
assert_eq!(
eval_str("var a=[]; Object.defineProperty(a,'length',{writable:false}); var r=[]; try{a.push();r.push('no-throw');}catch(e){r.push(e.name);} r.push(a.length); r.join(',')"),
"TypeError,0"
);
assert_eq!(
eval_str(
"var a=[]; Object.defineProperty(a,'length',{writable:false}); try{a.pop();'no-throw';}catch(e){e.name}"
),
"TypeError"
);
assert_eq!(
eval_str(
"var a=[]; Object.defineProperty(a,'length',{writable:false}); try{a.shift();'no-throw';}catch(e){e.name}"
),
"TypeError"
);
assert_eq!(
eval_str("var a=[]; Object.defineProperty(a,'length',{writable:false}); try{a.unshift();'no-throw';}catch(e){e.name}"),
"TypeError"
);
// frozen 空数组同样四方法皆抛。
assert_eq!(
eval_str("var a=Object.freeze([]); var r=[]; try{a.push();r.push('no-throw');}catch(e){r.push(e.name);} try{a.pop();r.push('no-throw');}catch(e){r.push(e.name);} try{a.shift();r.push('no-throw');}catch(e){r.push(e.name);} try{a.unshift();r.push('no-throw');}catch(e){r.push(e.name);} r.join(',')"),
"TypeError,TypeError,TypeError,TypeError"
);
}
#[test]
fn push_element_write_uses_prototype_setter() {
// 继承的索引 setter 在元素写入时执行并收窄 length,收尾 Set length 失败。
assert_eq!(
eval_str("var a=[]; var calls=0; Object.defineProperty(Array.prototype,'0',{set:function(v){Object.defineProperty(a,'length',{writable:false});calls++;}}); var r; try{a.push(1);r='no-throw';}catch(e){r=e.name;} r+':'+a.length+':'+a.hasOwnProperty(0)+':'+calls"),
"TypeError:0:false:1"
);
}
#[test]
fn mutators_still_work_with_writable_length() {
// 重写后正常路径回归:push/pop/shift/unshift 的元素搬移与返回值。
assert_eq!(eval_str("var a=[]; a.push(1,2); ''+a.length+':'+a[1]"), "2:2");
assert_eq!(
eval_str("var a=[1,2,3]; var p=a.pop(); var s=a.shift(); var u=a.unshift(9,8); p+':'+s+':'+u+':'+a.join(',')"),
"3:1:3:9,8,2"
);
}