From f9243c77c7609a1afc7cbe6a81fdaa988677d399 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Thu, 22 Oct 2026 13:01:40 +0800 Subject: [PATCH] Distinguish annotation validation from content read dependencies --- crates/core/src/README.md | 2 +- crates/core/src/lib.rs | 2 +- crates/core/tests/README.md | 2 +- crates/core/tests/native_context.rs | 46 +++++++++++++++ crates/objects/src/README.md | 8 +-- crates/objects/src/heap.rs | 91 +++++++++++++++++++++++++++-- crates/objects/src/lib.rs | 2 +- crates/objects/src/store.rs | 9 +++ crates/objects/src/types.rs | 9 +++ docs/README.md | 2 +- docs/runtime/README.md | 2 +- docs/runtime/native-calls.md | 7 +++ 12 files changed, 167 insertions(+), 15 deletions(-) diff --git a/crates/core/src/README.md b/crates/core/src/README.md index 9e32590..e31b833 100644 --- a/crates/core/src/README.md +++ b/crates/core/src/README.md @@ -4,7 +4,7 @@ | 文件 | 职责 | | --- | --- | -| [lib.rs](lib.rs) | VM 类型导出、objects 类型兼容重导出、宿主调用与字节码 rustdoc 示例 | +| [lib.rs](lib.rs) | VM 类型导出、objects 类型兼容重导出(含 ReadAccess)、宿主调用与字节码 rustdoc 示例 | | [vm.rs](vm.rs) | Vm 拥有 ObjectStore 与错误位置记录;对象操作和宿主 new_class 和显式Trait注册/关系查询显式委托给 objects;函数/字段检查统一开关、统一参数区间查询与函数类型配置 | | [frame.rs](frame.rs) | EndorFrame、栈条目、返回类型执行根、执行位置元数据、ExecutionLimits | | [eval.rs](eval.rs) | execute/call、只读调用与依赖结果、参数绑定、原生实现前数量检查、可选注解检查、源码类字段环境绑定、帧切换、取指分派、MakeFunction、结果根转移与失败清理 | diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 4bc1506..4e4c71e 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -56,6 +56,6 @@ pub use objects::{ EndorNativeFunctionObject, EndorNumber, EndorNumberObject, EndorObject, EndorRational, EndorReal, EndorStringObject, EndorTracebackEntry, Instruction, NativeCall, NativeCallable, NativeContext, NativeFunction, NativeResult, ObjectChange, ObjectHeader, ObjectPayload, - ObjectRef, Opcode, Record, SourceSignature, Value, VmError, WeakObjectRef, + ObjectRef, Opcode, ReadAccess, Record, SourceSignature, Value, VmError, WeakObjectRef, }; pub use vm::Vm; diff --git a/crates/core/tests/README.md b/crates/core/tests/README.md index 5745a1c..cfaa6e1 100644 --- a/crates/core/tests/README.md +++ b/crates/core/tests/README.md @@ -8,7 +8,7 @@ | [builtins.rs](builtins.rs) | 无损 Number、Unicode 字符串、Dict 顺序与键、Cell、函数环境/共享代码、异常数据 | | [execution.rs](execution.rs) | wordcode 与校验、控制流、参数/默认值、嵌套帧、字节码函数创建/默认值/环境捕获、闭包、返回值存活、失败清理、限额 | | [types.rs](types.rs) | 名义身份、继承与实例关系、类型边保活/循环回收、只读类型元数据与外来句柄、显式Trait身份/继承/参数约束及后代更新原子性;统一函数分类、原生数量元数据/副作用前拒绝/能力更新/默认参数与返回类型错误根清理;仅远端执行 | -| [native_context.rs](native_context.rs) | 原生作用域根、分配失败清理、委托、只读结果分配/写入拒绝/依赖与失败恢复、弱引用过期及有界变更;仅远端执行 | +| [native_context.rs](native_context.rs) | 原生作用域根、分配失败清理、委托、只读结果分配/写入拒绝/依赖/校验与普通读取区分及失败恢复、弱引用过期及有界变更;仅远端执行 | 核心对象现由 objects 定义,本目录保留既有 endor_core 导入与 Vm 方法的兼容性检查。独立对象存储检查见 [objects/tests](../../objects/tests/README.md)。堆内部伪造句柄与计数溢出检查在 objects 的 src/heap.rs,公开示例由 src/lib.rs rustdoc 提供。文件职责见 [src](../src/README.md),检查方式和结果边界见 [验证](../../../docs/operations/validation.md)。本地不执行测试;存在测试文件不代表某次提交已通过。返回 [core](../README.md)。 diff --git a/crates/core/tests/native_context.rs b/crates/core/tests/native_context.rs index 9b66526..2f957e5 100644 --- a/crates/core/tests/native_context.rs +++ b/crates/core/tests/native_context.rs @@ -222,3 +222,49 @@ fn read_only_calls_track_borrowed_dependencies_allow_results_and_restore_after_e vm.release(source).unwrap(); assert_eq!(vm.object_count(), 0); } + +#[test] +fn annotation_reads_remain_dependencies_without_becoming_program_content_reads() { + use endor_core::AnnotationType; + let mut store = objects::ObjectStore::new().unwrap(); + let object = store.new_object().unwrap(); + store + .set_attr(object, "value", Value::Number(1.into())) + .unwrap(); + let scope = store.begin_read_only(); + let token = store.begin_read_tracking().unwrap(); + assert!( + store + .matches_annotation(&Value::Object(object), AnnotationType::Any) + .unwrap() + ); + let reads = store.end_read_tracking_details(token).unwrap(); + assert!( + reads + .iter() + .any(|read| read.object == object && !read.content_read) + ); + let token = store.begin_read_tracking().unwrap(); + assert!( + !store + .matches_annotation(&Value::Object(object), AnnotationType::Number) + .unwrap() + ); + let mut foreign = objects::ObjectStore::new().unwrap(); + let alien = foreign.new_object().unwrap(); + assert!(matches!( + store.matches_annotation(&Value::Object(alien), AnnotationType::Any), + Err(VmError::ForeignHandle) + )); + foreign.release(alien).unwrap(); + store.get_attr(object, "value").unwrap(); + let reads = store.end_read_tracking_details(token).unwrap(); + assert!( + reads + .iter() + .any(|read| read.object == object && read.content_read) + ); + assert!(store.end_read_only(scope).unwrap().contains(&object)); + store.release(object).unwrap(); + assert_eq!(store.object_count(), 0); +} diff --git a/crates/objects/src/README.md b/crates/objects/src/README.md index 464074b..884aac6 100644 --- a/crates/objects/src/README.md +++ b/crates/objects/src/README.md @@ -4,15 +4,15 @@ | 文件 | 职责 | | --- | --- | -| [lib.rs](lib.rs) | 包边界、对象类型/ObjectStore 公开导出(含Trait/MethodRequirement/SourceSignature/AnnotationType/CallArity)与独立存储 rustdoc 示例 | +| [lib.rs](lib.rs) | 包边界、对象类型/ObjectStore 公开导出(含Trait/MethodRequirement/SourceSignature/AnnotationType/CallArity/ReadAccess)与独立存储 rustdoc 示例 | | [object.rs](object.rs) | Value、CompareOp、ObjectPayload(含显式函数标记)、ObjectHeader、EndorObject、Record、可发布数量元数据的 NativeFunction | -| [types.rs](types.rs) | 托管名义类型、显式 Object/Type 引导、继承查询、受管方法表/接收者绑定、类字段、显式Trait定义/声明/方法参数约束、原生构造委托、统一源码/原生/绑定方法参数区间查询与能力验证、声明环境托管边、字段构造/写入检查与注解解析/匹配 | +| [types.rs](types.rs) | 托管名义类型、显式 Object/Type 引导、继承查询、受管方法表/接收者绑定、类字段、显式Trait定义/声明/方法参数约束、原生构造委托、统一源码/原生/绑定方法参数区间查询与能力验证、声明环境托管边、字段构造/写入检查与标记为校验读取的注解解析/匹配 | | [native.rs](native.rs) | NativeContext、CallArity 元数据与调用前数量检查、上下文原生函数、作用域语言函数工厂、显式能力声明入口、函数标记、根与非递归委托调用 | | [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、引用事务、迭代释放、循环回收、后代契约更新的私有借用枚举、同类型附加幂等、只读变更屏障/全调用及嵌套子调用读取跟踪及私有边界测试 | -| [store.rs](store.rs) | ObjectStore、空堆创建、字段检查配置/属性写入屏障、对象操作、retain/release 和回收入口、弱 Function 类型配置与分配分类 | +| [heap.rs](heap.rs) | 私有 Heap、不可伪造 ObjectRef、稳定 Box、引用事务、迭代释放、循环回收、后代契约更新的私有借用枚举、同类型附加幂等、只读变更屏障/全调用及嵌套子调用读取跟踪(区分注解校验与内容读取)及私有边界测试 | +| [store.rs](store.rs) | ObjectStore、空堆创建、字段检查配置/属性写入屏障、详细读取区域结果、对象操作、retain/release 和回收入口、弱 Function 类型配置与分配分类 | | [store_builtins.rs](store_builtins.rs) | 载荷校验、代码/函数工厂、Dict/Cell 操作及键语义 | | [instruction.rs](instruction.rs) | Opcode、Instruction、wordcode 编解码、MakeFunction/比较参数与跳转读取接口 | | [code.rs](code.rs) | CodeLayout、EndorCode/EndorCodeObject、可选源码签名、共享不可变代码和操作数/控制流/栈验证 | diff --git a/crates/objects/src/heap.rs b/crates/objects/src/heap.rs index 42607aa..4d0da5d 100644 --- a/crates/objects/src/heap.rs +++ b/crates/objects/src/heap.rs @@ -1,7 +1,8 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::fmt; use std::sync::Mutex; use std::sync::atomic::{AtomicU64, Ordering}; +use std::thread::ThreadId; use crate::changes::MAX_PENDING_CHANGES; use crate::{ChangeBatch, ChangeKey, ObjectChange}; @@ -39,7 +40,7 @@ struct Slot { struct ReadOnly { protected: BTreeSet, reads: Mutex>, - regions: Mutex>>, + regions: Mutex>>, } #[derive(Debug)] @@ -51,6 +52,36 @@ pub(crate) struct Heap { changes: BTreeMap<(ObjectRef, ChangeKey), u64>, changes_overflow: bool, read_only: Vec, + validation_reads: Mutex>, +} + +/// Borrowed read identity; content_read distinguishes ordinary reads from +/// annotation validation alone. Neither form retains the object. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ReadAccess { + pub object: ObjectRef, + pub content_read: bool, +} + +struct ValidationScope<'a> { + heap: &'a Heap, + thread: ThreadId, +} +impl Drop for ValidationScope<'_> { + fn drop(&mut self) { + let mut active = self + .heap + .validation_reads + .lock() + .expect("read tracker poisoned"); + let depth = active + .get_mut(&self.thread) + .expect("active validation scope"); + *depth -= 1; + if *depth == 0 { + active.remove(&self.thread); + } + } } impl Heap { @@ -66,6 +97,7 @@ impl Heap { changes: BTreeMap::new(), changes_overflow: false, read_only: vec![], + validation_reads: Mutex::new(HashMap::new()), }) } @@ -120,11 +152,22 @@ impl Heap { "read tracking requires a read-only scope", ))?; let mut regions = scope.regions.lock().expect("read tracker poisoned"); - regions.push(BTreeSet::new()); + regions.push(BTreeMap::new()); Ok(regions.len()) } pub(crate) fn end_read_tracking(&self, token: usize) -> Result, VmError> { + Ok(self + .end_read_tracking_details(token)? + .into_iter() + .map(|read| read.object) + .collect()) + } + + pub(crate) fn end_read_tracking_details( + &self, + token: usize, + ) -> Result, VmError> { let scope = self.read_only.last().ok_or(VmError::InvalidArgument( "read tracking requires a read-only scope", ))?; @@ -132,14 +175,49 @@ impl Heap { if token == 0 || token != regions.len() { return Err(VmError::InvalidArgument("read tracking nesting mismatch")); } - Ok(regions.pop().unwrap().into_iter().collect()) + Ok(regions + .pop() + .unwrap() + .into_iter() + .map(|(object, content_read)| ReadAccess { + object, + content_read, + }) + .collect()) } pub(crate) fn is_read_only(&self) -> bool { !self.read_only.is_empty() } + pub(crate) fn with_validation_reads( + &self, + compute: impl FnOnce() -> Result, + ) -> Result { + if self.read_only.is_empty() { + return compute(); + } + let thread = std::thread::current().id(); + { + let mut active = self.validation_reads.lock().expect("read tracker poisoned"); + let depth = active.entry(thread).or_default(); + *depth = depth + .checked_add(1) + .ok_or(VmError::ExecutionLimit("validation scopes"))?; + } + let _scope = ValidationScope { heap: self, thread }; + compute() + } + pub(crate) fn record_read(&self, reference: ObjectRef) { + if self.read_only.is_empty() { + return; + } + let content = !self + .validation_reads + .lock() + .expect("read tracker poisoned") + .contains_key(&std::thread::current().id()); for scope in &self.read_only { if scope.protected.contains(&reference) { scope @@ -153,7 +231,10 @@ impl Heap { .expect("read tracker poisoned") .iter_mut() { - region.insert(reference); + region + .entry(reference) + .and_modify(|ordinary| *ordinary |= content) + .or_insert(content); } } } diff --git a/crates/objects/src/lib.rs b/crates/objects/src/lib.rs index 0e9051d..cb9434c 100644 --- a/crates/objects/src/lib.rs +++ b/crates/objects/src/lib.rs @@ -33,7 +33,7 @@ pub use builtins::{ pub use changes::{ChangeBatch, ChangeKey, ObjectChange}; pub use code::{CodeLayout, EndorCode, EndorCodeObject, SourceSignature}; pub use error::VmError; -pub use heap::{ObjectRef, WeakObjectRef}; +pub use heap::{ObjectRef, ReadAccess, WeakObjectRef}; pub use instruction::{DecodedInstruction, Instruction, Opcode}; pub use native::{CallArity, ContextNativeFunction, NativeCallable, NativeContext, NativeResult}; pub use number::{ diff --git a/crates/objects/src/store.rs b/crates/objects/src/store.rs index 9bd62b8..96a6fc0 100644 --- a/crates/objects/src/store.rs +++ b/crates/objects/src/store.rs @@ -44,6 +44,15 @@ impl ObjectStore { self.heap.end_read_tracking(token) } + /// Like end_read_tracking, retaining the distinction between program reads + /// and VM annotation validation. Ordinary reads always dominate. + pub fn end_read_tracking_details( + &self, + token: usize, + ) -> Result, VmError> { + self.heap.end_read_tracking_details(token) + } + pub fn is_read_only(&self) -> bool { self.heap.is_read_only() } diff --git a/crates/objects/src/types.rs b/crates/objects/src/types.rs index 8ffa606..dbe63ae 100644 --- a/crates/objects/src/types.rs +++ b/crates/objects/src/types.rs @@ -996,6 +996,15 @@ impl ObjectStore { &self, value: &Value, expected: AnnotationType, + ) -> Result { + self.heap + .with_validation_reads(|| self.matches_annotation_value(value, expected)) + } + + fn matches_annotation_value( + &self, + value: &Value, + expected: AnnotationType, ) -> Result { if let Value::Object(object) = value { self.object(*object)?; diff --git a/docs/README.md b/docs/README.md index aba3970..3fd5a70 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/README.md b/docs/runtime/README.md index 6d7d74f..b3f1b5e 100644 --- a/docs/runtime/README.md +++ b/docs/runtime/README.md @@ -4,7 +4,7 @@ | --- | --- | | [types.md](types.md) | 托管 Object/Type、名义继承、声明字段/函数注解检查、构造与类型边生命周期 | | [objects.md](objects.md) | 对象、句柄与默认操作 | -| [native-calls.md](native-calls.md) | 原生上下文、统一参数数量、非递归委托、弱引用、托管赋值与变更批次 | +| [native-calls.md](native-calls.md) | 原生上下文、读取分类、统一参数数量、非递归委托、弱引用、托管赋值与变更批次 | | [builtins.md](builtins.md) | Number、String、Dict、Cell、Function、异常数据 | | [reader.md](reader.md) | 输入、来源和修订标签 | | [frontend.md](frontend.md) | 词法、语法和诊断 | diff --git a/docs/runtime/native-calls.md b/docs/runtime/native-calls.md index decc37e..85d7791 100644 --- a/docs/runtime/native-calls.md +++ b/docs/runtime/native-calls.md @@ -79,3 +79,10 @@ take_changes 按对象与变更键合并同一批写入,返回最终版本。 显式 Trait 声明、源码派生及方法表更新使用统一区间检查“规范参数数量加 self”是否被接受;缺少元数据的原生方法不能再登记为能力实现。可选参数允许实现接受规范调用数量之外的额外形式。更新仍先检查后代再修改,无隐藏类型根。类型注解兼容性、方差、参数名称及效果检查尚未接入此结构。 Tessera 为原生方法、回调和内部视觉 continuation 发布数量元数据,事件监听要求接受零参数或一个 Event;两者都接受时优先传 Event。绑定计算和 Task 回调要求接受零参数,登记前拒绝无元数据/不兼容函数;普通旧原生调用的兼容性不等于允许其参加这些严格回调契约。 + + +## 校验读取与程序内容读取 + +只读子区域新增 `ObjectStore::end_read_tracking_details(token) -> Vec`,每项仍是借用 ObjectRef,content_read 表示是否存在普通内容读取。matches_annotation 内部的名义/基础值检查标为校验读取;同一区域随后发生普通读取时,content_read 合并为 true。嵌套区域均保留两类读取,不增加对象根。原 end_read_tracking 与完整 end_read_only 仍返回两类读取的完整并集,没有删除依赖或放宽只读写屏障。 + +校验分类按线程记录嵌套深度,作用域退出、错误和展开时通过 RAII 恢复,不让另一线程继承标签。公开 ReadAccess 仅提供信息,不保证调用者可忽略任何对象;动态类型/能力/全局定义等校验依赖仍须参与失效。Tessera 独立验证每次活动 Mount/归属/方法后,仅跳过该 Mount 的纯参数类型校验读取;程序实际读取 Mount 内容仍禁止复用测量缓存。其他读取继续记录版本。详见 core/tests/native_context.rs 的合并、完整依赖并集与错误恢复回归源码,仅编译未本地执行。