ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Aptos Move Model 开发指南:从 AST 到无栈字节码的静态分析全景

Aptos Move Model 开发指南:从 AST 到无栈字节码的静态分析全景 Aptos Move Model 开发指南从 AST 到无栈字节码的静态分析全景【免费下载链接】aptos-coreAptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.项目地址: https://gitcode.com/GitHub_Trending/ap/aptos-core本指南以 Aptos 仓库 third_party/move/move-model/CLAUDE.md 为骨架结合move-modelcrate 的完整源码系统讲解 Move Model 如何统一呈现 Move 源码语言、抽象语法树AST与中间表示IR。读完本文你将掌握 Move Model 的目录结构、四层环境体系GlobalEnv / ModuleEnv / StructEnv / FunctionEnv、表达式 AST 的构建与遍历、类型系统、无栈字节码Stackless Bytecode及规约Specification的数据结构并能直接上手编写基于该模型的静态分析工具。一、Move Model 是什么Move Modelmove-modelcrate是 Move 生态中面向程序分析与验证的统一代码表示层。它把 Move 的多种代码维度收敛到一个可编程访问的模型上源码语言source language、抽象语法树AST和中间表示IR见 lib.rs 的模块声明。从 model.rs 的模块文档可知该模型允许访问 Move 代码的几乎所有方面所有已声明的函数与类型它们关联的字节码源码位置source location与源码文本规约片段specification fragments。Move Model 是 Move Prover 验证流程的根基验证器需要从源码中提取前置/后置条件requires/ensures、不变量invariant等规约并把 Move 字节码转换为便于形式化推理的无栈形式同时借用检查borrow check等分析也运行在这一模型之上。整个 crate 位于仓库的 third_party/move/move-model 目录是 Move 工具链而非 Aptos 链上逻辑的组成部分。二、目录结构总览move-model仓库由三个核心单元组成move-model/ ├── src/ # 核心模型与 AST │ ├── ast.rs # 抽象语法树定义ExpData / Operation / Spec 等 │ ├── model.rs # 核心数据结构GlobalEnv, ModuleEnv 等 │ ├── ty.rs # 类型系统定义Type / PrimitiveType │ ├── lib.rs # 模型构建入口与模块声明 │ ├── builder/ # 模型构建模块model_builder / module_builder 等 │ ├── exp_builder.rs # 表达式构建辅助 │ ├── exp_rewriter.rs # 表达式变换访问器ExpRewriter │ ├── exp_generator.rs # 表达式生成工具 │ ├── exp_simplifier.rs # 表达式简化 │ ├── constant_folder.rs # 常量折叠优化 │ ├── sourcifier.rs # 将 AST 转回源码文本 │ ├── spec_translator.rs # 翻译规约构造 │ ├── spec_derivation.rs # 规约派生 │ ├── pragmas.rs # 规约 pragma 定义 │ ├── intrinsics.rs # 内建类型声明 │ ├── symbol.rs # 符号池管理 │ ├── memory_labels.rs # 内存标签 │ ├── metadata.rs # 语言版本等元数据 │ ├── options.rs # 模型构建选项 │ ├── pureness_checker.rs # 纯函数检查 │ ├── ty_invariant_analysis.rs # 类型不变量分析 │ └── well_known.rs # 众所周知的名称与常量 ├── bytecode/ # 无栈字节码与分析框架 │ └── src/ │ ├── stackless_bytecode.rs # 字节码与指令定义 │ ├── stackless_bytecode_generator.rs # 从 AST 生成字节码 │ ├── function_target.rs # 函数级分析上下文 │ ├── function_target_pipeline.rs # 变换流水线 │ ├── borrow_analysis.rs # 借用检查分析 │ ├── livevar_analysis.rs # 活跃变量分析 │ ├── reaching_def_analysis.rs # 到达定值分析 │ ├── dataflow_analysis.rs # 通用数据流框架 │ ├── dataflow_domains.rs # 数据流域定义 │ └── ...还有 usage_analysis / fat_loop / astifier 等 └── bytecode-test-utils/ # 测试工具从源码结构看src/层负责从源码构建带类型的模型 规约bytecode/层负责把模型降级为便于分析的指令序列并运行各种数据流分析bytecode-test-utils则提供跨 crate 复用的测试脚手架。三、环境层级GlobalEnv → ModuleEnv → StructEnv / FunctionEnv模型的顶层组织是一个嵌套的环境层级见 model.rsGlobalEnv // 根包含所有模块、源文件、诊断信息 └── ModuleEnv // 全局环境中的某个模块 ├── StructEnv // 模块中定义的某个结构体 └── FunctionEnv // 模块中定义的某个函数各层职责如下GlobalEnvmodel.rs——所有模型数据的所有者模块、源文件、诊断diagnostics、符号池SymbolPool。它是类型检查与错误报告的中枢也是唯一能直接持有数据的地方其余环境都是围绕它数据的引用包装。ModuleEnv——ModuleData的引用包装提供get_structs()、get_functions()、get_spec_vars()、get_named_constants()等访问方法。StructEnv——StructData的引用包装提供get_fields()、get_variants()枚举变体、get_abilities()等访问方法。FunctionEnv——FunctionData的引用包装提供get_parameters()、get_return_type()、get_spec()、get_bytecode()等访问方法。3.1 实体标识符Entity Identifiers所有主要实体都有唯一的标识符类型同样定义于 model.rs标识符定位方式说明ModuleId基于索引从GlobalEnv取得后始终有效StructId/FunId/FieldId基于符号相对于父模块按符号Symbol解析NodeId全局唯一AST 节点的唯一 ID用于挂接类型与位置信息QualifiedIdId模块限定(ModuleId, Id)消除同名冲突QualifiedInstIdId模块限定 实例化带类型实参的限定 ID如QualifiedInstIdStructId3.2 模型构建入口模型不是凭空出现的而是由 lib.rs 的run_model_builder_in_compiler_mode构建。其要点输入为PackageInfo源文件路径集合 命名地址映射区分source、source_deps与deps构建过程使用 v1 编译器作为解析器直到 expansion AST之后运行新的类型检查器将代码与规约编译成类型检查过的 AST该入口默认不附加字节码到模型上字节码由 bytecode 层另行生成。结合 model.rs 中的几个值得注意的常量脚本被当作名为SELF的特殊模块表示SCRIPT_MODULE_NAME支撑规约幽灵内存的结构体以Ghost$前缀命名GHOST_MEMORY_PREFIX编译器生成的局部变量在 source-map 中带有tmp#$标记TEMPORARY_LOCAL_MARKER。四、表达式 ASTExp / ExpData 两级结构表达式采用两级结构见 ast.rspub struct Exp(RcExpData); // 不可变、可共享、克隆廉价 pub enum ExpData { Invalid(NodeId), Value(NodeId, Value), LocalVar(NodeId, Symbol), Temporary(NodeId, TempIndex), Call(NodeId, Operation, VecExp), Invoke(NodeId, Exp, VecExp), Lambda(NodeId, Pattern, Exp, LambdaCaptureKind, OptionExp), Quant(NodeId, QuantKind, Vec(Pattern, Exp), Triggers, Exp), Block(NodeId, Vec(Pattern, Exp), Exp), IfElse(NodeId, Exp, Exp, Exp), // ... 更多变体 }实际源码中ExpData共定义了 22 个变体除上述外还包括Match枚举变体匹配、Return、Sequence、Loop、LoopCont、Assign、Mutate*lhs rhs引用修改、SpecBlock内联规约块类型为()等。Exp内部通过LocalInternExpData做**内部化internment**存储ast.rs因此Exp的克隆与比较极其廉价——这对需要频繁遍历和重建表达式的分析器至关重要。4.1 设计原则ast.rs 的注释明确了表达式布局的三条设计原则变体数量最小化尽量精简变体集合便于统一的泛型遍历内建函数与用户函数统一抽象为Call(.., operation, args)构造Operation枚举覆盖所有内建函数、运算符、常量访问以及用户函数调用每个表达式有唯一 NodeIdNodeId 全局唯一可用来构建属性表附加表达式类型与源码位置等额外信息Temporary(NodeId, TempIndex)既表示字节码中的临时变量也用于表示函数参数TempIndex 为参数在参数列表中的下标。4.2 Operation 枚举分类Operation枚举ast.rs覆盖了全部函数与运算符可归纳为以下几类算术Add、Sub、Mul、Div、Mod源码中还单独提供Negate位运算BitOr、BitAnd、Xor、Shl、Shr比较Eq、Neq、Lt、Le、Gt、Ge逻辑And、Or、Not、Implies、Iff规约专用Exists、Global、Old、Trace、TypeDomain、ResourceDomain、StateDomain、CanModify、SpecPublish/SpecRemove/SpecUpdate结构化Pack、Select、SelectVariants、TestVariants、Tuple、UpdateField内存操作BorrowGlobal、Borrow、Deref、MoveTo、MoveFrom、Freeze向量Vector、Index、Slice、Range、Len及EmptyVec、SingleVec、UpdateVec、ConcatVec、ReverseVec、ContainsVec等高阶与行为谓词Closure、Invoke、Behavior支持requires_of、ensures_of、aborts_of、result_of等函数值行为谓词变换辅助AbortFlag、AbortCode、WellFormed、BoxValue/UnboxValue、NoOp等4.3 表达式访问器Expression Visitors搜索与遍历表达式时优先使用ExpData提供的访问器。文档给出的两个常用入口// 谓词搜索是否存在满足条件的子表达式 exp.as_ref().any(mut |e| matches!(e, ExpData::Temporary(_, idx) if *idx n)); // 先序全遍历返回 false 可剪枝子树 exp.as_ref().visit_pre_order(mut |e| { /* process */ true });ExpData还提供visit_pre_post()先序/后序成对回调用于需要进入前/离开后两个时机的场景。4.4 表达式变换ExpRewriter需要重写表达式时使用 exp_rewriter.rs 中的ExpRewriter其能力定义在ExpRewriterFunctionstrait 中let mut rewriter ExpRewriter::new(env, mut |node_id, target| { match target { RewriteTarget::LocalVar(sym) Some(replacement), _ None, } }); let rewritten rewriter.rewrite_exp(exp);回调收到(NodeId, RewriteTarget)返回Some(new_exp)即替换该节点返回None保持原样。重写器自动处理节点的递归下降并保证 NodeId 的传递性。五、类型系统ty.rs 定义了模型使用的类型表示pub enum Type { Primitive(PrimitiveType), // u8, u64, bool 等 Tuple(VecType), Vector(BoxType), Struct(ModuleId, StructId, VecType), TypeParameter(u16), Fun(BoxType, BoxType, AbilitySet), // 函数类型参数类型 结果类型 能力集 Reference(ReferenceKind, BoxType), // 与 mut // 仅规约中出现的类型 TypeDomain(BoxType), // 类型域 ResourceDomain(ModuleId, StructId, OptionVecType), // 资源域 StateDomain, // 全局状态域 // 类型检查期间的临时类型 Error, Var(u32), // 类型变量unification 用 }PrimitiveType的完整集合ty.rs包括Bool、U8/U16/U32/U64/U128/U256、I8/I16/I32/I64/I128/I256、Address、Signer以及仅规约中出现的Num无界数、Range、EventStore。ReferenceKind区分Immutable与Mutablemut。能力Abilities为Copy、Drop、Key、Store与 Move 语言规范一致AbilitySet从move-core-types复用。类型检查期间Type::Var(u32)作为类型变量参与统一unificationty.rs 中的Substitution与Constraint负责记录变量绑定与约束例如SomeNumber整型字面量的数字类型约束、SomeReference、SomeStruct要求目标结构体具有某些字段、NoReference/NoTuple/NoPhantom字段与类型实参的禁用约束、HasAbilities以及WithDefault推断默认值等。理解这些约束有助于读懂类型推断错误报告。六、无栈字节码Stackless Bytecode与 Move VM 的栈式字节码不同无栈字节码使用临时变量temporaries与显式控制流每条指令的操作数都以TempIndex显式引用便于形式化分析与数据流框架处理。定义见 stackless_bytecode.rspub enum Bytecode { Assign(AttrId, TempIndex, TempIndex, AssignKind), Call(AttrId, VecTempIndex, Operation, VecTempIndex, OptionAbortAction), Ret(AttrId, VecTempIndex), Load(AttrId, TempIndex, Constant), Branch(AttrId, Label, Label, TempIndex), Jump(AttrId, Label), Label(AttrId, Label), Abort(AttrId, TempIndex, OptionTempIndex), Nop(AttrId), SpecBlock(AttrId, Spec), // 规约插桩专用扩展字节码 SaveMem(AttrId, MemoryLabel, QualifiedInstIdStructId), SaveSpecVar(AttrId, MemoryLabel, QualifiedInstIdSpecVarId), Prop(AttrId, PropKind, Exp), // Assert / assume }值得注意的细节AbortAction(Label, TempIndex)stackless_bytecode.rs描述调用失败时的动作跳转到哪个标签以及先把 abort code 存进哪个临时变量AssignKind同文件 L77-L89区分Copy、Move、Store与Inferred由活跃变量分析推断Constant覆盖Bool、U8/U16/U32/U64/U128/U256、I8/I16/I32/I64/I128、Address、ByteArray、AddressArray、Vector每个指令带AttrId可在FunctionTarget中挂接类型与位置属性Prop(.., PropKind, Exp)是断言/假设的载体规约插桩如SaveMem、SaveSpecVar会在程序点保存内存快照供old(..)引用。6.1 FunctionTarget 与变换流水线FunctionTarget——用可变的字节码/类型信息包装FunctionEnv。它支持同一函数的多个variant如Baseline、Verification基线变体保留原始字节码验证变体则经过规约插桩等变换。FunctionTargetsHolder——以限定函数 ID variant为键容纳所有FunctionTarget的容器。FunctionTargetPipeline——一串FunctionTargetProcessor按序对字节码做变换见 function_target_pipeline.rs。新增分析/变换时就是往这条流水线里挂一个新的 processor。6.2 字节码分析框架bytecode/src 下内置了多个可复用的分析器借用分析borrow_analysis.rs——追踪引用reference的生命周期与所有权识别借用冲突活跃变量分析livevar_analysis.rs——确定每个程序点上仍被使用的变量为死代码消除与AssignKind::Inferred提供依据到达定值分析reaching_def_analysis.rs——经典的 reaching definitions 数据流分析通用数据流框架dataflow_analysis.rs——通过TransferFunctionstrait 抽象前向/后向分析配合 dataflow_domains.rs 中的域定义DataflowDomain、Map、Set等编写新分析时只需实现转移函数与汇合join语义。从bytecode/tests/下的测试目录borrow/、livevar/、reaching_def/、usage_analysis/、from_move/可以看到每个分析器都配有.move源文件与.exp基线输出是学习各分析语义的最佳入口。七、规约SpecificationsMove Model 的规约层是为形式化验证Move Prover服务的核心数据结构。7.1 ConditionKind 完整列表ast.rs 中ConditionKind覆盖全部规约条件类型pub enum ConditionKind { LetPost(Symbol, Loc), LetPre(Symbol, Loc), // let 绑定后/前状态 Assert, Assume, Decreases, AbortsIf, AbortsWith, SucceedsIf, Emits, Ensures, Requires, StructInvariant, FunctionInvariant, LoopInvariant, GlobalInvariant(Vec(Symbol, Loc)), GlobalInvariantUpdate(Vec(Symbol, Loc)), SchemaInvariant, Axiom(Vec(Symbol, Loc)), Update, }同一文件还定义了辅助方法allows_old()判断该条件是否允许old(..)表达式如Ensures、LetPost、Emits允许Requires不允许、allowed_on_fun_decl()函数声明上允许的规约如Requires、AbortsIf、Emits、Ensures、LetPre/LetPost与allowed_on_fun_impl()函数体内允许的规约如Assert、Assume、Decreases、LoopInvariant。7.2 Spec 与 Condition 结构pub struct Spec { pub loc: OptionLoc, pub conditions: VecCondition, pub properties: PropertyBag, // pragma 属性集合 pub on_impl: BTreeMapConditionKind, Condition, // 函数体代码点关联的规约 } pub struct Condition { pub loc: Loc, pub kind: ConditionKind, pub properties: PropertyBag, pub exp: Exp, // 条件主表达式 pub additional_exps: VecExp, // 附加表达式如 Emits 的句柄/消息 }实际源码ast.rs在Spec上还增加了三个字段frame_spec: OptionFrameSpecmodifies/reads 框架条件、update_map: BTreeMapNodeId, Condition函数体内联的幽灵变量更新语句、proof: OptionProof引导 SMT 求解器的结构化证明提示。FrameSpecast.rs记录修改目标、读取资源类型以及modifies_off *这样的通配标记。此外pragmas.rs 定义了大量 pragma 常量如OPAQUE_PRAGMA、VERIFY_PRAGMA、DELEGATE_INVARIANTS_TO_CALLER_PRAGMA等它们会被编码进PropertyBag。八、常见代码模式以下模式来自文档并已对照源码确认 API 形态可直接用于编写分析工具。8.1 遍历模块与函数for module in env.get_modules() { if !module.is_target() { continue; } // 只处理目标模块跳过依赖 for func in module.get_functions() { if func.is_native() { continue; } // 跳过原生函数 // Process func } }8.2 读取函数规约let spec func_env.get_spec(); for cond in spec.conditions { if matches!(cond.kind, ConditionKind::Ensures) { // Handle ensures condition } }8.3 修改函数规约let mut spec func_env.get_mut_spec(); spec.conditions.push(Condition { loc: func_env.get_loc(), kind: ConditionKind::Ensures, properties: BTreeMap::new(), exp: my_exp, additional_exps: vec![], });8.4 构建表达式let node_id env.new_node(loc, result_type); let exp ExpData::Call(node_id, Operation::Eq, vec![left, right]).into_exp();更复杂的表达式可用 exp_builder.rs 与 builder/exp_builder.rs 中的构造辅助函数完成如构建old(..)、量词、exists等exp_generator.rs 则面向从数据生成表达式的场景。8.5 处理字节码let target targets.get_target(func_env, FunctionVariant::Baseline); for (offset, bc) in target.get_bytecode().iter().enumerate() { match bc { Bytecode::Ret(_, vals) { /* handle return */ }, Bytecode::Call(_, dests, op, srcs, _) { /* handle call */ }, _ {} } }注意FunctionVariant有多种Baseline / Verification 等分析原始语义用Baseline分析带规约插桩的验证语义用Verification。九、测试与验证Move Model 采用**基线测试baseline tests**机制move-model自身的测试位于 tests/testsuite.rstests/sources/下每对.move与.exp文件对应一个用例覆盖conditions、invariants、schemas、pragmas、quantifiers、structs、lets、intrinsic_decl等主题每个用例都有_err与_ok两个变体分别验证报错路径与通过路径move-stackless-bytecode的测试位于 bytecode/tests/testsuite.rsborrow、livevar、reaching_def等子目录字节码 AST 生成测试位于 bytecode/ast-generator-tests/tests/testsuite.rs覆盖loops、conditionals、match、abort_example等控制流形态通用测试脚手架由 bytecode-test-utils/src/lib.rs 提供。本地运行在仓库根目录执行cargo test -p move-model与cargo test -p move-stackless-bytecode即可复现这些基线测试UPDATE_BASELINE1环境变量可重生成.exp基线修改分析器行为后常用。这是验证你对模型的理解是否正确的最直接手段。十、编码建议编写基于 Move Model 的工具时CLAUDE.md 给出的一条核心纪律是Do always look into move-model helper functions before creating new functions on common data types like expressions.即在给Exp、Type这类公共数据类型新增方法之前先检查move-model是否已有现成的辅助函数——例如遍历用ExpData::visit_pre_order/any变换用ExpRewriter类型实例化用Type::instantiate。复用既有辅助既避免 API 膨胀也保证与 Prover 等下游消费者行为一致。结语Move Model 是连接Move 源码与程序分析/形式验证的桥梁GlobalEnv四层环境体系统一了模块、结构体、函数与规约的访问入口ExpData/Operation提供了最小而完备的表达式表示Type与约束系统支撑类型推断无栈字节码及其FunctionTarget流水线把代码降为可分析形式并通过数据流框架承载借用、活跃变量等分析Spec结构则为验证器提供全部规约素材。掌握这条从 AST 到无栈字节码的链路就掌握了为 Aptos/Move 编写静态分析、验证与代码生成工具的基础能力。【免费下载链接】aptos-coreAptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.项目地址: https://gitcode.com/GitHub_Trending/ap/aptos-core创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表