From c96dce118b30ce3b66ffe3f809a89f20f05883f5 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Thu, 22 Oct 2026 15:07:50 +0800 Subject: [PATCH] Classify read-only wrappers with dynamic delegated effects --- crates/core/tests/README.md | 2 +- crates/core/tests/native_context.rs | 67 +++++++++++++++++++++++++++++ crates/objects/src/README.md | 2 +- crates/objects/src/contracts.rs | 8 +++- crates/objects/src/native.rs | 7 ++- docs/README.md | 2 +- docs/runtime/README.md | 2 +- docs/runtime/native-calls.md | 5 ++- docs/runtime/types.md | 4 +- 9 files changed, 89 insertions(+), 10 deletions(-) diff --git a/crates/core/tests/README.md b/crates/core/tests/README.md index c06ceb7..9bf8de9 100644 --- a/crates/core/tests/README.md +++ b/crates/core/tests/README.md @@ -10,7 +10,7 @@ | [types.rs](types.rs) | 名义身份、继承与实例关系、类型边保活/循环回收、只读类型元数据与外来句柄、显式Trait身份/继承/参数约束及后代更新原子性;统一函数分类、原生数量元数据/副作用前拒绝/能力更新/默认参数与返回类型错误根清理;仅远端执行 | | [typed_traits.rs](typed_traits.rs) | 类型化Trait登记、原子拒绝、源码检查关闭时的显式约束、宽参数调用、类型强边GC、体后实际能力结果检查与临时验证集合恢复;仅远端执行 | | [method_contracts.rs](method_contracts.rs) | 源码/原生方法覆写参数逆变/返回协变、联合参数、效果、祖先替换原子性、动态名称重绑定/前向名称与体前拒绝/恢复及清理;仅远端执行 | -| [native_context.rs](native_context.rs) | 原生作用域根、分配失败清理、原生签名/联合与可空类型/效果和委托返回根、绑定方法元数据、委托、只读结果分配/写入拒绝/依赖/校验与普通读取区分及失败恢复、弱引用过期及有界变更;仅远端执行 | +| [native_context.rs](native_context.rs) | 原生作用域根、分配失败清理、原生签名/联合与可空类型/固定与Delegated效果和委托返回根、绑定方法元数据、委托、只读结果分配/写入拒绝/依赖/校验与普通读取区分及失败恢复、弱引用过期及有界变更;仅远端执行 | 核心对象现由 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 13c5a2e..c3ee7d9 100644 --- a/crates/core/tests/native_context.rs +++ b/crates/core/tests/native_context.rs @@ -719,3 +719,70 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met vm.release(function).unwrap(); assert_eq!(vm.object_count(), 0); } + +#[test] +fn delegated_effects_keep_the_original_read_only_scope_and_check_the_actual_target() { + use endor_core::{AnnotationType as T, CallEffect, CallSignature}; + use std::sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }; + let mut vm = Vm::new().unwrap(); + let calls = Arc::new(AtomicUsize::new(0)); + let target_calls = Arc::new(AtomicUsize::new(0)); + let target = vm + .allocate(ContextNativeFunction::new( + "target", + TypedNative { + signature: CallSignature::exact(vec![], T::Unit, CallEffect::Mutating), + calls: target_calls.clone(), + delegate: None, + allocate_result: false, + }, + vec![], + )) + .unwrap(); + let wrapper = vm + .allocate(ContextNativeFunction::new( + "wrapper", + TypedNative { + signature: CallSignature::exact(vec![], T::Unit, CallEffect::Delegated), + calls: calls.clone(), + delegate: Some(target), + allocate_result: false, + }, + vec![target], + )) + .unwrap(); + assert!( + vm.call_read_only_with_limits(wrapper, &[], ExecutionLimits::default()) + .is_err() + ); + assert_eq!(calls.load(Ordering::SeqCst), 1); + assert_eq!(target_calls.load(Ordering::SeqCst), 0); + assert_eq!(vm.call(wrapper, &[]).unwrap(), Value::Nil); + vm.replace_payload( + target, + ContextNativeFunction::new( + "pure", + TypedNative { + signature: CallSignature::exact(vec![], T::Unit, CallEffect::ReadOnly), + calls: target_calls.clone(), + delegate: None, + allocate_result: false, + }, + vec![], + ), + ) + .unwrap(); + assert_eq!( + vm.call_read_only_with_limits(wrapper, &[], ExecutionLimits::default()) + .unwrap() + .0, + Value::Nil + ); + for object in [wrapper, target] { + vm.release(object).unwrap(); + } + assert_eq!(vm.object_count(), 0); +} diff --git a/crates/objects/src/README.md b/crates/objects/src/README.md index 2bf17bd..8d6ef64 100644 --- a/crates/objects/src/README.md +++ b/crates/objects/src/README.md @@ -8,7 +8,7 @@ | [object.rs](object.rs) | Value、CompareOp、ObjectPayload(含显式函数标记)、ObjectHeader、EndorObject、Record、可发布数量元数据的 NativeFunction | | [types.rs](types.rs) | 托管名义类型、显式 Object/Type 引导、继承查询、受管方法表/接收者绑定、类字段、显式/类型化Trait定义、强类型签名边与声明约束、原生构造委托、统一源码/原生/绑定方法参数区间查询与能力验证、声明环境托管边、字段构造/写入检查与标记为校验读取的注解解析/匹配 | | [contracts.rs](contracts.rs) | 方法参数逆变/返回协变、名义/能力/联合包含关系、祖先方法更新的后代检查、动态注解调用前复查、类型化能力替换/返回约束与临时递归验证集合 | -| [native.rs](native.rs) | NativeContext、CallArity/CallSignature 类型与效果元数据、调用前检查、签名类型强边、上下文原生函数、作用域语言函数工厂、显式能力声明入口、函数标记、根与非递归委托调用 | +| [native.rs](native.rs) | NativeContext、CallArity/CallSignature 类型、Delegated/固定效果元数据、调用前检查、签名类型强边、上下文原生函数、作用域语言函数工厂、显式能力声明入口、函数标记、根与非递归委托调用 | | [changes.rs](changes.rs) | 合并变更批次、变更键与有界通知额度 | | [number.rs](number.rs) | Integer/Decimal/Rational/BinaryFloat/Complex 的无损表示和装箱载荷 | | [builtins.rs](builtins.rs) | String、Dict、Cell、Function、BaseException 载荷与 trace 边、语言函数标记 | diff --git a/crates/objects/src/contracts.rs b/crates/objects/src/contracts.rs index 4a88a6f..e1ccea9 100644 --- a/crates/objects/src/contracts.rs +++ b/crates/objects/src/contracts.rs @@ -25,7 +25,11 @@ impl From for Contract { parameters: value.parameters, variadic: value.variadic, returns: value.returns, - effect: Some(value.effect), + effect: if value.effect == CallEffect::Delegated { + None + } else { + Some(value.effect) + }, } } } @@ -296,7 +300,7 @@ impl ObjectStore { let compatible = match expected { CallEffect::ReadOnly => actual == CallEffect::ReadOnly, CallEffect::Mutating => actual != CallEffect::External, - CallEffect::External => true, + CallEffect::External | CallEffect::Delegated => true, }; if !compatible { return Err(invalid("override expands native effects")); diff --git a/crates/objects/src/native.rs b/crates/objects/src/native.rs index e5da36c..ce9c7d3 100644 --- a/crates/objects/src/native.rs +++ b/crates/objects/src/native.rs @@ -55,6 +55,9 @@ impl CallArity { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CallEffect { ReadOnly, + /// The native body itself is read-only; delegated effects are dynamic and + /// checked by the VM in the same execution/heap scope. + Delegated, Mutating, External, } @@ -192,7 +195,9 @@ impl CallSignature { ) -> Result<(), VmError> { self.validate(store)?; self.arity.check(name.into(), arguments.len())?; - if store.is_read_only() && self.effect != CallEffect::ReadOnly { + if store.is_read_only() + && !matches!(self.effect, CallEffect::ReadOnly | CallEffect::Delegated) + { return Err(VmError::InvalidArgument( "native effect is forbidden during read-only execution", )); diff --git a/docs/README.md b/docs/README.md index 4acbe25..d8cff76 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、contracts.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 11e6e6c..b48c436 100644 --- a/docs/runtime/README.md +++ b/docs/runtime/README.md @@ -4,7 +4,7 @@ | --- | --- | | [types.md](types.md) | 托管 Object/Type、名义继承、方法覆写/类型化Trait/动态检查、声明字段/函数注解检查、构造与类型边生命周期 | | [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 2c808c0..74fb9b8 100644 --- a/docs/runtime/native-calls.md +++ b/docs/runtime/native-calls.md @@ -100,10 +100,13 @@ NativeCallable::signature 默认 None,明确表示尚未发布类型/效果契 NativeResult::Invoke 仍是尾委托,不递归进入 Rust/VM。VM 为每层已声明的返回契约保留检查项;委托原生立即返回或 Endor 帧返回时,按由内到外顺序验证全部契约。返回定义作为执行根保留,原生载荷替换、释放原委托者或源码全局重绑定不会使检查丢失。准备失败、帧限额、指令限额、错误返回与正常完成都释放这些执行根。委托不会为检查额外创建 VM 帧;仍共享每次原生分派和 Endor 指令预算。NativeContext::execute 单独返回 Invoke 时,调用者须使用具有等效最终返回检查的求值器;通常直接使用 Vm::call。 -CallEffect::ReadOnly 声明只读现存对象,可分配临时结果;Mutating 与 External 在 ObjectStore 只读作用域内、进入实现前拒绝。普通执行允许这些效果。ReadOnly 声明不绕过已有写屏障,也不证明可信 Rust 不会进行隐藏 I/O;未知旧原生函数仍沿原调用/宿主检查,不能把 None 理解为纯函数。这不是静态效果推断、完整 I/O 沙箱或对阻塞原生代码的抢占。 +CallEffect::ReadOnly 声明只读现存对象,可分配临时结果;Delegated 声明原生包装自身只读,但后续委托的整体效果动态确定,VM 保持同一只读/预算作用域并检查实际目标;Mutating 与 External 在 ObjectStore 只读作用域内、进入实现前拒绝。普通执行允许这些效果。ReadOnly 声明不绕过已有写屏障,也不证明可信 Rust 不会进行隐藏 I/O;未知旧原生函数仍沿原调用/宿主检查,不能把 None 理解为纯函数。这不是静态效果推断、完整 I/O 沙箱或对阻塞原生代码的抢占。 Tessera 的 measure/arrange/paint/hitTest、Point 几何查询、Window、Block/Container/Page、Mount、Task、Event、Subscription 方法和 ObjectList.at 发布签名。Page.open 的 Window | Unit、Page.window 的 Window | Unit 返回,以及 Block.on 的 Bool | String 可选阶段有准确元数据;Mount 的父/子/同级/Page 弱查询为可空结果。Mount.bounds 现在返回带 Bounds 名义类型的现有几何记录,可直接组合到 Point.inside 或视觉方法。滚动位置/字典等通用记录仍声明 Object,不冒充新语言类型;任务 owner 的具体资源资格仍按原域检查。构造器和其他管理 API 的签名覆盖仍未全部接入。已接入的方法覆写方差与动态名称规则见类型主题。旧能力注册保留数量模式;类型化能力还检查类型/已知效果,并在实际返回和尾委托结果处保留能力返回约束。 回归源码 native_context.rs 覆盖参数副作用前拒绝、错误结果清理、跨原生/源码委托检查、单帧限制、名义签名边与绑定方法循环回收、只读效果拒绝、传统直接函数及重复参数和元数据错误。只编译,不本地执行。 联合/可空回归另覆盖两个标量分支、nil、数值拒绝、Object 分支不掩盖非法名义类型、空联合拒绝,以及移除签名后联合返回定义仍存活。 + + +Delegated 在替换兼容中视为未知整体效果,与源码未知效果一样不冒充静态纯度证明。包装不得在委托前进行业务写入或外部I/O,直接返回路径也须只读;委托的原生 Mutating/External 目标仍在当前只读作用域内、进入目标实现前拒绝。源码委托继续受堆写屏障约束,旧无元数据宿主调用仍须遵守可信边界。native_context.rs 的回归覆盖只读包装进入后拒绝写目标、普通作用域允许同一链,以及替换成纯目标后只读调用成功,仅编译。 diff --git a/docs/runtime/types.md b/docs/runtime/types.md index 3ad126d..cf6ba2f 100644 --- a/docs/runtime/types.md +++ b/docs/runtime/types.md @@ -126,7 +126,7 @@ new_class 在发布之前校验本类覆写,失败释放候选定义。define_ 未知前向注解在类建立和方法更新时暂缓,其他已知不兼容以 OverrideViolation 报类名、方法名和原因。启用 VM 类型检查时,源码和原生调用均在进入实现前检查实际接收者所属方法的当前替换契约;UnknownAnnotation 此时不能暂缓。普通函数的首参数恰好是对象,不会因此被视为方法。默认接收者也使用实际默认参数值参与复查。每次复查动态全局注解,所以 REPL 重绑定使旧覆写不兼容时,函数体执行前拒绝;恢复兼容绑定后可继续普通调用。不回滚先前已发生的其他输入副作用。 -已知原生效果只允许缩小:ReadOnly 基类只能被 ReadOnly 原生覆写;Mutating 基类不允许 External 覆写;External 可接受三者。源码效果尚无静态元数据,不能声称已经证明其纯度;实际只读布局/绘制仍通过原有堆屏障与原生效果入口限制执行。未知旧基类类型签名仍是未证明的兼容边界,旧 new_trait 仍仅校验数量;new_typed_trait 提供独立类型与已知效果要求,见下文。 +已知原生效果只允许缩小:ReadOnly 基类只能被 ReadOnly 原生覆写;Mutating 基类不允许 External 覆写;External 可接受三者。源码和原生 Delegated 的整体效果均未知,不能声称已经证明其纯度;实际只读布局/绘制仍通过原有堆屏障与原生效果入口限制执行。未知旧基类类型签名仍是未证明的兼容边界,旧 new_trait 仍仅校验数量;new_typed_trait 提供独立类型与已知效果要求,见下文。 回归源码 core/tests/method_contracts.rs 覆盖合法参数扩大/返回缩小、错误参数/返回、祖先替换对已有覆写后代的原子拒绝、调用前动态重绑定拒绝和恢复、前向名称、原生联合与效果及根清理,仅编译,不在本地执行。 @@ -143,6 +143,6 @@ ObjectStore/Vm::new_typed_trait(name, Vec) 以每项 nam 嵌套能力比较按线程/定义/能力保留临时验证集合,对已经在校验的同一名义能力关系按递归约束处理,避免无限递归;它不建立新声明关系。集合不持有 GC 根,RAII 在成功、错误和展开路径清理,不让失败验证成为后续成功缓存。没有长期签名缓存;动态名称每次仍从原环境解析。 -源码效果仍未知,类型化 ReadOnly 要求不等于已经证明源码纯度。已知原生效果会比较,真正只读布局/绘制继续执行堆屏障;普通输入中源码副作用仍按现有运行模式处理。没有泛型/关联类型/效果推断或 Trait 源码声明语法。Tessera 的四个宿主能力使用类型化要求,其中 Presentable 允许普通展示构造副作用,不额外宣称纯函数。 +源码和 Delegated 包装的整体效果仍未知,类型化 ReadOnly 要求不等于已经证明源码纯度。已知原生效果会比较,真正只读布局/绘制继续执行堆屏障;普通输入中源码副作用仍按现有运行模式处理。没有泛型/关联类型/效果推断或 Trait 源码声明语法。Tessera 的四个宿主能力使用类型化要求,其中 Presentable 允许普通展示构造副作用,不额外宣称纯函数。 core/tests/typed_traits.rs 覆盖登记失败原子性、参数扩大仍可直接调用、全局检查关闭时的显式能力实际参数/返回检查、签名类型保活/回收,以及体内动态改变返回对象能力后的最终拒绝和验证集合恢复,仅编译未本地执行。