def backtrack_instruction_sources(pc, instr, operands, ins): """ Backtrack instruction operand sources and add edges to DFG """ global dfg op = instr.op # Label operand sources of SWAP and DUP if opcodes.is_swap(op) or opcodes.is_dup(op): for i in range(ins): operands[i].instruction.reserved = True else: # Add IDG edges, representing data flow dependencies between instructions for i in range(ins): dfg.graph.add_edge(operands[i].pc, pc)
def update_instruction_source(instruction, operands, ins): """ Update the source of the instruction's operands """ op = instruction.op if opcodes.is_swap(op) or opcodes.is_dup(op): return source = instruction.source if source is None: source = [] for i in range(ins): source.append({operands[i]}) else: for i in range(ins): source[i].add(operands[i]) instruction.source = source
def analyze_destination(contract): """ Analyze the destination of data flows originated from instructions """ for block in contract.blocks: for index, instruction in enumerate(block.instructions): op = instruction.op if opcodes.is_swap(op) or opcodes.is_dup(op): continue source = instruction.source if source is not None: for pos, pointers in enumerate(source): for pointer in pointers: source_instruction = get_instruction(pointer) destination = source_instruction.destination destination.append( InstructionOperandPointer( InstructionPointer(block, index), pos)) source_instruction.destination = destination
def advance(state): """ Execute next block abstractly and advance the current state """ contract = state.contract block = state.next_block pc = state.next_block.offset stack = state.stack.copy() update_block_source(block, stack, len(stack)) for index, instruction in enumerate(state.next_block.instructions): op = instruction.op name = instruction.name old_stack = stack.copy() ins = opcodes.opcodes[op][1] operands = [] for i in range(ins): operand = stack.pop(0) operands.append(operand) update_instruction_source(instruction, operands, ins) if name == 'STOP' or name == 'RETURN' or name == 'REVERT' or name == 'SUICIDE': return [] elif opcodes.is_push(op): stack.insert(0, InstructionPointer(state.next_block, index)) elif opcodes.is_dup(op): old_stack.insert(0, operands[-1]) stack = old_stack elif opcodes.is_swap(op): tmp = old_stack[0] old_stack[0] = old_stack[ins - 1] old_stack[ins - 1] = tmp stack = old_stack elif name == 'JUMP': source_instruction = get_instruction(operands[0]) if not opcodes.is_push(source_instruction.op): raise TypeError( 'Error resolving JUMP address. pc: {:#x}, source: {:#x}'. format(pc, get_instruction_offset(operands[0]))) if source_instruction.arg not in contract.jump_destination.keys(): raise KeyError( 'Invalid JUMP address. pc: {:#x}, source: {:#x}, addr: {:#x}' .format(pc, get_instruction_offset(operands[0]), source_instruction.arg)) return [ State(contract, contract.jump_destination[source_instruction.arg], stack) ] elif name == 'JUMPI': source_instruction = get_instruction(operands[0]) if not opcodes.is_push(source_instruction.op): raise ValueError( 'Error resolving JUMP address. pc: {:#x}, source: {:#x}'. format(pc, get_instruction_offset(operands[0]))) if source_instruction.arg not in contract.jump_destination.keys(): raise KeyError( 'Invalid JUMP address. pc: {:#x}, source: {:#x}, addr: {:#x}' .format(pc, get_instruction_offset(operands[0]), source_instruction.arg)) ret = [ State(contract, contract.jump_destination[source_instruction.arg], stack) ] if state.next_block.next is not None: ret.append(State(contract, state.next_block.next, stack)) return ret else: outs = opcodes.opcodes[op][2] for i in range(outs): stack.insert(0, InstructionPointer(state.next_block, index)) if len(stack) > 1024: log.critical('Stack overflow. pc: {:#x}'.format(pc)) return [] size = opcodes.operand_size(op) pc += size + 1 if state.next_block.next is not None: return [State(contract, state.next_block.next, stack)] else: return []
def initialize(evm): """ Initialize contract analysis, disassemble EVM bytecode, construct basic blocks, create DFG and CFG """ contr = Contract() dfg = DiGraph() # Data Flow Graph cfg = DiGraph() # Control Flow Graph cur_blk = BasicBlock(0) pc = 0 while pc < len(evm): op = evm[pc] if op not in opcodes.listing: raise KeyError('Invalid op. op: {:#x}, offset: {:#x}'.format(op, pc)) name = opcodes.listing[op][0] size = opcodes.operand_size(op) if size != 0: arg = int.from_bytes(evm[pc + 1:pc + 1 + size], byteorder='big') else: arg = None instr = Instruction(op, name, arg) if name == 'JUMPDEST': if len(cur_blk.instructions) > 0: contr.blocks.append(cur_blk) # Add CFG nodes, representing basic blocks cfg.graph.add_node(cur_blk.offset, blk=cur_blk) new_blk = BasicBlock(pc) cur_blk.next = new_blk cur_blk = new_blk cur_blk.offset += 1 contr.jump_destination[pc] = cur_blk contr.instructions[pc] = instr else: cur_blk.instructions.append(instr) contr.instructions[pc] = instr if opcodes.is_swap(op) or opcodes.is_dup(op): # Omit SWAP and DUP from IDG pass elif (name == 'JUMP' or name == 'JUMPI' or name == 'STOP' or name == 'RETURN' or name == 'REVERT' or name == 'INVALID' or name == 'SUICIDE'): contr.blocks.append(cur_blk) # Add CFG nodes, representing basic blocks cfg.graph.add_node(cur_blk.offset, blk=cur_blk) new_blk = BasicBlock(pc + 1) cur_blk.next = new_blk cur_blk = new_blk # Add DFG nodes, representing instructions dfg.graph.add_node(pc, instr=instr) else: # Add DFG nodes, representing instructions dfg.graph.add_node(pc, instr=instr) pc += size + 1 if len(cur_blk.instructions) > 0 or cur_blk.offset - 1 in contr.jump_destination: contr.blocks.append(cur_blk) # Add CFG nodes, representing basic blocks cfg.graph.add_node(cur_blk.offset, blk=cur_blk) else: contr.blocks[-1].next = None return contr, dfg, cfg
def advance(elm): """ Execute next block abstractly and advance current state """ global contr global trace global relocate stat = elm.state blk = stat.next_block pc = blk.offset stack = stat.stack.copy() memory = stat.memory.copy() storage = stat.storage.copy() sequence = stat.sequence.copy() # Mark current state as executed elm.executed = True for instr in blk.instructions: op = instr.op name = instr.name old_stack = stack.copy() ins = opcodes.listing[op][1] if ins > len(stack): raise RuntimeError('Stack underflow. pc: {:#x}'.format(pc)) operands = [] for i in range(ins): operand = stack.pop(0) operands.append(operand) backtrack_instruction_sources(pc, instr, operands, ins) # Trace executed instructions from CALL to SSTORE, omit JUMPDEST, SWAP and DUP if name != 'JUMPDEST' and not opcodes.is_swap(op) and not opcodes.is_dup(op): for off in sequence: sequence[off].add(pc) if name == 'STOP' or name == 'SUICIDE': # Current block is NOT revert block return False elif name == 'INVALID': # Current block is revert block return True elif name == 'ADD': op0 = operands[0].value op1 = operands[1].value if op0 is not None and op1 is not None: stack.insert(0, OperandStackElement(pc, instr, (op0 + op1) & TT256M1)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'SUB': op0 = operands[0].value op1 = operands[1].value if op0 is not None and op1 is not None: stack.insert(0, OperandStackElement(pc, instr, (op0 - op1) & TT256M1)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'MUL': op0 = operands[0].value op1 = operands[1].value if op0 is not None and op1 is not None: stack.insert(0, OperandStackElement(pc, instr, (op0 * op1) & TT256M1)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'DIV': op0 = operands[0].value op1 = operands[1].value if op0 is not None and op1 is not None: stack.insert(0, OperandStackElement(pc, instr, 0 if op1 == 0 else op0 // op1)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'MOD': op0 = operands[0].value op1 = operands[1].value if op0 is not None and op1 is not None: stack.insert(0, OperandStackElement(pc, instr, 0 if op1 == 0 else op0 % op1)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'SDIV': op0 = convert_to_signed(operands[0].value) op1 = convert_to_signed(operands[1].value) if op0 is not None and op1 is not None: stack.insert(0, OperandStackElement(pc, instr, 0 if op1 == 0 else (abs(op0) // abs(op1) * (-1 if op0 * op1 < 0 else 1)) & TT256M1)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'SMOD': op0 = convert_to_signed(operands[0].value) op1 = convert_to_signed(operands[1].value) if op0 is not None and op1 is not None: stack.insert(0, OperandStackElement(pc, instr, 0 if op1 == 0 else (abs(op0) % abs(op1) * (-1 if op0 < 0 else 1)) & TT256M1)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'ADDMOD': op0 = operands[0].value op1 = operands[1].value op2 = operands[2].value if op0 is not None and op1 is not None and op2 is not None: stack.insert(0, OperandStackElement(pc, instr, (op0 + op1) % op2 if op2 else 0)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'MULMOD': op0 = operands[0].value op1 = operands[1].value op2 = operands[2].value if op0 is not None and op1 is not None and op2 is not None: stack.insert(0, OperandStackElement(pc, instr, (op0 * op1) % op2 if op2 else 0)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'EXP': base = operands[0].value exponent = operands[1].value if base is not None and exponent is not None: stack.insert(0, OperandStackElement(pc, instr, pow(base, exponent, TT256))) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'SIGNEXTEND': op0 = operands[0].value op1 = operands[1].value if op0 is not None and op1 is not None: if op0 <= 31: test_bit = op0 * 8 + 7 if op1 & (1 << test_bit): stack.insert(0, OperandStackElement(pc, instr, op1 | (TT256 - (1 << test_bit)))) else: stack.insert(0, OperandStackElement(pc, instr, op1 & ((1 << test_bit) - 1))) else: stack.insert(0, OperandStackElement(pc, instr, op1)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'LT': op0 = operands[0].value op1 = operands[1].value if op0 is not None and op1 is not None: stack.insert(0, OperandStackElement(pc, instr, 1 if op0 < op1 else 0)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'GT': op0 = operands[0].value op1 = operands[1].value if op0 is not None and op1 is not None: stack.insert(0, OperandStackElement(pc, instr, 1 if op0 > op1 else 0)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'SLT': op0 = convert_to_signed(operands[0].value) op1 = convert_to_signed(operands[1].value) if op0 is not None and op1 is not None: stack.insert(0, OperandStackElement(pc, instr, 1 if op0 < op1 else 0)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'SGT': op0 = convert_to_signed(operands[0].value) op1 = convert_to_signed(operands[1].value) if op0 is not None and op1 is not None: stack.insert(0, OperandStackElement(pc, instr, 1 if op0 > op1 else 0)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'EQ': op0 = operands[0].value op1 = operands[1].value if op0 is not None and op1 is not None: stack.insert(0, OperandStackElement(pc, instr, 1 if op0 == op1 else 0)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'ISZERO': op0 = operands[0].value if op0 is not None: stack.insert(0, OperandStackElement(pc, instr, 0 if op0 else 1)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'AND': op0 = operands[0].value op1 = operands[1].value if op0 is not None and op1 is not None: if op0 == 0xffffffff: stack.insert(0, OperandStackElement(pc, instr, op0 & op1, operands[1].source)) elif op1 == 0xffffffff: stack.insert(0, OperandStackElement(pc, instr, op0 & op1, operands[0].source)) else: stack.insert(0, OperandStackElement(pc, instr, op0 & op1)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'OR': op0 = operands[0].value op1 = operands[1].value if op0 is not None and op1 is not None: stack.insert(0, OperandStackElement(pc, instr, op0 | op1)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'XOR': op0 = operands[0].value op1 = operands[1].value if op0 is not None and op1 is not None: stack.insert(0, OperandStackElement(pc, instr, op0 ^ op1)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'NOT': op0 = operands[0].value if op0 is not None: stack.insert(0, OperandStackElement(pc, instr, TT256M1 - op0)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'BYTE': op0 = operands[0].value op1 = operands[1].value if op0 is not None and op1 is not None: if op0 >= 32: stack.insert(0, OperandStackElement(pc, instr, 0)) else: stack.insert(0, OperandStackElement(pc, instr, (op1 // 256 ** (31 - op0)) % 256)) else: stack.insert(0, OperandStackElement(pc, instr)) elif name == 'RETURN': address = operands[0].value size = operands[1].value # Resolve read address and read size, record memory dependencies if address is not None and size is not None: for i in range(size): if address + i in memory: instr.dependence.update(memory[address + i]) if 'all' in memory: instr.dependence.update(memory['all']) # Can not resolve read address or read size, record all possible memory dependencies else: for addr in memory: instr.dependence.update(memory[addr]) # Current block is NOT revert block return False elif name == 'REVERT': address = operands[0].value size = operands[1].value # Resolve read address and read size, record memory dependencies if address is not None and size is not None: for i in range(size): if address + i in memory: instr.dependence.update(memory[address + i]) if 'all' in memory: instr.dependence.update(memory['all']) # Can not resolve read address or read size, record all possible memory dependencies else: for addr in memory: instr.dependence.update(memory[addr]) # Current block is revert block return True elif opcodes.is_push(op): stack.insert(0, OperandStackElement(pc, instr, instr.arg, pc)) elif opcodes.is_dup(op): old_stack.insert(0, operands[-1]) stack = old_stack elif opcodes.is_swap(op): tmp = old_stack[0] old_stack[0] = old_stack[ins - 1] old_stack[ins - 1] = tmp stack = old_stack elif name == 'JUMP': src_pc = operands[0].source dst_pc = operands[0].value if src_pc is None or dst_pc is None: raise ValueError('Error resolving JUMP address. pc: {:#x}, source: {:#x}' .format(pc, operands[0].pc)) if dst_pc not in contr.jump_destination: # Current block is revert block return True else: dst_blk = contr.jump_destination[dst_pc] # Construct relocation table relocate.sources[src_pc] = None relocate.destinations[dst_pc] = None # Add CFG edges, representing control flow dependencies between basic blocks cfg.graph.add_edge(blk.offset, dst_blk.offset, type='JUMP') # Update number of return values to wait elm.num += 1 return [State(dst_blk, stack, memory, storage, sequence)] elif name == 'JUMPI': src_pc = operands[0].source dst_pc = operands[0].value if src_pc is None or dst_pc is None: raise ValueError('Error resolving JUMPI address. pc: {:#x}, source: {:#x}' .format(pc, operands[0].pc)) ret = [] if dst_pc in contr.jump_destination: dst_blk = contr.jump_destination[dst_pc] # Construct relocation table relocate.sources[src_pc] = None relocate.destinations[dst_pc] = None # Add CFG edges, representing control flow dependencies between basic blocks cfg.graph.add_edge(blk.offset, dst_blk.offset, type='JUMPI') # Update number of return values to wait elm.num += 1 ret.append(State(dst_blk, stack, memory, storage, sequence)) if blk.next is not None: # Add CFG edges, representing control flow dependencies between basic blocks cfg.graph.add_edge(blk.offset, blk.next.offset, type='JUMPI') # Update number of return values to wait elm.num += 1 ret.append(State(blk.next, stack, memory, storage, sequence)) if not ret: # Current block is revert block return True else: return ret elif name == 'SHA3': address = operands[0].value size = operands[1].value # Resolve read address and read size, record memory dependencies if address is not None and size is not None: for i in range(size): if address + i in memory: instr.dependence.update(memory[address + i]) if 'all' in memory: instr.dependence.update(memory['all']) # Can not resolve read address or read size, record all possible memory dependencies else: for addr in memory: instr.dependence.update(memory[addr]) stack.insert(0, OperandStackElement(pc, instr)) elif name == 'LOG0' or name == 'LOG1' or name == 'LOG2' or name == 'LOG3' or name == 'LOG4': address = operands[0].value size = operands[1].value # Resolve read address and read size, record memory dependencies if address is not None and size is not None: for i in range(size): if address + i in memory: instr.dependence.update(memory[address + i]) if 'all' in memory: instr.dependence.update(memory['all']) # Can not resolve read address or read size, record all possible memory dependencies else: for addr in memory: instr.dependence.update(memory[addr]) elif name == 'MLOAD': address = operands[0].value # Resolve read address, record memory dependencies if address is not None: for i in range(32): if address + i in memory: instr.dependence.update(memory[address + i]) if 'all' in memory: instr.dependence.update(memory['all']) # Can not resolve read address, record all possible memory dependencies else: for addr in memory: instr.dependence.update(memory[addr]) stack.insert(0, OperandStackElement(pc, instr)) elif name == 'CREATE': address = operands[0].value size = operands[1].value # Resolve read address and read size, record memory dependencies if address is not None and size is not None: for i in range(size): if address + i in memory: instr.dependence.update(memory[address + i]) if 'all' in memory: instr.dependence.update(memory['all']) # Can not resolve read address or read size, record all possible memory dependencies else: for addr in memory: instr.dependence.update(memory[addr]) stack.insert(0, OperandStackElement(pc, instr)) elif name == 'MSTORE': address = operands[0].value # Resolve write address, label memory if address is not None: for i in range(32): if address + i not in memory: memory[address + i] = set() instr.overwrite.update(memory[address + i]) memory[address + i].add(pc) # Can not resolve write address, label all possible addresses else: if 'all' not in memory: memory['all'] = set() for addr in memory: instr.overwrite.update(memory[addr]) memory['all'].add(pc) elif name == 'MSTORE8': address = operands[0].value # Resolve write address, label memory if address is not None: if address not in memory: memory[address] = set() instr.overwrite.update(memory[address]) memory[address].add(pc) # Can not resolve write address, label all possible addresses else: if 'all' not in memory: memory['all'] = set() for addr in memory: instr.overwrite.update(memory[addr]) memory['all'].add(pc) elif name == 'CALLDATACOPY' or name == 'RETURNDATACOPY': address = operands[0].value size = operands[2].value # Resolve write address and write size, label memory if address is not None and size is not None: for i in range(size): if address + i not in memory: memory[address + i] = set() instr.overwrite.update(memory[address + i]) memory[address + i].add(pc) # Can not resolve write address or write size, label all possible addresses else: if 'all' not in memory: memory['all'] = set() for addr in memory: instr.overwrite.update(memory[addr]) memory['all'].add(pc) elif name == 'CODECOPY': address = operands[0].value size = operands[2].value src_pc = operands[1].source dst_pc = operands[1].value if src_pc is None or dst_pc is None: raise ValueError('Error resolving CODECOPY address. pc: {:#x}, source: {:#x}' .format(pc, operands[1].pc)) # Construct relocation table relocate.sources[src_pc] = None relocate.destinations[dst_pc] = None # Resolve write address and write size, label memory if address is not None and size is not None: for i in range(size): if address + i not in memory: memory[address + i] = set() instr.overwrite.update(memory[address + i]) memory[address + i].add(pc) # Can not resolve write address or write size, label all possible addresses else: if 'all' not in memory: memory['all'] = set() for addr in memory: instr.overwrite.update(memory[addr]) memory['all'].add(pc) elif name == 'EXTCODECOPY': address = operands[1].value size = operands[3].value # Resolve write address and write size, label memory if address is not None and size is not None: for i in range(size): if address + i not in memory: memory[address + i] = set() instr.overwrite.update(memory[address + i]) memory[address + i].add(pc) # Can not resolve write address or write size, label all possible addresses else: if 'all' not in memory: memory['all'] = set() for addr in memory: instr.overwrite.update(memory[addr]) memory['all'].add(pc) elif name == 'CALL': # Start tracing executed instructions from CALL to SSTORE if pc not in sequence: sequence[pc] = set() read_address = operands[3].value read_size = operands[4].value # Resolve read address and read size, record memory dependencies if read_address is not None and read_size is not None: for i in range(read_size): if read_address + i in memory: instr.dependence.update(memory[read_address + i]) if 'all' in memory: instr.dependence.update(memory['all']) # Can not resolve read address or read size, record all possible memory dependencies else: for addr in memory: instr.dependence.update(memory[addr]) write_address = operands[5].value write_size = operands[6].value # Resolve write address and write size, label memory if write_address is not None and write_size is not None: for i in range(write_size): if write_address + i not in memory: memory[write_address + i] = set() instr.overwrite.update(memory[write_address + i]) memory[write_address + i].add(pc) # Can not resolve write address or write size, label all possible addresses else: if 'all' not in memory: memory['all'] = set() for addr in memory: instr.overwrite.update(memory[addr]) memory['all'].add(pc) stack.insert(0, OperandStackElement(pc, instr)) elif name == 'CALLCODE': read_address = operands[3].value read_size = operands[4].value # Resolve read address and read size, record memory dependencies if read_address is not None and read_size is not None: for i in range(read_size): if read_address + i in memory: instr.dependence.update(memory[read_address + i]) if 'all' in memory: instr.dependence.update(memory['all']) # Can not resolve read address or read size, record all possible memory dependencies else: for addr in memory: instr.dependence.update(memory[addr]) write_address = operands[5].value write_size = operands[6].value # Resolve write address and write size, label memory if write_address is not None and write_size is not None: for i in range(write_size): if write_address + i not in memory: memory[write_address + i] = set() instr.overwrite.update(memory[write_address + i]) memory[write_address + i].add(pc) # Can not resolve write address or write size, label all possible addresses else: if 'all' not in memory: memory['all'] = set() for addr in memory: instr.overwrite.update(memory[addr]) memory['all'].add(pc) stack.insert(0, OperandStackElement(pc, instr)) elif name == 'DELEGATECALL' or name == 'STATICCALL': read_address = operands[2].value read_size = operands[3].value # Resolve read address and read size, record memory dependencies if read_address is not None and read_size is not None: for i in range(read_size): if read_address + i in memory: instr.dependence.update(memory[read_address + i]) if 'all' in memory: instr.dependence.update(memory['all']) # Can not resolve read address or read size, record all possible memory dependencies else: for addr in memory: instr.dependence.update(memory[addr]) write_address = operands[4].value write_size = operands[5].value # Resolve write address and write size, label memory if write_address is not None and write_size is not None: for i in range(write_size): if write_address + i not in memory: memory[write_address + i] = set() instr.overwrite.update(memory[write_address + i]) memory[write_address + i].add(pc) # Can not resolve write address or write size, label all possible addresses else: if 'all' not in memory: memory['all'] = set() for addr in memory: instr.overwrite.update(memory[addr]) memory['all'].add(pc) stack.insert(0, OperandStackElement(pc, instr)) elif name == 'SLOAD': address = operands[0].value # Resolve read address, record storage dependencies if address is not None: if address in storage: instr.dependence.update(storage[address]) if 'all' in storage: instr.dependence.update(storage['all']) # Can not resolve read address, record all possible storage dependencies else: for addr in storage: instr.dependence.update(storage[addr]) stack.insert(0, OperandStackElement(pc, instr)) elif name == 'SSTORE': # End tracing executed instructions from CALL to SSTORE for off in sequence: if (off, pc) not in trace: trace[(off, pc)] = set() trace[(off, pc)].update(sequence[off]) address = operands[0].value # Resolve write address, label storage if address is not None: if address not in storage: storage[address] = set() instr.overwrite.update(storage[address]) storage[address].add(pc) # Can not resolve write address, label all possible addresses else: if 'all' not in storage: storage['all'] = set() for addr in storage: instr.overwrite.update(storage[addr]) storage['all'].add(pc) else: outs = opcodes.listing[op][2] for i in range(outs): stack.insert(0, OperandStackElement(pc, instr)) if len(stack) > MAX_DEPTH: raise RuntimeError('Stack overflow. pc: {:#x}'.format(pc)) size = opcodes.operand_size(op) pc += size + 1 if blk.next is not None: # Add CFG edges, representing control flow dependencies between basic blocks cfg.graph.add_edge(blk.offset, blk.next.offset, type='SEQUENTIAL') # Update number of return values to wait elm.num += 1 return [State(stat.next_block.next, stack, memory, storage, sequence)] else: # Current block is NOT revert block return False