From bf3313f58d334178c24eebcc37fddea77c5d5eec Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:11:22 +0800 Subject: [PATCH] docs(core): explain nested call closure and execution cleanup regressions --- crates/core/tests/execution.rs | 93 ++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/crates/core/tests/execution.rs b/crates/core/tests/execution.rs index a093c2c..61b18d6 100644 --- a/crates/core/tests/execution.rs +++ b/crates/core/tests/execution.rs @@ -391,9 +391,13 @@ fn relative_branches_and_back_edges_execute_a_loop() { } #[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)], @@ -404,7 +408,9 @@ fn nested_language_calls_bind_defaults_and_return_owned_argument_objects() { ..CodeLayout::default() }, ); + // 默认字符串先有测试根,函数默认值表另持内部边。 let default = vm.new_string("default").unwrap(); + // 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。 let function = vm .new_function( "identity", @@ -414,15 +420,20 @@ fn nested_language_calls_bind_defaults_and_return_owned_argument_objects() { 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, &[ @@ -438,18 +449,25 @@ fn nested_language_calls_bind_defaults_and_return_owned_argument_objects() { ..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)], @@ -460,6 +478,7 @@ fn argument_count_and_foreign_argument_failures_do_not_retain_inputs() { ..CodeLayout::default() }, ); + // 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。 let function = vm .new_function("identity", code, globals, vec![], vec![]) .unwrap(); @@ -479,8 +498,11 @@ fn argument_count_and_foreign_argument_failures_do_not_retain_inputs() { .. }) )); + // 另一堆产生真正有效但归属不同的参数,不能当本堆对象保留。 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)]), @@ -490,10 +512,15 @@ fn argument_count_and_foreign_argument_failures_do_not_retain_inputs() { } #[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, &[ @@ -505,10 +532,15 @@ fn freshly_built_maps_and_their_children_survive_frame_cleanup() { 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"); }; @@ -518,10 +550,12 @@ fn freshly_built_maps_and_their_children_survive_frame_cleanup() { 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() @@ -530,15 +564,21 @@ fn echo(arguments: &[Value]) -> Result { } #[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, &[ @@ -554,27 +594,36 @@ fn native_return_of_a_temporary_argument_is_retained_before_inputs_are_popped() ..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), @@ -591,7 +640,9 @@ fn native_failure_unwinds_temporary_roots_and_keeps_execution_locations() { ) .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), @@ -605,9 +656,13 @@ fn native_failure_unwinds_temporary_roots_and_keeps_execution_locations() { } #[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, &[ @@ -622,11 +677,13 @@ fn recursion_uses_explicit_frames_and_unwinds_at_the_frame_limit() { ..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( @@ -641,22 +698,30 @@ fn recursion_uses_explicit_frames_and_unwinds_at_the_frame_limit() { ); 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( @@ -674,14 +739,19 @@ fn instruction_limit_stops_a_valid_infinite_loop_without_leaking_frames() { } #[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)], @@ -691,12 +761,15 @@ fn function_globals_shadow_builtins_and_capture_the_original_builtins_dictionary ..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")) @@ -705,10 +778,15 @@ fn function_globals_shadow_builtins_and_capture_the_original_builtins_dictionary } #[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)], @@ -718,6 +796,7 @@ fn shared_free_variable_cells_are_read_written_and_deleted_by_bytecode() { ..CodeLayout::default() }, ); + // 有一个普通参数槽,外部 Cell 追加在索引一,StoreDeref 不能误写局部槽。 let write_code = make_code( &mut vm, &[ @@ -734,6 +813,7 @@ fn shared_free_variable_cells_are_read_written_and_deleted_by_bytecode() { ..CodeLayout::default() }, ); + // DeleteDeref 清空 Cell 而非写 Nil,随后返回直接 Nil。 let clear_code = make_code( &mut vm, &[ @@ -747,12 +827,15 @@ fn shared_free_variable_cells_are_read_written_and_deleted_by_bytecode() { ..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(); @@ -764,9 +847,13 @@ fn shared_free_variable_cells_are_read_written_and_deleted_by_bytecode() { } #[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)], @@ -778,17 +865,23 @@ fn captured_argument_cells_keep_their_contents_after_the_frame_returns() { ..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))); }