//! NativeContext 临时根、委托、观察依赖与完整签名的公开 API 回归;测试仍遵守远程执行约定。 //! Remote-only regression coverage. Do not execute locally. use endor_core::{ ChangeKey, ContextNativeFunction, ExecutionLimits, NativeCallable, NativeContext, NativeResult, ObjectRef, Value, Vm, VmError, }; #[derive(Debug)] /// bool 控制是否故意失败,两个临时分配用于观察成功/失败清理差异。 struct Allocate(bool); impl NativeCallable for Allocate { /// VM 在 NativeContext 内调用此测试分派器,临时分配与输入由上下文保活,返回/委托后移交输出根。 fn invoke(&self, cx: &mut NativeContext<'_>, _args: &[Value]) -> Result { // 分配一份上下文临时根,成功返回时仅输出另取根移交,其余根由 Drop 归还。 let value = cx.new_object()?; cx.set_attr(value, "label", Value::Text("scoped".into()))?; // 故意创建不返回对象,验证正常/报错都不会泄漏临时根。 let _temporary = cx.new_object()?; // 故意在完成分配后报错,检验 RAII 不依赖回调正常返回。 if self.0 { return Err(VmError::InvalidArgument("intentional failure")); } Ok(NativeResult::Return(Value::Object(value))) } } #[test] /// 验证成功只移交一个结果根,未返回临时对象及错误路径分配全部回收。 fn scoped_allocations_return_one_root_and_clean_up_errors() { // 独立 VM 隔离测试对象图,所有结果根由场景显式归还。 let mut vm = Vm::new().unwrap(); // 分配上下文函数载荷,captures 管理显式托管边,分派器 Arc 不代替它们。 let function = vm .allocate(ContextNativeFunction::new( "allocate", Allocate(false), vec![], )) .unwrap(); // VM 调用已完成根移交,返回对象应只有一份调用者根。 let value = vm.call(function, &[]).unwrap(); // 解构返回值只复制身份,不增加引用计数。 let Value::Object(object) = value else { panic!("expected object") }; assert_eq!(vm.ref_count(object).unwrap(), 1); assert_eq!(vm.object_count(), 2); // 归还成功结果,之后只剩函数载荷,临时对象应已自动清理。 vm.release_value(value).unwrap(); vm.replace_payload( function, ContextNativeFunction::new("fail", Allocate(true), vec![]), ) .unwrap(); assert!(vm.call(function, &[]).is_err()); assert_eq!(vm.object_count(), 1); vm.release(function).unwrap(); assert_eq!(vm.object_count(), 0); } #[derive(Debug)] /// 返回目标借用身份,由包装载荷 captures 保活,NativeContext 为 Invoke 输出取得拥有根。 struct Delegate(ObjectRef); impl NativeCallable for Delegate { /// VM 在 NativeContext 内调用此测试分派器,临时分配与输入由上下文保活,返回/委托后移交输出根。 fn invoke(&self, _cx: &mut NativeContext<'_>, args: &[Value]) -> Result { Ok(NativeResult::Invoke { function: self.0, arguments: args.to_vec(), }) } } #[test] /// 验证 intrinsic 检查读取当前载荷的真实 Rust 实现,不能仅按不变函数身份推断。 fn native_implementation_inspection_follows_payload_replacement() { // 独立 VM 隔离测试对象图,所有结果根由场景显式归还。 let mut vm = Vm::new().unwrap(); // 分配上下文函数载荷,captures 管理显式托管边,分派器 Arc 不代替它们。 let function = vm .allocate(ContextNativeFunction::new( "allocate", Allocate(false), vec![], )) .unwrap(); // 借用当前 ContextNativeFunction 载荷,下转型检查不分配对象或执行回调。 let payload = vm .payload::(function, "native") .unwrap(); assert!(payload.implementation::().is_some()); assert!(payload.implementation::().is_none()); // 记录更新前版本,替换实现后身份不变但版本必须增加。 let version = vm.change_version(function).unwrap(); // 委托目标有测试根,也会由 Delegate 包装载荷显式 captures 保留。 let target = vm .allocate(ContextNativeFunction::new( "target", Allocate(false), vec![], )) .unwrap(); vm.replace_payload( function, ContextNativeFunction::new("delegate", Delegate(target), vec![target]), ) .unwrap(); // 借用当前 ContextNativeFunction 载荷,下转型检查不分配对象或执行回调。 let payload = vm .payload::(function, "native") .unwrap(); assert!(payload.implementation::().is_none()); assert_eq!(payload.implementation::().unwrap().0, target); assert!(vm.change_version(function).unwrap() > version); // 逐项归还测试拥有根,包装载荷内部捕获边由 Heap 自动释放。 for object in [function, target] { vm.release(object).unwrap(); } assert_eq!(vm.object_count(), 0); } #[test] /// 验证原生委托与目标共享累计预算,预算失败后目标/参数临时根计数恢复。 fn native_delegation_shares_budget_and_cleans_owned_arguments() { // 独立 VM 隔离测试对象图,所有结果根由场景显式归还。 let mut vm = Vm::new().unwrap(); // 委托目标有测试根,也会由 Delegate 包装载荷显式 captures 保留。 let target = vm .allocate(ContextNativeFunction::new( "target", Allocate(false), vec![], )) .unwrap(); // Delegate 返回 Invoke 而不是递归 vm.call,让外层共享预算与清理流程。 let delegate = vm .allocate(ContextNativeFunction::new( "delegate", Delegate(target), vec![target], )) .unwrap(); // 额外对象参数用于观察失败委托是否遗留执行根。 let arg = vm.new_object().unwrap(); assert!( vm.call_with_limits( delegate, &[Value::Object(arg)], ExecutionLimits { max_frames: 1, max_instructions: 1 } ) .is_err() ); assert_eq!(vm.ref_count(arg).unwrap(), 1); assert_eq!(vm.ref_count(target).unwrap(), 2); // VM 调用已完成根移交,返回对象应只有一份调用者根。 let value = vm.call(delegate, &[]).unwrap(); // 归还成功结果,之后只剩函数载荷,临时对象应已自动清理。 vm.release_value(value).unwrap(); vm.release(delegate).unwrap(); vm.release(target).unwrap(); vm.release(arg).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] /// 弱句柄不持根;升级产生根,原对象释放后槽复用也不复活旧弱身份。 fn weak_references_expire_without_retaining_reused_slots() { // 独立 VM 隔离测试对象图,所有结果根由场景显式归还。 let mut vm = Vm::new().unwrap(); // 原始对象有一份分配根,弱身份或通知记录都不应额外持有它。 let object = vm.new_object().unwrap(); // downgrade 只形成借用弱身份,不延长对象寿命。 let weak = vm.downgrade(object).unwrap(); // 成功升级才取得一份独立外部根,随后必须归还。 let upgraded = vm.upgrade(weak).unwrap().unwrap(); vm.release(upgraded).unwrap(); vm.release(object).unwrap(); // 新对象可能复用同槽,但代际不同不能复活 weak。 let replacement = vm.new_object().unwrap(); assert!(vm.upgrade(weak).unwrap().is_none()); // 另一堆升级旧身份应明确报 ForeignHandle,而不是过期 None。 let mut foreign = Vm::new().unwrap(); assert_eq!(foreign.upgrade(weak), Err(VmError::ForeignHandle)); vm.release(replacement).unwrap(); } #[test] /// 同对象/字段通知合并最新版本,容器更新发 Contents,通知不充当 GC 根。 fn changes_coalesce_cover_containers_and_do_not_keep_sources_alive() { // 独立 VM 隔离测试对象图,所有结果根由场景显式归还。 let mut vm = Vm::new().unwrap(); // 原始对象有一份分配根,弱身份或通知记录都不应额外持有它。 let object = vm.new_object().unwrap(); vm.set_attr(object, "value", Value::Bool(false)).unwrap(); vm.set_attr(object, "value", Value::Bool(true)).unwrap(); // 两次同名写入的最终版本应出现在合并通知中。 let version = vm.change_version(object).unwrap(); // 取走通知批次同时重置缓存,数据中的源身份不 retain。 let changes = vm.take_changes(); assert!(!changes.rescan_required); assert_eq!(changes.changes.len(), 1); assert_eq!(changes.changes[0].key, ChangeKey::Attribute("value".into())); assert_eq!(changes.changes[0].version, version); // Cell 更新/清空使用载荷替换,通知键为 Contents,不是普通字段。 let cell = vm.new_cell(None).unwrap(); vm.cell_set(cell, Value::Object(object)).unwrap(); vm.cell_clear(cell).unwrap(); assert_eq!(vm.take_changes().changes[0].key, ChangeKey::Contents); vm.set_attr(object, "self", Value::Object(object)).unwrap(); vm.release(object).unwrap(); assert_eq!(vm.collect_cycles(), 1); vm.release(cell).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] /// 超过通知唯一键上限清空精确条目并标记重扫,取走批次后新通知正常开始。 fn notification_overflow_requires_rescan_instead_of_losing_changes_silently() { // 独立 VM 隔离测试对象图,所有结果根由场景显式归还。 let mut vm = Vm::new().unwrap(); // 原始对象有一份分配根,弱身份或通知记录都不应额外持有它。 let object = vm.new_object().unwrap(); // 制造超过 4096 个不同字段键,重复同键不足以触发通知溢出。 for index in 0..4100 { vm.set_attr(object, index.to_string(), Value::Nil).unwrap(); } // 取走通知批次同时重置缓存,数据中的源身份不 retain。 let changes = vm.take_changes(); assert!(changes.rescan_required); assert!(changes.changes.is_empty()); vm.set_attr(object, "after", Value::Nil).unwrap(); assert_eq!(vm.take_changes().changes.len(), 1); vm.release(object).unwrap(); } #[derive(Debug)] struct ReadResult { /// 输入借用身份,包装载荷 captures 负责保活。 source: ObjectRef, /// 是否故意写旧输入,触发只读保护错误。 mutate: bool, } impl NativeCallable for ReadResult { /// VM 在 NativeContext 内调用此测试分派器,临时分配与输入由上下文保活,返回/委托后移交输出根。 fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result { // 借用读取 source 的输入字段,读取路径纳入当前只读依赖收集。 let value = cx.store().get_attr(self.source, "value")?; // 非法分支试图修改进入只读前已存在的 source,应在写屏障处失败。 if self.mutate { cx.set_attr(self.source, "value", Value::Number(99_i64.into()))?; } // 新结果不在进入只读时的保护快照内,允许初始化 copied 字段。 let result = cx.new_object()?; cx.set_attr(result, "copied", value)?; Ok(NativeResult::Return(Value::Object(result))) } } #[test] /// 只读调用记录输入观察并允许构造新结果,非法改旧对象报错后作用域必须恢复。 fn read_only_calls_track_borrowed_dependencies_allow_results_and_restore_after_error() { // 独立 VM 隔离测试对象图,所有结果根由场景显式归还。 let mut vm = Vm::new().unwrap(); // 输入源在调用前创建并初始化,是只读保护与读取依赖的对象。 let source = vm.new_object().unwrap(); vm.set_attr(source, "value", Value::Number(7_i64.into())) .unwrap(); // 分配上下文函数载荷,captures 管理显式托管边,分派器 Arc 不代替它们。 let function = vm .allocate(ContextNativeFunction::new( "read result", ReadResult { source, mutate: false, }, vec![source], )) .unwrap(); // 丢弃场景初始化通知,只检查本次只读派生构造是否发布了输入变更。 vm.take_changes(); // 结果是拥有值,reads 是借用观察集合,两个所有权语义不能混同。 let (value, reads) = vm .call_read_only_with_limits(function, &[], ExecutionLimits::default()) .unwrap(); assert!(reads.contains(&source)); assert!(vm.take_changes().changes.is_empty()); // 检查新结果 copied 值后归还结果根,不能 release 读取依赖句柄。 let Value::Object(result) = value else { panic!("result") }; assert_eq!( vm.get_attr(result, "copied").unwrap(), Value::Number(7_i64.into()) ); vm.release(result).unwrap(); vm.replace_payload( function, ContextNativeFunction::new( "illegal write", ReadResult { source, mutate: true, }, vec![source], ), ) .unwrap(); assert!( vm.call_read_only_with_limits(function, &[], ExecutionLimits::default()) .is_err() ); assert_eq!( vm.get_attr(source, "value").unwrap(), Value::Number(7_i64.into()) ); vm.set_attr(source, "value", Value::Number(8_i64.into())) .unwrap(); vm.release(function).unwrap(); 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; // 本测试直接使用 Store 精确分类入口,不需要执行器帧。 let mut store = objects::ObjectStore::new().unwrap(); // 普通 Record 不符合 Number,但有效对象应符合 Any。 let object = store.new_object().unwrap(); store .set_attr(object, "value", Value::Number(1.into())) .unwrap(); // 只读范围收集整体观察,子区间分类不会清空外层依赖。 let scope = store.begin_read_only(); // 每个子区间重新开始收集,按 LIFO 结束;重复读取也要在新区域出现。 let token = store.begin_read_tracking().unwrap(); assert!( store .matches_annotation(&Value::Object(object), AnnotationType::Any) .unwrap() ); // 返回 ReadAccess 区分 content_read,句柄仍不拥有根。 let reads = store.end_read_tracking_details(token).unwrap(); assert!( reads .iter() .any(|read| read.object == object && !read.content_read) ); // 每个子区间重新开始收集,按 LIFO 结束;重复读取也要在新区域出现。 let token = store.begin_read_tracking().unwrap(); assert!( !store .matches_annotation(&Value::Object(object), AnnotationType::Number) .unwrap() ); // 异堆对象即使匹配 Any 也先拒绝无效归属。 let mut foreign = objects::ObjectStore::new().unwrap(); // 有效但异堆的测试输入,用于排除 Any 跳过句柄校验的错误实现。 let alien = foreign.new_object().unwrap(); assert!(matches!( store.matches_annotation(&Value::Object(alien), AnnotationType::Any), Err(VmError::ForeignHandle) )); foreign.release(alien).unwrap(); // 补一次普通字段读取,与前面的验证读取合并时 content_read 必须为 true。 store.get_attr(object, "value").unwrap(); // 返回 ReadAccess 区分 content_read,句柄仍不拥有根。 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); } #[derive(Debug)] struct TypedNative { /// 构造时快照的完整签名,包装载荷负责 trace 类型定义。 signature: endor_core::CallSignature, /// VM 外共享调用计数,clone Arc 不涉及托管对象根。 calls: std::sync::Arc, /// 可选委托目标借用身份,包装载荷 captures 必须持有对应强边。 delegate: Option, /// 无委托时是否分配新对象,否则回传最后参数/Nil。 allocate_result: bool, } impl NativeCallable for TypedNative { /// ContextNativeFunction 快照此完整元数据并 trace 定义边;clone 本身不增加外部根。 fn signature(&self) -> Option { Some(self.signature.clone()) } /// 测试原生分派入口,回调内借用参数/目标,NativeContext 校验并移交 Return 或 Invoke 输出根。 fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result { // 计数器只在真实回调进入后增加,测试据此区分预检失败与返回后失败。 self.calls.fetch_add(1, std::sync::atomic::Ordering::SeqCst); // 委托不递归执行 VM,返回目标/参数借用表示给统一准备循环。 if let Some(function) = self.delegate { return Ok(NativeResult::Invoke { function, arguments: args.to_vec(), }); } // 可选择产生新对象或回传最后参数,分别检验临时根清理和借用结果移交。 Ok(NativeResult::Return(if self.allocate_result { Value::Object(cx.new_object()?) } else { args.last().cloned().unwrap_or(Value::Nil) })) } } #[test] /// 参数错误必须在 calls 增加前拒绝,错误对象结果由上下文回收,不留下临时分配。 fn native_types_preflight_arguments_and_clean_wrong_result_roots() { use endor_core::{AnnotationType as T, CallEffect, CallSignature}; use std::sync::{ Arc, atomic::{AtomicUsize, Ordering}, }; // 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。 let mut vm = Vm::new().unwrap(); // VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。 let calls = Arc::new(AtomicUsize::new(0)); // 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。 let function = vm .allocate(ContextNativeFunction::new( "typed", TypedNative { signature: CallSignature::exact(vec![T::Number], T::Number, CallEffect::ReadOnly), calls: calls.clone(), delegate: None, allocate_result: true, }, vec![], )) .unwrap(); assert!( matches!(vm.call(function, &[Value::Text("bad".into())]), Err(VmError::TypeMismatch { position, .. }) if position == "parameter 1") ); assert_eq!(calls.load(Ordering::SeqCst), 0); assert!( matches!(vm.call(function, &[Value::Number(7.into())]), Err(VmError::TypeMismatch { position, .. }) if position == "return") ); assert_eq!(calls.load(Ordering::SeqCst), 1); assert_eq!(vm.object_count(), 1); vm.release(function).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] /// 多层原生包装委托同一源码帧,最终结果必须满足链上全部返回约束,不额外消耗源码帧深度。 fn delegated_source_results_obey_every_native_contract_without_extra_frames() { use endor_core::{ AnnotationType as T, CallEffect, CallSignature, CodeLayout, EndorCode, Instruction, Opcode, }; use std::sync::{Arc, atomic::AtomicUsize}; // 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。 let mut vm = Vm::new().unwrap(); // 最小源码函数返回固定常量,区分原生包装约束与源码帧自身的执行。 let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadConst, 0), Instruction::simple(Opcode::ReturnValue), ], vec![Value::Text("source result".into())], CodeLayout::default(), ) .unwrap(); // 托管代码拥有常量强边,源码函数还会持有代码边。 let code = vm.new_code(code).unwrap(); // 源码环境有分配根,函数持有它的内部边。 let globals = vm.new_dict().unwrap(); // 源码函数返回文本或 Nil,作为后续返回契约一致/不一致的终点。 let source = vm .new_function("source", code, globals, vec![], vec![]) .unwrap(); // VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。 let calls = Arc::new(AtomicUsize::new(0)); // 内层包装允许 String,与源码文本结果相符。 let inner = vm .allocate(ContextNativeFunction::new( "inner", TypedNative { signature: CallSignature::exact(vec![], T::String, CallEffect::ReadOnly), calls: calls.clone(), delegate: Some(source), allocate_result: false, }, vec![source], )) .unwrap(); // 外层却要求 Number,必须在源码最终返回时拒绝,即使内层已通过。 let outer = vm .allocate(ContextNativeFunction::new( "outer", TypedNative { signature: CallSignature::exact(vec![], T::Number, CallEffect::ReadOnly), calls, delegate: Some(inner), allocate_result: false, }, vec![inner], )) .unwrap(); // 只允许一层源码帧,验证多层原生委托不会伪造额外源码帧。 let limits = ExecutionLimits { max_frames: 1, max_instructions: 100, }; assert_eq!( vm.call_with_limits(inner, &[], limits).unwrap(), Value::Text("source result".into()) ); assert!( matches!(vm.call_with_limits(outer, &[], limits), Err(VmError::TypeMismatch { function, position, .. }) if function == "outer" && position == "return") ); assert!(matches!( vm.call_with_limits( outer, &[], ExecutionLimits { max_frames: 0, ..limits } ), Err(VmError::ExecutionLimit("frames")) )); // 归还测试每份拥有根,签名/捕获/方法内部边由 Heap 处理。 for object in [outer, inner, source, globals, code] { vm.release(object).unwrap(); } // 清理可能由类型/签名/方法持有关系形成的无根环,断言堆归零。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] /// 签名独立 trace 名义类型定义,绑定后仍使用真实目标签名并扣除 self。 fn signatures_trace_nominal_types_and_bound_receivers_use_the_actual_contract() { use endor_core::{AnnotationType as T, CallEffect, CallSignature, TypeConstructor}; use std::sync::{ Arc, atomic::{AtomicUsize, Ordering}, }; // 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。 let mut vm = Vm::new().unwrap(); // 显式自举 Object/Type,名义定义在同一对象图中受计数管理。 let roots = vm.bootstrap_types().unwrap(); // 名义定义用于签名约束,归还测试根后仍需由签名边保活。 let definition = vm .new_type("Receiver", roots.object, TypeConstructor::Record) .unwrap(); // VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。 let calls = Arc::new(AtomicUsize::new(0)); // 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。 let function = vm .allocate(ContextNativeFunction::new( "echo", TypedNative { signature: CallSignature::exact( vec![T::Nominal(definition), T::Number], T::Number, CallEffect::ReadOnly, ), calls: calls.clone(), delegate: None, allocate_result: false, }, vec![], )) .unwrap(); // Metadata alone traces the definition. // 弱身份不保活定义,清理后升级 None 用于检查无隐藏根。 let weak = vm.downgrade(definition).unwrap(); vm.define_method(definition, "echo", function).unwrap(); // 实例持有定义类型边,绑定方法再持有实例边。 let receiver = vm.new_object().unwrap(); vm.attach_type(receiver, definition).unwrap(); // 归还原始定义根;其后存活来自实例/签名或执行检查根。 vm.release(definition).unwrap(); // bind_attr 交付绑定对象拥有根,实际调用会先补接收者。 let Value::Object(bound) = vm.bind_attr(receiver, "echo").unwrap() else { panic!("bound method") }; assert_eq!( vm.call_arity(bound).unwrap(), Some(endor_core::CallArity::exact(1)) ); assert!(vm.call(bound, &[Value::Bool(true)]).is_err()); assert_eq!(calls.load(Ordering::SeqCst), 0); assert_eq!( vm.call(bound, &[Value::Number(9.into())]).unwrap(), Value::Number(9.into()) ); // 归还测试每份拥有根,签名/捕获/方法内部边由 Heap 处理。 for object in [bound, receiver, function, roots.object, roots.type_type] { vm.release(object).unwrap(); } // 清理可能由类型/签名/方法持有关系形成的无根环,断言堆归零。 vm.collect_cycles(); assert!(vm.upgrade(weak).unwrap().is_none()); assert_eq!(vm.object_count(), 0); } #[test] /// 只读域中 Mutating/External 在回调前拒绝,普通调用仍允许进入。 fn declared_host_effects_are_rejected_before_read_only_dispatch() { use endor_core::{AnnotationType as T, CallEffect, CallSignature}; use std::sync::{ Arc, atomic::{AtomicUsize, Ordering}, }; // 两个不可在只读域调用的声明分别测试,不能只覆盖其中一个。 for effect in [CallEffect::Mutating, CallEffect::External] { // 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。 let mut vm = Vm::new().unwrap(); // VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。 let calls = Arc::new(AtomicUsize::new(0)); // 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。 let function = vm .allocate(ContextNativeFunction::new( "effect", TypedNative { signature: CallSignature::exact(vec![], T::Unit, effect), calls: calls.clone(), delegate: None, allocate_result: false, }, vec![], )) .unwrap(); assert!( vm.call_read_only_with_limits(function, &[], ExecutionLimits::default()) .is_err() ); assert_eq!(calls.load(Ordering::SeqCst), 0); assert_eq!(vm.call(function, &[]).unwrap(), Value::Nil); vm.release(function).unwrap(); assert_eq!(vm.object_count(), 0); } } #[test] /// 旧式函数也执行完整签名/可变参数/返回检查,独立 arity 与签名不一致必须拒绝。 fn legacy_native_signatures_check_optional_variadic_types_and_metadata() { use endor_core::{AnnotationType as T, CallArity, CallEffect, CallSignature, NativeFunction}; /// 旧式回调返回最后一个借用参数,空参数返回 Nil;拥有根仍由 VM 补充。 fn echo(args: &[Value]) -> Result { Ok(args.last().cloned().unwrap_or(Value::Nil)) } // 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。 let mut vm = Vm::new().unwrap(); // 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。 let function = vm .allocate( NativeFunction::new("legacy", echo).with_signature(CallSignature { arity: CallArity::range(0, usize::MAX), parameters: vec![], variadic: Some(T::Number.into()), returns: T::Number.into(), effect: CallEffect::ReadOnly, }), ) .unwrap(); assert_eq!( vm.call( function, &[Value::Number(1.into()), Value::Number(2.into())] ) .unwrap(), Value::Number(2.into()) ); assert!(vm.call(function, &[Value::Bool(true)]).is_err()); assert!( matches!(vm.call(function, &[]), Err(VmError::TypeMismatch { position, .. }) if position == "return") ); // 故意同时配置互相矛盾的签名和独立 arity,查询/调用都应报元数据错误。 let invalid = vm .allocate( NativeFunction::new("invalid", echo) .with_signature(CallSignature::exact( vec![T::Number], T::Number, CallEffect::ReadOnly, )) .with_arity(CallArity::exact(0)), ) .unwrap(); assert!(vm.call_arity(invalid).is_err()); assert!(vm.call(invalid, &[]).is_err()); vm.release(invalid).unwrap(); vm.release(function).unwrap(); assert_eq!(vm.object_count(), 0); } #[derive(Debug)] struct EraseThenDelegate { /// 构造时快照的完整签名,包装载荷负责 trace 类型定义。 signature: endor_core::CallSignature, /// 被委托源码函数,由包装载荷 captures 保活至回调中输出保护接管。 target: ObjectRef, } impl NativeCallable for EraseThenDelegate { /// ContextNativeFunction 快照此完整元数据并 trace 定义边;clone 本身不增加外部根。 fn signature(&self) -> Option { Some(self.signature.clone()) } /// 测试原生分派入口,回调内借用参数/目标,NativeContext 校验并移交 Return 或 Invoke 输出根。 fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result { // 将当前函数换为普通 Record,移除签名及捕获边,迫使返回约束靠执行器自己的根存活。 cx.replace_payload(cx.callee(), endor_core::Record)?; Ok(NativeResult::Invoke { function: self.target, arguments: vec![], }) } } #[test] /// 回调替换自己的载荷删除签名边后,委托链独立持有返回类型根,诊断完成后才释放。 fn delegated_return_type_survives_removal_of_the_original_signature_edges() { use endor_core::{ AnnotationType as T, CallEffect, CallSignature, CodeLayout, EndorCode, Instruction, Opcode, TypeConstructor, }; // 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。 let mut vm = Vm::new().unwrap(); // 显式自举 Object/Type,名义定义在同一对象图中受计数管理。 let roots = vm.bootstrap_types().unwrap(); // 名义定义用于签名约束,归还测试根后仍需由签名边保活。 let definition = vm .new_type("Transient", roots.object, TypeConstructor::Record) .unwrap(); // 弱身份不保活定义,清理后升级 None 用于检查无隐藏根。 let weak = vm.downgrade(definition).unwrap(); // 最小源码函数返回固定常量,区分原生包装约束与源码帧自身的执行。 let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadConst, 0), Instruction::simple(Opcode::ReturnValue), ], vec![Value::Nil], CodeLayout::default(), ) .unwrap(); // 托管代码拥有常量强边,源码函数还会持有代码边。 let code = vm.new_code(code).unwrap(); // 源码环境有分配根,函数持有它的内部边。 let globals = vm.new_dict().unwrap(); // 源码函数返回文本或 Nil,作为后续返回契约一致/不一致的终点。 let source = vm .new_function("source", code, globals, vec![], vec![]) .unwrap(); // 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。 let mut signature = CallSignature::exact(vec![], T::Any, CallEffect::Mutating); // 联合第一项引用将失去原载荷保活的 Transient,错误诊断仍应能读取其名字。 signature.returns = endor_core::CallType::Union(vec![T::Nominal(definition), T::Number]); // 包装函数显式 captures 持有委托目标,输出目标根由 NativeContext 另取得。 let wrapper = vm .allocate(ContextNativeFunction::new( "erased", EraseThenDelegate { signature, target: source, }, vec![source], )) .unwrap(); // 归还原始定义根;其后存活来自实例/签名或执行检查根。 vm.release(definition).unwrap(); assert!( matches!(vm.call(wrapper, &[]), Err(VmError::TypeMismatch { function, expected, .. }) if function == "erased" && expected == "Transient | Number") ); assert!(vm.upgrade(weak).unwrap().is_none()); // 归还测试每份拥有根,签名/捕获/方法内部边由 Heap 处理。 for object in [ wrapper, source, code, globals, roots.object, roots.type_type, ] { vm.release(object).unwrap(); } // 清理可能由类型/签名/方法持有关系形成的无根环,断言堆归零。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] /// 联合各分支可接纳,非法后续类型及空联合不得被 Any/提前匹配掩盖。 fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_metadata() { use endor_core::{AnnotationType as T, CallEffect, CallSignature, CallType}; use std::sync::{ Arc, atomic::{AtomicUsize, Ordering}, }; // 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。 let mut vm = Vm::new().unwrap(); // VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。 let calls = Arc::new(AtomicUsize::new(0)); // 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。 let mut signature = CallSignature::exact(vec![T::Any], T::Any, CallEffect::ReadOnly); signature.parameters[0] = CallType::Union(vec![T::String, T::Bool]); signature.returns = signature.parameters[0].clone(); // 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。 let function = vm .allocate(ContextNativeFunction::new( "union", TypedNative { signature, calls: calls.clone(), delegate: None, allocate_result: false, }, vec![], )) .unwrap(); // 每个合法联合/nullable 分支都实际调用一次,计数器验证非法输入未进入。 for value in [ Value::Text("capture".into()), Value::Bool(true), Value::Bool(false), ] { assert_eq!( vm.call(function, std::slice::from_ref(&value)).unwrap(), value ); } assert!( matches!(vm.call(function, &[Value::Number(3.into())]), Err(VmError::TypeMismatch { expected, .. }) if expected == "String | Bool") ); assert_eq!(calls.load(Ordering::SeqCst), 3); // 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。 let mut signature = CallSignature::exact(vec![T::Any], T::Any, CallEffect::ReadOnly); signature.parameters[0] = CallType::nullable(T::Number); signature.returns = signature.parameters[0].clone(); vm.replace_payload( function, ContextNativeFunction::new( "nullable", TypedNative { signature, calls: calls.clone(), delegate: None, allocate_result: false, }, vec![], ), ) .unwrap(); // 每个合法联合/nullable 分支都实际调用一次,计数器验证非法输入未进入。 for value in [Value::Nil, Value::Number(2.into())] { assert_eq!( vm.call(function, std::slice::from_ref(&value)).unwrap(), value ); } // 普通 Record 不能用作 Nominal 的类型定义,即使前分支 Any 已能匹配也须拒绝。 let record = vm.new_object().unwrap(); // 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。 let mut signature = CallSignature::exact(vec![T::Any], T::Any, CallEffect::ReadOnly); // 完整联合先验证全部定义,不能因第一项 Any 匹配而跳过后面的非法类型句柄。 signature.parameters[0] = CallType::Union(vec![T::Any, T::Nominal(record)]); vm.replace_payload( function, ContextNativeFunction::new( "invalid union", TypedNative { signature, calls: calls.clone(), delegate: None, allocate_result: false, }, vec![], ), ) .unwrap(); assert!(vm.call(function, &[Value::Nil]).is_err()); assert_eq!(calls.load(Ordering::SeqCst), 5); // 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。 let mut signature = CallSignature::exact(vec![], T::Any, CallEffect::ReadOnly); signature.returns = CallType::Union(vec![]); vm.replace_payload( function, ContextNativeFunction::new( "empty union", TypedNative { signature, calls: calls.clone(), delegate: None, allocate_result: false, }, vec![], ), ) .unwrap(); assert!(vm.call(function, &[]).is_err()); assert_eq!(calls.load(Ordering::SeqCst), 5); vm.release(record).unwrap(); vm.release(function).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] /// Delegated 包装器能进入只读域,但实际目标仍在同域核对效果,目标换为纯函数后可通过。 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(); // VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。 let calls = Arc::new(AtomicUsize::new(0)); // 目标独立计数,用于证明包装器已经执行但目标在副作用前被效果检查拦截。 let target_calls = Arc::new(AtomicUsize::new(0)); // 初始目标声明 Mutating,在只读委托链中不允许执行;随后替换成 ReadOnly。 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(); // 包装函数显式 captures 持有委托目标,输出目标根由 NativeContext 另取得。 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 ); // 归还测试每份拥有根,签名/捕获/方法内部边由 Heap 处理。 for object in [wrapper, target] { vm.release(object).unwrap(); } assert_eq!(vm.object_count(), 0); }