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
[docs]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()
[docs] 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 = []
[docs] 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
[docs] 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
[docs] def get_code_block_index(self, blocks):
for index, block in enumerate(blocks):
if inspect.iscode(block.arg):
return index
[docs] def add_to_scope(self, argname):
if argname not in self.scope.keys():
current_total = len(self._scope)
self._scope[argname] = current_total
[docs] 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()
[docs] 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
[docs] 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