From 20e29e3c5a4c014a3e67aece65eb1c133a7794ec Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Thu, 22 Oct 2026 11:44:09 +0800 Subject: [PATCH] Validate declared class fields at construction and attribute writes --- crates/compiler/tests/README.md | 2 +- crates/compiler/tests/classes.rs | 151 +++++++++++++++++++++++++++++- crates/core/src/README.md | 4 +- crates/core/src/eval.rs | 3 +- crates/core/src/vm.rs | 1 + crates/objects/src/README.md | 4 +- crates/objects/src/store.rs | 19 ++++ crates/objects/src/types.rs | 155 ++++++++++++++++++++++++++++--- docs/README.md | 2 +- docs/contributing/handoff.md | 2 +- docs/runtime/README.md | 2 +- docs/runtime/native-calls.md | 2 + docs/runtime/types.md | 21 ++++- 13 files changed, 342 insertions(+), 26 deletions(-) diff --git a/crates/compiler/tests/README.md b/crates/compiler/tests/README.md index 8a438f6..14a60fe 100644 --- a/crates/compiler/tests/README.md +++ b/crates/compiler/tests/README.md @@ -1,6 +1,6 @@ # 编译链路检查 -[classes.rs](classes.rs) 覆盖源码 class、NativeTyped 实际原生载荷继承和多层字段构造、继承字段构造、动态方法覆盖、绑定接收者保活/回收、绑定后参数数量和重复成员/缺少 self 的预执行诊断。只编译,不在本地运行。 +[classes.rs](classes.rs) 覆盖源码 class、NativeTyped 实际原生载荷继承和多层字段构造、声明字段检查的构造/写入/继承/环境/GC、继承字段构造、动态方法覆盖、绑定接收者保活/回收、绑定后参数数量和重复成员/缺少 self 的预执行诊断。只编译,不在本地运行。 [pipeline.rs](pipeline.rs) 将真实 Endor 示例交给 reader/parser/名称分析/代码生成/VM,检查函数值、前向引用、局部遮蔽、精确数字常量、语义诊断、宿主全局与属性回调、对象结果保活、跨 VM 模板装载、源码签名保留和参数/返回失败清理、反复调用、错误帧清理、递归限额、共享交互命名空间及超过 255 的操作数。 diff --git a/crates/compiler/tests/classes.rs b/crates/compiler/tests/classes.rs index bc035b6..a124527 100644 --- a/crates/compiler/tests/classes.rs +++ b/crates/compiler/tests/classes.rs @@ -40,6 +40,7 @@ impl NativeCallable for WidgetFactory { if values.len() != fields.len() { return Err(VmError::InvalidArgument("widget field count")); } + cx.store().check_class_field_arguments(*requested, values)?; let instance = cx.allocate(NativeWidget(native_label.clone()))?; cx.attach_type(instance, *requested)?; for (field, value) in fields.iter().zip(values) { @@ -52,6 +53,7 @@ impl NativeCallable for WidgetFactory { #[test] fn source_subclasses_preserve_native_allocation_fields_and_nominal_identity() { let mut vm = Vm::new().unwrap(); + vm.set_source_type_checks(true).unwrap(); let globals = vm.new_dict().unwrap(); let roots = vm.bootstrap_types().unwrap(); let factory = vm @@ -79,6 +81,15 @@ fn source_subclasses_preserve_native_allocation_fields_and_nominal_identity() { execute(&mut vm, globals, source, ParseMode::Module), Value::Nil ); + assert!(matches!( + try_execute( + &mut vm, + globals, + r#"Grand("native", "bad", "extra")"#, + ParseMode::Expression + ), + Err(VmError::TypeMismatch { .. }) + )); let value = execute( &mut vm, globals, @@ -111,6 +122,15 @@ fn source_subclasses_preserve_native_allocation_fields_and_nominal_identity() { } fn execute(vm: &mut Vm, globals: ObjectRef, source: &str, mode: ParseMode) -> Value { + try_execute(vm, globals, source, mode).unwrap() +} + +fn try_execute( + vm: &mut Vm, + globals: ObjectRef, + source: &str, + mode: ParseMode, +) -> Result { let names = vm .dict_items(globals) .unwrap() @@ -126,7 +146,7 @@ fn execute(vm: &mut Vm, globals: ObjectRef, source: &str, mode: ParseMode) -> Va let source = EndorReader::from_text("", source); let template = compile_source(&source, mode, &CompileOptions { globals: names }).unwrap(); let code = template.load(vm).unwrap(); - let result = vm.execute(code, globals).unwrap(); + let result = vm.execute(code, globals); vm.release(code).unwrap(); result } @@ -221,3 +241,132 @@ fn duplicate_members_and_missing_receivers_fail_before_execution() { ); } } + +#[test] +fn declared_field_checks_cover_construction_inheritance_writes_and_gc() { + let mut vm = Vm::new().unwrap(); + vm.set_source_type_checks(true).unwrap(); + let globals = vm.new_dict().unwrap(); + let roots = vm.bootstrap_types().unwrap(); + vm.dict_set( + globals, + Value::Text("Object".into()), + Value::Object(roots.object), + ) + .unwrap(); + vm.release(roots.object).unwrap(); + vm.release(roots.type_type).unwrap(); + execute( + &mut vm, + globals, + r#" +class Base { count: Number } +class Child: Base { label: String } +value = Child(9, "ok") +class Holder { child: Base } +holder = Holder(value) +class Unknown { value: Missing } +"#, + ParseMode::Interactive, + ); + let Value::Object(value) = vm + .dict_get(globals, &Value::Text("value".into())) + .unwrap() + .unwrap() + else { + panic!("instance") + }; + let version = vm.object_store().change_version(value).unwrap(); + for source in [ + r#"Child("bad", "ok")"#, + "Child(9, 2)", + "Holder(1)", + "Unknown(1)", + r#"value.count = "bad""#, + ] { + assert!(matches!( + try_execute(&mut vm, globals, source, ParseMode::Interactive), + Err(VmError::TypeMismatch { .. } | VmError::UnknownAnnotation(_)) + )); + } + assert_eq!( + vm.get_attr(value, "count").unwrap(), + Value::Number(9.into()) + ); + assert_eq!(vm.object_store().change_version(value).unwrap(), version); + assert!(vm.del_attr(value, "count").is_err()); + vm.set_attr(value, "count", Value::Number(10.into())) + .unwrap(); + execute(&mut vm, globals, "class Base {}", ParseMode::Interactive); + // Other nominal names resolve dynamically in the declaring environment. + assert!(matches!( + try_execute(&mut vm, globals, "Holder(value)", ParseMode::Expression), + Err(VmError::TypeMismatch { .. }) + )); + // An inherited scalar field still belongs to its original declaration. + vm.set_attr(value, "count", Value::Number(11.into())) + .unwrap(); + vm.dict_clear(globals).unwrap(); + vm.release(globals).unwrap(); + vm.collect_cycles(); + assert_eq!(vm.object_count(), 0); +} + +#[test] +fn checked_fields_keep_declaring_self_type_and_can_be_opted_out() { + let mut vm = Vm::new().unwrap(); + vm.set_source_type_checks(true).unwrap(); + let globals = vm.new_dict().unwrap(); + let roots = vm.bootstrap_types().unwrap(); + vm.dict_set( + globals, + Value::Text("Object".into()), + Value::Object(roots.object), + ) + .unwrap(); + vm.release(roots.object).unwrap(); + vm.release(roots.type_type).unwrap(); + // Object permits nil, so a separate numeric field provides an initial instance. + execute( + &mut vm, + globals, + r#" +class Link { next: Link } +class Seed: Link {} +"#, + ParseMode::Interactive, + ); + // A recursive nonnullable field can be initialized by the trusted host in two steps. + let Value::Object(link) = vm + .dict_get(globals, &Value::Text("Link".into())) + .unwrap() + .unwrap() + else { + panic!("class") + }; + let seed = vm.new_object().unwrap(); + vm.attach_type(seed, link).unwrap(); + vm.set_attr(seed, "next", Value::Object(seed)).unwrap(); + vm.dict_set(globals, Value::Text("old".into()), Value::Object(seed)) + .unwrap(); + execute( + &mut vm, + globals, + "class Link { value: Number }", + ParseMode::Interactive, + ); + vm.set_attr(seed, "next", Value::Object(seed)).unwrap(); + assert!(matches!( + vm.set_attr(seed, "next", Value::Nil), + Err(VmError::TypeMismatch { .. }) + )); + vm.set_source_type_checks(false).unwrap(); + vm.set_attr(seed, "next", Value::Nil).unwrap(); + vm.set_source_type_checks(true).unwrap(); + vm.set_attr(seed, "next", Value::Object(seed)).unwrap(); + vm.release(seed).unwrap(); + vm.dict_clear(globals).unwrap(); + vm.release(globals).unwrap(); + vm.collect_cycles(); + assert_eq!(vm.object_count(), 0); +} diff --git a/crates/core/src/README.md b/crates/core/src/README.md index 2f1c8c2..d30a784 100644 --- a/crates/core/src/README.md +++ b/crates/core/src/README.md @@ -5,9 +5,9 @@ | 文件 | 职责 | | --- | --- | | [lib.rs](lib.rs) | VM 类型导出、objects 类型兼容重导出、宿主调用与字节码 rustdoc 示例 | -| [vm.rs](vm.rs) | Vm 拥有 ObjectStore 与错误位置记录;对象操作和宿主 new_class 和显式Trait注册/关系查询显式委托给 objects;源码检查开关与函数类型配置 | +| [vm.rs](vm.rs) | Vm 拥有 ObjectStore 与错误位置记录;对象操作和宿主 new_class 和显式Trait注册/关系查询显式委托给 objects;函数/字段检查统一开关与函数类型配置 | | [frame.rs](frame.rs) | EndorFrame、栈条目、返回类型执行根、执行位置元数据、ExecutionLimits | -| [eval.rs](eval.rs) | execute/call、只读调用与依赖结果、参数绑定、可选注解检查、帧切换、取指分派、MakeFunction、结果根转移与失败清理 | +| [eval.rs](eval.rs) | execute/call、只读调用与依赖结果、参数绑定、可选注解检查、源码类字段环境绑定、帧切换、取指分派、MakeFunction、结果根转移与失败清理 | ## 常见修改入口 diff --git a/crates/core/src/eval.rs b/crates/core/src/eval.rs index 025f8e9..970e6d1 100644 --- a/crates/core/src/eval.rs +++ b/crates/core/src/eval.rs @@ -501,7 +501,8 @@ impl Vm { _ => Err(VmError::ObjectKindMismatch("class method")), }) .collect::, _>>()?; - self.objects.new_class(name, base, fields, methods) + self.objects + .new_class_with_scope(name, base, fields, methods, Some(frame.function)) } fn eval_frames( diff --git a/crates/core/src/vm.rs b/crates/core/src/vm.rs index 42d5870..ad2261c 100644 --- a/crates/core/src/vm.rs +++ b/crates/core/src/vm.rs @@ -21,6 +21,7 @@ impl Vm { "configure source type checks during read-only call", )); } + self.objects.set_field_type_checks(enabled)?; self.source_type_checks = enabled; Ok(()) } diff --git a/crates/objects/src/README.md b/crates/objects/src/README.md index 7c621b5..bcf5f61 100644 --- a/crates/objects/src/README.md +++ b/crates/objects/src/README.md @@ -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、可选源码签名、共享不可变代码和操作数/控制流/栈验证 | diff --git a/crates/objects/src/store.rs b/crates/objects/src/store.rs index 192099c..9bd62b8 100644 --- a/crates/objects/src/store.rs +++ b/crates/objects/src/store.rs @@ -11,9 +11,21 @@ use crate::{ pub struct ObjectStore { pub(crate) heap: Heap, function_type: Option, + 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")); } diff --git a/crates/objects/src/types.rs b/crates/objects/src/types.rs index 181f49d..ca41ba6 100644 --- a/crates/objects/src/types.rs +++ b/crates/objects/src/types.rs @@ -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, fields: Vec, + // Declaring execution environment; a managed edge, never an external root. + field_scope: Option, traits: Vec, } @@ -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, methods: BTreeMap, ) -> Result { + 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, + base: ObjectRef, + own_fields: Vec, + methods: BTreeMap, + scope: Option, + ) -> Result { + if let Some(scope) = scope { + self.payload::(scope, "function")?; + } + let field_scope = if own_fields.is_empty() { None } else { scope }; let definition = self.payload::(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 { + 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, VmError> { + self.declared_field_in_type(self.type_of(object)?, name) + } + + fn declared_field_in_type( + &self, + mut current: Option, + name: &str, + ) -> Result, VmError> { + while let Some(definition) = current { + let data = self.payload::(definition, "Type")?; + let inherited = match data.base { + Some(base) => self.payload::(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::(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::(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 diff --git a/docs/README.md b/docs/README.md index 1ba7ac9..484306a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,7 +6,7 @@ | 解释执行、资源修订与引擎升级 | [执行与版本](architecture/execution-and-versions.md) | frontend/compiler/core | | EndorObject、句柄、默认操作 | [对象模型](runtime/objects.md) | objects/src | | 原生上下文、只读调用、子调用读取依赖、委托与变更通知 | [原生调用](runtime/native-calls.md) | objects/src/native.rs、changes.rs、core/src/eval.rs | -| 托管类型、宿主类注册、原生分配继承、字段存储、显式能力、函数分类与可选源码注解检查 | [名义类型](runtime/types.md) | objects/src/types.rs | +| 托管类型、宿主类注册、原生分配继承、字段存储、显式能力、函数分类、声明字段与可选源码注解检查 | [名义类型](runtime/types.md) | objects/src/types.rs | | Number、String、Dict、Cell、Function | [基本对象](runtime/builtins.md) | objects/src | | 源码读取与来源 | [Reader](runtime/reader.md) | frontend/src/reader.rs | | 词法、语法、裸赋值和诊断 | [Frontend](runtime/frontend.md) | frontend/src/lexer.peg/grammar.peg | diff --git a/docs/contributing/handoff.md b/docs/contributing/handoff.md index 0761366..99c1397 100644 --- a/docs/contributing/handoff.md +++ b/docs/contributing/handoff.md @@ -21,7 +21,7 @@ Aster 是可嵌入引擎,只保留 objects、core、frontend、compiler。Obje ## 当前缺口 -平台资源版本/依赖锁、源码闭包、数值算术、完整异常传播、内存配额、可靠取消、不可信代码沙箱、自动 GC 和宿主 panic 后执行根恢复尚未完成。源码函数参数/返回注解已支持默认关闭的运行时检查,Tessera 显式启用;普通名称按调用时函数全局解析,字段/静态类型/效果与原生统一签名仍未完成,见[类型机制](../runtime/types.md#源码函数注解检查)。不引入 Python 元类。UI 接入适配由 Tessera 实现。通用 ObjectPayload::assign_from 允许宿主对象选择共享引用赋值;协议和根计数归 Aster,UI 挂载、布局、绘制仍归 Tessera,见字节码 VM 的共享引用章节。 +平台资源版本/依赖锁、源码闭包、数值算术、完整异常传播、内存配额、可靠取消、不可信代码沙箱、自动 GC 和宿主 panic 后执行根恢复尚未完成。源码函数参数/返回注解已支持默认关闭的运行时检查,Tessera 显式启用;普通名称按调用时函数全局解析,声明字段构造/写入检查已接入同一可选开关;完整初始化、静态类型/效果与原生统一签名仍未完成,见[类型机制](../runtime/types.md#源码函数注解检查)。不引入 Python 元类。UI 接入适配由 Tessera 实现。通用 ObjectPayload::assign_from 允许宿主对象选择共享引用赋值;协议和根计数归 Aster,UI 挂载、布局、绘制仍归 Tessera,见字节码 VM 的共享引用章节。 ## 工作约定 diff --git a/docs/runtime/README.md b/docs/runtime/README.md index a402ea5..7f30e6a 100644 --- a/docs/runtime/README.md +++ b/docs/runtime/README.md @@ -2,7 +2,7 @@ | 文档 | 内容 | | --- | --- | -| [types.md](types.md) | 托管 Object/Type、名义继承、构造与类型边生命周期 | +| [types.md](types.md) | 托管 Object/Type、名义继承、声明字段/函数注解检查、构造与类型边生命周期 | | [objects.md](objects.md) | 对象、句柄与默认操作 | | [native-calls.md](native-calls.md) | 原生上下文、非递归委托、弱引用、托管赋值与变更批次 | | [builtins.md](builtins.md) | Number、String、Dict、Cell、Function、异常数据 | diff --git a/docs/runtime/native-calls.md b/docs/runtime/native-calls.md index ab5c5ed..75ff07b 100644 --- a/docs/runtime/native-calls.md +++ b/docs/runtime/native-calls.md @@ -64,3 +64,5 @@ take_changes 按对象与变更键合并同一批写入,返回最终版本。 通过 replace_payload、属性或容器事务更新;载荷内部自行变动不会自动通知。 远端回归用例见 [native_context.rs](../../crates/core/tests/native_context.rs);本地不执行测试。 + +声明字段检查启用时,NativeContext::set_attr 使用同一字段写入屏障。NativeTyped 分配器应在副作用前调用 ObjectStore::check_class_field_arguments 检查自己的字段参数切片,具体参数组合仍由宿主定义;配置、声明环境与生命周期的唯一契约见[名义类型](types.md#声明字段注解检查)。 diff --git a/docs/runtime/types.md b/docs/runtime/types.md index 3cb8c30..677be3c 100644 --- a/docs/runtime/types.md +++ b/docs/runtime/types.md @@ -56,7 +56,7 @@ class DetailedCounter: Counter { 方法表本身是类型对象拥有的托管边。宿主 define_method 可显式替换条目,不改变类型身份、基类或已有绑定方法选中的函数。bind_attr 返回一份拥有式根;get_attr 返回借用值和未绑定方法,宿主不能把两者的调用/释放协议混用。positional_parameter_count 会扣除绑定的接收者,供宿主判断事件回调的剩余源码参数。 -这是一份实现子集说明:构造器只按字段赋值,没有默认值、命名字段构造或隐式 init;字段注解保留名称但尚未约束字段写入;源码参数/返回注解支持下述宿主可选的运行时检查。源码类可继承 Record 或 NativeTyped 分配器;未选择支持继承的旧 Native 分配器仍明确报错。继承会保留分配器和父类字段,不能伪造带原生名义身份的普通 Record。字段构造参数如何与原生参数组合由宿主分配器负责;Tessera 使用原生参数在前、继承及本类字段在后的协议。trait 的正式源码语法、完整类型兼容性和效果系统仍待实现;宿主显式能力子集见下文。正式语言文档的候选语法不因这一实现被自动确认为规范。 +这是一份实现子集说明:构造器只按字段赋值,没有默认值、命名字段构造或隐式 init;字段注解在启用运行时检查后约束构造及字段写入,见下文;源码参数/返回注解支持下述宿主可选的运行时检查。源码类可继承 Record 或 NativeTyped 分配器;未选择支持继承的旧 Native 分配器仍明确报错。继承会保留分配器和父类字段,不能伪造带原生名义身份的普通 Record。字段构造参数如何与原生参数组合由宿主分配器负责;Tessera 使用原生参数在前、继承及本类字段在后的协议。trait 的正式源码语法、完整类型兼容性和效果系统仍待实现;宿主显式能力子集见下文。正式语言文档的候选语法不因这一实现被自动确认为规范。 ## 可转移的字段存储 @@ -81,7 +81,7 @@ implement_trait(Type, Trait)明确建立能力关系并校验必需方法。只 ## 源码函数注解检查 -`Vm::set_source_type_checks(true)` 启用源码函数调用的运行时检查,默认关闭;Tessera 显式启用,其他宿主须自行选择。Compiler 把形参注解、返回注解及可选的声明类名保存为 `SourceSignature`,模板装载后仍保留。宿主也可通过 `EndorCode::with_source_signature` 添加经过形参数量/非空名称校验的元数据;没有该元数据的宿主生成代码继续沿原调用协议执行。 +`Vm::set_source_type_checks(true)` 启用源码函数调用及声明字段写入的运行时检查,默认关闭;Tessera 显式启用,其他宿主须自行选择。Compiler 把形参注解、返回注解及可选的声明类名保存为 `SourceSignature`,模板装载后仍保留。宿主也可通过 `EndorCode::with_source_signature` 添加经过形参数量/非空名称校验的元数据;没有该元数据的宿主生成代码继续沿原调用协议执行。 每次调用在进入函数体前检查所有实参与用到的默认参数,并解析本次返回约束;`ReturnValue` 在交出结果前检查返回值。错误分别是 `UnknownAnnotation` 或带函数、参数/return、预期及实际名称的 `TypeMismatch`。参数失败不进入函数体;返回失败不回滚此前完成的业务写入。执行帧持有返回约束中实际 Type/Trait 的一份根,直到成功或失败清理;全局名字在调用中改变也不会释放这一约束。约束查询不写函数或类型对象,适用于只读布局/绘制调用。 @@ -96,6 +96,21 @@ implement_trait(Type, Trait)明确建立能力关系并校验必需方法。只 | Type/Trait | 真实 Type/Trait 定义载荷 | | 其他名称 | 从函数自己的 globals、其次创建时捕获的 builtins 找到实际 Type 或 Trait;前者检查名义实例/继承,后者检查显式能力 | -普通名称每次调用重新解析,不是静态推断或定义时冻结的类型绑定。同名新定义不等于旧定义;自由函数可观察后续全局类型名重绑定。方法注解中指向自身声明类的名称,优先从实际接收者祖先中查找仍持有该函数的方法定义,因此 REPL 重定义全局类名后,原实例保存的绑定方法仍可调用。其他类型名仍遵循上述全局解析规则。此机制不提供重写方差、字段赋值检查、泛型、效果类型或原生函数的统一签名元数据。 +普通名称每次调用重新解析,不是静态推断或定义时冻结的类型绑定。同名新定义不等于旧定义;自由函数可观察后续全局类型名重绑定。方法注解中指向自身声明类的名称,优先从实际接收者祖先中查找仍持有该函数的方法定义,因此 REPL 重定义全局类名后,原实例保存的绑定方法仍可调用。其他类型名仍遵循上述全局解析规则。此机制不提供重写方差、泛型、效果类型或原生函数的统一签名元数据。字段赋值检查见下文。 源码与原生函数的统一名义分类由 `ObjectStore::set_function_type`/`Vm::set_function_type` 显式配置。配置验证 Type、拒绝只读修改,只保留弱引用且不可改成另一个定义;设置后新分配的显式 `ObjectPayload::is_function` 载荷自动附加该类型,已有对象不追溯改写。语言函数、两类原生函数及绑定方法提供此标记;仅有 call 能力不算函数。配置类型过期则函数分配报错并清理新对象,不引入隐藏 GC 根。宿主应在创建函数前配置,并通过正常对象边保活定义。 + + +## 声明字段注解检查 + +`Vm::set_source_type_checks(true)` 同时启用 ObjectStore 的声明字段检查;独立存储宿主也可调用 `ObjectStore::set_field_type_checks(true)`,默认关闭。配置在只读作用域内被拒绝。启用不会追溯扫描已有对象,只检查此后发生的字段写入。 + +Record 构造在分配前校验全部位置字段。NativeTyped 分配器通过 `check_class_field_arguments(definition, values)` 在自身副作用前检查字段参数;方法始终检查数量,注解匹配遵循开关。字段参数与原生参数如何组合仍由分配器定义;Tessera 在原生参数之后放字段参数并在创建原生对象前检查。所有经 ObjectStore/Vm/NativeContext 的 `set_attr` 写入,包括 StoreAttr,按实际对象类型查找字段声明,在最终 attribute_target 存储变更前检查。失败保持该字段值、引用边和版本不变;不回滚先前完成的其他操作。启用后 `del_attr` 拒绝删除声明字段;未声明的动态属性沿用现有行为。 + +源码 BuildClass 把声明执行函数作为 Type 的托管强边保存,用其 globals 与创建时 builtins 解析字段注解;声明环境与实例/类型环由普通循环回收处理,没有隐藏外部根。`ObjectStore::new_class_with_scope(..., Some(function))` 提供相同的宿主入口。无环境的 `new_class` 支持上述基本注解及本类名;其他名称报 UnknownAnnotation,宿主可显式提供环境。没有本类字段的派生定义无需保存新的字段环境。 + +继承字段总是使用声明它的祖先定义和环境。注解为本类名时匹配该实际定义,REPL 重定义同名类不会改变旧实例的自引用字段约束;其他名称在每次构造/写入时从声明环境动态解析真实 Type/Trait,前向名称可在构造前建立,未知名称写入报错。不会仅以字符串相同判断名义兼容。基本注解与函数检查共用匹配规则。字段环境尚不解析函数局部类型名称或捕获的类型变量。 + +原生共享引用对象以当前 type_target 决定声明,以 attribute_target 决定存储;共享替换后的别名因而按新内容的字段契约写入。宿主直接写裸存储对象、改载荷、附加类型或改变字段所引用对象的共享转发,不属于 `set_attr` 的完整初始化/持续不变量保证;这些低层入口仍由宿主维护。没有静态推断、字段默认值、非空递归图初始化语法、重写方差或效果系统。 + +回归源码位于 compiler/tests/classes.rs,覆盖普通/原生构造、继承、错误写入的值/版本保持、未知名称、动态类型重绑定、自引用定义、开关、删除和 GC 清理,仅编译未本地执行。Tessera 在真实原生程序中验证共享字段更新、错误赋值与构造前副作用保护。