Validate method substitution before definition updates and execution
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
| [builtins.rs](builtins.rs) | 无损 Number、Unicode 字符串、Dict 顺序与键、Cell、函数环境/共享代码、异常数据 |
|
||||
| [execution.rs](execution.rs) | wordcode 与校验、控制流、参数/默认值、嵌套帧、字节码函数创建/默认值/环境捕获、闭包、返回值存活、失败清理、限额 |
|
||||
| [types.rs](types.rs) | 名义身份、继承与实例关系、类型边保活/循环回收、只读类型元数据与外来句柄、显式Trait身份/继承/参数约束及后代更新原子性;统一函数分类、原生数量元数据/副作用前拒绝/能力更新/默认参数与返回类型错误根清理;仅远端执行 |
|
||||
| [method_contracts.rs](method_contracts.rs) | 源码/原生方法覆写参数逆变/返回协变、联合参数、效果、祖先替换原子性、动态名称重绑定/前向名称与体前拒绝/恢复及清理;仅远端执行 |
|
||||
| [native_context.rs](native_context.rs) | 原生作用域根、分配失败清理、原生签名/联合与可空类型/效果和委托返回根、绑定方法元数据、委托、只读结果分配/写入拒绝/依赖/校验与普通读取区分及失败恢复、弱引用过期及有界变更;仅远端执行 |
|
||||
|
||||
核心对象现由 objects 定义,本目录保留既有 endor_core 导入与 Vm 方法的兼容性检查。独立对象存储检查见 [objects/tests](../../objects/tests/README.md)。堆内部伪造句柄与计数溢出检查在 objects 的 src/heap.rs,公开示例由 src/lib.rs rustdoc 提供。文件职责见 [src](../src/README.md),检查方式和结果边界见 [验证](../../../docs/operations/validation.md)。本地不执行测试;存在测试文件不代表某次提交已通过。返回 [core](../README.md)。
|
||||
|
||||
@@ -0,0 +1,445 @@
|
||||
//! 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;
|
||||
|
||||
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(),
|
||||
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
|
||||
}
|
||||
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() {
|
||||
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 base_fn = source(
|
||||
&mut vm,
|
||||
globals,
|
||||
"Base",
|
||||
&["Base", "Number"],
|
||||
"Object",
|
||||
Value::Nil,
|
||||
);
|
||||
let base = class(&mut vm, "Base", roots.object, base_fn).unwrap();
|
||||
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();
|
||||
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",
|
||||
..
|
||||
})
|
||||
));
|
||||
let numeric = source(
|
||||
&mut vm,
|
||||
globals,
|
||||
"Numeric",
|
||||
&["Numeric", "Object"],
|
||||
"Number",
|
||||
Value::Number(0.into()),
|
||||
);
|
||||
let numeric_type = class(&mut vm, "Numeric", roots.object, numeric).unwrap();
|
||||
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",
|
||||
..
|
||||
})
|
||||
));
|
||||
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() {
|
||||
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 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();
|
||||
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)
|
||||
);
|
||||
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() {
|
||||
let mut vm = Vm::new().unwrap();
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
vm.set_source_type_checks(true).unwrap();
|
||||
let parent = vm
|
||||
.new_type("Result", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
let result_type = vm
|
||||
.new_type("ResultChild", parent, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
let other = vm
|
||||
.new_type("Other", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
let result = vm.new_object().unwrap();
|
||||
vm.attach_type(result, result_type).unwrap();
|
||||
let base_globals = vm.new_dict().unwrap();
|
||||
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();
|
||||
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();
|
||||
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();
|
||||
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)]
|
||||
struct Native(CallSignature);
|
||||
impl NativeCallable for Native {
|
||||
fn signature(&self) -> Option<CallSignature> {
|
||||
Some(self.0.clone())
|
||||
}
|
||||
fn invoke(&self, _: &mut NativeContext<'_>, _: &[Value]) -> Result<NativeResult, VmError> {
|
||||
Ok(NativeResult::Return(Value::Number(0.into())))
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() {
|
||||
let mut vm = Vm::new().unwrap();
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
vm.set_source_type_checks(true).unwrap();
|
||||
let base = vm
|
||||
.new_type("Base", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
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]);
|
||||
let base_fn = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"check",
|
||||
Native(signature),
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
vm.define_method(base, "check", base_fn).unwrap();
|
||||
let child = vm.new_type("Child", base, TypeConstructor::Record).unwrap();
|
||||
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 native effects",
|
||||
..
|
||||
})
|
||||
));
|
||||
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",
|
||||
..
|
||||
})
|
||||
));
|
||||
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() {
|
||||
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();
|
||||
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();
|
||||
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();
|
||||
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(_))
|
||||
));
|
||||
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()))
|
||||
);
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user