def _patch_function(fn: FunctionType, nargs: int) -> FunctionType: co = fn.__code__ co_flags = co.co_flags & ~HAS_VARSTUFF co_args : tuple if hasattr(co, "co_posonlyargcount"): co_args = ( nargs, 0, 0, co.co_nlocals, co.co_stacksize, co_flags, co.co_code, co.co_consts, co.co_names, co.co_varnames, co.co_filename, co.co_name, co.co_firstlineno, co.co_lnotab, co.co_freevars, co.co_cellvars ) else: co_args = ( nargs, 0, co.co_nlocals, co.co_stacksize, co_flags, co.co_code, co.co_consts, co.co_names, co.co_varnames, co.co_filename, co.co_name, co.co_firstlineno, co.co_lnotab, co.co_freevars, co.co_cellvars) new_code = CodeType(*co_args) # type: ignore return FunctionType(new_code, fn.__globals__, fn.__name__, fn.__defaults__, fn.__closure__)
def rewrite_lnotab(code): """Replace a code object's line number information to claim that every byte of the bytecode is a new line. Returns a new code object. Also recurses to hack the line numbers in nested code objects. Based on Ned Batchelder's hackpyc.py: http://nedbatchelder.com/blog/200804/wicked_hack_python_bytecode_tracing.html """ if has_been_rewritten(code): return code n_bytes = len(code.co_code) new_lnotab = "\x01\x01" * (n_bytes-1) new_consts = [] for const in code.co_consts: if type(const) is CodeType: new_consts.append(rewrite_lnotab(const)) else: new_consts.append(const) return CodeType(code.co_argcount, code.co_nlocals, code.co_stacksize, code.co_flags, code.co_code, tuple(new_consts), code.co_names, code.co_varnames, code.co_filename, code.co_name, 0, new_lnotab, code.co_freevars, code.co_cellvars)
def _code_constructor(argcount, kwonlyargcount, nlocals, stacksize, flags, codestring, constants, names, varnames, filename, name, firstlineno, lnotab, freevars, cellvars): # noinspection PyTypeChecker return CodeType( argcount, 0, kwonlyargcount, nlocals, stacksize, flags, codestring, constants, names, varnames, filename, name, firstlineno, lnotab, freevars, cellvars, )
def recompile(source, filename, mode, flags=0, lineno=1, prefix=None): if isinstance(source, ast.AST): root = source else: root = parse_snippet(source, filename, mode, flags, lineno) node = root.body[0] if not isinstance(node, ast.FunctionDef): raise RuntimeError('expected FunctionDef AST node') code = compile(root, filename, mode, flags, True) for cobj in code.co_consts: if not isinstance(cobj, CodeType): continue if cobj.co_name == node.name and cobj.co_firstlineno == node.lineno: break else: raise RuntimeError('function code not found') # Mangle private names if necessary. if prefix is not None: is_private = re.compile('^__.*(?<!__)$').match def fix_names(names): return tuple(prefix + name if is_private(name) else name for name in names) cobj = CodeType(cobj.co_argcount, cobj.co_nlocals, cobj.co_stacksize, cobj.co_flags, cobj.co_code, cobj.co_consts, fix_names(cobj.co_names), fix_names(cobj.co_varnames), cobj.co_filename, cobj.co_name, cobj.co_firstlineno, cobj.co_lnotab, cobj.co_freevars, cobj.co_cellvars) return cobj
def pycode(argcount, kwonlyargcount, nlocals, stacksize, flags, codestring, constants, names, varnames, filename, name, firstlineno, lnotab, freevars=(), cellvars=()): """types.CodeType constructor that accepts keyword arguments. See Also -------- types.CodeType """ return CodeType( argcount, kwonlyargcount, nlocals, stacksize, flags, codestring, constants, names, varnames, filename, name, firstlineno, lnotab, freevars, cellvars, )
def make(): consts = [makelambda().__code__, '<func>', None] args = [] names = [ 'add', ] lstbin = [ opcode.opmap["LOAD_CONST"], 0, opcode.opmap["LOAD_CONST"], 1, opcode.opmap["MAKE_FUNCTION"], 0, opcode.opmap["STORE_FAST"], 0, opcode.opmap["LOAD_FAST"], 0, opcode.opmap["RETURN_VALUE"], 0, ] code = CodeType( 0, # argcount 0, # kwonlyargcount 0, # nlocals 2, # stacksize 64, # flags bytes(lstbin), # codestring tuple(consts), # consts tuple(names), # names tuple(args + names), # varnames '<string>', # filename '<nil>', # name 1, # firstlineno b'', # lnotab tuple(), # freevars tuple(), # cellvars ) return FunctionType(code, {})
def deserialize_function(f: dict): code_fields = f[CODE_FIELD_NAME][VALUE] code_args = [] for field in CODE_OBJECT_ARGS: arg = code_fields[field] if type(arg) == dict: code_args.append(deserialize(arg)) else: code_args.append(arg) details = [CodeType(*code_args)] glob = {'__builtins__': __builtins__} for name, o in f[GLOBAL_FIELD_NAME].items(): glob[name] = deserialize(o) details.append(glob) for attr in FUNCTION_ATTRS_NAMES: if attr == CODE_FIELD_NAME: continue details.append(deserialize(f[attr])) result_func = FunctionType(*details) if result_func.__name__ in result_func.__getattribute__(GLOBAL_FIELD_NAME): result_func.__getattribute__(GLOBAL_FIELD_NAME)[result_func.__name__] = result_func return result_func
def deserialize_function(obj): recursive_flag = False globals = obj['__globals__'] for outer_obj_name, outer_obj in globals.items(): if outer_obj_name == obj['__name__']: recursive_flag = True globals[outer_obj_name] = deserialize(outer_obj) globals['__builtins__'] = __builtins__ code = obj['__code__'] for i in range(len(code)): # co_lnotab if i == 13 and code[i] is None: code[i] = b'' if code[i] is None: code[i] = () elif isinstance(code[i], list): code[i] = bytes(code[i]) func = FunctionType(CodeType(*code), globals, obj['__name__'], obj['__defaults__'], None) if recursive_flag: func.__getattribute__('__globals__')[obj['__name__']] = func return func
def _import_codetup(codetup): if is_py_3k: # Handle tuples sent from 3.8 as well as 3 < version < 3.8. if len(codetup) == 16: (argcount, posonlyargcount, kwonlyargcount, nlocals, stacksize, flags, code, consts, names, varnames, filename, name, firstlineno, lnotab, freevars, cellvars) = codetup else: (argcount, kwonlyargcount, nlocals, stacksize, flags, code, consts, names, varnames, filename, name, firstlineno, lnotab, freevars, cellvars) = codetup posonlyargcount = 0 else: (argcount, nlocals, stacksize, flags, code, consts, names, varnames, filename, name, firstlineno, lnotab, freevars, cellvars) = codetup consts2 = [] for const in consts: if isinstance(const, tuple) and len(const) == 2 and const[0] == CODEOBJ_MAGIC: consts2.append(_import_codetup(const[1])) else: consts2.append(const) consts = tuple(consts2) if is_py_gte38: codetup = (argcount, posonlyargcount, kwonlyargcount, nlocals, stacksize, flags, code, consts, names, varnames, filename, name, firstlineno, lnotab, freevars, cellvars) elif is_py_3k: codetup = (argcount, kwonlyargcount, nlocals, stacksize, flags, code, consts, names, varnames, filename, name, firstlineno, lnotab, freevars, cellvars) else: codetup = (argcount, nlocals, stacksize, flags, code, consts, names, varnames, filename, name, firstlineno, lnotab, freevars, cellvars) return CodeType(*codetup)
def new_code( self, code, argcount=0, posonlyargcount=0, kwonlyargcount=0, nlocals=0, stacksize=0, flags=0, constants=(), names=(), varnames=(), filename="foo.py", name="foo", firstlineno=1, lnotab=b"", freevars=(), cellvars=(), ): return CodeType( argcount, posonlyargcount, kwonlyargcount, nlocals, stacksize, flags, code, constants, names, varnames, filename, name, firstlineno, lnotab, freevars, cellvars, )
def _make_code(code, filename, consts): if sys.version_info[0] == 2: # pragma: no cover (PY2) arglist = [ code.co_argcount, code.co_nlocals, code.co_stacksize, code.co_flags, code.co_code, tuple(consts), code.co_names, code.co_varnames, filename, code.co_name, code.co_firstlineno, code.co_lnotab, code.co_freevars, code.co_cellvars, ] else: # pragma: no cover (PY3) arglist = [ code.co_argcount, code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize, code.co_flags, code.co_code, tuple(consts), code.co_names, code.co_varnames, filename, code.co_name, code.co_firstlineno, code.co_lnotab, code.co_freevars, code.co_cellvars, ] return CodeType(*arglist)
def update_code(f, **kwargs): """Update attributes of a function's __code__.""" code = f.__code__ newcode = CodeType( kwargs.get('co_argcount', code.co_argcount), kwargs.get('co_kwonlyargcount', code.co_kwonlyargcount), kwargs.get('co_nlocals', code.co_nlocals), kwargs.get('co_stacksize', code.co_stacksize), kwargs.get('co_flags', code.co_flags), kwargs.get('co_code', code.co_code), kwargs.get('co_consts', code.co_consts), kwargs.get('co_names', code.co_names), kwargs.get('co_varnames', code.co_varnames), kwargs.get('co_filename', code.co_filename), kwargs.get('co_name', code.co_name), kwargs.get('co_firstlineno', code.co_firstlineno), kwargs.get('co_lnotab', code.co_lnotab), kwargs.get('co_freevars', code.co_freevars), kwargs.get('co_cellvars', code.co_cellvars), ) return FunctionType( newcode, f.__globals__, f.__name__, f.__defaults__, f.__closure__, )
def to_code(self, from_function=False): """Assemble a Python code object from a Code object""" num_fastnames = sum(1 for op, arg in self.code if isopcode(op) and op in haslocal) is_function = self.newlocals or num_fastnames > 0 or len(self.args) > 0 nested = is_function and from_function co_flags = {op[0] for op in self.code} is_generator = self.force_generator or (YIELD_VALUE in co_flags or YIELD_FROM in co_flags) no_free = (not self.freevars) and (not co_flags & hasfree) if version_info >= ( 3, 5, ): is_native_coroutine = bool(self.force_coroutine or (co_flags & coroutine_opcodes)) assert not (is_native_coroutine and self.force_iterable_coroutine) co_flags =\ (not(STORE_NAME in co_flags or LOAD_NAME in co_flags or DELETE_NAME in co_flags)) |\ (self.newlocals and CO_NEWLOCALS) |\ (self.varargs and CO_VARARGS) |\ (self.varkwargs and CO_VARKEYWORDS) |\ (is_generator and CO_GENERATOR) |\ (no_free and CO_NOFREE) |\ (nested and CO_NESTED) if version_info >= ( 3, 5, ): co_flags |= (is_native_coroutine and CO_COROUTINE) |\ (self.force_iterable_coroutine and CO_ITERABLE_COROUTINE) |\ (self.future_generator_stop and CO_FUTURE_GENERATOR_STOP) co_consts = [self.docstring] co_names = [] co_varnames = list(self.args) co_freevars = tuple(self.freevars) # Find all cellvars beforehand for two reasons # Need the number of them to construct the numeric arg for ops in hasfree # Need to put args which are cells in the beginning of co_cellvars cellvars = { arg for op, arg in self.code if isopcode(op) and op in hasfree and arg not in co_freevars } co_cellvars = [jumps for jumps in self.args if jumps in cellvars] def index(seq, item, eq=True, can_append=True): for i, x in enumerate(seq): if x == item if eq else x is item: return i if can_append: seq.append(item) return len(seq) - 1 else: raise IndexError("Item not found") jumps = [] label_pos = {} lastlineno = self.firstlineno lastlinepos = 0 co_code = bytearray() co_lnotab = bytearray() for i, (op, arg) in enumerate(self.code): if isinstance(op, Label): label_pos[op] = len(co_code) elif op is SetLineno: incr_lineno = arg - lastlineno incr_pos = len(co_code) - lastlinepos lastlineno = arg lastlinepos += incr_pos if incr_lineno != 0 or incr_pos != 0: while incr_pos > 255: co_lnotab += b"\xFF\0" incr_pos -= 255 while incr_lineno > 255: co_lnotab += bytes((incr_pos, 255)) incr_pos = 0 incr_lineno -= 255 if incr_pos or incr_lineno: co_lnotab += bytes((incr_pos, incr_lineno)) elif op == opcode.EXTENDED_ARG: self.code[i + 1][1] |= 1 << 32 elif op not in hasarg: co_code += bytes((op, )) else: if op in hasconst: if isinstance(arg, Code) and\ i + 2 < len(self.code) and self.code[i + 2][0] in hascode: arg = arg.to_code(from_function=is_function) assert arg is not None arg = index(co_consts, arg, 0) elif op in hasname: arg = index(co_names, arg) elif op in hasjump: jumps.append((len(co_code), arg)) co_code += bytes((op, 0, 0)) continue elif op in haslocal: arg = index(co_varnames, arg) elif op in hascompare: arg = index(cmp_op, arg, can_append=False) elif op in hasfree: try: arg = index(co_freevars, arg, can_append=False) + len(cellvars) except IndexError: arg = index(co_cellvars, arg) if arg > 0xFFFF: co_code += bytes((opcode.EXTENDED_ARG, arg >> 16 & 0xFF, arg >> 24 & 0xFF)) co_code += bytes((op, arg & 0xFF, arg >> 8 & 0xFF)) for pos, label in jumps: jump = label_pos[label] if co_code[pos] in hasjrel: jump -= pos + 3 if jump > 0xFFFF: raise NotImplementedError("Extended jumps not implemented") co_code[pos + 1] = jump & 0xFF co_code[pos + 2] = jump >> 8 & 0xFF co_argcount = len( self.args) - self.varargs - self.varkwargs - self.kwonly co_stacksize = self._compute_stacksize() return CodeType(co_argcount, self.kwonly, len(co_varnames), co_stacksize, co_flags, bytes(co_code), tuple(co_consts), tuple(co_names), tuple(co_varnames), self.filename, self.name, self.firstlineno, bytes(co_lnotab), co_freevars, tuple(co_cellvars))
def recreate_function(func, module=None, name=None, add_args=[], firstlineno=None): """Recreate a function, replacing some info. :param func: Function object. :param module: Module to contribute to. :param add_args: Additional arguments to add to function. :return: Function copy. """ def get_code(func): return func.__code__ if six.PY3 else func.func_code def set_code(func, code): if six.PY3: func.__code__ = code else: func.func_code = code argnames = [ "co_argcount", "co_nlocals", "co_stacksize", "co_flags", "co_code", "co_consts", "co_names", "co_varnames", "co_filename", "co_name", "co_firstlineno", "co_lnotab", "co_freevars", "co_cellvars", ] if six.PY3: argnames.insert(1, "co_kwonlyargcount") for arg in get_args(func): if arg in add_args: add_args.remove(arg) args = [] code = get_code(func) for arg in argnames: if module is not None and arg == "co_filename": args.append(module.__file__) elif name is not None and arg == "co_name": args.append(name) elif arg == "co_argcount": args.append(getattr(code, arg) + len(add_args)) elif arg == "co_varnames": co_varnames = getattr(code, arg) args.append(co_varnames[:code.co_argcount] + tuple(add_args) + co_varnames[code.co_argcount:]) elif arg == "co_firstlineno": args.append(firstlineno if firstlineno else 1) else: args.append(getattr(code, arg)) set_code(func, CodeType(*args)) if name is not None: func.__name__ = name return func
def _insert_code(code_to_modify, code_to_insert, before_line): """ Insert piece of code `code_to_insert` to `code_to_modify` right inside the line `before_line` before the instruction on this line by modifying original bytecode :param code_to_modify: Code to modify :param code_to_insert: Code to insert :param before_line: Number of line for code insertion :return: boolean flag whether insertion was successful, modified code """ linestarts = dict(dis.findlinestarts(code_to_modify)) if not linestarts: return False, code_to_modify if code_to_modify.co_name == '<module>': # There's a peculiarity here: if a breakpoint is added in the first line of a module, we # can't replace the code because we require a line event to stop and the line event # was already generated, so, fallback to tracing. if before_line == min(linestarts.values()): return False, code_to_modify if before_line not in linestarts.values(): return False, code_to_modify offset = None for off, line_no in linestarts.items(): if line_no == before_line: offset = off break code_to_insert_list = add_jump_instruction(offset, code_to_insert) try: code_to_insert_list, new_names = \ _add_attr_values_from_insert_to_original(code_to_modify, code_to_insert, code_to_insert_list, 'co_names', dis.hasname) code_to_insert_list, new_consts = \ _add_attr_values_from_insert_to_original(code_to_modify, code_to_insert, code_to_insert_list, 'co_consts', [opmap['LOAD_CONST']]) code_to_insert_list, new_vars = \ _add_attr_values_from_insert_to_original(code_to_modify, code_to_insert, code_to_insert_list, 'co_varnames', dis.haslocal) new_bytes, all_inserted_code = _update_label_offsets( code_to_modify.co_code, offset, list(code_to_insert_list)) new_lnotab = _modify_new_lines(code_to_modify, offset, code_to_insert_list) if new_lnotab is None: return False, code_to_modify except ValueError: traceback.print_exc() return False, code_to_modify new_code = CodeType( code_to_modify.co_argcount, # integer code_to_modify.co_kwonlyargcount, # integer len(new_vars), # integer code_to_modify.co_stacksize, # integer code_to_modify.co_flags, # integer new_bytes, # bytes new_consts, # tuple new_names, # tuple new_vars, # tuple code_to_modify.co_filename, # string code_to_modify.co_name, # string code_to_modify.co_firstlineno, # integer new_lnotab, # bytes code_to_modify.co_freevars, # tuple code_to_modify.co_cellvars # tuple ) return True, new_code
def check(func): _globals_ = func.__globals__ typs = func.__annotations__.copy() ret = typs.pop('return') _code_ = func.__code__ o_varnames = list(_code_.co_varnames) o_names = list(_code_.co_names) o_consts = list(_code_.co_consts) n_consts = o_consts + list( set( list(typs.values()) + [ret]) ) + [typcheck] #print( "new consts:",n_consts ) info = {} for k,v in typs.items(): info[o_varnames.index(k)] = v o_binlst = list( _code_.co_code ) o_argcount = _code_.co_argcount lst = [ ] n = o_argcount i = 0 while n : t = o_binlst[i+1] lst += [opmap["LOAD_CONST"],len(n_consts)-1, o_binlst[i],t, opmap["LOAD_CONST"],n_consts.index( info[t] ), opmap["CALL_FUNCTION"],2, opmap["POP_TOP"],0 ] i += 2 n -=1 n = len(o_binlst) - 2 i = 0 # ------------ # func ^ stack top # result | last = [ opmap["LOAD_CONST"],len(n_consts)-1, opmap["ROT_TWO"],0,# Swaps the two top-most stack items. opmap["LOAD_CONST"],n_consts.index( ret ), opmap["CALL_FUNCTION"],2 ] while n < len(o_binlst) : t = o_binlst[n + 1] last += [o_binlst[n],t] n +=2 o_binlst = o_binlst[0:len(o_binlst)-2] lstbin = lst + o_binlst + last code = CodeType(_code_.co_argcount, # argcount _code_.co_kwonlyargcount, # kwonlyargcount _code_.co_nlocals, # nlocals _code_.co_stacksize, # stacksize _code_.co_flags, # flags bytes(lstbin), # codestring tuple(n_consts), # consts _code_.co_names, # names _code_.co_varnames, # varnames _code_.co_filename, # filename _code_.co_name, # name _code_.co_firstlineno, # firstlineno _code_.co_lnotab, # lnotab _code_.co_freevars, # freevars _code_.co_cellvars, # cellvars ) #dis.dis(code) #dis.show_code(code) #print( lstbin ) return FunctionType(code,_globals_)
def __setstate__(self, state): self.source = state['source'] self.ast = state['ast'] self.code = CodeType(0, *state['code']) self._globals = state['lookup'].globals
def check(func): _globals_ = func.__globals__ temp = {} old_annotations = {} temp.update(func.__annotations__) old_annotations.update(func.__annotations__) typs = temp ret = typs.pop('return') _code_ = func.__code__ o_varnames = list(_code_.co_varnames) o_names = list(_code_.co_names) o_consts = list(_code_.co_consts) n_consts = o_consts + list(set(list(typs.values()) + [ret])) + [typcheck] #print( "new consts:",n_consts ) info = {} for k, v in typs.items(): info[o_varnames.index(k)] = v o_binlst = list(_code_.co_code) o_argcount = _code_.co_argcount mlst = [] n = o_argcount for i in range(n): mlst += [ opmap["LOAD_CONST"], len(n_consts) - 1, opmap["LOAD_FAST"], i, opmap["LOAD_CONST"], n_consts.index(info[i]), opmap["CALL_FUNCTION"], 2, opmap["POP_TOP"], 0 ] jumps = [ opmap["POP_JUMP_IF_FALSE"], opmap["POP_JUMP_IF_TRUE"], opmap["JUMP_IF_TRUE_OR_POP"], opmap["JUMP_IF_FALSE_OR_POP"], opmap["JUMP_ABSOLUTE"] ] jump_forward = opmap["JUMP_FORWARD"] OffSet = len(mlst) # ------------ # func ^ stack top # result | last = [ opmap["LOAD_CONST"], len(n_consts) - 1, opmap["ROT_TWO"], 0, # Swaps the two top-most stack items. opmap["LOAD_CONST"], n_consts.index(ret), opmap["CALL_FUNCTION"], 2, opmap["RETURN_VALUE"], 0 ] new_binlst = o_binlst lset = len(last) - 2 old_addrs = list() for i in range(len(o_binlst)): if o_binlst[i] in jumps: old_addrs += [o_binlst[i + 1]] lst = new_binlst for i in old_addrs: new_binlst[i] = [-1, new_binlst[i]] new_binlst[i + 1] = [-1, new_binlst[i + 1]] acc = [] while lst: op, arg = lst[0], lst[1] if op == opmap["RETURN_VALUE"]: acc += last else: acc += [op, arg] lst = lst[2:] new_binlst = mlst + acc new_addrs = list() for i in range(len(new_binlst)): op = new_binlst[i] if isinstance(op, list): new_addrs += [i] new_binlst[i] = new_binlst[i][1] new_binlst[i + 1] = new_binlst[i + 1][1] for i in range(len(new_binlst)): op = new_binlst[i] if op in jumps: new_binlst[i + 1] = new_addrs[0] new_addrs = new_addrs[1:] lstbin = new_binlst code = CodeType( _code_.co_argcount, # argcount _code_.co_kwonlyargcount, # kwonlyargcount _code_.co_nlocals, # nlocals _code_.co_stacksize, # stacksize _code_.co_flags, # flags bytes(lstbin), # codestring tuple(n_consts), # consts _code_.co_names, # names _code_.co_varnames, # varnames _code_.co_filename, # filename _code_.co_name, # name _code_.co_firstlineno, # firstlineno _code_.co_lnotab, # lnotab _code_.co_freevars, # freevars _code_.co_cellvars, # cellvars ) #dis.dis(code) #dis.show_code(code) #print( lstbin ) func = FunctionType(code, _globals_) func.__annotations__ = old_annotations return func
def _patch_code_obj(code, custom_co_code): return CodeType(code.co_argcount, code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize, code.co_flags, custom_co_code, code.co_consts, code.co_names, code.co_varnames, code.co_filename, code.co_name, code.co_firstlineno, code.co_lnotab, code.co_freevars, code.co_cellvars)
def _implement_maybe(code_object): """ Manipulate the f**k out of a code object to make Maybe a perfectly "valid" keyword. This will return a fresh new CodeType object with the necessary surgery performed on it. Oh, and it implements Maybe recursively. Any code objects in the constants of this one will also be mutilated. :) """ # immediately die if Maybe is used as a function parameter name argnames = code_object.co_varnames[:code_object.co_argcount] if "Maybe" in argnames: _mock_syntax_error(message="can't use keyword as parameter name", line_no=code_object.co_firstlineno) # add the maybe function to the constants new_constants = [*code_object.co_consts, _rand_bool] maybe_index = len(new_constants) - 1 for index in range(maybe_index): const = new_constants[index] # modify the bytecode recursively if possible. if isinstance(const, CodeType): new_constants[index] = _implement_maybe(const) insert_points = [] # these will be used to realign jump locations new_ops = [] current_line = 0 for instr in dis.get_instructions(code_object): ignore_instr = False # keep track of line numbers for making syntax errors if instr.starts_line is not None: current_line = instr.starts_line # it is illegal to use Maybe as a keyword argument if instr.opname == "CALL_FUNCTION_KW": _, prev_arg = new_ops[-1] # check if Maybe is in the list of kwargs if "Maybe" in new_constants[prev_arg]: _mock_syntax_error(message="keyword can't be an expression", line_no=current_line) if instr.argval == "Maybe": # if this script assigns to Maybe, tell it to bugger off if instr.opname.startswith("STORE"): _mock_syntax_error(message="can't assign to keyword", line_no=current_line) # and the same if they're trying to do `del Maybe`. if instr.opname.startswith("DELETE"): _mock_syntax_error(message="can't delete keyword", line_no=current_line) # otherwise, replace any access to Maybe with a _rand_bool call if instr.opname.startswith("LOAD"): # consider everything except "Maybe" strings though, ew. if instr.opname != "LOAD_CONST": new_ops.extend([ ["LOAD_CONST", maybe_index], ["CALL_FUNCTION", 0] ]) # equivalent to replacing `Maybe` with `_rand_bool()` insert_points.append(instr.offset) ignore_instr = True # ignore the original instruction if not ignore_instr: new_ops.append([instr.opname, instr.arg]) # now go through the instructions and realign any jump locations. for index in range(len(new_ops)): opname, arg = new_ops[index] opcode = dis.opmap[opname] if opcode in dis.hasjabs: # move the jump location to account for every insertion before it. offset = 0 for point in insert_points: if point >= arg: break offset += 2 # then just change the arg of the instruction. new_ops[index][1] += offset if opcode in dis.hasjrel: # same as above, but only consider the insertions between # the jump instruction and the jump destination. offset = 0 for point in insert_points: # if an insertion occurred between the jump and its # destination, make note of the new jump size if point >= index * 2: offset += 2 # anything beyond the jump location won't affect it if point > arg: break new_ops[index][1] += offset # now we can construct the new bytecode... spaghetti incoming :D new_code_obj = CodeType( # these attributes are unchanged from the original code object code_object.co_argcount, code_object.co_kwonlyargcount, code_object.co_nlocals, code_object.co_stacksize, code_object.co_flags, # these are the only attributes which change _assemble_instructions(new_ops), tuple(new_constants), # then these ones are also unchanged code_object.co_names, code_object.co_varnames, code_object.co_filename, code_object.co_name, code_object.co_firstlineno, code_object.co_lnotab, code_object.co_freevars, code_object.co_cellvars) return new_code_obj
def fake_traceback(exc_value, tb, filename, lineno): """Produce a new traceback object that looks like it came from the template source instead of the compiled code. The filename, line number, and location name will point to the template, and the local variables will be the current template context. :param exc_value: The original exception to be re-raised to create the new traceback. :param tb: The original traceback to get the local variables and code info from. :param filename: The template filename. :param lineno: The line number in the template source. """ if tb is not None: # Replace the real locals with the context that would be # available at that point in the template. locals = get_template_locals(tb.tb_frame.f_locals) locals.pop("__jinja_exception__", None) else: locals = {} globals = { "__name__": filename, "__file__": filename, "__jinja_exception__": exc_value, } # Raise an exception at the correct line number. code = compile("\n" * (lineno - 1) + "raise __jinja_exception__", filename, "exec") # Build a new code object that points to the template file and # replaces the location with a block name. try: location = "template" if tb is not None: function = tb.tb_frame.f_code.co_name if function == "root": location = "top-level template code" elif function.startswith("block_"): location = 'block "%s"' % function[6:] # Collect arguments for the new code object. CodeType only # accepts positional arguments, and arguments were inserted in # new Python versions. code_args = [] for attr in ( "argcount", "posonlyargcount", # Python 3.8 "kwonlyargcount", # Python 3 "nlocals", "stacksize", "flags", "code", # codestring "consts", # constants "names", "varnames", ("filename", filename), ("name", location), "firstlineno", "lnotab", "freevars", "cellvars", ): if isinstance(attr, tuple): # Replace with given value. code_args.append(attr[1]) continue try: # Copy original value if it exists. code_args.append(getattr(code, "co_" + attr)) except AttributeError: # Some arguments were added later. continue code = CodeType(*code_args) except Exception: # Some environments such as Google App Engine don't support # modifying code objects. pass # Execute the new code, which is guaranteed to raise, and return # the new traceback without this frame. try: exec(code, globals, locals) except BaseException: return sys.exc_info()[2].tb_next
def pin_arguments(func: FunctionType, arguments: dict): """Transform `func` in a function with no arguments. Example: def func(a, b): c = 4 print(str(a) + str(c)) return b The function returned by pin_arguments(func, {"a": 10, "b": 11}) is equivalent to: def pinned_func(): c = 4 print(str(10) + str(c)) return 11 This function is in some ways equivalent to functools.partials but with a faster runtime. `arguments` keys should be identical as `func` arguments names else a TypeError is raised. """ if signature(func).parameters.keys() != set(arguments): raise TypeError("`arguments` and `func` arguments do not correspond") func_code = func.__code__ func_co_consts = func_code.co_consts func_co_varnames = func_code.co_varnames new_co_consts = remove_duplicates(func_co_consts + tuple(arguments.values())) new_co_varnames = tuple(item for item in func_co_varnames if item not in arguments) trans_co_varnames2_co_consts = { func_co_varnames.index(key): new_co_consts.index(value) for key, value in arguments.items() } trans_co_varnames = get_transitions(func_co_varnames, new_co_varnames) transitions = { **get_b_transitions(trans_co_varnames2_co_consts, OpCode.LOAD_FAST, OpCode.LOAD_CONST), **get_b_transitions(trans_co_varnames, OpCode.LOAD_FAST, OpCode.LOAD_FAST), **get_b_transitions(trans_co_varnames, OpCode.STORE_FAST, OpCode.STORE_FAST), } func_instructions = get_instructions(func) new_func_instructions = tuple( transitions.get(instruction, instruction) for instruction in func_instructions) new_co_code = b"".join(new_func_instructions) new_func = FunctionType( func.__code__, func.__globals__, func.__name__, func.__defaults__, func.__closure__, ) nfcode = new_func.__code__ python_version = sys.version_info if python_version.minor != 8: new_func.__code__ = CodeType( 0, 0, len(new_co_varnames), nfcode.co_stacksize, nfcode.co_flags, new_co_code, new_co_consts, nfcode.co_names, new_co_varnames, nfcode.co_filename, nfcode.co_name, nfcode.co_firstlineno, nfcode.co_lnotab, nfcode.co_freevars, nfcode.co_cellvars, ) return new_func new_func.__code__ = CodeType( 0, 0, 0, len(new_co_varnames), nfcode.co_stacksize, nfcode.co_flags, new_co_code, new_co_consts, nfcode.co_names, new_co_varnames, nfcode.co_filename, nfcode.co_name, nfcode.co_firstlineno, nfcode.co_lnotab, nfcode.co_freevars, nfcode.co_cellvars, ) return new_func
def fake_exc_info(exc_info, filename, lineno): """Helper for `translate_exception`.""" exc_type, exc_value, tb = exc_info # figure the real context out if tb is not None: locals = get_jinja_locals(tb.tb_frame.f_locals) # if there is a local called __jinja_exception__, we get # rid of it to not break the debug functionality. locals.pop('__jinja_exception__', None) else: locals = {} # assamble fake globals we need globals = { '__name__': filename, '__file__': filename, '__jinja_exception__': exc_info[:2], # we don't want to keep the reference to the template around # to not cause circular dependencies, but we mark it as Jinja # frame for the ProcessedTraceback '__jinja_template__': None } # and fake the exception code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec') # if it's possible, change the name of the code. This won't work # on some python environments such as google appengine try: if tb is None: location = 'template' else: function = tb.tb_frame.f_code.co_name if function == 'root': location = 'top-level template code' elif function.startswith('block_'): location = 'block "%s"' % function[6:] else: location = 'template' if PY2: code = CodeType(0, code.co_nlocals, code.co_stacksize, code.co_flags, code.co_code, code.co_consts, code.co_names, code.co_varnames, filename, location, code.co_firstlineno, code.co_lnotab, (), ()) else: code = CodeType(0, code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize, code.co_flags, code.co_code, code.co_consts, code.co_names, code.co_varnames, filename, location, code.co_firstlineno, code.co_lnotab, (), ()) except Exception as e: pass # execute the code and catch the new traceback try: exec(code, globals, locals) except: exc_info = sys.exc_info() new_tb = exc_info[2].tb_next # return without this frame return exc_info[:2] + (new_tb, )
def inline(pre_func: FunctionType, func: FunctionType, pre_func_arguments: dict): """Insert `prefunc` at the beginning of `func` and return the corresponding function. `pre_func` should not have a return statement (else a ValueError is raised). `pre_func_arguments` keys should be identical as `pre_func` arguments names else a TypeError is raised. This approach takes less CPU instructions than the standard decorator approach. Example: def pre_func(b, c): a = "hello" print(a + " " + b + " " + c) def func(x, y): z = x + 2 * y return z ** 2 The returned function corresponds to: def inlined(x, y): a = "hello" print(a) z = x + 2 * y return z ** 2 """ new_func = FunctionType( func.__code__, func.__globals__, func.__name__, func.__defaults__, func.__closure__, ) if not has_no_return(pre_func): raise ValueError("`pre_func` returns something") pinned_pre_func = pin_arguments(pre_func, pre_func_arguments) pinned_pre_func_code = pinned_pre_func.__code__ pinned_pre_func_co_consts = pinned_pre_func_code.co_consts pinned_pre_func_co_names = pinned_pre_func_code.co_names pinned_pre_func_co_varnames = pinned_pre_func_code.co_varnames pinned_pre_func_instructions = tuple(get_instructions(pinned_pre_func)) pinned_pre_func_instructions_without_return = pinned_pre_func_instructions[: -2] func_code = func.__code__ func_co_consts = func_code.co_consts func_co_names = func_code.co_names func_co_varnames = func_code.co_varnames func_instructions = tuple(get_instructions(func)) shifted_func_instructions = shift_instructions( func_instructions, len(b"".join(pinned_pre_func_instructions_without_return))) new_co_consts = remove_duplicates(func_co_consts + pinned_pre_func_co_consts) new_co_names = remove_duplicates(func_co_names + pinned_pre_func_co_names) new_co_varnames = remove_duplicates(func_co_varnames + pinned_pre_func_co_varnames) trans_co_consts = get_transitions(pinned_pre_func_co_consts, new_co_consts) trans_co_names = get_transitions(pinned_pre_func_co_names, new_co_names) trans_co_varnames = get_transitions(pinned_pre_func_co_varnames, new_co_varnames) transitions = { **get_b_transitions(trans_co_consts, OpCode.LOAD_CONST, OpCode.LOAD_CONST), **get_b_transitions(trans_co_names, OpCode.LOAD_GLOBAL, OpCode.LOAD_GLOBAL), **get_b_transitions(trans_co_names, OpCode.LOAD_METHOD, OpCode.LOAD_METHOD), **get_b_transitions(trans_co_names, OpCode.LOAD_ATTR, OpCode.LOAD_ATTR), **get_b_transitions(trans_co_names, OpCode.STORE_ATTR, OpCode.STORE_ATTR), **get_b_transitions(trans_co_varnames, OpCode.LOAD_FAST, OpCode.LOAD_FAST), **get_b_transitions(trans_co_varnames, OpCode.STORE_FAST, OpCode.STORE_FAST), } new_pinned_pre_func_instructions = tuple( transitions.get(instruction, instruction) for instruction in pinned_pre_func_instructions_without_return) new_instructions = new_pinned_pre_func_instructions + shifted_func_instructions new_co_code = b"".join(new_instructions) nfcode = new_func.__code__ python_version = sys.version_info if python_version.minor != 8: new_func.__code__ = CodeType( nfcode.co_argcount, nfcode.co_kwonlyargcount, len(new_co_varnames), nfcode.co_stacksize, nfcode.co_flags, new_co_code, new_co_consts, new_co_names, new_co_varnames, nfcode.co_filename, nfcode.co_name, nfcode.co_firstlineno, nfcode.co_lnotab, nfcode.co_freevars, nfcode.co_cellvars, ) return new_func new_func.__code__ = CodeType( nfcode.co_argcount, nfcode.co_posonlyargcount, nfcode.co_kwonlyargcount, len(new_co_varnames), nfcode.co_stacksize, nfcode.co_flags, new_co_code, new_co_consts, new_co_names, new_co_varnames, nfcode.co_filename, nfcode.co_name, nfcode.co_firstlineno, nfcode.co_lnotab, nfcode.co_freevars, nfcode.co_cellvars, ) return new_func
def _build_preprocessed_function(func, processors, args_defaults, varargs, varkw): """ Build a preprocessed function with the same signature as `func`. Uses `exec` internally to build a function that actually has the same signature as `func. """ format_kwargs = {'func_name': func.__name__} def mangle(name): return 'a' + uuid4().hex + name format_kwargs['mangled_func'] = mangled_funcname = mangle(func.__name__) def make_processor_assignment(arg, processor_name): template = "{arg} = {processor}({func}, '{arg}', {arg})" return template.format( arg=arg, processor=processor_name, func=mangled_funcname, ) exec_globals = {mangled_funcname: func, 'wraps': wraps} defaults_seen = 0 default_name_template = 'a' + uuid4().hex + '_%d' signature = [] call_args = [] assignments = [] star_map = { varargs: '*', varkw: '**', } def name_as_arg(arg): return star_map.get(arg, '') + arg for arg, default in args_defaults: if default is NO_DEFAULT: signature.append(name_as_arg(arg)) else: default_name = default_name_template % defaults_seen exec_globals[default_name] = default signature.append('='.join([name_as_arg(arg), default_name])) defaults_seen += 1 if arg in processors: procname = mangle('_processor_' + arg) exec_globals[procname] = processors[arg] assignments.append(make_processor_assignment(arg, procname)) call_args.append(name_as_arg(arg)) exec_str = dedent("""\ @wraps({wrapped_funcname}) def {func_name}({signature}): {assignments} return {wrapped_funcname}({call_args}) """).format( func_name=func.__name__, signature=', '.join(signature), assignments='\n '.join(assignments), wrapped_funcname=mangled_funcname, call_args=', '.join(call_args), ) compiled = compile( exec_str, func.__code__.co_filename, mode='exec', ) exec_locals = {} exec(compiled, exec_globals, exec_locals) new_func = exec_locals[func.__name__] code = new_func.__code__ args = { attr: getattr(code, attr) for attr in dir(code) if attr.startswith('co_') } # Copy the firstlineno out of the underlying function so that exceptions # get raised with the correct traceback. # This also makes dynamic source inspection (like IPython `??` operator) # work as intended. try: # Try to get the pycode object from the underlying function. original_code = func.__code__ except AttributeError: try: # The underlying callable was not a function, try to grab the # `__func__.__code__` which exists on method objects. original_code = func.__func__.__code__ except AttributeError: # The underlying callable does not have a `__code__`. There is # nothing for us to correct. return new_func args['co_firstlineno'] = original_code.co_firstlineno new_func.__code__ = CodeType(*map(getitem(args), _code_argorder)) return new_func
#coding=utf-8 import dis import opcode from types import CodeType """ CodeType( argcount, # integer kwonlyargcount, # integer nlocals, # integer stacksize, # integer flags, # integer codestring, # bytes consts, # tuple names, # tuple varnames, # tuple filename, # string name, # string firstlineno, # integer lnotab, # bytes freevars, # tuple cellvars # tuple ) """ def code(): return a + 1 dis.dis(code.__code__) co_code = code.__code__.co_code
def _make_constants( function, builtins=None, builtin_only=False, stoplist=(), constant_fold=True, verbose=False, ): """Return the given ``function`` with its constants folded.""" if verbose == 2: logging.info("# OPTIMISING : %s.%s", function.__module__, function.__name__) co = function.func_code newcode = map(ord, co.co_code) newconsts = list(co.co_consts) names = co.co_names codelen = len(newcode) if builtins: env = vars(builtins).copy() else: env = vars(__builtin__).copy() if builtin_only: stoplist = dict.fromkeys(stoplist) stoplist.update(function.func_globals) else: env.update(function.func_globals) # first pass converts global lookups into constants i = 0 while i < codelen: opcode = newcode[i] if opcode in (EXTENDED_ARG, STORE_GLOBAL): # for simplicity, only optimise common cases logging.info("\n\nFound opcode\n\n") return function if opcode == LOAD_GLOBAL: oparg = newcode[i + 1] + (newcode[i + 2] << 8) name = co.co_names[oparg] if name in env and name not in stoplist: value = env[name] for pos, v in enumerate(newconsts): if v is value: break else: pos = len(newconsts) newconsts.append(value) newcode[i] = LOAD_CONST newcode[i + 1] = pos & 0xFF newcode[i + 2] = pos >> 8 if verbose: logging.info("%s --> %s", name, value) i += 1 if opcode >= HAVE_ARGUMENT: i += 2 # second pass folds tuples of constants and constant attribute lookups if constant_fold: i = 0 while i < codelen: newtuple = [] while newcode[i] == LOAD_CONST: oparg = newcode[i + 1] + (newcode[i + 2] << 8) newtuple.append(newconsts[oparg]) i += 3 opcode = newcode[i] if not newtuple: i += 1 if opcode >= HAVE_ARGUMENT: i += 2 continue if opcode == LOAD_ATTR: obj = newtuple[-1] oparg = newcode[i + 1] + (newcode[i + 2] << 8) name = names[oparg] try: value = getattr(obj, name) except AttributeError: continue deletions = 1 elif opcode == BUILD_TUPLE: oparg = newcode[i + 1] + (newcode[i + 2] << 8) if oparg != len(newtuple): continue deletions = len(newtuple) value = tuple(newtuple) else: continue reljump = deletions * 3 newcode[i - reljump] = JUMP_FORWARD newcode[i - reljump + 1] = (reljump - 3) & 0xFF newcode[i - reljump + 2] = (reljump - 3) >> 8 n = len(newconsts) newconsts.append(value) newcode[i] = LOAD_CONST newcode[i + 1] = n & 0xFF newcode[i + 2] = n >> 8 i += 3 if verbose: logging.info("New folded constant: %s", value) codestr = ''.join(map(chr, newcode)) codeobj = CodeType(co.co_argcount, co.co_nlocals, co.co_stacksize, co.co_flags, codestr, tuple(newconsts), co.co_names, co.co_varnames, co.co_filename, co.co_name, co.co_firstlineno, co.co_lnotab, co.co_freevars, co.co_cellvars) return FunctionType(codeobj, function.func_globals, function.func_name, function.func_defaults, function.func_closure)
import marshal from types import CodeType c = CodeType( 0, 0, 3, 30, 64, bytes([ 100, 0, 125, 0, 100, 1, 125, 1, 100, 2, 100, 3, 132, 0, 125, 2, 124, 0, 100, 0, 107, 4, 114, 32, 116, 0, 100, 0, 131, 1, 1, 0, 124, 1, 100, 0, 107, 4, 114, 48, 116, 0, 100, 5, 131, 1, 1, 0, 116, 0, 124, 2, 124, 0, 124, 1, 131, 2, 124, 2, 100, 0, 100, 1, 131, 2, 23, 0, 131, 1, 1, 0, 100, 6, 83, 0 ]), (1, 2, CodeType( 2, 0, 2, 30, 64, bytes([124, 0, 124, 1, 23, 0, 124, 0, 124, 1, 23, 0, 20, 0, 83, 0]), (), (), ("a", "b"), "qwe.py", "sum", 0, b'\x00', (), ()), "sum", ">", 123, None), ("print", ), ("a", "b", "sum"), "qwe.py", "<module>", 0, b'\x00', (), ()) x = marshal.dumps(c) f = open('res.pyc', "wb+") f.write(b'\x33\x0D\x0D\x0A') f.write(b'\x8F\xB1\x21\x59') f.write(b'\x69\x00\x00\x00') f.write(x)
# noinspection PyProtectedMember from bytecode import Instr, BasicBlock from types import CodeType from typing import List from pyChecco.configuration import Configuration from pyChecco.instrumentation.instruction_instrumentation import InstructionInstrumentation from pyChecco.execution.executiontrace import ExecutionTrace from pyChecco.execution.executiontracer import ExecutionTracer from pyChecco.execution.testexecution.testexecutor import TestExecutor from pyChecco.slicer.instruction import UniqueInstruction from pyChecco.slicer.dynamic_slicer import DynamicSlicer, SlicingCriterion, DynamicSlice from pyChecco.utils.pyc import Pyc dummy_code_object = CodeType(0, 0, 0, 0, 0, 0, bytes(), (), (), (), "", "", 0, bytes()) def compare(dynamic_slice: List[UniqueInstruction], expected_slice: List[Instr]): expected_copy = expected_slice.copy() slice_copy = dynamic_slice.copy() for unique_instr in dynamic_slice: if isinstance(unique_instr.arg, BasicBlock) or isinstance(unique_instr.arg, CodeType) or \ isinstance(unique_instr.arg, tuple): # Don't distinguish arguments for basic blocks, code objects and tuples jump_instr = _contains_name_argtype(expected_copy, unique_instr) try: expected_copy.remove(jump_instr) slice_copy.remove(unique_instr) except ValueError:
def wrap_algorithm(algo, name=None): """ Return the function representation of an Algorithm derived class. NOTE: If algo.apply has ``*args`` and ``**kwargs`` parameters, this doesn't work. """ if not issubclass(algo, Algorithm): raise ValueError('Class must be an instance of `nd.Algorithm`.') def _wrapper(*args, **kwargs): # First, apply the arguments to .apply(): apply_kwargs = utils.extract_arguments(algo.apply, args, kwargs) init_args = apply_kwargs.pop('args', ()) init_kwargs = apply_kwargs.pop('kwargs', {}) return algo(*init_args, **init_kwargs).apply(**apply_kwargs) # Override function module _wrapper.__module__ = algo.__module__ # Override position of source code to fix source code links in # documentation. This is probably a bad idea. code = _wrapper.__code__ new_filename = inspect.getfile(algo) caller = inspect.getframeinfo(inspect.stack()[1][0]) new_firstlineno = caller.lineno if PY_VERSION >= (3, 8): new_code = code.replace( co_filename=new_filename, co_firstlineno=new_firstlineno ) else: new_code = CodeType( code.co_argcount, code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize, code.co_flags, code.co_code, code.co_consts, code.co_names, code.co_varnames, new_filename, code.co_name, new_firstlineno, code.co_lnotab, code.co_freevars, code.co_cellvars ) _wrapper.__code__ = new_code # Override function name if name is not None: _wrapper.__name__ = name # Override signature sig_init = inspect.signature(algo.__init__) sig_apply = inspect.signature(algo.apply) parameters = tuple(sig_apply.parameters.values())[1:] + \ tuple(sig_init.parameters.values())[1:] # Sort parameters: # 1) put variadic parameters last # 2) put arguments without defaults first parameters = sorted( parameters, key=lambda p: (p.kind, p.default is not inspect._empty) ) new_parameters = [] for p in parameters: if p not in new_parameters: new_parameters.append(p) sig = sig_init.replace(parameters=new_parameters) _wrapper.__signature__ = sig # Override docstring link = ':class:`{}.{}`'.format(algo.__module__, algo.__name__) doc = utils.parse_docstring(algo.__doc__) doc[None].insert(0, "Wrapper for {}.".format(link)) doc[None].insert(1, "") if algo.apply.__doc__ is not None: apply_doc = utils.parse_docstring(algo.apply.__doc__) if 'Parameters' in apply_doc: if 'Parameters' not in doc: doc['Parameters'] = apply_doc['Parameters'] else: doc['Parameters'] = apply_doc['Parameters'] + doc['Parameters'] if 'Returns' in apply_doc: doc['Returns'] = apply_doc['Returns'] _wrapper.__doc__ = utils.assemble_docstring(doc, sig=sig) return _wrapper