Validate declared class fields at construction and attribute writes

This commit is contained in:
2026-10-22 11:44:09 +08:00
parent 6346041f77
commit 20e29e3c5a
13 changed files with 342 additions and 26 deletions
+2 -2
View File
@@ -6,13 +6,13 @@
| --- | --- |
| [lib.rs](lib.rs) | 包边界、对象类型/ObjectStore 公开导出(含Trait/MethodRequirement/SourceSignature/AnnotationType)与独立存储 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定义/声明/方法参数约束、原生构造委托、声明环境托管边、字段构造/写入检查与注解解析/匹配 |
| [native.rs](native.rs) | NativeContext、上下文原生函数、作用域语言函数工厂、显式能力声明入口、函数标记、根与非递归委托调用 |
| [changes.rs](changes.rs) | 合并变更批次、变更键与有界通知额度 |
| [number.rs](number.rs) | Integer/Decimal/Rational/BinaryFloat/Complex 的无损表示和装箱载荷 |
| [builtins.rs](builtins.rs) | String、Dict、Cell、Function、BaseException 载荷与 trace 边、语言函数标记 |
| [heap.rs](heap.rs) | 私有 Heap、不可伪造 ObjectRef、稳定 Box、引用事务、迭代释放、循环回收、后代契约更新的私有借用枚举、同类型附加幂等、只读变更屏障/全调用及嵌套子调用读取跟踪及私有边界测试 |
| [store.rs](store.rs) | ObjectStore、空堆创建、对象操作、retain/release 和回收入口、弱 Function 类型配置与分配分类 |
| [store.rs](store.rs) | ObjectStore、空堆创建、字段检查配置/属性写入屏障、对象操作、retain/release 和回收入口、弱 Function 类型配置与分配分类 |
| [store_builtins.rs](store_builtins.rs) | 载荷校验、代码/函数工厂、Dict/Cell 操作及键语义 |
| [instruction.rs](instruction.rs) | Opcode、Instruction、wordcode 编解码、MakeFunction/比较参数与跳转读取接口 |
| [code.rs](code.rs) | CodeLayout、EndorCode/EndorCodeObject、可选源码签名、共享不可变代码和操作数/控制流/栈验证 |
+19
View File
@@ -11,9 +11,21 @@ use crate::{
pub struct ObjectStore {
pub(crate) heap: Heap,
function_type: Option<WeakObjectRef>,
pub(crate) field_type_checks: bool,
}
impl ObjectStore {
/// Opt-in declared-field checks, shared by VM and NativeContext writes.
pub fn set_field_type_checks(&mut self, enabled: bool) -> Result<(), VmError> {
if self.is_read_only() {
return Err(VmError::UnsupportedOperation(
"configure field type checks during read-only call",
));
}
self.field_type_checks = enabled;
Ok(())
}
/// Existing objects are immutable; fresh result graphs may be constructed.
/// Scopes must end in LIFO order. Read handles are borrowed, never GC roots.
pub fn begin_read_only(&mut self) -> usize {
@@ -129,6 +141,7 @@ impl ObjectStore {
Ok(Self {
heap: Heap::new()?,
function_type: None,
field_type_checks: false,
})
}
@@ -280,6 +293,9 @@ impl ObjectStore {
if name == "type" && self.type_of(reference)?.is_some() {
return Err(VmError::UnsupportedOperation("write object type"));
}
if self.field_type_checks {
self.check_field_write(reference, &name, &value)?;
}
let storage = *self
.attribute_path(reference)?
.last()
@@ -288,6 +304,9 @@ impl ObjectStore {
}
pub fn del_attr(&mut self, reference: ObjectRef, name: &str) -> Result<(), VmError> {
if self.field_type_checks && self.declared_field(reference, name)?.is_some() {
return Err(VmError::UnsupportedOperation("delete declared class field"));
}
if name == "type" && self.type_of(reference)?.is_some() {
return Err(VmError::UnsupportedOperation("delete object type"));
}
+142 -13
View File
@@ -21,7 +21,7 @@ pub enum TypeConstructor {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClassField {
pub name: String,
/// Preserved source annotation, like current function annotations.
/// Source annotation resolved at checked construction/field writes.
pub type_name: String,
}
@@ -35,6 +35,8 @@ pub struct EndorTypeObject {
constructor: TypeConstructor,
methods: BTreeMap<String, ObjectRef>,
fields: Vec<ClassField>,
// Declaring execution environment; a managed edge, never an external root.
field_scope: Option<ObjectRef>,
traits: Vec<ObjectRef>,
}
@@ -69,6 +71,9 @@ impl ObjectPayload for EndorTypeObject {
{
visit(function);
}
if let Some(scope) = self.field_scope {
visit(scope);
}
for capability in &self.traits {
visit(*capability);
}
@@ -121,6 +126,8 @@ impl NativeCallable for Construct {
});
}
let fields = definition.fields.clone();
cx.store()
.check_class_field_arguments(type_object, arguments)?;
let instance = cx.new_object()?;
cx.attach_type(instance, type_object)?;
for (field, value) in fields.iter().zip(arguments) {
@@ -149,6 +156,7 @@ impl ObjectStore {
constructor: TypeConstructor::Record,
methods: BTreeMap::new(),
fields: Vec::new(),
field_scope: None,
traits: Vec::new(),
})?;
let mut allocated_type = None;
@@ -159,6 +167,7 @@ impl ObjectStore {
constructor: TypeConstructor::Abstract,
methods: BTreeMap::new(),
fields: Vec::new(),
field_scope: None,
traits: Vec::new(),
})?;
allocated_type = Some(type_type);
@@ -200,6 +209,7 @@ impl ObjectStore {
constructor,
methods: BTreeMap::new(),
fields,
field_scope: None,
traits: Vec::new(),
})?;
if let Err(error) = self.attach_type(definition, metatype) {
@@ -284,6 +294,7 @@ impl ObjectStore {
constructor: definition.constructor,
methods,
fields: definition.fields.clone(),
field_scope: definition.field_scope,
traits: definition.traits.clone(),
};
self.heap.replace_payload(type_object, updated)
@@ -299,6 +310,23 @@ impl ObjectStore {
own_fields: Vec<ClassField>,
methods: BTreeMap<String, ObjectRef>,
) -> Result<ObjectRef, VmError> {
self.new_class_with_scope(name, base, own_fields, methods, None)
}
/// Source class builder with a traced function environment for field names.
/// None supports primitive annotations and the declaring class itself.
pub fn new_class_with_scope(
&mut self,
name: impl Into<String>,
base: ObjectRef,
own_fields: Vec<ClassField>,
methods: BTreeMap<String, ObjectRef>,
scope: Option<ObjectRef>,
) -> Result<ObjectRef, VmError> {
if let Some(scope) = scope {
self.payload::<crate::EndorFunctionObject>(scope, "function")?;
}
let field_scope = if own_fields.is_empty() { None } else { scope };
let definition = self.payload::<EndorTypeObject>(base, "Type")?;
if matches!(definition.constructor, TypeConstructor::Native(_)) {
return Err(VmError::UnsupportedOperation(
@@ -309,6 +337,7 @@ impl ObjectStore {
let mut fields = definition.fields.clone();
for field in own_fields {
if field.name.is_empty()
|| field.type_name.is_empty()
|| field.name == "type"
|| fields.iter().any(|old| old.name == field.name)
|| self.lookup_method(base, &field.name)?.is_some()
@@ -340,6 +369,7 @@ impl ObjectStore {
constructor,
methods,
fields,
field_scope,
traits: Vec::new(),
})?;
if let Err(error) = self
@@ -754,6 +784,116 @@ impl AnnotationType {
}
}
impl ObjectStore {
fn primitive_annotation(name: &str) -> Option<AnnotationType> {
match name {
"Object" => Some(AnnotationType::Any),
"Unit" => Some(AnnotationType::Unit),
"Number" => Some(AnnotationType::Number),
"String" | "Text" => Some(AnnotationType::String),
"Bool" => Some(AnnotationType::Bool),
"Function" => Some(AnnotationType::Function),
"Type" => Some(AnnotationType::Type),
"Trait" => Some(AnnotationType::Trait),
_ => None,
}
}
pub(crate) fn declared_field(
&self,
object: ObjectRef,
name: &str,
) -> Result<Option<(ObjectRef, &ClassField)>, VmError> {
self.declared_field_in_type(self.type_of(object)?, name)
}
fn declared_field_in_type(
&self,
mut current: Option<ObjectRef>,
name: &str,
) -> Result<Option<(ObjectRef, &ClassField)>, VmError> {
while let Some(definition) = current {
let data = self.payload::<EndorTypeObject>(definition, "Type")?;
let inherited = match data.base {
Some(base) => self.payload::<EndorTypeObject>(base, "Type")?.fields.len(),
None => 0,
};
if let Some(field) = data.fields[inherited..]
.iter()
.find(|field| field.name == name)
{
return Ok(Some((definition, field)));
}
current = data.base;
}
Ok(None)
}
pub(crate) fn check_field_write(
&self,
object: ObjectRef,
name: &str,
value: &Value,
) -> Result<(), VmError> {
let Some((owner, field)) = self.declared_field(object, name)? else {
return Ok(());
};
self.check_field_value(owner, field, value)
}
/// NativeTyped allocators can validate their field suffix before any effects.
/// Count is always checked; annotation matching follows the opt-in setting.
pub fn check_class_field_arguments(
&self,
definition: ObjectRef,
arguments: &[Value],
) -> Result<(), VmError> {
let data = self.payload::<EndorTypeObject>(definition, "Type")?;
if arguments.len() != data.fields.len() {
return Err(VmError::ArgumentCount {
name: data.name.clone(),
required: data.fields.len(),
maximum: data.fields.len(),
provided: arguments.len(),
});
}
if self.field_type_checks {
for (field, value) in data.fields.iter().zip(arguments) {
let (owner, field) = self
.declared_field_in_type(Some(definition), &field.name)?
.expect("class field has a declaring type");
self.check_field_value(owner, field, value)?;
}
}
Ok(())
}
fn check_field_value(
&self,
owner: ObjectRef,
field: &ClassField,
value: &Value,
) -> Result<(), VmError> {
let definition = self.payload::<EndorTypeObject>(owner, "Type")?;
let expected = if field.type_name == definition.name {
AnnotationType::Nominal(owner)
} else if let Some(primitive) = Self::primitive_annotation(&field.type_name) {
primitive
} else if let Some(scope) = definition.field_scope {
self.annotation_type(scope, &field.type_name, None)?
} else {
return Err(VmError::UnknownAnnotation(field.type_name.clone()));
};
if !self.matches_annotation(value, expected)? {
return Err(VmError::TypeMismatch {
function: definition.name.clone(),
position: format!("field {}", field.name),
expected: field.type_name.clone(),
actual: self.value_type_name(value)?,
});
}
Ok(())
}
pub fn annotation_type(
&self,
function: ObjectRef,
@@ -781,18 +921,7 @@ impl ObjectStore {
current = payload.base;
}
}
let primitive = match name {
"Object" => Some(AnnotationType::Any),
"Unit" => Some(AnnotationType::Unit),
"Number" => Some(AnnotationType::Number),
"String" | "Text" => Some(AnnotationType::String),
"Bool" => Some(AnnotationType::Bool),
"Function" => Some(AnnotationType::Function),
"Type" => Some(AnnotationType::Type),
"Trait" => Some(AnnotationType::Trait),
_ => None,
};
if let Some(primitive) = primitive {
if let Some(primitive) = Self::primitive_annotation(name) {
return Ok(primitive);
}
let value = self