docs(core): explain dynamic contract and effect graph regressions
This commit is contained in:
@@ -448,13 +448,19 @@ fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 构类时可暂缓未知前向注解,调用时必须解析并重验覆盖;未知和已知但不兼容都不能进入源码体。
|
||||
fn unknown_forward_names_defer_until_call_without_entering_the_body() {
|
||||
// 独立 VM 隔离全局与方法重绑定,避免其他场景状态干扰快照证明。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 显式类型自举根,用于实例与绑定方法测试,最终归还并收集类型环。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 启用普通覆盖验证,前向未知仅在定义阶段暂缓,调用阶段不豁免。
|
||||
vm.set_source_type_checks(true).unwrap();
|
||||
// 源码名查找环境,更新其中键可改变契约或调用图,不能把首次查询缓存为永久证明。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
vm.dict_set(globals, Value::Text("ran".into()), Value::Number(0.into()))
|
||||
.unwrap();
|
||||
// 父方法要求 Number 参数,子类尚未定义的 Later 参数暂缓到调用检查。
|
||||
let base_fn = source(
|
||||
&mut vm,
|
||||
globals,
|
||||
@@ -463,7 +469,9 @@ fn unknown_forward_names_defer_until_call_without_entering_the_body() {
|
||||
"Number",
|
||||
Value::Number(0.into()),
|
||||
);
|
||||
// 父定义先合法创建,后代契约需相对这份真实定义检查。
|
||||
let base = class(&mut vm, "Base", roots.object, base_fn).unwrap();
|
||||
// 子类使用尚未解析的 Later 名称,不代表可永久跳过参数替换验证。
|
||||
let child_fn = source(
|
||||
&mut vm,
|
||||
globals,
|
||||
@@ -472,7 +480,9 @@ fn unknown_forward_names_defer_until_call_without_entering_the_body() {
|
||||
"Number",
|
||||
Value::Number(0.into()),
|
||||
);
|
||||
// 定义阶段允许未知名称的派生创建,实例调用时会重新核对。
|
||||
let child = class(&mut vm, "Child", base, child_fn).unwrap();
|
||||
// 实例提供实际接收者身份,绑定效果分析可据此查 helper,未绑定函数则未知。
|
||||
let receiver = vm.new_object().unwrap();
|
||||
vm.attach_type(receiver, child).unwrap();
|
||||
assert!(matches!(
|
||||
@@ -482,6 +492,7 @@ fn unknown_forward_names_defer_until_call_without_entering_the_body() {
|
||||
),
|
||||
Err(VmError::UnknownAnnotation(_))
|
||||
));
|
||||
// Later 最终解析为独立名义类型,不接纳父参数 Number,故由未知错误变为覆盖不兼容。
|
||||
let later = vm
|
||||
.new_type("Later", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
@@ -498,6 +509,7 @@ fn unknown_forward_names_defer_until_call_without_entering_the_body() {
|
||||
vm.dict_get(globals, &Value::Text("ran".into())).unwrap(),
|
||||
Some(Value::Number(0.into()))
|
||||
);
|
||||
// 归还测试拥有根,内部方法/签名/类型边由堆维护,避免重复 release。
|
||||
for object in [
|
||||
receiver,
|
||||
child,
|
||||
@@ -511,16 +523,23 @@ fn unknown_forward_names_defer_until_call_without_entering_the_body() {
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 回收无根函数/全局/类型环,最终对象数应为零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 已验证源码只读效果约束原生覆盖;Mutating/External 拒绝,Delegated 仍为未知而不是确定只读。
|
||||
fn verified_source_read_only_effects_constrain_native_overrides() {
|
||||
// 独立 VM 隔离全局与方法重绑定,避免其他场景状态干扰快照证明。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 显式类型自举根,用于实例与绑定方法测试,最终归还并收集类型环。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 启用普通覆盖验证,前向未知仅在定义阶段暂缓,调用阶段不豁免。
|
||||
vm.set_source_type_checks(true).unwrap();
|
||||
// 源码名查找环境,更新其中键可改变契约或调用图,不能把首次查询缓存为永久证明。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 手工指令使控制流和调用目标来源可精确观察,构造器先验证栈高/跳转。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadFast, 1),
|
||||
@@ -541,26 +560,35 @@ fn verified_source_read_only_effects_constrain_native_overrides() {
|
||||
})
|
||||
.unwrap();
|
||||
assert!(code.is_statically_read_only());
|
||||
// 代码数据只验证与分配,call_effect 不应实际执行其中指令。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 源码方法只装载参数并返回,可由本地白名单直接证明 ReadOnly。
|
||||
let source = vm
|
||||
.new_function("check", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
// 父定义先合法创建,后代契约需相对这份真实定义检查。
|
||||
let base = class(&mut vm, "PureBase", roots.object, source).unwrap();
|
||||
// 后代继承已证明只读的方法,候选原生覆盖不能扩大已知效果。
|
||||
let child = vm.new_type("Child", base, TypeConstructor::Record).unwrap();
|
||||
assert_eq!(vm.call_effect(source).unwrap(), Some(CallEffect::ReadOnly));
|
||||
// 实例提供实际接收者身份,绑定效果分析可据此查 helper,未绑定函数则未知。
|
||||
let receiver = vm.new_object().unwrap();
|
||||
vm.attach_type(receiver, child).unwrap();
|
||||
// 绑定对象拥有函数和实际接收者边,分析可用 receiver 区分实例方法/字段。
|
||||
let Value::Object(bound) = vm.bind_attr(receiver, "check").unwrap() else {
|
||||
panic!("bound method")
|
||||
};
|
||||
assert_eq!(vm.call_effect(bound).unwrap(), Some(CallEffect::ReadOnly));
|
||||
// 保存所有候选原生函数拥有根,避免只清理最后成功安装的方法。
|
||||
let mut functions = vec![];
|
||||
// 四种原生声明分别测试;未知 Delegated 不等于允许在只读域执行任意目标。
|
||||
for effect in [
|
||||
CallEffect::Mutating,
|
||||
CallEffect::External,
|
||||
CallEffect::ReadOnly,
|
||||
CallEffect::Delegated,
|
||||
] {
|
||||
// 函数持有其代码/全局边,效果查询从当前载荷及环境读取快照。
|
||||
let function = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"check",
|
||||
@@ -572,8 +600,11 @@ fn verified_source_read_only_effects_constrain_native_overrides() {
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 失败更新不能改变后代定义版本,用于证明检查在提交方法表之前完成。
|
||||
let version = vm.change_version(child).unwrap();
|
||||
// 保存更新结果,再按已知效果是否扩大区分成功与拒绝。
|
||||
let update = vm.define_method(child, "check", function);
|
||||
// Mutating/External 扩大 ReadOnly;其余候选按当前规则检查与查询。
|
||||
if matches!(effect, CallEffect::Mutating | CallEffect::External) {
|
||||
assert!(matches!(
|
||||
update,
|
||||
@@ -592,6 +623,7 @@ fn verified_source_read_only_effects_constrain_native_overrides() {
|
||||
);
|
||||
functions.push(function);
|
||||
}
|
||||
// 归还测试拥有根,内部方法/签名/类型边由堆维护,避免重复 release。
|
||||
for object in functions.into_iter().chain([
|
||||
bound,
|
||||
receiver,
|
||||
@@ -605,12 +637,15 @@ fn verified_source_read_only_effects_constrain_native_overrides() {
|
||||
]) {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 回收无根函数/全局/类型环,最终对象数应为零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 局部共享引用赋值可能修改旧对象,Cell 写入/未知调用也不能由本地白名单直接证明纯。
|
||||
fn shared_local_assignment_and_dynamic_calls_are_not_inferred_pure() {
|
||||
// StoreFast 可能触发旧对象共享引用赋值,不能仅因目标是局部变量就判纯。
|
||||
let assignment = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadFast, 1),
|
||||
@@ -627,6 +662,7 @@ fn shared_local_assignment_and_dynamic_calls_are_not_inferred_pure() {
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!assignment.is_statically_read_only());
|
||||
// StoreDeref 修改已有捕获 Cell,必须从本地只读白名单排除。
|
||||
let captured = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadConst, 0),
|
||||
@@ -642,6 +678,7 @@ fn shared_local_assignment_and_dynamic_calls_are_not_inferred_pure() {
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!captured.is_statically_read_only());
|
||||
// 动态 Call 没有本地目标证明,is_statically_read_only 不能直接通过。
|
||||
let call = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadGlobal, 0),
|
||||
@@ -659,7 +696,9 @@ fn shared_local_assignment_and_dynamic_calls_are_not_inferred_pure() {
|
||||
assert!(!call.is_statically_read_only());
|
||||
}
|
||||
|
||||
/// 效果测试辅助,手工建立调用指定全局名并返回的源码函数,不在查询时执行。
|
||||
fn global_caller(vm: &mut Vm, globals: ObjectRef, name: &str) -> ObjectRef {
|
||||
// 手工指令使控制流和调用目标来源可精确观察,构造器先验证栈高/跳转。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadGlobal, 0),
|
||||
@@ -674,18 +713,25 @@ fn global_caller(vm: &mut Vm, globals: ObjectRef, name: &str) -> ObjectRef {
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
// 代码数据只验证与分配,call_effect 不应实际执行其中指令。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 函数持有其代码/全局边,效果查询从当前载荷及环境读取快照。
|
||||
let function = vm
|
||||
.new_function("caller", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
// 辅助函数归还临时代码根,函数仍保留代码对象。
|
||||
vm.release(code).unwrap();
|
||||
function
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 调用效果快照随全局遮蔽、内置回退、闭包 Cell 及递归边变化重新求证,不保留过期目标。
|
||||
fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() {
|
||||
// 独立 VM 隔离全局与方法重绑定,避免其他场景状态干扰快照证明。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 源码名查找环境,更新其中键可改变契约或调用图,不能把首次查询缓存为永久证明。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 函数创建时捕获该内置字典身份,全局缺名才回退这里。
|
||||
let builtins = vm.new_dict().unwrap();
|
||||
vm.dict_set(
|
||||
globals,
|
||||
@@ -693,6 +739,7 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() {
|
||||
Value::Object(builtins),
|
||||
)
|
||||
.unwrap();
|
||||
// 明确声明 ReadOnly 的原生目标,作为可证明调用边。
|
||||
let pure = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"pure",
|
||||
@@ -704,6 +751,7 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() {
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 声明 Mutating/External 的目标,使源码整体调用效果无法证明 ReadOnly。
|
||||
let impure = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"impure",
|
||||
@@ -717,7 +765,9 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() {
|
||||
.unwrap();
|
||||
vm.dict_set(builtins, Value::Text("target".into()), Value::Object(pure))
|
||||
.unwrap();
|
||||
// 第一层从环境解析 target,初始由内置字典提供 pure。
|
||||
let wrapper = global_caller(&mut vm, globals, "target");
|
||||
// 外层再经全局 wrapper 调用,检验传递分析不只看一跳。
|
||||
let outer = global_caller(&mut vm, globals, "wrapper");
|
||||
vm.dict_set(
|
||||
globals,
|
||||
@@ -732,7 +782,9 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() {
|
||||
vm.dict_delete(globals, &Value::Text("target".into()))
|
||||
.unwrap();
|
||||
assert_eq!(vm.call_effect(outer).unwrap(), Some(CallEffect::ReadOnly));
|
||||
// 闭包捕获目标通过共享 Cell 更新,函数身份不变而调用图可变化。
|
||||
let cell = vm.new_cell(Some(Value::Object(pure))).unwrap();
|
||||
// 手工指令使控制流和调用目标来源可精确观察,构造器先验证栈高/跳转。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadDeref, 0),
|
||||
@@ -747,7 +799,9 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() {
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
// 代码数据只验证与分配,call_effect 不应实际执行其中指令。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 源码经 LoadDeref 取目标再 Call,分析需读 Cell 当前内容而不执行函数体。
|
||||
let closure = vm
|
||||
.new_function("closure", code, globals, vec![], vec![cell])
|
||||
.unwrap();
|
||||
@@ -756,6 +810,7 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() {
|
||||
assert_eq!(vm.call_effect(closure).unwrap(), None);
|
||||
vm.cell_set(cell, Value::Object(pure)).unwrap();
|
||||
assert_eq!(vm.call_effect(closure).unwrap(), Some(CallEffect::ReadOnly));
|
||||
// 全局同名绑定回自己形成不终止调用图;查询必须通过去重收敛而非执行。
|
||||
let recursive = global_caller(&mut vm, globals, "recursive");
|
||||
vm.dict_set(
|
||||
globals,
|
||||
@@ -763,7 +818,7 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() {
|
||||
Value::Object(recursive),
|
||||
)
|
||||
.unwrap();
|
||||
// No function body is executed, including this non-terminating call graph.
|
||||
// 查询不会执行任何源码体;即使图有无限自递归,visited 去重也应使分析结束。
|
||||
assert_eq!(
|
||||
vm.call_effect(recursive).unwrap(),
|
||||
Some(CallEffect::ReadOnly)
|
||||
@@ -775,19 +830,25 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() {
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(vm.call_effect(recursive).unwrap(), None);
|
||||
// 归还测试拥有根,内部方法/签名/类型边由堆维护,避免重复 release。
|
||||
for object in [
|
||||
recursive, closure, code, cell, outer, wrapper, pure, impure, builtins, globals,
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 回收无根函数/全局/类型环,最终对象数应为零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 条件两路径必须汇合事实,目标不一致时不可保留旧的已知纯目标;恢复相同目标后可重新证明。
|
||||
fn effect_control_flow_joins_do_not_keep_a_stale_known_call_target() {
|
||||
// 独立 VM 隔离全局与方法重绑定,避免其他场景状态干扰快照证明。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 源码名查找环境,更新其中键可改变契约或调用图,不能把首次查询缓存为永久证明。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 明确声明 ReadOnly 的原生目标,作为可证明调用边。
|
||||
let pure = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"pure",
|
||||
@@ -799,6 +860,7 @@ fn effect_control_flow_joins_do_not_keep_a_stale_known_call_target() {
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 声明 Mutating/External 的目标,使源码整体调用效果无法证明 ReadOnly。
|
||||
let impure = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"impure",
|
||||
@@ -810,10 +872,12 @@ fn effect_control_flow_joins_do_not_keep_a_stale_known_call_target() {
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 两个分支初始指向同一 pure 身份,汇合可以保留该确定目标。
|
||||
for name in ["left", "right"] {
|
||||
vm.dict_set(globals, Value::Text(name.into()), Value::Object(pure))
|
||||
.unwrap();
|
||||
}
|
||||
// 手工指令使控制流和调用目标来源可精确观察,构造器先验证栈高/跳转。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadFast, 0),
|
||||
@@ -834,7 +898,9 @@ fn effect_control_flow_joins_do_not_keep_a_stale_known_call_target() {
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
// 代码数据只验证与分配,call_effect 不应实际执行其中指令。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 函数持有其代码/全局边,效果查询从当前载荷及环境读取快照。
|
||||
let function = vm
|
||||
.new_function("choose", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
@@ -851,21 +917,29 @@ fn effect_control_flow_joins_do_not_keep_a_stale_known_call_target() {
|
||||
vm.call_effect(function).unwrap(),
|
||||
Some(CallEffect::ReadOnly)
|
||||
);
|
||||
// 归还测试拥有根,内部方法/签名/类型边由堆维护,避免重复 release。
|
||||
for object in [function, code, pure, impure, globals] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 回收无根函数/全局/类型环,最终对象数应为零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 绑定接收者提供方法目标身份;类方法更新和实例字段遮蔽都必须影响效果查询。
|
||||
fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() {
|
||||
// 独立 VM 隔离全局与方法重绑定,避免其他场景状态干扰快照证明。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 显式类型自举根,用于实例与绑定方法测试,最终归还并收集类型环。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 源码名查找环境,更新其中键可改变契约或调用图,不能把首次查询缓存为永久证明。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// Receiver 名义类型保存 helper/check 方法,实例可另以字段遮蔽 helper。
|
||||
let definition = vm
|
||||
.new_type("Receiver", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 明确声明 ReadOnly 的原生目标,作为可证明调用边。
|
||||
let pure = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"helper",
|
||||
@@ -877,6 +951,7 @@ fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() {
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 声明 Mutating/External 的目标,使源码整体调用效果无法证明 ReadOnly。
|
||||
let impure = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"helper",
|
||||
@@ -888,6 +963,7 @@ fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() {
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 手工指令使控制流和调用目标来源可精确观察,构造器先验证栈高/跳转。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadFast, 0),
|
||||
@@ -905,14 +981,18 @@ fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() {
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
// 代码数据只验证与分配,call_effect 不应实际执行其中指令。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 函数持有其代码/全局边,效果查询从当前载荷及环境读取快照。
|
||||
let function = vm
|
||||
.new_function("check", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
vm.define_method(definition, "helper", pure).unwrap();
|
||||
vm.define_method(definition, "check", function).unwrap();
|
||||
// 实例提供实际接收者身份,绑定效果分析可据此查 helper,未绑定函数则未知。
|
||||
let receiver = vm.new_object().unwrap();
|
||||
vm.attach_type(receiver, definition).unwrap();
|
||||
// 绑定对象拥有函数和实际接收者边,分析可用 receiver 区分实例方法/字段。
|
||||
let Value::Object(bound) = vm.bind_attr(receiver, "check").unwrap() else {
|
||||
panic!("bound")
|
||||
};
|
||||
@@ -927,6 +1007,7 @@ fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() {
|
||||
assert_eq!(vm.call_effect(bound).unwrap(), None);
|
||||
vm.del_attr(receiver, "helper").unwrap();
|
||||
assert_eq!(vm.call_effect(bound).unwrap(), Some(CallEffect::ReadOnly));
|
||||
// 归还测试拥有根,内部方法/签名/类型边由堆维护,避免重复 release。
|
||||
for object in [
|
||||
bound,
|
||||
receiver,
|
||||
@@ -941,6 +1022,7 @@ fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() {
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 回收无根函数/全局/类型环,最终对象数应为零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user