def __insert_code(self, vm_code: VMCode): """ Inserts one vmcode into the bytecode :param vm_code: the opcode that will be inserted """ VMCodeMapping.instance().insert_code(vm_code)
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_Call(self, call: ast.Call): """ Visitor of a function call node :param call: the python ast function call node """ # the parameters are included into the stack in the reversed order function_id = self.visit(call.func) symbol = self.generator.get_symbol(function_id) args_addresses: List[int] = [] if isinstance(symbol, IBuiltinMethod) and symbol.push_self_first(): args = call.args[1:] args_addresses.append(VMCodeMapping.instance().bytecode_size) self.visit_to_generate(call.args[0]) else: args = call.args for arg in reversed(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)) else: self.generator.convert_load_symbol(function_id, args_addresses)
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 convert_assert(self): asserted_type = self._stack[-1] if len(self._stack) > 0 else Type.any if not isinstance(asserted_type, PrimitiveType): len_pos = VMCodeMapping.instance().bytecode_size # if the value is an array, a map or a struct, asserts it is not empty self.convert_builtin_method_call(Builtin.Len) len_code = VMCodeMapping.instance().code_map[len_pos] if asserted_type is Type.any: # need to check in runtime self.duplicate_stack_top_item() self.__insert1(OpcodeInfo.ISTYPE, StackItemType.Array) self._insert_jump(OpcodeInfo.JMPIF, len_code) self.duplicate_stack_top_item() self.__insert1(OpcodeInfo.ISTYPE, StackItemType.Map) self._insert_jump(OpcodeInfo.JMPIF, len_code) self.duplicate_stack_top_item() self.__insert1(OpcodeInfo.ISTYPE, StackItemType.Struct) self._insert_jump(OpcodeInfo.JMPIFNOT, 2) VMCodeMapping.instance().move_to_end(len_pos, len_pos) self.__insert1(OpcodeInfo.ASSERT)
def convert_end_try_finally(self, last_address: int, start_address: int, has_try_body: bool = False): """ Converts the end of the try finally statement :param last_address: the address of the try except last opcode. :param start_address: the address of the try first opcode :param has_try_body: whether this try statement has a finally body. :return: the last address of the except body """ if has_try_body: self.__insert1(OpcodeInfo.ENDFINALLY) vmcode_mapping_instance = VMCodeMapping.instance() try_vm_code = vmcode_mapping_instance.get_code(start_address) try_last_code = vmcode_mapping_instance.get_code(last_address) finally_start_address = vmcode_mapping_instance.get_end_address( try_last_code) + 1 finally_start_code = vmcode_mapping_instance.get_code( finally_start_address) if isinstance(try_vm_code, TryCode): try_vm_code.set_finally_code(finally_start_code) self._update_jump(vmcode_mapping_instance.bytecode_size, self.last_code_start_address) self._update_jump(last_address, VMCodeMapping.instance().bytecode_size)
def convert_get_sub_array(self, value_addresses: List[int] = None): """ Converts the end of get a slice in the beginning of an array :param value_addresses: the start and end values addresses """ # top: length, index, array if len(self._stack) > 2 and isinstance(self._stack[-3], SequenceType): if value_addresses is not None: opcodes = [ VMCodeMapping.instance().code_map[address] for address in value_addresses ] for code in opcodes: self.fix_negative_index( VMCodeMapping.instance().get_end_address(code) + 1) if self._stack[-3].stack_item in (StackItemType.ByteString, StackItemType.Buffer): self.duplicate_stack_item(2) self.convert_operation(BinaryOp.Sub) self.convert_get_substring() else: array = self._stack[-3] self.duplicate_stack_item( 3 ) # if slice end is greater than the array size, fixes them self.convert_builtin_method_call(Builtin.Len) # TODO: change to convert_builtin_method_call(Builtin.Min) when min(a, b) is implemented self.__insert1(OpcodeInfo.MIN) self._stack.pop() self.convert_get_array_slice(array)
def last_code(self) -> Optional[VMCode]: """ Gets the last code in the bytecode :return: the last code. If the bytecode is empty, returns None :rtype: VMCode or None """ if len(VMCodeMapping.instance().codes) > 0: return VMCodeMapping.instance().codes[-1] else: return None
def _update_break_jumps(self, loop_start_address) -> int: jump_target = VMCodeMapping.instance().bytecode_size if loop_start_address in self._jumps_to_loop_break: jump_addresses = self._jumps_to_loop_break.pop(loop_start_address) for address in jump_addresses: self._update_jump(address, jump_target)
def _get_method_debug_info(self, method_id: str, method: Method) -> Dict[str, Any]: from boa3.compiler.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 convert_end_for(self, start_address: int) -> int: """ Converts the end of the for statement :param start_address: the address of the for first opcode :return: the address of the loop condition """ self.__insert1(OpcodeInfo.INC) # index += 1 if len(self._stack) < 1 or self._stack[-1] is not Type.int: self._stack.append(Type.int) for_increment = self.last_code_start_address test_address = VMCodeMapping.instance().bytecode_size self._update_continue_jumps(start_address, for_increment) self.duplicate_stack_top_item() # dup index and sequence self.duplicate_stack_item(3) self.convert_builtin_method_call(Builtin.Len) self.convert_operation( BinaryOp.Lt) # continue loop condition: index < len(sequence) self.convert_end_while(start_address, test_address) self.remove_stack_top_item() # removes index and sequence from stack self.remove_stack_top_item() return test_address
def convert_end_method(self): """ Converts the end of the method """ if (self._current_method.init_bytecode is None and self._current_method.init_address in VMCodeMapping.instance().code_map): self._current_method.init_bytecode = VMCodeMapping.instance( ).code_map[self._current_method.init_address] if self.last_code.opcode is not Opcode.RET: self.insert_return() self._current_method.end_bytecode = self.last_code self._current_method = None self._stack.clear()
def bytecode_size(self) -> int: """ Gets the current bytecode size :return: the current bytecode size """ return VMCodeMapping.instance().bytecode_size
def convert_get_array_slice(self, array: SequenceType): """ Converts the end of get a substring """ self.convert_new_empty_array(0, array) # slice = [] self.duplicate_stack_item(3) # index = slice_start start_jump = self.convert_begin_while() # while index < slice_end self.duplicate_stack_top_item() # if index >= slice_start self.duplicate_stack_item(5) self.convert_operation(BinaryOp.GtE) is_valid_index = self.convert_begin_if() self.duplicate_stack_item(2) # slice.append(array[index]) self.duplicate_stack_item(6) self.duplicate_stack_item(3) self.convert_get_item() self.convert_builtin_method_call(Builtin.SequenceAppend) self.convert_end_if(is_valid_index) self.__insert1(OpcodeInfo.INC) # index += 1 condition_address = VMCodeMapping.instance().bytecode_size self.duplicate_stack_top_item() # end while index < slice_end self.duplicate_stack_item(4) self.convert_operation(BinaryOp.Lt) self.convert_end_while(start_jump, condition_address) self.remove_stack_top_item( ) # removes from the stack the arguments and the index self.swap_reverse_stack_items( 4) # doesn't use CLEAR opcode because this would delete self.remove_stack_top_item() # data from external scopes self.remove_stack_top_item() self.remove_stack_top_item()
def convert_end_try(self, start_address: int, end_address: Optional[int] = None) -> int: """ Converts the end of the try statement :param start_address: the address of the try first opcode :param end_address: the address of the try last opcode. If it is None, there's no except body. :return: the last address of the except body """ self.__insert1(OpcodeInfo.ENDTRY) if end_address is not None: vmcode_mapping_instance = VMCodeMapping.instance() try_vm_code = vmcode_mapping_instance.get_code(start_address) try_jump = vmcode_mapping_instance.get_code(end_address) except_start_address = vmcode_mapping_instance.get_end_address( try_jump) + 1 except_start_code = vmcode_mapping_instance.get_code( except_start_address) if isinstance(try_vm_code, TryCode): try_vm_code.set_except_code(except_start_code) self._update_jump(end_address, self.last_code_start_address) return self.last_code_start_address
def bytecode(self) -> bytes: """ Gets the bytecode of the translated code :return: the generated bytecode """ self.set_code_targets() return VMCodeMapping.instance().bytecode()
def _insert_jump(self, op_info: OpcodeInformation, jump_to: Union[int, VMCode] = 0): """ Inserts a jump opcode into the bytecode :param op_info: info of the opcode that will be inserted :param jump_to: data of the opcode """ if isinstance(jump_to, VMCode): jump_to = VMCodeMapping.instance().get_start_address( jump_to) - VMCodeMapping.instance().bytecode_size if self.last_code.opcode is not Opcode.RET: data: bytes = self._get_jump_data(op_info, jump_to) self.__insert1(op_info, data) for x in range(op_info.stack_items): self._stack.pop()
def convert_end_if(self, start_address: int): """ Converts the end of the if statement :param start_address: the address of the if first opcode """ # updates the begin jmp with the target address self._update_jump(start_address, VMCodeMapping.instance().bytecode_size)
def convert_begin_if(self) -> int: """ Converts the beginning of the if statement :return: the address of the if first opcode """ # it will be updated when the if ends self._insert_jump(OpcodeInfo.JMPIFNOT) return VMCodeMapping.instance().get_start_address(self.last_code)
def convert_end_while(self, start_address: int, test_address: int): """ Converts the end of the while statement :param start_address: the address of the while first opcode :param test_address: the address of the while test fist opcode """ # updates the begin jmp with the target address self._update_jump(start_address, test_address) self._update_continue_jumps(start_address, test_address) # inserts end jmp while_begin: VMCode = VMCodeMapping.instance().code_map[start_address] while_body: int = VMCodeMapping.instance().get_end_address( while_begin) + 1 end_jmp_to: int = while_body - VMCodeMapping.instance().bytecode_size self._insert_jump(OpcodeInfo.JMPIF, end_jmp_to) self._current_loop.pop()
def set_code_targets(self): for target, vmcodes in self._missing_target.copy().items(): if target is None: for code in vmcodes.copy(): relative_address: int = Integer.from_bytes(code.raw_data, signed=True) code_address: int = VMCodeMapping.instance( ).get_start_address(code) absolute_address = code_address + relative_address code.set_target( VMCodeMapping.instance().get_code(absolute_address)) vmcodes.remove(code) else: for code in vmcodes.copy(): code.set_target(VMCodeMapping.instance().get_code(target)) vmcodes.remove(code) if len(vmcodes) == 0: self._missing_target.pop(target)
def last_code_start_address(self) -> int: """ Gets the first address from last code in the bytecode :return: the last code's first address """ instance = VMCodeMapping.instance() if len(instance.codes) > 0: return instance.get_start_address(instance.codes[-1]) else: return 0
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.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.vmcodemapping import VMCodeMapping return VMCodeMapping.instance().get_start_address( self.init_bytecode)
def fix_negative_index(self, value_index: int = None): self._can_append_target = not self._can_append_target value_code = self.last_code_start_address size = VMCodeMapping.instance().bytecode_size self.duplicate_stack_top_item() self.__insert1(OpcodeInfo.SIGN) self.convert_literal(-1) jmp_address = VMCodeMapping.instance().bytecode_size self._insert_jump(OpcodeInfo.JMPNE) # if index < 0 self.duplicate_stack_item(2) # index += len(array) self.convert_builtin_method_call(Builtin.Len) self.convert_operation(BinaryOp.Add) if not isinstance(value_index, int): value_index = VMCodeMapping.instance().bytecode_size jmp_target = value_index if value_index < size else VMCodeMapping.instance( ).bytecode_size self._update_jump(jmp_address, jmp_target) VMCodeMapping.instance().move_to_end(value_index, value_code) self._can_append_target = not self._can_append_target
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 self.visit_to_generate(value) 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) value_address = self.visit_to_generate(value) 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 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 _update_codes_with_target(self, vm_code: VMCode): """ Verifies if there are any instructions targeting the code. If it exists, updates each instruction found :param vm_code: targeted instruction """ instance = VMCodeMapping.instance() vm_code_start_address = instance.get_start_address(vm_code) for target_address, codes in list(self._missing_target.items()): if target_address is not None and target_address <= vm_code_start_address: for code in codes: code.set_target(vm_code) self._missing_target.pop(target_address)
def __insert1(self, op_info: OpcodeInformation, data: bytes = None): """ Inserts one opcode into the bytecode :param op_info: info of the opcode that will be inserted :param data: data of the opcode, if needed """ vm_code = VMCode(op_info, data) if op_info.opcode.has_target(): data = vm_code.data relative_address: int = Integer.from_bytes(data, signed=True) actual_address = VMCodeMapping.instance( ).bytecode_size + relative_address if (self._can_append_target and relative_address != 0 and actual_address in VMCodeMapping.instance().code_map): vm_code.set_target( VMCodeMapping.instance().code_map[actual_address]) else: self._include_missing_target(vm_code, actual_address) self.__insert_code(vm_code) self._update_codes_with_target(vm_code)
def convert_builtin_method_call(self, function: IBuiltinMethod, args_address: List[int] = None): """ Converts a builtin method function call :param function: the function to be converted :param args_address: a list with each function arguments' first addresses """ if args_address is None: args_address = [] store_opcode: OpcodeInformation = None store_data: bytes = b'' if function.stores_on_slot and 0 < len( function.args) <= len(args_address): address = args_address[-len(function.args)] load_instr = VMCodeMapping.instance().code_map[address] if load_instr.opcode.is_load_slot: store: Opcode = Opcode.get_store_from_load(load_instr.opcode) store_opcode = OpcodeInfo.get_info(store) store_data = load_instr.data for opcode, data in function.opcode: op_info = OpcodeInfo.get_info(opcode) self.__insert1(op_info, data) if store_opcode is not None: self._insert_jump(OpcodeInfo.JMP) jump = self.last_code_start_address self.__insert1(store_opcode, store_data) self._update_jump(jump, VMCodeMapping.instance().bytecode_size) for _ in range(function.args_on_stack): self._stack.pop() if function.return_type not in (None, Type.none): self._stack.append(function.return_type)