//! 通过手工指令验证 VM 求值、根生命周期及暂停行为,测试框架直接调用,无需源码解析器。 //! op/simple/make_code 统一构造已验证代码;错误编码用 EndorCode 直接检验构造阶段拒绝。 use std::any::Any; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; use endor_core::{ CodeLayout, CompareOp, EndorCode, EndorCodeObject, EndorFunctionObject, EndorNativeFunctionObject, ExecutionLimits, Instruction, ObjectPayload, ObjectRef, Opcode, Value, Vm, VmError, }; /// 测试辅助,构造完整 u32 操作数逻辑指令,实际扩展前缀由编码器生成。 fn op(opcode: Opcode, argument: u32) -> Instruction { Instruction::new(opcode, argument) } /// 测试辅助,构造参数为零的无操作数指令,不手写物理编码。 fn simple(opcode: Opcode) -> Instruction { Instruction::simple(opcode) } /// 测试辅助,创建直接文本 Value,不分配堆字符串。 fn text(value: &str) -> Value { Value::Text(value.into()) } /// 测试辅助,编码并验证指令/常量/布局后分配代码拥有根;失败直接使测试失败。 fn make_code( vm: &mut Vm, instructions: &[Instruction], constants: Vec, layout: CodeLayout, ) -> ObjectRef { vm.new_code(EndorCode::from_instructions(instructions, constants, layout).unwrap()) .unwrap() } /// 其他执行测试复用的最小返回常量代码,根由调用测试归还。 fn return_constant(vm: &mut Vm, value: Value) -> ObjectRef { make_code( vm, &[op(Opcode::LoadConst, 0), simple(Opcode::ReturnValue)], vec![value], CodeLayout::default(), ) } #[derive(Debug)] /// 可选原地赋值测试载荷;只保存 Rust 原子数,不含可变托管对象边。 struct SharedReference(AtomicUsize); impl ObjectPayload for SharedReference { /// Store 下转型识别测试载荷;与语言名义类型分开。 fn as_any(&self) -> &dyn Any { self } /// 测试错误/诊断类别。 fn kind_name(&self) -> &'static str { "shared-reference" } /// Store 可选旧式原地赋值协议,仅支持同类且先校验后原子更新,不改变托管引用边。 fn assign_from(&self, source: &dyn ObjectPayload) -> Result { // 异类载荷返回 false 表示不做原地赋值,不是错误,也不修改目标。 let Some(source) = source.as_any().downcast_ref::() else { return Ok(false); }; // 先读候选内容再校验,拒绝值不能留下半写状态。 let value = source.0.load(Ordering::SeqCst); // 注入明确协议错误,验证执行器清理而不是静默重绑定。 if value == 99 { return Err(VmError::InvalidArgument("rejected replacement")); } // 只更新宿主原子状态,不引入未 trace 的托管引用边。 self.0.store(value, Ordering::SeqCst); Ok(true) } } #[test] /// 局部与全局赋值都保留共享对象旧身份并更新内容,已有 alias 可见,清理不泄漏根。 fn opt_in_assignment_keeps_alias_identity_ordinary_values_and_root_accounting() { // 同一字节码模板分别覆盖 StoreFast 与 StoreGlobal,避免只测试一条赋值入口。 for local in [false, true] { // 独立 VM 隔离对象计数/全局绑定,清理断言只针对本场景。 let mut vm = Vm::new().unwrap(); // 共享目标以 1 初始化,同时另由 alias 持有;赋值后身份应保持。 let old = vm.allocate(SharedReference(AtomicUsize::new(1))).unwrap(); // 候选共享值初始化为 2,复制内容到 old 不等于让 alias 指向此新身份。 let new = vm.allocate(SharedReference(AtomicUsize::new(2))).unwrap(); // 函数执行环境与名称表索引对应,字典内部边独立于测试外部根。 let globals = vm.new_dict().unwrap(); vm.dict_set(globals, text("alias"), Value::Object(old)) .unwrap(); // 只切换变量访问策略,操作数零在两类布局中分别代表槽/全局名。 let (store, load) = if local { (Opcode::StoreFast, Opcode::LoadFast) } else { (Opcode::StoreGlobal, Opcode::LoadGlobal) }; // 操作数和布局在构造时校验,execute 期间测试实际值流与引用行为。 let code = make_code( &mut vm, &[ op(Opcode::LoadConst, 0), op(store, 0), op(Opcode::LoadConst, 1), op(store, 0), op(load, 0), simple(Opcode::ReturnValue), ], vec![Value::Object(old), Value::Object(new)], // 局部模板使用 locals,另一分支使用 names;不能把两类索引混用。 if local { CodeLayout { locals: vec!["value".into()], ..CodeLayout::default() } } else { CodeLayout { names: vec!["value".into()], ..CodeLayout::default() } }, ); // 执行结果拥有根,读取完成后必须单独归还,不能当作纯借用复制。 let value = vm.execute(code, globals).unwrap(); assert_eq!(value, Value::Object(old)); assert_eq!( vm.dict_get(globals, &text("alias")).unwrap(), Some(Value::Object(old)) ); assert_eq!( vm.payload::(old, "shared-reference") .unwrap() .0 .load(Ordering::SeqCst), 2 ); // 归还结果根后,代码常量、全局 alias 和测试根仍按各自拥有关系存在。 vm.release_value(value).unwrap(); // 归还代码根同时移除常量表内部边,不重复释放常量对象的测试根。 vm.release(code).unwrap(); // 移除全局内部边,使后续测试根归还后图真正为空。 vm.dict_clear(globals).unwrap(); vm.release(globals).unwrap(); vm.release(old).unwrap(); vm.release(new).unwrap(); assert_eq!(vm.object_count(), 0); } } #[test] /// 赋值协议拒绝新值时旧对象内容/全局绑定不变,执行错误根由统一帧清理归还。 fn failed_opt_in_assignment_preserves_target_and_cleans_execution_roots() { // 独立 VM 隔离对象计数/全局绑定,清理断言只针对本场景。 let mut vm = Vm::new().unwrap(); // 共享目标以 1 初始化,同时另由 alias 持有;赋值后身份应保持。 let old = vm.allocate(SharedReference(AtomicUsize::new(1))).unwrap(); // 以哨兵值 99 触发校验拒绝,不允许污染旧目标值。 let rejected = vm.allocate(SharedReference(AtomicUsize::new(99))).unwrap(); // 函数执行环境与名称表索引对应,字典内部边独立于测试外部根。 let globals = vm.new_dict().unwrap(); vm.dict_set(globals, text("value"), Value::Object(old)) .unwrap(); // 操作数和布局在构造时校验,execute 期间测试实际值流与引用行为。 let code = make_code( &mut vm, &[ op(Opcode::LoadConst, 0), op(Opcode::StoreGlobal, 0), op(Opcode::LoadConst, 1), simple(Opcode::ReturnValue), ], vec![Value::Object(rejected), Value::Nil], CodeLayout { names: vec!["value".into()], ..CodeLayout::default() }, ); assert!(vm.execute(code, globals).is_err()); assert_eq!( vm.payload::(old, "shared-reference") .unwrap() .0 .load(Ordering::SeqCst), 1 ); assert_eq!( vm.dict_get(globals, &text("value")).unwrap(), Some(Value::Object(old)) ); // 归还代码根同时移除常量表内部边,不重复释放常量对象的测试根。 vm.release(code).unwrap(); // 移除全局内部边,使后续测试根归还后图真正为空。 vm.dict_clear(globals).unwrap(); vm.release(globals).unwrap(); vm.release(old).unwrap(); vm.release(rejected).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] /// 检查四字节参数前缀编码,再用第 256 号常量验证扩展操作数执行与偏移。 fn wordcode_extended_operands_decode_and_execute_without_truncation() { // 只编码操作数不做常量索引验证,用固定字节向量证明高位未被截断。 let encoded = Instruction::encode_all(&[op(Opcode::LoadConst, 0x01020304)]).unwrap(); assert_eq!(encoded, vec![255, 1, 255, 2, 255, 3, 1, 4]); // 独立 VM 隔离对象计数/全局绑定,清理断言只针对本场景。 let mut vm = Vm::new().unwrap(); // 常量表至少 257 项才能合法引用索引 256。 let mut constants = vec![Value::Nil; 257]; // 将边界索引放置独特值,执行返回可检出仅保留低八位的错误。 constants[256] = text("extended"); // 操作数和布局在构造时校验,execute 期间测试实际值流与引用行为。 let code = make_code( &mut vm, &[op(Opcode::LoadConst, 256), simple(Opcode::ReturnValue)], constants, CodeLayout::default(), ); // 借用已验证逻辑指令检查起始/末尾代码单元位置及最大栈高。 let executable = vm.payload::(code, "code").unwrap().code(); assert_eq!(executable.instructions()[0].offset, 0); assert_eq!(executable.instructions()[0].next_offset, 2); assert_eq!(executable.stack_size(), 1); // 函数执行环境与名称表索引对应,字典内部边独立于测试外部根。 let globals = vm.new_dict().unwrap(); assert_eq!(vm.execute(code, globals).unwrap(), text("extended")); } #[test] /// 空/截断/未知/前缀过多/无参带数据及跳入前缀中部在执行前拒绝。 fn malformed_wordcode_and_unaligned_jumps_are_rejected_at_construction() { // 各非法物理流独立走 EndorCode 构造,均须返回 InvalidBytecode。 for bytes in [ vec![], vec![0], vec![254, 0], vec![255, 1], vec![255, 1, 255, 1, 255, 1, 255, 1, 1, 0], vec![13, 1], ] { assert!(matches!( EndorCode::new(bytes, vec![Value::Nil], vec![], vec![]), Err(VmError::InvalidBytecode { .. }) )); } // 跳转目标 2 位于从代码单元 1 开始的扩展 LOAD_CONST 内部,不能作为逻辑起点。 // 故意跳至带前缀 LOAD_CONST 的第二单元,而非该逻辑指令首前缀。 let bytes = vec![14, 1, 255, 1, 1, 0, 13, 0]; assert!(EndorCode::new(bytes, vec![Value::Nil; 257], vec![], vec![]).is_err()); assert!( EndorCode::from_instructions( &[op(Opcode::LoadConst, 1), simple(Opcode::ReturnValue)], vec![Value::Nil], CodeLayout::default() ) .is_err() ); } #[test] /// 检查栈下溢、落出代码、变高回边、无 Cell 槽与分支汇合高度不一致。 fn verifier_rejects_underflow_fallthrough_bad_cells_and_inconsistent_stack_joins() { // 每个逻辑指令序列隔离一种栈/控制流/槽布局错误。 let cases = [ vec![simple(Opcode::PopTop), simple(Opcode::ReturnValue)], vec![op(Opcode::LoadConst, 0)], vec![op(Opcode::LoadConst, 0), op(Opcode::JumpBackward, 2)], vec![op(Opcode::LoadDeref, 0), simple(Opcode::ReturnValue)], vec![ op(Opcode::LoadConst, 0), op(Opcode::PopJumpIfFalse, 2), op(Opcode::LoadConst, 0), op(Opcode::JumpForward, 1), simple(Opcode::Nop), op(Opcode::LoadConst, 0), simple(Opcode::ReturnValue), ], ]; // 相同常量和空布局下逐例验证,不能用执行时崩溃替代静态拒绝。 for instructions in cases { assert!( EndorCode::from_instructions(&instructions, vec![Value::Nil], CodeLayout::default()) .is_err() ); } // 把局部 captured 明确标为 Cell,再用 LoadFast 证明访问操作码必须匹配槽种类。 let layout = CodeLayout { locals: vec!["captured".into()], cell_variables: vec!["captured".into()], ..CodeLayout::default() }; assert!( EndorCode::from_instructions( &[op(Opcode::LoadFast, 0), simple(Opcode::ReturnValue)], vec![], layout ) .is_err() ); } #[test] /// 验证 Load/StoreFast、全局存取和返回构成完整值流,成功不产生错误回溯。 fn execute_moves_values_between_constants_local_slots_and_globals() { // 独立 VM 隔离对象计数/全局绑定,清理断言只针对本场景。 let mut vm = Vm::new().unwrap(); assert_eq!(vm.object_count(), 0); // 函数执行环境与名称表索引对应,字典内部边独立于测试外部根。 let globals = vm.new_dict().unwrap(); // 操作数和布局在构造时校验,execute 期间测试实际值流与引用行为。 let code = make_code( &mut vm, &[ op(Opcode::LoadConst, 0), op(Opcode::StoreFast, 0), op(Opcode::LoadFast, 0), op(Opcode::StoreGlobal, 0), op(Opcode::LoadGlobal, 0), simple(Opcode::ReturnValue), ], vec![text("hello")], CodeLayout { locals: vec!["local".into()], names: vec!["saved".into()], ..CodeLayout::default() }, ); assert_eq!(vm.execute(code, globals).unwrap(), text("hello")); assert_eq!( vm.dict_get(globals, &text("saved")).unwrap(), Some(text("hello")) ); assert!(vm.last_traceback().is_empty()); // 归还代码根同时移除常量表内部边,不重复释放常量对象的测试根。 vm.release(code).unwrap(); vm.release(globals).unwrap(); assert_eq!(vm.object_count(), 0); } #[derive(Debug)] /// 驱动循环条件的无语言类型原生载荷,Arc 计数器由测试在 VM 外观察。 struct Tick(Arc); impl ObjectPayload for Tick { /// Store 下转型识别测试载荷;与语言名义类型分开。 fn as_any(&self) -> &dyn Any { self } /// 测试错误/诊断类别。 fn kind_name(&self) -> &'static str { "tick" } /// Tick 旧式原生回调,前三次返回真、第四次为假,驱动可终止的字节码回边循环。 fn call(&self, arguments: &[Value]) -> Result { assert!(arguments.is_empty()); Ok(Value::Bool(self.0.fetch_add(1, Ordering::SeqCst) < 3)) } } #[test] /// 条件分支从指令末尾计算相对距离,回边重进 tick 调用,最后返回 done。 fn relative_branches_and_back_edges_execute_a_loop() { // 独立 VM 隔离对象计数/全局绑定,清理断言只针对本场景。 let mut vm = Vm::new().unwrap(); // VM 外原子计数用于确认循环调用次数,不通过返回文本猜测回边是否执行。 let count = Arc::new(AtomicUsize::new(0)); // Tick 回调载荷持有 Rust Arc,返回直接 Bool,无托管捕获边。 let tick = vm.allocate(Tick(count.clone())).unwrap(); // 函数执行环境与名称表索引对应,字典内部边独立于测试外部根。 let globals = vm.new_dict().unwrap(); vm.dict_set(globals, text("tick"), Value::Object(tick)) .unwrap(); // 操作数和布局在构造时校验,execute 期间测试实际值流与引用行为。 let code = make_code( &mut vm, &[ op(Opcode::LoadGlobal, 0), simple(Opcode::PushNull), op(Opcode::Call, 0), op(Opcode::PopJumpIfFalse, 1), op(Opcode::JumpBackward, 5), op(Opcode::LoadConst, 0), simple(Opcode::ReturnValue), ], vec![text("done")], CodeLayout { names: vec!["tick".into()], ..CodeLayout::default() }, ); assert_eq!(vm.execute(code, globals).unwrap(), text("done")); assert_eq!(count.load(Ordering::SeqCst), 4); } #[test] /// 验证直接默认参数和嵌套显式参数的对象结果均新增一份拥有根,归还后恢复原计数。 fn nested_language_calls_bind_defaults_and_return_owned_argument_objects() { // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); // 单参数源码函数只读参数再返回,默认值与显式实参应走相同拥有结果协议。 let identity = make_code( &mut vm, &[op(Opcode::LoadFast, 0), simple(Opcode::ReturnValue)], vec![], CodeLayout { parameters: vec!["value".into()], locals: vec!["value".into()], ..CodeLayout::default() }, ); // 默认字符串先有测试根,函数默认值表另持内部边。 let default = vm.new_string("default").unwrap(); // 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。 let function = vm .new_function( "identity", identity, globals, vec![Value::Object(default)], vec![], ) .unwrap(); // 保存调用前计数,结果交付多一根,归还结果后必须恢复。 let before = vm.ref_count(default).unwrap(); // 函数把已有参数/默认值作为结果,身份不变但结果仍取得独立拥有根。 let returned = vm.call(function, &[]).unwrap(); assert_eq!(returned, Value::Object(default)); assert_eq!(vm.ref_count(default).unwrap(), before + 1); // 只归还本次调用结果根,不触碰默认表/常量/测试输入的所有权。 vm.release_value(returned).unwrap(); assert_eq!(vm.ref_count(default).unwrap(), before); vm.dict_set(globals, text("identity"), Value::Object(function)) .unwrap(); // 显式实参放进包装代码常量表,也有独立测试根。 let argument = vm.new_string("argument").unwrap(); // 包装指令按函数、Null、参数布局调用 identity,检验跨两个源码帧的引用转交。 let wrapper = make_code( &mut vm, &[ op(Opcode::LoadGlobal, 0), simple(Opcode::PushNull), op(Opcode::LoadConst, 0), op(Opcode::Call, 1), simple(Opcode::ReturnValue), ], vec![Value::Object(argument)], CodeLayout { names: vec!["identity".into()], ..CodeLayout::default() }, ); // 保存调用前计数,结果交付多一根,归还结果后必须恢复。 let before = vm.ref_count(argument).unwrap(); // 临时执行函数与嵌套帧清理后,结果对象仍须保持拥有。 let returned = vm.execute(wrapper, globals).unwrap(); assert_eq!(returned, Value::Object(argument)); assert_eq!(vm.ref_count(argument).unwrap(), before + 1); // 只归还本次调用结果根,不触碰默认表/常量/测试输入的所有权。 vm.release_value(returned).unwrap(); assert_eq!(vm.ref_count(argument).unwrap(), before); } #[test] /// 参数过少/过多及异堆参数均在帧提交前拒绝,函数根计数不得因失败增加。 fn argument_count_and_foreign_argument_failures_do_not_retain_inputs() { // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); // 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。 let code = make_code( &mut vm, &[op(Opcode::LoadFast, 0), simple(Opcode::ReturnValue)], vec![], CodeLayout { parameters: vec!["value".into()], locals: vec!["value".into()], ..CodeLayout::default() }, ); // 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。 let function = vm .new_function("identity", code, globals, vec![], vec![]) .unwrap(); assert!(matches!( vm.call(function, &[]), Err(VmError::ArgumentCount { required: 1, provided: 0, .. }) )); assert!(matches!( vm.call(function, &[Value::Nil, Value::Nil]), Err(VmError::ArgumentCount { maximum: 1, provided: 2, .. }) )); // 另一堆产生真正有效但归属不同的参数,不能当本堆对象保留。 let mut other = Vm::new().unwrap(); // 异堆参数在执行源码之前拒绝,不改变当前函数计数。 let foreign = other.new_object().unwrap(); // 保存调用前计数,结果交付多一根,归还结果后必须恢复。 let before = vm.ref_count(function).unwrap(); assert_eq!( vm.call(function, &[Value::Object(foreign)]), Err(VmError::ForeignHandle) ); assert_eq!(vm.ref_count(function).unwrap(), before); } #[test] /// BuildMap 返回根在代码/全局释放后独立保活结果字典和子对象,释放结果才清空图。 fn freshly_built_maps_and_their_children_survive_frame_cleanup() { // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); // 子字符串先由测试根和代码常量持有,最终转由结果字典边保活。 let child = vm.new_string("owned by result").unwrap(); // 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。 let code = make_code( &mut vm, &[ op(Opcode::LoadConst, 0), op(Opcode::LoadConst, 1), op(Opcode::BuildMap, 1), simple(Opcode::ReturnValue), ], vec![text("child"), Value::Object(child)], CodeLayout::default(), ); // 归还测试根,代码常量在实际执行前仍维持子对象存活。 vm.release(child).unwrap(); // 执行结果拥有根可跨越代码与全局生命周期。 let result = vm.execute(code, globals).unwrap(); // 移除代码常量边后,返回图必须自己保留所需对象;不是只靠旧帧保活。 vm.release(code).unwrap(); // 归还环境测试根,临时执行函数已清理,结果不能依赖其持续存在。 vm.release(globals).unwrap(); // 结果字典只有一份外部根,其 child 值是独立内部强边。 let Value::Object(dictionary) = result else { panic!("expected a map"); }; assert_eq!(vm.ref_count(dictionary).unwrap(), 1); assert_eq!( vm.dict_get(dictionary, &text("child")).unwrap(), Some(Value::Object(child)) ); assert_eq!(vm.str(child).unwrap(), "owned by result"); // 结果最后根归还,子对象的最后字典边随之释放,堆应归零。 vm.release(dictionary).unwrap(); assert_eq!(vm.object_count(), 0); } /// 原生辅助只借用返回首参数,空参数错误;对象结果拥有根由 VM 补充。 fn echo(arguments: &[Value]) -> Result { arguments .first() .cloned() .ok_or(VmError::InvalidArgument("expected an argument")) } #[test] /// 原生 echo 返回临时 BuildMap 实参,输入弹栈前必须先保留结果根。 fn native_return_of_a_temporary_argument_is_retained_before_inputs_are_popped() { // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); // 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。 let function = vm .allocate(EndorNativeFunctionObject::new("echo", echo)) .unwrap(); vm.dict_set(globals, text("echo"), Value::Object(function)) .unwrap(); // 调用前/后按场景归还测试函数根;全局字典可能仍持有函数内部边。 vm.release(function).unwrap(); // 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。 let code = make_code( &mut vm, &[ op(Opcode::LoadGlobal, 0), simple(Opcode::PushNull), op(Opcode::BuildMap, 0), op(Opcode::Call, 1), simple(Opcode::ReturnValue), ], vec![], CodeLayout { names: vec!["echo".into()], ..CodeLayout::default() }, ); // 执行结果拥有根可跨越代码与全局生命周期。 let result = vm.execute(code, globals).unwrap(); // 移除代码常量边后,返回图必须自己保留所需对象;不是只靠旧帧保活。 vm.release(code).unwrap(); // 归还环境测试根,临时执行函数已清理,结果不能依赖其持续存在。 vm.release(globals).unwrap(); assert_eq!(vm.object_count(), 1); vm.release_value(result).unwrap(); assert_eq!(vm.object_count(), 0); } /// 原生辅助恒定报错,使执行器在 Call 处回溯与清理临时参数。 fn fail(_arguments: &[Value]) -> Result { Err(VmError::InvalidArgument("host failure")) } #[test] /// 错误后对象数恢复调用前,回溯保留故障 Call 偏移及显式代码位置元数据。 fn native_failure_unwinds_temporary_roots_and_keeps_execution_locations() { // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); // 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。 let function = vm .allocate(EndorNativeFunctionObject::new("fail", fail)) .unwrap(); vm.dict_set(globals, text("fail"), Value::Object(function)) .unwrap(); // 附显式名称、文件和起始行,用故障回溯断言实际保存的元数据。 let code = EndorCode::from_instructions( &[ op(Opcode::LoadGlobal, 0), simple(Opcode::PushNull), op(Opcode::BuildMap, 0), op(Opcode::Call, 1), simple(Opcode::ReturnValue), ], vec![], CodeLayout { names: vec!["fail".into()], ..CodeLayout::default() }, ) .unwrap() .with_location("outer", "demo.endor", 12); // 将已验证代码分配为拥有根,临时执行函数之后另外持代码边。 let code = vm.new_code(code).unwrap(); // 记录代码/全局等场景根数,失败后不得残留临时字典、函数或帧根。 let before = vm.object_count(); assert_eq!( vm.execute(code, globals), Err(VmError::InvalidArgument("host failure")) ); assert_eq!(vm.object_count(), before); assert_eq!(vm.last_traceback()[0].function, "outer"); assert_eq!(vm.last_traceback()[0].instruction_offset, 3); assert_eq!(vm.last_traceback()[0].filename, "demo.endor"); assert_eq!(vm.last_traceback()[0].first_line, 12); } #[test] /// 递归源码调用由显式帧向量推进,触及深度硬上限后全部执行根恢复,环境环可显式回收。 fn recursion_uses_explicit_frames_and_unwinds_at_the_frame_limit() { // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); // 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。 let code = make_code( &mut vm, &[ op(Opcode::LoadGlobal, 0), simple(Opcode::PushNull), op(Opcode::Call, 0), simple(Opcode::ReturnValue), ], vec![], CodeLayout { names: vec!["recurse".into()], ..CodeLayout::default() }, ); // 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。 let function = vm .new_function("recurse", code, globals, vec![], vec![]) .unwrap(); vm.dict_set(globals, text("recurse"), Value::Object(function)) .unwrap(); // 递归深度会临时保留多份函数根,退出后应恢复精确初始计数。 let roots = vm.ref_count(function).unwrap(); assert_eq!( vm.call_with_limits( function, &[], ExecutionLimits { max_frames: 64, max_instructions: 1000 } ), Err(VmError::ExecutionLimit("frames")) ); assert_eq!(vm.last_traceback().len(), 64); assert_eq!(vm.ref_count(function).unwrap(), roots); // 调用前/后按场景归还测试函数根;全局字典可能仍持有函数内部边。 vm.release(function).unwrap(); // 移除代码常量边后,返回图必须自己保留所需对象;不是只靠旧帧保活。 vm.release(code).unwrap(); // 归还环境测试根,临时执行函数已清理,结果不能依赖其持续存在。 vm.release(globals).unwrap(); assert_eq!(vm.collect_cycles(), 3); } #[test] /// 栈高合法的无限回边仍须由硬指令预算终止,不能泄漏临时函数或帧。 fn instruction_limit_stops_a_valid_infinite_loop_without_leaking_frames() { // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); // 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。 let code = make_code( &mut vm, &[op(Opcode::JumpBackward, 1)], vec![], CodeLayout::default(), ); // 记录代码/全局等场景根数,失败后不得残留临时字典、函数或帧根。 let before = vm.object_count(); assert_eq!( vm.execute_with_limits( code, globals, ExecutionLimits { max_frames: 8, max_instructions: 3 } ), Err(VmError::ExecutionLimit("instructions")) ); assert_eq!(vm.object_count(), before); assert_eq!(vm.last_traceback().len(), 1); } #[test] /// 函数捕获创建时内置字典身份,全局同名键仍优先,改写 __builtins__ 不重绑定旧函数。 fn function_globals_shadow_builtins_and_capture_the_original_builtins_dictionary() { // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); // 原内置字典由函数创建时捕获,不因稍后 globals.__builtins__ 改变而替换身份。 let builtins = vm.new_dict().unwrap(); vm.dict_set(builtins, text("answer"), text("builtin")) .unwrap(); vm.dict_set(globals, text("__builtins__"), Value::Object(builtins)) .unwrap(); // 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。 let code = make_code( &mut vm, &[op(Opcode::LoadGlobal, 0), simple(Opcode::ReturnValue)], vec![], CodeLayout { names: vec!["answer".into()], ..CodeLayout::default() }, ); // 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。 let function = vm .new_function("read", code, globals, vec![], vec![]) .unwrap(); // 新内置字典仅更新 globals 字段,用于证明旧函数仍引用原内置空间。 let replacement = vm.new_dict().unwrap(); vm.dict_set(globals, text("__builtins__"), Value::Object(replacement)) .unwrap(); // 归还原内置测试根后仍应由函数 captured builtins 强边保活。 vm.release(builtins).unwrap(); assert_eq!(vm.call(function, &[]).unwrap(), text("builtin")); vm.dict_set(globals, text("answer"), text("global")) .unwrap(); assert_eq!(vm.call(function, &[]).unwrap(), text("global")); } #[test] /// 多个函数共享同一 Cell,Load/Store/DeleteDeref 对内容变化立即可见,clear 后读取报未绑定。 fn shared_free_variable_cells_are_read_written_and_deleted_by_bytecode() { // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); // 共享槽初值 first,read/write/clear 函数各自持有同一 Cell 强边。 let cell = vm.new_cell(Some(text("first"))).unwrap(); // 没有局部槽,唯一自由变量 Cell 位于 localsplus 索引零。 let read_code = make_code( &mut vm, &[op(Opcode::LoadDeref, 0), simple(Opcode::ReturnValue)], vec![], CodeLayout { free_variables: vec!["x".into()], ..CodeLayout::default() }, ); // 有一个普通参数槽,外部 Cell 追加在索引一,StoreDeref 不能误写局部槽。 let write_code = make_code( &mut vm, &[ op(Opcode::LoadFast, 0), op(Opcode::StoreDeref, 1), op(Opcode::LoadConst, 0), simple(Opcode::ReturnValue), ], vec![Value::Nil], CodeLayout { parameters: vec!["value".into()], locals: vec!["value".into()], free_variables: vec!["x".into()], ..CodeLayout::default() }, ); // DeleteDeref 清空 Cell 而非写 Nil,随后返回直接 Nil。 let clear_code = make_code( &mut vm, &[ op(Opcode::DeleteDeref, 0), op(Opcode::LoadConst, 0), simple(Opcode::ReturnValue), ], vec![Value::Nil], CodeLayout { free_variables: vec!["x".into()], ..CodeLayout::default() }, ); // 只读捕获值的函数,共享槽未复制成独立快照。 let read = vm .new_function("read", read_code, globals, vec![], vec![cell]) .unwrap(); // 把显式参数写到同一捕获 Cell,其他闭包应立即看到新值。 let write = vm .new_function("write", write_code, globals, vec![], vec![cell]) .unwrap(); // 删除同一捕获变量后 read 映射 EmptyCell 为具名 UnboundLocal。 let clear = vm .new_function("clear", clear_code, globals, vec![], vec![cell]) .unwrap(); assert_eq!(vm.call(read, &[]).unwrap(), text("first")); vm.call(write, &[text("updated")]).unwrap(); assert_eq!(vm.call(read, &[]).unwrap(), text("updated")); vm.call(clear, &[]).unwrap(); assert_eq!(vm.call(read, &[]), Err(VmError::UnboundLocal("x".into()))); } #[test] /// 参数提升为 Cell 后通过 LoadClosure 返回,原帧与参数测试根释放仍由 Cell 边保活内容。 fn captured_argument_cells_keep_their_contents_after_the_frame_returns() { // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); // 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。 let code = make_code( &mut vm, &[op(Opcode::LoadClosure, 0), simple(Opcode::ReturnValue)], vec![], CodeLayout { parameters: vec!["x".into()], locals: vec!["x".into()], cell_variables: vec!["x".into()], ..CodeLayout::default() }, ); // 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。 let function = vm .new_function("capture", code, globals, vec![], vec![]) .unwrap(); // 参数对象有测试根,帧初始化提升 Cell 时另建立内容强边。 let value = vm.new_string("captured").unwrap(); // 函数把已有参数/默认值作为结果,身份不变但结果仍取得独立拥有根。 let returned = vm.call(function, &[Value::Object(value)]).unwrap(); // 归还原参数测试根,返回的 Cell 内容边仍须保活它。 vm.release(value).unwrap(); // LoadClosure 返回的是 Cell 本身,不是 Cell 当前内容。 let Value::Object(cell) = returned else { panic!("expected cell"); }; assert_eq!(vm.cell_get(cell).unwrap(), Value::Object(value)); assert_eq!(vm.str(value).unwrap(), "captured"); // 最后 Cell 结果根归还后其内容边释放,原字符串身份应立即失效。 vm.release(cell).unwrap(); assert!(matches!(vm.object(value), Err(VmError::InvalidHandle))); } #[test] /// StoreAttr/LoadAttr 与 Copy/Swap/PopTop 组合后仅保留宿主、属性和结果三份根,不残留复制栈根。 fn attribute_operations_and_stack_copies_transfer_roots_without_leaks() { // 独立 VM 隔离堆计数和全局/回溯状态。 let mut vm = Vm::new().unwrap(); // 测试执行环境本身有根,函数可能另持全局边形成待收集环。 let globals = vm.new_dict().unwrap(); // 手工已验证指令,按下面布局解释局部槽和 names 索引。 let code = make_code( &mut vm, &[ op(Opcode::LoadFast, 1), op(Opcode::LoadFast, 0), op(Opcode::StoreAttr, 0), op(Opcode::LoadFast, 0), op(Opcode::LoadAttr, 0), op(Opcode::Copy, 1), op(Opcode::Swap, 2), simple(Opcode::PopTop), simple(Opcode::ReturnValue), ], vec![], CodeLayout { parameters: vec!["record".into(), "value".into()], locals: vec!["record".into(), "value".into()], names: vec!["x".into()], ..CodeLayout::default() }, ); // 源码/原生函数有一份测试根,调用时另行保留帧/协议根。 let function = vm .new_function("assign", code, globals, vec![], vec![]) .unwrap(); // 属性写入目标拥有测试根,字段还会独立持有 value 边。 let record = vm.new_object().unwrap(); // 原值测试根保留到最后,调用返回和属性边应各增加一份持有。 let value = vm.new_string("value").unwrap(); // 返回对象必须独立拥有,栈 Copy 产生的额外根应已被 PopTop 清理。 let returned = vm .call(function, &[Value::Object(record), Value::Object(value)]) .unwrap(); assert_eq!(vm.ref_count(value).unwrap(), 3); // host, attribute, return value // 先归还调用结果根,再释放 record 去掉字段边,value 最终只剩测试根。 vm.release_value(returned).unwrap(); // 属性 owner 最后根消失,其字段拥有边随对象释放。 vm.release(record).unwrap(); assert_eq!(vm.ref_count(value).unwrap(), 1); } #[test] /// 文本比较经 ToBool/UnaryNot 后控制前向分支,检验跳转基点与运行时真值。 fn comparison_truth_and_forward_conditionals_use_runtime_values() { // 独立 VM 隔离堆计数和全局/回溯状态。 let mut vm = Vm::new().unwrap(); // 测试执行环境本身有根,函数可能另持全局边形成待收集环。 let globals = vm.new_dict().unwrap(); // 手工已验证指令,按下面布局解释局部槽和 names 索引。 let code = make_code( &mut vm, &[ op(Opcode::LoadConst, 0), op(Opcode::LoadConst, 1), Instruction::compare(CompareOp::Lt), simple(Opcode::ToBool), simple(Opcode::UnaryNot), op(Opcode::PopJumpIfTrue, 2), op(Opcode::LoadConst, 2), simple(Opcode::ReturnValue), op(Opcode::LoadConst, 3), simple(Opcode::ReturnValue), ], vec![text("a"), text("b"), text("less"), text("other")], CodeLayout::default(), ); assert_eq!(vm.execute(code, globals).unwrap(), text("less")); } #[test] /// 缺全局、删除局部后读取和 Null 逃逸分别报错,随后成功执行应清掉旧回溯。 fn unbound_names_deleted_locals_and_invalid_null_values_fail_cleanly() { // 独立 VM 隔离堆计数和全局/回溯状态。 let mut vm = Vm::new().unwrap(); // 测试执行环境本身有根,函数可能另持全局边形成待收集环。 let globals = vm.new_dict().unwrap(); // 名称表合法但环境无该键,属于执行期 NameNotFound 而非索引验证错误。 let missing = make_code( &mut vm, &[op(Opcode::LoadGlobal, 0), simple(Opcode::ReturnValue)], vec![], CodeLayout { names: vec!["missing".into()], ..CodeLayout::default() }, ); assert_eq!( vm.execute(missing, globals), Err(VmError::NameNotFound("missing".into())) ); // 先成功赋值再 DeleteFast,最后读取检验 None 未绑定状态。 let deleted = make_code( &mut vm, &[ op(Opcode::LoadConst, 0), op(Opcode::StoreFast, 0), op(Opcode::DeleteFast, 0), op(Opcode::LoadFast, 0), simple(Opcode::ReturnValue), ], vec![Value::Nil], CodeLayout { locals: vec!["x".into()], ..CodeLayout::default() }, ); assert_eq!( vm.execute(deleted, globals), Err(VmError::UnboundLocal("x".into())) ); // PushNull 满足抽象栈高度但不是语言 Value,ReturnValue 必须在运行时拒绝。 let null = make_code( &mut vm, &[simple(Opcode::PushNull), simple(Opcode::ReturnValue)], vec![], CodeLayout::default(), ); assert!(matches!( vm.execute(null, globals), Err(VmError::InvalidBytecode { .. }) )); // 错误后执行简单成功代码,验证 last_traceback 不残留前一次故障。 let success = return_constant(&mut vm, Value::Nil); assert_eq!(vm.execute(success, globals).unwrap(), Value::Nil); assert!(vm.last_traceback().is_empty()); } #[test] /// 不可哈希数值键使外层建图失败,半成品和作为值的临时空图都须被清理。 fn failed_map_construction_cleans_both_partial_map_and_temporary_input() { // 独立 VM 隔离堆计数和全局/回溯状态。 let mut vm = Vm::new().unwrap(); // 测试执行环境本身有根,函数可能另持全局边形成待收集环。 let globals = vm.new_dict().unwrap(); // 手工已验证指令,按下面布局解释局部槽和 names 索引。 let code = make_code( &mut vm, &[ op(Opcode::LoadConst, 0), op(Opcode::BuildMap, 0), op(Opcode::BuildMap, 1), simple(Opcode::ReturnValue), ], vec![Value::Number(1_i64.into())], CodeLayout::default(), ); // 失败前存活对象基线,执行临时函数和两层临时图不得泄漏。 let before = vm.object_count(); assert_eq!( vm.execute(code, globals), Err(VmError::UnsupportedOperation("numeric hash")) ); assert_eq!(vm.object_count(), before); } #[test] /// 调用协议位置的 Value::Nil 是真实隐式参数,不得像专用 Null 标记那样被忽略。 fn implicit_receiver_slot_is_distinct_from_the_null_call_marker() { // 独立 VM 隔离堆计数和全局/回溯状态。 let mut vm = Vm::new().unwrap(); // 测试执行环境本身有根,函数可能另持全局边形成待收集环。 let globals = vm.new_dict().unwrap(); // 源码/原生函数有一份测试根,调用时另行保留帧/协议根。 let function = vm .allocate(EndorNativeFunctionObject::new("echo", echo)) .unwrap(); // 手工已验证指令,按下面布局解释局部槽和 names 索引。 let code = make_code( &mut vm, &[ op(Opcode::LoadConst, 0), op(Opcode::LoadConst, 1), op(Opcode::Call, 0), simple(Opcode::ReturnValue), ], vec![Value::Object(function), Value::Nil], CodeLayout::default(), ); // 此处 Nil 会作为真实隐式参数传入 echo,只有 StackEntry::Null 才省略协议位置。 assert_eq!(vm.execute(code, globals).unwrap(), Value::Nil); } #[test] /// 验证默认值/闭包计数打包编码和边界值;构造校验要求完整输入栈高,但不验证运行时载荷种类。 fn function_creation_operands_round_trip_and_require_all_inputs() { assert_eq!( Instruction::encode_all(&[Instruction::make_function(1, 1)]).unwrap(), vec![255, 1, 255, 0, 28, 1] ); assert_eq!( Instruction::encode_all(&[Instruction::make_function(u16::MAX, u16::MAX)]).unwrap(), vec![255, 255, 255, 255, 255, 255, 28, 255] ); // 这里只验证 MakeFunction 逻辑输入数量/编码,常量 Nil 占位并不实际执行构造。 let code = EndorCode::from_instructions( &[ op(Opcode::LoadConst, 0), op(Opcode::LoadConst, 0), op(Opcode::LoadConst, 0), Instruction::make_function(1, 1), simple(Opcode::ReturnValue), ], vec![Value::Nil], CodeLayout::default(), ) .unwrap(); assert_eq!( code.instructions()[3].instruction, Instruction::make_function(1, 1) ); assert_eq!(code.instructions()[3].next_offset, 6); assert_eq!(code.stack_size(), 3); // 不同计数组合都在空栈上缺少代码对象项,必须构造阶段报栈下溢。 for (defaults, closure) in [(0, 0), (1, 0), (0, 1), (1, 1), (u16::MAX, u16::MAX)] { assert!(matches!( EndorCode::from_instructions( &[ Instruction::make_function(defaults, closure), simple(Opcode::ReturnValue) ], vec![], CodeLayout::default(), ), Err(VmError::InvalidBytecode { .. }) )); } // 只有一个常量不能满足含默认值或捕获槽的函数构造,缺输入仍须静态拒绝。 for creation in [ Instruction::make_function(1, 0), Instruction::make_function(0, 1), ] { assert!( EndorCode::from_instructions( &[ op(Opcode::LoadConst, 0), creation, simple(Opcode::ReturnValue) ], vec![Value::Nil], CodeLayout::default(), ) .is_err() ); } } #[test] /// MakeFunction 在每次模块执行创建新函数身份,代码可共享,默认值/显式实参都能调用。 fn bytecode_defines_global_functions_and_calls_them_with_defaults() { // 独立 VM 隔离堆计数和全局/回溯状态。 let mut vm = Vm::new().unwrap(); // 测试执行环境本身有根,函数可能另持全局边形成待收集环。 let globals = vm.new_dict().unwrap(); // 内部 identity 的参数读取直接返回,代码诊断位置给新函数命名。 let inner = EndorCode::from_instructions( &[op(Opcode::LoadFast, 0), simple(Opcode::ReturnValue)], vec![], CodeLayout { parameters: vec!["value".into()], locals: vec!["value".into()], ..CodeLayout::default() }, ) .unwrap() .with_location("identity", "functions.endor", 2); // 代码常量由模块/工厂保留,归还测试根后仍用于后续 MakeFunction。 let inner = vm.new_code(inner).unwrap(); // 按默认值、代码顺序压栈创建函数,存全局后再 Call 零显式参数走默认值。 let module = make_code( &mut vm, &[ op(Opcode::LoadConst, 0), op(Opcode::LoadConst, 1), Instruction::make_function(1, 0), op(Opcode::StoreGlobal, 0), op(Opcode::LoadGlobal, 0), simple(Opcode::PushNull), op(Opcode::Call, 0), simple(Opcode::ReturnValue), ], vec![text("default"), Value::Object(inner)], CodeLayout { names: vec!["identity".into()], ..CodeLayout::default() }, ); // 归还内层代码测试根;外层代码常量边接管保活,不能仅靠裸句柄继续使用。 vm.release(inner).unwrap(); assert_eq!(vm.execute(module, globals).unwrap(), text("default")); // 全局读取返回借用函数身份,要跨后续重定义存活必须显式 retain。 let Some(Value::Object(first)) = vm.dict_get(globals, &text("identity")).unwrap() else { panic!("expected a function"); }; // 保留旧函数独立根,模块再次执行替换全局后还可比较它与新函数身份。 vm.retain(first).unwrap(); // 借用函数元数据确认 name/code/globals 来自创建上下文。 let data = vm .payload::(first, "function") .unwrap(); assert_eq!(data.name(), "identity"); assert_eq!(data.code(), inner); assert_eq!(data.globals(), globals); assert_eq!( vm.call(first, &[text("explicit")]).unwrap(), text("explicit") ); assert_eq!(vm.execute(module, globals).unwrap(), text("default")); // 再次执行得到不同函数对象,但两者仍共享同一个代码对象。 let Some(Value::Object(second)) = vm.dict_get(globals, &text("identity")).unwrap() else { panic!("expected a second function"); }; assert_ne!(first, second); assert_eq!( vm.payload::(second, "function") .unwrap() .code(), inner ); // 归还为跨重定义而取得的旧函数根,不能把借用全局结果当作拥有根。 vm.release(first).unwrap(); // 模块代码常量边释放,仍由全局函数持有内部代码。 vm.release(module).unwrap(); // 归还环境测试根,若函数与环境互相持有则需显式环回收。 vm.release(globals).unwrap(); // 全局字典持有函数、函数持有全局和代码,失去外部根后形成三个对象的可回收图。 assert_eq!(vm.collect_cycles(), 3); assert_eq!(vm.object_count(), 0); } #[test] /// 工厂创建临时默认字典,函数返回后默认值仍由函数边保活,每次调用返回同一默认对象的独立根。 fn bytecode_function_defaults_keep_temporary_objects_alive_after_factory_returns() { // 独立 VM 隔离堆计数和全局/回溯状态。 let mut vm = Vm::new().unwrap(); // 测试执行环境本身有根,函数可能另持全局边形成待收集环。 let globals = vm.new_dict().unwrap(); // 内部函数有一个必填与两个默认参数,返回中间 defaulted 槽。 let inner = make_code( &mut vm, &[op(Opcode::LoadFast, 1), simple(Opcode::ReturnValue)], vec![], CodeLayout { parameters: vec!["required".into(), "defaulted".into(), "tag".into()], locals: vec!["required".into(), "defaulted".into(), "tag".into()], ..CodeLayout::default() }, ); // 先在栈上新建默认空图,再压 tag 和代码,MakeFunction 把前两项作为默认值。 let factory = make_code( &mut vm, &[ op(Opcode::BuildMap, 0), op(Opcode::LoadConst, 1), op(Opcode::LoadConst, 0), Instruction::make_function(2, 0), simple(Opcode::ReturnValue), ], vec![Value::Object(inner), text("tag")], CodeLayout::default(), ); // 归还内层代码测试根;外层代码常量边接管保活,不能仅靠裸句柄继续使用。 vm.release(inner).unwrap(); // 工厂结果函数拥有一根,默认字典由其 defaults 强边持有。 let Value::Object(function) = vm.execute(factory, globals).unwrap() else { panic!("expected a function"); }; // 工厂帧和代码释放后,结果函数必须独立保留代码/环境/默认图。 vm.release(factory).unwrap(); // 归还环境测试根,若函数与环境互相持有则需显式环回收。 vm.release(globals).unwrap(); assert!(matches!( vm.call(function, &[]), Err(VmError::ArgumentCount { required: 1, .. }) )); // 第一次默认调用返回默认字典对象,需独立归还调用结果根。 let first = vm.call(function, &[Value::Nil]).unwrap(); // 第二次返回同一默认对象,默认表达式不应每次重新求值创建新图。 let second = vm.call(function, &[Value::Nil]).unwrap(); assert_eq!(first, second); // Defaults are evaluated once per creation. // 默认图此时有函数默认边加两份结果根,共三份强引用。 let Value::Object(default) = first else { panic!("expected a default map"); }; assert_eq!(vm.ref_count(default).unwrap(), 3); // Function and two results. // 释放函数与默认值内部边,两个结果根仍使默认图存活。 vm.release(function).unwrap(); // 仅归还第一调用结果根,第二结果仍独立拥有一根。 vm.release_value(first).unwrap(); assert_eq!(vm.ref_count(default).unwrap(), 1); // 最后结果根归还,默认图及其余无根对象应全部释放。 vm.release_value(second).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] fn bytecode_nested_functions_share_cells_and_bind_defaults_after_outer_returns() { let mut vm = Vm::new().unwrap(); let globals = vm.new_dict().unwrap(); let read = make_code( &mut vm, &[op(Opcode::LoadDeref, 0), simple(Opcode::ReturnValue)], vec![], CodeLayout { free_variables: vec!["x".into(), "sentinel".into()], ..CodeLayout::default() }, ); let write = make_code( &mut vm, &[ op(Opcode::LoadFast, 0), op(Opcode::StoreDeref, 1), op(Opcode::LoadDeref, 1), simple(Opcode::ReturnValue), ], vec![], CodeLayout { parameters: vec!["value".into()], locals: vec!["value".into()], free_variables: vec!["x".into(), "sentinel".into()], ..CodeLayout::default() }, ); let outer = make_code( &mut vm, &[ op(Opcode::LoadConst, 0), op(Opcode::LoadClosure, 0), op(Opcode::LoadClosure, 1), op(Opcode::LoadConst, 1), Instruction::make_function(0, 2), op(Opcode::LoadConst, 2), op(Opcode::LoadConst, 3), op(Opcode::LoadClosure, 0), op(Opcode::LoadClosure, 1), op(Opcode::LoadConst, 4), Instruction::make_function(1, 2), op(Opcode::BuildMap, 2), simple(Opcode::ReturnValue), ], vec![ text("read"), Value::Object(read), text("write"), text("default"), Value::Object(write), ], CodeLayout { parameters: vec!["x".into(), "sentinel".into()], locals: vec!["x".into(), "sentinel".into()], cell_variables: vec!["x".into(), "sentinel".into()], ..CodeLayout::default() }, ); let outer_function = vm .new_function("outer", outer, globals, vec![], vec![]) .unwrap(); let Value::Object(pair) = vm .call(outer_function, &[text("first"), text("sentinel")]) .unwrap() else { panic!("expected a function pair"); }; for reference in [read, write, outer, outer_function, globals] { vm.release(reference).unwrap(); } let Some(Value::Object(read)) = vm.dict_get(pair, &text("read")).unwrap() else { panic!("expected read"); }; let Some(Value::Object(write)) = vm.dict_get(pair, &text("write")).unwrap() else { panic!("expected write"); }; assert_eq!( vm.payload::(read, "function") .unwrap() .closure(), vm.payload::(write, "function") .unwrap() .closure(), ); assert_eq!(vm.collect_cycles(), 0); assert_eq!(vm.call(read, &[]).unwrap(), text("first")); assert_eq!(vm.call(write, &[text("updated")]).unwrap(), text("updated")); assert_eq!(vm.call(read, &[]).unwrap(), text("updated")); assert_eq!(vm.call(write, &[]).unwrap(), text("default")); assert_eq!(vm.call(read, &[]).unwrap(), text("default")); vm.release(pair).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] fn bytecode_function_creation_rejects_bad_inputs_without_leaking_roots() { let mut vm = Vm::new().unwrap(); let globals = vm.new_dict().unwrap(); let plain = return_constant(&mut vm, Value::Nil); let captured = make_code( &mut vm, &[op(Opcode::LoadDeref, 0), simple(Opcode::ReturnValue)], vec![], CodeLayout { free_variables: vec!["x".into()], ..CodeLayout::default() }, ); let cell = vm.new_cell(None).unwrap(); let mismatch = VmError::InvalidArgument("function defaults or closure do not match code"); let cases = [ ( vec![op(Opcode::LoadConst, 0), Instruction::make_function(0, 0)], vec![Value::Nil], VmError::ObjectKindMismatch("code"), ), ( vec![op(Opcode::BuildMap, 0), Instruction::make_function(0, 0)], vec![], VmError::ObjectKindMismatch("code"), ), ( vec![simple(Opcode::PushNull), Instruction::make_function(0, 0)], vec![], invalid_null(), ), ( vec![ op(Opcode::BuildMap, 0), op(Opcode::LoadConst, 0), Instruction::make_function(1, 0), ], vec![Value::Object(plain)], mismatch.clone(), ), ( vec![op(Opcode::LoadConst, 0), Instruction::make_function(0, 0)], vec![Value::Object(captured)], mismatch.clone(), ), ( vec![ op(Opcode::LoadConst, 0), op(Opcode::LoadConst, 1), Instruction::make_function(0, 1), ], vec![Value::Object(cell), Value::Object(plain)], mismatch, ), ( vec![ op(Opcode::LoadConst, 0), op(Opcode::LoadConst, 1), Instruction::make_function(0, 1), ], vec![Value::Nil, Value::Object(captured)], VmError::ObjectKindMismatch("cell"), ), ( vec![ op(Opcode::BuildMap, 0), op(Opcode::LoadConst, 0), Instruction::make_function(0, 1), ], vec![Value::Object(captured)], VmError::ObjectKindMismatch("cell"), ), ( vec![ simple(Opcode::PushNull), op(Opcode::LoadConst, 0), Instruction::make_function(0, 1), ], vec![Value::Object(captured)], invalid_null(), ), ( vec![ simple(Opcode::PushNull), op(Opcode::LoadConst, 0), Instruction::make_function(1, 0), ], vec![Value::Object(plain)], invalid_null(), ), ]; for (mut instructions, constants, expected) in cases { instructions.push(simple(Opcode::ReturnValue)); let code = make_code(&mut vm, &instructions, constants, CodeLayout::default()); let count = vm.object_count(); let roots = [plain, captured, cell, globals].map(|reference| vm.ref_count(reference).unwrap()); let error = vm.execute(code, globals).unwrap_err(); if matches!(expected, VmError::InvalidBytecode { .. }) { assert!(matches!( error, VmError::InvalidBytecode { reason: "NULL call marker used as a value", .. } )); } else { assert_eq!(error, expected); } assert_eq!(vm.object_count(), count); assert_eq!( [plain, captured, cell, globals].map(|reference| vm.ref_count(reference).unwrap()), roots ); vm.release(code).unwrap(); } for reference in [plain, captured, cell, globals] { vm.release(reference).unwrap(); } assert_eq!(vm.object_count(), 0); } fn invalid_null() -> VmError { VmError::InvalidBytecode { offset: 0, reason: "NULL call marker used as a value", } } #[test] fn bytecode_functions_capture_builtins_at_creation_and_share_live_globals() { let mut vm = Vm::new().unwrap(); let globals = vm.new_dict().unwrap(); let original = vm.new_dict().unwrap(); let replacement = vm.new_dict().unwrap(); vm.dict_set(original, text("answer"), text("original")) .unwrap(); vm.dict_set(replacement, text("answer"), text("replacement")) .unwrap(); vm.dict_set(globals, text("__builtins__"), Value::Object(original)) .unwrap(); let inner = make_code( &mut vm, &[op(Opcode::LoadGlobal, 0), simple(Opcode::ReturnValue)], vec![], CodeLayout { names: vec!["answer".into()], ..CodeLayout::default() }, ); let factory = make_code( &mut vm, &[ op(Opcode::LoadConst, 0), Instruction::make_function(0, 0), simple(Opcode::ReturnValue), ], vec![Value::Object(inner)], CodeLayout::default(), ); let Value::Object(first) = vm.execute(factory, globals).unwrap() else { panic!("expected first function"); }; vm.dict_set(globals, text("__builtins__"), Value::Object(replacement)) .unwrap(); let Value::Object(second) = vm.execute(factory, globals).unwrap() else { panic!("expected second function"); }; for reference in [original, replacement, factory, inner, globals] { vm.release(reference).unwrap(); } assert_eq!(vm.call(first, &[]).unwrap(), text("original")); assert_eq!(vm.call(second, &[]).unwrap(), text("replacement")); vm.dict_set(globals, text("answer"), text("global")) .unwrap(); assert_eq!(vm.call(first, &[]).unwrap(), text("global")); assert_eq!(vm.call(second, &[]).unwrap(), text("global")); vm.release(first).unwrap(); vm.release(second).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] fn later_execution_failure_releases_a_new_function_and_its_temporary_default() { let mut vm = Vm::new().unwrap(); let globals = vm.new_dict().unwrap(); let inner = make_code( &mut vm, &[op(Opcode::LoadFast, 0), simple(Opcode::ReturnValue)], vec![], CodeLayout { parameters: vec!["value".into()], locals: vec!["value".into()], ..CodeLayout::default() }, ); let fail = vm .allocate(EndorNativeFunctionObject::new("fail", fail)) .unwrap(); let factory = make_code( &mut vm, &[ op(Opcode::LoadConst, 0), simple(Opcode::PushNull), op(Opcode::BuildMap, 0), op(Opcode::LoadConst, 1), Instruction::make_function(1, 0), op(Opcode::Call, 1), simple(Opcode::ReturnValue), ], vec![Value::Object(fail), Value::Object(inner)], CodeLayout::default(), ); vm.release(fail).unwrap(); vm.release(inner).unwrap(); let count = vm.object_count(); assert_eq!( vm.execute(factory, globals), Err(VmError::InvalidArgument("host failure")) ); assert_eq!(vm.object_count(), count); vm.release(factory).unwrap(); vm.release(globals).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] fn suspended_read_only_call_keeps_roots_and_releases_them_on_completion() { use endor_core::ExecutionProgress; let mut vm = Vm::new().unwrap(); let shared = vm.new_object().unwrap(); vm.set_attr(shared, "value", Value::Number(7_i64.into())) .unwrap(); let globals = vm.new_dict().unwrap(); let mut instructions = vec![simple(Opcode::Nop); 16]; instructions.extend([op(Opcode::LoadConst, 0), simple(Opcode::ReturnValue)]); let code = make_code( &mut vm, &instructions, vec![Value::Object(shared)], CodeLayout::default(), ); let function = vm .new_function("sliced", code, globals, vec![], vec![]) .unwrap(); let mut progress = vm .start_read_only_call( function, &[], ExecutionLimits { max_frames: 8, max_instructions: 100, }, 1, ) .unwrap(); assert!(matches!( progress, ExecutionProgress::Pending { instructions: 1 } )); assert!( vm.set_attr(shared, "value", Value::Number(8_i64.into())) .is_err() ); assert!(vm.call(function, &[]).is_err()); vm.release(function).unwrap(); vm.release(code).unwrap(); vm.release(globals).unwrap(); vm.release(shared).unwrap(); vm.collect_cycles(); loop { match progress { ExecutionProgress::Pending { .. } => progress = vm.resume_read_only_call(2).unwrap(), ExecutionProgress::Complete { value, instructions, .. } => { assert_eq!(instructions, 18); assert_eq!(value, Value::Object(shared)); assert_eq!( vm.get_attr(shared, "value").unwrap(), Value::Number(7_i64.into()) ); vm.release_value(value).unwrap(); break; } } } assert!(!vm.has_suspended_call()); assert_eq!(vm.object_count(), 0); } #[test] fn suspended_call_cancellation_and_total_budget_restore_read_only_state() { let mut vm = Vm::new().unwrap(); let shared = vm.new_object().unwrap(); let globals = vm.new_dict().unwrap(); let mut instructions = vec![simple(Opcode::Nop); 8]; instructions.extend([op(Opcode::LoadConst, 0), simple(Opcode::ReturnValue)]); let code = make_code( &mut vm, &instructions, vec![Value::Object(shared)], CodeLayout::default(), ); let function = vm .new_function("budget", code, globals, vec![], vec![]) .unwrap(); let roots = vm.ref_count(function).unwrap(); vm.start_read_only_call( function, &[], ExecutionLimits { max_frames: 8, max_instructions: 3, }, 1, ) .unwrap(); assert!(vm.resume_read_only_call(0).is_err()); assert!(vm.has_suspended_call()); vm.resume_read_only_call(1).unwrap(); assert_eq!( vm.resume_read_only_call(1).unwrap_err(), VmError::ExecutionLimit("instructions") ); assert!(!vm.has_suspended_call()); assert_eq!(vm.ref_count(function).unwrap(), roots); vm.set_attr(shared, "after", Value::Bool(true)).unwrap(); vm.start_read_only_call(function, &[], ExecutionLimits::default(), 1) .unwrap(); assert!(vm.cancel_read_only_call().unwrap()); assert!(!vm.cancel_read_only_call().unwrap()); assert_eq!(vm.ref_count(function).unwrap(), roots); vm.set_attr(shared, "after", Value::Bool(false)).unwrap(); vm.release(function).unwrap(); vm.release(code).unwrap(); vm.release(globals).unwrap(); vm.release(shared).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] fn managed_coroutines_interleave_without_replaying_writes() { use endor_core::CoroutineProgress; let mut vm = Vm::new().unwrap(); let globals = vm.new_dict().unwrap(); let code = make_code( &mut vm, &[ op(Opcode::LoadConst, 0), op(Opcode::StoreGlobal, 0), op(Opcode::LoadConst, 1), op(Opcode::StoreGlobal, 1), op(Opcode::LoadConst, 2), simple(Opcode::ReturnValue), ], vec![text("first"), text("second"), Value::Nil], CodeLayout { names: vec!["x".into(), "y".into()], ..CodeLayout::default() }, ); let function = vm .new_function("work", code, globals, vec![], vec![]) .unwrap(); let first = vm .new_coroutine(function, &[], ExecutionLimits::default()) .unwrap(); let second = vm .new_coroutine(function, &[], ExecutionLimits::default()) .unwrap(); assert!(matches!( vm.resume_coroutine(first, 2).unwrap(), CoroutineProgress::Pending { instructions: 2 } )); assert!(matches!( vm.resume_coroutine(second, 2).unwrap(), CoroutineProgress::Pending { instructions: 2 } )); vm.dict_set(globals, text("x"), text("interleaved")) .unwrap(); assert!(matches!( vm.resume_coroutine(first, 16).unwrap(), CoroutineProgress::Complete { value: Value::Nil, instructions: 6 } )); assert_eq!( vm.dict_get(globals, &text("x")).unwrap(), Some(text("interleaved")) ); assert_eq!( vm.dict_get(globals, &text("y")).unwrap(), Some(text("second")) ); assert!(vm.cancel_coroutine(second).unwrap()); assert!(!vm.cancel_coroutine(second).unwrap()); assert!(vm.resume_coroutine(first, 1).is_err()); for object in [first, second, function, code, globals] { vm.release(object).unwrap(); } assert_eq!(vm.object_count(), 0); } #[test] fn parked_coroutine_cycles_are_collectable_without_cancellation() { let mut vm = Vm::new().unwrap(); let globals = vm.new_dict().unwrap(); let code = return_constant(&mut vm, Value::Nil); let function = vm .new_function("cycle", code, globals, vec![], vec![]) .unwrap(); let coroutine = vm .new_coroutine(function, &[], ExecutionLimits::default()) .unwrap(); vm.dict_set(globals, text("task"), Value::Object(coroutine)) .unwrap(); for object in [coroutine, function, code, globals] { vm.release(object).unwrap(); } assert!(vm.collect_cycles() > 0); assert_eq!(vm.object_count(), 0); } #[test] fn managed_coroutine_hard_budget_accumulates_and_error_releases_frames() { use endor_core::CoroutineProgress; let mut vm = Vm::new().unwrap(); let globals = vm.new_dict().unwrap(); // Reaching ReturnValue at the limit succeeds; keep a third instruction // beyond the hard limit to exercise failure before the next Pending yield. let code = vm .new_code( EndorCode::from_instructions( &[ Instruction::simple(Opcode::Nop), Instruction::new(Opcode::LoadConst, 0), Instruction::simple(Opcode::ReturnValue), ], vec![Value::Nil], CodeLayout::default(), ) .unwrap(), ) .unwrap(); let function = vm .new_function("limited", code, globals, vec![], vec![]) .unwrap(); let coroutine = vm .new_coroutine( function, &[], ExecutionLimits { max_frames: 4, // The second resume reaches the cumulative limit before // the third instruction, without replaying the first. max_instructions: 2, }, ) .unwrap(); assert!(matches!( vm.resume_coroutine(coroutine, 1).unwrap(), CoroutineProgress::Pending { instructions: 1 } )); assert!(matches!( vm.resume_coroutine(coroutine, 1), Err(VmError::ExecutionLimit("instructions")) )); assert!(!vm.last_traceback().is_empty()); assert!(!vm.cancel_coroutine(coroutine).unwrap()); for object in [coroutine, function, code, globals] { vm.release(object).unwrap(); } assert_eq!(vm.object_count(), 0); } #[derive(Debug)] struct AwaitGate { ready: Arc, value: Value, } impl ObjectPayload for AwaitGate { fn as_any(&self) -> &dyn Any { self } fn kind_name(&self) -> &'static str { "await-gate" } fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { if let Value::Object(reference) = self.value { visit(reference); } } fn poll_await(&self) -> Result { Ok(if self.ready.load(Ordering::Acquire) { endor_core::AwaitPoll::Ready(self.value.clone()) } else { endor_core::AwaitPoll::Pending }) } } #[test] fn await_keeps_operand_and_owned_object_result_without_replaying_prefix() { use endor_core::CoroutineProgress; let mut vm = Vm::new().unwrap(); let ready = Arc::new(std::sync::atomic::AtomicBool::new(false)); let value = vm.new_object().unwrap(); vm.set_attr(value, "marker", text("kept")).unwrap(); let gate = vm .allocate(AwaitGate { ready: ready.clone(), value: Value::Object(value), }) .unwrap(); let globals = vm.new_dict().unwrap(); let code = make_code( &mut vm, &[ op(Opcode::LoadConst, 0), op(Opcode::StoreGlobal, 0), op(Opcode::LoadConst, 1), simple(Opcode::Await), simple(Opcode::ReturnValue), ], vec![text("prefix"), Value::Object(gate)], CodeLayout { names: vec!["step".into()], ..CodeLayout::default() }, ); let function = vm .new_function("awaited", code, globals, vec![], vec![]) .unwrap(); let coroutine = vm .new_coroutine(function, &[], ExecutionLimits::default()) .unwrap(); assert!( matches!(vm.resume_coroutine(coroutine, 64).unwrap(), CoroutineProgress::Waiting { awaitable, .. } if awaitable == gate) ); vm.release(value).unwrap(); vm.release(gate).unwrap(); vm.dict_set(globals, text("step"), text("interleaved")) .unwrap(); vm.collect_cycles(); ready.store(true, Ordering::Release); let CoroutineProgress::Complete { value: result, .. } = vm.resume_coroutine(coroutine, 64).unwrap() else { panic!("ready await must complete") }; assert_eq!( vm.dict_get(globals, &text("step")).unwrap(), Some(text("interleaved")) ); for object in [coroutine, function, code, globals] { vm.release(object).unwrap(); } let Value::Object(reference) = result else { panic!("owned object result") }; assert_eq!(vm.get_attr(reference, "marker").unwrap(), text("kept")); vm.release_value(result).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] fn named_reads_ignore_unrelated_globals_but_track_missing_shadow_and_payload_kind() { let mut vm = Vm::new().unwrap(); let globals = vm.new_dict().unwrap(); let builtins = vm.new_dict().unwrap(); vm.dict_set(builtins, text("name"), text("builtin")) .unwrap(); vm.dict_set(globals, text("__builtins__"), Value::Object(builtins)) .unwrap(); let code = make_code( &mut vm, &[op(Opcode::LoadGlobal, 0), simple(Opcode::ReturnValue)], vec![], CodeLayout { names: vec!["name".into()], ..CodeLayout::default() }, ); let function = vm .new_function("read-name", code, globals, vec![], vec![]) .unwrap(); let endor_core::ExecutionProgress::Complete { value, reads, dependencies, .. } = vm .start_read_only_call(function, &[], ExecutionLimits::default(), 64) .unwrap() else { panic!("complete") }; assert_eq!(value, text("builtin")); assert!(reads.contains(&globals) && reads.contains(&builtins)); vm.release_value(value).unwrap(); vm.dict_set(globals, text("_"), text("unrelated")).unwrap(); let (value, synchronous) = vm .call_read_only_precise_with_limits(function, &[], ExecutionLimits::default()) .unwrap(); assert_eq!(value, text("builtin")); assert_eq!(synchronous.dependencies, dependencies); assert_eq!(synchronous.objects, reads); vm.release_value(value).unwrap(); let (value, conservative) = vm .call_read_only_with_limits(function, &[], ExecutionLimits::default()) .unwrap(); assert_eq!(conservative, reads); vm.release_value(value).unwrap(); assert!( dependencies .iter() .all(|dependency| vm.object_store().read_dependency_valid(dependency)) ); vm.dict_set(globals, text("name"), text("shadow")).unwrap(); assert!( dependencies .iter() .any(|dependency| !vm.object_store().read_dependency_valid(dependency)) ); vm.dict_delete(globals, &text("name")).unwrap(); assert!( dependencies .iter() .all(|dependency| vm.object_store().read_dependency_valid(dependency)) ); vm.dict_set(builtins, text("name"), text("changed")) .unwrap(); assert!( dependencies .iter() .any(|dependency| !vm.object_store().read_dependency_valid(dependency)) ); for object in [function, code, globals, builtins] { vm.release(object).unwrap(); } assert_eq!(vm.object_count(), 0); assert!( dependencies .iter() .any(|dependency| !vm.object_store().read_dependency_valid(dependency)) ); } #[test] fn precise_nested_regions_replay_borrowed_dependencies_without_hiding_full_dict_reads() { let mut vm = endor_core::ObjectStore::new().unwrap(); let globals = vm.new_dict().unwrap(); vm.dict_set(globals, text("name"), text("value")).unwrap(); let scope = vm.begin_read_only(); let token = vm.begin_read_tracking().unwrap(); vm.dict_get_name(globals, "name").unwrap(); let tracked = vm.end_read_tracking_precise(token).unwrap(); assert!( tracked .accesses .iter() .any(|access| access.object == globals) ); for dependency in &tracked.dependencies { assert!(vm.read_dependency_valid(dependency)); } let reads = vm.end_read_only_precise(scope).unwrap(); vm.dict_set(globals, text("other"), Value::Nil).unwrap(); assert!( reads .dependencies .iter() .all(|dependency| vm.read_dependency_valid(dependency)) ); let scope = vm.begin_read_only(); vm.dict_items(globals).unwrap(); let reads = vm.end_read_only_precise(scope).unwrap(); vm.dict_set(globals, text("third"), Value::Nil).unwrap(); assert!( reads .dependencies .iter() .any(|dependency| !vm.read_dependency_valid(dependency)) ); vm.release(globals).unwrap(); assert_eq!(vm.object_count(), 0); }