diff --git a/crates/core/tests/builtins.rs b/crates/core/tests/builtins.rs index de46a70..a942d6e 100644 --- a/crates/core/tests/builtins.rs +++ b/crates/core/tests/builtins.rs @@ -148,8 +148,10 @@ fn dictionaries_keep_order_and_first_key_and_own_keys_and_values() { ); assert_eq!(vm.dict_items(dict).unwrap()[0].0, Value::Object(key)); assert_eq!(vm.dict_items(dict).unwrap().len(), 2); + // 字典覆盖旧值后,old 已无任何拥有根,访问必须报告失效句柄。 assert!(matches!(vm.object(old), Err(VmError::InvalidHandle))); assert!(vm.dict_delete(dict, &text("answer")).unwrap()); + // 删除 answer 同时释放保留的首键对象,等价 Text 键不会替它延长寿命。 assert!(matches!(vm.object(key), Err(VmError::InvalidHandle))); assert!(!vm.dict_delete(dict, &text("missing")).unwrap()); vm.dict_clear(dict).unwrap(); @@ -227,6 +229,7 @@ fn cells_share_rebinding_distinguish_empty_and_none_and_keep_stable_objects() { vm.cell_set(cell, Value::Object(child)).unwrap(); assert_eq!(vm.ref_count(child).unwrap(), 1); vm.cell_clear(cell).unwrap(); + // 清空 Cell 归还子对象最后的内部边,child 不能仍然可访问。 assert!(matches!(vm.object(child), Err(VmError::InvalidHandle))); assert_eq!(vm.cell_get(cell), Err(VmError::EmptyCell)); } diff --git a/crates/core/tests/execution.rs b/crates/core/tests/execution.rs index 4a790de..4c3567b 100644 --- a/crates/core/tests/execution.rs +++ b/crates/core/tests/execution.rs @@ -237,6 +237,7 @@ fn malformed_wordcode_and_unaligned_jumps_are_rejected_at_construction() { vec![255, 1, 255, 1, 255, 1, 255, 1, 1, 0], vec![13, 1], ] { + // 每份非法物理编码都必须在代码构造阶段拒绝,不能等到 VM 取指才失败。 assert!(matches!( EndorCode::new(bytes, vec![Value::Nil], vec![], vec![]), Err(VmError::InvalidBytecode { .. }) @@ -482,6 +483,7 @@ fn argument_count_and_foreign_argument_failures_do_not_retain_inputs() { let function = vm .new_function("identity", code, globals, vec![], vec![]) .unwrap(); + // 无实参调用必须准确报告缺少唯一必需参数,不把缺参隐式当作 Nil。 assert!(matches!( vm.call(function, &[]), Err(VmError::ArgumentCount { @@ -490,6 +492,7 @@ fn argument_count_and_foreign_argument_failures_do_not_retain_inputs() { .. }) )); + // 两个实参超过该函数的最大数量一,预检应在进入函数体前拒绝。 assert!(matches!( vm.call(function, &[Value::Nil, Value::Nil]), Err(VmError::ArgumentCount { @@ -1011,6 +1014,7 @@ fn unbound_names_deleted_locals_and_invalid_null_values_fail_cleanly() { vec![], CodeLayout::default(), ); + // Null 是 VM 协议哨兵而非语言值,源码执行把它用于非法值路径时必须拒绝。 assert!(matches!( vm.execute(null, globals), Err(VmError::InvalidBytecode { .. }) @@ -1699,6 +1703,7 @@ fn suspended_read_only_call_keeps_roots_and_releases_them_on_completion() { 1, ) .unwrap(); + // 首量子只消耗一条 Nop,应保存暂停帧并报告累计一条,不能提前完成。 assert!(matches!( progress, ExecutionProgress::Pending { instructions: 1 } @@ -1852,13 +1857,13 @@ fn managed_coroutines_interleave_without_replaying_writes() { let second = vm .new_coroutine(function, &[], ExecutionLimits::default()) .unwrap(); + // 推进第一份到首次 StoreGlobal 之后,累计两条指令。 assert!(matches!( - // 推进第一份到首次 StoreGlobal 之后,累计两条指令。 vm.resume_coroutine(first, 2).unwrap(), CoroutineProgress::Pending { instructions: 2 } )); + // 第二份也停在首次写入之后,后续都不应重放这个前缀。 assert!(matches!( - // 第二份也停在首次写入之后,后续都不应重放这个前缀。 vm.resume_coroutine(second, 2).unwrap(), CoroutineProgress::Pending { instructions: 2 } )); @@ -1965,13 +1970,13 @@ fn managed_coroutine_hard_budget_accumulates_and_error_releases_frames() { }, ) .unwrap(); + // 首量子只执行 Nop 并停放,累计已用一条。 assert!(matches!( - // 首量子只执行 Nop 并停放,累计已用一条。 vm.resume_coroutine(coroutine, 1).unwrap(), CoroutineProgress::Pending { instructions: 1 } )); + // 第二次执行 LoadConst 后预算耗尽,尚未 Return,因此应终止而非再次 Pending。 assert!(matches!( - // 第二次执行 LoadConst 后预算耗尽,尚未 Return,因此应终止而非再次 Pending。 vm.resume_coroutine(coroutine, 1), Err(VmError::ExecutionLimit("instructions")) )); diff --git a/crates/core/tests/method_contracts.rs b/crates/core/tests/method_contracts.rs index 40bda5d..d355421 100644 --- a/crates/core/tests/method_contracts.rs +++ b/crates/core/tests/method_contracts.rs @@ -106,6 +106,7 @@ fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation() "Number", Value::Nil, ); + // 子方法缩窄参数可接受范围会破坏替换关系,必须在建类时报告覆盖违规。 assert!(matches!( class(&mut vm, "Narrow", base, narrow), Err(VmError::OverrideViolation { @@ -133,6 +134,7 @@ fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation() "Object", Value::Nil, ); + // 子方法放宽返回类型会破坏父方法承诺,必须在建类时拒绝。 assert!(matches!( class(&mut vm, "Broad", numeric_type, broad), Err(VmError::OverrideViolation { @@ -305,6 +307,7 @@ fn global_rebinding_is_rechecked_before_the_source_body_and_recovers() { vm.attach_type(receiver, child).unwrap(); vm.dict_set(globals, Value::Text("Actual".into()), Value::Object(other)) .unwrap(); + // Actual 的全局类型绑定变化后,调用前重新检查覆盖关系,失败时不能运行方法体。 assert!(matches!( vm.call(child_fn, &[Value::Object(receiver)]), Err(VmError::OverrideViolation { .. }) @@ -404,6 +407,7 @@ fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() { vec![], )) .unwrap(); + // 只读父方法不能被已知有写效果的子方法替换,更新必须在提交前失败。 assert!(matches!( vm.define_method(child, "check", effect_fn), Err(VmError::OverrideViolation { @@ -423,6 +427,7 @@ fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() { vec![], )) .unwrap(); + // 子方法仅接受 Number 会丢失父联合参数的 String 候选,不能缩窄父方法可接受的输入。 assert!(matches!( vm.define_method(child, "check", narrow_fn), Err(VmError::OverrideViolation { @@ -498,6 +503,7 @@ fn unknown_forward_names_defer_until_call_without_entering_the_body() { .unwrap(); vm.dict_set(globals, Value::Text("Later".into()), Value::Object(later)) .unwrap(); + // Later 从未知名解析成不兼容名义类型后,错误应转为覆盖违规且仍在方法体前发生。 assert!(matches!( vm.call( child_fn, diff --git a/crates/core/tests/native_context.rs b/crates/core/tests/native_context.rs index 09d77f6..8f74b97 100644 --- a/crates/core/tests/native_context.rs +++ b/crates/core/tests/native_context.rs @@ -442,10 +442,12 @@ fn native_types_preflight_arguments_and_clean_wrong_result_roots() { )) .unwrap(); assert!( + // 错误实参在回调前由 parameter 1 约束拒绝,后面的计数零验证未进入宿主代码。 matches!(vm.call(function, &[Value::Text("bad".into())]), Err(VmError::TypeMismatch { position, .. }) if position == "parameter 1") ); assert_eq!(calls.load(Ordering::SeqCst), 0); assert!( + // 合法数值允许回调执行一次,但宿主返回值违约应明确定位 return。 matches!(vm.call(function, &[Value::Number(7.into())]), Err(VmError::TypeMismatch { position, .. }) if position == "return") ); assert_eq!(calls.load(Ordering::SeqCst), 1); @@ -519,8 +521,10 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() { Value::Text("source result".into()) ); assert!( + // 多层原生委托结果返回外层时,仍需兑现 outer 自己的返回约束并标明其名称。 matches!(vm.call_with_limits(outer, &[], limits), Err(VmError::TypeMismatch { function, position, .. }) if function == "outer" && position == "return") ); + // 零源码帧预算必须由受委托源码入口拒绝,不能因原生委托不占帧而绕过限制。 assert!(matches!( vm.call_with_limits( outer, @@ -656,7 +660,7 @@ fn legacy_native_signatures_check_optional_variadic_types_and_metadata() { } // 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。 let mut vm = Vm::new().unwrap(); - // 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。 + // 分配旧式 NativeFunction 载荷,签名引用的类型定义仍通过 trace 保活。 let function = vm .allocate( NativeFunction::new("legacy", echo).with_signature(CallSignature { @@ -678,6 +682,7 @@ fn legacy_native_signatures_check_optional_variadic_types_and_metadata() { ); assert!(vm.call(function, &[Value::Bool(true)]).is_err()); assert!( + // 可变参数允许空列表,但旧式 echo 此时返回 Nil,不符合声明的 Number 返回约束。 matches!(vm.call(function, &[]), Err(VmError::TypeMismatch { position, .. }) if position == "return") ); // 故意同时配置互相矛盾的签名和独立 arity,查询/调用都应报元数据错误。 @@ -835,6 +840,7 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met ); } assert!( + // 联合都不匹配时诊断需完整保留 String | Bool 候选,不只显示最后尝试的一项。 matches!(vm.call(function, &[Value::Number(3.into())]), Err(VmError::TypeMismatch { expected, .. }) if expected == "String | Bool") ); assert_eq!(calls.load(Ordering::SeqCst), 3); diff --git a/crates/core/tests/runtime.rs b/crates/core/tests/runtime.rs index 1d81305..4ba7ebc 100644 --- a/crates/core/tests/runtime.rs +++ b/crates/core/tests/runtime.rs @@ -164,6 +164,7 @@ fn explicit_references_control_lifetime_and_copies_are_borrowed() { vm.release(owned).unwrap(); assert_eq!(drops.load(Ordering::SeqCst), 1); assert_eq!(vm.object_count(), 0); + // 最后拥有根释放后,复制来的借用句柄必须失效,不能当作额外拥有根。 assert!(matches!(vm.object(borrowed), Err(VmError::InvalidHandle))); assert_eq!(vm.release(borrowed), Err(VmError::InvalidHandle)); } @@ -210,6 +211,7 @@ fn replacing_the_same_edge_does_not_destroy_the_target() { assert_eq!(vm.ref_count(target).unwrap(), 1); vm.set_attr(owner, "child", Value::Number(7_i64.into())) .unwrap(); + // 属性从对象改为数值会归还原目标最后的边,因此旧 target 句柄应失效。 assert!(matches!(vm.object(target), Err(VmError::InvalidHandle))); assert_eq!( vm.get_attr(owner, "child").unwrap(), @@ -350,7 +352,9 @@ fn cycles_remain_while_rooted_and_are_collected_when_unreachable() { assert_eq!(vm.collect_cycles(), 2); assert_eq!(vm.collect_cycles(), 0); assert_eq!(drops.load(Ordering::SeqCst), 2); + // 无根环回收后第一个旧句柄失效,不能只依赖析构计数判断槽是否仍可访问。 assert!(matches!(vm.object(first), Err(VmError::InvalidHandle))); + // 同一垃圾环中的第二个对象也必须失效,避免只回收部分环。 assert!(matches!(vm.object(second), Err(VmError::InvalidHandle))); } diff --git a/crates/core/tests/typed_traits.rs b/crates/core/tests/typed_traits.rs index 23cb45d..2245181 100644 --- a/crates/core/tests/typed_traits.rs +++ b/crates/core/tests/typed_traits.rs @@ -224,6 +224,7 @@ fn typed_source_methods_enforce_their_annotations_even_when_global_checks_are_of Some(Value::Number(0.into())) ); assert!( + // 参数合法时方法体可运行,但违背能力方法的返回约束应在出口定位 return。 matches!(vm.call(function, &[Value::Object(receiver), Value::Number(1.into())]), Err(VmError::TypeMismatch { position, .. }) if position=="return") ); assert_eq!( diff --git a/crates/core/tests/types.rs b/crates/core/tests/types.rs index 347b7b7..03ce664 100644 --- a/crates/core/tests/types.rs +++ b/crates/core/tests/types.rs @@ -154,6 +154,7 @@ fn explicit_traits_inherit_validate_overrides_and_own_definition_edges() { .new_class("Child", base, vec![], std::collections::BTreeMap::new()) .unwrap(); assert!(vm.implements_trait(child, capability).unwrap()); + // 祖先方法更新不得破坏已声明后代能力,失败后原方法仍应保持不变。 assert!(matches!( vm.define_method(base, "render", invalid), Err(VmError::TraitViolation { .. }) @@ -446,6 +447,7 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures() )) .unwrap(); assert_eq!(vm.call_arity(native).unwrap(), Some(CallArity::range(1, 2))); + // 零实参违反原生函数声明的 arity,必须在回调修改 called 状态之前拒绝。 assert!(matches!( vm.call(native, &[]), Err(VmError::ArgumentCount { .. }) diff --git a/crates/frontend/tests/ast.rs b/crates/frontend/tests/ast.rs index 7d2ce89..ef4a9f4 100644 --- a/crates/frontend/tests/ast.rs +++ b/crates/frontend/tests/ast.rs @@ -26,6 +26,7 @@ fn function_bodies_accept_method_statements_and_optional_trailing_return() { .body .statements .iter() + // 两个调用都应是普通 Expression 语句,不能被误识别为返回或绑定。 .all(|s| matches!(s.kind, StmtKind::Expression(_))) ); assert!(parse_ast("function empty(): Unit {}", ParseMode::Module).is_ok()); @@ -258,6 +259,7 @@ fn json_interfaces_reject_unknown_fields_schema_changes_and_invalid_ranges() { kind: ExprKind::Name("値".into()), span: Span::new(1, 3), }); + // 结构范围虽非逆序,起点切断三字节日文字符,仍须在提供原文的校验阶段失败。 assert!(matches!( AstDocument::new(root).unwrap().validate_source("値"), Err(AstError::InvalidSpan(_)) diff --git a/crates/frontend/tests/reader.rs b/crates/frontend/tests/reader.rs index 37e5451..851a525 100644 --- a/crates/frontend/tests/reader.rs +++ b/crates/frontend/tests/reader.rs @@ -291,6 +291,7 @@ fn uri_snapshots_require_a_revision_and_preserve_it_in_source_diagnostics() { revision: " \t", bytes: b"1", }; + // 字节有效不能补救缺版本身份,空白版本标签应单独报告 MissingRevision。 assert!(matches!( EndorReader::from_endor(uri, &missing).unwrap_err().kind, SourceReadErrorKind::MissingRevision @@ -336,6 +337,7 @@ fn invalid_uris_do_not_call_the_provider_and_denials_do_not_fall_back_to_files() "endor:space here", "endor:bad\nname", ] { + // 每个非法定位符都必须先被基础 URI 校验拒绝,后面的计数零验证未访问宿主。 assert!(matches!( EndorReader::from_endor(uri, &provider).unwrap_err().kind, SourceReadErrorKind::InvalidEndorUri diff --git a/crates/frontend/tests/syntax.rs b/crates/frontend/tests/syntax.rs index b57a185..3d5866d 100644 --- a/crates/frontend/tests/syntax.rs +++ b/crates/frontend/tests/syntax.rs @@ -190,6 +190,7 @@ fn arithmetic_precedence_and_left_associativity_form_the_expected_tree() { let ExprKind::Unary { operand, .. } = nested.kind else { panic!("expected outer negate"); }; + // 外层负号的操作数仍是 Negate,证明解析器保留两个前缀节点而未提前计算数值。 assert!(matches!( operand.kind, ExprKind::Unary { diff --git a/crates/objects/src/heap.rs b/crates/objects/src/heap.rs index 0ffa60f..8bdc255 100644 --- a/crates/objects/src/heap.rs +++ b/crates/objects/src/heap.rs @@ -926,7 +926,9 @@ mod tests { generation: 1, ..reference }; + // 槽索引越界时,即使堆身份和代际来自有效对象,也必须拒绝只读访问。 assert!(matches!(heap.get(invalid), Err(VmError::InvalidHandle))); + // 可变访问同样检查代际,伪造旧代际不能借出当前对象。 assert!(matches!(heap.get_mut(stale), Err(VmError::InvalidHandle))); } @@ -942,6 +944,7 @@ mod tests { let second = heap.allocate(Box::new(Record)).unwrap(); assert_eq!(first.index, second.index); assert_ne!(first.generation, second.generation); + // 复用同一槽但代际变化后,旧句柄必须失效,不能别名到新对象。 assert!(matches!(heap.get(first), Err(VmError::InvalidHandle))); // 内部故障注入:把槽及对象身份同步改成最高代际,再验证释放后的永久退休。 heap.slots[second.index].generation = u64::MAX; diff --git a/crates/objects/src/native.rs b/crates/objects/src/native.rs index 2c34e17..8bd8fc1 100644 --- a/crates/objects/src/native.rs +++ b/crates/objects/src/native.rs @@ -119,6 +119,7 @@ impl CallType { pub fn is_any(&self) -> bool { self.alternatives() .iter() + // 只检测是否存在 Any 候选,不据此省略联合中其他定义引用的验证或保活。 .any(|value| matches!(value, crate::AnnotationType::Any)) } /// 调用/签名检查确认联合非空,且每个托管类型/能力引用有效并有正确载荷。 diff --git a/crates/objects/src/number.rs b/crates/objects/src/number.rs index 5a76016..0def382 100644 --- a/crates/objects/src/number.rs +++ b/crates/objects/src/number.rs @@ -608,8 +608,10 @@ impl EndorNumber { CompareOp::Eq => order == Some(Ordering::Equal), CompareOp::Ne => order != Some(Ordering::Equal), CompareOp::Lt => order == Some(Ordering::Less), + // 小于等于仅接受已知 Less 或 Equal;NaN 造成的无序 None 不满足关系。 CompareOp::Le => matches!(order, Some(Ordering::Less | Ordering::Equal)), CompareOp::Gt => order == Some(Ordering::Greater), + // 大于等于仅接受已知 Greater 或 Equal,无序值同样返回 false。 CompareOp::Ge => matches!(order, Some(Ordering::Greater | Ordering::Equal)), }) } diff --git a/crates/objects/src/types.rs b/crates/objects/src/types.rs index cca867a..487d6e8 100644 --- a/crates/objects/src/types.rs +++ b/crates/objects/src/types.rs @@ -1604,21 +1604,29 @@ impl ObjectStore { AnnotationType::Any => true, AnnotationType::Unit => matches!(value, Value::Nil), AnnotationType::Number => { + // Number 同时接受直接数值;另一路只认 EndorNumberObject 载荷,不把任意名义类当数值。 matches!(value, Value::Number(_)) + // 对象句柄已在入口验证存活,仍须下转到具体数值载荷以匹配 Number 类别。 || matches!(value,Value::Object(object) if self.object(*object)?.payload().as_any().is::()) } AnnotationType::String => { + // String 同时接受直接 Text;对象表示只接受内置字符串载荷。 matches!(value, Value::Text(_)) + // 对象必须确为 EndorStringObject,不通过 str 协议把任意对象隐式转文本。 || matches!(value,Value::Object(object) if self.object(*object)?.payload().as_any().is::()) } + // Bool 仅匹配直接布尔值,不用真值判断把其他对象归成 Bool。 AnnotationType::Bool => matches!(value, Value::Bool(_)), AnnotationType::Function => { + // Function 看载荷的函数分类标志;任意可调用 Type 不自动属于 Function。 matches!(value,Value::Object(object) if self.object(*object)?.payload().is_function()) } AnnotationType::Type => { + // Type 要求实际类型定义载荷,不按对象名或是否可调用猜测。 matches!(value,Value::Object(object) if self.object(*object)?.payload().as_any().is::()) } AnnotationType::Trait => { + // Trait 要求实际能力定义载荷,与实现某能力的普通实例不同。 matches!(value,Value::Object(object) if self.object(*object)?.payload().as_any().is::()) } AnnotationType::Nominal(definition) => match value { diff --git a/crates/objects/tests/attribute_storage.rs b/crates/objects/tests/attribute_storage.rs index f8047b5..0267331 100644 --- a/crates/objects/tests/attribute_storage.rs +++ b/crates/objects/tests/attribute_storage.rs @@ -210,7 +210,7 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() { // 能力声明失败应保持定义版本和声明集合不变。 let version = store.change_version(class).unwrap(); assert!(matches!( - // 存储冲突消除后能力声明才允许提交。 + // 当前后端仍有遮蔽字段,能力声明必须在状态提交前失败。 store.implement_trait(class, capability), Err(VmError::TraitViolation { .. }) )); @@ -236,18 +236,22 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() { store.supports(proxy, capability), Err(VmError::TraitViolation { .. }) )); + // 后端已出现遮蔽方法的冲突字段时,即使只读属性查询也必须拒绝不合法的能力存储。 assert!(matches!( store.get_attr(proxy, "check"), Err(VmError::TraitViolation { .. }) )); + // 绑定方法入口不能绕过同一存储校验,不能先创建可调用绑定再放过冲突。 assert!(matches!( store.bind_attr(proxy, "check"), Err(VmError::TraitViolation { .. }) )); + // 直接方法契约检查也拒绝代理后端冲突,防止跳过属性查找后绕过门禁。 assert!(matches!( store.check_method_contract_call(function, proxy, false), Err(VmError::TraitViolation { .. }) )); + // 显式实例存储检查应给出同类能力违规,保持各调用入口的检查一致。 assert!(matches!( store.check_typed_instance_storage(proxy), Err(VmError::TraitViolation { .. }) @@ -289,6 +293,7 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() { Value::Bool(true) ); assert!(store.supports(reflective, capability).unwrap()); + // 带反射检查的载荷第一次非法写入仍须拒绝,递归防护不能误跳过最外层约束。 assert!(matches!( store.set_attr(reflective, "check", Value::Nil), Err(VmError::TraitViolation { .. }) diff --git a/crates/objects/tests/store.rs b/crates/objects/tests/store.rs index 83129be..0a14d1b 100644 --- a/crates/objects/tests/store.rs +++ b/crates/objects/tests/store.rs @@ -42,6 +42,7 @@ fn standalone_storage_preserves_identity_validates_edges_and_collects_cycles() { store.release(cell).unwrap(); assert_eq!(store.collect_cycles(), 2); assert_eq!(store.object_count(), 0); + // 独立 Store 释放完拥有图后,原 Cell 借用句柄也应失效。 assert!(matches!(store.object(cell), Err(VmError::InvalidHandle))); other.release(foreign).unwrap(); }