docs(core): explain native signature and delegated result regressions
This commit is contained in:
@@ -382,23 +382,32 @@ fn annotation_reads_remain_dependencies_without_becoming_program_content_reads()
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TypedNative {
|
||||
/// 构造时快照的完整签名,包装载荷负责 trace 类型定义。
|
||||
signature: endor_core::CallSignature,
|
||||
/// VM 外共享调用计数,clone Arc 不涉及托管对象根。
|
||||
calls: std::sync::Arc<std::sync::atomic::AtomicUsize>,
|
||||
/// 可选委托目标借用身份,包装载荷 captures 必须持有对应强边。
|
||||
delegate: Option<ObjectRef>,
|
||||
/// 无委托时是否分配新对象,否则回传最后参数/Nil。
|
||||
allocate_result: bool,
|
||||
}
|
||||
impl NativeCallable for TypedNative {
|
||||
/// ContextNativeFunction 快照此完整元数据并 trace 定义边;clone 本身不增加外部根。
|
||||
fn signature(&self) -> Option<endor_core::CallSignature> {
|
||||
Some(self.signature.clone())
|
||||
}
|
||||
/// 测试原生分派入口,回调内借用参数/目标,NativeContext 校验并移交 Return 或 Invoke 输出根。
|
||||
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
|
||||
// 计数器只在真实回调进入后增加,测试据此区分预检失败与返回后失败。
|
||||
self.calls.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
// 委托不递归执行 VM,返回目标/参数借用表示给统一准备循环。
|
||||
if let Some(function) = self.delegate {
|
||||
return Ok(NativeResult::Invoke {
|
||||
function,
|
||||
arguments: args.to_vec(),
|
||||
});
|
||||
}
|
||||
// 可选择产生新对象或回传最后参数,分别检验临时根清理和借用结果移交。
|
||||
Ok(NativeResult::Return(if self.allocate_result {
|
||||
Value::Object(cx.new_object()?)
|
||||
} else {
|
||||
@@ -408,14 +417,18 @@ impl NativeCallable for TypedNative {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 参数错误必须在 calls 增加前拒绝,错误对象结果由上下文回收,不留下临时分配。
|
||||
fn native_types_preflight_arguments_and_clean_wrong_result_roots() {
|
||||
use endor_core::{AnnotationType as T, CallEffect, CallSignature};
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
// 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。
|
||||
let calls = Arc::new(AtomicUsize::new(0));
|
||||
// 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。
|
||||
let function = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"typed",
|
||||
@@ -442,12 +455,15 @@ fn native_types_preflight_arguments_and_clean_wrong_result_roots() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 多层原生包装委托同一源码帧,最终结果必须满足链上全部返回约束,不额外消耗源码帧深度。
|
||||
fn delegated_source_results_obey_every_native_contract_without_extra_frames() {
|
||||
use endor_core::{
|
||||
AnnotationType as T, CallEffect, CallSignature, CodeLayout, EndorCode, Instruction, Opcode,
|
||||
};
|
||||
use std::sync::{Arc, atomic::AtomicUsize};
|
||||
// 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 最小源码函数返回固定常量,区分原生包装约束与源码帧自身的执行。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadConst, 0),
|
||||
@@ -457,12 +473,17 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() {
|
||||
CodeLayout::default(),
|
||||
)
|
||||
.unwrap();
|
||||
// 托管代码拥有常量强边,源码函数还会持有代码边。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 源码环境有分配根,函数持有它的内部边。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 源码函数返回文本或 Nil,作为后续返回契约一致/不一致的终点。
|
||||
let source = vm
|
||||
.new_function("source", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
// VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。
|
||||
let calls = Arc::new(AtomicUsize::new(0));
|
||||
// 内层包装允许 String,与源码文本结果相符。
|
||||
let inner = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"inner",
|
||||
@@ -475,6 +496,7 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() {
|
||||
vec![source],
|
||||
))
|
||||
.unwrap();
|
||||
// 外层却要求 Number,必须在源码最终返回时拒绝,即使内层已通过。
|
||||
let outer = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"outer",
|
||||
@@ -487,6 +509,7 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() {
|
||||
vec![inner],
|
||||
))
|
||||
.unwrap();
|
||||
// 只允许一层源码帧,验证多层原生委托不会伪造额外源码帧。
|
||||
let limits = ExecutionLimits {
|
||||
max_frames: 1,
|
||||
max_instructions: 100,
|
||||
@@ -509,26 +532,34 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() {
|
||||
),
|
||||
Err(VmError::ExecutionLimit("frames"))
|
||||
));
|
||||
// 归还测试每份拥有根,签名/捕获/方法内部边由 Heap 处理。
|
||||
for object in [outer, inner, source, globals, code] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 清理可能由类型/签名/方法持有关系形成的无根环,断言堆归零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 签名独立 trace 名义类型定义,绑定后仍使用真实目标签名并扣除 self。
|
||||
fn signatures_trace_nominal_types_and_bound_receivers_use_the_actual_contract() {
|
||||
use endor_core::{AnnotationType as T, CallEffect, CallSignature, TypeConstructor};
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
// 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 显式自举 Object/Type,名义定义在同一对象图中受计数管理。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 名义定义用于签名约束,归还测试根后仍需由签名边保活。
|
||||
let definition = vm
|
||||
.new_type("Receiver", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。
|
||||
let calls = Arc::new(AtomicUsize::new(0));
|
||||
// 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。
|
||||
let function = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"echo",
|
||||
@@ -545,11 +576,15 @@ fn signatures_trace_nominal_types_and_bound_receivers_use_the_actual_contract()
|
||||
vec![],
|
||||
))
|
||||
.unwrap(); // Metadata alone traces the definition.
|
||||
// 弱身份不保活定义,清理后升级 None 用于检查无隐藏根。
|
||||
let weak = vm.downgrade(definition).unwrap();
|
||||
vm.define_method(definition, "echo", function).unwrap();
|
||||
// 实例持有定义类型边,绑定方法再持有实例边。
|
||||
let receiver = vm.new_object().unwrap();
|
||||
vm.attach_type(receiver, definition).unwrap();
|
||||
// 归还原始定义根;其后存活来自实例/签名或执行检查根。
|
||||
vm.release(definition).unwrap();
|
||||
// bind_attr 交付绑定对象拥有根,实际调用会先补接收者。
|
||||
let Value::Object(bound) = vm.bind_attr(receiver, "echo").unwrap() else {
|
||||
panic!("bound method")
|
||||
};
|
||||
@@ -563,24 +598,31 @@ fn signatures_trace_nominal_types_and_bound_receivers_use_the_actual_contract()
|
||||
vm.call(bound, &[Value::Number(9.into())]).unwrap(),
|
||||
Value::Number(9.into())
|
||||
);
|
||||
// 归还测试每份拥有根,签名/捕获/方法内部边由 Heap 处理。
|
||||
for object in [bound, receiver, function, roots.object, roots.type_type] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 清理可能由类型/签名/方法持有关系形成的无根环,断言堆归零。
|
||||
vm.collect_cycles();
|
||||
assert!(vm.upgrade(weak).unwrap().is_none());
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 只读域中 Mutating/External 在回调前拒绝,普通调用仍允许进入。
|
||||
fn declared_host_effects_are_rejected_before_read_only_dispatch() {
|
||||
use endor_core::{AnnotationType as T, CallEffect, CallSignature};
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
// 两个不可在只读域调用的声明分别测试,不能只覆盖其中一个。
|
||||
for effect in [CallEffect::Mutating, CallEffect::External] {
|
||||
// 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。
|
||||
let calls = Arc::new(AtomicUsize::new(0));
|
||||
// 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。
|
||||
let function = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"effect",
|
||||
@@ -605,12 +647,16 @@ fn declared_host_effects_are_rejected_before_read_only_dispatch() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 旧式函数也执行完整签名/可变参数/返回检查,独立 arity 与签名不一致必须拒绝。
|
||||
fn legacy_native_signatures_check_optional_variadic_types_and_metadata() {
|
||||
use endor_core::{AnnotationType as T, CallArity, CallEffect, CallSignature, NativeFunction};
|
||||
/// 旧式回调返回最后一个借用参数,空参数返回 Nil;拥有根仍由 VM 补充。
|
||||
fn echo(args: &[Value]) -> Result<Value, VmError> {
|
||||
Ok(args.last().cloned().unwrap_or(Value::Nil))
|
||||
}
|
||||
// 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。
|
||||
let function = vm
|
||||
.allocate(
|
||||
NativeFunction::new("legacy", echo).with_signature(CallSignature {
|
||||
@@ -634,6 +680,7 @@ fn legacy_native_signatures_check_optional_variadic_types_and_metadata() {
|
||||
assert!(
|
||||
matches!(vm.call(function, &[]), Err(VmError::TypeMismatch { position, .. }) if position == "return")
|
||||
);
|
||||
// 故意同时配置互相矛盾的签名和独立 arity,查询/调用都应报元数据错误。
|
||||
let invalid = vm
|
||||
.allocate(
|
||||
NativeFunction::new("invalid", echo)
|
||||
@@ -654,14 +701,19 @@ fn legacy_native_signatures_check_optional_variadic_types_and_metadata() {
|
||||
|
||||
#[derive(Debug)]
|
||||
struct EraseThenDelegate {
|
||||
/// 构造时快照的完整签名,包装载荷负责 trace 类型定义。
|
||||
signature: endor_core::CallSignature,
|
||||
/// 被委托源码函数,由包装载荷 captures 保活至回调中输出保护接管。
|
||||
target: ObjectRef,
|
||||
}
|
||||
impl NativeCallable for EraseThenDelegate {
|
||||
/// ContextNativeFunction 快照此完整元数据并 trace 定义边;clone 本身不增加外部根。
|
||||
fn signature(&self) -> Option<endor_core::CallSignature> {
|
||||
Some(self.signature.clone())
|
||||
}
|
||||
/// 测试原生分派入口,回调内借用参数/目标,NativeContext 校验并移交 Return 或 Invoke 输出根。
|
||||
fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result<NativeResult, VmError> {
|
||||
// 将当前函数换为普通 Record,移除签名及捕获边,迫使返回约束靠执行器自己的根存活。
|
||||
cx.replace_payload(cx.callee(), endor_core::Record)?;
|
||||
Ok(NativeResult::Invoke {
|
||||
function: self.target,
|
||||
@@ -671,17 +723,23 @@ impl NativeCallable for EraseThenDelegate {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 回调替换自己的载荷删除签名边后,委托链独立持有返回类型根,诊断完成后才释放。
|
||||
fn delegated_return_type_survives_removal_of_the_original_signature_edges() {
|
||||
use endor_core::{
|
||||
AnnotationType as T, CallEffect, CallSignature, CodeLayout, EndorCode, Instruction, Opcode,
|
||||
TypeConstructor,
|
||||
};
|
||||
// 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// 显式自举 Object/Type,名义定义在同一对象图中受计数管理。
|
||||
let roots = vm.bootstrap_types().unwrap();
|
||||
// 名义定义用于签名约束,归还测试根后仍需由签名边保活。
|
||||
let definition = vm
|
||||
.new_type("Transient", roots.object, TypeConstructor::Record)
|
||||
.unwrap();
|
||||
// 弱身份不保活定义,清理后升级 None 用于检查无隐藏根。
|
||||
let weak = vm.downgrade(definition).unwrap();
|
||||
// 最小源码函数返回固定常量,区分原生包装约束与源码帧自身的执行。
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadConst, 0),
|
||||
@@ -691,13 +749,19 @@ fn delegated_return_type_survives_removal_of_the_original_signature_edges() {
|
||||
CodeLayout::default(),
|
||||
)
|
||||
.unwrap();
|
||||
// 托管代码拥有常量强边,源码函数还会持有代码边。
|
||||
let code = vm.new_code(code).unwrap();
|
||||
// 源码环境有分配根,函数持有它的内部边。
|
||||
let globals = vm.new_dict().unwrap();
|
||||
// 源码函数返回文本或 Nil,作为后续返回契约一致/不一致的终点。
|
||||
let source = vm
|
||||
.new_function("source", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
// 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。
|
||||
let mut signature = CallSignature::exact(vec![], T::Any, CallEffect::Mutating);
|
||||
// 联合第一项引用将失去原载荷保活的 Transient,错误诊断仍应能读取其名字。
|
||||
signature.returns = endor_core::CallType::Union(vec![T::Nominal(definition), T::Number]);
|
||||
// 包装函数显式 captures 持有委托目标,输出目标根由 NativeContext 另取得。
|
||||
let wrapper = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"erased",
|
||||
@@ -708,11 +772,13 @@ fn delegated_return_type_survives_removal_of_the_original_signature_edges() {
|
||||
vec![source],
|
||||
))
|
||||
.unwrap();
|
||||
// 归还原始定义根;其后存活来自实例/签名或执行检查根。
|
||||
vm.release(definition).unwrap();
|
||||
assert!(
|
||||
matches!(vm.call(wrapper, &[]), Err(VmError::TypeMismatch { function, expected, .. }) if function == "erased" && expected == "Transient | Number")
|
||||
);
|
||||
assert!(vm.upgrade(weak).unwrap().is_none());
|
||||
// 归还测试每份拥有根,签名/捕获/方法内部边由 Heap 处理。
|
||||
for object in [
|
||||
wrapper,
|
||||
source,
|
||||
@@ -723,22 +789,28 @@ fn delegated_return_type_survives_removal_of_the_original_signature_edges() {
|
||||
] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
// 清理可能由类型/签名/方法持有关系形成的无根环,断言堆归零。
|
||||
vm.collect_cycles();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 联合各分支可接纳,非法后续类型及空联合不得被 Any/提前匹配掩盖。
|
||||
fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_metadata() {
|
||||
use endor_core::{AnnotationType as T, CallEffect, CallSignature, CallType};
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
// 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。
|
||||
let calls = Arc::new(AtomicUsize::new(0));
|
||||
// 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。
|
||||
let mut signature = CallSignature::exact(vec![T::Any], T::Any, CallEffect::ReadOnly);
|
||||
signature.parameters[0] = CallType::Union(vec![T::String, T::Bool]);
|
||||
signature.returns = signature.parameters[0].clone();
|
||||
// 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。
|
||||
let function = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"union",
|
||||
@@ -751,6 +823,7 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 每个合法联合/nullable 分支都实际调用一次,计数器验证非法输入未进入。
|
||||
for value in [
|
||||
Value::Text("capture".into()),
|
||||
Value::Bool(true),
|
||||
@@ -765,6 +838,7 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met
|
||||
matches!(vm.call(function, &[Value::Number(3.into())]), Err(VmError::TypeMismatch { expected, .. }) if expected == "String | Bool")
|
||||
);
|
||||
assert_eq!(calls.load(Ordering::SeqCst), 3);
|
||||
// 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。
|
||||
let mut signature = CallSignature::exact(vec![T::Any], T::Any, CallEffect::ReadOnly);
|
||||
signature.parameters[0] = CallType::nullable(T::Number);
|
||||
signature.returns = signature.parameters[0].clone();
|
||||
@@ -782,15 +856,18 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
// 每个合法联合/nullable 分支都实际调用一次,计数器验证非法输入未进入。
|
||||
for value in [Value::Nil, Value::Number(2.into())] {
|
||||
assert_eq!(
|
||||
vm.call(function, std::slice::from_ref(&value)).unwrap(),
|
||||
value
|
||||
);
|
||||
}
|
||||
// 普通 Record 不能用作 Nominal 的类型定义,即使前分支 Any 已能匹配也须拒绝。
|
||||
let record = vm.new_object().unwrap();
|
||||
// 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。
|
||||
let mut signature = CallSignature::exact(vec![T::Any], T::Any, CallEffect::ReadOnly);
|
||||
// A matching first branch must not hide a later invalid nominal definition.
|
||||
// 完整联合先验证全部定义,不能因第一项 Any 匹配而跳过后面的非法类型句柄。
|
||||
signature.parameters[0] = CallType::Union(vec![T::Any, T::Nominal(record)]);
|
||||
vm.replace_payload(
|
||||
function,
|
||||
@@ -808,6 +885,7 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met
|
||||
.unwrap();
|
||||
assert!(vm.call(function, &[Value::Nil]).is_err());
|
||||
assert_eq!(calls.load(Ordering::SeqCst), 5);
|
||||
// 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。
|
||||
let mut signature = CallSignature::exact(vec![], T::Any, CallEffect::ReadOnly);
|
||||
signature.returns = CallType::Union(vec![]);
|
||||
vm.replace_payload(
|
||||
@@ -832,15 +910,20 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Delegated 包装器能进入只读域,但实际目标仍在同域核对效果,目标换为纯函数后可通过。
|
||||
fn delegated_effects_keep_the_original_read_only_scope_and_check_the_actual_target() {
|
||||
use endor_core::{AnnotationType as T, CallEffect, CallSignature};
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
// 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。
|
||||
let mut vm = Vm::new().unwrap();
|
||||
// VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。
|
||||
let calls = Arc::new(AtomicUsize::new(0));
|
||||
// 目标独立计数,用于证明包装器已经执行但目标在副作用前被效果检查拦截。
|
||||
let target_calls = Arc::new(AtomicUsize::new(0));
|
||||
// 初始目标声明 Mutating,在只读委托链中不允许执行;随后替换成 ReadOnly。
|
||||
let target = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"target",
|
||||
@@ -853,6 +936,7 @@ fn delegated_effects_keep_the_original_read_only_scope_and_check_the_actual_targ
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
// 包装函数显式 captures 持有委托目标,输出目标根由 NativeContext 另取得。
|
||||
let wrapper = vm
|
||||
.allocate(ContextNativeFunction::new(
|
||||
"wrapper",
|
||||
@@ -892,6 +976,7 @@ fn delegated_effects_keep_the_original_read_only_scope_and_check_the_actual_targ
|
||||
.0,
|
||||
Value::Nil
|
||||
);
|
||||
// 归还测试每份拥有根,签名/捕获/方法内部边由 Heap 处理。
|
||||
for object in [wrapper, target] {
|
||||
vm.release(object).unwrap();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user