Files
endorphin-aster-vm/crates/core/tests/runtime.rs
T

627 lines
27 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Rust 测试框架调用的 VM/Store 基础协议集成场景,不依赖源码编译器。
//! Resource 用 Rust Drop 计数观察实际析构,Number/ReflectedOrdering 验证宿主载荷协议覆盖和反向分派。
//! 每个测试独立建 VM;未显式归还的根及未收集环最终仍须由整个 VM 的销毁清理。
use std::any::Any;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use endor_core::{CompareOp, NativeFunction, ObjectPayload, ObjectRef, Record, Value, Vm, VmError};
#[derive(Debug)]
/// 生命周期场景的宿主资源:以 Drop 计数观测真正销毁,edges 同时让载荷参与托管图。
struct Resource {
/// VM 外观察实际析构次数的共享计数器。
drops: Arc<AtomicUsize>,
/// 分配时由 Heap 快照持有的内部强边,含重复项。
edges: Vec<ObjectRef>,
}
impl Drop for Resource {
/// Resource 真正销毁时累加共享计数,区分句柄复制/归还与实际 Rust 资源释放。
fn drop(&mut self) {
self.drops.fetch_add(1, Ordering::SeqCst);
}
}
impl ObjectPayload for Resource {
/// Store/比较协议的具体载荷下转型入口,不依赖语言名义类型。
fn as_any(&self) -> &dyn Any {
self
}
/// 错误与诊断显示的类别标签。
fn kind_name(&self) -> &'static str {
"resource"
}
/// Heap 分配时枚举载荷拥有边;重复项必须逐项计数,不能去重。
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
// 每个槽都是独立拥有边,不能因为身份相同而合并计数。
for reference in &self.edges {
visit(*reference);
}
}
/// Resource 允许公共属性表,使测试能组合载荷边和属性边成环。
fn allows_attributes(&self) -> bool {
true
}
}
#[derive(Debug)]
/// 协议覆盖场景的自定义整数载荷,与内置 EndorNumberObject 分开,专门验证 Store 的动态分派。
struct Number(i64);
impl ObjectPayload for Number {
/// Store/比较协议的具体载荷下转型入口,不依赖语言名义类型。
fn as_any(&self) -> &dyn Any {
self
}
/// 错误与诊断显示的类别标签。
fn kind_name(&self) -> &'static str {
"number"
}
/// 测试 Number 用值覆盖默认身份表示,供 str 默认回退调用。
fn repr(&self, _identity: ObjectRef) -> Result<String, VmError> {
Ok(self.0.to_string())
}
/// 测试 Number 仅零为假,不使用默认对象恒真规则。
fn is_true(&self) -> Result<bool, VmError> {
Ok(self.0 != 0)
}
/// Store::hash 调用此载荷协议,按整数值生成哈希;负数按 Rust as 转为 u64。
fn hash(&self, _identity: ObjectRef) -> Result<u64, VmError> {
Ok(self.0 as u64)
}
/// Store 先正向后反向调用;返回 None 是未实现,Some(false) 则不再回退。
fn compare(
&self,
other: &dyn ObjectPayload,
operation: CompareOp,
) -> Result<Option<bool>, VmError> {
// Number 只处理同类,其他载荷返回 None 允许 Store 继续分派。
let Some(other) = other.as_any().downcast_ref::<Self>() else {
return Ok(None);
};
// 同类 Number 每个关系都返回明确结果,false 也不是未实现。
Ok(Some(match operation {
CompareOp::Eq => self.0 == other.0,
CompareOp::Ne => self.0 != other.0,
CompareOp::Lt => self.0 < other.0,
CompareOp::Le => self.0 <= other.0,
CompareOp::Gt => self.0 > other.0,
CompareOp::Ge => self.0 >= other.0,
}))
}
}
/// 各生命周期测试共用分配器,保存计数器 Arc 与给定不可变边,返回一份拥有根。
fn resource(vm: &mut Vm, drops: &Arc<AtomicUsize>, edges: Vec<ObjectRef>) -> ObjectRef {
vm.allocate(Resource {
drops: drops.clone(),
edges,
})
.unwrap()
}
#[test]
/// 验证新 VM 不隐式自举名义类型或内置对象;不代表 VM 没有显式类型 API。
fn starts_empty_without_language_classes_or_a_type_system() {
// 只观察初始空堆,不因查询自动初始化对象。
let vm = Vm::new().unwrap();
assert_eq!(vm.object_count(), 0);
}
#[test]
/// 验证普通 Record 的身份表示、恒真和身份相等回退,无序比较及不可调用的错误。
fn defaults_provide_identity_representation_hash_and_truth() {
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// 第一个独立对象/资源保留一份根,与 second/equal 比较身份或引用关系。
let first = vm.new_object().unwrap();
// 另一个分配结果有独立身份,即使内容相同也不是同对象。
let second = vm.new_object().unwrap();
assert_eq!(vm.object(first).unwrap().header().identity(), first);
assert!(vm.is_same(first, first).unwrap());
assert!(!vm.is_same(first, second).unwrap());
assert!(vm.repr(first).unwrap().contains(&first.to_string()));
assert_eq!(vm.repr(first).unwrap(), vm.str(first).unwrap());
assert_eq!(vm.hash(first).unwrap(), vm.hash(first).unwrap());
assert!(vm.is_true(first).unwrap());
assert!(vm.compare(first, first, CompareOp::Eq).unwrap());
assert!(vm.compare(first, second, CompareOp::Ne).unwrap());
assert_eq!(
vm.compare(first, second, CompareOp::Lt),
Err(VmError::UnsupportedOperation("ordering"))
);
assert_eq!(
vm.call(first, &[]),
Err(VmError::UnsupportedOperation("call"))
);
}
#[test]
/// 验证复制句柄不保留对象,显式 retain/release 控制资源销毁与过期访问。
fn explicit_references_control_lifetime_and_copies_are_borrowed() {
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
let drops = Arc::new(AtomicUsize::new(0));
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// resource 返回一份外部根,后续复制不新增根。
let owned = resource(&mut vm, &drops, vec![]);
// 直接复制 ObjectRef,ref_count 仍为一,不能额外 release。
let borrowed = owned;
assert_eq!(vm.ref_count(borrowed).unwrap(), 1);
// 显式多取得一份外部根,需两次归还后才实际析构。
vm.retain(owned).unwrap();
assert_eq!(vm.ref_count(owned).unwrap(), 2);
vm.release(owned).unwrap();
assert_eq!(drops.load(Ordering::SeqCst), 0);
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));
}
#[test]
/// 验证字段独立持有内部边,不能用外部 release 归还仅借用的属性边。
fn attributes_keep_objects_alive_and_delete_releases_them() {
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
let drops = Arc::new(AtomicUsize::new(0));
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// 普通 Record 字段作为子对象强边来源。
let owner = vm.new_object().unwrap();
// 子对象先有分配根,写入属性后另有内部边。
let child = resource(&mut vm, &drops, vec![]);
vm.set_attr(owner, "child", Value::Object(child)).unwrap();
assert_eq!(vm.ref_count(child).unwrap(), 2);
// 归还分配根后属性边继续保活,不能再当作拥有根重复 release。
vm.release(child).unwrap();
assert_eq!(vm.ref_count(child).unwrap(), 1);
assert_eq!(vm.release(child), Err(VmError::NoOwnedReference));
assert_eq!(vm.get_attr(owner, "child").unwrap(), Value::Object(child));
vm.del_attr(owner, "child").unwrap();
assert_eq!(drops.load(Ordering::SeqCst), 1);
assert_eq!(
vm.get_attr(owner, "child"),
Err(VmError::AttributeNotFound("child".into()))
);
}
#[test]
/// 验证属性同对象重写先保留新边再归还旧边,随后改标量才释放最后强边。
fn replacing_the_same_edge_does_not_destroy_the_target() {
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// 普通 Record 字段作为子对象强边来源。
let owner = vm.new_object().unwrap();
// 独立目标用于属性替换或多重载荷边计数。
let target = vm.new_object().unwrap();
vm.set_attr(owner, "child", Value::Object(target)).unwrap();
// 归还原根,后续存活完全依赖已建立的内部边。
vm.release(target).unwrap();
vm.set_attr(owner, "child", Value::Object(target)).unwrap();
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(),
Value::Number(7_i64.into())
);
}
#[test]
/// 验证宿主主动 retain 属性借用结果后,可在原父对象释放后继续使用。
fn borrowed_attribute_results_can_be_retained_by_the_host() {
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// 父对象拥有 child 字段边,释放父根会移除该边。
let parent = vm.new_object().unwrap();
// 子对象先有分配根,写入属性后另有内部边。
let child = vm.new_object().unwrap();
vm.set_attr(parent, "child", Value::Object(child)).unwrap();
// 归还分配根后属性边继续保活,不能再当作拥有根重复 release。
vm.release(child).unwrap();
// get_attr 返回借用值,宿主若要跨父对象寿命使用须手动 retain。
let Value::Object(borrowed) = vm.get_attr(parent, "child").unwrap() else {
panic!("expected an object");
};
// 取得独立宿主根后,原持有字段消失也不会使结果失效。
vm.retain(borrowed).unwrap();
vm.release(parent).unwrap();
assert_eq!(vm.ref_count(borrowed).unwrap(), 1);
vm.release(borrowed).unwrap();
assert_eq!(vm.object_count(), 0);
}
#[test]
/// 验证异堆和过期值写入失败保持旧字段,retain/release/身份比较也验证堆归属。
fn failed_foreign_and_stale_writes_preserve_the_old_attribute() {
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// 第二个 VM 用于构造确实属于异堆的有效身份。
let mut other = Vm::new().unwrap();
// 普通 Record 字段作为子对象强边来源。
let owner = vm.new_object().unwrap();
// 句柄在所属 VM 中有效,但本 VM 不能 retain、存储或当作结果返回。
let foreign = other.new_object().unwrap();
// 制造旧身份;释放后写入必须失败而非覆盖旧值。
let stale = vm.new_object().unwrap();
// 销毁对象使句柄过期,后续不再拥有任何可归还的根。
vm.release(stale).unwrap();
vm.set_attr(owner, "value", Value::Number(1_i64.into()))
.unwrap();
assert_eq!(
vm.set_attr(owner, "value", Value::Object(foreign)),
Err(VmError::ForeignHandle)
);
assert_eq!(
vm.set_attr(owner, "value", Value::Object(stale)),
Err(VmError::InvalidHandle)
);
assert_eq!(
vm.get_attr(owner, "value").unwrap(),
Value::Number(1_i64.into())
);
assert_eq!(vm.retain(foreign), Err(VmError::ForeignHandle));
assert_eq!(vm.release(foreign), Err(VmError::ForeignHandle));
assert_eq!(vm.is_same(owner, foreign), Err(VmError::ForeignHandle));
}
#[test]
/// 验证槽代际与堆身份共同防止旧句柄别名新对象。
fn released_handles_cannot_alias_new_objects_or_new_vms() {
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// 释放后保留旧句柄表示,检查槽复用不会让它复活。
let old = vm.new_object().unwrap();
vm.release(old).unwrap();
// 新分配可复用槽号,但代际必须不同。
let new = vm.new_object().unwrap();
assert_ne!(old, new);
assert_eq!(vm.repr(old), Err(VmError::InvalidHandle));
// 同号槽在另一堆也不是有效身份,必须报 ForeignHandle。
let other = Vm::new().unwrap();
assert!(matches!(other.object(new), Err(VmError::ForeignHandle)));
}
#[test]
/// 验证槽向量扩容不移动独立装箱对象实体。
fn boxed_objects_keep_their_addresses_when_the_slot_vector_grows() {
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// 保持首个对象存活,扩容前后比较其独立 Box 地址而非槽向量中的位置。
let first = vm.new_object().unwrap();
// 保存对象实体地址,不保存可能因向量扩容变化的槽地址。
let address = std::ptr::from_ref(vm.object(first).unwrap());
// 额外分配四千零九十六个对象,迫使 slots 向量扩容;所有对象在 VM 结束时清理。
for _ in 0..4096 {
vm.new_object().unwrap();
}
assert_eq!(std::ptr::from_ref(vm.object(first).unwrap()), address);
}
#[test]
/// 构造长单向拥有链,释放唯一头根应通过显式工作栈完成级联回收。
fn zero_reference_chains_are_released_without_recursive_destructors() {
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// 长链唯一保留外部根的起点。
let head = vm.new_object().unwrap();
// tail 只是借用游标,后续节点仅由前节点 next 属性持有。
let mut tail = head;
// 压力设置:足够多次分配或链节点,检验稳定盒与非递归清理。
for _ in 0..4096 {
// 新节点先分配根,接上链后归还该根,仅留下内部边。
let next = vm.new_object().unwrap();
vm.set_attr(tail, "next", Value::Object(next)).unwrap();
vm.release(next).unwrap();
tail = next;
}
// 唯一头根移除应触发整条无环链的级联释放。
vm.release(head).unwrap();
assert_eq!(vm.object_count(), 0);
}
#[test]
/// 验证有外部根的互引环保留,最后根归还后 collect_cycles 才整环回收。
fn cycles_remain_while_rooted_and_are_collected_when_unreachable() {
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
let drops = Arc::new(AtomicUsize::new(0));
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// 第一个独立对象/资源保留一份根,与 second/equal 比较身份或引用关系。
let first = resource(&mut vm, &drops, vec![]);
// 另一个分配结果有独立身份,即使内容相同也不是同对象。
let second = resource(&mut vm, &drops, vec![]);
vm.set_attr(first, "next", Value::Object(second)).unwrap();
vm.set_attr(second, "next", Value::Object(first)).unwrap();
vm.release(second).unwrap();
assert_eq!(vm.collect_cycles(), 0);
vm.release(first).unwrap();
assert_eq!(vm.object_count(), 2);
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)));
}
#[test]
/// 验证回收垃圾环时只扣除其指向幸存者的边,不销毁仍有根的目标。
fn cycle_collection_preserves_rooted_targets_and_updates_counts() {
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// 幸存者保留外部根,即使垃圾环另持有它也不能被 GC 误删。
let live = vm.new_object().unwrap();
// 自环使普通强计数无法归零;无外部根后交给显式环回收或 VM Drop。
let cycle = vm.new_object().unwrap();
vm.set_attr(cycle, "self", Value::Object(cycle)).unwrap();
vm.set_attr(cycle, "live", Value::Object(live)).unwrap();
vm.release(cycle).unwrap();
assert_eq!(vm.ref_count(live).unwrap(), 2);
assert_eq!(vm.collect_cycles(), 1);
assert_eq!(vm.ref_count(live).unwrap(), 1);
vm.release(live).unwrap();
assert_eq!(vm.object_count(), 0);
}
#[test]
/// 验证 trace 两次报告同一目标就持有两条强边,释放载荷逐边归还。
fn payload_edges_are_owned_and_duplicate_edges_are_counted() {
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
let drops = Arc::new(AtomicUsize::new(0));
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// 独立目标用于属性替换或多重载荷边计数。
let target = vm.new_object().unwrap();
// 载荷同时拥有同一目标两次,总强计数应包含两条内部边。
let holder = resource(&mut vm, &drops, vec![target, target]);
assert_eq!(vm.ref_count(target).unwrap(), 3);
// 归还原根,后续存活完全依赖已建立的内部边。
vm.release(target).unwrap();
vm.release(holder).unwrap();
assert_eq!(vm.object_count(), 0);
assert_eq!(drops.load(Ordering::SeqCst), 1);
}
#[test]
/// 验证混合载荷边与属性边的环能被同一次图遍历回收。
fn payload_edges_participate_in_cycle_tracing() {
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
let drops = Arc::new(AtomicUsize::new(0));
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// 普通属性对象作为混合环的一端。
let record = vm.new_object().unwrap();
// Resource 载荷拥有 record,record 的字段再反向拥有 Resource。
let native = resource(&mut vm, &drops, vec![record]);
vm.set_attr(record, "native", Value::Object(native))
.unwrap();
vm.release(record).unwrap();
vm.release(native).unwrap();
assert_eq!(vm.collect_cycles(), 2);
assert_eq!(drops.load(Ordering::SeqCst), 1);
}
#[test]
/// 验证分配前完整边预检,后部异堆边失败不能部分保留前部有效边。
fn invalid_payload_edges_abort_allocation_without_retaining_valid_edges() {
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
let drops = Arc::new(AtomicUsize::new(0));
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// 第二个 VM 用于构造确实属于异堆的有效身份。
let mut other = Vm::new().unwrap();
// 合法本堆边放在错误边前面,验证失败不部分提交其计数。
let local = vm.new_object().unwrap();
// 句柄在所属 VM 中有效,但本 VM 不能 retain、存储或当作结果返回。
let foreign = other.new_object().unwrap();
assert_eq!(
vm.allocate(Resource {
drops: drops.clone(),
edges: vec![local, foreign],
}),
Err(VmError::ForeignHandle)
);
assert_eq!(vm.ref_count(local).unwrap(), 1);
assert_eq!(vm.object_count(), 1);
assert_eq!(drops.load(Ordering::SeqCst), 1);
}
#[test]
/// 验证 Rust 载荷可覆盖显示、真值、哈希和比较,无需附着语言类型。
fn rust_payload_operations_override_defaults_without_language_types() {
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// 测试用 Number(0) 自定义真值为假,不是内置 EndorNumberObject。
let zero = vm.allocate(Number(0)).unwrap();
// 第一个独立对象/资源保留一份根,与 second/equal 比较身份或引用关系。
let first = vm.allocate(Number(42)).unwrap();
// 同值不同身份的 Number,必须内容相等且哈希一致。
let equal = vm.allocate(Number(42)).unwrap();
assert!(!vm.is_same(first, equal).unwrap());
assert!(vm.compare(first, equal, CompareOp::Eq).unwrap());
assert!(vm.compare(zero, first, CompareOp::Lt).unwrap());
assert_eq!(vm.hash(first).unwrap(), vm.hash(equal).unwrap());
assert_eq!(vm.str(first).unwrap(), "42");
assert!(!vm.is_true(zero).unwrap());
assert!(vm.is_true(first).unwrap());
}
#[derive(Debug)]
/// 反向比较场景的右操作数:只对 Gt 返回 true,用于识别左 Lt 是否正确交换方向。
struct ReflectedOrdering;
impl ObjectPayload for ReflectedOrdering {
/// Store/比较协议的具体载荷下转型入口,不依赖语言名义类型。
fn as_any(&self) -> &dyn Any {
self
}
/// 错误与诊断显示的类别标签。
fn kind_name(&self) -> &'static str {
"reflected-ordering"
}
/// Store 先正向后反向调用;返回 None 是未实现,Some(false) 则不再回退。
fn compare(
&self,
_other: &dyn ObjectPayload,
operation: CompareOp,
) -> Result<Option<bool>, VmError> {
Ok(Some(operation == CompareOp::Gt))
}
/// Store::hash 调用时明确返回不支持,不能退回默认对象身份哈希。
fn hash(&self, _identity: ObjectRef) -> Result<u64, VmError> {
Err(VmError::UnsupportedOperation("hash"))
}
}
#[test]
/// 验证左侧未实现时把 Lt 反向为右侧 Gt,显式不支持 hash 原样传播。
fn reflected_operations_and_explicitly_unsupported_hashing_are_respected() {
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// 左侧 Record 不实现有序比较,Store 应尝试右侧反向协议。
let left = vm.allocate(Record).unwrap();
// 右侧专门只对 Gt 返回真,证明左 Lt 正确反射。
let right = vm.allocate(ReflectedOrdering).unwrap();
assert!(vm.compare(left, right, CompareOp::Lt).unwrap());
assert_eq!(vm.hash(right), Err(VmError::UnsupportedOperation("hash")));
}
/// 简单旧式原生回调,仅接受两个直接整数并检查 i64 加法溢出;不是 Endor 通用算术实现。
fn add(arguments: &[Value]) -> Result<Value, VmError> {
// 先按参数形状匹配两个直接 Number,其他输入走回调参数错误。
match arguments {
[Value::Number(left), Value::Number(right)] => {
// as_i64 同时成功才允许此测试回调运算,不接受大整数或小数隐式转换。
let (left, right) = left
.as_i64()
.zip(right.as_i64())
.ok_or(VmError::InvalidArgument("expected two integers"))?;
left.checked_add(right)
.map(|value| Value::Number(value.into()))
.ok_or(VmError::InvalidArgument("integer overflow"))
}
_ => Err(VmError::InvalidArgument("expected two integers")),
}
}
#[test]
/// 验证原生分派、回调错误、禁止动态属性,以及异堆参数在回调前拒绝。
fn native_calls_dispatch_validate_arguments_and_propagate_errors() {
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// 把 Rust add 包装为旧式原生函数,由 VM 负责参数/结果身份验证。
let function = vm.allocate(NativeFunction::new("add", add)).unwrap();
assert_eq!(vm.repr(function).unwrap(), "<native-function add>");
assert_eq!(
vm.call(
function,
&[Value::Number(2_i64.into()), Value::Number(3_i64.into())]
)
.unwrap(),
Value::Number(5_i64.into())
);
assert_eq!(
vm.call(function, &[]),
Err(VmError::InvalidArgument("expected two integers"))
);
assert_eq!(
vm.call(
function,
&[Value::Number(i64::MAX.into()), Value::Number(1_i64.into())]
),
Err(VmError::InvalidArgument("integer overflow"))
);
assert_eq!(
vm.set_attr(function, "x", Value::Nil),
Err(VmError::UnsupportedOperation("attributes"))
);
// 第二个 VM 用于构造确实属于异堆的有效身份。
let mut other = Vm::new().unwrap();
// 句柄在所属 VM 中有效,但本 VM 不能 retain、存储或当作结果返回。
let foreign = other.new_object().unwrap();
assert_eq!(
vm.call(function, &[Value::Object(foreign)]),
Err(VmError::ForeignHandle)
);
}
#[derive(Debug)]
/// 非法结果场景的故障注入载荷:只保存未 trace 的借用身份,调用时故意返还它让 VM 拒绝。
struct ReturnsObject(ObjectRef);
impl ObjectPayload for ReturnsObject {
/// Store/比较协议的具体载荷下转型入口,不依赖语言名义类型。
fn as_any(&self) -> &dyn Any {
self
}
/// 错误与诊断显示的类别标签。
fn kind_name(&self) -> &'static str {
"invalid-return"
}
/// 故意返回未经验证的原始对象句柄,测试 VM 能否在结果移交前阻止非法身份。
fn call(&self, _arguments: &[Value]) -> Result<Value, VmError> {
Ok(Value::Object(self.0))
}
}
#[test]
/// 验证原生结果仍需堆归属/存活检查,宿主回调不能借返回通道塞入非法句柄。
fn native_results_cannot_smuggle_foreign_or_dead_handles_into_the_vm() {
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// 第二个 VM 用于构造确实属于异堆的有效身份。
let mut other = Vm::new().unwrap();
// 句柄在所属 VM 中有效,但本 VM 不能 retain、存储或当作结果返回。
let foreign = other.new_object().unwrap();
// 故意不 trace 持有的句柄,因为这里测试的是非法借用结果而非合法拥有边。
let invalid_return = vm.allocate(ReturnsObject(foreign)).unwrap();
assert_eq!(vm.call(invalid_return, &[]), Err(VmError::ForeignHandle));
// 本堆对象释放后制造过期返回值,不与异堆错误混淆。
let dead = vm.new_object().unwrap();
// 之后只剩失效的借用身份,回调返回不能把它重新保活。
vm.release(dead).unwrap();
// 故意不 trace 持有的句柄,因为这里测试的是非法借用结果而非合法拥有边。
let invalid_return = vm.allocate(ReturnsObject(dead)).unwrap();
assert_eq!(vm.call(invalid_return, &[]), Err(VmError::InvalidHandle));
}
#[test]
/// 验证离开 VM 作用域最终销毁仍有根对象及尚未显式 GC 的自环。
fn dropping_the_vm_cleans_up_rooted_objects_and_uncollected_cycles() {
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
let drops = Arc::new(AtomicUsize::new(0));
{
// 建立独立堆,句柄不能跨测试/VM 使用。
let mut vm = Vm::new().unwrap();
// 离开局部 VM 作用域时仍有一份根,VM Drop 也必须释放宿主资源。
let rooted = resource(&mut vm, &drops, vec![]);
// 自环使普通强计数无法归零;无外部根后交给显式环回收或 VM Drop。
let cycle = resource(&mut vm, &drops, vec![]);
vm.set_attr(cycle, "self", Value::Object(cycle)).unwrap();
vm.release(cycle).unwrap();
assert_eq!(vm.ref_count(rooted).unwrap(), 1);
}
assert_eq!(drops.load(Ordering::SeqCst), 2);
}