diff --git a/crates/core/tests/execution.rs b/crates/core/tests/execution.rs index 61b18d6..4905aaf 100644 --- a/crates/core/tests/execution.rs +++ b/crates/core/tests/execution.rs @@ -887,9 +887,13 @@ fn captured_argument_cells_keep_their_contents_after_the_frame_returns() { } #[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, &[ @@ -911,24 +915,34 @@ fn attribute_operations_and_stack_copies_transfer_roots_without_leaks() { ..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, &[ @@ -950,9 +964,13 @@ fn comparison_truth_and_forward_conditionals_use_runtime_values() { } #[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)], @@ -966,6 +984,7 @@ fn unbound_names_deleted_locals_and_invalid_null_values_fail_cleanly() { vm.execute(missing, globals), Err(VmError::NameNotFound("missing".into())) ); + // 先成功赋值再 DeleteFast,最后读取检验 None 未绑定状态。 let deleted = make_code( &mut vm, &[ @@ -985,6 +1004,7 @@ fn unbound_names_deleted_locals_and_invalid_null_values_fail_cleanly() { vm.execute(deleted, globals), Err(VmError::UnboundLocal("x".into())) ); + // PushNull 满足抽象栈高度但不是语言 Value,ReturnValue 必须在运行时拒绝。 let null = make_code( &mut vm, &[simple(Opcode::PushNull), simple(Opcode::ReturnValue)], @@ -995,15 +1015,20 @@ fn unbound_names_deleted_locals_and_invalid_null_values_fail_cleanly() { 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, &[ @@ -1015,6 +1040,7 @@ fn failed_map_construction_cleans_both_partial_map_and_temporary_input() { vec![Value::Number(1_i64.into())], CodeLayout::default(), ); + // 失败前存活对象基线,执行临时函数和两层临时图不得泄漏。 let before = vm.object_count(); assert_eq!( vm.execute(code, globals), @@ -1024,12 +1050,17 @@ fn failed_map_construction_cleans_both_partial_map_and_temporary_input() { } #[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, &[ @@ -1041,11 +1072,12 @@ fn implicit_receiver_slot_is_distinct_from_the_null_call_marker() { vec![Value::Object(function), Value::Nil], CodeLayout::default(), ); - // Nil is an actual implicit argument here, not the internal NULL marker. + // 此处 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(), @@ -1055,6 +1087,7 @@ fn function_creation_operands_round_trip_and_require_all_inputs() { 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), @@ -1073,6 +1106,7 @@ fn function_creation_operands_round_trip_and_require_all_inputs() { ); 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( @@ -1086,6 +1120,7 @@ fn function_creation_operands_round_trip_and_require_all_inputs() { Err(VmError::InvalidBytecode { .. }) )); } + // 只有一个常量不能满足含默认值或捕获槽的函数构造,缺输入仍须静态拒绝。 for creation in [ Instruction::make_function(1, 0), Instruction::make_function(0, 1), @@ -1106,9 +1141,13 @@ fn function_creation_operands_round_trip_and_require_all_inputs() { } #[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![], @@ -1120,7 +1159,9 @@ fn bytecode_defines_global_functions_and_calls_them_with_defaults() { ) .unwrap() .with_location("identity", "functions.endor", 2); + // 代码常量由模块/工厂保留,归还测试根后仍用于后续 MakeFunction。 let inner = vm.new_code(inner).unwrap(); + // 按默认值、代码顺序压栈创建函数,存全局后再 Call 零显式参数走默认值。 let module = make_code( &mut vm, &[ @@ -1139,12 +1180,16 @@ fn bytecode_defines_global_functions_and_calls_them_with_defaults() { ..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(); @@ -1156,6 +1201,7 @@ fn bytecode_defines_global_functions_and_calls_them_with_defaults() { 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"); }; @@ -1166,18 +1212,25 @@ fn bytecode_defines_global_functions_and_calls_them_with_defaults() { .code(), inner ); + // 归还为跨重定义而取得的旧函数根,不能把借用全局结果当作拥有根。 vm.release(first).unwrap(); + // 模块代码常量边释放,仍由全局函数持有内部代码。 vm.release(module).unwrap(); + // 归还环境测试根,若函数与环境互相持有则需显式环回收。 vm.release(globals).unwrap(); - // The global function, its code and its globals form a collectible cycle. + // 全局字典持有函数、函数持有全局和代码,失去外部根后形成三个对象的可回收图。 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)], @@ -1188,6 +1241,7 @@ fn bytecode_function_defaults_keep_temporary_objects_alive_after_factory_returns ..CodeLayout::default() }, ); + // 先在栈上新建默认空图,再压 tag 和代码,MakeFunction 把前两项作为默认值。 let factory = make_code( &mut vm, &[ @@ -1200,26 +1254,36 @@ fn bytecode_function_defaults_keep_temporary_objects_alive_after_factory_returns 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); }