From 0513b96dfbc3a93fbca7e885c9883c1f5b84e6fc Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Thu, 22 Oct 2026 15:28:19 +0800 Subject: [PATCH] Prevent instance attributes from shadowing typed capability methods --- crates/objects/src/README.md | 2 +- crates/objects/src/contracts.rs | 1 + crates/objects/src/store.rs | 7 + crates/objects/src/types.rs | 168 ++++++++++++++++++++- crates/objects/tests/README.md | 2 +- crates/objects/tests/attribute_storage.rs | 171 ++++++++++++++++++++++ docs/runtime/types.md | 2 + 7 files changed, 349 insertions(+), 4 deletions(-) diff --git a/crates/objects/src/README.md b/crates/objects/src/README.md index 8d6ef64..8e53fef 100644 --- a/crates/objects/src/README.md +++ b/crates/objects/src/README.md @@ -6,7 +6,7 @@ | --- | --- | | [lib.rs](lib.rs) | 包边界、对象类型/ObjectStore 公开导出(含Trait/MethodRequirement/TypedMethodRequirement/SourceSignature/AnnotationType/CallArity/CallSignature/CallType/CallEffect/ReadAccess)与独立存储 rustdoc 示例 | | [object.rs](object.rs) | Value、CompareOp、ObjectPayload(含显式函数标记)、ObjectHeader、EndorObject、Record、可发布数量元数据的 NativeFunction | -| [types.rs](types.rs) | 托管名义类型、显式 Object/Type 引导、继承查询、受管方法表/接收者绑定、类字段、显式/类型化Trait定义、强类型签名边与声明约束、原生构造委托、统一源码/原生/绑定方法参数区间查询与能力验证、声明环境托管边、字段构造/写入检查与标记为校验读取的注解解析/匹配 | +| [types.rs](types.rs) | 托管名义类型、显式 Object/Type 引导、继承查询、受管方法表/接收者绑定、类字段、显式/类型化Trait定义、强类型签名边与声明约束、实例方法遮蔽检查和宿主缓存依赖名、原生构造委托、统一源码/原生/绑定方法参数区间查询与能力验证、声明环境托管边、字段构造/写入检查与标记为校验读取的注解解析/匹配 | | [contracts.rs](contracts.rs) | 方法参数逆变/返回协变、名义/能力/联合包含关系、祖先方法更新的后代检查、动态注解调用前复查、类型化能力替换/返回约束与临时递归验证集合 | | [native.rs](native.rs) | NativeContext、CallArity/CallSignature 类型、Delegated/固定效果元数据、调用前检查、签名类型强边、上下文原生函数、作用域语言函数工厂、显式能力声明入口、函数标记、根与非递归委托调用 | | [changes.rs](changes.rs) | 合并变更批次、变更键与有界通知额度 | diff --git a/crates/objects/src/contracts.rs b/crates/objects/src/contracts.rs index e1ccea9..ae9612e 100644 --- a/crates/objects/src/contracts.rs +++ b/crates/objects/src/contracts.rs @@ -391,6 +391,7 @@ impl ObjectStore { if *candidate != function { continue; } + self.check_typed_instance_storage(receiver)?; if overrides { self.check_override(definition, name, function, None, false)?; } diff --git a/crates/objects/src/store.rs b/crates/objects/src/store.rs index e731795..68219e4 100644 --- a/crates/objects/src/store.rs +++ b/crates/objects/src/store.rs @@ -12,6 +12,9 @@ pub struct ObjectStore { pub(crate) heap: Heap, function_type: Option, pub(crate) field_type_checks: bool, + pub(crate) active_attribute_checks: std::sync::Mutex< + std::collections::HashMap>, + >, pub(crate) active_trait_checks: std::sync::Mutex< std::collections::HashMap< std::thread::ThreadId, @@ -157,6 +160,7 @@ impl ObjectStore { heap: Heap::new()?, function_type: None, field_type_checks: false, + active_attribute_checks: std::sync::Mutex::new(std::collections::HashMap::new()), active_trait_checks: std::sync::Mutex::new(std::collections::HashMap::new()), }) } @@ -312,6 +316,9 @@ impl ObjectStore { if self.field_type_checks { self.check_field_write(reference, &name, &value)?; } + for object in self.attribute_path(reference)? { + self.check_typed_method_write(object, &name)?; + } let storage = *self .attribute_path(reference)? .last() diff --git a/crates/objects/src/types.rs b/crates/objects/src/types.rs index 0e67f69..5b614a8 100644 --- a/crates/objects/src/types.rs +++ b/crates/objects/src/types.rs @@ -8,6 +8,30 @@ use crate::{ VmError, }; +// A host type_target may read its receiver's raw attributes. Validate the +// outer lookup after resolution without recursively re-entering its guard. +struct AttributeCheckGuard<'a> { + store: &'a ObjectStore, + thread: std::thread::ThreadId, + object: ObjectRef, +} +impl Drop for AttributeCheckGuard<'_> { + fn drop(&mut self) { + let mut checks = self + .store + .active_attribute_checks + .lock() + .expect("attribute checker poisoned"); + let active = checks + .get_mut(&self.thread) + .expect("active attribute check"); + active.remove(&self.object); + if active.is_empty() { + checks.remove(&self.thread); + } + } +} + #[derive(Debug, Clone, Copy)] pub enum TypeConstructor { /// Allocate a plain, attribute-bearing instance of this type. @@ -227,6 +251,9 @@ impl ObjectStore { type_object: ObjectRef, ) -> Result<(), VmError> { self.payload::(type_object, "Type")?; + for capability in self.inherited_traits(type_object)? { + self.check_capability_storage(object, capability)?; + } self.heap.attach_type(object, type_object) } @@ -541,6 +568,7 @@ impl ObjectStore { if name == "name" && let Some(capability) = object.payload().as_any().downcast_ref::() { + self.check_typed_method_write(receiver, name)?; return Ok(AttributeLookup::Value(Value::Text(capability.name.clone()))); } if let Some(definition) = object.payload().as_any().downcast_ref::() { @@ -550,9 +578,11 @@ impl ObjectStore { _ => None, }; if let Some(value) = metadata { + self.check_typed_method_write(receiver, name)?; return Ok(AttributeLookup::Value(value)); } if let Some(function) = self.lookup_method(receiver, name)? { + self.check_typed_method_write(receiver, name)?; return Ok(AttributeLookup::Value(Value::Object(function))); } } @@ -561,7 +591,10 @@ impl ObjectStore { .last() .expect("nonempty attribute path"); match self.heap.get_attr(storage, name) { - Ok(value) => return Ok(AttributeLookup::Value(value)), + Ok(value) => { + self.check_typed_method_write(receiver, name)?; + return Ok(AttributeLookup::Value(value)); + } Err(VmError::AttributeNotFound(_) | VmError::UnsupportedOperation("attributes")) => {} Err(error) => return Err(error), } @@ -899,6 +932,21 @@ impl ObjectStore { for descendant in self.descendant_types(definition)? { self.validate_trait(descendant, capability, false)?; } + // Existing objects must satisfy the same storage invariant as new writes. + // Borrowed heap enumeration adds no owning instance registry. + if !self + .payload::(capability, "Trait")? + .signatures + .is_empty() + { + for object in self.heap.live_references() { + if let Some(actual) = self.type_of(object)? + && self.is_subtype(actual, definition)? + { + self.check_capability_storage(object, capability)?; + } + } + } let mut updated = self.payload::(definition, "Type")?.clone(); if !updated.traits.contains(&capability) { updated.traits.push(capability); @@ -918,10 +966,126 @@ impl ObjectStore { } Ok(declared) } + /// Typed capability methods belong to the method table. Instance fields + /// cannot replace them independently of dispatch through that table. + pub(crate) fn check_typed_method_write( + &self, + object: ObjectRef, + name: &str, + ) -> Result<(), VmError> { + let thread = std::thread::current().id(); + if !self + .active_attribute_checks + .lock() + .expect("attribute checker poisoned") + .entry(thread) + .or_default() + .insert(object) + { + return Ok(()); + } + let _guard = AttributeCheckGuard { + store: self, + thread, + object, + }; + if let Some(definition) = self.type_of(object)? { + for capability in self.inherited_traits(definition)? { + let contract = self.payload::(capability, "Trait")?; + if contract.signatures.contains_key(name) { + return Err(VmError::TraitViolation { + capability: contract.name.clone(), + method: name.into(), + reason: "instance attribute shadows a typed capability method", + }); + } + } + } + Ok(()) + } + + fn check_capability_storage( + &self, + object: ObjectRef, + capability: ObjectRef, + ) -> Result<(), VmError> { + let contract = self.payload::(capability, "Trait")?; + if contract.signatures.is_empty() { + return Ok(()); + } + let storage = *self + .attribute_path(object)? + .last() + .expect("nonempty attribute path"); + for name in contract.signatures.keys() { + let payload = self.object(object)?.payload(); + let intrinsic = if payload.as_any().is::() { + name == "name" || name == "base" || self.lookup_method(object, name)?.is_some() + } else { + payload.as_any().is::() && name == "name" + }; + if intrinsic { + return Err(VmError::TraitViolation { + capability: contract.name.clone(), + method: name.clone(), + reason: "intrinsic attribute shadows a typed capability method", + }); + } + match self.heap.get_attr(storage, name) { + Ok(_) => { + return Err(VmError::TraitViolation { + capability: contract.name.clone(), + method: name.clone(), + reason: "instance attribute shadows a typed capability method", + }); + } + Err( + VmError::AttributeNotFound(_) | VmError::UnsupportedOperation("attributes"), + ) => {} + Err(error) => return Err(error), + } + } + Ok(()) + } + + /// Method names whose instance storage participates in typed capability + /// validity. Hosts can subscribe to these attribute keys when caching. + pub fn typed_method_names(&self, object: ObjectRef) -> Result, VmError> { + let mut names = std::collections::BTreeSet::new(); + if let Some(definition) = self.type_of(object)? { + for capability in self.inherited_traits(definition)? { + names.extend( + self.payload::(capability, "Trait")? + .signatures + .keys() + .cloned(), + ); + } + } + Ok(names.into_iter().collect()) + } + + /// Validate instance storage before host dispatch that bypasses attribute + /// lookup. Raw storage access is trusted but cannot certify conformance. + pub fn check_typed_instance_storage(&self, object: ObjectRef) -> Result<(), VmError> { + if let Some(definition) = self.type_of(object)? { + for capability in self.inherited_traits(definition)? { + self.check_capability_storage(object, capability)?; + } + } + Ok(()) + } + pub fn supports(&self, object: ObjectRef, capability: ObjectRef) -> Result { self.payload::(capability, "Trait")?; match self.type_of(object)? { - Some(definition) => self.implements_trait(definition, capability), + Some(definition) => { + let declared = self.implements_trait(definition, capability)?; + if declared { + self.check_capability_storage(object, capability)?; + } + Ok(declared) + } None => Ok(false), } } diff --git a/crates/objects/tests/README.md b/crates/objects/tests/README.md index 11c3e22..481778c 100644 --- a/crates/objects/tests/README.md +++ b/crates/objects/tests/README.md @@ -4,7 +4,7 @@ | 文件 | 覆盖 | | --- | --- | -| [attribute_storage.rs](attribute_storage.rs) | 可转移字段存储、原类型身份、替换释放、属性写删和非法路径拒绝 | +| [attribute_storage.rs](attribute_storage.rs) | 可转移字段存储、原类型身份、替换释放、属性写删、非法路径拒绝及类型化能力方法遮蔽的登记/调用/修复 | | [store.rs](store.rs) | 只读嵌套、容器与类型写入屏障、稳定地址、跨存储句柄拒绝与更新原子性、Dict/Cell 循环回收、函数校验失败无泄漏、共享代码与常量生命周期 | 非法句柄索引、槽位代数与计数溢出仍在 src/heap.rs 的私有单元测试内,不开放句柄构造器。VM 委托 API 与求值行为继续由 [core/tests](../../core/tests/README.md) 检查。随工作区在指定远端运行,本地不测试;现有测试源码不代表已通过,见 [验证](../../../docs/operations/validation.md)。返回 [objects](../README.md)。 diff --git a/crates/objects/tests/attribute_storage.rs b/crates/objects/tests/attribute_storage.rs index 435eebd..ea6289b 100644 --- a/crates/objects/tests/attribute_storage.rs +++ b/crates/objects/tests/attribute_storage.rs @@ -76,3 +76,174 @@ fn cyclic_and_foreign_attribute_storage_fail_without_mutating_fields() { store.release(proxy).unwrap(); assert_eq!(store.collect_cycles(), 1); } + +#[derive(Debug)] +struct ReflectiveType(ObjectRef); +impl ObjectPayload for ReflectiveType { + fn as_any(&self) -> &dyn std::any::Any { + self + } + fn kind_name(&self) -> &'static str { + "reflective-type" + } + fn allows_attributes(&self) -> bool { + true + } + fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { + visit(self.0); + } + fn type_target(&self, store: &ObjectStore) -> Result, VmError> { + 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 { + fn signature(&self) -> Option { + Some(objects::CallSignature::exact( + vec![objects::AnnotationType::Any], + objects::AnnotationType::Number, + objects::CallEffect::ReadOnly, + )) + } + fn invoke( + &self, + _: &mut objects::NativeContext<'_>, + _: &[Value], + ) -> Result { + Ok(objects::NativeResult::Return(Value::Number(1.into()))) + } +} + +#[test] +fn typed_method_storage_cannot_diverge_from_class_dispatch() { + let mut store = ObjectStore::new().unwrap(); + 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(); + 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(); + let storage = store.new_object().unwrap(); + 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()); + // A trusted host can access the untyped backing record directly. Every + // language/host dispatch gate must detect the resulting invalid instance. + 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 { .. }) + )); + 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(); + let self_cell = store.new_cell(Some(Value::Nil)).unwrap(); + 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(); + 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 { .. }) + )); + // A failed guarded check must not suppress the next one. + assert!(matches!( + store.set_attr(reflective, "check", Value::Nil), + Err(VmError::TraitViolation { .. }) + )); + store.release(reflective).unwrap(); + store.release(self_cell).unwrap(); + + 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); +} diff --git a/docs/runtime/types.md b/docs/runtime/types.md index cf6ba2f..c8feada 100644 --- a/docs/runtime/types.md +++ b/docs/runtime/types.md @@ -137,6 +137,8 @@ ObjectStore/Vm::new_typed_trait(name, Vec) 以每项 nam 显式 implement_trait 严格检查实际方法的参数逆变、返回协变、接收者和已知原生效果;失败不登记、不改变方法表/版本。所有已有后代也检查。继承的源码子类及方法更新使用同一规则,未知前向注解可暂缓,但显式登记/查询和实际调用不暂缓。旧无类型元数据的方法不能满足类型化要求。Trait 的 self 可声明 Object 或实际名义约束,实际接收者必须满足它;不会因为普通函数首参是对象而隐式声明能力。 +类型化能力的方法槽不允许由实例属性遮蔽;正常 set_attr 在修改前检查接收者及属性转发路径,旧数量型 Trait 仍保留动态属性行为。attach_type 与 implement_trait 分别检查待分类对象和已有实例的存储,冲突时不建立类型/能力关系。宿主仍可直接修改独立存储对象,但 supports、属性读取和方法调用会拒绝这种已失效的实例;删除冲突属性可以恢复。宿主绕过普通属性调用(如原生渲染快路径)须先调用 check_typed_instance_storage。该检查只验证实例存储,不替代方法签名检查,也不引入实例强引用索引。typed_method_names 返回宿主缓存应订阅的属性名;固有 Type/Trait 属性也不能遮住类型化方法。宿主 type_target 读取自身原始属性时,作用域检查集合避免重复进入,外层解析完成后仍执行完整校验;错误/展开路径通过 RAII 清理。 + 对于实际接收者拥有类型化能力的方法,VM 在体前复查当前契约,并强制该源码方法自己的参数/返回注解,即使全局源码检查开关关闭。参数更宽的实现仍可以通过具体方法接受额外值;不会把实现收窄成 Trait 的规范参数集合,也不删除原生方便调用的省略参数形式。未携带相应能力的普通函数调用仍沿全局配置,不是对所有自由函数的静态类型系统。 除了实现自己的返回注解,所有匹配的类型化 Trait 返回约束也检查最终实际结果,覆盖源码/原生及尾委托链。执行帧/原生作用域保留这些定义的执行根,失败和预算清理均释放。方法体中修改其他类型的动态注解可能使返回对象的能力失效,不能仅依赖体前形式签名比较;最终 Capability 匹配会再次校验。错误返回不回滚已经发生的业务修改。