diff --git a/crates/core/tests/native_context.rs b/crates/core/tests/native_context.rs index 9b344ab..09d77f6 100644 --- a/crates/core/tests/native_context.rs +++ b/crates/core/tests/native_context.rs @@ -382,23 +382,32 @@ fn annotation_reads_remain_dependencies_without_becoming_program_content_reads() #[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 { @@ -408,14 +417,18 @@ impl NativeCallable for TypedNative { } #[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", @@ -442,12 +455,15 @@ fn native_types_preflight_arguments_and_clean_wrong_result_roots() { } #[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), @@ -457,12 +473,17 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() { 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", @@ -475,6 +496,7 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() { vec![source], )) .unwrap(); + // 外层却要求 Number,必须在源码最终返回时拒绝,即使内层已通过。 let outer = vm .allocate(ContextNativeFunction::new( "outer", @@ -487,6 +509,7 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() { vec![inner], )) .unwrap(); + // 只允许一层源码帧,验证多层原生委托不会伪造额外源码帧。 let limits = ExecutionLimits { max_frames: 1, max_instructions: 100, @@ -509,26 +532,34 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() { ), 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", @@ -545,11 +576,15 @@ fn signatures_trace_nominal_types_and_bound_receivers_use_the_actual_contract() 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") }; @@ -563,24 +598,31 @@ fn signatures_trace_nominal_types_and_bound_receivers_use_the_actual_contract() 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", @@ -605,12 +647,16 @@ fn declared_host_effects_are_rejected_before_read_only_dispatch() { } #[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 { @@ -634,6 +680,7 @@ fn legacy_native_signatures_check_optional_variadic_types_and_metadata() { assert!( matches!(vm.call(function, &[]), Err(VmError::TypeMismatch { position, .. }) if position == "return") ); + // 故意同时配置互相矛盾的签名和独立 arity,查询/调用都应报元数据错误。 let invalid = vm .allocate( NativeFunction::new("invalid", echo) @@ -654,14 +701,19 @@ fn legacy_native_signatures_check_optional_variadic_types_and_metadata() { #[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, @@ -671,17 +723,23 @@ impl NativeCallable for EraseThenDelegate { } #[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), @@ -691,13 +749,19 @@ fn delegated_return_type_survives_removal_of_the_original_signature_edges() { 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", @@ -708,11 +772,13 @@ fn delegated_return_type_survives_removal_of_the_original_signature_edges() { 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, @@ -723,22 +789,28 @@ fn delegated_return_type_survives_removal_of_the_original_signature_edges() { ] { 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", @@ -751,6 +823,7 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met vec![], )) .unwrap(); + // 每个合法联合/nullable 分支都实际调用一次,计数器验证非法输入未进入。 for value in [ Value::Text("capture".into()), Value::Bool(true), @@ -765,6 +838,7 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met 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(); @@ -782,15 +856,18 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met ), ) .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); - // A matching first branch must not hide a later invalid nominal definition. + // 完整联合先验证全部定义,不能因第一项 Any 匹配而跳过后面的非法类型句柄。 signature.parameters[0] = CallType::Union(vec![T::Any, T::Nominal(record)]); vm.replace_payload( function, @@ -808,6 +885,7 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met .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( @@ -832,15 +910,20 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met } #[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", @@ -853,6 +936,7 @@ fn delegated_effects_keep_the_original_read_only_scope_and_check_the_actual_targ vec![], )) .unwrap(); + // 包装函数显式 captures 持有委托目标,输出目标根由 NativeContext 另取得。 let wrapper = vm .allocate(ContextNativeFunction::new( "wrapper", @@ -892,6 +976,7 @@ fn delegated_effects_keep_the_original_read_only_scope_and_check_the_actual_targ .0, Value::Nil ); + // 归还测试每份拥有根,签名/捕获/方法内部边由 Heap 处理。 for object in [wrapper, target] { vm.release(object).unwrap(); }