Prevent instance attributes from shadowing typed capability methods

This commit is contained in:
2026-10-22 15:28:19 +08:00
parent c96dce118b
commit 0513b96dfb
7 changed files with 349 additions and 4 deletions
+1 -1
View File
@@ -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) | 合并变更批次、变更键与有界通知额度 |
+1
View File
@@ -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)?;
}
+7
View File
@@ -12,6 +12,9 @@ pub struct ObjectStore {
pub(crate) heap: Heap,
function_type: Option<WeakObjectRef>,
pub(crate) field_type_checks: bool,
pub(crate) active_attribute_checks: std::sync::Mutex<
std::collections::HashMap<std::thread::ThreadId, std::collections::BTreeSet<ObjectRef>>,
>,
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()
+166 -2
View File
@@ -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::<EndorTypeObject>(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::<EndorTraitObject>()
{
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::<EndorTypeObject>() {
@@ -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::<EndorTraitObject>(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::<EndorTypeObject>(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::<EndorTraitObject>(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::<EndorTraitObject>(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::<EndorTypeObject>() {
name == "name" || name == "base" || self.lookup_method(object, name)?.is_some()
} else {
payload.as_any().is::<EndorTraitObject>() && 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<Vec<String>, 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::<EndorTraitObject>(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<bool, VmError> {
self.payload::<EndorTraitObject>(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),
}
}
+1 -1
View File
@@ -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)。
+171
View File
@@ -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<Option<ObjectRef>, 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<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();
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);
}
+2
View File
@@ -137,6 +137,8 @@ ObjectStore/Vm::new_typed_trait(name, Vec<TypedMethodRequirement>) 以每项 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 匹配会再次校验。错误返回不回滚已经发生的业务修改。