Prevent instance attributes from shadowing typed capability methods
This commit is contained in:
@@ -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) | 合并变更批次、变更键与有界通知额度 |
|
||||
|
||||
@@ -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)?;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)。
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 匹配会再次校验。错误返回不回滚已经发生的业务修改。
|
||||
|
||||
Reference in New Issue
Block a user