From c067dcce21bbe0fc2163bfec69d8838aadf927b9 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 29 Sep 2026 14:47:19 +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=E9=98=BB=E6=8C=A1=E6=89=AB=E6=8F=8F=E5=8F=96?= =?UTF-8?q?=E7=A8=A0=E5=AF=86=E4=B8=8E=E5=91=BD=E5=90=8D=E5=8C=BA=E6=9C=80?= =?UTF-8?q?=E9=AB=98=E9=98=BB=E6=8C=A1=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/vm_props.rs | 50 +++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index eba48101..933e95e7 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -1368,26 +1368,31 @@ impl Vm { /// # 边界与前提 /// - 稠密区扫描上界取逻辑长度与稠密元素数的较小者;命名整数键区经 shape /// 链扫描,稠密上限之上的索引同样可阻挡。 + /// - 稠密区与命名区各自扫完取二者最大值:稠密阻挡点恒低于命名阻挡点, + /// 不得遮蔽命名阻挡点。 /// - hole 标记为可配置(删除成功),不计入阻挡。 fn highest_non_configurable_index(&self, obj: &JsObject, from: u32, old_len: u32) -> Option { - // 稠密元素区降序扫描:首个不可配置索引即最高阻挡点。 + // 稠密元素区降序扫描:首个不可配置索引即稠密区阻挡点。 let scan_hi = (old_len as usize).min(obj.array_prop_count as usize); + let mut dense_highest: Option = None; for idx in (from as usize..scan_hi).rev() { if obj.prop_meta_at(idx).is_some_and(|m| !m.attributes.configurable()) { - return Some(idx as u32); + dense_highest = Some(idx as u32); + break; } } // 命名整数键区:取范围内不可配置键的最高索引。 - let mut highest: Option = None; + let mut named_highest: Option = None; for (index, meta) in self.named_int_keys_in_range(obj, from, old_len) { if meta.is_some_and(|m| !m.attributes.configurable()) - && (highest.is_none() || index > highest.unwrap()) + && (named_highest.is_none() || index > named_highest.unwrap()) { - highest = Some(index); + named_highest = Some(index); } } - highest + + dense_highest.max(named_highest) } /// 收集命名区(shape 链)中值落在 `[from, to)` 的数组整数键,返回 @@ -1788,6 +1793,39 @@ mod tests { assert_eq!(vm.ordinary_get(obj, si, a).expect("get"), JsValue::int(1)); } + #[test] + fn dense_and_named_blockers_take_highest_on_shrink() { + let mut vm = Vm::new(); + let a = new_array_val(&mut vm, 3); + unsafe { + let arr = a.as_js_object_ptr(); + (*arr).set_prop_at(0, JsValue::int(1)); + (*arr).set_prop_at(1, JsValue::int(2)); + (*arr).set_prop_at(2, JsValue::int(3)); + } + let attrs = PropAttributes::new(true, true, false); + // 稠密阻挡点 D=2:元素 2 改为不可配置。 + let dense_si = array_index_si(&mut vm, 2); + vm.define_data_property(unsafe { &mut *a.as_js_object_ptr() }, dense_si, JsValue::int(3), attrs) + .expect("define dense"); + + // 命名阻挡点 N=2000000:大索引写后改为不可配置。 + let si = array_index_si(&mut vm, 2_000_000); + vm.ordinary_set(unsafe { &mut *a.as_js_object_ptr() }, si, JsValue::int(9), a, true).expect("set"); + vm.define_data_property(unsafe { &mut *a.as_js_object_ptr() }, si, JsValue::int(9), attrs) + .expect("define named"); + + // a.length = 1:截断被命名阻挡点 N 阻挡,最终长度取 N+1。 + let err = vm + .set_array_length_value(unsafe { &mut *a.as_js_object_ptr() }, JsValue::int(1), true, true) + .expect_err("截断被阻挡"); + assert!(err.starts_with("TypeError"), "阻挡截断须按 TypeError 失败,实际: {err}"); + let obj = unsafe { &*a.as_js_object_ptr() }; + assert_eq!(obj.logical_len(), 2_000_001, "length 取命名阻挡点 + 1"); + assert_eq!(vm.ordinary_get(obj, si, a).expect("get"), JsValue::int(9), "命名不可配置键存活"); + assert_eq!(vm.ordinary_get(obj, dense_si, a).expect("get"), JsValue::int(3), "稠密不可配置键存活"); + } + #[test] fn delete_named_large_index_succeeds_and_length_unchanged() { let mut vm = Vm::new();