mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 属性值覆盖同步推进脏代际
This commit is contained in:
@@ -805,12 +805,13 @@ impl JsObject {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 当前 generation(对象结构变更计数器,IC 失效依据之一)。
|
||||
/// 当前 generation(对象结构/值变更计数器,脏检测与并发模板裁决依据)。
|
||||
pub fn generation(&self) -> u32 {
|
||||
self.generation
|
||||
}
|
||||
|
||||
/// 递增 generation(`wrapping_add`)。
|
||||
/// 递增 generation(`wrapping_add`)。新增属性、改写既有属性值与原型接线均调用,
|
||||
/// 快照仅做等值比较,数值大小无语义。
|
||||
pub fn bump_generation(&mut self) {
|
||||
self.generation = self.generation.wrapping_add(1);
|
||||
}
|
||||
|
||||
@@ -432,6 +432,9 @@ impl JsObject {
|
||||
|
||||
/// 设置数组元素 position 处的值(数组对象);普通对象按绝对下标写入并自动扩容。
|
||||
/// 数组元素写入会更新 `array_prop_count`(元素数随最高索引增长)。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 递增本对象 generation:值覆盖属可观察变更,脏检测依赖世代对比。
|
||||
pub fn set_prop_at(&mut self, position: impl PropIndex, val: JsValue) {
|
||||
let pos = position.to_u32() as usize;
|
||||
if pos > MAX_DENSE_PROPS {
|
||||
@@ -445,6 +448,7 @@ impl JsObject {
|
||||
let vec = self.ensure_array_elements();
|
||||
vec[pos] = val;
|
||||
self.clear_hole_marker(pos);
|
||||
self.bump_generation();
|
||||
return;
|
||||
}
|
||||
{
|
||||
@@ -463,6 +467,7 @@ impl JsObject {
|
||||
meta.push(None);
|
||||
}
|
||||
}
|
||||
self.bump_generation();
|
||||
}
|
||||
|
||||
/// 数组对象(shape 槽位 → 命名属性区存储索引)的属性写入;普通对象等价
|
||||
@@ -478,6 +483,9 @@ impl JsObject {
|
||||
|
||||
/// 按绝对存储索引写入值,数组对象把元素区与命名属性区分派到各自存储。
|
||||
/// 用于调用方已知属性存储位置(如 `get_own_property_slot` 返回的索引)的场景。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 递增本对象 generation:值覆盖属可观察变更,脏检测依赖世代对比。
|
||||
pub fn set_prop_storage(&mut self, idx: usize, val: JsValue) {
|
||||
if self.is_array() {
|
||||
let count = self.array_prop_count as usize;
|
||||
@@ -488,6 +496,7 @@ impl JsObject {
|
||||
}
|
||||
vec[idx] = val;
|
||||
self.clear_hole_marker(idx);
|
||||
self.bump_generation();
|
||||
return;
|
||||
}
|
||||
let np = idx - count;
|
||||
@@ -502,6 +511,7 @@ impl JsObject {
|
||||
meta.push(None);
|
||||
}
|
||||
}
|
||||
self.bump_generation();
|
||||
return;
|
||||
}
|
||||
let vec = self.ensure_hash_props();
|
||||
@@ -515,6 +525,7 @@ impl JsObject {
|
||||
meta.push(None);
|
||||
}
|
||||
}
|
||||
self.bump_generation();
|
||||
}
|
||||
|
||||
/// 数组对象属性读取(shape 槽位 → 命名属性区存储索引);普通对象等价
|
||||
|
||||
@@ -321,9 +321,9 @@ impl Vm {
|
||||
///
|
||||
/// 用于在多次 JS 执行之间达到完全隔离:session 内未被污染的 builtin 保留原指针。
|
||||
pub fn full_reset(&mut self) {
|
||||
// session 对象只能来自用户写 + promote,跨 full_reset 若保留 global 会悬垂:
|
||||
// 覆盖既有 global 槽的写入不递增 generation(脏检测依赖 generation 对比),
|
||||
// 此处强制 bump 使带 session 对象时 global 必然重建。
|
||||
// 防御兜底:属性值写原语已推进 generation,常规覆盖写由快照对比发现;
|
||||
// 若未来出现绕过属性写原语的裸属性区改写,session 对象会随 epoch 释放,
|
||||
// 保留 global 将持悬垂指针,故带 session 对象时强制 bump 保证 global 重建。
|
||||
if !self.gc_state.session_object_ptrs.is_empty() {
|
||||
let global_ptr = self.session.global_object().as_ptr() as *mut JsObject;
|
||||
unsafe { &mut *global_ptr }.bump_generation();
|
||||
|
||||
@@ -884,6 +884,113 @@ fn reset_clears_runtime_state_like_rerun() {
|
||||
assert!(vm.immutables().is_empty());
|
||||
}
|
||||
|
||||
/// 既有 own 属性值覆盖须推进世代:`Array.prototype.push = 9`(P 对象既有槽、
|
||||
/// prop_meta 路径)后 full_reset 必须重建 array 家族,函数值恢复。
|
||||
#[test]
|
||||
fn full_reset_value_overwrite_on_builtin_proto_rebuilds_family() {
|
||||
let mut vm = Vm::new();
|
||||
let _ = run_source(&mut vm, "0");
|
||||
let old_array_proto = vm.session.builtin_world().array_proto.as_ptr();
|
||||
|
||||
assert_eq!(run_source(&mut vm, "Array.prototype.push = 9"), JsValue::int(9));
|
||||
assert_eq!(run_source(&mut vm, "Array.prototype.push"), JsValue::int(9));
|
||||
assert!(vm.session.is_dirty_since_snapshot(), "值覆盖应推进世代被脏检测捕获");
|
||||
|
||||
vm.full_reset();
|
||||
|
||||
assert!(!std::ptr::eq(old_array_proto, vm.session.builtin_world().array_proto.as_ptr()));
|
||||
let t = run_source(&mut vm, "typeof Array.prototype.push");
|
||||
assert_eq!(vm.lookup_str(t).as_deref(), Some("function"));
|
||||
assert_eq!(run_source(&mut vm, "[1, 2].push(3)"), JsValue::int(3));
|
||||
assert!(!vm.session.is_dirty_since_snapshot());
|
||||
}
|
||||
|
||||
/// 对象值形态的既有槽覆盖同样须被发现:epoch 对象写入 builtin P 对象不被晋升,
|
||||
/// 若漏 bump 则 full_reset 保留旧槽持悬垂指针(UAF);修复后家族重建、槽被丢弃。
|
||||
#[test]
|
||||
fn full_reset_value_overwrite_with_object_value_drops_stale_pointer() {
|
||||
let mut vm = Vm::new();
|
||||
let _ = run_source(&mut vm, "0");
|
||||
let old_array_proto = vm.session.builtin_world().array_proto.as_ptr();
|
||||
|
||||
let _ = run_source(&mut vm, "Array.prototype.push = {}; 0");
|
||||
assert!(vm.session.is_dirty_since_snapshot(), "对象值覆盖应被脏检测捕获");
|
||||
|
||||
vm.full_reset();
|
||||
|
||||
assert!(!std::ptr::eq(old_array_proto, vm.session.builtin_world().array_proto.as_ptr()));
|
||||
let t = run_source(&mut vm, "typeof Array.prototype.push");
|
||||
assert_eq!(vm.lookup_str(t).as_deref(), Some("function"));
|
||||
assert!(!vm.session.is_dirty_since_snapshot());
|
||||
}
|
||||
|
||||
/// 零 session 对象的全局内置值覆盖:`globalThis.Array = 9` 不触发 promote,
|
||||
/// 旧 full_reset 兜底(仅在存在 session 对象时强制 bump global)不生效;
|
||||
/// 值写原语 bump 后 global 脏,full_reset 重绑回内置构造器。
|
||||
#[test]
|
||||
fn full_reset_global_builtin_value_overwrite_restores_constructor() {
|
||||
let mut vm = Vm::new();
|
||||
let _ = run_source(&mut vm, "0");
|
||||
|
||||
let _ = run_source(&mut vm, "globalThis.Array = 9; 0");
|
||||
assert!(vm.gc_state.session_object_ptrs.is_empty(), "原始值覆盖不产生 session 对象");
|
||||
assert!(vm.session.is_dirty_since_snapshot(), "全局值覆盖应推进世代");
|
||||
|
||||
vm.full_reset();
|
||||
|
||||
assert!(std::ptr::eq(
|
||||
global_prop(&vm, "Array").as_js_object_ptr(),
|
||||
vm.session.builtin_world().array_constructor.as_ptr() as *mut JsObject
|
||||
));
|
||||
assert_eq!(run_source(&mut vm, "[1].push(2)"), JsValue::int(2));
|
||||
assert!(!vm.session.is_dirty_since_snapshot());
|
||||
}
|
||||
|
||||
/// IC 二次写(miss 落既有槽 + 命中直写)与复合赋值(set_member_prop 既有槽)
|
||||
/// 两条写路径同样须推进世代,full_reset 后值恢复。
|
||||
#[test]
|
||||
fn full_reset_value_overwrite_via_ic_and_compound_marks_dirty() {
|
||||
let mut vm = Vm::new();
|
||||
let _ = run_source(&mut vm, "0");
|
||||
|
||||
// IC 首写 miss 落既有槽、二次写经缓存命中,两次都须 bump。
|
||||
let _ = run_source(&mut vm, "Array.prototype.push = 9; Array.prototype.push = 8; 0");
|
||||
assert!(vm.session.is_dirty_since_snapshot(), "IC 二次写应被脏检测捕获");
|
||||
vm.full_reset();
|
||||
let t = run_source(&mut vm, "typeof Array.prototype.push");
|
||||
assert_eq!(vm.lookup_str(t).as_deref(), Some("function"));
|
||||
|
||||
// 复合赋值读改写走 set_member_prop 既有槽路径,同样须 bump。
|
||||
let _ = run_source(&mut vm, "Array.prototype.push = 9; Array.prototype.push += 1; 0");
|
||||
assert!(vm.session.is_dirty_since_snapshot(), "复合赋值应被脏检测捕获");
|
||||
vm.full_reset();
|
||||
let t = run_source(&mut vm, "typeof Array.prototype.push");
|
||||
assert_eq!(vm.lookup_str(t).as_deref(), Some("function"));
|
||||
assert_eq!(run_source(&mut vm, "[1, 2, 3].push(4)"), JsValue::int(4));
|
||||
assert!(!vm.session.is_dirty_since_snapshot());
|
||||
}
|
||||
|
||||
/// 假阳性护栏:只写用户对象的良性运行不得误判 builtin/global 脏。
|
||||
/// 用对象/数组字面量表达式(对象分配在 epoch、非 session 直分)隔离掉
|
||||
/// full_reset 对 session 对象的既有兜底,从而验证选择性重建未因值写 bump 误触发。
|
||||
#[test]
|
||||
fn benign_run_does_not_falsely_dirty_builtins() {
|
||||
let mut vm = Vm::new();
|
||||
let _ = run_source(&mut vm, "0");
|
||||
let world_ptr = Arc::as_ptr(&vm.session.builtin_world);
|
||||
let global_ptr = vm.session.global_object.as_ptr();
|
||||
|
||||
run_source(&mut vm, "({ x: 1 }).x + [1, 2].length");
|
||||
assert!(vm.gc_state.session_object_ptrs.is_empty(), "良性表达式不应产生 session 直分对象");
|
||||
assert!(!vm.session.is_dirty_since_snapshot(), "良性运行不应误判 builtin 脏");
|
||||
|
||||
vm.full_reset();
|
||||
|
||||
assert!(std::ptr::eq(world_ptr, Arc::as_ptr(&vm.session.builtin_world)), "builtin world 不应重建");
|
||||
assert!(std::ptr::eq(global_ptr, vm.session.global_object.as_ptr()), "global 不应重建");
|
||||
assert!(!vm.session.is_dirty_since_snapshot());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_reset_clears_symbol_state() {
|
||||
let mut vm = Vm::new();
|
||||
|
||||
Reference in New Issue
Block a user