From a6411a495d01fa2dff18e86c1a33e709693f5be5 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 18 Sep 2026 02:41:09 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=95=B0=E7=BB=84=E9=95=BF=E5=BA=A6?= =?UTF-8?q?=E6=94=B6=E7=BC=A9=E5=A4=B1=E8=B4=A5=E6=8C=89=E9=98=BB=E6=8C=A1?= =?UTF-8?q?=E7=B4=A2=E5=BC=95=E9=83=A8=E5=88=86=E6=88=AA=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/vm_props.rs | 71 ++++++++++++++----- .../tests/array_length_define_tests.rs | 37 ++++++++++ 2 files changed, 90 insertions(+), 18 deletions(-) diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index 8d2add8b..5aa3eb93 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -1041,12 +1041,13 @@ impl Vm { /// 强转、校验并应用到元素区与逻辑长度。 /// /// # 步骤 - /// 1. `ToUint32` 与 `ToNumber` 两次强转(均可触发用户代码),结果不等抛 - /// RangeError;强转严格早于任何描述符校验。 + /// 1. BigInt 值在强转前拦截为 TypeError;随后 `ToUint32` 与 `ToNumber` 两次 + /// 强转(均可触发用户代码),结果不等抛 RangeError,强转严格早于描述符校验。 /// 2. 校验当前非可配置数据属性的收窄:configurable/enumerable 不得置真; /// 当前不可写时不得再请求 writable 或改动值。 - /// 3. 收缩时先检查 `[newLen, oldLen)` 内是否存在不可配置元素,存在则整体失败 - /// 且不改动元素区。 + /// 3. 收缩时按规范删除循环语义求最高不可配置索引:存在则以「阻挡索引 + 1」 + /// 为最终长度部分截断(删除其上全部可配置元素),再返回失败;全部可配置 + /// 时完整截断到新长度。 /// 4. 应用新逻辑长度(截断/扩 hole、dense 上限覆盖)并按描述符写可写位。 /// /// # 边界与前提 @@ -1080,30 +1081,64 @@ impl Vm { 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()); - } + + // 收缩按 ArraySetLength 删除循环语义:自最高索引向下删除可配置元素,遇最高 + // 不可配置索引 P 时停止并把 length 收敛到 P+1(其上元素已在循环中删除)。 + // 仅当 [newLen, oldLen) 全可配置时才完整截断到 newLen。 + let target_len = if new_len < old_logical { + match Self::highest_non_configurable_index(obj, new_len, old_logical) { + Some(blocker) => blocker + 1, + None => new_len, } + } else { + new_len + }; + + Self::apply_array_length(obj, target_len); + obj.set_length_non_writable(!attributes.writable()); + obj.bump_generation(); + + if target_len != new_len { + return Err("cannot redefine non-configurable property".to_string()); } - // dense 物理槽以 MAX_DENSE_PROPS 封顶,超限长度另记逻辑长度覆盖。 + Ok(()) + } + + /// 返回 `[from, old_len)` 内最高的不可配置自身元素索引;无则 `None`。 + /// + /// # 边界与前提 + /// - 扫描上界取逻辑长度与稠密元素数的较小者:逻辑长度超过 + /// `MAX_DENSE_PROPS` 时,其上的索引不存于稠密元素区,无自身属性可阻挡。 + /// - hole 标记为可配置(删除成功),不计入阻挡。 + fn highest_non_configurable_index(obj: &JsObject, from: u32, old_len: u32) -> Option { + let scan_hi = (old_len as usize).min(obj.array_prop_count as usize); + (from as usize..scan_hi) + .rev() + .find(|&idx| obj.prop_meta_at(idx).is_some_and(|m| !m.attributes.configurable())) + .map(|idx| idx as u32) + } + + /// 把数组逻辑长度与物理元素区收敛到 `final_len`:截断/补齐稠密元素并维护 + /// `array_len_override`。 + /// + /// # 边界与前提 + /// - 稠密物理槽以 `MAX_DENSE_PROPS` 封顶,超出部分仅记逻辑长度覆盖。 + /// - 不写 length 可写位、不 bump 世代,由调用方统一处理。 + fn apply_array_length(obj: &mut JsObject, final_len: u32) { let old_count = obj.array_prop_count as usize; - let new_phys = (new_len as usize).min(oxide_types::object::MAX_DENSE_PROPS); + let new_phys = (final_len as usize).min(oxide_types::object::MAX_DENSE_PROPS); obj.set_prop_count(new_phys); + + // 扩出的槽是稀疏 hole:存在性检查与原型链读取须视同不存在。 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); + + if final_len as usize > oxide_types::object::MAX_DENSE_PROPS { + obj.set_array_len_override(final_len); } else { obj.clear_array_len_override(); } - obj.set_length_non_writable(!attributes.writable()); - obj.bump_generation(); - Ok(()) } } diff --git a/crates/oxide_vm/tests/array_length_define_tests.rs b/crates/oxide_vm/tests/array_length_define_tests.rs index 06b99d0d..c06cf7ee 100644 --- a/crates/oxide_vm/tests/array_length_define_tests.rs +++ b/crates/oxide_vm/tests/array_length_define_tests.rs @@ -109,6 +109,43 @@ fn define_length_shrink_blocked_by_non_configurable_element() { ); } +#[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_configurable_true_error_leaves_elements() { // TypeError 后元素区不截断。