328 lines
15 KiB
Rust
328 lines
15 KiB
Rust
//! Rust 测试框架调用的属性存储回归,不执行源码指令。
|
||
//! Proxy 让字段存储与名义身份分开;ReflectiveType 触发类型查询对自身属性的重入。
|
||
//! 断言覆盖托管边生命周期、失败不修改版本、能力方法不得被实例属性遮蔽。
|
||
|
||
use objects::{ObjectPayload, ObjectRef, ObjectStore, TypeConstructor, Value, VmError};
|
||
|
||
#[derive(Debug)]
|
||
/// 一个拥有后端存储引用的门面,type_target 保持默认 None。
|
||
struct Proxy(ObjectRef);
|
||
impl ObjectPayload for Proxy {
|
||
/// Store 下转型测试载荷,不进行语言类型判定。
|
||
fn as_any(&self) -> &dyn std::any::Any {
|
||
self
|
||
}
|
||
/// 测试错误/诊断使用的类别标签。
|
||
fn kind_name(&self) -> &'static str {
|
||
"proxy"
|
||
}
|
||
/// Heap 分配/替换时记录一条拥有边,确保被代理存储或自引用 Cell 存活。
|
||
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
|
||
visit(self.0);
|
||
}
|
||
/// Store::attribute_path 追踪实际字段存储,不改变代理对象头部类型。
|
||
fn attribute_target(&self, _: &ObjectStore) -> Result<Option<ObjectRef>, VmError> {
|
||
Ok(Some(self.0))
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
/// 验证代理改指新存储后,名义类型不变、旧值释放、读写删除转到新终点。
|
||
fn storage_retargeting_preserves_type_identity_and_releases_old_state() {
|
||
// 建立独立堆隔离场景,所有分配根在测试末尾明确归还。
|
||
let mut store = ObjectStore::new().unwrap();
|
||
// 自举 Object/Type,各有一份外部根;其图内自环需最终 collect_cycles。
|
||
let roots = store.bootstrap_types().unwrap();
|
||
// 给代理/反射实例附着真实名义类型,字段转发不应替换它。
|
||
let class = store
|
||
.new_type("Proxy", roots.object, TypeConstructor::Record)
|
||
.unwrap();
|
||
// 普通 Record 作为无类型后端存储,只有代理持有时也须存活。
|
||
let storage = store.new_object().unwrap();
|
||
// 旧属性值先由测试根持有,之后转由字段强边持有。
|
||
let old_value = store.new_object().unwrap();
|
||
// 弱句柄不持有旧值,替换存储后可观测其真正释放。
|
||
let weak_value = store.downgrade(old_value).unwrap();
|
||
store
|
||
.set_attr(storage, "value", Value::Object(old_value))
|
||
.unwrap();
|
||
// 归还外部根,留下 storage.value 一条强边。
|
||
store.release(old_value).unwrap();
|
||
// Proxy 的 trace 持有后端,字段实际写入后端但类型留在 Proxy 头部。
|
||
let proxy = store.allocate(Proxy(storage)).unwrap();
|
||
store.attach_type(proxy, class).unwrap();
|
||
// 归还后端外部根后仍由 Proxy 的载荷边维持存活。
|
||
store.release(storage).unwrap();
|
||
// 确认路径同时包含门面和终点,不只返回头部类型对象。
|
||
assert_eq!(store.attribute_path(proxy).unwrap(), vec![proxy, storage]);
|
||
// 保留 type 属性查询代理自身名义类型,不跟随后端身份变化。
|
||
assert_eq!(store.get_attr(proxy, "type").unwrap(), Value::Object(class));
|
||
assert_eq!(
|
||
store.get_attr(proxy, "value").unwrap(),
|
||
Value::Object(old_value)
|
||
);
|
||
// 新后端先由根持有,替换成功后 Proxy 自己持有一条边。
|
||
let next = store.new_object().unwrap();
|
||
// 受控替换重新登记边,旧后端及其唯一持有的属性值应被级联释放。
|
||
store.replace_payload(proxy, Proxy(next)).unwrap();
|
||
// 归还新后端根,后续访问证明其仍由 Proxy 强边保留。
|
||
store.release(next).unwrap();
|
||
// 旧值没有残余根,弱升级必须返回 None。
|
||
assert!(store.upgrade(weak_value).unwrap().is_none());
|
||
store
|
||
.set_attr(proxy, "value", Value::Number(9_i64.into()))
|
||
.unwrap();
|
||
assert_eq!(
|
||
store.get_attr(next, "value").unwrap(),
|
||
Value::Number(9_i64.into())
|
||
);
|
||
// 通过代理删除应作用于新后端,不只是门面自己的属性表。
|
||
store.del_attr(proxy, "value").unwrap();
|
||
assert!(store.get_attr(next, "value").is_err());
|
||
// 逐项释放测试拥有根,不手动 release 内部边,避免重复归还。
|
||
for object in [proxy, class, roots.object, roots.type_type] {
|
||
store.release(object).unwrap();
|
||
}
|
||
// 回收类型自举环及其他已无外部根的环,最终堆应为空。
|
||
store.collect_cycles();
|
||
assert_eq!(store.object_count(), 0);
|
||
}
|
||
|
||
#[test]
|
||
/// 验证属性转发环和异堆身份被拒绝,失败不产生新的变更版本。
|
||
fn cyclic_and_foreign_attribute_storage_fail_without_mutating_fields() {
|
||
// 建立独立堆隔离场景,所有分配根在测试末尾明确归还。
|
||
let mut store = ObjectStore::new().unwrap();
|
||
// 普通 Record 作为无类型后端存储,只有代理持有时也须存活。
|
||
let storage = store.new_object().unwrap();
|
||
// Proxy 的 trace 持有后端,字段实际写入后端但类型留在 Proxy 头部。
|
||
let proxy = store.allocate(Proxy(storage)).unwrap();
|
||
// 注入合法托管自环但非法属性转发环;属性路径必须检测,不无限循环。
|
||
store.replace_payload(proxy, Proxy(proxy)).unwrap();
|
||
// 记录失败读写前版本,路径检查失败不得修改对象。
|
||
let version = store.change_version(proxy).unwrap();
|
||
assert!(store.get_attr(proxy, "field").is_err());
|
||
assert!(store.set_attr(proxy, "field", Value::Nil).is_err());
|
||
assert_eq!(store.change_version(proxy).unwrap(), version);
|
||
// 异堆即使槽号相同也必须报 ForeignHandle,不当成普通缺字段。
|
||
let other = ObjectStore::new().unwrap();
|
||
assert_eq!(other.attribute_path(proxy), Err(VmError::ForeignHandle));
|
||
// 归还后端外部根后仍由 Proxy 的载荷边维持存活。
|
||
store.release(storage).unwrap();
|
||
store.release(proxy).unwrap();
|
||
// 外部根释放后只有 Proxy 自环残留,显式回收恰好一个对象。
|
||
assert_eq!(store.collect_cycles(), 1);
|
||
}
|
||
|
||
#[derive(Debug)]
|
||
/// 拥有一个 Cell,type_target 读 Cell 指回自己,测试递归属性查询。
|
||
struct ReflectiveType(ObjectRef);
|
||
impl ObjectPayload for ReflectiveType {
|
||
/// Store 下转型测试载荷,不进行语言类型判定。
|
||
fn as_any(&self) -> &dyn std::any::Any {
|
||
self
|
||
}
|
||
/// 测试错误/诊断使用的类别标签。
|
||
fn kind_name(&self) -> &'static str {
|
||
"reflective-type"
|
||
}
|
||
/// 反射对象自行存字段,允许 selector 查询从公共属性表读取。
|
||
fn allows_attributes(&self) -> bool {
|
||
true
|
||
}
|
||
/// Heap 分配/替换时记录一条拥有边,确保被代理存储或自引用 Cell 存活。
|
||
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
|
||
visit(self.0);
|
||
}
|
||
/// Store::type_of 调用时通过 Cell 找自己并读 selector,专门制造属性检查重入。
|
||
fn type_target(&self, store: &ObjectStore) -> Result<Option<ObjectRef>, VmError> {
|
||
// Cell 初始 Nil 时不重入,安装自引用后读自身 selector 触发保护守卫路径。
|
||
if let Value::Object(receiver) = store.cell_get(self.0)? {
|
||
store.get_attr(receiver, "selector")?;
|
||
}
|
||
Ok(None)
|
||
}
|
||
}
|
||
|
||
#[derive(Debug)]
|
||
/// 无捕获且不修改存储的能力方法测试实现;完整签名供声明/调用门禁读取。
|
||
struct TypedCheck;
|
||
impl objects::NativeCallable for TypedCheck {
|
||
/// 为 check 声明显式 self、Number 返回和 ReadOnly 效果,供能力契约核对。
|
||
fn signature(&self) -> Option<objects::CallSignature> {
|
||
Some(objects::CallSignature::exact(
|
||
vec![objects::AnnotationType::Any],
|
||
objects::AnnotationType::Number,
|
||
objects::CallEffect::ReadOnly,
|
||
))
|
||
}
|
||
/// 原生分派返回直接整数,无对象结果需要额外保留;本文件主要验证契约门禁。
|
||
fn invoke(
|
||
&self,
|
||
_: &mut objects::NativeContext<'_>,
|
||
_: &[Value],
|
||
) -> Result<objects::NativeResult, VmError> {
|
||
Ok(objects::NativeResult::Return(Value::Number(1.into())))
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
/// 验证能力声明/调用各入口均拒绝同名字段遮蔽,且递归守卫失败后正确清理。
|
||
fn typed_method_storage_cannot_diverge_from_class_dispatch() {
|
||
// 建立独立堆隔离场景,所有分配根在测试末尾明确归还。
|
||
let mut store = ObjectStore::new().unwrap();
|
||
// 自举 Object/Type,各有一份外部根;其图内自环需最终 collect_cycles。
|
||
let roots = store.bootstrap_types().unwrap();
|
||
// 给代理/反射实例附着真实名义类型,字段转发不应替换它。
|
||
let class = store
|
||
.new_type("Checked", roots.object, TypeConstructor::Record)
|
||
.unwrap();
|
||
// 上下文原生函数发布完整签名;类方法表还会单独拥有它的强边。
|
||
let function = store
|
||
.allocate(objects::ContextNativeFunction::new(
|
||
"check",
|
||
TypedCheck,
|
||
vec![],
|
||
))
|
||
.unwrap();
|
||
store.define_method(class, "check", function).unwrap();
|
||
// 能力要求与原生方法完整契约一致,包含显式 self 参数。
|
||
let capability = store
|
||
.new_typed_trait(
|
||
"Checked",
|
||
vec![objects::TypedMethodRequirement {
|
||
name: "check".into(),
|
||
signature: objects::CallSignature::exact(
|
||
vec![objects::AnnotationType::Any],
|
||
objects::AnnotationType::Number,
|
||
objects::CallEffect::ReadOnly,
|
||
),
|
||
}],
|
||
)
|
||
.unwrap();
|
||
// 普通 Record 作为无类型后端存储,只有代理持有时也须存活。
|
||
let storage = store.new_object().unwrap();
|
||
// Proxy 的 trace 持有后端,字段实际写入后端但类型留在 Proxy 头部。
|
||
let proxy = store.allocate(Proxy(storage)).unwrap();
|
||
store.attach_type(proxy, class).unwrap();
|
||
// 直接写无类型后端可以造出对代理而言不符合能力的实例,后续门禁应拒绝。
|
||
store.set_attr(storage, "check", Value::Nil).unwrap();
|
||
// 能力声明失败应保持定义版本和声明集合不变。
|
||
let version = store.change_version(class).unwrap();
|
||
assert!(matches!(
|
||
// 当前后端仍有遮蔽字段,能力声明必须在状态提交前失败。
|
||
store.implement_trait(class, capability),
|
||
Err(VmError::TraitViolation { .. })
|
||
));
|
||
assert_eq!(store.change_version(class).unwrap(), version);
|
||
// 失败声明不能留下一半能力关系。
|
||
assert!(!store.implements_trait(class, capability).unwrap());
|
||
// 删除遮蔽字段恢复合法存储,后续能力声明/查询应重新成功。
|
||
store.del_attr(storage, "check").unwrap();
|
||
// 存储冲突消除后能力声明才允许提交。
|
||
store.implement_trait(class, capability).unwrap();
|
||
// 代理受控写入在真正触及后端前失败,后端版本不得增加。
|
||
let version = store.change_version(storage).unwrap();
|
||
assert!(matches!(
|
||
store.set_attr(proxy, "check", Value::Nil),
|
||
Err(VmError::TraitViolation { .. })
|
||
));
|
||
assert_eq!(store.change_version(storage).unwrap(), version);
|
||
assert!(store.supports(proxy, capability).unwrap());
|
||
// 可信宿主能直接写无类型后端;supports/get_attr/bind_attr/调用检查/直接存储检查都必须发现失效实例。
|
||
// 直接写无类型后端可以造出对代理而言不符合能力的实例,后续门禁应拒绝。
|
||
store.set_attr(storage, "check", Value::Nil).unwrap();
|
||
assert!(matches!(
|
||
store.supports(proxy, capability),
|
||
Err(VmError::TraitViolation { .. })
|
||
));
|
||
// 后端已出现遮蔽方法的冲突字段时,即使只读属性查询也必须拒绝不合法的能力存储。
|
||
assert!(matches!(
|
||
store.get_attr(proxy, "check"),
|
||
Err(VmError::TraitViolation { .. })
|
||
));
|
||
// 绑定方法入口不能绕过同一存储校验,不能先创建可调用绑定再放过冲突。
|
||
assert!(matches!(
|
||
store.bind_attr(proxy, "check"),
|
||
Err(VmError::TraitViolation { .. })
|
||
));
|
||
// 直接方法契约检查也拒绝代理后端冲突,防止跳过属性查找后绕过门禁。
|
||
assert!(matches!(
|
||
store.check_method_contract_call(function, proxy, false),
|
||
Err(VmError::TraitViolation { .. })
|
||
));
|
||
// 显式实例存储检查应给出同类能力违规,保持各调用入口的检查一致。
|
||
assert!(matches!(
|
||
store.check_typed_instance_storage(proxy),
|
||
Err(VmError::TraitViolation { .. })
|
||
));
|
||
// 另建已有冲突字段的记录,验证 attach_type 也守住能力存储约束。
|
||
let candidate = store.new_object().unwrap();
|
||
// 先放同名字段再附着类型,不能绕过“创建后写入”的校验。
|
||
store.set_attr(candidate, "check", Value::Nil).unwrap();
|
||
assert!(matches!(
|
||
store.attach_type(candidate, class),
|
||
Err(VmError::TraitViolation { .. })
|
||
));
|
||
// 附着失败不能留下部分头部类型关系。
|
||
assert_eq!(store.type_of(candidate).unwrap(), None);
|
||
// 删除遮蔽字段恢复合法存储,后续能力声明/查询应重新成功。
|
||
store.del_attr(storage, "check").unwrap();
|
||
assert!(store.supports(proxy, capability).unwrap());
|
||
assert_eq!(
|
||
store.get_attr(proxy, "check").unwrap(),
|
||
Value::Object(function)
|
||
);
|
||
store
|
||
.check_method_contract_call(function, proxy, false)
|
||
.unwrap();
|
||
// 用托管 Cell 保存反射对象,初始 Nil 避免初始化时就递归查询未设字段。
|
||
let self_cell = store.new_cell(Some(Value::Nil)).unwrap();
|
||
// 载荷持有 Cell,Cell 稍后反向持有对象,形成可回收的托管环。
|
||
let reflective = store.allocate(ReflectiveType(self_cell)).unwrap();
|
||
store
|
||
.set_attr(reflective, "selector", Value::Bool(true))
|
||
.unwrap();
|
||
store
|
||
.cell_set(self_cell, Value::Object(reflective))
|
||
.unwrap();
|
||
// 附着能力类型期间查询 selector 会重入检查,守卫应避免无限递归。
|
||
store.attach_type(reflective, class).unwrap();
|
||
assert_eq!(
|
||
store.get_attr(reflective, "selector").unwrap(),
|
||
Value::Bool(true)
|
||
);
|
||
assert!(store.supports(reflective, capability).unwrap());
|
||
// 带反射检查的载荷第一次非法写入仍须拒绝,递归防护不能误跳过最外层约束。
|
||
assert!(matches!(
|
||
store.set_attr(reflective, "check", Value::Nil),
|
||
Err(VmError::TraitViolation { .. })
|
||
));
|
||
// 首次失败必须通过 RAII 移除标记;第二次同样的非法写入仍应失败,不能被误当成递归跳过。
|
||
assert!(matches!(
|
||
store.set_attr(reflective, "check", Value::Nil),
|
||
Err(VmError::TraitViolation { .. })
|
||
));
|
||
// 归还自引用图外部根;环内边仍在,统一由最终回收处理。
|
||
store.release(reflective).unwrap();
|
||
// Cell 的外部根也归还,不能仅释放反射门面而留下测试根。
|
||
store.release(self_cell).unwrap();
|
||
|
||
// 逐项释放测试拥有根,不手动 release 内部边,避免重复归还。
|
||
for object in [
|
||
candidate,
|
||
proxy,
|
||
storage,
|
||
function,
|
||
capability,
|
||
class,
|
||
roots.object,
|
||
roots.type_type,
|
||
] {
|
||
store.release(object).unwrap();
|
||
}
|
||
// 回收类型自举环及其他已无外部根的环,最终堆应为空。
|
||
store.collect_cycles();
|
||
assert_eq!(store.object_count(), 0);
|
||
}
|