Distinguish annotation validation from content read dependencies

This commit is contained in:
2026-10-22 13:01:40 +08:00
parent 44519abf7e
commit f9243c77c7
12 changed files with 167 additions and 15 deletions
+1 -1
View File
@@ -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、结果根转移与失败清理 |
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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)。
+46
View File
@@ -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);
}
+4 -4
View File
@@ -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、可选源码签名、共享不可变代码和操作数/控制流/栈验证 |
+86 -5
View File
@@ -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<ObjectRef>,
reads: Mutex<BTreeSet<ObjectRef>>,
regions: Mutex<Vec<BTreeSet<ObjectRef>>>,
regions: Mutex<Vec<BTreeMap<ObjectRef, bool>>>,
}
#[derive(Debug)]
@@ -51,6 +52,36 @@ pub(crate) struct Heap {
changes: BTreeMap<(ObjectRef, ChangeKey), u64>,
changes_overflow: bool,
read_only: Vec<ReadOnly>,
validation_reads: Mutex<HashMap<ThreadId, usize>>,
}
/// 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<Vec<ObjectRef>, 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<Vec<ReadAccess>, 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<T>(
&self,
compute: impl FnOnce() -> Result<T, VmError>,
) -> Result<T, VmError> {
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);
}
}
}
+1 -1
View File
@@ -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::{
+9
View File
@@ -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<Vec<crate::ReadAccess>, VmError> {
self.heap.end_read_tracking_details(token)
}
pub fn is_read_only(&self) -> bool {
self.heap.is_read_only()
}
+9
View File
@@ -996,6 +996,15 @@ impl ObjectStore {
&self,
value: &Value,
expected: AnnotationType,
) -> Result<bool, VmError> {
self.heap
.with_validation_reads(|| self.matches_annotation_value(value, expected))
}
fn matches_annotation_value(
&self,
value: &Value,
expected: AnnotationType,
) -> Result<bool, VmError> {
if let Value::Object(object) = value {
self.object(*object)?;
+1 -1
View File
@@ -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 |
+1 -1
View File
@@ -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) | 词法、语法和诊断 |
+7
View File
@@ -79,3 +79,10 @@ take_changes 按对象与变更键合并同一批写入,返回最终版本。
显式 Trait 声明、源码派生及方法表更新使用统一区间检查“规范参数数量加 self”是否被接受;缺少元数据的原生方法不能再登记为能力实现。可选参数允许实现接受规范调用数量之外的额外形式。更新仍先检查后代再修改,无隐藏类型根。类型注解兼容性、方差、参数名称及效果检查尚未接入此结构。
Tessera 为原生方法、回调和内部视觉 continuation 发布数量元数据,事件监听要求接受零参数或一个 Event;两者都接受时优先传 Event。绑定计算和 Task 回调要求接受零参数,登记前拒绝无元数据/不兼容函数;普通旧原生调用的兼容性不等于允许其参加这些严格回调契约。
## 校验读取与程序内容读取
只读子区域新增 `ObjectStore::end_read_tracking_details(token) -> Vec<ReadAccess>`,每项仍是借用 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 的合并、完整依赖并集与错误恢复回归源码,仅编译未本地执行。