Validate declared class fields at construction and attribute writes
This commit is contained in:
@@ -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、可选源码签名、共享不可变代码和操作数/控制流/栈验证 |
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user