def __exit__(self, *args): frame = self._get_context_frame() retcode = super(namespace, self).__exit__(*args) # funcode = copy.deepcopy(self.bytecode) funcode = copy.copy(self.bytecode) # Ensure it's a properly formed func by always returning something funcode.append(bytecode.Instr('LOAD_CONST', None)) funcode.append(bytecode.Instr('RETURN_VALUE')) # Switch LOAD/STORE/DELETE_FAST/NAME to LOAD/STORE/DELETE_ATTR to_replace = [] for i, instr in enumerate(funcode): repl = self._replace_opcode(instr, frame) if repl: to_replace.append((i, repl)) offset = 0 for i, repl in to_replace: funcode[i + offset:i + offset + 1] = repl offset += len(repl) - 1 # Create function object to do the manipulation funcode.argnames = ("_[namespace]", ) funcode.argcount = 1 funcode.name = "<withhack>" gs = self._get_context_frame().f_globals func = types.FunctionType(funcode.to_code(), gs) # Execute bytecode in context of namespace retval = func(self.namespace) self._run_as_clause(self.namespace) return retcode
def __exit__(self, *args): self.test_frames = inspect.getouterframes(inspect.currentframe()) # from IPython.core.debugger import set_trace; set_trace() retcode = super(CacheLocals, self).__exit__(*args) funcode = copy.copy(self.bytecode) funcode.append(bytecode.Instr('LOAD_CONST', None)) funcode.append(bytecode.Instr('RETURN_VALUE')) frame = self._get_context_frame() change_lookups(funcode, locals_=frame.f_locals) # self.source = inspect.getsource(funcode.to_code()) assigned_locals = {} for i, instr in enumerate(funcode): if getattr(instr, 'name', None) in ('STORE_FAST', 'STORE_NAME'): # TODO: Could we use the code leading up to a variable as a # hash? assigned_locals[instr.arg] = bytecode.Bytecode(funcode[:i + 1]) self._get_cached(funcode.to_code(), assigned_locals) self.assigned_locals = assigned_locals return retcode
def __exit__(self, *args): frame = self._get_context_frame() retcode = super(CaptureFunction, self).__exit__(*args) funcode = copy.copy(self.bytecode) # Ensure it's a properly formed func by always returning something funcode.append(bytecode.Instr('LOAD_CONST', None)) funcode.append(bytecode.Instr('RETURN_VALUE')) change_lookups(funcode, args=self.__args, locals_=frame.f_locals) # Create the resulting function object # funcode.args = self.__args # funcode.varargs = self.__varargs # funcode.varkwargs = self.__varkwargs funcode.name = self.__name funcode.argnames = self.__args funcode.argcount = len(self.__args) if self.__varargs: funcode.flags |= inspect.CO_VARARGS funcode.argcount -= 1 if self.__varkwargs: funcode.flags |= inspect.CO_VARKEYWORDS funcode.argcount -= 1 gs = self._get_context_frame().f_globals nm = self.__name defs = self.__argdefs self.function = types.FunctionType(funcode.to_code(), gs, nm, defs) return retcode
def _replace_opcode( self, instr, frame, *, _load=lambda i: [bytecode.Instr('LOAD_ATTR', i.arg)], _store=lambda i: [bytecode.Instr('STORE_ATTR', i.arg)], _delete=lambda i: [bytecode.Instr('DELETE_ATTR', i.arg)], _exc=AttributeError): Instr = bytecode.Instr Label = bytecode.Label if instr.name in ( 'STORE_FAST', 'STORE_NAME', ): return [Instr('LOAD_FAST', "_[namespace]")] + _store(instr) if instr.name in ( 'DELETE_FAST', 'DELETE_NAME', ): return [Instr('LOAD_FAST', "_[namespace]")] + _delete(instr) if instr.name in ('LOAD_FAST', 'LOAD_NAME', 'LOAD_GLOBAL', 'LOAD_DEREF'): excIn = Label() excOut = Label() end = Label() # try: # x = namespace.<attr> # except AttributeError: # x = load_name(frame, '<attr>') return [ Instr('SETUP_EXCEPT', excIn), Instr('LOAD_FAST', "_[namespace]") ] + _load(instr) + [ Instr('STORE_FAST', "_[ns_value]"), Instr('POP_BLOCK'), Instr('JUMP_FORWARD', end), excIn, Instr('DUP_TOP'), Instr('LOAD_CONST', _exc), Instr('COMPARE_OP', bytecode.Compare.EXC_MATCH), Instr('POP_JUMP_IF_FALSE', excOut), Instr('POP_TOP'), Instr('POP_TOP'), Instr('POP_TOP'), Instr('LOAD_CONST', load_name), Instr('LOAD_CONST', frame), Instr('LOAD_CONST', instr.arg), Instr('CALL_FUNCTION', 2), Instr('STORE_FAST', "_[ns_value]"), Instr('POP_EXCEPT'), Instr('JUMP_FORWARD', end), excOut, Instr('END_FINALLY'), end, Instr('LOAD_FAST', "_[ns_value]") ] return None
def try_squash_raise(self): """ A context manager for squashing tracebacks. The code written during this context will be wrapped so that any exception raised will appear to have been generated from the code, rather than any function called by the code. """ exc_label = bc.Label() end_label = bc.Label() op_code = "SETUP_FINALLY" if PY38 else "SETUP_EXCEPT" self.code_ops.append( bc.Instr(op_code, exc_label), # TOS ) yield self.code_ops.extend([ # TOS bc.Instr("POP_BLOCK"), # TOS bc.Instr("JUMP_FORWARD", end_label), # TOS exc_label, # TOS -> tb -> val -> exc bc.Instr("ROT_THREE"), # TOS -> exc -> tb -> val bc.Instr("ROT_TWO"), # TOS -> exc -> val -> tb bc.Instr("POP_TOP"), # TOS -> exc -> val bc.Instr("RAISE_VARARGS", 2), # TOS bc.Instr("JUMP_FORWARD", end_label), # TOS bc.Instr("END_FINALLY"), # TOS end_label, # TOS ])
def build_tuple(self, n=0): """ Build a tuple from items on the TOS. """ if n == 0: self.code_ops.append( # TOS bc.Instr("LOAD_CONST", ()), # TOS -> tuple ) else: self.code_ops.append( # TOS bc.Instr("BUILD_TUPLE", n), # TOS -> tuple )
def _run_as_clause(self, value): """ Run the as clause, setting the target expression to `value` This handles arbitrary as clause expressions, like with somehack as d['item'][i].foo().bar: pass """ assert self._as_clause if len(self._as_clause) == 1: first = self._as_clause[0] # store_fast has to be handled specially if first.name == 'STORE_FAST': self._set_context_locals({first.arg: value}) return # pop_top is a no-op elif first.name == 'POP_TOP': return # if somehow there's a STORE_FAST in there, it's not going to work if any(instr.name == 'STORE_FAST' for instr in self._as_clause): raise NotImplementedError("Cannot handle this as clause") frame = self._get_context_frame() # prepend a LOAD_CONST with a dummy value dummy = object() code = copy.copy(self._as_clause) code[:0] = [bytecode.Instr('LOAD_CONST', dummy)] code.extend([ bytecode.Instr('LOAD_CONST', None), bytecode.Instr('RETURN_VALUE') ]) # configure the object code.argcount = 0 code.name = '<as clause>' code.flags &= ~inspect.CO_NEWLOCALS # fiddle with variable lookups change_lookups(code, locals_=frame.f_locals) # now swap out the constant (which would be rejected by to_concrete_bytecode) concrete_code = code.to_concrete_bytecode() concrete_code.consts[concrete_code.consts.index(dummy)] = value # run the assignment in the context frame raw_code = concrete_code.to_code() exec(raw_code, frame.f_globals, frame.f_locals)
def run_in_dynamic_scope(code, global_vars): """Wrap functions defined in operators/decl func to run them in the proper dynamic scope. This will also rewrite the function to convert each LOAD_GLOBAL opcode into a LOAD_NAME opcode, unless the associated name is known to have been made global via the 'global' keyword. Parameters ---------- code : bytecode.Bytecode Code object in which functions are defined. """ # Code generator used to modify the bytecode cg = CodeGenerator() fetch_helpers(cg) # Scan all ops to detect function call after GET_ITER for instr in code: if not isinstance(instr, bc.Instr): cg.code_ops.append(instr) continue i_name, i_arg = instr.name, instr.arg if isinstance(i_arg, CodeType): # Allow to pass the dynamic scope as locals. There is no need # to copy it as internal variables are stored in fast locals # and hence does not affect the scope content. inner = bc.Bytecode.from_code(i_arg) inner.flags ^= (inner.flags & bc.CompilerFlags.NEWLOCALS) # Set the NESTED flag since even though we may have obtained the # outer code from an expr it will run as a function. inner.flags |= bc.CompilerFlags.NESTED run_in_dynamic_scope(inner, global_vars) inner.update_flags() i_arg = inner.to_code() elif any(i_name == make_fun_op for make_fun_op in _MAKE_FUNC): cg.code_ops.append(bc.Instr(i_name, i_arg)) # func load_helper(cg, 'wrap_func') # func -> wrap cg.rot_two() # wrap -> func cg.load_global('__scope__') # wrap -> func -> scope cg.call_function(2) # wrapped continue cg.code_ops.append(bc.Instr(i_name, i_arg)) del code[:] code.extend(cg.code_ops) rewrite_globals_access(code, global_vars) code.update_flags()
def rot_three(self): """ Rotate the three values on the TOS. """ self.code_ops.append( # TOS -> val_1 -> val_2 -> val_3 bc.Instr("ROT_THREE"), # TOS -> val_3 -> val_1 -> val_2 )
def rot_two(self): """ Rotate the two values on the TOS. """ self.code_ops.append( # TOS -> val_1 -> val_2 bc.Instr("ROT_TWO"), # TOS -> val_2 -> val_1 )
def pop_top(self): """ Pop the value from the TOS. """ self.code_ops.append( # TOS -> value bc.Instr("POP_TOP"), # TOS )
def make_function(self, n_defaults=0): """ Make a function from a code object on the TOS. """ self.code_ops.append( # TOS -> qual_name -> code -> defaults bc.Instr("MAKE_FUNCTION", n_defaults), # TOS -> func )
def load_build_class(self): """ Build a class from the top 3 stack items. """ self.code_ops.append( # TOS bc.Instr("LOAD_BUILD_CLASS"), # TOS -> builtins.__build_class__ )
def delete_fast(self, name): """ Delete a named fast local variable. """ self.code_ops.append( # TOS bc.Instr("DELETE_FAST", name), # TOS )
def store_attr(self, name): """ Store the value at 2nd as an attr on 1st. """ self.code_ops.append( # TOS -> val -> obj bc.Instr("STORE_ATTR", name), # TOS )
def build_map(self, n=0): """ Build a map and store it onto the TOS. """ self.code_ops.append( # TOS bc.Instr("BUILD_MAP", n), # TOS -> map )
def dup_top(self): """ Duplicate the value on the TOS. """ self.code_ops.append( # TOS -> value bc.Instr("DUP_TOP"), # TOS -> value -> value )
def binary_add(self): """ Multiple the 2 items on the TOS. """ self.code_ops.append( # TOS -> val_1 -> val_2 bc.Instr("BINARY_ADD"), # TOS -> retval )
def binary_multiply(self): """ Multiple the 2 items on the TOS. """ self.code_ops.append( # TOS -> val_1 -> val_2 bc.Instr("BINARY_MULTIPLY"), # TOS -> retval )
def binary_subscr(self): """ Subscript the #2 item with the TOS. """ self.code_ops.append( # TOS -> obj -> idx bc.Instr("BINARY_SUBSCR"), # TOS -> value )
def return_value(self): """ Return the value from the TOS. """ self.code_ops.append( # TOS -> value bc.Instr("RETURN_VALUE"), # TOS )
def unpack_sequence(self, n): """ Unpack the sequence on the TOS. """ self.code_ops.append( # TOS -> obj bc.Instr("UNPACK_SEQUENCE", n), # TOS -> val_n -> val_2 -> val_1 )
def store_fast(self, name): """ Store the TOS as a fast local. """ self.code_ops.append( # TOS -> value bc.Instr("STORE_FAST", name), # TOS )
def store_name(self, name): """ Store the TOS under name. """ self.code_ops.append( # TOS -> value bc.Instr("STORE_NAME", name), # TOS )
def store_subscr(self): """ Store the index/value pair on the TOS into the 3rd item. """ self.code_ops.append( # TOS -> value -> obj -> index bc.Instr("STORE_SUBSCR"), # TOS )
def delete_global(self, name): """ Delete a named global variable. """ self.code_ops.append( # TOS bc.Instr("DELETE_GLOBAL", name), # TOS )
def load_attr(self, name): """ Load an attribute from the object on TOS. """ self.code_ops.append( # TOS -> obj bc.Instr("LOAD_ATTR", name), # TOS -> value )
def store_global(self, name): """ Store the TOS as a global. """ self.code_ops.append( # TOS -> value bc.Instr("STORE_GLOBAL", name), # TOS )
def build_list(self, n=0): """ Build a list from items on the TOS. """ self.code_ops.append( # TOS bc.Instr("BUILD_LIST", n), # TOS -> list )
def load_const(self, const): """ Load a const value onto the TOS. """ self.code_ops.append( # TOS bc.Instr("LOAD_CONST", const), # TOS -> value )