def include_instruction(self, node: ast.AST, address: int): if self.current_method is not None and address in VMCodeMapping.instance( ).code_map: bytecode = VMCodeMapping.instance().code_map[address] from boa3.model.debuginstruction import DebugInstruction self.current_method.include_instruction( DebugInstruction.build(node, bytecode))
def visit_Subscript_Slice(self, subscript: ast.Subscript): """ Visitor of a subscript node with slice :param subscript: the python ast subscript node """ lower_omitted = subscript.slice.lower is None upper_omitted = subscript.slice.upper is None self.visit_to_generate(subscript.value) # if both are explicit if not lower_omitted and not upper_omitted: addresses = [VMCodeMapping.instance().bytecode_size] self.visit_to_generate(subscript.slice.lower) # length of slice addresses.append(VMCodeMapping.instance().bytecode_size) self.visit_to_generate(subscript.slice.upper) self.generator.convert_get_sub_array(addresses) # only one of them is omitted elif lower_omitted != upper_omitted: # end position is omitted if lower_omitted: self.visit_to_generate(subscript.slice.upper) self.generator.convert_get_array_beginning() # start position is omitted else: self.generator.duplicate_stack_top_item() # length of slice self.generator.convert_builtin_method_call(Builtin.Len) self.visit_to_generate(subscript.slice.lower) self.generator.convert_get_array_ending() else: self.generator.convert_copy()
def store_variable(self, *var_ids: Tuple[str, Optional[ast.AST]], value: ast.AST): # if the value is None, it is a variable declaration if value is not None: if len(var_ids) == 1: # it's a simple assignment var_id, index = var_ids[0] if index is None: # if index is None, then it is a variable assignment result_type = self.visit_to_generate(value) if result_type is Type.none and not self.generator.is_none_inserted( ): self.generator.convert_literal(None) self.generator.convert_store_variable(var_id) else: # if not, it is an array assignment self.generator.convert_load_symbol(var_id) self.visit_to_generate(index) aux_index = VMCodeMapping.instance().bytecode_size value_address = self.visit_to_generate(value) if isinstance(value, ast.Name): value_address = aux_index self.generator.convert_set_item(value_address) elif len(var_ids) > 0: # it's a chained assignment self.visit_to_generate(value) for pos, (var_id, index) in enumerate(reversed(var_ids)): if index is None: # if index is None, then it is a variable assignment if pos < len(var_ids) - 1: self.generator.duplicate_stack_top_item() self.generator.convert_store_variable(var_id) else: # if not, it is an array assignment if pos < len(var_ids) - 1: self.generator.convert_load_symbol(var_id) self.visit_to_generate(index) fix_index = VMCodeMapping.instance().bytecode_size self.generator.duplicate_stack_item(3) else: self.visit_to_generate(index) fix_index = VMCodeMapping.instance().bytecode_size self.generator.convert_load_symbol(var_id) self.generator.swap_reverse_stack_items(3) self.generator.convert_set_item(fix_index)
def _get_method_debug_info(self, method_id: str, method: Method) -> Dict[str, Any]: from boa3.compiler.codegenerator.vmcodemapping import VMCodeMapping return { "id": str(id(method)), "name": ',{0}'.format(method_id), # TODO: include module name "range": '{0}-{1}'.format(method.start_address, method.end_address), "params": [ '{0},{1}'.format(name, var.type.abi_type) for name, var in method.args.items() ], "return": method.return_type.abi_type, "variables": [ '{0},{1}'.format(name, var.type.abi_type) for name, var in method.locals.items() ], "sequence-points": [ '{0}[{1}]{2}:{3}-{4}:{5}'.format( VMCodeMapping.instance().get_start_address( instruction.code), self._get_method_origin_index(method), instruction.start_line, instruction.start_col, instruction.end_line, instruction.end_col) for instruction in method.debug_map() ] }
def end_address(self) -> Optional[int]: """ Gets the address of this method's last operation in the bytecode :return: the last address of the method """ if self.end_bytecode is None: return self.start_address else: from boa3.compiler.codegenerator.vmcodemapping import VMCodeMapping return VMCodeMapping.instance().get_end_address(self.end_bytecode)
def start_address(self) -> Optional[int]: """ Gets the address where this method starts in the bytecode :return: the first address of the method """ if self.init_bytecode is None and self.init_defaults_bytecode is None: return self.init_address else: from boa3.compiler.codegenerator.vmcodemapping import VMCodeMapping return VMCodeMapping.instance().get_start_address( self.init_bytecode)
def visit_to_map(self, node: ast.AST, generate: bool = False): address: int = VMCodeMapping.instance().bytecode_size if isinstance(node, ast.Expr): value = self.visit_Expr(node, generate) elif generate: value = self.visit_to_generate(node) else: value = self.visit(node) if not hasattr(node, 'test'): # control flow nodes must map each of their instructions self.include_instruction(node, address) return value
def append(self, value: IType, code: VMCode): states = self.stack_map index = VMCodeMapping.instance().get_start_address(code) if index in states: states[index].append(value) else: if self._current_stack is not None: stack = self._current_stack.copy() else: stack = NeoStack() stack.append(value) self._stacks.append((code, stack)) self._current_stack = stack
def _construct_abi_method(self, method_id: str, method: Method) -> Dict[str, Any]: from boa3.compiler.codegenerator.vmcodemapping import VMCodeMapping return { "name": method_id, "offset": (VMCodeMapping.instance().get_start_address(method.start_bytecode) if method.start_bytecode is not None else 0), "parameters": [ { "name": arg_id, "type": arg.type.abi_type } for arg_id, arg in method.args.items() ], "returntype": method.type.abi_type, "safe": False }
def _get_raw_data(self, opcode: VMCode) -> bytes: """ Gets the Neo VM raw data of the code :return: the unformatted data in bytes of the code. """ min_len = self._info.data_len // 2 # for try opcode, data_len adds catch and finally addresses if opcode is None: return bytes(min_len) else: from boa3.compiler.codegenerator.vmcodemapping import VMCodeMapping code_mapping = VMCodeMapping.instance() self_start = code_mapping.get_start_address(self) target_start = code_mapping.get_start_address(opcode) return (Integer(target_start - self_start).to_byte_array( signed=True, min_length=min_len))
def visit_While(self, while_node: ast.While): """ Visitor of a while statement node :param while_node: the python ast while statement node """ start_addr: int = self.generator.convert_begin_while() for stmt in while_node.body: self.visit_to_map(stmt, generate=True) test_address: int = VMCodeMapping.instance().bytecode_size self.visit_to_map(while_node.test, generate=True) self.generator.convert_end_while(start_addr, test_address) else_begin_address: int = self.generator.last_code_start_address for stmt in while_node.orelse: self.visit_to_map(stmt, generate=True) self.generator.convert_end_loop_else(start_addr, else_begin_address, len(while_node.orelse) > 0)
def _construct_abi_method(self, method_id: str, method: Method) -> Dict[str, Any]: from boa3.compiler.codegenerator.vmcodemapping import VMCodeMapping abi_method_name = method.external_name if isinstance( method.external_name, str) else method_id logging.info(f"'{abi_method_name}' method included in the manifest") return { "name": abi_method_name, "offset": (VMCodeMapping.instance().get_start_address(method.start_bytecode) if method.start_bytecode is not None else 0), "parameters": [{ "name": arg_id, "type": arg.type.abi_type } for arg_id, arg in method.args.items()], "returntype": method.type.abi_type, "safe": method.is_safe }
def visit_Call(self, call: ast.Call) -> IType: """ Visitor of a function call node :param call: the python ast function call node :returns: The called function return type """ # the parameters are included into the stack in the reversed order last_address = VMCodeMapping.instance().bytecode_size last_stack = self.generator.stack_size function_id = self.visit(call.func) if not isinstance(function_id, str): if not isinstance(function_id, tuple) or len(function_id) != 2: return Type.none class_type, identifier = function_id if (isinstance(class_type, ClassType) and identifier in class_type.symbols and isinstance(class_type.symbols[identifier], IExpression)): symbol = class_type.symbols[identifier] function_id = identifier else: return Type.none else: is_internal = hasattr(call, 'is_internal_call') and call.is_internal_call symbol = self.generator.get_symbol(function_id, is_internal=is_internal) if isinstance(symbol, ClassType): symbol = symbol.constructor_method() args_addresses: List[int] = [] if VMCodeMapping.instance().bytecode_size > last_address: # remove opcodes inserted during the evaluation of the symbol VMCodeMapping.instance().remove_opcodes(last_address, VMCodeMapping.instance().bytecode_size) if last_stack < self.generator.stack_size: # remove any additional values pushed to the stack during the evalution of the symbol for _ in range(self.generator.stack_size - last_stack): self.generator._stack_pop() if isinstance(symbol, Method): args_to_generate = [arg for index, arg in enumerate(call.args) if index in symbol.args_to_be_generated()] else: args_to_generate = call.args if isinstance(symbol, IBuiltinMethod): reordered_args = [] for index in symbol.generation_order: if 0 <= index < len(args_to_generate): reordered_args.append(args_to_generate[index]) args = reordered_args else: args = reversed(args_to_generate) for arg in args: args_addresses.append( VMCodeMapping.instance().bytecode_size ) self.visit_to_generate(arg) if self.is_exception_name(function_id): self.generator.convert_new_exception(len(call.args)) elif isinstance(symbol, IBuiltinMethod): self.generator.convert_builtin_method_call(symbol, args_addresses) else: self.generator.convert_load_symbol(function_id, args_addresses) return symbol.type if isinstance(symbol, IExpression) else symbol
def debug_map(self) -> List[DebugInstruction]: """ Returns a list with the debug information of each mapped Python instruction inside this method """ from boa3.compiler.codegenerator.vmcodemapping import VMCodeMapping return sorted(self._debug_map, key=lambda instr: VMCodeMapping.instance().get_start_address(instr.code))
def stack_map(self) -> Dict[int, NeoStack]: vm_code_mapping = VMCodeMapping.instance() return {vm_code_mapping.get_start_address(vmcode): stack for vmcode, stack in self._stacks}
def build_code_generator(self) -> CodeGenerator: from boa3.compiler.codegenerator.vmcodemapping import VMCodeMapping VMCodeMapping.reset() return CodeGenerator({})