import inspect from bytecode import Instr, Bytecode, Label from boa import abi from boa.code.vmtoken import VMTokenizer, Nep8VMTokenizer from boa.code.expression import Expression from boa.code import pyop from boa.code.ast_preprocess import preprocess_method_body from uuid import UUID, uuid3 import pdb import dis class method(object): code = None bytecode = None block = None blocks = [] stack_size = 0 tokens = [] tokenizer = None address = 0 module = None name = None module_name = None dictionary_defs = None start_line_no = None _blocks = None _expressions = None _scope = None _forloop_counter = 0 _extra = None _id = None code_object = None @property def id(self): return self._id @property def forloop_counter(self): self._forloop_counter += 1 return self._forloop_counter @property def vm_tokens(self): """ Returns a list of all vm tokens in this method. :return: a list of vm tokens in this method :rtype: list """ return self.tokenizer.vm_tokens @property def is_interop(self): if 'boa.interop' in self.full_name: return True if 'boa.builtins' in self.full_name and self.full_name != 'boa.builtins.range': return True return False @property def is_abi_decorator(self): if 'boa.abi' in self.full_name: return True return False @property def full_name(self): if len(self.module_name): return '%s.%s' % (self.module_name, self.name) return self.name @property def scope(self): return self._scope @property def args(self): return self.bytecode.argnames @property def stacksize(self): return self.bytecode.argcount + len(self._blocks) + 2 def __init__(self, module, block, module_name, extra): self.module = module self.block = block self.module_name = module_name self._extra = extra method_block_index = self.get_code_block_index(self.block) if method_block_index is None: raise Exception('Block of code of a method from %s module was not found', self.module_name) self.name = self.block[method_block_index + 1].arg self._id = uuid3(UUID('{baa187e0-2c51-4ef6-aa42-b3421c22d5e1}'), self.full_name) self.start_line_no = self.block[method_block_index].lineno self.code_object = self.block[method_block_index].arg # dis.dis(code_object) self.code, self.dictionary_defs = preprocess_method_body(self.code_object) self.bytecode = Bytecode.from_code(self.code) self.evaluate_annotations(method_block_index) self.setup() def setup(self): self._scope = {} for index, name in enumerate(self.bytecode.argnames): self._scope[name] = index blocks = [] # find LOAD_GLOBALS gbl = [] for instr in self.bytecode: if isinstance(instr, Instr) and instr.opcode == pyop.LOAD_GLOBAL: gbl.append(instr.arg) # if there are global things passed in # we want to check if they are used in the method # and if so, load them in global_blocks = [] if len(self._extra): for item in self._extra: if item[-1].opcode == pyop.STORE_NAME: if item[-1].arg in gbl: global_blocks.append(item) self.add_to_scope(item[-1].arg) if item[0].opcode == pyop.LOAD_NAME: item[0].opcode = pyop.LOAD_GLOBAL blocks = global_blocks instructions = [] last_ln = self.bytecode[0].lineno for instr in self.bytecode: if not isinstance(instr, Label) and instr.lineno != last_ln: last_ln = instr.lineno if len(instructions): blocks.append(instructions) instructions = [] if not isinstance(instr, Label) and instr.opcode == pyop.STORE_FAST: self.add_to_scope(instr.arg) instructions.append(instr) if len(instructions): blocks.append(instructions) self._blocks = blocks from ..compiler import Compiler if Compiler.instance().nep8: self.tokenizer = Nep8VMTokenizer(self) else: self.tokenizer = VMTokenizer(self) self._expressions = [] def evaluate_annotations(self, index): block_index = 0 args_types = [] while block_index < index: if self.block[block_index].opcode == pyop.LOAD_NAME and 'abi' in self.block[block_index].arg: block_index = self.include_abi_info(block_index) else: block_index = block_index + 1 def include_abi_info(self, start_index): index = start_index load_method_instr = self.block[index] while load_method_instr.opcode != pyop.LOAD_METHOD and load_method_instr.opcode != pyop.LOAD_NAME: index = index + 1 load_method_instr = self.block[index] args_types = [] if load_method_instr.arg == 'abi_method' or load_method_instr.arg == 'abi_entry_point': index = index + 1 arg_instr = self.block[index] while arg_instr.opcode == pyop.LOAD_NAME or arg_instr.opcode == pyop.LOAD_ATTR: if abi.is_abi_type(arg_instr.arg): args_types.append(arg_instr.arg) index = index + 1 arg_instr = self.block[index] # return type not specified if len(args_types) == len(self.args): args_types.append(abi.Void) if arg_instr.opcode == pyop.CALL_METHOD: index = index + 1 if load_method_instr.arg == 'abi_entry_point': self.module.set_abi_entry_point(self, args_types) else: self.module.include_abi_method(self, args_types) return index def get_code_block_index(self, blocks): for index, block in enumerate(blocks): if inspect.iscode(block.arg): return index def add_to_scope(self, argname): if argname not in self.scope.keys(): current_total = len(self._scope) self._scope[argname] = current_total def prepare(self): last_exp = None for block in self._blocks: exp = Expression(block, self.tokenizer, self) self._expressions.append(exp) if last_exp: last_exp.next = exp last_exp = exp for exp in self._expressions: exp.tokenize() self.convert_breaks() self.convert_jumps() def convert_jumps(self): filtered = [] for vmtoken in self.tokenizer.vm_tokens.values(): if vmtoken.pytoken: filtered.append(vmtoken) for vmtoken in filtered: if vmtoken.pytoken.jump_from and not vmtoken.pytoken.jump_found: for vmtoken2 in filtered: if vmtoken2.pytoken.jump_target == vmtoken.pytoken.jump_from: diff = vmtoken2.addr - vmtoken.addr vmtoken.data = diff.to_bytes(2, 'little', signed=True) vmtoken2.pytoken.jump_from_addr = vmtoken.addr vmtoken.pytoken.jump_to_addr = vmtoken2.addr def convert_breaks(self): tokens = list(self.tokenizer.vm_tokens.values()) setup_token_label = None for tkn in tokens: if tkn.pytoken: if tkn.pytoken.pyop == pyop.SETUP_LOOP: setup_token_label = tkn.pytoken.jump_from if tkn.pytoken.pyop == pyop.BREAK_LOOP: if not setup_token_label: raise Exception("No loopsetup for break") tkn.pytoken.jump_from = setup_token_label