Validate method substitution before definition updates and execution
This commit is contained in:
@@ -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)。
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user