aptos-disassemble
Version:
Aptos Move disassembler for TypeScript. This package provides utilities to disassemble Move bytecode and modules for the Aptos blockchain.
1 lines • 208 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/utils/SignatureUtils.ts","../src/types/DisassemblerOptions.ts","../src/core/DisassemblerContext.ts","../src/core/ControlFlowGraph.ts","../src/disassemblers/InstructionDisassembler.ts","../src/disassemblers/ModuleDisassembler.ts","../src/types/MoveModule.ts","../src/types/serializedType.ts","../src/types/disassembler.ts","../src/instruction-helpers.ts","../src/loaders/BytecodeLoader.ts"],"sourcesContent":["/**\n * Main entry point for the aptos-disassemble package\n * Provides clean, class-based API for Move bytecode disassembly\n */\n\n// Export main classes\nexport { DisassemblerContext } from \"./core/DisassemblerContext\";\nexport { VMControlFlowGraph } from \"./core/ControlFlowGraph\";\nexport type { ControlFlowGraph } from \"./core/ControlFlowGraph\";\nexport { ModuleDisassembler } from \"./disassemblers/ModuleDisassembler\";\nexport { InstructionDisassembler } from \"./disassemblers/InstructionDisassembler\";\nexport { BytecodeLoader } from \"./loaders/BytecodeLoader\";\n\n// Export types\nexport * from \"./types/MoveModule\";\nexport type { DisassemblerOptions } from \"./types/DisassemblerOptions\";\n\n// Export utilities\nexport { parseSignatureToken, parseAbilities } from \"./utils/SignatureUtils\";\n\n// Export compatibility functions\nexport { disassembleMoveModule, disassemble_instruction } from \"./types\";\n\nimport { BytecodeLoader } from \"./loaders/BytecodeLoader\";\nimport { DisassemblerContext } from \"./core/DisassemblerContext\";\nimport { ModuleDisassembler } from \"./disassemblers/ModuleDisassembler\";\nimport { VMControlFlowGraph } from \"./core/ControlFlowGraph\";\nimport { MoveModule } from \"./types/MoveModule\";\nimport { DisassemblerOptions } from \"./types/DisassemblerOptions\";\n\n/**\n * Main disassembly function - improved version with class-based architecture\n */\nexport function disassemble(module: MoveModule, options?: Partial<DisassemblerOptions>): string {\n const context = new DisassemblerContext(module, options);\n const disassembler = new ModuleDisassembler(context);\n return disassembler.disassemble();\n}\n\n/**\n * Convenience function to disassemble bytecode directly to string\n */\nexport function disassemble_to_string(\n bytecode: Uint8Array,\n options?: Partial<DisassemblerOptions>\n): string {\n const module = BytecodeLoader.loadFromBytecode(bytecode);\n return disassemble(module, options);\n}\n\n/**\n * Class-based API for more advanced usage\n */\nexport class MoveDisassembler {\n private context: DisassemblerContext;\n private moduleDisassembler: ModuleDisassembler;\n\n constructor(module: MoveModule, options?: Partial<DisassemblerOptions>) {\n this.context = new DisassemblerContext(module, options);\n this.moduleDisassembler = new ModuleDisassembler(this.context);\n }\n\n static fromBytecode(\n bytecode: Uint8Array,\n options?: Partial<DisassemblerOptions>\n ): MoveDisassembler {\n const module = BytecodeLoader.loadFromBytecode(bytecode);\n return new MoveDisassembler(module, options);\n }\n\n disassemble(): string {\n return this.moduleDisassembler.disassemble();\n }\n\n getContext(): DisassemblerContext {\n return this.context;\n }\n\n getModule(): MoveModule {\n return this.context.getRawModule();\n }\n\n getSelfModuleName(): string {\n return this.context.getSelfModuleName();\n }\n\n getSelfModuleAddress(): string {\n return this.context.getSelfModuleAddress();\n }\n\n getVersion(): number {\n return this.context.version;\n }\n\n /**\n * Create a control flow graph for a specific function in the module\n */\n createControlFlowGraph(functionIndex: number): VMControlFlowGraph {\n const module = this.context.getRawModule();\n if (!module.function_defs || functionIndex >= module.function_defs.length) {\n throw new Error(`Function index ${functionIndex} out of bounds`);\n }\n\n const functionDef = module.function_defs[functionIndex];\n if (!functionDef.code) {\n throw new Error(`Function at index ${functionIndex} has no code`);\n }\n\n return new VMControlFlowGraph(functionDef.code.code);\n }\n}\n","/**\n * Utility functions for parsing signature tokens and abilities\n */\nimport { SignatureToken, MoveModule } from \"../types/MoveModule\";\n\nexport const AbilityValues = {\n Copy: 0x1,\n Drop: 0x2,\n Store: 0x4,\n Key: 0x8,\n};\n\nexport function parseAbilities(abilities: number): string[] {\n const result: string[] = [];\n if (abilities & AbilityValues.Copy) result.push(\"copy\");\n if (abilities & AbilityValues.Drop) result.push(\"drop\");\n if (abilities & AbilityValues.Store) result.push(\"store\");\n if (abilities & AbilityValues.Key) result.push(\"key\");\n return result;\n}\n\nexport function parseSignatureToken(token: SignatureToken, module: MoveModule): string {\n switch (token.kind) {\n case \"Bool\":\n return \"bool\";\n case \"U8\":\n return \"u8\";\n case \"U16\":\n return \"u16\";\n case \"U32\":\n return \"u32\";\n case \"U64\":\n return \"u64\";\n case \"U128\":\n return \"u128\";\n case \"U256\":\n return \"u256\";\n case \"Address\":\n return \"address\";\n case \"Signer\":\n return \"signer\";\n case \"Vector\":\n return `vector<${parseSignatureToken(token.type, module)}>`;\n case \"Reference\":\n return `&${parseSignatureToken(token.type, module)}`;\n case \"MutableReference\":\n return `&mut ${parseSignatureToken(token.type, module)}`;\n case \"TypeParameter\":\n return `T${token.index}`;\n case \"Struct\": {\n const structHandle = module.struct_handles[token.handle];\n if (!structHandle) {\n throw new Error(`Struct handle at index ${token.handle} is out of bounds`);\n }\n const structName = module.identifiers[structHandle.name];\n if (!structName) {\n throw new Error(`Struct name at index ${structHandle.name} is out of bounds`);\n }\n\n // Only add module prefix if it's not the current module\n if (structHandle.module === module.selfModuleHandleIdx) {\n return structName;\n } else {\n const moduleHandle = module.module_handles[structHandle.module];\n if (!moduleHandle) {\n throw new Error(`Module handle at index ${structHandle.module} is out of bounds`);\n }\n const moduleName = module.identifiers[moduleHandle.name];\n if (!moduleName) {\n throw new Error(`Module name at index ${moduleHandle.name} is out of bounds`);\n }\n return `${moduleName}::${structName}`;\n }\n }\n case \"StructInstantiation\": {\n const structHandle = module.struct_handles[token.handle];\n if (!structHandle) {\n throw new Error(`Struct handle at index ${token.handle} is out of bounds`);\n }\n const structName = module.identifiers[structHandle.name];\n if (!structName) {\n throw new Error(`Struct name at index ${structHandle.name} is out of bounds`);\n }\n const typeParams = token.typeParams.map((tp) => parseSignatureToken(tp, module));\n\n // Only add module prefix if it's not the current module\n if (structHandle.module === module.selfModuleHandleIdx) {\n return `${structName}<${typeParams.join(\", \")}>`;\n } else {\n const moduleHandle = module.module_handles[structHandle.module];\n if (!moduleHandle) {\n throw new Error(`Module handle at index ${structHandle.module} is out of bounds`);\n }\n const moduleName = module.identifiers[moduleHandle.name];\n if (!moduleName) {\n throw new Error(`Module name at index ${moduleHandle.name} is out of bounds`);\n }\n return `${moduleName}::${structName}<${typeParams.join(\", \")}>`;\n }\n }\n case \"Function\": {\n const args = token.args.map((arg) => parseSignatureToken(arg, module));\n const results = token.results.map((result) => parseSignatureToken(result, module));\n const abilities = parseAbilities(token.abilities);\n const abilitiesStr = abilities.length > 0 ? ` + ${abilities.join(\" + \")}` : \"\";\n return `|${args.join(\", \")}| -> (${results.join(\", \")})${abilitiesStr}`;\n }\n default:\n throw new Error(`Unknown signature token kind: ${token}`);\n }\n}\n","/**\n * Configuration options for the disassembler\n */\nexport interface DisassemblerOptions {\n /** Whether to print basic block markers (B0:, B1:, etc.) in function code */\n printBasicBlocks?: boolean;\n}\n\nexport const DEFAULT_DISASSEMBLER_OPTIONS: DisassemblerOptions = {\n printBasicBlocks: true,\n};\n","/**\n * DisassemblerContext - Core context class that holds module information\n * and provides convenient methods for accessing module data\n */\nimport { MoveModule, SignatureToken } from \"../types/MoveModule\";\nimport { parseSignatureToken, parseAbilities } from \"../utils/SignatureUtils\";\nimport { DisassemblerOptions, DEFAULT_DISASSEMBLER_OPTIONS } from \"../types/DisassemblerOptions\";\n\nexport class DisassemblerContext {\n public readonly options: DisassemblerOptions;\n\n constructor(\n private readonly module: MoveModule,\n options?: Partial<DisassemblerOptions>\n ) {\n this.options = { ...DEFAULT_DISASSEMBLER_OPTIONS, ...options };\n }\n\n get version(): number {\n return this.module.version;\n }\n\n get selfModuleHandleIdx(): number {\n return this.module.selfModuleHandleIdx;\n }\n\n // Module handle utilities\n getSelfModule() {\n const selfModule = this.module.module_handles[this.selfModuleHandleIdx];\n if (!selfModule) {\n throw new Error(\"Self module handle index is out of bounds\");\n }\n return selfModule;\n }\n\n getSelfModuleAddress(): string {\n const selfModule = this.getSelfModule();\n return this.getAddressIdentifier(selfModule.address);\n }\n\n getSelfModuleName(): string {\n const selfModule = this.getSelfModule();\n return this.getIdentifier(selfModule.name);\n }\n\n // Identifier utilities\n getIdentifier(index: number): string {\n const identifier = this.module.identifiers[index];\n if (identifier === undefined) {\n throw new Error(`Identifier at index ${index} is out of bounds`);\n }\n return identifier;\n }\n\n getAddressIdentifier(index: number): string {\n const address = this.module.address_identifiers[index];\n if (address === undefined) {\n throw new Error(`Address identifier at index ${index} is out of bounds`);\n }\n return address;\n }\n\n // Signature utilities\n getSignature(index: number): SignatureToken[] {\n const signature = this.module.signatures[index];\n if (!signature) {\n throw new Error(`Signature at index ${index} is out of bounds`);\n }\n return signature;\n }\n\n parseSignatureToken(token: SignatureToken): string {\n return parseSignatureToken(token, this.module);\n }\n\n parseAbilities(abilities: number): string[] {\n return parseAbilities(abilities);\n }\n\n // Handle utilities\n getModuleHandle(index: number) {\n const handle = this.module.module_handles[index];\n if (!handle) {\n throw new Error(`Module handle at index ${index} is out of bounds`);\n }\n return handle;\n }\n\n getStructHandle(index: number) {\n const handle = this.module.struct_handles[index];\n if (!handle) {\n throw new Error(`Struct handle at index ${index} is out of bounds`);\n }\n return handle;\n }\n\n getFunctionHandle(index: number) {\n const handle = this.module.function_handles[index];\n if (!handle) {\n throw new Error(`Function handle at index ${index} is out of bounds`);\n }\n return handle;\n }\n\n getFieldHandle(index: number) {\n const handle = this.module.field_handles[index];\n if (!handle) {\n throw new Error(`Field handle at index ${index} is out of bounds`);\n }\n return handle;\n }\n\n // Definition utilities\n getStructDefinition(index: number) {\n const def = this.module.struct_defs[index];\n if (!def) {\n throw new Error(`Struct definition at index ${index} is out of bounds`);\n }\n return def;\n }\n\n getFunctionDefinition(index: number) {\n const def = this.module.function_defs[index];\n if (!def) {\n throw new Error(`Function definition at index ${index} is out of bounds`);\n }\n return def;\n }\n\n // Instantiation utilities\n getStructDefInstantiation(index: number) {\n const inst = this.module.struct_defs_inst[index];\n if (!inst) {\n throw new Error(`Struct def instantiation at index ${index} is out of bounds`);\n }\n return inst;\n }\n\n getFunctionInstantiation(index: number) {\n const inst = this.module.function_inst[index];\n if (!inst) {\n throw new Error(`Function instantiation at index ${index} is out of bounds`);\n }\n return inst;\n }\n\n getFieldInstantiation(index: number) {\n const inst = this.module.field_insts[index];\n if (!inst) {\n throw new Error(`Field instantiation at index ${index} is out of bounds`);\n }\n return inst;\n }\n\n // Variant utilities (for enum support)\n getStructVariantHandle(index: number) {\n const handle = this.module.struct_variant_handles[index];\n if (!handle) {\n throw new Error(`Struct variant handle at index ${index} is out of bounds`);\n }\n return handle;\n }\n\n getStructVariantInstantiation(index: number) {\n const inst = this.module.struct_variant_inst[index];\n if (!inst) {\n throw new Error(`Struct variant instantiation at index ${index} is out of bounds`);\n }\n return inst;\n }\n\n getVariantFieldHandle(index: number) {\n const handle = this.module.variant_field_handles[index];\n if (!handle) {\n throw new Error(`Variant field handle at index ${index} is out of bounds`);\n }\n return handle;\n }\n\n getVariantFieldInstantiation(index: number) {\n const inst = this.module.variant_field_inst[index];\n if (!inst) {\n throw new Error(`Variant field instantiation at index ${index} is out of bounds`);\n }\n return inst;\n }\n\n // Access to raw module for compatibility\n getRawModule(): MoveModule {\n return this.module;\n }\n}\n","/**\n * Control Flow Graph implementation for Move bytecode verification\n * Based on the Rust implementation from Move VM\n * \n * Copyright (c) The Diem Core Contributors\n * Copyright ( // Build a mapping from a block id to the next block id in the traversal order\n const traversalSuccessors = new Map<BlockId, BlockId>();\n for (let i = 0; i < traversalOrder.length - 1; i++) {\n traversalSuccessors.set(traversalOrder[i], traversalOrder[i + 1]);\n }\n\n this.traversalSuccessors = traversalSuccessors;\n this.loopHeads = loopHeads;ove Contributors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { Bytecode, CodeOffset } from \"../types/MoveModule\";\n\n// Type aliases for better compatibility with Rust implementation\ntype BlockId = CodeOffset;\n\n/**\n * A trait that specifies the basic requirements for a CFG\n */\nexport interface ControlFlowGraph {\n /** Start index of the block ID in the bytecode vector */\n blockStart(blockId: BlockId): CodeOffset;\n\n /** End index of the block ID in the bytecode vector */\n blockEnd(blockId: BlockId): CodeOffset;\n\n /** Successors of the block ID in the bytecode vector */\n successors(blockId: BlockId): BlockId[];\n\n /** Return the next block in traversal order */\n nextBlock(blockId: BlockId): BlockId | null;\n\n /** Iterator over the indexes of instructions in this block */\n instrIndexes(blockId: BlockId): Iterable<CodeOffset>;\n\n /** Return an iterator over the blocks of the CFG */\n blocks(): BlockId[];\n\n /** Return the number of blocks (vertices) in the control flow graph */\n numBlocks(): number;\n\n /** Return the id of the entry block for this control-flow graph */\n entryBlockId(): BlockId;\n\n /** Checks if the block ID is a loop head */\n isLoopHead(blockId: BlockId): boolean;\n\n /** Checks if the edge from cur->next is a back edge */\n isBackEdge(cur: BlockId, next: BlockId): boolean;\n\n /** Return the number of back edges in the cfg */\n numBackEdges(): number;\n}\n\n/**\n * Basic block structure\n */\nclass BasicBlock {\n constructor(\n public readonly exit: CodeOffset,\n public readonly successors: BlockId[]\n ) {}\n\n public display(entry: BlockId): void {\n console.log(\"+=======================+\");\n console.log(`| Enter: ${entry} |`);\n console.log(\"+-----------------------+\");\n console.log(`==> Children: ${JSON.stringify(this.successors)}`);\n console.log(\"+-----------------------+\");\n console.log(`| Exit: ${this.exit} |`);\n console.log(\"+=======================+\");\n }\n}\n\n/**\n * Exploration state for loop analysis\n */\nenum Exploration {\n InProgress = \"InProgress\",\n Done = \"Done\",\n}\n\nconst ENTRY_BLOCK_ID: BlockId = 0;\n\n/**\n * The control flow graph that we build from the bytecode.\n */\nexport class VMControlFlowGraph implements ControlFlowGraph {\n /** The basic blocks */\n private readonly basicBlocks: Map<BlockId, BasicBlock>;\n /** Basic block ordering for traversal */\n private readonly traversalSuccessors: Map<BlockId, BlockId>;\n /** Map of loop heads with all of their back edges */\n private readonly loopHeads: Map<BlockId, Set<BlockId>>;\n\n constructor(code: Bytecode[]) {\n const codeLen = code.length as CodeOffset;\n\n // First go through and collect block ids, i.e., offsets that begin basic blocks.\n // Need to do this first in order to handle backwards edges.\n const blockIds = new Set<BlockId>();\n blockIds.add(ENTRY_BLOCK_ID);\n\n for (let pc = 0; pc < code.length; pc++) {\n VMControlFlowGraph.recordBlockIds(pc as CodeOffset, code, blockIds);\n }\n\n // Create basic blocks\n const basicBlocks = new Map<BlockId, BasicBlock>();\n let entry = 0;\n const exitToEntry = new Map<CodeOffset, BlockId>();\n\n for (let pc = 0; pc < code.length; pc++) {\n const coPc = pc as CodeOffset;\n\n // Create a basic block\n if (this.isEndOfBlock(coPc, code, blockIds)) {\n const exit = coPc;\n exitToEntry.set(exit, entry);\n const successors = this.getSuccessors(coPc, code);\n const bb = new BasicBlock(exit, successors);\n basicBlocks.set(entry, bb);\n entry = coPc + 1;\n }\n }\n\n this.basicBlocks = basicBlocks;\n console.assert(entry === codeLen, `Entry ${entry} should equal code length ${codeLen}`);\n\n // Loop analysis\n // This section identifies loops in the control-flow graph, picks a back edge and loop head\n // (the basic block the back edge returns to), and decides the order that blocks are\n // traversed during abstract interpretation (reverse post-order).\n\n const exploration = new Map<BlockId, Exploration>();\n const stack: BlockId[] = [ENTRY_BLOCK_ID];\n\n // For every loop in the CFG that is reachable from the entry block, there is an entry in\n // `loopHeads` mapping to all the back edges pointing to it, and vice versa.\n const loopHeads = new Map<BlockId, Set<BlockId>>();\n\n // Blocks appear in `postOrder` after all the blocks in their (non-reflexive) sub-graph.\n const postOrder: BlockId[] = [];\n\n while (stack.length > 0) {\n const block = stack.pop()!;\n const explorationState = exploration.get(block);\n\n if (explorationState === undefined) {\n // Record the fact that exploration of this block and its sub-graph has started.\n exploration.set(block, Exploration.InProgress);\n\n // Push the block back on the stack to finish processing it, and mark it as done\n // once its sub-graph has been traversed.\n stack.push(block);\n\n const successors = this.basicBlocks.get(block)?.successors || [];\n for (const succ of successors) {\n const succExploration = exploration.get(succ);\n\n if (succExploration === undefined) {\n // This successor has never been visited before, add it to the stack to\n // be explored before `block` gets marked `Done`.\n stack.push(succ);\n } else if (succExploration === Exploration.InProgress) {\n // This block's sub-graph was being explored, meaning it is a (reflexive\n // transitive) predecessor of `block` as well as being a successor,\n // implying a loop has been detected -- greedily choose the successor\n // block as the loop head.\n if (!loopHeads.has(succ)) {\n loopHeads.set(succ, new Set());\n }\n loopHeads.get(succ)!.add(block);\n }\n // Cross-edge detected, this block and its entire sub-graph (modulo\n // cycles) has already been explored via a different path, and is\n // already present in `postOrder`.\n // We skip this case.\n }\n } else if (explorationState === Exploration.InProgress) {\n // Finish up the traversal by adding this block to the post-order traversal\n // after its sub-graph (modulo cycles).\n postOrder.push(block);\n exploration.set(block, Exploration.Done);\n }\n // Already traversed the sub-graph reachable from this block, so skip it.\n }\n\n const traversalOrder = postOrder.reverse(); // Reverse post order\n\n // Build a mapping from a block id to the next block id in the traversal order\n const traversalSuccessors = new Map<BlockId, BlockId>();\n for (let i = 0; i < traversalOrder.length - 1; i++) {\n traversalSuccessors.set(traversalOrder[i], traversalOrder[i + 1]);\n }\n\n this.traversalSuccessors = traversalSuccessors;\n this.loopHeads = loopHeads;\n }\n\n public display(): void {\n for (const [entry, block] of this.basicBlocks) {\n block.display(entry);\n }\n console.log(\"Traversal:\", Object.fromEntries(this.traversalSuccessors));\n }\n\n private isEndOfBlock(pc: CodeOffset, code: Bytecode[], blockIds: Set<BlockId>): boolean {\n return pc + 1 === code.length || blockIds.has(pc + 1);\n }\n\n private static recordBlockIds(pc: CodeOffset, code: Bytecode[], blockIds: Set<BlockId>): void {\n const bytecode = code[pc];\n\n // Get the offset from branch instructions\n const offset = this.getOffset(bytecode);\n if (offset !== null) {\n blockIds.add(offset);\n }\n\n // If this is a branch instruction and not the last instruction, the next instruction starts a new block\n if (this.isBranch(bytecode) && pc + 1 < code.length) {\n blockIds.add(pc + 1);\n }\n }\n\n private static getOffset(bytecode: Bytecode): CodeOffset | null {\n switch (bytecode.kind) {\n case \"BrTrue\":\n case \"BrFalse\":\n case \"Branch\":\n return bytecode.codeOffset;\n default:\n return null;\n }\n }\n\n private static isBranch(bytecode: Bytecode): boolean {\n switch (bytecode.kind) {\n case \"BrTrue\":\n case \"BrFalse\":\n case \"Branch\":\n case \"Ret\":\n case \"Abort\":\n return true;\n default:\n return false;\n }\n }\n\n private getSuccessors(pc: CodeOffset, code: Bytecode[]): BlockId[] {\n const bytecode = code[pc];\n const successors: BlockId[] = [];\n\n switch (bytecode.kind) {\n case \"BrTrue\":\n case \"BrFalse\":\n // Conditional branches have two successors: the branch target and the next instruction\n successors.push(bytecode.codeOffset);\n if (pc + 1 < code.length) {\n successors.push(pc + 1);\n }\n break;\n case \"Branch\":\n // Unconditional branch has one successor: the branch target\n successors.push(bytecode.codeOffset);\n break;\n case \"Ret\":\n case \"Abort\":\n // Terminal instructions have no successors\n break;\n default:\n // Regular instructions flow to the next instruction\n if (pc + 1 < code.length) {\n successors.push(pc + 1);\n }\n break;\n }\n\n return successors;\n }\n\n /**\n * A utility function that implements BFS-reachability from blockId\n */\n private traverseBy(blockId: BlockId): BlockId[] {\n const ret: BlockId[] = [];\n let index = 0;\n const seen = new Set<BlockId>();\n\n ret.push(blockId);\n seen.add(blockId);\n\n while (index < ret.length) {\n const currentBlockId = ret[index];\n index += 1;\n const successors = this.successors(currentBlockId);\n for (const successor of successors) {\n if (!seen.has(successor)) {\n ret.push(successor);\n seen.add(successor);\n }\n }\n }\n\n return ret;\n }\n\n public reachableFrom(blockId: BlockId): BlockId[] {\n return this.traverseBy(blockId);\n }\n\n public traversalIndex(blockId: BlockId): number {\n const keys = Array.from(this.traversalSuccessors.keys());\n const index = keys.indexOf(blockId);\n return index === -1 ? this.traversalSuccessors.size : index;\n }\n\n // ControlFlowGraph interface implementation\n\n blockStart(blockId: BlockId): CodeOffset {\n return blockId;\n }\n\n blockEnd(blockId: BlockId): CodeOffset {\n const block = this.basicBlocks.get(blockId);\n if (!block) {\n throw new Error(`Block ${blockId} not found`);\n }\n return block.exit;\n }\n\n successors(blockId: BlockId): BlockId[] {\n const block = this.basicBlocks.get(blockId);\n if (!block) {\n throw new Error(`Block ${blockId} not found`);\n }\n return [...block.successors]; // Return a copy to prevent mutation\n }\n\n nextBlock(blockId: BlockId): BlockId | null {\n console.assert(this.basicBlocks.has(blockId), `Block ${blockId} should exist`);\n return this.traversalSuccessors.get(blockId) || null;\n }\n\n *instrIndexes(blockId: BlockId): Iterable<CodeOffset> {\n const start = this.blockStart(blockId);\n const end = this.blockEnd(blockId);\n for (let i = start; i <= end; i++) {\n yield i;\n }\n }\n\n blocks(): BlockId[] {\n return Array.from(this.basicBlocks.keys());\n }\n\n numBlocks(): number {\n return this.basicBlocks.size;\n }\n\n entryBlockId(): BlockId {\n return ENTRY_BLOCK_ID;\n }\n\n isLoopHead(blockId: BlockId): boolean {\n return this.loopHeads.has(blockId);\n }\n\n isBackEdge(cur: BlockId, next: BlockId): boolean {\n const backEdges = this.loopHeads.get(next);\n return backEdges ? backEdges.has(cur) : false;\n }\n\n numBackEdges(): number {\n let count = 0;\n for (const edges of this.loopHeads.values()) {\n count += edges.size;\n }\n return count;\n }\n}\n","/**\n * InstructionDisassembler - Handles disassembly of individual bytecode instructions\n */\nimport { Bytecode, FieldDefinition, FieldHandle, StructVariantHandle } from \"../types/MoveModule\";\nimport { DisassemblerContext } from \"../core/DisassemblerContext\";\nimport { SignatureToken } from \"../types/MoveModule\";\n\nexport class InstructionDisassembler {\n constructor(\n private readonly context: DisassemblerContext,\n private readonly params: string[],\n private readonly locals: string[]\n ) {}\n\n disassemble(instruction: Bytecode): string {\n switch (instruction.kind) {\n // Stack operations\n case \"Pop\":\n return \"Pop\";\n case \"Ret\":\n return \"Ret\";\n case \"Nop\":\n return \"Nop\";\n\n // Branch operations\n case \"BrTrue\":\n return `BrTrue(${instruction.codeOffset})`;\n case \"BrFalse\":\n return `BrFalse(${instruction.codeOffset})`;\n case \"Branch\":\n return `Branch(${instruction.codeOffset})`;\n\n // Load operations\n case \"LdU8\":\n return `LdU8(${instruction.value})`;\n case \"LdU16\":\n return `LdU16(${instruction.value})`;\n case \"LdU32\":\n return `LdU32(${instruction.value})`;\n case \"LdU64\":\n return `LdU64(${instruction.value})`;\n case \"LdU128\":\n return `LdU128(${instruction.value})`;\n case \"LdU256\":\n return `LdU256(${instruction.value})`;\n case \"LdTrue\":\n return \"LdTrue\";\n case \"LdFalse\":\n return \"LdFalse\";\n case \"LdConst\":\n return this.formatLdConst(instruction.constIdx);\n\n // Cast operations\n case \"CastU8\":\n return \"CastU8\";\n case \"CastU16\":\n return \"CastU16\";\n case \"CastU32\":\n return \"CastU32\";\n case \"CastU64\":\n return \"CastU64\";\n case \"CastU128\":\n return \"CastU128\";\n case \"CastU256\":\n return \"CastU256\";\n\n // Local variable operations\n case \"CopyLoc\":\n return this.formatLocalInstruction(instruction.localIdx, \"CopyLoc\");\n case \"MoveLoc\":\n return this.formatLocalInstruction(instruction.localIdx, \"MoveLoc\");\n case \"StLoc\":\n return this.formatLocalInstruction(instruction.localIdx, \"StLoc\");\n case \"MutBorrowLoc\":\n return this.formatLocalInstruction(instruction.localIdx, \"MutBorrowLoc\");\n case \"ImmBorrowLoc\":\n return this.formatLocalInstruction(instruction.localIdx, \"ImmBorrowLoc\");\n\n // Function call operations\n case \"Call\":\n return this.formatCall(instruction.funcHandleIdx);\n case \"CallGeneric\":\n return this.formatCallGeneric(instruction.funcInstIdx);\n case \"CallClosure\":\n return this.formatCallClosure(instruction.sigIdx);\n\n // Closure operations\n case \"PackClosure\":\n return this.formatPackClosure(instruction.fun, instruction.mask);\n case \"PackClosureGeneric\":\n return this.formatPackClosureGeneric(instruction.fun, instruction.mask);\n\n // Struct operations\n case \"Pack\":\n return this.formatStructOperation(instruction.structDefIdx, \"Pack\");\n case \"Unpack\":\n return this.formatStructOperation(instruction.structDefIdx, \"Unpack\");\n case \"PackGeneric\":\n return this.formatGenericStructOperation(instruction.structInstIdx, \"PackGeneric\");\n case \"UnpackGeneric\":\n return this.formatGenericStructOperation(instruction.structInstIdx, \"UnpackGeneric\");\n\n // Variant operations\n case \"PackVariant\":\n return this.formatVariantOperation(instruction.structVariantHandleIdx, \"PackVariant\");\n case \"UnpackVariant\":\n return this.formatVariantOperation(instruction.structVariantHandleIdx, \"UnpackVariant\");\n case \"TestVariant\":\n return this.formatVariantOperation(instruction.structVariantHandleIdx, \"TestVariant\");\n case \"PackVariantGeneric\":\n return this.formatGenericVariantOperation(\n instruction.structVariantInstIdx,\n \"PackVariantGeneric\"\n );\n case \"UnpackVariantGeneric\":\n return this.formatGenericVariantOperation(\n instruction.structVariantInstIdx,\n \"UnpackVariantGeneric\"\n );\n case \"TestVariantGeneric\":\n return this.formatGenericVariantOperation(\n instruction.structVariantInstIdx,\n \"TestVariantGeneric\"\n );\n\n // Field operations\n case \"MutBorrowField\":\n return this.formatFieldOperation(instruction.fieldHandleIdx, \"MutBorrowField\");\n case \"ImmBorrowField\":\n return this.formatFieldOperation(instruction.fieldHandleIdx, \"ImmBorrowField\");\n case \"MutBorrowFieldGeneric\":\n return this.formatGenericFieldOperation(instruction.fieldInstIdx, \"MutBorrowFieldGeneric\");\n case \"ImmBorrowFieldGeneric\":\n return this.formatGenericFieldOperation(instruction.fieldInstIdx, \"ImmBorrowFieldGeneric\");\n\n // Variant field operations\n case \"MutBorrowVariantField\":\n return this.formatVariantFieldOperation(\n instruction.variantFieldHandleIdx,\n \"MutBorrowVariantField\"\n );\n case \"ImmBorrowVariantField\":\n return this.formatVariantFieldOperation(\n instruction.variantFieldHandleIdx,\n \"ImmBorrowVariantField\"\n );\n case \"MutBorrowVariantFieldGeneric\":\n return this.formatGenericVariantFieldOperation(\n instruction.variantFieldInstIdx,\n \"MutBorrowVariantFieldGeneric\"\n );\n case \"ImmBorrowVariantFieldGeneric\":\n return this.formatGenericVariantFieldOperation(\n instruction.variantFieldInstIdx,\n \"ImmBorrowVariantFieldGeneric\"\n );\n\n // Global operations\n case \"MutBorrowGlobal\":\n return this.formatStructOperation(instruction.structDefIdx, \"MutBorrowGlobal\");\n case \"ImmBorrowGlobal\":\n return this.formatStructOperation(instruction.structDefIdx, \"ImmBorrowGlobal\");\n case \"MutBorrowGlobalGeneric\":\n return this.formatGenericStructOperation(\n instruction.structInstIdx,\n \"MutBorrowGlobalGeneric\"\n );\n case \"ImmBorrowGlobalGeneric\":\n return this.formatGenericStructOperation(\n instruction.structInstIdx,\n \"ImmBorrowGlobalGeneric\"\n );\n case \"Exists\":\n return this.formatStructOperation(instruction.structDefIdx, \"Exists\");\n case \"ExistsGeneric\":\n return this.formatGenericStructOperation(instruction.structInstIdx, \"ExistsGeneric\");\n case \"MoveFrom\":\n return this.formatStructOperation(instruction.structDefIdx, \"MoveFrom\");\n case \"MoveFromGeneric\":\n return this.formatGenericStructOperation(instruction.structInstIdx, \"MoveFromGeneric\");\n case \"MoveTo\":\n return this.formatStructOperation(instruction.structDefIdx, \"MoveTo\");\n case \"MoveToGeneric\":\n return this.formatGenericStructOperation(instruction.structInstIdx, \"MoveToGeneric\");\n\n // Reference operations\n case \"ReadRef\":\n return \"ReadRef\";\n case \"WriteRef\":\n return \"WriteRef\";\n case \"FreezeRef\":\n return \"FreezeRef\";\n\n // Arithmetic operations\n case \"Add\":\n return \"Add\";\n case \"Sub\":\n return \"Sub\";\n case \"Mul\":\n return \"Mul\";\n case \"Mod\":\n return \"Mod\";\n case \"Div\":\n return \"Div\";\n case \"Shl\":\n return \"Shl\";\n case \"Shr\":\n return \"Shr\";\n\n // Bitwise operations\n case \"BitOr\":\n return \"BitOr\";\n case \"BitAnd\":\n return \"BitAnd\";\n case \"Xor\":\n return \"Xor\";\n\n // Logical operations\n case \"Or\":\n return \"Or\";\n case \"And\":\n return \"And\";\n case \"Not\":\n return \"Not\";\n\n // Comparison operations\n case \"Eq\":\n return \"Eq\";\n case \"Neq\":\n return \"Neq\";\n case \"Lt\":\n return \"Lt\";\n case \"Gt\":\n return \"Gt\";\n case \"Le\":\n return \"Le\";\n case \"Ge\":\n return \"Ge\";\n\n // Other operations\n case \"Abort\":\n return \"Abort\";\n\n // Vector operations\n case \"VecPack\":\n return this.formatVectorOperation(\n instruction.elemTyIdx,\n instruction.numElements,\n \"VecPack\"\n );\n case \"VecLen\":\n return this.formatVectorOperation(instruction.elemTyIdx, undefined, \"VecLen\");\n case \"VecImmBorrow\":\n return this.formatVectorOperation(instruction.elemTyIdx, undefined, \"VecImmBorrow\");\n case \"VecMutBorrow\":\n return this.formatVectorOperation(instruction.elemTyIdx, undefined, \"VecMutBorrow\");\n case \"VecPushBack\":\n return this.formatVectorOperation(instruction.elemTyIdx, undefined, \"VecPushBack\");\n case \"VecPopBack\":\n return this.formatVectorOperation(instruction.elemTyIdx, undefined, \"VecPopBack\");\n case \"VecUnpack\":\n return this.formatVectorOperation(\n instruction.elemTyIdx,\n instruction.numElements,\n \"VecUnpack\"\n );\n case \"VecSwap\":\n return this.formatVectorOperation(instruction.elemTyIdx, undefined, \"VecSwap\");\n\n default:\n throw new Error(`Unsupported instruction: ${instruction}`);\n }\n }\n\n private formatLocalInstruction(localIdx: number, instructionName: string): string {\n const localVar = this.getLocalVariable(localIdx);\n return `${instructionName}[${localIdx}](${localVar})`;\n }\n\n private getLocalVariable(localIdx: number): string {\n if (localIdx < this.params.length) {\n return `arg${localIdx}: ${this.params[localIdx]}`;\n } else if (localIdx < this.params.length + this.locals.length) {\n const localIndex = localIdx - this.params.length;\n return `loc${localIndex}: ${this.locals[localIndex]}`;\n } else {\n throw new Error(`Invalid local index: ${localIdx}`);\n }\n }\n\n private formatCall(funcHandleIdx: number): string {\n const functionHandle = this.context.getFunctionHandle(funcHandleIdx);\n const functionName = this.context.getIdentifier(functionHandle.name);\n const moduleHandle = this.context.getModuleHandle(functionHandle.module);\n\n let functionString = \"\";\n if (this.context.selfModuleHandleIdx === functionHandle.module) {\n functionString = functionName;\n } else {\n const moduleName = this.context.getIdentifier(moduleHandle.name);\n functionString = `${moduleName}::${functionName}`;\n }\n\n // Get function signature\n const paramSignature = this.context.getSignature(functionHandle.parameters);\n const returnSignature = this.context.getSignature(functionHandle.return_);\n\n // Format parameters\n const paramTypes = paramSignature.map((token) => this.context.parseSignatureToken(token));\n const paramStr = `(${paramTypes.join(\", \")})`;\n\n // Format return type\n let returnStr = \"\";\n if (returnSignature.length > 0) {\n const returnTypes = returnSignature.map((token) => this.context.parseSignatureToken(token));\n if (returnTypes.length === 1) {\n returnStr = `: ${returnTypes[0]}`;\n } else {\n returnStr = `: (${returnTypes.join(\", \")})`;\n }\n }\n\n return `Call ${functionString}${paramStr}${returnStr}`;\n }\n\n private formatCallGeneric(funcInstIdx: number): string {\n const functionInst = this.context.getFunctionInstantiation(funcInstIdx);\n const functionHandle = this.context.getFunctionHandle(functionInst.handle);\n const functionName = this.context.getIdentifier(functionHandle.name);\n const moduleHandle = this.context.getModuleHandle(functionHandle.module);\n\n let functionString = \"\";\n if (this.context.selfModuleHandleIdx === functionHandle.module) {\n functionString = functionName;\n } else {\n const moduleName = this.context.getIdentifier(moduleHandle.name);\n functionString = `${moduleName}::${functionName}`;\n }\n\n // Get generic type parameters\n const typeParamSignature = this.context.getSignature(functionInst.type_parameters);\n const typeParams = typeParamSignature.map((token) => this.context.parseSignatureToken(token));\n const typeParamsStr = typeParams.length > 0 ? `<${typeParams.join(\", \")}>` : \"\";\n\n // Get function signature\n const paramSignature = this.context.getSignature(functionHandle.parameters);\n const returnSignature = this.context.getSignature(functionHandle.return_);\n\n // Format parameters\n const paramTypes = paramSignature.map((token) => this.context.parseSignatureToken(token));\n const paramStr = `(${paramTypes.join(\", \")})`;\n\n // Format return type\n let returnStr = \"\";\n if (returnSignature.length > 0) {\n const returnTypes = returnSignature.map((token) => this.context.parseSignatureToken(token));\n if (returnTypes.length === 1) {\n returnStr = `: ${returnTypes[0]}`;\n } else {\n returnStr = `: (${returnTypes.join(\", \")})`;\n }\n }\n\n return `Call ${functionString}${typeParamsStr}${paramStr}${returnStr}`;\n }\n\n private formatCallClosure(sigIdx: number): string {\n const closureSignature = this.context.getSignature(sigIdx);\n const closureType = closureSignature.map((tp) => this.context.parseSignatureToken(tp));\n if (closureType.length !== 1) {\n throw new Error(\"CallClosure with type parameters is not supported yet\");\n }\n return `CallClosure[${sigIdx}](${closureType[0]})`;\n }\n\n private formatPackClosure(fun: number, mask: number): string {\n const functionHandle = this.context.getFunctionHandle(fun);\n const functionName = this.context.getIdentifier(functionHandle.name);\n const moduleHandle = this.context.getModuleHandle(functionHandle.module);\n\n let functionString = \"\";\n if (this.context.selfModuleHandleIdx === functionHandle.module) {\n functionString = functionName;\n } else {\n const moduleName = this.context.getIdentifier(moduleHandle.name);\n functionString = `${moduleName}::${functionName}`;\n }\n\n const typeArguments = functionHandle.type_parameters.map((tp) =>\n this.context.parseAbilities(tp)\n );\n const typeParamsStr = typeArguments.length > 0 ? `<${typeArguments.join(\", \")}>` : \"\";\n\n return `PackClosure#${mask}[${fun}](${functionString}${typeParamsStr})`;\n }\n\n private formatPackClosureGeneric(fun: number, mask: number): string {\n return `PackClosureGeneric#${mask}[${fun}]`;\n }\n\n private formatStructOperation(structDefIdx: number, instructionName: string): string {\n const structDef = this.context.getStructDefinition(structDefIdx);\n const structHandle = this.context.getStructHandle(structDef.struct_handle);\n const structName = this.context.getIdentifier(structHandle.name);\n return `${instructionName}[${structDefIdx}](${structName})`;\n }\n\n private formatGenericStructOperation(structInstIdx: number, instructionName: string): string {\n const structInst = this.context.getStructDefInstantiation(structInstIdx);\n const structDef = this.context.getStructDefinition(structInst.def);\n const structHandle = this.context.getStructHandle(structDef.struct_handle);\n const structName = this.context.getIdentifier(structHandle.name);\n const typeParams = this.context\n .getSignature(structInst.typeParameters)\n .map((tp) => this.context.parseSignatureToken(tp));\n const typeParamsStr = typeParams.length > 0 ? `<${typeParams.join(\", \")}>` : \"\";\n return `${instructionName}[${structInstIdx}](${structName}${typeParamsStr})`;\n }\n\n private formatVariantOperation(structVariantHandleIdx: number, instructionName: string): string {\n const structVariantHandle = this.context.getStructVariantHandle(structVariantHandleIdx);\n const { structName, variantName } = this.getVariantInfo(structVariantHandle);\n return `${instructionName}[${structVariantHandleIdx}](${structName}/${variantName})`;\n }\n\n private formatGenericVariantOperation(\n structVariantInstIdx: number,\n instructionName: string\n ): string {\n const structVariantInst = this.context.getStructVariantInstantiation(structVariantInstIdx);\n const typeParams = this.context.getSignature(structVariantInst.type_parameters);\n const structVariantHandle = this.context.getStructVariantHandle(structVariantInst.handle);\n const { structName, variantName } = this.getVariantInfo(structVariantHandle);\n\n const structTypeParams = typeParams.map((tp) => this.context.parseSignatureToken(tp));\n const typeParamsStr = structTypeParams.length > 0 ? `<${structTypeParams.join(\", \")}>` : \"\";\n return `${instructionName}[${structVariantInstIdx}](${structName}/${variantName}${typeParamsStr})`;\n }\n\n private formatFieldOperation(fieldHandleIdx: number, instructionName: string): string {\n const fieldHandle = this.context.getFieldHandle(fieldHandleIdx);\n const { structName, fieldName, fieldType } = this.getFieldInfo(fieldHandle);\n return `${instructionName}[${fieldHandleIdx}](${structName}.${fieldName}: ${fieldType})`;\n }\n\n private formatGenericFieldOperation(fieldInstIdx: number, instructionName: string): string {\n const fieldInst = this.context.getFieldInstantiation(fieldInstIdx);\n const fieldHandle = this.context.getFieldHandle(fieldInst.handle);\n const { structName, fieldName, fieldType } = this.getFieldInfo(fieldHandle);\n return `${instructionName}[${fieldInstIdx}](${structName}.${fieldName}: ${fieldType})`;\n }\n\n private formatVariantFieldOperation(\n variantFieldHandleIdx: number,\n instructionName: string\n ): string {\n // const variantFieldHandle = this.context.getVariantFieldHandle(variantFieldHandleIdx);\n // Implementation for variant field operations would be needed here\n return `${instructionName}[${variantFieldHandleIdx}]`;\n }\n\n private formatGenericVariantFieldOperation(\n variantFieldInstIdx: number,\n instructionName: string\n ): string {\n // const variantFieldInst = this.context.getVariantFieldInstantiation(variantFieldInstIdx);\n // Implementation for generic variant field operations would be needed here\n return `${instructionName}[${variantFieldInstIdx}]`;\n }\n\n private formatVectorOperation(\n elemTyIdx: number,\n numElements: bigint | undefined,\n instructionName: string\n ): string {\n const elementsStr = numElements !== undefined ? `, ${numElements}` : \"\";\n return `${instructionName}(${elemTyIdx}${elementsStr})`;\n }\n\n private formatLdConst(constIdx: number): string {\n const rawModule = this.context.getRawModule();\n const constant = rawModule.constant_pool[constIdx];\n if (!constant) {\n throw new Error(`Constant at index ${constIdx} is out of bounds`);\n }\n\n // Format the constant value based on its type\n const typeStr = this.context.parseSignatureToken(constant.type);\n const dataStr = this.formatConstantData(constant.data, constant.type);\n return `LdConst[${constIdx}](${typeStr}: ${dataStr})`;\n }\n\n private formatConstantData(data: Uint8Array, type: SignatureToken): string {\n // For address type, format as hex string with commas\n if (type.kind === \"Address\") {\n return Array.from(data).join(\",\");\n }\n\n // For other types, we can add more specific formatting later\n return Array.from(data).join(\",\");\n }\n\n private getFieldInfo(fieldHandle: FieldHandle): {\n structName: string;\n fieldName: string;\n fieldType: string;\n } {\n const structDef = this.context.getStructDefinition(fieldHandle.owner);\n const structHandle = this.context.getStructHandle(structDef.struct_handle);\n const structName = this.context.getIdentifier(structHandle.name);\n\n let fields: FieldDefinition[] = [];\n if (structDef.field_information.kind === \"Declared\") {\n fields = structDef.field_information.fields;\n }\n\n const field = fields[fieldHandle.field];\n if (!field) {\n throw new Error(`Field not found: ${fieldHandle.field}`);\n }\n\n const fieldName = this.context.getIdentifier(field.name);\n const fieldType = this.context.parseSignatureToken(field.type);\n return { structName, fieldName, fieldType };\n }\n\n private getVariantInfo(structVariantHandle: StructVariantHandle): {\n structName: string;\n variantName: string;\n } {\n const structDef = this.context.getStructDefinition(structVariantHandle.struct_index);\n const structHandle = this.context.getStructHandle(structDef.struct_handle);\n const structName = this.context.getIdentifier(structHandle.name);\n\n if (structDef.field_information.kind !== \"DeclaredVariants\") {\n throw new Error(\n `PackVariant is not supported for field information kind: ${structDef.field_information.kind}`\n );\n }\n\n const variant = structDef.field_information.variants[structVariantHandle.variant];\n if (!variant) {\n throw new Error(`Variant not found: ${structVariantHandle.variant}`);\n }\n\n const variantName = this.context.getIdentifier(variant.name);\n return { structName, variantName };\n }\n}\n","/**\r\n * ModuleDisassembler - Handles disassembly of entire Move modules\r\n */\r\nimport { DisassemblerContext } from \"../core/DisassemblerContext\";\r\nimport { InstructionDisassembler } from \"./InstructionDisassembler\";\r\nimport { VMControlFlowGraph } from \"../core/ControlFlowGraph\";\r\nimport { DisassemblerOptions } from \"../types/DisassemblerOptions\";\r\n\r\nexport class ModuleDisassembler {\r\n private readonly options: DisassemblerOptions;\r\n\r\n constructor(private readonly context: DisassemblerContext) {\r\n this.options = this.context.options;\r\n }\r\n\r\n disassemble(): string {\r\n const { moduleNames, moduleAliases } = this.buildModuleAliases();\r\n\r\n const header = this.generateHeader();\r\n const imports = this.generateImports(moduleNames, moduleAliases);\r\n const structs = this.generateStructs();\r\n const functions = this.generateFunctions();\r\n\r\n return `// Move bytecode v${this.context.version}\\n${header} {\\n\\n${imports}\\n\\n${structs}\\n\\n${functions}\\n\\n}`;\r\n }\r\n\r\n private buildModuleAliases(): {\r\n moduleNames: Map<string, number>;\r\n moduleAliases: Map<string, string>;\r\n } {\r\n const moduleNames = new Map<string, number>();\r\n const moduleAliases = new Map