diff --git a/crates/objects/src/README.md b/crates/objects/src/README.md index 9b31d8d..6d2ffee 100644 --- a/crates/objects/src/README.md +++ b/crates/objects/src/README.md @@ -11,7 +11,7 @@ | [changes.rs](changes.rs) | 合并变更批次、变更键与有界通知额度 | | [number.rs](number.rs) | Integer/Decimal/Rational/BinaryFloat/Complex 的无损表示和装箱载荷 | | [builtins.rs](builtins.rs) | String、Dict、Cell、Function、BaseException 载荷与 trace 边 | -| [heap.rs](heap.rs) | 私有 Heap、不可伪造 ObjectRef、稳定 Box、引用事务、迭代释放、循环回收、只读变更屏障/读取跟踪及私有边界测试 | +| [heap.rs](heap.rs) | 私有 Heap、不可伪造 ObjectRef、稳定 Box、引用事务、迭代释放、循环回收、只读变更屏障/全调用及嵌套子调用读取跟踪及私有边界测试 | | [store.rs](store.rs) | ObjectStore、空堆创建、对象操作、retain/release 和回收入口 | | [store_builtins.rs](store_builtins.rs) | 载荷校验、代码/函数工厂、Dict/Cell 操作及键语义 | | [instruction.rs](instruction.rs) | Opcode、Instruction、wordcode 编解码、MakeFunction/比较参数与跳转读取接口 | diff --git a/crates/objects/src/heap.rs b/crates/objects/src/heap.rs index 117ecad..0b92471 100644 --- a/crates/objects/src/heap.rs +++ b/crates/objects/src/heap.rs @@ -39,6 +39,7 @@ struct Slot { struct ReadOnly { protected: BTreeSet, reads: Mutex>, + regions: Mutex>>, } #[derive(Debug)] @@ -84,6 +85,7 @@ impl Heap { self.read_only.push(ReadOnly { protected, reads: Mutex::new(BTreeSet::new()), + regions: Mutex::new(Vec::new()), }); self.read_only.len() } @@ -103,6 +105,26 @@ impl Heap { .collect()) } + pub(crate) fn begin_read_tracking(&self) -> Result { + let scope = self.read_only.last().ok_or(VmError::InvalidArgument( + "read tracking requires a read-only scope", + ))?; + let mut regions = scope.regions.lock().expect("read tracker poisoned"); + regions.push(BTreeSet::new()); + Ok(regions.len()) + } + + pub(crate) fn end_read_tracking(&self, token: usize) -> Result, VmError> { + let scope = self.read_only.last().ok_or(VmError::InvalidArgument( + "read tracking requires a read-only scope", + ))?; + let mut regions = scope.regions.lock().expect("read tracker poisoned"); + if token == 0 || token != regions.len() { + return Err(VmError::InvalidArgument("read tracking nesting mismatch")); + } + Ok(regions.pop().unwrap().into_iter().collect()) + } + pub(crate) fn is_read_only(&self) -> bool { !self.read_only.is_empty() } @@ -115,6 +137,14 @@ impl Heap { .lock() .expect("read tracker poisoned") .insert(reference); + for region in scope + .regions + .lock() + .expect("read tracker poisoned") + .iter_mut() + { + region.insert(reference); + } } } } diff --git a/crates/objects/src/store.rs b/crates/objects/src/store.rs index dd81d7c..02a6b50 100644 --- a/crates/objects/src/store.rs +++ b/crates/objects/src/store.rs @@ -21,6 +21,16 @@ impl ObjectStore { pub fn end_read_only(&mut self, scope: usize) -> Result, VmError> { self.heap.end_read_only(scope) } + /// Track one subcall inside the active read-only scope. Regions nest in + /// LIFO order and do not clear the enclosing call's read dependencies. + /// Handles returned by end_read_tracking are borrowed, never owning roots. + pub fn begin_read_tracking(&self) -> Result { + self.heap.begin_read_tracking() + } + pub fn end_read_tracking(&self, token: usize) -> Result, VmError> { + self.heap.end_read_tracking(token) + } + pub fn is_read_only(&self) -> bool { self.heap.is_read_only() } diff --git a/crates/objects/tests/README.md b/crates/objects/tests/README.md index 8b8e905..11c3e22 100644 --- a/crates/objects/tests/README.md +++ b/crates/objects/tests/README.md @@ -8,3 +8,5 @@ | [store.rs](store.rs) | 只读嵌套、容器与类型写入屏障、稳定地址、跨存储句柄拒绝与更新原子性、Dict/Cell 循环回收、函数校验失败无泄漏、共享代码与常量生命周期 | 非法句柄索引、槽位代数与计数溢出仍在 src/heap.rs 的私有单元测试内,不开放句柄构造器。VM 委托 API 与求值行为继续由 [core/tests](../../core/tests/README.md) 检查。随工作区在指定远端运行,本地不测试;现有测试源码不代表已通过,见 [验证](../../../docs/operations/validation.md)。返回 [objects](../README.md)。 + +store.rs 还覆盖只读子调用读取区域、重复读取、LIFO 校验与未关闭区域的调用结束清理;这些用例按约定不在本地运行。 diff --git a/crates/objects/tests/store.rs b/crates/objects/tests/store.rs index 7ba0ea3..7486d00 100644 --- a/crates/objects/tests/store.rs +++ b/crates/objects/tests/store.rs @@ -153,3 +153,33 @@ fn read_only_scopes_block_existing_container_and_type_writes_but_allow_fresh_res store.collect_cycles(); assert_eq!(store.object_count(), 0); } + +#[test] +fn read_tracking_regions_repeat_shared_reads_and_preserve_enclosing_dependencies() { + let mut store = ObjectStore::new().unwrap(); + let first = store.new_object().unwrap(); + let second = store.new_object().unwrap(); + assert!(store.begin_read_tracking().is_err()); + let scope = store.begin_read_only(); + let outer = store.begin_read_tracking().unwrap(); + store.object(first).unwrap(); + let nested = store.begin_read_tracking().unwrap(); + store.object(first).unwrap(); + store.object(second).unwrap(); + assert!(store.end_read_tracking(outer).is_err()); + assert_eq!( + store.end_read_tracking(nested).unwrap(), + vec![first, second] + ); + assert_eq!(store.end_read_tracking(outer).unwrap(), vec![first, second]); + let repeated = store.begin_read_tracking().unwrap(); + store.object(first).unwrap(); + assert_eq!(store.end_read_tracking(repeated).unwrap(), vec![first]); + // Ending the call discards an unfinished region, as on callback failure. + store.begin_read_tracking().unwrap(); + assert_eq!(store.end_read_only(scope).unwrap(), vec![first, second]); + assert!(store.end_read_tracking(1).is_err()); + assert!(!store.is_read_only()); + store.release(first).unwrap(); + store.release(second).unwrap(); +} diff --git a/docs/README.md b/docs/README.md index c18ce87..c29e3ac 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,7 +5,7 @@ | 引擎与宿主边界 | [架构](architecture/README.md) | [crates](../crates/README.md) | | 解释执行、资源修订与引擎升级 | [执行与版本](architecture/execution-and-versions.md) | frontend/compiler/core | | EndorObject、句柄、默认操作 | [对象模型](runtime/objects.md) | objects/src | -| 原生上下文、只读调用、读取依赖、委托与变更通知 | [原生调用](runtime/native-calls.md) | objects/src/native.rs、changes.rs、core/src/eval.rs | +| 原生上下文、只读调用、子调用读取依赖、委托与变更通知 | [原生调用](runtime/native-calls.md) | objects/src/native.rs、changes.rs、core/src/eval.rs | | 托管类型、宿主类注册、原生分配继承与字段存储 | [名义类型](runtime/types.md) | objects/src/types.rs | | Number、String、Dict、Cell、Function | [基本对象](runtime/builtins.md) | objects/src | | 源码读取与来源 | [Reader](runtime/reader.md) | frontend/src/reader.rs | diff --git a/docs/runtime/native-calls.md b/docs/runtime/native-calls.md index 8ffcf7e..ab5c5ed 100644 --- a/docs/runtime/native-calls.md +++ b/docs/runtime/native-calls.md @@ -31,6 +31,8 @@ Vm::call_read_only_with_limits 沿用显式帧与预算,返回拥有式结果 ObjectStore 的 begin_read_only/end_read_only 提供同样的 LIFO 嵌套机制,is_read_only 供宿主协议检查。保护集合与读取集合不持有 GC 根,代际句柄避免槽位复用误判。读取是对象粒度;宿主须把借用身份转为版本依赖并验证存活,不能据此保活整个对象图。 +ObjectStore::begin_read_tracking/end_read_tracking 可在当前只读作用域中登记单次子调用的对象读取区域。区域按 LIFO 嵌套,重复读取仍分别进入每个活动区域,不清空整个调用的读取集合;返回身份仍是借用,不保活对象。NativeContext 可经 store() 使用此接口。没有只读作用域或关闭顺序不匹配时返回错误。外层只读调用退出时丢弃未结束区域,因此源码子调用出错不会把区域泄漏到下一次调用。 + 只读结果的构造仍维护自身变更版本,但不推送应用 ChangeBatch 通知;现有对象不会在此模式成功写入,因此没有遗漏已有观察者的修改。通过普通调用保存结果到旧对象时,保存动作仍照常通知。可信 Rust 的内部可变状态、外部资源与 I/O 需要宿主遵守只读契约;本机制只强制堆的公开变更入口。 ## 托管共享引用赋值