1029 lines
40 KiB
Rust
1029 lines
40 KiB
Rust
//! 方法替换规则的集成回归:使用源码/原生完整签名,观察定义提交与调用前后契约边界。
|
|
//! Compile here; execute only in the designated remote validation environment.
|
|
use endor_core::{
|
|
AnnotationType as T, CallEffect, CallSignature, CallType, CodeLayout, ContextNativeFunction,
|
|
EndorCode, Instruction, NativeCallable, NativeContext, NativeResult, ObjectRef, Opcode,
|
|
SourceSignature, TypeConstructor, Value, Vm, VmError,
|
|
};
|
|
use std::collections::BTreeMap;
|
|
|
|
/// 测试辅助,生成先写 globals.ran=42 再返回 result 的源码方法,附显式 owner/参数/返回注解。
|
|
fn source(
|
|
vm: &mut Vm,
|
|
globals: ObjectRef,
|
|
owner: &str,
|
|
parameters: &[&str],
|
|
returns: &str,
|
|
result: Value,
|
|
) -> ObjectRef {
|
|
// 手工指令保证源码体可观测,参数/局部槽按注解数量保持一致。
|
|
let code = EndorCode::from_instructions(
|
|
&[
|
|
Instruction::new(Opcode::LoadConst, 0),
|
|
Instruction::new(Opcode::StoreGlobal, 0),
|
|
Instruction::new(Opcode::LoadConst, 1),
|
|
Instruction::simple(Opcode::ReturnValue),
|
|
],
|
|
vec![Value::Number(42.into()), result],
|
|
CodeLayout {
|
|
parameters: (0..parameters.len()).map(|i| format!("p{i}")).collect(),
|
|
locals: (0..parameters.len()).map(|i| format!("p{i}")).collect(),
|
|
names: vec!["ran".into()],
|
|
..CodeLayout::default()
|
|
},
|
|
)
|
|
.unwrap()
|
|
.with_source_signature(SourceSignature {
|
|
parameters: parameters.iter().map(|s| (*s).into()).collect(),
|
|
return_type: returns.into(),
|
|
method_owner: Some(owner.into()),
|
|
})
|
|
.unwrap();
|
|
// 代码先分配拥有根,后续函数持有它的内部边。
|
|
let code = vm.new_code(code).unwrap();
|
|
// 辅助方法函数持有代码/全局,返回拥有根供类方法表引用。
|
|
let function = vm
|
|
.new_function("check", code, globals, vec![], vec![])
|
|
.unwrap();
|
|
// 归还辅助构造代码临时根,不释放函数内部拥有边。
|
|
vm.release(code).unwrap();
|
|
function
|
|
}
|
|
/// 测试辅助,以给定基类构造只声明 check 方法、无自有字段的源码类。
|
|
fn class(
|
|
vm: &mut Vm,
|
|
name: &str,
|
|
base: ObjectRef,
|
|
function: ObjectRef,
|
|
) -> Result<ObjectRef, VmError> {
|
|
vm.new_class(
|
|
name,
|
|
base,
|
|
vec![],
|
|
BTreeMap::from([("check".into(), function)]),
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
/// 创建派生类即检查参数可接受集合不能缩窄、返回集合不能扩大。
|
|
fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation() {
|
|
// 独立 VM 及名义类型图,避免全局重绑定与定义修改跨测试污染。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 显式自举 Object/Type 根,最终归还并收集其类型图环。
|
|
let roots = vm.bootstrap_types().unwrap();
|
|
// 普通 override 检查显式开启,不能仅依赖能力 opt-in 强制规则。
|
|
vm.set_source_type_checks(true).unwrap();
|
|
// 当前源码方法自己的注解解析环境,同时保存 ran 副作用标志。
|
|
let globals = vm.new_dict().unwrap();
|
|
// 父签名确定可接受参数集合和返回上界,方法自身类名由 owner 解析。
|
|
let base_fn = source(
|
|
&mut vm,
|
|
globals,
|
|
"Base",
|
|
&["Base", "Number"],
|
|
"Object",
|
|
Value::Nil,
|
|
);
|
|
// 创建基类并提交父方法,自身没有父同名覆盖约束。
|
|
let base = class(&mut vm, "Base", roots.object, base_fn).unwrap();
|
|
// 参数 Number 放宽到 Object,返回 Object 收窄到 Number,符合可替换关系。
|
|
let good_fn = source(
|
|
&mut vm,
|
|
globals,
|
|
"Good",
|
|
&["Good", "Object"],
|
|
"Number",
|
|
Value::Number(7.into()),
|
|
);
|
|
// 合法派生创建成功,保留其函数及基类引用。
|
|
let good = class(&mut vm, "Good", base, good_fn).unwrap();
|
|
// String 不能接纳父方法允许的 Number,所以即使返回约束合法也应拒绝。
|
|
let narrow = source(
|
|
&mut vm,
|
|
globals,
|
|
"Narrow",
|
|
&["Narrow", "String"],
|
|
"Number",
|
|
Value::Nil,
|
|
);
|
|
assert!(matches!(
|
|
class(&mut vm, "Narrow", base, narrow),
|
|
Err(VmError::OverrideViolation {
|
|
reason: "override narrows a parameter type",
|
|
..
|
|
})
|
|
));
|
|
// 另建父方法要求返回 Number,用于单独测试返回协变方向。
|
|
let numeric = source(
|
|
&mut vm,
|
|
globals,
|
|
"Numeric",
|
|
&["Numeric", "Object"],
|
|
"Number",
|
|
Value::Number(0.into()),
|
|
);
|
|
// 以返回 Number 的方法作为新基线,不和先前 Object 返回父方法混淆。
|
|
let numeric_type = class(&mut vm, "Numeric", roots.object, numeric).unwrap();
|
|
// 返回 Object 比 Number 更宽,派生实现不能让父调用者失去数值返回保证。
|
|
let broad = source(
|
|
&mut vm,
|
|
globals,
|
|
"Broad",
|
|
&["Broad", "Object"],
|
|
"Object",
|
|
Value::Nil,
|
|
);
|
|
assert!(matches!(
|
|
class(&mut vm, "Broad", numeric_type, broad),
|
|
Err(VmError::OverrideViolation {
|
|
reason: "override widens the return type",
|
|
..
|
|
})
|
|
));
|
|
// 归还每份测试拥有根,类方法/基类/签名内部边由 Heap 维护。
|
|
for object in [
|
|
good,
|
|
good_fn,
|
|
narrow,
|
|
broad,
|
|
numeric_type,
|
|
numeric,
|
|
base,
|
|
base_fn,
|
|
globals,
|
|
roots.object,
|
|
roots.type_type,
|
|
] {
|
|
vm.release(object).unwrap();
|
|
}
|
|
// 显式回收无根类型/函数图环,最后验证存活对象数为零。
|
|
vm.collect_cycles();
|
|
assert_eq!(vm.object_count(), 0);
|
|
}
|
|
|
|
#[test]
|
|
/// 父方法拟议更新必须检查现有覆盖后代,失败保持父版本及双方原方法表。
|
|
fn replacing_an_ancestor_validates_overriding_descendants_before_mutation() {
|
|
// 独立 VM 及名义类型图,避免全局重绑定与定义修改跨测试污染。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 显式自举 Object/Type 根,最终归还并收集其类型图环。
|
|
let roots = vm.bootstrap_types().unwrap();
|
|
// 普通 override 检查显式开启,不能仅依赖能力 opt-in 强制规则。
|
|
vm.set_source_type_checks(true).unwrap();
|
|
// 当前源码方法自己的注解解析环境,同时保存 ran 副作用标志。
|
|
let globals = vm.new_dict().unwrap();
|
|
// 父签名确定可接受参数集合和返回上界,方法自身类名由 owner 解析。
|
|
let base_fn = source(
|
|
&mut vm,
|
|
globals,
|
|
"Base",
|
|
&["Base", "Number"],
|
|
"Object",
|
|
Value::Nil,
|
|
);
|
|
// 创建基类并提交父方法,自身没有父同名覆盖约束。
|
|
let base = class(&mut vm, "Base", roots.object, base_fn).unwrap();
|
|
// 后代自己的签名需与当前父签名保持替换关系,即使后代已先创建。
|
|
let child_fn = source(
|
|
&mut vm,
|
|
globals,
|
|
"Child",
|
|
&["Child", "Number"],
|
|
"Number",
|
|
Value::Number(0.into()),
|
|
);
|
|
// 先创建当前合法后代,再对父方法提出可能使它失效的更新。
|
|
let child = class(&mut vm, "Child", base, child_fn).unwrap();
|
|
// 父参数从 Number 放宽到 Object 后,现有子方法仍只接收 Number,将不再可替换。
|
|
let replacement = source(
|
|
&mut vm,
|
|
globals,
|
|
"Base",
|
|
&["Base", "Object"],
|
|
"Object",
|
|
Value::Nil,
|
|
);
|
|
// 记录父定义版本,验证失败在真实载荷替换前发生。
|
|
let version = vm.change_version(base).unwrap();
|
|
assert!(matches!(
|
|
vm.define_method(base, "check", replacement),
|
|
Err(VmError::OverrideViolation { .. })
|
|
));
|
|
assert_eq!(vm.change_version(base).unwrap(), version);
|
|
assert_eq!(
|
|
vm.object_store().lookup_method(base, "check").unwrap(),
|
|
Some(base_fn)
|
|
);
|
|
assert_eq!(
|
|
vm.object_store().lookup_method(child, "check").unwrap(),
|
|
Some(child_fn)
|
|
);
|
|
// 归还每份测试拥有根,类方法/基类/签名内部边由 Heap 维护。
|
|
for object in [
|
|
replacement,
|
|
child,
|
|
child_fn,
|
|
base,
|
|
base_fn,
|
|
globals,
|
|
roots.object,
|
|
roots.type_type,
|
|
] {
|
|
vm.release(object).unwrap();
|
|
}
|
|
// 显式回收无根类型/函数图环,最后验证存活对象数为零。
|
|
vm.collect_cycles();
|
|
assert_eq!(vm.object_count(), 0);
|
|
}
|
|
|
|
#[test]
|
|
/// 类型名全局重绑定可破坏已有覆盖关系,调用前须重验并阻止体副作用,恢复绑定后可再调用。
|
|
fn global_rebinding_is_rechecked_before_the_source_body_and_recovers() {
|
|
// 独立 VM 及名义类型图,避免全局重绑定与定义修改跨测试污染。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 显式自举 Object/Type 根,最终归还并收集其类型图环。
|
|
let roots = vm.bootstrap_types().unwrap();
|
|
// 普通 override 检查显式开启,不能仅依赖能力 opt-in 强制规则。
|
|
vm.set_source_type_checks(true).unwrap();
|
|
// Result 基类型是父方法返回约束,ResultChild 初始满足其协变要求。
|
|
let parent = vm
|
|
.new_type("Result", roots.object, TypeConstructor::Record)
|
|
.unwrap();
|
|
// 派生返回类型起初是父返回类型子类,后续全局绑定将故意破坏该关系。
|
|
let result_type = vm
|
|
.new_type("ResultChild", parent, TypeConstructor::Record)
|
|
.unwrap();
|
|
// 无关类型可实例化但不是 Result 子类,不能作为合法协变返回类型。
|
|
let other = vm
|
|
.new_type("Other", roots.object, TypeConstructor::Record)
|
|
.unwrap();
|
|
// 实际返回对象先保留测试根,源码常量表也会持有它的边。
|
|
let result = vm.new_object().unwrap();
|
|
vm.attach_type(result, result_type).unwrap();
|
|
// 父方法独立环境固定 Result,子环境 Actual 重绑定不应改变父约束。
|
|
let base_globals = vm.new_dict().unwrap();
|
|
// 当前源码方法自己的注解解析环境,同时保存 ran 副作用标志。
|
|
let globals = vm.new_dict().unwrap();
|
|
vm.dict_set(
|
|
base_globals,
|
|
Value::Text("Result".into()),
|
|
Value::Object(parent),
|
|
)
|
|
.unwrap();
|
|
vm.dict_set(
|
|
globals,
|
|
Value::Text("Actual".into()),
|
|
Value::Object(result_type),
|
|
)
|
|
.unwrap();
|
|
vm.dict_set(globals, Value::Text("ran".into()), Value::Number(0.into()))
|
|
.unwrap();
|
|
// 父签名确定可接受参数集合和返回上界,方法自身类名由 owner 解析。
|
|
let base_fn = source(
|
|
&mut vm,
|
|
base_globals,
|
|
"Base",
|
|
&["Base"],
|
|
"Result",
|
|
Value::Nil,
|
|
);
|
|
// 创建基类并提交父方法,自身没有父同名覆盖约束。
|
|
let base = class(&mut vm, "Base", roots.object, base_fn).unwrap();
|
|
// 后代自己的签名需与当前父签名保持替换关系,即使后代已先创建。
|
|
let child_fn = source(
|
|
&mut vm,
|
|
globals,
|
|
"Child",
|
|
&["Child"],
|
|
"Actual",
|
|
Value::Object(result),
|
|
);
|
|
// 先创建当前合法后代,再对父方法提出可能使它失效的更新。
|
|
let child = class(&mut vm, "Child", base, child_fn).unwrap();
|
|
// 显式 self 为 Child 实例,调用时据其继承链解析各方法 own-class 注解。
|
|
let receiver = vm.new_object().unwrap();
|
|
vm.attach_type(receiver, child).unwrap();
|
|
vm.dict_set(globals, Value::Text("Actual".into()), Value::Object(other))
|
|
.unwrap();
|
|
assert!(matches!(
|
|
vm.call(child_fn, &[Value::Object(receiver)]),
|
|
Err(VmError::OverrideViolation { .. })
|
|
));
|
|
assert_eq!(
|
|
vm.dict_get(globals, &Value::Text("ran".into())).unwrap(),
|
|
Some(Value::Number(0.into()))
|
|
);
|
|
vm.dict_set(
|
|
globals,
|
|
Value::Text("Actual".into()),
|
|
Value::Object(result_type),
|
|
)
|
|
.unwrap();
|
|
// 恢复合法全局绑定后调用成功,结果根仍须与原测试对象根分别归还。
|
|
let value = vm.call(child_fn, &[Value::Object(receiver)]).unwrap();
|
|
assert_eq!(value, Value::Object(result));
|
|
// 只释放本次调用新增的结果根,源码常量及测试原根还在。
|
|
vm.release_value(value).unwrap();
|
|
// 归还每份测试拥有根,类方法/基类/签名内部边由 Heap 维护。
|
|
for object in [
|
|
receiver,
|
|
child,
|
|
child_fn,
|
|
base,
|
|
base_fn,
|
|
globals,
|
|
base_globals,
|
|
result,
|
|
other,
|
|
result_type,
|
|
parent,
|
|
roots.object,
|
|
roots.type_type,
|
|
] {
|
|
vm.release(object).unwrap();
|
|
}
|
|
// 显式回收无根类型/函数图环,最后验证存活对象数为零。
|
|
vm.collect_cycles();
|
|
assert_eq!(vm.object_count(), 0);
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
/// 无捕获测试回调,签名通过包装载荷 trace 保活,不手动按定义身份 retain。
|
|
struct Native(CallSignature);
|
|
impl NativeCallable for Native {
|
|
/// 包装载荷取得完整签名快照,定义引用由 ContextNativeFunction trace 持有。
|
|
fn signature(&self) -> Option<CallSignature> {
|
|
Some(self.0.clone())
|
|
}
|
|
/// 测试原生体只返回直接 Number;被拒绝场景主要检查声明的替换关系而非运行副作用。
|
|
fn invoke(&self, _: &mut NativeContext<'_>, _: &[Value]) -> Result<NativeResult, VmError> {
|
|
Ok(NativeResult::Return(Value::Number(0.into())))
|
|
}
|
|
}
|
|
#[test]
|
|
/// 原生覆盖不能丢弃父联合参数分支,也不能把已知 ReadOnly 扩大为 Mutating。
|
|
fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() {
|
|
// 独立 VM 及名义类型图,避免全局重绑定与定义修改跨测试污染。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 显式自举 Object/Type 根,最终归还并收集其类型图环。
|
|
let roots = vm.bootstrap_types().unwrap();
|
|
// 普通 override 检查显式开启,不能仅依赖能力 opt-in 强制规则。
|
|
vm.set_source_type_checks(true).unwrap();
|
|
// 原生场景的名义 Base,签名首项按它校验接收者。
|
|
let base = vm
|
|
.new_type("Base", roots.object, TypeConstructor::Record)
|
|
.unwrap();
|
|
// 先建立完整父签名,再把普通参数换为联合 Number | String。
|
|
let mut signature = CallSignature::exact(
|
|
vec![T::Nominal(base), T::Any],
|
|
T::Number,
|
|
CallEffect::ReadOnly,
|
|
);
|
|
// 保留两条可接受输入分支,子实现必须全部接纳。
|
|
signature.parameters[1] = CallType::Union(vec![T::Number, T::String]);
|
|
// ContextNativeFunction 签名快照持有 Base 定义,方法表又持函数边,最终可能成环。
|
|
let base_fn = vm
|
|
.allocate(ContextNativeFunction::new(
|
|
"check",
|
|
Native(signature),
|
|
vec![],
|
|
))
|
|
.unwrap();
|
|
vm.define_method(base, "check", base_fn).unwrap();
|
|
// 接收者由 Base 派生,子签名仍须接受声明类型实例。
|
|
let child = vm.new_type("Child", base, TypeConstructor::Record).unwrap();
|
|
// 输入 Any 足够宽,但 Mutating 扩大父 ReadOnly 效果,必须拒绝。
|
|
let effect_fn = vm
|
|
.allocate(ContextNativeFunction::new(
|
|
"effect",
|
|
Native(CallSignature::exact(
|
|
vec![T::Nominal(child), T::Any],
|
|
T::Number,
|
|
CallEffect::Mutating,
|
|
)),
|
|
vec![],
|
|
))
|
|
.unwrap();
|
|
assert!(matches!(
|
|
vm.define_method(child, "check", effect_fn),
|
|
Err(VmError::OverrideViolation {
|
|
reason: "override expands known effects",
|
|
..
|
|
})
|
|
));
|
|
// 效果保持只读却只接受 Number,丢失父 String 分支,同样拒绝。
|
|
let narrow_fn = vm
|
|
.allocate(ContextNativeFunction::new(
|
|
"narrow",
|
|
Native(CallSignature::exact(
|
|
vec![T::Nominal(child), T::Number],
|
|
T::Number,
|
|
CallEffect::ReadOnly,
|
|
)),
|
|
vec![],
|
|
))
|
|
.unwrap();
|
|
assert!(matches!(
|
|
vm.define_method(child, "check", narrow_fn),
|
|
Err(VmError::OverrideViolation {
|
|
reason: "override narrows a parameter type",
|
|
..
|
|
})
|
|
));
|
|
// 归还每份测试拥有根,类方法/基类/签名内部边由 Heap 维护。
|
|
for object in [
|
|
narrow_fn,
|
|
effect_fn,
|
|
child,
|
|
base_fn,
|
|
base,
|
|
roots.object,
|
|
roots.type_type,
|
|
] {
|
|
vm.release(object).unwrap();
|
|
}
|
|
// 显式回收无根类型/函数图环,最后验证存活对象数为零。
|
|
vm.collect_cycles();
|
|
assert_eq!(vm.object_count(), 0);
|
|
}
|
|
|
|
#[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,
|
|
"Base",
|
|
&["Base", "Number"],
|
|
"Number",
|
|
Value::Number(0.into()),
|
|
);
|
|
// 父定义先合法创建,后代契约需相对这份真实定义检查。
|
|
let base = class(&mut vm, "Base", roots.object, base_fn).unwrap();
|
|
// 子类使用尚未解析的 Later 名称,不代表可永久跳过参数替换验证。
|
|
let child_fn = source(
|
|
&mut vm,
|
|
globals,
|
|
"Child",
|
|
&["Child", "Later"],
|
|
"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!(
|
|
vm.call(
|
|
child_fn,
|
|
&[Value::Object(receiver), Value::Number(1.into())]
|
|
),
|
|
Err(VmError::UnknownAnnotation(_))
|
|
));
|
|
// Later 最终解析为独立名义类型,不接纳父参数 Number,故由未知错误变为覆盖不兼容。
|
|
let later = vm
|
|
.new_type("Later", roots.object, TypeConstructor::Record)
|
|
.unwrap();
|
|
vm.dict_set(globals, Value::Text("Later".into()), Value::Object(later))
|
|
.unwrap();
|
|
assert!(matches!(
|
|
vm.call(
|
|
child_fn,
|
|
&[Value::Object(receiver), Value::Number(1.into())]
|
|
),
|
|
Err(VmError::OverrideViolation { .. })
|
|
));
|
|
assert_eq!(
|
|
vm.dict_get(globals, &Value::Text("ran".into())).unwrap(),
|
|
Some(Value::Number(0.into()))
|
|
);
|
|
// 归还测试拥有根,内部方法/签名/类型边由堆维护,避免重复 release。
|
|
for object in [
|
|
receiver,
|
|
child,
|
|
child_fn,
|
|
base,
|
|
base_fn,
|
|
later,
|
|
globals,
|
|
roots.object,
|
|
roots.type_type,
|
|
] {
|
|
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),
|
|
Instruction::simple(Opcode::ReturnValue),
|
|
],
|
|
vec![],
|
|
CodeLayout {
|
|
parameters: vec!["self".into(), "value".into()],
|
|
locals: vec!["self".into(), "value".into()],
|
|
..CodeLayout::default()
|
|
},
|
|
)
|
|
.unwrap()
|
|
.with_source_signature(SourceSignature {
|
|
parameters: vec!["PureBase".into(), "Number".into()],
|
|
return_type: "Number".into(),
|
|
method_owner: Some("PureBase".into()),
|
|
})
|
|
.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",
|
|
Native(CallSignature::exact(
|
|
vec![T::Any, T::Number],
|
|
T::Number,
|
|
effect,
|
|
)),
|
|
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,
|
|
Err(VmError::OverrideViolation {
|
|
reason: "override expands known effects",
|
|
..
|
|
})
|
|
));
|
|
assert_eq!(vm.change_version(child).unwrap(), version);
|
|
} else {
|
|
update.unwrap();
|
|
}
|
|
assert_eq!(
|
|
vm.call_effect(function).unwrap(),
|
|
(effect != CallEffect::Delegated).then_some(effect)
|
|
);
|
|
functions.push(function);
|
|
}
|
|
// 归还测试拥有根,内部方法/签名/类型边由堆维护,避免重复 release。
|
|
for object in functions.into_iter().chain([
|
|
bound,
|
|
receiver,
|
|
child,
|
|
base,
|
|
source,
|
|
code,
|
|
globals,
|
|
roots.object,
|
|
roots.type_type,
|
|
]) {
|
|
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),
|
|
Instruction::new(Opcode::StoreFast, 0),
|
|
Instruction::new(Opcode::LoadFast, 0),
|
|
Instruction::simple(Opcode::ReturnValue),
|
|
],
|
|
vec![],
|
|
CodeLayout {
|
|
parameters: vec!["target".into(), "value".into()],
|
|
locals: vec!["target".into(), "value".into()],
|
|
..CodeLayout::default()
|
|
},
|
|
)
|
|
.unwrap();
|
|
assert!(!assignment.is_statically_read_only());
|
|
// StoreDeref 修改已有捕获 Cell,必须从本地只读白名单排除。
|
|
let captured = EndorCode::from_instructions(
|
|
&[
|
|
Instruction::new(Opcode::LoadConst, 0),
|
|
Instruction::new(Opcode::StoreDeref, 0),
|
|
Instruction::new(Opcode::LoadConst, 0),
|
|
Instruction::simple(Opcode::ReturnValue),
|
|
],
|
|
vec![Value::Nil],
|
|
CodeLayout {
|
|
free_variables: vec!["captured".into()],
|
|
..CodeLayout::default()
|
|
},
|
|
)
|
|
.unwrap();
|
|
assert!(!captured.is_statically_read_only());
|
|
// 动态 Call 没有本地目标证明,is_statically_read_only 不能直接通过。
|
|
let call = EndorCode::from_instructions(
|
|
&[
|
|
Instruction::new(Opcode::LoadGlobal, 0),
|
|
Instruction::simple(Opcode::PushNull),
|
|
Instruction::new(Opcode::Call, 0),
|
|
Instruction::simple(Opcode::ReturnValue),
|
|
],
|
|
vec![],
|
|
CodeLayout {
|
|
names: vec!["dynamic".into()],
|
|
..CodeLayout::default()
|
|
},
|
|
)
|
|
.unwrap();
|
|
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),
|
|
Instruction::simple(Opcode::PushNull),
|
|
Instruction::new(Opcode::Call, 0),
|
|
Instruction::simple(Opcode::ReturnValue),
|
|
],
|
|
vec![],
|
|
CodeLayout {
|
|
names: vec![name.into()],
|
|
..CodeLayout::default()
|
|
},
|
|
)
|
|
.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,
|
|
Value::Text("__builtins__".into()),
|
|
Value::Object(builtins),
|
|
)
|
|
.unwrap();
|
|
// 明确声明 ReadOnly 的原生目标,作为可证明调用边。
|
|
let pure = vm
|
|
.allocate(ContextNativeFunction::new(
|
|
"pure",
|
|
Native(CallSignature::exact(
|
|
vec![],
|
|
T::Number,
|
|
CallEffect::ReadOnly,
|
|
)),
|
|
vec![],
|
|
))
|
|
.unwrap();
|
|
// 声明 Mutating/External 的目标,使源码整体调用效果无法证明 ReadOnly。
|
|
let impure = vm
|
|
.allocate(ContextNativeFunction::new(
|
|
"impure",
|
|
Native(CallSignature::exact(
|
|
vec![],
|
|
T::Number,
|
|
CallEffect::Mutating,
|
|
)),
|
|
vec![],
|
|
))
|
|
.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,
|
|
Value::Text("wrapper".into()),
|
|
Value::Object(wrapper),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(vm.call_effect(outer).unwrap(), Some(CallEffect::ReadOnly));
|
|
vm.dict_set(globals, Value::Text("target".into()), Value::Object(impure))
|
|
.unwrap();
|
|
assert_eq!(vm.call_effect(outer).unwrap(), None);
|
|
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),
|
|
Instruction::simple(Opcode::PushNull),
|
|
Instruction::new(Opcode::Call, 0),
|
|
Instruction::simple(Opcode::ReturnValue),
|
|
],
|
|
vec![],
|
|
CodeLayout {
|
|
free_variables: vec!["target".into()],
|
|
..CodeLayout::default()
|
|
},
|
|
)
|
|
.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();
|
|
assert_eq!(vm.call_effect(closure).unwrap(), Some(CallEffect::ReadOnly));
|
|
vm.cell_set(cell, Value::Object(impure)).unwrap();
|
|
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,
|
|
Value::Text("recursive".into()),
|
|
Value::Object(recursive),
|
|
)
|
|
.unwrap();
|
|
// 查询不会执行任何源码体;即使图有无限自递归,visited 去重也应使分析结束。
|
|
assert_eq!(
|
|
vm.call_effect(recursive).unwrap(),
|
|
Some(CallEffect::ReadOnly)
|
|
);
|
|
vm.dict_set(
|
|
globals,
|
|
Value::Text("recursive".into()),
|
|
Value::Object(impure),
|
|
)
|
|
.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",
|
|
Native(CallSignature::exact(
|
|
vec![],
|
|
T::Number,
|
|
CallEffect::ReadOnly,
|
|
)),
|
|
vec![],
|
|
))
|
|
.unwrap();
|
|
// 声明 Mutating/External 的目标,使源码整体调用效果无法证明 ReadOnly。
|
|
let impure = vm
|
|
.allocate(ContextNativeFunction::new(
|
|
"impure",
|
|
Native(CallSignature::exact(
|
|
vec![],
|
|
T::Number,
|
|
CallEffect::External,
|
|
)),
|
|
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),
|
|
Instruction::new(Opcode::PopJumpIfFalse, 2),
|
|
Instruction::new(Opcode::LoadGlobal, 0),
|
|
Instruction::new(Opcode::JumpForward, 1),
|
|
Instruction::new(Opcode::LoadGlobal, 1),
|
|
Instruction::simple(Opcode::PushNull),
|
|
Instruction::new(Opcode::Call, 0),
|
|
Instruction::simple(Opcode::ReturnValue),
|
|
],
|
|
vec![],
|
|
CodeLayout {
|
|
parameters: vec!["condition".into()],
|
|
locals: vec!["condition".into()],
|
|
names: vec!["left".into(), "right".into()],
|
|
..CodeLayout::default()
|
|
},
|
|
)
|
|
.unwrap();
|
|
// 代码数据只验证与分配,call_effect 不应实际执行其中指令。
|
|
let code = vm.new_code(code).unwrap();
|
|
// 函数持有其代码/全局边,效果查询从当前载荷及环境读取快照。
|
|
let function = vm
|
|
.new_function("choose", code, globals, vec![], vec![])
|
|
.unwrap();
|
|
assert_eq!(
|
|
vm.call_effect(function).unwrap(),
|
|
Some(CallEffect::ReadOnly)
|
|
);
|
|
vm.dict_set(globals, Value::Text("right".into()), Value::Object(impure))
|
|
.unwrap();
|
|
assert_eq!(vm.call_effect(function).unwrap(), None);
|
|
vm.dict_set(globals, Value::Text("right".into()), Value::Object(pure))
|
|
.unwrap();
|
|
assert_eq!(
|
|
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",
|
|
Native(CallSignature::exact(
|
|
vec![T::Any],
|
|
T::Number,
|
|
CallEffect::ReadOnly,
|
|
)),
|
|
vec![],
|
|
))
|
|
.unwrap();
|
|
// 声明 Mutating/External 的目标,使源码整体调用效果无法证明 ReadOnly。
|
|
let impure = vm
|
|
.allocate(ContextNativeFunction::new(
|
|
"helper",
|
|
Native(CallSignature::exact(
|
|
vec![T::Any],
|
|
T::Number,
|
|
CallEffect::Mutating,
|
|
)),
|
|
vec![],
|
|
))
|
|
.unwrap();
|
|
// 手工指令使控制流和调用目标来源可精确观察,构造器先验证栈高/跳转。
|
|
let code = EndorCode::from_instructions(
|
|
&[
|
|
Instruction::new(Opcode::LoadFast, 0),
|
|
Instruction::new(Opcode::LoadAttr, 0),
|
|
Instruction::simple(Opcode::PushNull),
|
|
Instruction::new(Opcode::Call, 0),
|
|
Instruction::simple(Opcode::ReturnValue),
|
|
],
|
|
vec![],
|
|
CodeLayout {
|
|
parameters: vec!["self".into()],
|
|
locals: vec!["self".into()],
|
|
names: vec!["helper".into()],
|
|
..CodeLayout::default()
|
|
},
|
|
)
|
|
.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")
|
|
};
|
|
assert_eq!(vm.call_effect(function).unwrap(), None);
|
|
assert_eq!(vm.call_effect(bound).unwrap(), Some(CallEffect::ReadOnly));
|
|
vm.define_method(definition, "helper", impure).unwrap();
|
|
assert_eq!(vm.call_effect(bound).unwrap(), None);
|
|
vm.define_method(definition, "helper", pure).unwrap();
|
|
assert_eq!(vm.call_effect(bound).unwrap(), Some(CallEffect::ReadOnly));
|
|
vm.set_attr(receiver, "helper", Value::Object(impure))
|
|
.unwrap();
|
|
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,
|
|
function,
|
|
code,
|
|
pure,
|
|
impure,
|
|
definition,
|
|
globals,
|
|
roots.object,
|
|
roots.type_type,
|
|
] {
|
|
vm.release(object).unwrap();
|
|
}
|
|
// 回收无根函数/全局/类型环,最终对象数应为零。
|
|
vm.collect_cycles();
|
|
assert_eq!(vm.object_count(), 0);
|
|
}
|