Validate method substitution before definition updates and execution

This commit is contained in:
2026-10-22 14:13:46 +08:00
parent 6b4cce1fee
commit e26e227dd0
14 changed files with 803 additions and 14 deletions
+2 -1
View File
@@ -7,6 +7,7 @@
| [lib.rs](lib.rs) | 包边界、对象类型/ObjectStore 公开导出(含Trait/MethodRequirement/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定义/声明/方法参数约束、原生构造委托、统一源码/原生/绑定方法参数区间查询与能力验证、声明环境托管边、字段构造/写入检查与标记为校验读取的注解解析/匹配 |
| [contracts.rs](contracts.rs) | 方法参数逆变/返回协变、名义/能力/联合包含关系、祖先方法更新的后代检查、动态注解调用前复查 |
| [native.rs](native.rs) | NativeContext、CallArity/CallSignature 类型与效果元数据、调用前检查、签名类型强边、上下文原生函数、作用域语言函数工厂、显式能力声明入口、函数标记、根与非递归委托调用 |
| [changes.rs](changes.rs) | 合并变更批次、变更键与有界通知额度 |
| [number.rs](number.rs) | Integer/Decimal/Rational/BinaryFloat/Complex 的无损表示和装箱载荷 |
@@ -16,7 +17,7 @@
| [store_builtins.rs](store_builtins.rs) | 载荷校验、代码/函数工厂、Dict/Cell 操作及键语义 |
| [instruction.rs](instruction.rs) | Opcode、Instruction、wordcode 编解码、MakeFunction/比较参数与跳转读取接口 |
| [code.rs](code.rs) | CodeLayout、EndorCode/EndorCodeObject、可选源码签名、共享不可变代码和操作数/控制流/栈验证 |
| [error.rs](error.rs) | 对象、代码与执行共用的 VmError(含TraitViolation、UnknownAnnotation、TypeMismatch),避免反向依赖 core |
| [error.rs](error.rs) | 对象、代码与执行共用的 VmError(含TraitViolation、OverrideViolation、UnknownAnnotation、TypeMismatch),避免反向依赖 core |
对象操作变更同步 [对象模型](../../../docs/runtime/objects.md),内置载荷同步 [基本对象](../../../docs/runtime/builtins.md)。新增操作码需要同时修改本包 instruction.rs/code.rs 和 core 的 eval.rs 分派,见 [字节码 VM](../../../docs/runtime/bytecode.md)。
+292
View File
@@ -0,0 +1,292 @@
//! Method substitution rules. This is a child of types so validation can inspect
//! method tables without exporting mutable definition internals.
use super::{AnnotationType, EndorTypeObject};
use crate::{
CallArity, CallEffect, CallSignature, CallType, EndorCodeObject, EndorFunctionObject,
ObjectRef, ObjectStore, VmError,
};
struct Contract {
arity: CallArity,
parameters: Vec<CallType>,
variadic: Option<CallType>,
returns: CallType,
effect: Option<CallEffect>,
}
impl Contract {
fn parameter(&self, index: usize) -> Option<&CallType> {
self.parameters.get(index).or(self.variadic.as_ref())
}
}
impl From<CallSignature> for Contract {
fn from(value: CallSignature) -> Self {
Self {
arity: value.arity,
parameters: value.parameters,
variadic: value.variadic,
returns: value.returns,
effect: Some(value.effect),
}
}
}
#[derive(Clone, Copy)]
struct Patch<'a> {
owner: ObjectRef,
name: &'a str,
function: ObjectRef,
}
impl CallType {
/// Whether every value admitted by provided is admitted by self.
/// Nominal inheritance and explicit capabilities participate; no structural
/// or callable-signature inference is implied by this relation.
pub fn accepts_type(&self, store: &ObjectStore, provided: &Self) -> Result<bool, VmError> {
self.validate(store)?;
provided.validate(store)?;
for actual in provided.alternatives() {
let mut accepted = false;
for expected in self.alternatives() {
accepted = match (*expected, *actual) {
(AnnotationType::Any, _) => true,
(AnnotationType::Nominal(base), AnnotationType::Nominal(child)) => {
store.is_subtype(child, base)?
}
(
AnnotationType::Capability(capability),
AnnotationType::Nominal(definition),
) => store.implements_trait(definition, capability)?,
(expected, actual) => expected == actual,
};
if accepted {
break;
}
}
if !accepted {
return Ok(false);
}
}
Ok(true)
}
}
impl ObjectStore {
fn method_contract(
&self,
function: ObjectRef,
owner: ObjectRef,
) -> Result<Option<Contract>, VmError> {
if let Some(native) = self.native_signature(function)? {
return Ok(Some(native.into()));
}
let Some(data) = self
.object(function)?
.payload()
.as_any()
.downcast_ref::<EndorFunctionObject>()
else {
return Ok(None);
};
let code = self.payload::<EndorCodeObject>(data.code(), "code")?.code();
let Some(signature) = code.source_signature() else {
return Ok(None);
};
let resolve = |name: &str| {
if signature.method_owner.as_deref() == Some(name) {
let mut current = Some(owner);
while let Some(definition) = current {
let data = self.payload::<EndorTypeObject>(definition, "Type")?;
if data.name == name
&& (definition == owner
|| data
.methods
.values()
.any(|candidate| *candidate == function))
{
return Ok(AnnotationType::Nominal(definition));
}
current = data.base;
}
}
self.annotation_type(function, name, None)
};
let parameters = signature
.parameters
.iter()
.map(|name| resolve(name).map(CallType::from))
.collect::<Result<Vec<_>, _>>()?;
Ok(Some(Contract {
arity: self
.call_arity(function)?
.ok_or(VmError::InvalidArgument("source method lacks arity"))?,
parameters,
variadic: None,
returns: resolve(&signature.return_type)?.into(),
effect: None,
}))
}
fn own_method(
&self,
owner: ObjectRef,
name: &str,
patch: Option<Patch<'_>>,
) -> Result<Option<ObjectRef>, VmError> {
if let Some(patch) = patch
&& patch.owner == owner
&& patch.name == name
{
return Ok(Some(patch.function));
}
Ok(self
.payload::<EndorTypeObject>(owner, "Type")?
.methods
.get(name)
.copied())
}
fn inherited_method(
&self,
mut owner: Option<ObjectRef>,
name: &str,
patch: Option<Patch<'_>>,
) -> Result<Option<(ObjectRef, ObjectRef)>, VmError> {
while let Some(definition) = owner {
if let Some(function) = self.own_method(definition, name, patch)? {
return Ok(Some((definition, function)));
}
owner = self.payload::<EndorTypeObject>(definition, "Type")?.base;
}
Ok(None)
}
fn check_override(
&self,
owner: ObjectRef,
name: &str,
function: ObjectRef,
patch: Option<Patch<'_>>,
defer_unknown: bool,
) -> Result<(), VmError> {
let definition = self.payload::<EndorTypeObject>(owner, "Type")?;
let Some((base_owner, base_function)) =
self.inherited_method(definition.base, name, patch)?
else {
return Ok(());
};
let checked = (|| {
let Some(base) = self.method_contract(base_function, base_owner)? else {
return Ok(());
};
let invalid = |reason| VmError::OverrideViolation {
definition: definition.name.clone(),
method: name.into(),
reason,
};
let derived = self
.method_contract(function, owner)?
.ok_or_else(|| invalid("override lacks type metadata"))?;
if derived.arity.required > base.arity.required
|| derived.arity.maximum < base.arity.maximum
{
return Err(invalid("override narrows accepted argument counts"));
}
let receiver = CallType::from(AnnotationType::Nominal(owner));
if !derived
.parameter(0)
.ok_or_else(|| invalid("override lacks receiver"))?
.accepts_type(self, &receiver)?
{
return Err(invalid("override receiver rejects its declaring type"));
}
let fixed = base
.parameters
.len()
.max(derived.parameters.len())
.min(base.arity.maximum);
for index in 1..fixed {
let expected = base
.parameter(index)
.ok_or_else(|| invalid("incomplete base parameter metadata"))?;
let actual = derived
.parameter(index)
.ok_or_else(|| invalid("incomplete override parameter metadata"))?;
if !actual.accepts_type(self, expected)? {
return Err(invalid("override narrows a parameter type"));
}
}
if base.arity.maximum > fixed {
let expected = base
.parameter(fixed)
.ok_or_else(|| invalid("incomplete base variadic metadata"))?;
let actual = derived
.parameter(fixed)
.ok_or_else(|| invalid("incomplete override variadic metadata"))?;
if !actual.accepts_type(self, expected)? {
return Err(invalid("override narrows a variadic parameter type"));
}
}
if !base.returns.accepts_type(self, &derived.returns)? {
return Err(invalid("override widens the return type"));
}
if let (Some(expected), Some(actual)) = (base.effect, derived.effect) {
let compatible = match expected {
CallEffect::ReadOnly => actual == CallEffect::ReadOnly,
CallEffect::Mutating => actual != CallEffect::External,
CallEffect::External => true,
};
if !compatible {
return Err(invalid("override expands native effects"));
}
}
Ok(())
})();
match checked {
Err(VmError::UnknownAnnotation(_)) if defer_unknown => Ok(()),
result => result,
}
}
pub(super) fn validate_class_overrides(&self, definition: ObjectRef) -> Result<(), VmError> {
if self.field_type_checks {
for (name, function) in &self.payload::<EndorTypeObject>(definition, "Type")?.methods {
self.check_override(definition, name, *function, None, true)?;
}
}
Ok(())
}
pub(super) fn validate_override_update(
&self,
owner: ObjectRef,
name: &str,
function: ObjectRef,
) -> Result<(), VmError> {
if self.field_type_checks {
let patch = Some(Patch {
owner,
name,
function,
});
for descendant in self.descendant_types(owner)? {
if let Some(function) = self.own_method(descendant, name, patch)? {
self.check_override(descendant, name, function, patch, true)?;
}
}
}
Ok(())
}
/// Revalidate dynamic annotation names before a source method body executes.
/// Ordinary functions whose first argument is an object are not methods.
pub fn check_method_override_call(
&self,
function: ObjectRef,
receiver: ObjectRef,
) -> Result<(), VmError> {
let mut owner = self.type_of(receiver)?;
while let Some(definition) = owner {
let data = self.payload::<EndorTypeObject>(definition, "Type")?;
for (name, candidate) in &data.methods {
if *candidate == function {
self.check_override(definition, name, function, None, false)?;
}
}
owner = data.base;
}
Ok(())
}
}
+10
View File
@@ -18,6 +18,11 @@ pub enum VmError {
method: String,
reason: &'static str,
},
OverrideViolation {
definition: String,
method: String,
reason: &'static str,
},
UnknownAnnotation(String),
TypeMismatch {
function: String,
@@ -63,6 +68,11 @@ impl fmt::Display for VmError {
Self::UnknownAnnotation(name) => {
write!(f, "unknown or invalid type annotation: {name}")
}
Self::OverrideViolation {
definition,
method,
reason,
} => write!(f, "override {definition}.{method}: {reason}"),
Self::TypeMismatch {
function,
position,
+1 -1
View File
@@ -75,7 +75,7 @@ impl CallType {
pub fn nullable(value: crate::AnnotationType) -> Self {
Self::Union(vec![value, crate::AnnotationType::Unit])
}
fn alternatives(&self) -> &[crate::AnnotationType] {
pub(crate) fn alternatives(&self) -> &[crate::AnnotationType] {
match self {
Self::Single(value) => std::slice::from_ref(value),
Self::Union(values) => values,
+1 -1
View File
@@ -15,7 +15,7 @@ pub struct ObjectStore {
}
impl ObjectStore {
/// Opt-in declared-field checks, shared by VM and NativeContext writes.
/// Opt-in declared-field and method substitution checks, shared with the VM.
pub fn set_field_type_checks(&mut self, enabled: bool) -> Result<(), VmError> {
if self.is_read_only() {
return Err(VmError::UnsupportedOperation(
+5 -1
View File
@@ -1,4 +1,6 @@
//! Nominal type identities in the same managed heap as their instances.
#[path = "contracts.rs"]
mod contracts;
use std::{collections::BTreeMap, sync::Arc};
use crate::{
@@ -286,6 +288,7 @@ impl ObjectStore {
));
}
let mut methods = definition.methods.clone();
self.validate_override_update(type_object, &name, function)?;
self.validate_method_update(type_object, &name, function)?;
methods.insert(name, function);
let updated = EndorTypeObject {
@@ -374,6 +377,7 @@ impl ObjectStore {
})?;
if let Err(error) = self
.validate_inherited_traits(class)
.and_then(|()| self.validate_class_overrides(class))
.and_then(|()| self.attach_type(class, metatype))
{
self.release(class)?;
@@ -835,7 +839,7 @@ impl ObjectStore {
/// Borrowed expected-type descriptor. Frames retain its managed reference
/// while validating the return, so rebinding globals cannot release it mid-call.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnnotationType {
Any,
Unit,