from .opcodes import * from .space import * from .base import * class Code: '''Can generate, store and run sequence of ops representing js code''' def __init__(self, is_strict=False, debug_mode=False): self.tape = [] self.compiled = False self.label_locs = None self.is_strict = is_strict self.debug_mode = debug_mode self.contexts = [] self.current_ctx = None self.return_locs = [] self._label_count = 0 self.label_locs = None # useful references self.GLOBAL_THIS = None self.space = None # dbg self.ctx_depth = 0 def get_new_label(self): self._label_count += 1 return self._label_count def emit(self, op_code, *args): ''' Adds op_code with specified args to tape ''' self.tape.append(OP_CODES[op_code](*args)) def compile(self, start_loc=0): ''' Records locations of labels and compiles the code ''' self.label_locs = {} if self.label_locs is None else self.label_locs loc = start_loc while loc < len(self.tape): if type(self.tape[loc]) == LABEL: self.label_locs[self.tape[loc].num] = loc del self.tape[loc] continue loc += 1 self.compiled = True def _call(self, func, this, args): ''' Calls a bytecode function func NOTE: use !ONLY! when calling functions from native methods! ''' assert not func.is_native # fake call - the the runner to return to the end of the file old_contexts = self.contexts old_return_locs = self.return_locs old_curr_ctx = self.current_ctx self.contexts = [FakeCtx()] self.return_locs = [len(self.tape)] # target line after return # prepare my ctx my_ctx = func._generate_my_context(this, args) self.current_ctx = my_ctx # execute dunction ret = self.run(my_ctx, starting_loc=self.label_locs[func.code]) # bring back old execution self.current_ctx = old_curr_ctx self.contexts = old_contexts self.return_locs = old_return_locs return ret def execute_fragment_under_context(self, ctx, start_label, end_label): ''' just like run but returns if moved outside of the specified fragment # 4 different exectution results # 0=normal, 1=return, 2=jump_outside, 3=errors # execute_fragment_under_context returns: # (return_value, typ, return_value/jump_loc/py_error) # IMPARTANT: It is guaranteed that the length of the ctx.stack is unchanged. ''' old_curr_ctx = self.current_ctx self.ctx_depth += 1 old_stack_len = len(ctx.stack) old_ret_len = len(self.return_locs) old_ctx_len = len(self.contexts) try: self.current_ctx = ctx return self._execute_fragment_under_context( ctx, start_label, end_label) except JsException as err: if self.debug_mode: self._on_fragment_exit("js errors") # undo the things that were put on the stack (if any) to ensure a proper error recovery del ctx.stack[old_stack_len:] del self.return_locs[old_ret_len:] del self.contexts[old_ctx_len :] return undefined, 3, err finally: self.ctx_depth -= 1 self.current_ctx = old_curr_ctx assert old_stack_len == len(ctx.stack) def _get_dbg_indent(self): return self.ctx_depth * ' ' def _on_fragment_exit(self, mode): print(self._get_dbg_indent() + 'ctx exit (%s)' % mode) def _execute_fragment_under_context(self, ctx, start_label, end_label): start, end = self.label_locs[start_label], self.label_locs[end_label] initial_len = len(ctx.stack) loc = start entry_level = len(self.contexts) # for e in self.tape[start:end]: # print e if self.debug_mode: print(self._get_dbg_indent() + 'ctx entry (from:%d, to:%d)' % (start, end)) while loc < len(self.tape): if len(self.contexts) == entry_level and loc >= end: if self.debug_mode: self._on_fragment_exit('normal') assert loc == end delta_stack = len(ctx.stack) - initial_len assert delta_stack == +1, 'Stack change must be equal to +1! got %d' % delta_stack return ctx.stack.pop(), 0, None # means normal return # execute instruction if self.debug_mode: print(self._get_dbg_indent() + str(loc), self.tape[loc]) status = self.tape[loc].eval(ctx) # check status for special actions if status is not None: if type(status) == int: # jump to label loc = self.label_locs[status] if len(self.contexts) == entry_level: # check if jumped outside of the fragment and break if so if not start <= loc < end: if self.debug_mode: self._on_fragment_exit('jump outside loc:%d label:%d' % (loc, status)) delta_stack = len(ctx.stack) - initial_len assert delta_stack == +1, 'Stack change must be equal to +1! got %d' % delta_stack return ctx.stack.pop(), 2, status # jump outside continue elif len(status) == 2: # a call or a return! # call: (new_ctx, func_loc_label_num) if status[0] is not None: # append old state to the stack self.contexts.append(ctx) self.return_locs.append(loc + 1) # set new state loc = self.label_locs[status[1]] ctx = status[0] self.current_ctx = ctx continue # return: (None, None) else: if len(self.contexts) == entry_level: if self.debug_mode: self._on_fragment_exit('return') delta_stack = len(ctx.stack) - initial_len assert delta_stack == +1, 'Stack change must be equal to +1! got %d' % delta_stack return undefined, 1, ctx.stack.pop( ) # return signal return_value = ctx.stack.pop() ctx = self.contexts.pop() self.current_ctx = ctx ctx.stack.append(return_value) loc = self.return_locs.pop() continue # next instruction loc += 1 if self.debug_mode: self._on_fragment_exit('internal error - unexpected end of tape, will crash') assert False, 'Remember to add NOP at the end!' def run(self, ctx, starting_loc=0): loc = starting_loc self.current_ctx = ctx while loc < len(self.tape): # execute instruction if self.debug_mode: print(loc, self.tape[loc]) status = self.tape[loc].eval(ctx) # check status for special actions if status is not None: if type(status) == int: # jump to label loc = self.label_locs[status] continue elif len(status) == 2: # a call or a return! # call: (new_ctx, func_loc_label_num) if status[0] is not None: # append old state to the stack self.contexts.append(ctx) self.return_locs.append(loc + 1) # set new state loc = self.label_locs[status[1]] ctx = status[0] self.current_ctx = ctx continue # return: (None, None) else: return_value = ctx.stack.pop() ctx = self.contexts.pop() self.current_ctx = ctx ctx.stack.append(return_value) loc = self.return_locs.pop() continue # next instruction loc += 1 assert len(ctx.stack) == 1, ctx.stack return ctx.stack.pop() class FakeCtx(object): def __init__(self): self.stack = []