| | import dis |
| | from typing import List, Tuple, Union, Optional, Callable, Any, Dict, Set |
| | from types import CodeType |
| |
|
| |
|
| | def get_function_signature(code_obj: CodeType, |
| | overwite_fn_name: Optional[str] = None) -> str: |
| | |
| | |
| | |
| | normal_arg_count = code_obj.co_argcount + code_obj.co_kwonlyargcount |
| | arg_names = code_obj.co_varnames[:normal_arg_count] |
| | arg_names = [ |
| | x if not x.startswith(".") else x.replace( |
| | ".", "comp_arg_") for x in arg_names] |
| |
|
| | import inspect |
| | if code_obj.co_flags & inspect.CO_VARARGS: |
| | arg_names.append('*' + code_obj.co_varnames[normal_arg_count]) |
| | normal_arg_count += 1 |
| | if code_obj.co_flags & inspect.CO_VARKEYWORDS: |
| | arg_names.append('**' + code_obj.co_varnames[normal_arg_count]) |
| | normal_arg_count += 1 |
| | args_str = ', '.join(arg_names) |
| | fn_name = overwite_fn_name if overwite_fn_name is not None else code_obj.co_name |
| | header = f"def {fn_name}({args_str}):\n" |
| | return header |
| |
|
| |
|
| | def collect_all_code_objects(code: CodeType) -> List[CodeType]: |
| | code_objects = [code] |
| | for const in code.co_consts: |
| | if isinstance(const, type(code)): |
| | code_objects.extend(collect_all_code_objects(const)) |
| | return code_objects |
| |
|
| |
|
| | def safe_create_directory(path): |
| | |
| | import os |
| | try: |
| | os.makedirs(path, exist_ok=True) |
| | except OSError as e: |
| | if not os.path.isdir(path): |
| | raise |
| |
|
| |
|
| |
|
| | def get_code_owner(fn): |
| | """A callable object `fn` might have a __code__ attribute, which is a code object. |
| | However, `fn` might not be the owner of the code object. Only the code owner can change the code object. |
| | This function returns the owner of the code object. |
| | An example: |
| | class A: |
| | def func(self): |
| | return 1 |
| | a = A() |
| | `a.func.__code__` is read-only. `A.func.__code__` is writable. |
| | We can change the code object via `a.func.__func__.__code__`. |
| | """ |
| | import functools |
| | while True: |
| | if hasattr(fn, "__func__"): |
| | |
| | fn = fn.__func__ |
| | elif hasattr(fn, "__wrapped__"): |
| | |
| | fn = fn.__wrapped__ |
| | elif isinstance(fn, functools.partial): |
| | |
| | fn = fn.func |
| | elif hasattr(fn, "__call__") and hasattr(fn.__call__, "__func__"): |
| | |
| | fn = fn.__call__.__func__ |
| | else: |
| | break |
| | return fn |
| |
|
| |
|
| |
|
| | def decompile_ensure(fn: CodeType, overwite_fn_name=None): |
| | import depyf |
| | from depyf.decompiler import DecompilationError |
| | try: |
| | decompiled_source_code = depyf.Decompiler( |
| | fn).decompile(overwite_fn_name=overwite_fn_name) |
| | except DecompilationError as e: |
| | header = get_function_signature(fn, overwite_fn_name=overwite_fn_name) |
| | decompiled_source_code = header + " 'Failed to decompile.'\n" |
| | return decompiled_source_code |
| |
|