mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-26 11:41:13 +08:00
fix: 数组长度按虚拟属性处理定义与截断
This commit is contained in:
@@ -364,6 +364,13 @@ impl Vm {
|
||||
if obj.is_frozen() {
|
||||
return self.raise_type_error("Cannot assign to read only property 'length'");
|
||||
}
|
||||
// defineProperty 显式收窄的不可写 length:严格模式抛 TypeError,sloppy 静默。
|
||||
if !obj.is_length_writable() {
|
||||
if strict {
|
||||
return self.raise_type_error("Cannot assign to read only property 'length'");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
let pc_before = self.pc;
|
||||
let number_len = self.coerce_number_bounded(val)?;
|
||||
// ToPrimitive 抛错(valueOf/toString throw)已被 unwind 定向到外围 catch 时
|
||||
@@ -844,6 +851,11 @@ impl Vm {
|
||||
);
|
||||
}
|
||||
}
|
||||
// 数组 length 是无 shape 槽的虚拟数据属性:按 ArraySetLength 语义应用,
|
||||
// 不得走通用命名属性路径建影子槽(会造成读写分叉)。
|
||||
if obj.is_array() && prop_name_si == self.length_si {
|
||||
return self.define_array_length(obj, val, attributes);
|
||||
}
|
||||
// 新命名属性(shape 链 lookup miss)且对象不可扩展 → 拒绝定义
|
||||
// (Object.defineProperty 抛 TypeError;Reflect.defineProperty 自动转 false)。
|
||||
if self
|
||||
@@ -924,6 +936,10 @@ impl Vm {
|
||||
return self.define_array_index_element(obj, index, JsValue::undefined(), attributes, true, get, set);
|
||||
}
|
||||
}
|
||||
// 数组 length 当前为不可配置数据属性,禁止转为访问器属性。
|
||||
if obj.is_array() && prop_name_si == self.length_si {
|
||||
return Err("cannot redefine non-configurable property".to_string());
|
||||
}
|
||||
// 新命名属性(shape 链 lookup miss)且对象不可扩展 → 拒绝定义。
|
||||
if self
|
||||
.kernel_core
|
||||
@@ -977,6 +993,11 @@ impl Vm {
|
||||
if pos > oxide_types::object::MAX_DENSE_PROPS {
|
||||
return Err("array index out of dense range".to_string());
|
||||
}
|
||||
// 数组 exotic [[DefineOwnProperty]]:索引达到/超过当前 length 时须增长 length,
|
||||
// length 不可写则拒绝;索引小于 length 的元素重定义不受此限。
|
||||
if pos as u32 >= obj.logical_len() && !obj.is_length_writable() {
|
||||
return Err("cannot define property beyond non-writable length".to_string());
|
||||
}
|
||||
// 新元素(越界或 hole 空洞)要求对象可扩展;已有元素重定义不受限(走下方
|
||||
// non-configurable 校验)。
|
||||
let is_new = pos >= obj.array_prop_count as usize || obj.prop_meta_at(pos).is_some_and(|m| m.is_hole());
|
||||
@@ -1015,6 +1036,75 @@ impl Vm {
|
||||
obj.bump_generation();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 数组 length 虚拟属性的 define 路径:按 `ArraySetLength` 语义把描述符值
|
||||
/// 强转、校验并应用到元素区与逻辑长度。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. `ToUint32` 与 `ToNumber` 两次强转(均可触发用户代码),结果不等抛
|
||||
/// RangeError;强转严格早于任何描述符校验。
|
||||
/// 2. 校验当前非可配置数据属性的收窄:configurable/enumerable 不得置真;
|
||||
/// 当前不可写时不得再请求 writable 或改动值。
|
||||
/// 3. 收缩时先检查 `[newLen, oldLen)` 内是否存在不可配置元素,存在则整体失败
|
||||
/// 且不改动元素区。
|
||||
/// 4. 应用新逻辑长度(截断/扩 hole、dense 上限覆盖)并按描述符写可写位。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 仅由 `define_data_property` 对 `is_array()` 对象且键为 `length_si` 时调用。
|
||||
/// - 描述符缺 `value` 时调用方以当前逻辑长度为哨兵值,数值强转为恒等无副作用
|
||||
/// 转换。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 截断/扩展元素区与元素元数据、可能置/清 `array_len_override`、写 length
|
||||
/// 可写位、bump 世代。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// - 非法长度以 `"RangeError: "` 前缀标记 kind,由 Object/Reflect 入口各自投影;
|
||||
/// 其余失败投影为 TypeError。
|
||||
fn define_array_length(
|
||||
&mut self, obj: &mut JsObject, val: JsValue, attributes: PropAttributes,
|
||||
) -> Result<(), String> {
|
||||
// [[Value]] 两次强转都要实际执行(可触发用户代码),且须早于描述符校验;
|
||||
// 两次强转之间用户代码可能把 length 收窄为不可写。
|
||||
let new_len = self.coerce_uint32_bounded(val)?;
|
||||
let number_len = self.coerce_number_bounded(val)?;
|
||||
if new_len as f64 != number_len {
|
||||
return Err("RangeError: Invalid array length".to_string());
|
||||
}
|
||||
|
||||
let old_logical = obj.logical_len();
|
||||
// 当前 length 是不可配置、不可枚举的数据属性;writable 由冻结标志与独立位决定。
|
||||
if attributes.configurable() || attributes.enumerable() {
|
||||
return Err("cannot redefine non-configurable property".to_string());
|
||||
}
|
||||
if !obj.is_length_writable() && (attributes.writable() || new_len != old_logical) {
|
||||
return Err("cannot redefine non-configurable property".to_string());
|
||||
}
|
||||
// 收缩先验证不可配置元素:存在则整体失败,元素区保持原状(错误先于变更)。
|
||||
if new_len < old_logical {
|
||||
let old_count = obj.array_prop_count as usize;
|
||||
for idx in new_len as usize..old_count {
|
||||
if obj.prop_meta_at(idx).is_some_and(|m| !m.attributes.configurable()) {
|
||||
return Err("cannot redefine non-configurable property".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
// dense 物理槽以 MAX_DENSE_PROPS 封顶,超限长度另记逻辑长度覆盖。
|
||||
let old_count = obj.array_prop_count as usize;
|
||||
let new_phys = (new_len as usize).min(oxide_types::object::MAX_DENSE_PROPS);
|
||||
obj.set_prop_count(new_phys);
|
||||
for idx in old_count..new_phys {
|
||||
obj.mark_hole_at(idx);
|
||||
}
|
||||
if new_len as usize > oxide_types::object::MAX_DENSE_PROPS {
|
||||
obj.set_array_len_override(new_len);
|
||||
} else {
|
||||
obj.clear_array_len_override();
|
||||
}
|
||||
obj.set_length_non_writable(!attributes.writable());
|
||||
obj.bump_generation();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
//! 数组 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_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"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user