def RAISE_VARARGS(ctx: _VSContext, instruction: dis.Instruction): """ Raises an exception to either the current scope or the outer scope. """ # This is relatively simple. # We ignore the argc == 3, and pretend it's argc == 2 argc = instruction.arg if argc == 3: # f**k you ctx.pop() argc = 2 if argc == 2: # FROM exception is Top of stack now. fr = ctx.pop() # The real exception is top of stack now. exc = ctx.pop() exc.__cause__ = fr elif argc == 1: exc = ctx.pop() else: # Bare raise. exc = ctx._exception_state # Inject the exception. safe_raise(ctx, exc) # Raise the exception. return exc
def DUP_TOP(ctx: _VSContext, instruction: dis.Instruction): """ Duplicates the top-most item on the stack. """ item = ctx.pop() ctx.push(item) ctx.push(item) return ctx
def RETURN_VALUE(ctx: _VSContext, instruction: dis.Instruction): """ Returns a value. This will set the state of the context. """ ctx._result = ctx.pop() ctx.state = VSCtxState.FINISHED ctx._handling_exception = False return ctx
def POP_JUMP_IF_FALSE(ctx: _VSContext, instruction: dis.Instruction): """ Jumps to the specified instruction if False-y is on the top of the stack. """ i = ctx.pop() if i: # Truthy, don't jump. return ctx # Jump! ctx.instruction_pointer = get_instruction_index_by_offset(ctx, instruction) return ctx
def LOAD_FAST(ctx: _VSContext, instruction: dis.Instruction): """ Loads from VARNAMES. """ item = ctx.varnames[instruction.arg] if item == NO_RESULT: safe_raise( ctx, NameError("name '{}' is not defined".format( ctx.co_varnames[instruction.arg]))) return ctx ctx.push(item) return ctx
def POP_JUMP_IF_TRUE(ctx: _VSContext, instruction: dis.Instruction): """ Jumps to the specified instruction if True-y is on the top of the stack. """ i = ctx.pop() if not i: # Falsey, stay where we are. return ctx # Jump, again. ctx.instruction_pointer = get_instruction_index_by_offset(ctx, instruction) return ctx
def LOAD_GLOBAL(ctx: _VSContext, instruction: dis.Instruction): """ Loads a global from `ctx.__globals__`. """ name = ctx.co_names[instruction.arg] try: item = ctx.get_global(name) except KeyError: # todo: safe_raise return safe_raise(ctx, NameError("name '{}' is not defined".format(name))) ctx.push(item) return ctx
def JUMP_FORWARD(ctx: _VSContext, instruction: dis.Instruction): """ Jumps forward to the specified instruction. """ ctx.instruction_pointer = get_instruction_index_by_offset(ctx, instruction) return ctx
def safe_raise(ctx: _VSContext, exception: BaseException): """ Attempts to "safely" raise an exception into the context. If the exception is being handled by a `try` block, it will automatically move the pointer to the Except block that is consistent with it. Otherwise, it will attempt to bubble it out of the stack. :param ctx: The context to raise into. :param exception: The exception to raise. :return: The context. """ # Create a traceback for this exception. exception._tb = create_traceback(ctx) # Inject the exception. ctx.inject_exception(exception) return ctx
def SETUP_EXCEPT(ctx: _VSContext, instruction: dis.Instruction): """ Sets a context up for an except. """ # Update the exception pointer with the calculated offset. # This is where we will jump to if an error is encountered. ctx.exc_next_pointer = get_instruction_index_by_offset(ctx, instruction) return ctx
def POP_EXCEPT(ctx: _VSContext, instruction: dis.Instruction): """ Pops an except block. """ # Here, we can make several assumptions: # 1) The exception has been handled. # 2) We can empty the exception state. # 3) The function can continue on as normal. # This means the exception state is cleared, handling_exception is removed, and it is safe to jump forward as # appropriate. ctx._exception_state = None ctx._handling_exception = False # Also, remove the exception pointer. # That way, it won't try to safely handle an exception that happens later on. ctx.exc_next_pointer = None return ctx
def COMPARE_OP(ctx: _VSContext, instruction: dis.Instruction): """ Implements comparison operators. """ # TODO: Rewrite COMPARE_OP into Vanstein-ran function calls. # TODO: Add all of the comparison functions. if instruction.arg == 10: # Pop the too match off. to_match = ctx.pop() # This is what we check. raised = ctx.pop() from collections import Iterable if not isinstance(to_match, Iterable): to_match = (to_match, ) for e in to_match: # PyType_IsSubType if issubclass(type(raised), e): ctx.push(True) break else: ctx.push(False) return ctx
def run_context(self, context: _VSContext) -> _VSContext: """ Runs the current bytecode for a context. This will instructions off of the instruction stack, until it reaches a context switch. """ # Welcome to the main bulk of Vanstein. # Enjoy your stay! # Switch to running state for this context. context.state = VSCtxState.RUNNING self.current_context = context while True: if context.state is VSCtxState.FINISHED: # Done after a successful RETURN_VALUE. # Break the loop, and return the context. context.finish() return context if context.state is VSCtxState.ERRORED: return context next_instruction = context.next_instruction() self.current_instruction = next_instruction # First, we check if we need to context switch. # Check if it's CALL_FUNCTION. if next_instruction.opname == "CALL_FUNCTION": # This is the instruction for CALL_FUNCTION. No specialized one exists in the instructions.py file. # We need to context switch, so suspend this current one. context.state = VSCtxState.SUSPENDED # Get STACK[-arg] # CALL_FUNCTION(arg) => arg is number of positional arguments to use, so pop that off of the stack. bottom_of_stack = context.stack[-(next_instruction.arg + 1)] # method wrappers die if type(bottom_of_stack) is type: bottom_of_stack = bottom_of_stack.__new__ # Here's some context switching. # First, check if it's a builtin or is a native invoke. # Also, check if we should even do context switching. if inspect.isbuiltin(bottom_of_stack) or hasattr(bottom_of_stack, "_native_invoke")\ or self.do_context_switching is False: # Run it! result = self.__run_natively(context, next_instruction) # Set the result on the context. context.state = VSCtxState.RUNNING # Push the result onto the stack. context.push(result) # Continue the loop to the next instruction. continue if isinstance(bottom_of_stack, VSWrappedFunction): # Call the VSWrappedFunction to get a new context. # We'll manually fill these args. new_ctx = bottom_of_stack() else: # Wrap the function in a context. new_ctx = _VSContext(bottom_of_stack) # Set the previous context, for stack frame chaining. new_ctx.prev_ctx = context # Doubly linked list! context.next_ctx = new_ctx # Set the new state to PENDING so it knows to run it on the next run. new_ctx.state = VSCtxState.PENDING # Add a callback to the new context. # This is so the loop can schedule execution of the new context soon. new_ctx.add_done_callback(context._on_result_cb) new_ctx.add_exception_callback(context._on_exception_cb) # Fill the number of arguments the function call requests. args = [] for _ in range(0, next_instruction.arg): args.append(context.pop()) args = reversed(args) new_ctx.fill_args(*args) # Pop the function object off, too. context.pop() return new_ctx # Else, we run the respective instruction. try: i = getattr(instructions, next_instruction.opname) except AttributeError: raise NotImplementedError(next_instruction.opname) # Call the instruction handler. i(context, next_instruction)
def STORE_NAME(ctx: _VSContext, instruction: dis.Instruction): ctx.names[instruction.arg] = ctx.pop() return ctx
def STORE_FAST(ctx: _VSContext, instruction: dis.Instruction): """ Stores data in co_varnames. """ ctx.varnames[instruction.arg] = ctx.pop() return ctx
def POP_TOP(ctx: _VSContext, instruction: dis.Instruction): """ Pops off the top of the stack. """ ctx.pop() return ctx
def LOAD_CONST(ctx: _VSContext, instruction: dis.Instruction): """ Loads a const from `ctx.co_consts`. """ ctx.push(ctx.co_consts[instruction.arg]) return ctx