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