feat: inherit native allocators and forward managed field storage

This commit is contained in:
2026-10-21 02:05:52 +08:00
parent a970fd7707
commit be9a032647
13 changed files with 275 additions and 23 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# objects
Endor 核心对象与对象存储包,零外部依赖,不依赖 core。包含 EndorObject、ObjectPayload、Value、ObjectRef、Number、字符串/字典/Cell/函数/异常载荷,以及代码对象与 wordcode 数据定义、编解码和静态验证。提供显式引导的 Object/Type 名义类型,已接入普通源码 class 和继承方法分派,trait、原生分配继承与完整元类行为仍待实现。
Endor 核心对象与对象存储包,零外部依赖,不依赖 core。包含 EndorObject、ObjectPayload、Value、ObjectRef、Number、字符串/字典/Cell/函数/异常载荷,以及代码对象与 wordcode 数据定义、编解码和静态验证。提供显式引导的 Object/Type 名义类型,已接入普通源码 class 和继承方法分派,已支持显式 NativeTyped 分配继承和可转移字段存储;trait 与完整元类行为仍待实现。
| 内容 | 职责 |
| --- | --- |
+6
View File
@@ -51,6 +51,12 @@ impl CompareOp {
/// Rust Drop releases host resources when the payload is destroyed.
/// Equal payloads must provide compatible hashes, or reject hashing explicitly.
pub trait ObjectPayload: Any + fmt::Debug + Send + Sync {
/// Ordinary field storage may be indirect, independently of type identity.
/// Targets must be traced; retargeting records Contents on the forwarder.
fn attribute_target(&self, _store: &crate::ObjectStore) -> Result<Option<ObjectRef>, VmError> {
Ok(None)
}
/// Shared-reference facades may expose the current referent's type. The
/// referent must also be held by traced managed edges. Ordinary objects
/// keep their immutable header type; this does not change inheritance.
+28 -2
View File
@@ -110,6 +110,24 @@ impl ObjectStore {
self.heap.get(reference)
}
/// Borrowed storage path: depend on Contents for intermediate objects and
/// Attribute(name) on the final storage object. Every handle is validated.
pub fn attribute_path(&self, reference: ObjectRef) -> Result<Vec<ObjectRef>, VmError> {
let mut path = Vec::new();
let mut seen = std::collections::BTreeSet::new();
let mut current = reference;
loop {
if !seen.insert(current) {
return Err(VmError::InvalidArgument("cyclic attribute storage"));
}
path.push(current);
match self.object(current)?.payload().attribute_target(self)? {
Some(target) => current = target,
None => return Ok(path),
}
}
}
pub fn retain(&mut self, reference: ObjectRef) -> Result<(), VmError> {
self.heap.retain(reference)
}
@@ -187,14 +205,22 @@ impl ObjectStore {
if name == "type" && self.type_of(reference)?.is_some() {
return Err(VmError::UnsupportedOperation("write object type"));
}
self.heap.set_attr(reference, name, value)
let storage = *self
.attribute_path(reference)?
.last()
.expect("nonempty attribute path");
self.heap.set_attr(storage, name, value)
}
pub fn del_attr(&mut self, reference: ObjectRef, name: &str) -> Result<(), VmError> {
if name == "type" && self.type_of(reference)?.is_some() {
return Err(VmError::UnsupportedOperation("delete object type"));
}
self.heap.del_attr(reference, name)
let storage = *self
.attribute_path(reference)?
.last()
.expect("nonempty attribute path");
self.heap.del_attr(storage, name)
}
/// Validate a borrowed object handle without taking an owned reference.
+35 -10
View File
@@ -12,6 +12,9 @@ pub enum TypeConstructor {
Record,
/// Delegate to a managed callable. It must return an instance of this type.
Native(ObjectRef),
/// Opt-in allocator inheritance. The requested nominal type is prepended
/// to the constructor arguments; the allocator must create that real type.
NativeTyped(ObjectRef),
Abstract,
}
@@ -41,6 +44,9 @@ impl EndorTypeObject {
pub fn base(&self) -> Option<ObjectRef> {
self.base
}
pub fn fields(&self) -> &[ClassField] {
&self.fields
}
}
impl ObjectPayload for EndorTypeObject {
@@ -54,7 +60,9 @@ impl ObjectPayload for EndorTypeObject {
if let Some(base) = self.base {
visit(base);
}
if let TypeConstructor::Native(function) = self.constructor {
if let TypeConstructor::Native(function) | TypeConstructor::NativeTyped(function) =
self.constructor
{
visit(function);
}
for method in self.methods.values() {
@@ -84,6 +92,15 @@ impl NativeCallable for Construct {
function,
arguments: arguments.to_vec(),
}),
TypeConstructor::NativeTyped(function) => {
let mut values = Vec::with_capacity(arguments.len() + 1);
values.push(Value::Object(type_object));
values.extend_from_slice(arguments);
Ok(NativeResult::Invoke {
function,
arguments: values,
})
}
TypeConstructor::Abstract => {
Err(VmError::UnsupportedOperation("abstract type construction"))
}
@@ -157,7 +174,10 @@ impl ObjectStore {
base: ObjectRef,
constructor: TypeConstructor,
) -> Result<ObjectRef, VmError> {
self.payload::<EndorTypeObject>(base, "Type")?;
let fields = self
.payload::<EndorTypeObject>(base, "Type")?
.fields
.clone();
let metatype = self
.type_of(base)?
.ok_or(VmError::InvalidArgument("base type has no metatype"))?;
@@ -170,7 +190,7 @@ impl ObjectStore {
base: Some(base),
constructor,
methods: BTreeMap::new(),
fields: Vec::new(),
fields,
})?;
if let Err(error) = self.attach_type(definition, metatype) {
self.release(definition)?;
@@ -258,8 +278,8 @@ impl ObjectStore {
}
/// Atomically build a source class, including inherited constructor fields.
/// Native allocation needs its own protocol; never forge a Record payload
/// claiming to be a native visual instance.
/// Only explicitly inheritable native allocators are reused. Abstract
/// subclasses remain abstract; no Record can forge a native payload.
pub fn new_class(
&mut self,
name: impl Into<String>,
@@ -268,11 +288,12 @@ impl ObjectStore {
methods: BTreeMap<String, ObjectRef>,
) -> Result<ObjectRef, VmError> {
let definition = self.payload::<EndorTypeObject>(base, "Type")?;
if !matches!(definition.constructor, TypeConstructor::Record) {
if matches!(definition.constructor, TypeConstructor::Native(_)) {
return Err(VmError::UnsupportedOperation(
"source subclass of native or abstract allocator",
"native allocator does not opt into source subclass construction",
));
}
let constructor = definition.constructor;
let mut fields = definition.fields.clone();
for field in own_fields {
if field.name.is_empty()
@@ -304,7 +325,7 @@ impl ObjectStore {
let class = self.allocate(EndorTypeObject {
name,
base: Some(base),
constructor: TypeConstructor::Record,
constructor,
methods,
fields,
})?;
@@ -406,7 +427,11 @@ impl ObjectStore {
return Ok(AttributeLookup::Value(Value::Object(function)));
}
}
match self.heap.get_attr(receiver, name) {
let storage = *self
.attribute_path(receiver)?
.last()
.expect("nonempty attribute path");
match self.heap.get_attr(storage, name) {
Ok(value) => return Ok(AttributeLookup::Value(value)),
Err(VmError::AttributeNotFound(_) | VmError::UnsupportedOperation("attributes")) => {}
Err(error) => return Err(error),
@@ -418,7 +443,7 @@ impl ObjectStore {
return Err(VmError::AttributeNotFound(name.into()));
}
self.heap
.get_attr(receiver, name)
.get_attr(storage, name)
.map(AttributeLookup::Value)
}
}
+1
View File
@@ -4,6 +4,7 @@
| 文件 | 覆盖 |
| --- | --- |
| [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)。
+78
View File
@@ -0,0 +1,78 @@
use objects::{ObjectPayload, ObjectRef, ObjectStore, TypeConstructor, Value, VmError};
#[derive(Debug)]
struct Proxy(ObjectRef);
impl ObjectPayload for Proxy {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn kind_name(&self) -> &'static str {
"proxy"
}
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
visit(self.0);
}
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();
let roots = store.bootstrap_types().unwrap();
let class = store
.new_type("Proxy", roots.object, TypeConstructor::Record)
.unwrap();
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();
store.release(old_value).unwrap();
let proxy = store.allocate(Proxy(storage)).unwrap();
store.attach_type(proxy, class).unwrap();
store.release(storage).unwrap();
assert_eq!(store.attribute_path(proxy).unwrap(), vec![proxy, storage]);
assert_eq!(store.get_attr(proxy, "type").unwrap(), Value::Object(class));
assert_eq!(
store.get_attr(proxy, "value").unwrap(),
Value::Object(old_value)
);
let next = store.new_object().unwrap();
store.replace_payload(proxy, Proxy(next)).unwrap();
store.release(next).unwrap();
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());
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();
let storage = store.new_object().unwrap();
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);
let other = ObjectStore::new().unwrap();
assert_eq!(other.attribute_path(proxy), Err(VmError::ForeignHandle));
store.release(storage).unwrap();
store.release(proxy).unwrap();
assert_eq!(store.collect_cycles(), 1);
}