Validate unified source and native positional call contracts
This commit is contained in:
@@ -4,10 +4,10 @@
|
||||
|
||||
| 文件 | 职责 |
|
||||
| --- | --- |
|
||||
| [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定义/声明/方法参数约束、原生构造委托、声明环境托管边、字段构造/写入检查与注解解析/匹配 |
|
||||
| [native.rs](native.rs) | NativeContext、上下文原生函数、作用域语言函数工厂、显式能力声明入口、函数标记、根与非递归委托调用 |
|
||||
| [lib.rs](lib.rs) | 包边界、对象类型/ObjectStore 公开导出(含Trait/MethodRequirement/SourceSignature/AnnotationType/CallArity)与独立存储 rustdoc 示例 |
|
||||
| [object.rs](object.rs) | Value、CompareOp、ObjectPayload(含显式函数标记)、ObjectHeader、EndorObject、Record、可发布数量元数据的 NativeFunction |
|
||||
| [types.rs](types.rs) | 托管名义类型、显式 Object/Type 引导、继承查询、受管方法表/接收者绑定、类字段、显式Trait定义/声明/方法参数约束、原生构造委托、统一源码/原生/绑定方法参数区间查询与能力验证、声明环境托管边、字段构造/写入检查与注解解析/匹配 |
|
||||
| [native.rs](native.rs) | NativeContext、CallArity 元数据与调用前数量检查、上下文原生函数、作用域语言函数工厂、显式能力声明入口、函数标记、根与非递归委托调用 |
|
||||
| [changes.rs](changes.rs) | 合并变更批次、变更键与有界通知额度 |
|
||||
| [number.rs](number.rs) | Integer/Decimal/Rational/BinaryFloat/Complex 的无损表示和装箱载荷 |
|
||||
| [builtins.rs](builtins.rs) | String、Dict、Cell、Function、BaseException 载荷与 trace 边、语言函数标记 |
|
||||
|
||||
@@ -35,7 +35,7 @@ pub use code::{CodeLayout, EndorCode, EndorCodeObject, SourceSignature};
|
||||
pub use error::VmError;
|
||||
pub use heap::{ObjectRef, WeakObjectRef};
|
||||
pub use instruction::{DecodedInstruction, Instruction, Opcode};
|
||||
pub use native::{ContextNativeFunction, NativeCallable, NativeContext, NativeResult};
|
||||
pub use native::{CallArity, ContextNativeFunction, NativeCallable, NativeContext, NativeResult};
|
||||
pub use number::{
|
||||
EndorDecimal, EndorInteger, EndorNumber, EndorNumberObject, EndorRational, EndorReal,
|
||||
};
|
||||
|
||||
@@ -13,7 +13,49 @@ pub enum NativeResult {
|
||||
},
|
||||
}
|
||||
|
||||
/// Accepted positional argument interval, including explicit receivers.
|
||||
/// The store validates metadata before use; usize::MAX denotes an open upper bound.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct CallArity {
|
||||
pub required: usize,
|
||||
pub maximum: usize,
|
||||
}
|
||||
impl CallArity {
|
||||
pub const fn exact(count: usize) -> Self {
|
||||
Self::range(count, count)
|
||||
}
|
||||
pub const fn range(required: usize, maximum: usize) -> Self {
|
||||
Self { required, maximum }
|
||||
}
|
||||
pub fn accepts(self, count: usize) -> bool {
|
||||
(self.required..=self.maximum).contains(&count)
|
||||
}
|
||||
pub(crate) fn validate(self) -> Result<Self, VmError> {
|
||||
if self.required > self.maximum {
|
||||
return Err(VmError::InvalidArgument("invalid callable arity metadata"));
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
pub(crate) fn check(self, name: String, provided: usize) -> Result<(), VmError> {
|
||||
self.validate()?;
|
||||
if !self.accepts(provided) {
|
||||
return Err(VmError::ArgumentCount {
|
||||
name,
|
||||
required: self.required,
|
||||
maximum: self.maximum,
|
||||
provided,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NativeCallable: fmt::Debug + Send + Sync + 'static {
|
||||
/// Optional positional contract. Missing metadata remains an explicit legacy gap.
|
||||
fn arity(&self) -> Option<CallArity> {
|
||||
None
|
||||
}
|
||||
|
||||
fn invoke(
|
||||
&self,
|
||||
context: &mut NativeContext<'_>,
|
||||
@@ -149,6 +191,12 @@ impl<'a> NativeContext<'a> {
|
||||
arguments: &[Value],
|
||||
callable: Arc<dyn NativeCallable>,
|
||||
) -> Result<NativeResult, VmError> {
|
||||
if let Some(arity) = callable.arity() {
|
||||
arity.check(
|
||||
store.object(function)?.payload().kind_name().into(),
|
||||
arguments.len(),
|
||||
)?;
|
||||
}
|
||||
let mut context = Self {
|
||||
store,
|
||||
roots: Vec::new(),
|
||||
|
||||
@@ -117,6 +117,11 @@ pub trait ObjectPayload: Any + fmt::Debug + Send + Sync {
|
||||
/// Host-call entry: arguments and raw results are borrowed handles. The VM
|
||||
/// validates/retains the result before exposing it as an owned call result.
|
||||
/// Language functions are dispatched by Vm's frame evaluator instead.
|
||||
/// Positional metadata for native call protocols; source code is queried by the store.
|
||||
fn call_arity(&self) -> Option<crate::CallArity> {
|
||||
self.native_callable().and_then(|callable| callable.arity())
|
||||
}
|
||||
|
||||
fn call(&self, _arguments: &[Value]) -> Result<Value, VmError> {
|
||||
Err(VmError::UnsupportedOperation("call"))
|
||||
}
|
||||
@@ -176,6 +181,7 @@ pub type NativeCall = EndorNativeCall;
|
||||
pub struct EndorNativeFunctionObject {
|
||||
name: String,
|
||||
function: NativeCall,
|
||||
arity: Option<crate::CallArity>,
|
||||
}
|
||||
|
||||
impl EndorNativeFunctionObject {
|
||||
@@ -183,12 +189,17 @@ impl EndorNativeFunctionObject {
|
||||
Self {
|
||||
name: name.into(),
|
||||
function,
|
||||
arity: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
pub fn with_arity(mut self, arity: crate::CallArity) -> Self {
|
||||
self.arity = Some(arity);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Compatibility name for existing embedders.
|
||||
@@ -213,7 +224,13 @@ impl ObjectPayload for EndorNativeFunctionObject {
|
||||
Ok(format!("<native-function {}>", self.name))
|
||||
}
|
||||
|
||||
fn call_arity(&self) -> Option<crate::CallArity> {
|
||||
self.arity
|
||||
}
|
||||
fn call(&self, arguments: &[Value]) -> Result<Value, VmError> {
|
||||
if let Some(arity) = self.arity {
|
||||
arity.check(self.name.clone(), arguments.len())?;
|
||||
}
|
||||
(self.function)(arguments)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,6 +434,58 @@ impl ObjectStore {
|
||||
}
|
||||
}
|
||||
|
||||
/// Unified source/native positional interval, after any bound receivers.
|
||||
/// None means no published native metadata (including generic Type construction).
|
||||
pub fn call_arity(&self, function: ObjectRef) -> Result<Option<crate::CallArity>, VmError> {
|
||||
let mut current = function;
|
||||
let mut receivers = 0usize;
|
||||
loop {
|
||||
let payload = self.object(current)?.payload();
|
||||
if let Some(bound) = payload.as_any().downcast_ref::<BoundMethod>() {
|
||||
receivers = receivers
|
||||
.checked_add(1)
|
||||
.ok_or(VmError::ExecutionLimit("bound receivers"))?;
|
||||
current = bound.function;
|
||||
continue;
|
||||
}
|
||||
let arity = if let Some(function) = payload
|
||||
.as_any()
|
||||
.downcast_ref::<crate::EndorFunctionObject>()
|
||||
{
|
||||
let maximum = self
|
||||
.payload::<crate::EndorCodeObject>(function.code(), "code")?
|
||||
.code()
|
||||
.parameters()
|
||||
.len();
|
||||
Some(crate::CallArity::range(
|
||||
maximum - function.defaults().len(),
|
||||
maximum,
|
||||
))
|
||||
} else {
|
||||
payload.call_arity()
|
||||
};
|
||||
return arity
|
||||
.map(|arity| {
|
||||
let arity = arity.validate()?;
|
||||
let maximum = if arity.maximum == usize::MAX {
|
||||
usize::MAX
|
||||
} else {
|
||||
arity
|
||||
.maximum
|
||||
.checked_sub(receivers)
|
||||
.ok_or(VmError::InvalidArgument(
|
||||
"method receiver exceeds parameter count",
|
||||
))?
|
||||
};
|
||||
Ok(crate::CallArity::range(
|
||||
arity.required.saturating_sub(receivers),
|
||||
maximum,
|
||||
))
|
||||
})
|
||||
.transpose();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns one owned root, including for a newly bound inherited method.
|
||||
pub fn bind_attr(&mut self, receiver: ObjectRef, name: &str) -> Result<Value, VmError> {
|
||||
match self.lookup_attr(receiver, name)? {
|
||||
@@ -641,14 +693,13 @@ impl ObjectStore {
|
||||
reason,
|
||||
};
|
||||
let function = function.ok_or_else(|| invalid("required method is missing"))?;
|
||||
if let Some(count) = self.positional_parameter_count(function)? {
|
||||
if count != requirement.parameters + 1 {
|
||||
return Err(invalid(
|
||||
"positional parameter count does not match (including self)",
|
||||
));
|
||||
}
|
||||
} else if self.object(function)?.payload().native_callable().is_none() {
|
||||
return Err(invalid("required member is not callable"));
|
||||
let arity = self
|
||||
.call_arity(function)?
|
||||
.ok_or_else(|| invalid("required member has no positional signature"))?;
|
||||
if !arity.accepts(requirement.parameters + 1) {
|
||||
return Err(invalid(
|
||||
"positional parameter count does not match (including self)",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user