From 58ec874dc0d82bfebb22417f59cf747d9a6e66be Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:33:03 +0800 Subject: [PATCH] docs(objects): explain snapshot call effect analysis --- crates/objects/src/effects.rs | 65 +++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/crates/objects/src/effects.rs b/crates/objects/src/effects.rs index cdd89e9..2dafb7a 100644 --- a/crates/objects/src/effects.rs +++ b/crates/objects/src/effects.rs @@ -1,3 +1,5 @@ +//! Store::call_effect 的当前快照证明;None 表示无法证明,不代表确定有副作用。 +//! 查询经过 Store 读取追踪,但不执行源码或分配托管对象;所有身份仅借用。 //! Snapshot proof of read-only call graphs. All identities are borrowed and all //! traversals iterative; analysis neither executes code nor allocates VM objects. use super::{AttributeLookup, BoundMethod}; @@ -14,18 +16,24 @@ struct Target { } #[derive(Clone, Copy, PartialEq, Eq)] enum Fact { + // 没有可证明的身份,不是实际 Nil。 Unknown, + // 调用协议 Null 标记,不是语言空值。 Null, + // 已知借用对象身份,不增加根引用。 Object(ObjectRef), + // 已解析函数和绑定接收者,不创建托管绑定对象。 Method(Target), } impl Fact { + // 把实际值降为抽象身份事实,直接标量不保留其内容。 fn value(value: Value) -> Self { match value { Value::Object(object) => Self::Object(object), _ => Self::Unknown, } } + // 只从普通对象事实提取身份,不把方法对当作对象。 fn object(self) -> Option { match self { Self::Object(object) => Some(object), @@ -35,9 +43,13 @@ impl Fact { } impl ObjectStore { + // 解包 BoundMethod,保留底层函数和实际接收者,供调用图节点归一化。 fn effect_target(&self, mut target: Target) -> Result { + // 逐层解绑定载荷,不递归进入 Rust 调用栈。 loop { + // 验证函数身份并记录当前载荷读取,避免绕过依赖追踪。 let payload = self.object(target.function)?.payload(); + // 不是绑定方法即结束归一化,不更改现有接收者。 let Some(bound) = payload.as_any().downcast_ref::() else { return Ok(target); }; @@ -48,14 +60,17 @@ impl ObjectStore { } } + // types::call_effect 委托入口;原生根返回已声明效果,源码根只尝试证明全图只读。 pub(super) fn infer_call_effect( &self, function: ObjectRef, ) -> Result, VmError> { + // 根也可能是绑定方法,先解包再识别源码/原生。 let root = self.effect_target(Target { function, receiver: None, })?; + // 非源码根查询原生签名,Delegated 效果动态决定,不能直接当作整体效果。 if !self .object(root.function)? .payload() @@ -66,19 +81,26 @@ impl ObjectStore { (signature.effect != CallEffect::Delegated).then_some(signature.effect) })); } + // 迭代工作队列防止递归调用图造成 Rust 递归。 let mut pending = VecDeque::from([root]); + // 节点去重包含接收者,同一函数的不同绑定对象不能混为一项。 let mut visited = BTreeSet::new(); + // 所有可达节点都能证明只读才允许根返回 ReadOnly。 while let Some(target) = pending.pop_front() { + // 子调用也可能经过绑定方法,统一归一化。 let target = self.effect_target(target)?; + // 递归回边已访问就跳过,不重复展开。 if !visited.insert(target) { continue; } + // 源码节点分析栈事实,原生节点则必须明确声明 ReadOnly。 if let Some(source) = self .object(target.function)? .payload() .as_any() .downcast_ref::() { + // 任意一个节点无法证明,整个源码根的证明立即返回 None。 let Some(callees) = self.read_only_callees(source, target.receiver)? else { return Ok(None); }; @@ -87,41 +109,55 @@ impl ObjectStore { .native_signature(target.function)? .is_none_or(|signature| signature.effect != CallEffect::ReadOnly) { - // An impure target could rebind later globals/Cells/methods, so - // snapshot edges cannot bound this source invocation's effects. + // 未知/非只读目标可重绑定后续全局、Cell 或方法,当前快照不足以证明整体效果。 return Ok(None); } } Ok(Some(CallEffect::ReadOnly)) } + // 分析一个源码函数的抽象栈,收集已知被调目标;不执行实际调用。 fn read_only_callees( &self, function: &EndorFunctionObject, receiver: Option, ) -> Result>, VmError> { + // 借用函数持有的代码;后续索引和栈高度依赖代码构造时的验证。 let code = self .payload::(function.code(), "code")? .code(); + // 全表白名单已足够时无需分析调用图,返回空被调集合。 if code.is_statically_read_only() { return Ok(Some(BTreeSet::new())); } + // 有 Call 时要求其他全部指令都通过本地白名单。 let instructions = code.instructions(); + // 过滤覆盖不可达指令,不仅检查当前可达路径。 if instructions.iter().any(|decoded| { decoded.instruction.opcode != Opcode::Call && !crate::code::read_only_opcode(decoded.instruction.opcode) }) { return Ok(None); } + // 每个逻辑指令入口一份抽象栈,None 表示尚未到达。 let mut states: Vec>> = vec![None; instructions.len()]; + // 已验证代码非空,入口求值栈为空。 states[0] = Some(vec![]); + // 分支汇合只能遗忘事实,状态变保守后再入队,最终收敛。 let mut work = VecDeque::from([0]); + // 调用边只增加不删除,保守保留曾经识别的目标。 let mut callees = BTreeSet::new(); + // 逐指令模拟身份流动,不计算实际数值或条件。 while let Some(index) = work.pop_front() { + // 偏移使用物理代码单元,工作队列使用逻辑指令索引。 let decoded = instructions[index]; + // 操作码控制抽象栈变换,不在分析阶段执行其载荷协议。 let opcode = decoded.instruction.opcode; + // 完整操作数已合并扩展前缀。 let argument = decoded.instruction.argument as usize; + // 复制当前入口事实,模拟本步后再传给后继。 let mut stack = states[index].clone().expect("reachable effect state"); + // 仅保留调用目标识别所需事实,结果通常退化为 Unknown。 match opcode { Opcode::Await => return Ok(None), Opcode::LoadConst => stack.push(Fact::value(code.constants()[argument].clone())), @@ -131,8 +167,11 @@ impl ObjectStore { Fact::Unknown }), Opcode::LoadGlobal => { + // 与 eval 一致,先查询全局字典,再回退捕获的内置空间。 let name = &code.layout().names[argument]; + // 名称读取登记命中/缺失依赖,后续重绑定能影响快照证明。 let mut value = self.dict_get_name(function.globals(), name)?; + // 只有缺键才回退,Some(Nil) 不等于名称不存在。 if value.is_none() && let Some(builtins) = function.builtins() { @@ -141,16 +180,20 @@ impl ObjectStore { stack.push(value.map(Fact::value).unwrap_or(Fact::Unknown)); } Opcode::LoadDeref | Opcode::LoadClosure => { + // 外部自由变量可读当前捕获 Cell;局部只尝试识别已知接收者。 let fact = if argument >= code.layout().locals.len() { + // 把 localsplus 自由变量段索引换算到函数 closure 列表。 let cell = *function .closure() .get(argument - code.layout().locals.len()) .ok_or(VmError::InvalidArgument( "function closure does not match code", ))?; + // LoadClosure 保留 Cell 自身身份,LoadDeref 读取其当前内容。 if opcode == Opcode::LoadClosure { Fact::Object(cell) } else { + // 空 Cell 为 Unknown,其他协议/句柄错误仍传播。 match self.cell_get(cell) { Ok(value) => Fact::value(value), Err(VmError::EmptyCell) => Fact::Unknown, @@ -167,6 +210,7 @@ impl ObjectStore { Opcode::PushNull => stack.push(Fact::Null), Opcode::Copy => stack.push(stack[stack.len() - argument]), Opcode::Swap => { + // Swap 深度已由代码验证,直接模拟事实位置交换。 let length = stack.len(); stack.swap(length - 1, length - argument); } @@ -184,8 +228,11 @@ impl ObjectStore { *stack.last_mut().unwrap() = Fact::Unknown; } Opcode::LoadAttr => { + // 只有 owner 身份已知才可查询其属性,未知 owner 不猜测方法。 let fact = stack.last_mut().unwrap(); + // 绑定方法保留函数与接收者对,不实际分配方法载荷。 *fact = if let Some(owner) = fact.object() { + // 缺属性/不支持属性视为未知,其他错误不吞掉。 match self.lookup_attr(owner, &code.layout().names[argument]) { Ok(AttributeLookup::Value(value)) => Fact::value(value), Ok(AttributeLookup::Method { function, receiver }) => { @@ -205,7 +252,9 @@ impl ObjectStore { }; } Opcode::Call => { + // 调用输入包括函数、接收者或 Null 及显式参数,代码校验保证足够高度。 let start = stack.len() - argument - 2; + // Null 协议从首显式实参识别可能接收者,否则用隐式接收者项。 let implicit = if stack[start + 1] == Fact::Null { (argument > 0) .then(|| stack[start + 2]) @@ -213,6 +262,7 @@ impl ObjectStore { } else { stack[start + 1].object() }; + // 未知函数目标无法证明整个函数;已知方法直接保留绑定接收者。 let target = match stack[start] { Fact::Object(function) => Target { function, @@ -221,6 +271,7 @@ impl ObjectStore { Fact::Method(target) => target, _ => return Ok(None), }; + // 记录目标后移除输入,调用结果不猜测身份,标为 Unknown。 callees.insert(target); stack.truncate(start); stack.push(Fact::Unknown); @@ -230,14 +281,17 @@ impl ObjectStore { stack.push(Fact::Unknown); } Opcode::MakeFunction => { + // 函数构造消耗默认值、捕获槽和代码项;新对象未实际分配,结果未知。 let (defaults, closure) = crate::Instruction::function_counts(decoded.instruction.argument); stack.truncate(stack.len() - defaults - closure - 1); stack.push(Fact::Unknown); } Opcode::Nop | Opcode::DeleteFast | Opcode::JumpForward | Opcode::JumpBackward => {} + // 注意:当前白名单含算术操作,但此模拟器尚无对应分支;含 Call 的混合路径需单独修复。 _ => unreachable!("unproved opcodes were rejected before analysis"), } + // 条件跳转传播两条后继,返回无后继,不根据当前值猜测条件真假。 let successors = match opcode { Opcode::ReturnValue => vec![], Opcode::JumpForward | Opcode::JumpBackward => { @@ -248,13 +302,19 @@ impl ObjectStore { } _ => vec![decoded.next_offset], }; + // 后继都已通过代码验证,必须落在完整逻辑指令起点。 for offset in successors { + // 将物理单元偏移查为逻辑索引,expect 依赖已验证控制流。 let next = instructions .binary_search_by_key(&offset, |item| item.offset) .expect("validated successor"); + // 汇合时逐槽遗忘有分歧的身份;栈长度由代码验证保证一致。 let changed = if let Some(existing) = &mut states[next] { + // 只有新增状态或事实首次退化才再次传播。 let mut changed = false; + // Unknown 不恢复为具体事实,相同身份继续保留。 for (old, new) in existing.iter_mut().zip(&stack) { + // 不同路径的目标不能任取其一,合并为 Unknown。 if *old != *new && *old != Fact::Unknown { *old = Fact::Unknown; changed = true; @@ -265,6 +325,7 @@ impl ObjectStore { states[next] = Some(stack.clone()); true }; + // 状态更保守时重新处理后继,直至所有入口稳定。 if changed { work.push_back(next); }