Add typed trait requirements and enforce actual capability results

This commit is contained in:
2026-10-22 14:36:57 +08:00
parent e26e227dd0
commit 100bf637f9
16 changed files with 799 additions and 107 deletions
+4 -4
View File
@@ -4,16 +4,16 @@
| 文件 | 职责 |
| --- | --- |
| [lib.rs](lib.rs) | 包边界、对象类型/ObjectStore 公开导出(含Trait/MethodRequirement/SourceSignature/AnnotationType/CallArity/CallSignature/CallType/CallEffect/ReadAccess)与独立存储 rustdoc 示例 |
| [lib.rs](lib.rs) | 包边界、对象类型/ObjectStore 公开导出(含Trait/MethodRequirement/TypedMethodRequirement/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) | 方法参数逆变/返回协变、名义/能力/联合包含关系、祖先方法更新的后代检查、动态注解调用前复查 |
| [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 的无损表示和装箱载荷 |
| [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、可选源码签名、共享不可变代码和操作数/控制流/栈验证 |
+223 -58
View File
@@ -36,6 +36,26 @@ struct Patch<'a> {
function: ObjectRef,
}
struct TraitCheckGuard<'a> {
store: &'a ObjectStore,
thread: std::thread::ThreadId,
key: (ObjectRef, ObjectRef),
}
impl Drop for TraitCheckGuard<'_> {
fn drop(&mut self) {
let mut checks = self
.store
.active_trait_checks
.lock()
.expect("trait checker poisoned");
let active = checks.get_mut(&self.thread).expect("active trait check");
active.remove(&self.key);
if active.is_empty() {
checks.remove(&self.thread);
}
}
}
impl CallType {
/// Whether every value admitted by provided is admitted by self.
/// Nominal inheritance and explicit capabilities participate; no structural
@@ -70,6 +90,31 @@ impl CallType {
}
impl ObjectStore {
pub(super) fn with_trait_check(
&self,
definition: ObjectRef,
capability: ObjectRef,
check: impl FnOnce() -> Result<(), VmError>,
) -> Result<(), VmError> {
let thread = std::thread::current().id();
let key = (definition, capability);
if !self
.active_trait_checks
.lock()
.expect("trait checker poisoned")
.entry(thread)
.or_default()
.insert(key)
{
return Ok(());
}
let _guard = TraitCheckGuard {
store: self,
thread,
key,
};
check()
}
fn method_contract(
&self,
function: ObjectRef,
@@ -182,59 +227,7 @@ impl ObjectStore {
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"));
}
}
self.check_substitution(owner, &derived, &base, invalid)?;
Ok(())
})();
match checked {
@@ -242,6 +235,104 @@ impl ObjectStore {
result => result,
}
}
fn check_substitution(
&self,
owner: ObjectRef,
derived: &Contract,
base: &Contract,
invalid: impl Fn(&'static str) -> VmError,
) -> Result<(), VmError> {
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"));
}
if !base
.parameter(0)
.ok_or_else(|| invalid("required signature lacks receiver"))?
.accepts_type(self, &receiver)?
{
return Err(invalid("required receiver rejects implementing 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(())
}
pub(super) fn validate_typed_requirement(
&self,
definition: ObjectRef,
capability: ObjectRef,
name: &str,
function: ObjectRef,
expected: &CallSignature,
defer_unknown: bool,
) -> Result<(), VmError> {
let invalid = |reason| VmError::TraitViolation {
capability: self
.payload::<super::EndorTraitObject>(capability, "Trait")
.expect("checked trait")
.name
.clone(),
method: name.into(),
reason,
};
let checked = (|| {
let actual = self
.method_contract(function, definition)?
.ok_or_else(|| invalid("required member lacks type metadata"))?;
self.check_substitution(definition, &actual, &expected.clone().into(), invalid)
})();
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 {
@@ -270,23 +361,97 @@ impl ObjectStore {
}
Ok(())
}
/// Revalidate dynamic annotation names before a source method body executes.
/// Ordinary functions whose first argument is an object are not methods.
/// Explicit override validation, including any typed capabilities.
pub fn check_method_override_call(
&self,
function: ObjectRef,
receiver: ObjectRef,
) -> Result<(), VmError> {
let mut owner = self.type_of(receiver)?;
self.check_method_contract_call(function, receiver, true)
.map(|_| ())
}
/// Typed capabilities are explicit opt-ins and always checked. Ordinary
/// override checking remains configurable by the VM's source-check flag.
pub fn check_method_contract_call(
&self,
function: ObjectRef,
receiver: ObjectRef,
overrides: bool,
) -> Result<bool, VmError> {
let mut typed = false;
let actual = self.type_of(receiver)?;
let mut owner = actual;
while let Some(definition) = owner {
let data = self.payload::<EndorTypeObject>(definition, "Type")?;
for (name, candidate) in &data.methods {
if *candidate == function {
if *candidate != function {
continue;
}
if overrides {
self.check_override(definition, name, function, None, false)?;
}
let effective = if self
.lookup_method(actual.expect("method receiver has a type"), name)?
== Some(function)
{
actual.unwrap()
} else {
definition
};
for capability in self.inherited_traits(effective)? {
if let Some(expected) = self
.payload::<super::EndorTraitObject>(capability, "Trait")?
.signatures
.get(name)
{
typed = true;
self.validate_typed_requirement(
effective, capability, name, function, expected, false,
)?;
}
}
}
owner = data.base;
}
Ok(())
Ok(typed)
}
/// Borrowed expected results for all typed capabilities of this effective
/// method. The evaluator retains definitions before entering the body.
pub fn method_trait_returns(
&self,
function: ObjectRef,
receiver: ObjectRef,
) -> Result<Vec<(String, CallType)>, VmError> {
let actual = self.type_of(receiver)?;
let mut owner = actual;
let mut returns = Vec::new();
while let Some(definition) = owner {
let data = self.payload::<EndorTypeObject>(definition, "Type")?;
for (name, candidate) in &data.methods {
if *candidate != function {
continue;
}
let effective = if self
.lookup_method(actual.expect("method receiver has a type"), name)?
== Some(function)
{
actual.unwrap()
} else {
definition
};
for capability in self.inherited_traits(effective)? {
let contract = self.payload::<super::EndorTraitObject>(capability, "Trait")?;
if let Some(expected) = contract.signatures.get(name) {
returns.push((
format!("{}.{}", contract.name, name),
expected.returns.clone(),
));
}
}
}
owner = data.base;
}
Ok(returns)
}
}
+1 -1
View File
@@ -49,5 +49,5 @@ pub use object::{
pub use store::ObjectStore;
pub use types::{
AnnotationType, ClassField, EndorTraitObject, EndorTypeObject, MethodRequirement, RootTypes,
TypeConstructor,
TypeConstructor, TypedMethodRequirement,
};
+16
View File
@@ -375,6 +375,12 @@ impl<'a> NativeContext<'a> {
if let Some(signature) = &signature {
signature.check_arguments(store, &name, arguments)?;
}
let trait_returns = if let Some(Value::Object(receiver)) = arguments.first() {
store.check_method_contract_call(function, *receiver, false)?;
store.method_trait_returns(function, *receiver)?
} else {
Vec::new()
};
let mut context = Self {
store,
roots: Vec::new(),
@@ -386,6 +392,11 @@ impl<'a> NativeContext<'a> {
context.protect(&Value::Object(reference))?;
}
}
for (_, expected) in &trait_returns {
for reference in expected.references() {
context.protect(&Value::Object(reference))?;
}
}
for argument in arguments {
context.protect(argument)?;
}
@@ -393,6 +404,11 @@ impl<'a> NativeContext<'a> {
if let (Some(signature), NativeResult::Return(value)) = (&signature, &result) {
signature.check_return(context.store(), &name, value)?;
}
if let NativeResult::Return(value) = &result {
for (name, expected) in &trait_returns {
expected.check(context.store(), name, "return".into(), value)?;
}
}
let transferred_from = context.roots.len();
match &result {
NativeResult::Return(value) => context.protect(value)?,
+7
View File
@@ -12,6 +12,12 @@ pub struct ObjectStore {
pub(crate) heap: Heap,
function_type: Option<WeakObjectRef>,
pub(crate) field_type_checks: bool,
pub(crate) active_trait_checks: std::sync::Mutex<
std::collections::HashMap<
std::thread::ThreadId,
std::collections::BTreeSet<(ObjectRef, ObjectRef)>,
>,
>,
}
impl ObjectStore {
@@ -151,6 +157,7 @@ impl ObjectStore {
heap: Heap::new()?,
function_type: None,
field_type_checks: false,
active_trait_checks: std::sync::Mutex::new(std::collections::HashMap::new()),
})
}
+104 -14
View File
@@ -634,16 +634,23 @@ impl NativeCallable for CallBoundMethod {
}
/// Minimal explicit capability contract. Arity excludes the bound receiver.
/// Parameter/return types, default implementations and syntax are host concerns.
/// Use TypedMethodRequirement for parameter/return/effect contracts.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MethodRequirement {
pub name: String,
pub parameters: usize,
}
/// Typed capability requirement, including the explicit receiver slot.
#[derive(Debug, Clone)]
pub struct TypedMethodRequirement {
pub name: String,
pub signature: crate::CallSignature,
}
#[derive(Debug)]
pub struct EndorTraitObject {
name: String,
requirements: Vec<MethodRequirement>,
signatures: BTreeMap<String, crate::CallSignature>,
}
impl EndorTraitObject {
pub fn name(&self) -> &str {
@@ -652,8 +659,18 @@ impl EndorTraitObject {
pub fn requirements(&self) -> &[MethodRequirement] {
&self.requirements
}
pub fn signature(&self, name: &str) -> Option<&crate::CallSignature> {
self.signatures.get(name)
}
}
impl ObjectPayload for EndorTraitObject {
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
for signature in self.signatures.values() {
for reference in signature.references() {
visit(reference);
}
}
}
fn reference_assignment_only(&self) -> bool {
true
}
@@ -690,7 +707,49 @@ impl ObjectStore {
));
}
}
self.allocate(EndorTraitObject { name, requirements })
self.allocate(EndorTraitObject {
name,
requirements,
signatures: BTreeMap::new(),
})
}
pub fn new_typed_trait(
&mut self,
name: impl Into<String>,
methods: Vec<TypedMethodRequirement>,
) -> Result<ObjectRef, VmError> {
let name = name.into();
if name.is_empty() {
return Err(VmError::InvalidArgument("trait name must not be empty"));
}
let mut signatures = BTreeMap::new();
let mut requirements = Vec::new();
for method in methods {
method.signature.validate(self)?;
let arity = method.signature.arity;
if method.name.is_empty()
|| method.name == "type"
|| arity.required == 0
|| arity.required != arity.maximum
|| arity.maximum == usize::MAX
|| signatures.contains_key(&method.name)
{
return Err(VmError::InvalidArgument(
"invalid typed trait canonical signature",
));
}
requirements.push(MethodRequirement {
name: method.name.clone(),
parameters: arity.maximum - 1,
});
signatures.insert(method.name, method.signature);
}
self.allocate(EndorTraitObject {
name,
requirements,
signatures,
})
}
fn inherited_traits(&self, definition: ObjectRef) -> Result<Vec<ObjectRef>, VmError> {
@@ -708,6 +767,8 @@ impl ObjectStore {
capability: ObjectRef,
requirement: &MethodRequirement,
function: Option<ObjectRef>,
definition: ObjectRef,
defer_unknown: bool,
) -> Result<(), VmError> {
let invalid = |reason| VmError::TraitViolation {
capability: self
@@ -727,22 +788,45 @@ impl ObjectStore {
"positional parameter count does not match (including self)",
));
}
Ok(())
}
fn validate_trait(&self, definition: ObjectRef, capability: ObjectRef) -> Result<(), VmError> {
let contract = self.payload::<EndorTraitObject>(capability, "Trait")?;
for requirement in &contract.requirements {
self.validate_requirement(
if let Some(expected) = self
.payload::<EndorTraitObject>(capability, "Trait")?
.signatures
.get(&requirement.name)
{
self.validate_typed_requirement(
definition,
capability,
requirement,
self.lookup_method(definition, &requirement.name)?,
&requirement.name,
function,
expected,
defer_unknown,
)?;
}
Ok(())
}
fn validate_trait(
&self,
definition: ObjectRef,
capability: ObjectRef,
defer_unknown: bool,
) -> Result<(), VmError> {
self.with_trait_check(definition, capability, || {
let contract = self.payload::<EndorTraitObject>(capability, "Trait")?;
for requirement in &contract.requirements {
self.validate_requirement(
capability,
requirement,
self.lookup_method(definition, &requirement.name)?,
definition,
defer_unknown,
)?;
}
Ok(())
})
}
fn validate_inherited_traits(&self, definition: ObjectRef) -> Result<(), VmError> {
for capability in self.inherited_traits(definition)? {
self.validate_trait(definition, capability)?;
self.validate_trait(definition, capability, true)?;
}
Ok(())
}
@@ -793,7 +877,13 @@ impl ObjectStore {
.requirements
{
if requirement.name == name {
self.validate_requirement(capability, requirement, Some(function))?;
self.validate_requirement(
capability,
requirement,
Some(function),
descendant,
true,
)?;
}
}
}
@@ -807,7 +897,7 @@ impl ObjectStore {
) -> Result<(), VmError> {
self.payload::<EndorTraitObject>(capability, "Trait")?;
for descendant in self.descendant_types(definition)? {
self.validate_trait(descendant, capability)?;
self.validate_trait(descendant, capability, false)?;
}
let mut updated = self.payload::<EndorTypeObject>(definition, "Type")?.clone();
if !updated.traits.contains(&capability) {
@@ -824,7 +914,7 @@ impl ObjectStore {
self.payload::<EndorTraitObject>(capability, "Trait")?;
let declared = self.inherited_traits(definition)?.contains(&capability);
if declared {
self.validate_trait(definition, capability)?;
self.validate_trait(definition, capability, false)?;
}
Ok(declared)
}