docs(objects): explain executable layout and bytecode verification
This commit is contained in:
+110
-20
@@ -1,3 +1,7 @@
|
||||
//! 编译器/宿主提交代码后在此构造不可变执行数据;core::frame 共享代码,eval 按偏移读取指令。
|
||||
//! 构造先验证名字布局、解码和控制流栈高度;这不是执行器,也不证明运行时值类型或调用一定成功。
|
||||
//! 常量中的对象句柄在 EndorCode 内只是表示,由堆上 EndorCodeObject 的 trace 建立托管强边。
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::{BTreeSet, VecDeque};
|
||||
use std::sync::Arc;
|
||||
@@ -6,39 +10,58 @@ use crate::builtins::trace_value;
|
||||
use crate::instruction::{compare_operation, decode, function_counts, invalid};
|
||||
use crate::{DecodedInstruction, Instruction, ObjectPayload, ObjectRef, Opcode, Value, VmError};
|
||||
|
||||
/// Local slots start with positional parameters. Captured locals occupy their
|
||||
/// original slots as cells; free-variable cells follow all local slots.
|
||||
/// 编译器生成、执行帧消费的槽布局:参数占局部槽前缀,捕获局部仍占原槽但存 Cell。
|
||||
/// 外部自由变量 Cell 追加在所有局部槽之后,不能与局部变量重名。
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct CodeLayout {
|
||||
/// 位置参数名称顺序,必须与 locals 前缀相同。
|
||||
pub parameters: Vec<String>,
|
||||
/// 全部局部名称,参数必须是其完整前缀。
|
||||
pub locals: Vec<String>,
|
||||
/// 需要提升为 Cell 的局部名称子集,不额外新增局部槽。
|
||||
pub cell_variables: Vec<String>,
|
||||
/// 从外层捕获的 Cell 名称,顺序决定闭包传入槽顺序。
|
||||
pub free_variables: Vec<String>,
|
||||
/// 全局与属性操作使用的文本名称索引表。
|
||||
pub names: Vec<String>,
|
||||
}
|
||||
|
||||
/// Source declaration metadata, separate from executable slot layout.
|
||||
/// 编译器传入的源码类型声明,与执行槽布局分开;类型名称在调用/契约检查时解析。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SourceSignature {
|
||||
/// 位置参数的类型注解顺序,数量必须与代码参数槽数相同。
|
||||
pub parameters: Vec<String>,
|
||||
/// 非空的返回类型注解名称,尚不保证该名称可成功解析。
|
||||
pub return_type: String,
|
||||
/// 可选方法所属类型名,用于源码方法契约;Some 空串非法。
|
||||
pub method_owner: Option<String>,
|
||||
}
|
||||
|
||||
/// 已通过构造校验的 Rust 执行数据,尚未分配为托管代码对象;clone 不为常量对象增加根。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EndorCode {
|
||||
/// 保留的物理双字节单元编码,含扩展前缀。
|
||||
bytecode: Vec<u8>,
|
||||
/// 解码后按偏移排序的逻辑指令列表,执行时无需重新组合前缀。
|
||||
instructions: Vec<DecodedInstruction>,
|
||||
/// 常量表示;Rust clone 不为其中的对象句柄 retain。
|
||||
constants: Vec<Value>,
|
||||
/// 经 validate_layout 检查的槽和名称布局。
|
||||
layout: CodeLayout,
|
||||
/// 从可达控制流计算的最大栈高度,供帧预留容量。
|
||||
stack_size: usize,
|
||||
/// 诊断显示的代码名称。
|
||||
name: String,
|
||||
/// 诊断显示的源码文件名,不读取该文件。
|
||||
filename: String,
|
||||
/// 调用方给出的起始行,供回溯显示,不是逐指令行号表。
|
||||
first_line: usize,
|
||||
/// 可选源码声明;不改变已校验的指令布局。
|
||||
signature: Option<SourceSignature>,
|
||||
}
|
||||
|
||||
impl EndorCode {
|
||||
/// 宿主从原始字节码建立只有参数局部槽的代码;需要额外局部或本地 Cell 时用 with_layout。
|
||||
pub fn new(
|
||||
bytecode: Vec<u8>,
|
||||
constants: Vec<Value>,
|
||||
@@ -57,6 +80,7 @@ impl EndorCode {
|
||||
)
|
||||
}
|
||||
|
||||
/// 编译器与宿主把逻辑指令编码后走统一构造校验;此入口不替调用者修补跳转距离。
|
||||
pub fn from_instructions(
|
||||
instructions: &[Instruction],
|
||||
constants: Vec<Value>,
|
||||
@@ -65,13 +89,17 @@ impl EndorCode {
|
||||
Self::with_layout(Instruction::encode_all(instructions)?, constants, layout)
|
||||
}
|
||||
|
||||
/// 全部构造入口汇合处:先检查布局,再解码,最后验证索引、跳转和可达路径栈高度。
|
||||
pub fn with_layout(
|
||||
bytecode: Vec<u8>,
|
||||
constants: Vec<Value>,
|
||||
layout: CodeLayout,
|
||||
) -> Result<Self, VmError> {
|
||||
// 布局错误必须先拒绝,后续槽索引校验依赖这些不变量。
|
||||
validate_layout(&layout)?;
|
||||
// 合并 EXTENDED_ARG 并保留物理单元偏移,未知/截断编码在这里失败。
|
||||
let instructions = decode(&bytecode)?;
|
||||
// 校验成功后才建立可供执行器使用的代码,栈高只来自可达指令。
|
||||
let stack_size = verify(&instructions, constants.len(), &layout)?;
|
||||
Ok(Self {
|
||||
bytecode,
|
||||
@@ -86,7 +114,9 @@ impl EndorCode {
|
||||
})
|
||||
}
|
||||
|
||||
/// 编译器在代码构造后附加声明;这里只检查数量和非空字符串,不解析类型名。
|
||||
pub fn with_source_signature(mut self, signature: SourceSignature) -> Result<Self, VmError> {
|
||||
// 参数注解数量必须对应位置参数;每项与可选 owner 都不允许空串。
|
||||
if signature.parameters.len() != self.parameters().len()
|
||||
|| signature.return_type.is_empty()
|
||||
|| signature.parameters.iter().any(String::is_empty)
|
||||
@@ -102,20 +132,20 @@ impl EndorCode {
|
||||
self.signature = Some(signature);
|
||||
Ok(self)
|
||||
}
|
||||
/// Store 的源码调用/效果/契约检查借用声明;None 表示没有附加源码类型元数据。
|
||||
pub fn source_signature(&self) -> Option<&SourceSignature> {
|
||||
self.signature.as_ref()
|
||||
}
|
||||
|
||||
/// Conservative proof over validated instructions. Local assignment is not
|
||||
/// automatically pure: shared-reference assignment may update its old value.
|
||||
/// Unknown calls and writes remain unknown, even when they could be safe for
|
||||
/// a particular invocation. New opcodes require an explicit audit here.
|
||||
/// Store/effects 的保守只读证明:所有指令(包括不可达部分)都须在白名单内。
|
||||
/// 局部赋值可能触发共享引用更新,不自动视为纯;未知调用/写入也不在这里证明安全。
|
||||
pub fn is_statically_read_only(&self) -> bool {
|
||||
self.instructions
|
||||
.iter()
|
||||
.all(|decoded| read_only_opcode(decoded.instruction.opcode))
|
||||
}
|
||||
|
||||
/// 编译器/宿主附加诊断位置;不校验文件存在或起始行范围,也不改变执行布局。
|
||||
pub fn with_location(
|
||||
mut self,
|
||||
name: impl Into<String>,
|
||||
@@ -128,55 +158,67 @@ impl EndorCode {
|
||||
self
|
||||
}
|
||||
|
||||
/// 宿主诊断/序列查看借用原编码;不允许绕过验证修改字节。
|
||||
pub fn bytecode(&self) -> &[u8] {
|
||||
&self.bytecode
|
||||
}
|
||||
/// eval、效果分析与诊断借用已解码逻辑指令。
|
||||
pub fn instructions(&self) -> &[DecodedInstruction] {
|
||||
&self.instructions
|
||||
}
|
||||
/// 执行器读取常量,代码载荷 trace 枚举其对象边;返回切片不新增托管根。
|
||||
pub fn constants(&self) -> &[Value] {
|
||||
&self.constants
|
||||
}
|
||||
/// 帧参数绑定与函数默认值校验读取参数顺序。
|
||||
pub fn parameters(&self) -> &[String] {
|
||||
&self.layout.parameters
|
||||
}
|
||||
/// 函数闭包校验和帧初始化读取捕获槽顺序。
|
||||
pub fn free_variables(&self) -> &[String] {
|
||||
&self.layout.free_variables
|
||||
}
|
||||
/// 编译器/帧与 Store 分析借用完整布局。
|
||||
pub fn layout(&self) -> &CodeLayout {
|
||||
&self.layout
|
||||
}
|
||||
/// 执行帧用可达路径最大栈高预留容量,不是执行步数上限。
|
||||
pub fn stack_size(&self) -> usize {
|
||||
self.stack_size
|
||||
}
|
||||
/// 错误和代码 repr 读取诊断名称。
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
/// 回溯与 repr 读取文件标识,不访问文件系统。
|
||||
pub fn filename(&self) -> &str {
|
||||
&self.filename
|
||||
}
|
||||
/// 回溯/诊断读取调用方设置的起始行。
|
||||
pub fn first_line(&self) -> usize {
|
||||
self.first_line
|
||||
}
|
||||
/// 帧创建 localsplus 时计算局部槽加外部自由变量槽的总数。
|
||||
pub fn slot_count(&self) -> usize {
|
||||
self.layout.locals.len() + self.layout.free_variables.len()
|
||||
}
|
||||
/// Whether a local/free slot holds a managed closure cell.
|
||||
/// 帧初始化/闭包访问查询槽是否存 Cell,越界槽返回 false。
|
||||
pub fn is_cell_slot(&self, slot: usize) -> bool {
|
||||
is_cell_slot(&self.layout, slot)
|
||||
}
|
||||
/// Name of a local/free slot used by validated instructions.
|
||||
/// 执行器为已验证槽生成未绑定变量等诊断时查询名称。
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the slot is outside this code's local/free-variable layout.
|
||||
/// slot 超出局部/自由变量布局时 panic;调用者不能传入未经边界检查的槽。
|
||||
pub fn slot_name(&self, slot: usize) -> &str {
|
||||
// 局部槽保留原顺序,自由变量索引从局部槽末尾偏移。
|
||||
if slot < self.layout.locals.len() {
|
||||
&self.layout.locals[slot]
|
||||
} else {
|
||||
&self.layout.free_variables[slot - self.layout.locals.len()]
|
||||
}
|
||||
}
|
||||
/// Look up a logical instruction by its code-unit offset.
|
||||
/// eval 按指令指针查询完整逻辑起点;非法偏移返回带位置的字节码错误。
|
||||
pub fn instruction_at(&self, offset: usize) -> Result<DecodedInstruction, VmError> {
|
||||
instruction_index(&self.instructions, offset)
|
||||
.map(|index| self.instructions[index])
|
||||
@@ -189,45 +231,52 @@ impl EndorCode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Heap-allocated, immutable and shareable executable. Function objects hold a
|
||||
/// managed reference to this object rather than embedding a copy of its code.
|
||||
/// Store::new_code 分配的代码载荷;函数持有其托管引用,帧可共享内部不可变执行数据。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EndorCodeObject {
|
||||
/// 共享不可变 Rust 执行数据;Arc 本身不是常量对象的托管根。
|
||||
code: Arc<EndorCode>,
|
||||
}
|
||||
|
||||
impl EndorCodeObject {
|
||||
/// Store::new_code 将已校验执行数据包装成待分配载荷,共享数据由 Arc 持有。
|
||||
pub fn new(code: EndorCode) -> Self {
|
||||
Self {
|
||||
code: Arc::new(code),
|
||||
}
|
||||
}
|
||||
/// Store 校验函数引用的代码布局时借用共享执行数据。
|
||||
pub fn code(&self) -> &EndorCode {
|
||||
&self.code
|
||||
}
|
||||
/// Share immutable executable data without copying constants or wordcode.
|
||||
/// This Rust Arc does not retain heap objects referenced by constants;
|
||||
/// callers must keep the managed code object rooted while using them.
|
||||
/// core::frame 共享执行数据,不深拷贝常量和编码;Arc 不是常量托管对象的拥有根。
|
||||
/// 调用方使用常量时仍须保持托管代码对象有根,不能只靠此 Arc 延长常量对象寿命。
|
||||
pub fn shared_code(&self) -> Arc<EndorCode> {
|
||||
Arc::clone(&self.code)
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectPayload for EndorCodeObject {
|
||||
/// 不可变代码载荷不实现原地赋值,允许只读局部引用重绑定。
|
||||
fn reference_assignment_only(&self) -> bool {
|
||||
true
|
||||
}
|
||||
/// Store::payload 的 Rust 下转型入口。
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
/// 代码载荷的诊断类别,不是名义类型判断。
|
||||
fn kind_name(&self) -> &'static str {
|
||||
"code"
|
||||
}
|
||||
/// Heap 分配/替换代码载荷时登记常量对象边;不递归扫描其指令或源码文本。
|
||||
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
|
||||
// 仅对象变体贡献边,重复出现的同一对象仍逐次计数。
|
||||
for value in self.code.constants() {
|
||||
trace_value(value, visit);
|
||||
}
|
||||
}
|
||||
/// Store::repr 用代码名/文件/起始行生成诊断,不运行代码。
|
||||
fn repr(&self, _identity: ObjectRef) -> Result<String, VmError> {
|
||||
Ok(format!(
|
||||
"<code {} at {}:{}>",
|
||||
@@ -238,7 +287,8 @@ impl ObjectPayload for EndorCodeObject {
|
||||
}
|
||||
}
|
||||
|
||||
// Shared by the local proof and call-graph analysis. Unknown opcodes fail closed.
|
||||
// 白名单之外一律不在此处证明只读;动态调用图分析可以另行判断已知调用。
|
||||
/// 本地只读证明和 effects 调用图分析共享白名单;新增指令需显式审查其副作用。
|
||||
pub(crate) fn read_only_opcode(opcode: Opcode) -> bool {
|
||||
matches!(
|
||||
opcode,
|
||||
@@ -269,7 +319,9 @@ pub(crate) fn read_only_opcode(opcode: Opcode) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
/// with_layout 在解码前调用,验证各名称表内部唯一性以及局部/捕获槽的交叉关系。
|
||||
fn validate_layout(layout: &CodeLayout) -> Result<(), VmError> {
|
||||
// 每类名称表都检查内部唯一性,不能把全部表混在一个 seen 中。
|
||||
for names in [
|
||||
&layout.parameters,
|
||||
&layout.locals,
|
||||
@@ -277,8 +329,11 @@ fn validate_layout(layout: &CodeLayout) -> Result<(), VmError> {
|
||||
&layout.free_variables,
|
||||
&layout.names,
|
||||
] {
|
||||
// 每张表单独去重;不同表间允许的重名关系在后面单独检查。
|
||||
let mut seen = BTreeSet::new();
|
||||
// 空名称无法可靠绑定,表内重复会使槽或名称索引产生歧义。
|
||||
for name in names {
|
||||
// 发现首个违规就退出,构造不产生可执行半成品。
|
||||
if name.is_empty() || !seen.insert(name) {
|
||||
return Err(VmError::InvalidArgument(
|
||||
"empty or duplicate code variable name",
|
||||
@@ -286,11 +341,13 @@ fn validate_layout(layout: &CodeLayout) -> Result<(), VmError> {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 帧按局部槽前缀绑定参数,不能仅检查包含关系。
|
||||
if !layout.locals.starts_with(&layout.parameters) {
|
||||
return Err(VmError::InvalidArgument(
|
||||
"parameter names must lead local slots",
|
||||
));
|
||||
}
|
||||
// Cell 局部必须来自 locals;外部自由变量不得同时作为本地变量。
|
||||
if layout
|
||||
.cell_variables
|
||||
.iter()
|
||||
@@ -307,22 +364,29 @@ fn validate_layout(layout: &CodeLayout) -> Result<(), VmError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 代码公共查询与操作数校验共用:外部自由变量槽全是 Cell,局部槽按名称判断。
|
||||
fn is_cell_slot(layout: &CodeLayout, slot: usize) -> bool {
|
||||
// 自由变量段范围内都为 Cell;段外返回 false,避免越界索引。
|
||||
if slot >= layout.locals.len() {
|
||||
slot < layout.locals.len() + layout.free_variables.len()
|
||||
} else {
|
||||
// 局部槽按名字判断是否提升为 Cell,不改变其原有位置。
|
||||
layout.cell_variables.contains(&layout.locals[slot])
|
||||
}
|
||||
}
|
||||
|
||||
/// instruction_at、跳转验证与栈分析用二分查找逻辑起点;前缀中段不是合法目标。
|
||||
fn instruction_index(instructions: &[DecodedInstruction], offset: usize) -> Option<usize> {
|
||||
instructions
|
||||
.binary_search_by_key(&offset, |instruction| instruction.offset)
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// DecodedInstruction 与 verify 共用相对偏移计算;此助手只算数,不验证目标边界。
|
||||
pub(crate) fn jump_target(instruction: DecodedInstruction) -> Result<usize, VmError> {
|
||||
// 操作数是代码单元距离,不是字节数或逻辑指令数量。
|
||||
let argument = instruction.instruction.argument as usize;
|
||||
// 仅 JumpBackward 做减法,条件跳转与 JumpForward 做加法;checked 防止绕回。
|
||||
let target = if instruction.instruction.opcode == Opcode::JumpBackward {
|
||||
instruction.next_offset.checked_sub(argument)
|
||||
} else {
|
||||
@@ -331,17 +395,20 @@ pub(crate) fn jump_target(instruction: DecodedInstruction) -> Result<usize, VmEr
|
||||
target.ok_or_else(|| invalid(instruction.offset, "jump offset overflow"))
|
||||
}
|
||||
|
||||
/// Validate every operand, then propagate stack heights through reachable
|
||||
/// control flow. Bad jumps, underflow, mismatched joins and fallthrough fail
|
||||
/// before execution; loops must return to the same stack height.
|
||||
/// 拒绝非法索引/跳转、可达栈下溢、路径汇合高度不符和落出代码;循环回边必须保持同高。
|
||||
/// with_layout 解码后调用;先检查全部指令操作数,再分析入口可达控制流并返回最大栈高。
|
||||
fn verify(
|
||||
instructions: &[DecodedInstruction],
|
||||
constants: usize,
|
||||
layout: &CodeLayout,
|
||||
) -> Result<usize, VmError> {
|
||||
// 操作数检查覆盖全部指令,包括入口不可达部分。
|
||||
for instruction in instructions {
|
||||
// 逻辑操作码与完整 u32 参数;前缀已由解码器合并。
|
||||
let Instruction { opcode, argument } = instruction.instruction;
|
||||
// 表索引以 usize 使用;其上界由对应布局或常量数量限定。
|
||||
let index = argument as usize;
|
||||
// Fast 不能指向 Cell,Deref 必须指向 Cell;跳转必须落在完整逻辑起点。
|
||||
let valid = match opcode {
|
||||
Opcode::LoadConst => index < constants,
|
||||
Opcode::LoadFast | Opcode::StoreFast | Opcode::DeleteFast => {
|
||||
@@ -368,6 +435,7 @@ fn verify(
|
||||
Opcode::ExtendedArg => false,
|
||||
_ => true,
|
||||
};
|
||||
// 将错误定位到指令起点,尚未开始传播栈状态。
|
||||
if !valid {
|
||||
return Err(invalid(
|
||||
instruction.offset,
|
||||
@@ -375,15 +443,24 @@ fn verify(
|
||||
));
|
||||
}
|
||||
}
|
||||
// None 表示尚未从入口到达,不表示其操作数未检查。
|
||||
let mut heights = vec![None; instructions.len()];
|
||||
// decode 保证列表非空;代码入口求值栈为空。
|
||||
heights[0] = Some(0_usize);
|
||||
// 工作队列保存待展开的指令索引,每个新到达位置只加入一次。
|
||||
let mut pending = VecDeque::from([0]);
|
||||
// 全部可达指令执行前后的最大栈高度。
|
||||
let mut maximum = 0;
|
||||
// 前向/回向边共用工作队列,汇合高度一致时不重复展开。
|
||||
while let Some(index) = pending.pop_front() {
|
||||
// 队列只保存已赋高度的位置,因此下方 unwrap 由入队不变量保证。
|
||||
let instruction = instructions[index];
|
||||
// 当前指令执行前高度,不含任何类型或值内容信息。
|
||||
let height = heights[index].unwrap();
|
||||
// 逻辑操作码与完整 u32 参数;前缀已由解码器合并。
|
||||
let Instruction { opcode, argument } = instruction.instruction;
|
||||
let argument = argument as usize;
|
||||
// required 是最深访问需求,popped/pushed 是净变化来源;Copy/Swap 不弹出其深层项。
|
||||
let (required, popped, pushed) = match opcode {
|
||||
Opcode::LoadConst
|
||||
| Opcode::LoadFast
|
||||
@@ -407,19 +484,23 @@ fn verify(
|
||||
Opcode::Copy => (argument, 0, 1),
|
||||
Opcode::Swap => (argument, 0, 0),
|
||||
Opcode::Call => {
|
||||
// 显式参数之外还消费函数与接收者/Null 两项,先检查加法溢出。
|
||||
let consumed = argument
|
||||
.checked_add(2)
|
||||
.ok_or_else(|| invalid(instruction.offset, "call operand overflow"))?;
|
||||
(consumed, consumed, 1)
|
||||
}
|
||||
Opcode::BuildMap => {
|
||||
// 每对键值占两项,先检查乘法溢出。
|
||||
let consumed = argument
|
||||
.checked_mul(2)
|
||||
.ok_or_else(|| invalid(instruction.offset, "map operand overflow"))?;
|
||||
(consumed, consumed, 1)
|
||||
}
|
||||
Opcode::MakeFunction => {
|
||||
// 函数创建消费默认值、捕获 Cell 和一个代码对象,压回一个函数。
|
||||
let (defaults, closure) = function_counts(instruction.instruction.argument);
|
||||
// 两个计数来自 16 位段,总和加代码对象项有界。
|
||||
let consumed = defaults + closure + 1;
|
||||
(consumed, consumed, 1)
|
||||
}
|
||||
@@ -427,17 +508,21 @@ fn verify(
|
||||
Opcode::ReturnValue => (1, 1, 0),
|
||||
_ => (0, 0, 0),
|
||||
};
|
||||
// 在减 popped 前检查足够高度,避免栈下溢。
|
||||
if height < required {
|
||||
return Err(invalid(instruction.offset, "evaluation stack underflow"));
|
||||
}
|
||||
// 返回必须恰好留下一个结果供移出,禁止帧内遗留其他栈项。
|
||||
if opcode == Opcode::ReturnValue && height != 1 {
|
||||
return Err(invalid(
|
||||
instruction.offset,
|
||||
"RETURN_VALUE must leave an empty frame stack",
|
||||
));
|
||||
}
|
||||
// 校验保证减法安全;随后统计当前和后继高度。
|
||||
let next_height = height - popped + pushed;
|
||||
maximum = maximum.max(height).max(next_height);
|
||||
// 返回无后继;条件跳转同时传播跳转/顺序两条路径,不猜测条件值。
|
||||
let successors: Vec<usize> = match opcode {
|
||||
Opcode::ReturnValue => Vec::new(),
|
||||
Opcode::JumpForward | Opcode::JumpBackward => vec![jump_target(instruction)?],
|
||||
@@ -446,11 +531,15 @@ fn verify(
|
||||
}
|
||||
_ => vec![instruction.next_offset],
|
||||
};
|
||||
// 每条后继都必须指向现存逻辑指令,顺序落到代码末尾也属于错误。
|
||||
for successor in successors {
|
||||
// 将物理单元偏移转换为逻辑指令索引,拒绝扩展前缀中部。
|
||||
let target = instruction_index(instructions, successor).ok_or_else(|| {
|
||||
invalid(instruction.offset, "execution falls off the code object")
|
||||
})?;
|
||||
// 已到达位置只能接受同高;新位置设置高度并排队。
|
||||
match heights[target] {
|
||||
// 回边或分支汇合必须与第一次到达时的高度完全一致。
|
||||
Some(existing) if existing != next_height => {
|
||||
return Err(invalid(
|
||||
successor,
|
||||
@@ -458,6 +547,7 @@ fn verify(
|
||||
));
|
||||
}
|
||||
Some(_) => {}
|
||||
// 首次到达的后继写入高度,之后排队检查它的栈需求。
|
||||
None => {
|
||||
heights[target] = Some(next_height);
|
||||
pending.push_back(target);
|
||||
|
||||
Reference in New Issue
Block a user