Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
dis.py1158 linesDownload Raw Back to Lib
1"""Disassembler of Python byte code into mnemonics."""2 3import sys4import types5import collections6import io7 8from opcode import *9from opcode import (10    __all__ as _opcodes_all,11    _cache_format,12    _inline_cache_entries,13    _nb_ops,14    _common_constants,15    _intrinsic_1_descs,16    _intrinsic_2_descs,17    _special_method_names,18    _specializations,19    _specialized_opmap,20)21 22from _opcode import get_executor23 24__all__ = ["code_info", "dis", "disassemble", "distb", "disco",25           "findlinestarts", "findlabels", "show_code",26           "get_instructions", "Instruction", "Bytecode"] + _opcodes_all27del _opcodes_all28 29_have_code = (types.MethodType, types.FunctionType, types.CodeType,30              classmethod, staticmethod, type)31 32CONVERT_VALUE = opmap['CONVERT_VALUE']33 34SET_FUNCTION_ATTRIBUTE = opmap['SET_FUNCTION_ATTRIBUTE']35FUNCTION_ATTR_FLAGS = ('defaults', 'kwdefaults', 'annotations', 'closure', 'annotate')36 37ENTER_EXECUTOR = opmap['ENTER_EXECUTOR']38LOAD_GLOBAL = opmap['LOAD_GLOBAL']39LOAD_SMALL_INT = opmap['LOAD_SMALL_INT']40BINARY_OP = opmap['BINARY_OP']41JUMP_BACKWARD = opmap['JUMP_BACKWARD']42FOR_ITER = opmap['FOR_ITER']43SEND = opmap['SEND']44LOAD_ATTR = opmap['LOAD_ATTR']45LOAD_SUPER_ATTR = opmap['LOAD_SUPER_ATTR']46CALL_INTRINSIC_1 = opmap['CALL_INTRINSIC_1']47CALL_INTRINSIC_2 = opmap['CALL_INTRINSIC_2']48LOAD_COMMON_CONSTANT = opmap['LOAD_COMMON_CONSTANT']49LOAD_SPECIAL = opmap['LOAD_SPECIAL']50LOAD_FAST_LOAD_FAST = opmap['LOAD_FAST_LOAD_FAST']51LOAD_FAST_BORROW_LOAD_FAST_BORROW = opmap['LOAD_FAST_BORROW_LOAD_FAST_BORROW']52STORE_FAST_LOAD_FAST = opmap['STORE_FAST_LOAD_FAST']53STORE_FAST_STORE_FAST = opmap['STORE_FAST_STORE_FAST']54IS_OP = opmap['IS_OP']55CONTAINS_OP = opmap['CONTAINS_OP']56END_ASYNC_FOR = opmap['END_ASYNC_FOR']57 58CACHE = opmap["CACHE"]59 60_all_opname = list(opname)61_all_opmap = dict(opmap)62for name, op in _specialized_opmap.items():63    # fill opname and opmap64    assert op < len(_all_opname)65    _all_opname[op] = name66    _all_opmap[name] = op67 68deoptmap = {69    specialized: base for base, family in _specializations.items() for specialized in family70}71 72def _try_compile(source, name):73    """Attempts to compile the given source, first as an expression and74       then as a statement if the first approach fails.75 76       Utility function to accept strings in functions that otherwise77       expect code objects78    """79    try:80        return compile(source, name, 'eval')81    except SyntaxError:82        pass83    return compile(source, name, 'exec')84 85def dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False,86        show_offsets=False, show_positions=False):87    """Disassemble classes, methods, functions, and other compiled objects.88 89    With no argument, disassemble the last traceback.90 91    Compiled objects currently include generator objects, async generator92    objects, and coroutine objects, all of which store their code object93    in a special attribute.94    """95    if x is None:96        distb(file=file, show_caches=show_caches, adaptive=adaptive,97              show_offsets=show_offsets, show_positions=show_positions)98        return99    # Extract functions from methods.100    if hasattr(x, '__func__'):101        x = x.__func__102    # Extract compiled code objects from...103    if hasattr(x, '__code__'):  # ...a function, or104        x = x.__code__105    elif hasattr(x, 'gi_code'):  #...a generator object, or106        x = x.gi_code107    elif hasattr(x, 'ag_code'):  #...an asynchronous generator object, or108        x = x.ag_code109    elif hasattr(x, 'cr_code'):  #...a coroutine.110        x = x.cr_code111    # Perform the disassembly.112    if hasattr(x, '__dict__'):  # Class or module113        items = sorted(x.__dict__.items())114        for name, x1 in items:115            if isinstance(x1, _have_code):116                print("Disassembly of %s:" % name, file=file)117                try:118                    dis(x1, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions)119                except TypeError as msg:120                    print("Sorry:", msg, file=file)121                print(file=file)122    elif hasattr(x, 'co_code'): # Code object123        _disassemble_recursive(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions)124    elif isinstance(x, (bytes, bytearray)): # Raw bytecode125        labels_map = _make_labels_map(x)126        label_width = 4 + len(str(len(labels_map)))127        formatter = Formatter(file=file,128                              offset_width=len(str(max(len(x) - 2, 9999))) if show_offsets else 0,129                              label_width=label_width,130                              show_caches=show_caches)131        arg_resolver = ArgResolver(labels_map=labels_map)132        _disassemble_bytes(x, arg_resolver=arg_resolver, formatter=formatter)133    elif isinstance(x, str):    # Source code134        _disassemble_str(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions)135    else:136        raise TypeError("don't know how to disassemble %s objects" %137                        type(x).__name__)138 139def distb(tb=None, *, file=None, show_caches=False, adaptive=False, show_offsets=False, show_positions=False):140    """Disassemble a traceback (default: last traceback)."""141    if tb is None:142        try:143            if hasattr(sys, 'last_exc'):144                tb = sys.last_exc.__traceback__145            else:146                tb = sys.last_traceback147        except AttributeError:148            raise RuntimeError("no last traceback to disassemble") from None149        while tb.tb_next: tb = tb.tb_next150    disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions)151 152# The inspect module interrogates this dictionary to build its153# list of CO_* constants. It is also used by pretty_flags to154# turn the co_flags field into a human readable list.155COMPILER_FLAG_NAMES = {156            1: "OPTIMIZED",157            2: "NEWLOCALS",158            4: "VARARGS",159            8: "VARKEYWORDS",160           16: "NESTED",161           32: "GENERATOR",162           64: "NOFREE",163          128: "COROUTINE",164          256: "ITERABLE_COROUTINE",165          512: "ASYNC_GENERATOR",166    0x4000000: "HAS_DOCSTRING",167    0x8000000: "METHOD",168}169 170def pretty_flags(flags):171    """Return pretty representation of code flags."""172    names = []173    for i in range(32):174        flag = 1<<i175        if flags & flag:176            names.append(COMPILER_FLAG_NAMES.get(flag, hex(flag)))177            flags ^= flag178            if not flags:179                break180    else:181        names.append(hex(flags))182    return ", ".join(names)183 184class _Unknown:185    def __repr__(self):186        return "<unknown>"187 188# Sentinel to represent values that cannot be calculated189UNKNOWN = _Unknown()190 191def _get_code_object(x):192    """Helper to handle methods, compiled or raw code objects, and strings."""193    # Extract functions from methods.194    if hasattr(x, '__func__'):195        x = x.__func__196    # Extract compiled code objects from...197    if hasattr(x, '__code__'):  # ...a function, or198        x = x.__code__199    elif hasattr(x, 'gi_code'):  #...a generator object, or200        x = x.gi_code201    elif hasattr(x, 'ag_code'):  #...an asynchronous generator object, or202        x = x.ag_code203    elif hasattr(x, 'cr_code'):  #...a coroutine.204        x = x.cr_code205    # Handle source code.206    if isinstance(x, str):207        x = _try_compile(x, "<disassembly>")208    # By now, if we don't have a code object, we can't disassemble x.209    if hasattr(x, 'co_code'):210        return x211    raise TypeError("don't know how to disassemble %s objects" %212                    type(x).__name__)213 214def _deoptop(op):215    name = _all_opname[op]216    return _all_opmap[deoptmap[name]] if name in deoptmap else op217 218def _get_code_array(co, adaptive):219    if adaptive:220        code = co._co_code_adaptive221        res = []222        found = False223        for i in range(0, len(code), 2):224            op, arg = code[i], code[i+1]225            if op == ENTER_EXECUTOR:226                try:227                    ex = get_executor(co, i)228                except (ValueError, RuntimeError):229                    ex = None230 231                if ex:232                    op, arg = ex.get_opcode(), ex.get_oparg()233                    found = True234 235            res.append(op.to_bytes())236            res.append(arg.to_bytes())237        return code if not found else b''.join(res)238    else:239        return co.co_code240 241def code_info(x):242    """Formatted details of methods, functions, or code."""243    return _format_code_info(_get_code_object(x))244 245def _format_code_info(co):246    lines = []247    lines.append("Name:              %s" % co.co_name)248    lines.append("Filename:          %s" % co.co_filename)249    lines.append("Argument count:    %s" % co.co_argcount)250    lines.append("Positional-only arguments: %s" % co.co_posonlyargcount)251    lines.append("Kw-only arguments: %s" % co.co_kwonlyargcount)252    lines.append("Number of locals:  %s" % co.co_nlocals)253    lines.append("Stack size:        %s" % co.co_stacksize)254    lines.append("Flags:             %s" % pretty_flags(co.co_flags))255    if co.co_consts:256        lines.append("Constants:")257        for i_c in enumerate(co.co_consts):258            lines.append("%4d: %r" % i_c)259    if co.co_names:260        lines.append("Names:")261        for i_n in enumerate(co.co_names):262            lines.append("%4d: %s" % i_n)263    if co.co_varnames:264        lines.append("Variable names:")265        for i_n in enumerate(co.co_varnames):266            lines.append("%4d: %s" % i_n)267    if co.co_freevars:268        lines.append("Free variables:")269        for i_n in enumerate(co.co_freevars):270            lines.append("%4d: %s" % i_n)271    if co.co_cellvars:272        lines.append("Cell variables:")273        for i_n in enumerate(co.co_cellvars):274            lines.append("%4d: %s" % i_n)275    return "\n".join(lines)276 277def show_code(co, *, file=None):278    """Print details of methods, functions, or code to *file*.279 280    If *file* is not provided, the output is printed on stdout.281    """282    print(code_info(co), file=file)283 284Positions = collections.namedtuple(285    'Positions',286    [287        'lineno',288        'end_lineno',289        'col_offset',290        'end_col_offset',291    ],292    defaults=[None] * 4293)294 295_Instruction = collections.namedtuple(296    "_Instruction",297    [298        'opname',299        'opcode',300        'arg',301        'argval',302        'argrepr',303        'offset',304        'start_offset',305        'starts_line',306        'line_number',307        'label',308        'positions',309        'cache_info',310    ],311    defaults=[None, None, None]312)313 314_Instruction.opname.__doc__ = "Human readable name for operation"315_Instruction.opcode.__doc__ = "Numeric code for operation"316_Instruction.arg.__doc__ = "Numeric argument to operation (if any), otherwise None"317_Instruction.argval.__doc__ = "Resolved arg value (if known), otherwise same as arg"318_Instruction.argrepr.__doc__ = "Human readable description of operation argument"319_Instruction.offset.__doc__ = "Start index of operation within bytecode sequence"320_Instruction.start_offset.__doc__ = (321    "Start index of operation within bytecode sequence, including extended args if present; "322    "otherwise equal to Instruction.offset"323)324_Instruction.starts_line.__doc__ = "True if this opcode starts a source line, otherwise False"325_Instruction.line_number.__doc__ = "source line number associated with this opcode (if any), otherwise None"326_Instruction.label.__doc__ = "A label (int > 0) if this instruction is a jump target, otherwise None"327_Instruction.positions.__doc__ = "dis.Positions object holding the span of source code covered by this instruction"328_Instruction.cache_info.__doc__ = "list of (name, size, data), one for each cache entry of the instruction"329 330_ExceptionTableEntryBase = collections.namedtuple("_ExceptionTableEntryBase",331    "start end target depth lasti")332 333class _ExceptionTableEntry(_ExceptionTableEntryBase):334    pass335 336_OPNAME_WIDTH = 20337_OPARG_WIDTH = 5338 339def _get_cache_size(opname):340    return _inline_cache_entries.get(opname, 0)341 342def _get_jump_target(op, arg, offset):343    """Gets the bytecode offset of the jump target if this is a jump instruction.344 345    Otherwise return None.346    """347    deop = _deoptop(op)348    caches = _get_cache_size(_all_opname[deop])349    if deop in hasjrel:350        if _is_backward_jump(deop):351            arg = -arg352        target = offset + 2 + arg*2353        target += 2 * caches354    elif deop in hasjabs:355        target = arg*2356    else:357        target = None358    return target359 360class Instruction(_Instruction):361    """Details for a bytecode operation.362 363       Defined fields:364         opname - human readable name for operation365         opcode - numeric code for operation366         arg - numeric argument to operation (if any), otherwise None367         argval - resolved arg value (if known), otherwise same as arg368         argrepr - human readable description of operation argument369         offset - start index of operation within bytecode sequence370         start_offset - start index of operation within bytecode sequence including extended args if present;371                        otherwise equal to Instruction.offset372         starts_line - True if this opcode starts a source line, otherwise False373         line_number - source line number associated with this opcode (if any), otherwise None374         label - A label if this instruction is a jump target, otherwise None375         positions - Optional dis.Positions object holding the span of source code376                     covered by this instruction377         cache_info - information about the format and content of the instruction's cache378                        entries (if any)379    """380 381    @staticmethod382    def make(383        opname, arg, argval, argrepr, offset, start_offset, starts_line,384        line_number, label=None, positions=None, cache_info=None385    ):386        return Instruction(opname, _all_opmap[opname], arg, argval, argrepr, offset,387                           start_offset, starts_line, line_number, label, positions, cache_info)388 389    @property390    def oparg(self):391        """Alias for Instruction.arg."""392        return self.arg393 394    @property395    def baseopcode(self):396        """Numeric code for the base operation if operation is specialized.397 398        Otherwise equal to Instruction.opcode.399        """400        return _deoptop(self.opcode)401 402    @property403    def baseopname(self):404        """Human readable name for the base operation if operation is specialized.405 406        Otherwise equal to Instruction.opname.407        """408        return opname[self.baseopcode]409 410    @property411    def cache_offset(self):412        """Start index of the cache entries following the operation."""413        return self.offset + 2414 415    @property416    def end_offset(self):417        """End index of the cache entries following the operation."""418        return self.cache_offset + _get_cache_size(_all_opname[self.opcode])*2419 420    @property421    def jump_target(self):422        """Bytecode index of the jump target if this is a jump operation.423 424        Otherwise return None.425        """426        return _get_jump_target(self.opcode, self.arg, self.offset)427 428    @property429    def is_jump_target(self):430        """True if other code jumps to here, otherwise False"""431        return self.label is not None432 433    def __str__(self):434        output = io.StringIO()435        formatter = Formatter(file=output)436        formatter.print_instruction(self, False)437        return output.getvalue()438 439 440class Formatter:441 442    def __init__(self, file=None, lineno_width=0, offset_width=0, label_width=0,443                 line_offset=0, show_caches=False, *, show_positions=False):444        """Create a Formatter445 446        *file* where to write the output447        *lineno_width* sets the width of the source location field (0 omits it).448        Should be large enough for a line number or full positions (depending449        on the value of *show_positions*).450        *offset_width* sets the width of the instruction offset field451        *label_width* sets the width of the label field452        *show_caches* is a boolean indicating whether to display cache lines453        *show_positions* is a boolean indicating whether full positions should454        be reported instead of only the line numbers.455        """456        self.file = file457        self.lineno_width = lineno_width458        self.offset_width = offset_width459        self.label_width = label_width460        self.show_caches = show_caches461        self.show_positions = show_positions462 463    def print_instruction(self, instr, mark_as_current=False):464        self.print_instruction_line(instr, mark_as_current)465        if self.show_caches and instr.cache_info:466            offset = instr.offset467            for name, size, data in instr.cache_info:468                for i in range(size):469                    offset += 2470                    # Only show the fancy argrepr for a CACHE instruction when it's471                    # the first entry for a particular cache value:472                    if i == 0:473                        argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}"474                    else:475                        argrepr = ""476                    self.print_instruction_line(477                        Instruction("CACHE", CACHE, 0, None, argrepr, offset, offset,478                                    False, None, None, instr.positions),479                        False)480 481    def print_instruction_line(self, instr, mark_as_current):482        """Format instruction details for inclusion in disassembly output."""483        lineno_width = self.lineno_width484        offset_width = self.offset_width485        label_width = self.label_width486 487        new_source_line = (lineno_width > 0 and488                           instr.starts_line and489                           instr.offset > 0)490        if new_source_line:491            print(file=self.file)492 493        fields = []494        # Column: Source code locations information495        if lineno_width:496            if self.show_positions:497                # reporting positions instead of just line numbers498                if instr_positions := instr.positions:499                    if all(p is None for p in instr_positions):500                        positions_str = _NO_LINENO501                    else:502                        ps = tuple('?' if p is None else p for p in instr_positions)503                        positions_str = f"{ps[0]}:{ps[2]}-{ps[1]}:{ps[3]}"504                    fields.append(f'{positions_str:{lineno_width}}')505                else:506                    fields.append(' ' * lineno_width)507            else:508                if instr.starts_line:509                    lineno_fmt = "%%%dd" if instr.line_number is not None else "%%%ds"510                    lineno_fmt = lineno_fmt % lineno_width511                    lineno = _NO_LINENO if instr.line_number is None else instr.line_number512                    fields.append(lineno_fmt % lineno)513                else:514                    fields.append(' ' * lineno_width)515        # Column: Label516        if instr.label is not None:517            lbl = f"L{instr.label}:"518            fields.append(f"{lbl:>{label_width}}")519        else:520            fields.append(' ' * label_width)521        # Column: Instruction offset from start of code sequence522        if offset_width > 0:523            fields.append(f"{repr(instr.offset):>{offset_width}}  ")524        # Column: Current instruction indicator525        if mark_as_current:526            fields.append('-->')527        else:528            fields.append('   ')529        # Column: Opcode name530        fields.append(instr.opname.ljust(_OPNAME_WIDTH))531        # Column: Opcode argument532        if instr.arg is not None:533            arg = repr(instr.arg)534            # If opname is longer than _OPNAME_WIDTH, we allow it to overflow into535            # the space reserved for oparg. This results in fewer misaligned opargs536            # in the disassembly output.537            opname_excess = max(0, len(instr.opname) - _OPNAME_WIDTH)538            fields.append(repr(instr.arg).rjust(_OPARG_WIDTH - opname_excess))539            # Column: Opcode argument details540            if instr.argrepr:541                fields.append('(' + instr.argrepr + ')')542        print(' '.join(fields).rstrip(), file=self.file)543 544    def print_exception_table(self, exception_entries):545        file = self.file546        if exception_entries:547            print("ExceptionTable:", file=file)548            for entry in exception_entries:549                lasti = " lasti" if entry.lasti else ""550                start = entry.start_label551                end = entry.end_label552                target = entry.target_label553                print(f"  L{start} to L{end} -> L{target} [{entry.depth}]{lasti}", file=file)554 555 556class ArgResolver:557    def __init__(self, co_consts=None, names=None, varname_from_oparg=None, labels_map=None):558        self.co_consts = co_consts559        self.names = names560        self.varname_from_oparg = varname_from_oparg561        self.labels_map = labels_map or {}562 563    def offset_from_jump_arg(self, op, arg, offset):564        deop = _deoptop(op)565        if deop in hasjabs:566            return arg * 2567        elif deop in hasjrel:568            signed_arg = -arg if _is_backward_jump(deop) else arg569            argval = offset + 2 + signed_arg*2570            caches = _get_cache_size(_all_opname[deop])571            argval += 2 * caches572            return argval573        return None574 575    def get_label_for_offset(self, offset):576        return self.labels_map.get(offset, None)577 578    def get_argval_argrepr(self, op, arg, offset):579        get_name = None if self.names is None else self.names.__getitem__580        argval = None581        argrepr = ''582        deop = _deoptop(op)583        if arg is not None:584            #  Set argval to the dereferenced value of the argument when585            #  available, and argrepr to the string representation of argval.586            #    _disassemble_bytes needs the string repr of the587            #    raw name index for LOAD_GLOBAL, LOAD_CONST, etc.588            argval = arg589            if deop in hasconst:590                argval, argrepr = _get_const_info(deop, arg, self.co_consts)591            elif deop in hasname:592                if deop == LOAD_GLOBAL:593                    argval, argrepr = _get_name_info(arg//2, get_name)594                    if (arg & 1) and argrepr:595                        argrepr = f"{argrepr} + NULL"596                elif deop == LOAD_ATTR:597                    argval, argrepr = _get_name_info(arg//2, get_name)598                    if (arg & 1) and argrepr:599                        argrepr = f"{argrepr} + NULL|self"600                elif deop == LOAD_SUPER_ATTR:601                    argval, argrepr = _get_name_info(arg//4, get_name)602                    if (arg & 1) and argrepr:603                        argrepr = f"{argrepr} + NULL|self"604                else:605                    argval, argrepr = _get_name_info(arg, get_name)606            elif deop in hasjump or deop in hasexc:607                argval = self.offset_from_jump_arg(op, arg, offset)608                lbl = self.get_label_for_offset(argval)609                assert lbl is not None610                preposition = "from" if deop == END_ASYNC_FOR else "to"611                argrepr = f"{preposition} L{lbl}"612            elif deop in (LOAD_FAST_LOAD_FAST, LOAD_FAST_BORROW_LOAD_FAST_BORROW, STORE_FAST_LOAD_FAST, STORE_FAST_STORE_FAST):613                arg1 = arg >> 4614                arg2 = arg & 15615                val1, argrepr1 = _get_name_info(arg1, self.varname_from_oparg)616                val2, argrepr2 = _get_name_info(arg2, self.varname_from_oparg)617                argrepr = argrepr1 + ", " + argrepr2618                argval = val1, val2619            elif deop in haslocal or deop in hasfree:620                argval, argrepr = _get_name_info(arg, self.varname_from_oparg)621            elif deop in hascompare:622                argval = cmp_op[arg >> 5]623                argrepr = argval624                if arg & 16:625                    argrepr = f"bool({argrepr})"626            elif deop == CONVERT_VALUE:627                argval = (None, str, repr, ascii)[arg]628                argrepr = ('', 'str', 'repr', 'ascii')[arg]629            elif deop == SET_FUNCTION_ATTRIBUTE:630                argrepr = ', '.join(s for i, s in enumerate(FUNCTION_ATTR_FLAGS)631                                    if arg & (1<<i))632            elif deop == BINARY_OP:633                _, argrepr = _nb_ops[arg]634            elif deop == CALL_INTRINSIC_1:635                argrepr = _intrinsic_1_descs[arg]636            elif deop == CALL_INTRINSIC_2:637                argrepr = _intrinsic_2_descs[arg]638            elif deop == LOAD_COMMON_CONSTANT:639                obj = _common_constants[arg]640                if isinstance(obj, type):641                    argrepr = obj.__name__642                else:643                    argrepr = repr(obj)644            elif deop == LOAD_SPECIAL:645                argrepr = _special_method_names[arg]646            elif deop == IS_OP:647                argrepr = 'is not' if argval else 'is'648            elif deop == CONTAINS_OP:649                argrepr = 'not in' if argval else 'in'650        return argval, argrepr651 652def get_instructions(x, *, first_line=None, show_caches=None, adaptive=False):653    """Iterator for the opcodes in methods, functions or code654 655    Generates a series of Instruction named tuples giving the details of656    each operations in the supplied code.657 658    If *first_line* is not None, it indicates the line number that should659    be reported for the first source line in the disassembled code.660    Otherwise, the source line information (if any) is taken directly from661    the disassembled code object.662    """663    co = _get_code_object(x)664    linestarts = dict(findlinestarts(co))665    if first_line is not None:666        line_offset = first_line - co.co_firstlineno667    else:668        line_offset = 0669 670    original_code = co.co_code671    arg_resolver = ArgResolver(co_consts=co.co_consts,672                               names=co.co_names,673                               varname_from_oparg=co._varname_from_oparg,674                               labels_map=_make_labels_map(original_code))675    return _get_instructions_bytes(_get_code_array(co, adaptive),676                                   linestarts=linestarts,677                                   line_offset=line_offset,678                                   co_positions=co.co_positions(),679                                   original_code=original_code,680                                   arg_resolver=arg_resolver)681 682def _get_const_value(op, arg, co_consts):683    """Helper to get the value of the const in a hasconst op.684 685       Returns the dereferenced constant if this is possible.686       Otherwise (if it is a LOAD_CONST and co_consts is not687       provided) returns the dis.UNKNOWN sentinel.688    """689    assert op in hasconst or op == LOAD_SMALL_INT690 691    if op == LOAD_SMALL_INT:692        return arg693    argval = UNKNOWN694    if co_consts is not None:695        argval = co_consts[arg]696    return argval697 698def _get_const_info(op, arg, co_consts):699    """Helper to get optional details about const references700 701       Returns the dereferenced constant and its repr if the value702       can be calculated.703       Otherwise returns the sentinel value dis.UNKNOWN for the value704       and an empty string for its repr.705    """706    argval = _get_const_value(op, arg, co_consts)707    argrepr = repr(argval) if argval is not UNKNOWN else ''708    return argval, argrepr709 710def _get_name_info(name_index, get_name, **extrainfo):711    """Helper to get optional details about named references712 713       Returns the dereferenced name as both value and repr if the name714       list is defined.715       Otherwise returns the sentinel value dis.UNKNOWN for the value716       and an empty string for its repr.717    """718    if get_name is not None:719        argval = get_name(name_index, **extrainfo)720        return argval, argval721    else:722        return UNKNOWN, ''723 724def _parse_varint(iterator):725    b = next(iterator)726    val = b & 63727    while b&64:728        val <<= 6729        b = next(iterator)730        val |= b&63731    return val732 733def _parse_exception_table(code):734    iterator = iter(code.co_exceptiontable)735    entries = []736    try:737        while True:738            start = _parse_varint(iterator)*2739            length = _parse_varint(iterator)*2740            end = start + length741            target = _parse_varint(iterator)*2742            dl = _parse_varint(iterator)743            depth = dl >> 1744            lasti = bool(dl&1)745            entries.append(_ExceptionTableEntry(start, end, target, depth, lasti))746    except StopIteration:747        return entries748 749def _is_backward_jump(op):750    return opname[op] in ('JUMP_BACKWARD',751                          'JUMP_BACKWARD_NO_INTERRUPT',752                          'END_ASYNC_FOR') # Not really a jump, but it has a "target"753 754def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=None,755                            original_code=None, arg_resolver=None):756    """Iterate over the instructions in a bytecode string.757 758    Generates a sequence of Instruction namedtuples giving the details of each759    opcode.760 761    """762    # Use the basic, unadaptive code for finding labels and actually walking the763    # bytecode, since replacements like ENTER_EXECUTOR and INSTRUMENTED_* can764    # mess that logic up pretty badly:765    original_code = original_code or code766    co_positions = co_positions or iter(())767 768    starts_line = False769    local_line_number = None770    line_number = None771    for offset, start_offset, op, arg in _unpack_opargs(original_code):772        if linestarts is not None:773            starts_line = offset in linestarts774            if starts_line:775                local_line_number = linestarts[offset]776            if local_line_number is not None:777                line_number = local_line_number + line_offset778            else:779                line_number = None780        positions = Positions(*next(co_positions, ()))781        deop = _deoptop(op)782        op = code[offset]783 784        if arg_resolver:785            argval, argrepr = arg_resolver.get_argval_argrepr(op, arg, offset)786        else:787            argval, argrepr = arg, repr(arg)788 789        caches = _get_cache_size(_all_opname[deop])790        # Advance the co_positions iterator:791        for _ in range(caches):792            next(co_positions, ())793 794        if caches:795            cache_info = []796            cache_offset = offset797            for name, size in _cache_format[opname[deop]].items():798                data = code[cache_offset + 2: cache_offset + 2 + 2 * size]799                cache_offset += size * 2800                cache_info.append((name, size, data))801        else:802            cache_info = None803 804        label = arg_resolver.get_label_for_offset(offset) if arg_resolver else None805        yield Instruction(_all_opname[op], op, arg, argval, argrepr,806                          offset, start_offset, starts_line, line_number,807                          label, positions, cache_info)808 809 810def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False,811                show_offsets=False, show_positions=False):812    """Disassemble a code object."""813    linestarts = dict(findlinestarts(co))814    exception_entries = _parse_exception_table(co)815    if show_positions:816        lineno_width = _get_positions_width(co)817    else:818        lineno_width = _get_lineno_width(linestarts)819    labels_map = _make_labels_map(co.co_code, exception_entries=exception_entries)820    label_width = 4 + len(str(len(labels_map)))821    formatter = Formatter(file=file,822                          lineno_width=lineno_width,823                          offset_width=len(str(max(len(co.co_code) - 2, 9999))) if show_offsets else 0,824                          label_width=label_width,825                          show_caches=show_caches,826                          show_positions=show_positions)827    arg_resolver = ArgResolver(co_consts=co.co_consts,828                               names=co.co_names,829                               varname_from_oparg=co._varname_from_oparg,830                               labels_map=labels_map)831    _disassemble_bytes(_get_code_array(co, adaptive), lasti, linestarts,832                       exception_entries=exception_entries, co_positions=co.co_positions(),833                       original_code=co.co_code, arg_resolver=arg_resolver, formatter=formatter)834 835def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adaptive=False, show_offsets=False, show_positions=False):836    disassemble(co, file=file, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions)837    if depth is None or depth > 0:838        if depth is not None:839            depth = depth - 1840        for x in co.co_consts:841            if hasattr(x, 'co_code'):842                print(file=file)843                print("Disassembly of %r:" % (x,), file=file)844                _disassemble_recursive(845                    x, file=file, depth=depth, show_caches=show_caches,846                    adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions847                )848 849 850def _make_labels_map(original_code, exception_entries=()):851    jump_targets = set(findlabels(original_code))852    labels = set(jump_targets)853    for start, end, target, _, _ in exception_entries:854        labels.add(start)855        labels.add(end)856        labels.add(target)857    labels = sorted(labels)858    labels_map = {offset: i+1 for (i, offset) in enumerate(sorted(labels))}859    for e in exception_entries:860        e.start_label = labels_map[e.start]861        e.end_label = labels_map[e.end]862        e.target_label = labels_map[e.target]863    return labels_map864 865_NO_LINENO = '  --'866 867def _get_lineno_width(linestarts):868    if linestarts is None:869        return 0870    maxlineno = max(filter(None, linestarts.values()), default=-1)871    if maxlineno == -1:872        # Omit the line number column entirely if we have no line number info873        return 0874    lineno_width = max(3, len(str(maxlineno)))875    if lineno_width < len(_NO_LINENO) and None in linestarts.values():876        lineno_width = len(_NO_LINENO)877    return lineno_width878 879def _get_positions_width(code):880    # Positions are formatted as 'LINE:COL-ENDLINE:ENDCOL ' (note trailing space).881    # A missing component appears as '?', and when all components are None, we882    # render '_NO_LINENO'. thus the minimum width is 1 + len(_NO_LINENO).883    #884    # If all values are missing, positions are not printed (i.e. positions_width = 0).885    has_value = False886    values_width = 0887    for positions in code.co_positions():888        has_value |= any(isinstance(p, int) for p in positions)889        width = sum(1 if p is None else len(str(p)) for p in positions)890        values_width = max(width, values_width)891    if has_value:892        # 3 = number of separators in a normal format893        return 1 + max(len(_NO_LINENO), 3 + values_width)894    return 0895 896def _disassemble_bytes(code, lasti=-1, linestarts=None,897                       *, line_offset=0, exception_entries=(),898                       co_positions=None, original_code=None,899                       arg_resolver=None, formatter=None):900 901    assert formatter is not None902    assert arg_resolver is not None903 904    instrs = _get_instructions_bytes(code, linestarts=linestarts,905                                           line_offset=line_offset,906                                           co_positions=co_positions,907                                           original_code=original_code,908                                           arg_resolver=arg_resolver)909 910    print_instructions(instrs, exception_entries, formatter, lasti=lasti)911 912 913def print_instructions(instrs, exception_entries, formatter, lasti=-1):914    for instr in instrs:915        # Each CACHE takes 2 bytes916        is_current_instr = instr.offset <= lasti \917            <= instr.offset + 2 * _get_cache_size(_all_opname[_deoptop(instr.opcode)])918        formatter.print_instruction(instr, is_current_instr)919 920    formatter.print_exception_table(exception_entries)921 922def _disassemble_str(source, **kwargs):923    """Compile the source string, then disassemble the code object."""924    _disassemble_recursive(_try_compile(source, '<dis>'), **kwargs)925 926disco = disassemble                     # XXX For backwards compatibility927 928 929# Rely on C `int` being 32 bits for oparg930_INT_BITS = 32931# Value for c int when it overflows932_INT_OVERFLOW = 2 ** (_INT_BITS - 1)933 934def _unpack_opargs(code):935    extended_arg = 0936    extended_args_offset = 0  # Number of EXTENDED_ARG instructions preceding the current instruction937    caches = 0938    for i in range(0, len(code), 2):939        # Skip inline CACHE entries:940        if caches:941            caches -= 1942            continue943        op = code[i]944        deop = _deoptop(op)945        caches = _get_cache_size(_all_opname[deop])946        if deop in hasarg:947            arg = code[i+1] | extended_arg948            extended_arg = (arg << 8) if deop == EXTENDED_ARG else 0949            # The oparg is stored as a signed integer950            # If the value exceeds its upper limit, it will overflow and wrap951            # to a negative integer952            if extended_arg >= _INT_OVERFLOW:953                extended_arg -= 2 * _INT_OVERFLOW954        else:955            arg = None956            extended_arg = 0957        if deop == EXTENDED_ARG:958            extended_args_offset += 1959            yield (i, i, op, arg)960        else:961            start_offset = i - extended_args_offset*2962            yield (i, start_offset, op, arg)963            extended_args_offset = 0964 965def findlabels(code):966    """Detect all offsets in a byte code which are jump targets.967 968    Return the list of offsets.969 970    """971    labels = []972    for offset, _, op, arg in _unpack_opargs(code):973        if arg is not None:974            label = _get_jump_target(op, arg, offset)975            if label is None:976                continue977            if label not in labels:978                labels.append(label)979    return labels980 981def findlinestarts(code):982    """Find the offsets in a byte code which are start of lines in the source.983 984    Generate pairs (offset, lineno)985    lineno will be an integer or None the offset does not have a source line.986    """987 988    lastline = False # None is a valid line number989    for start, end, line in code.co_lines():990        if line is not lastline:991            lastline = line992            yield start, line993    return994 995def _find_imports(co):996    """Find import statements in the code997 998    Generate triplets (name, level, fromlist) where999    name is the imported module and level, fromlist are1000    the corresponding args to __import__.1001    """1002    IMPORT_NAME = opmap['IMPORT_NAME']1003 1004    consts = co.co_consts1005    names = co.co_names1006    opargs = [(op, arg) for _, _, op, arg in _unpack_opargs(co.co_code)1007                  if op != EXTENDED_ARG]1008    for i, (op, oparg) in enumerate(opargs):1009        if op == IMPORT_NAME and i >= 2:1010            from_op = opargs[i-1]1011            level_op = opargs[i-2]1012            if (from_op[0] in hasconst and1013                (level_op[0] in hasconst or level_op[0] == LOAD_SMALL_INT)):1014                level = _get_const_value(level_op[0], level_op[1], consts)1015                fromlist = _get_const_value(from_op[0], from_op[1], consts)1016                yield (names[oparg], level, fromlist)1017 1018def _find_store_names(co):1019    """Find names of variables which are written in the code1020 1021    Generate sequence of strings1022    """1023    STORE_OPS = {1024        opmap['STORE_NAME'],1025        opmap['STORE_GLOBAL']1026    }1027 1028    names = co.co_names1029    for _, _, op, arg in _unpack_opargs(co.co_code):1030        if op in STORE_OPS:1031            yield names[arg]1032 1033 1034class Bytecode:1035    """The bytecode operations of a piece of code1036 1037    Instantiate this with a function, method, other compiled object, string of1038    code, or a code object (as returned by compile()).1039 1040    Iterating over this yields the bytecode operations as Instruction instances.1041    """1042    def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False, adaptive=False, show_offsets=False, show_positions=False):1043        self.codeobj = co = _get_code_object(x)1044        if first_line is None:1045            self.first_line = co.co_firstlineno1046            self._line_offset = 01047        else:1048            self.first_line = first_line1049            self._line_offset = first_line - co.co_firstlineno1050        self._linestarts = dict(findlinestarts(co))1051        self._original_object = x1052        self.current_offset = current_offset1053        self.exception_entries = _parse_exception_table(co)1054        self.show_caches = show_caches1055        self.adaptive = adaptive1056        self.show_offsets = show_offsets1057        self.show_positions = show_positions1058 1059    def __iter__(self):1060        co = self.codeobj1061        original_code = co.co_code1062        labels_map = _make_labels_map(original_code, self.exception_entries)1063        arg_resolver = ArgResolver(co_consts=co.co_consts,1064                                   names=co.co_names,1065                                   varname_from_oparg=co._varname_from_oparg,1066                                   labels_map=labels_map)1067        return _get_instructions_bytes(_get_code_array(co, self.adaptive),1068                                       linestarts=self._linestarts,1069                                       line_offset=self._line_offset,1070                                       co_positions=co.co_positions(),1071                                       original_code=original_code,1072                                       arg_resolver=arg_resolver)1073 1074    def __repr__(self):1075        return "{}({!r})".format(self.__class__.__name__,1076                                 self._original_object)1077 1078    @classmethod1079    def from_traceback(cls, tb, *, show_caches=False, adaptive=False):1080        """ Construct a Bytecode from the given traceback """1081        while tb.tb_next:1082            tb = tb.tb_next1083        return cls(1084            tb.tb_frame.f_code, current_offset=tb.tb_lasti, show_caches=show_caches, adaptive=adaptive1085        )1086 1087    def info(self):1088        """Return formatted information about the code object."""1089        return _format_code_info(self.codeobj)1090 1091    def dis(self):1092        """Return a formatted view of the bytecode operations."""1093        co = self.codeobj1094        if self.current_offset is not None:1095            offset = self.current_offset1096        else:1097            offset = -11098        with io.StringIO() as output:1099            code = _get_code_array(co, self.adaptive)1100            offset_width = len(str(max(len(code) - 2, 9999))) if self.show_offsets else 01101            if self.show_positions:1102                lineno_width = _get_positions_width(co)1103            else:1104                lineno_width = _get_lineno_width(self._linestarts)1105            labels_map = _make_labels_map(co.co_code, self.exception_entries)1106            label_width = 4 + len(str(len(labels_map)))1107            formatter = Formatter(file=output,1108                                  lineno_width=lineno_width,1109                                  offset_width=offset_width,1110                                  label_width=label_width,1111                                  line_offset=self._line_offset,1112                                  show_caches=self.show_caches,1113                                  show_positions=self.show_positions)1114 1115            arg_resolver = ArgResolver(co_consts=co.co_consts,1116                                       names=co.co_names,1117                                       varname_from_oparg=co._varname_from_oparg,1118                                       labels_map=labels_map)1119            _disassemble_bytes(code,1120                               linestarts=self._linestarts,1121                               line_offset=self._line_offset,1122                               lasti=offset,1123                               exception_entries=self.exception_entries,1124                               co_positions=co.co_positions(),1125                               original_code=co.co_code,1126                               arg_resolver=arg_resolver,1127                               formatter=formatter)1128            return output.getvalue()1129 1130 1131def main(args=None):1132    import argparse1133 1134    parser = argparse.ArgumentParser(color=True)1135    parser.add_argument('-C', '--show-caches', action='store_true',1136                        help='show inline caches')1137    parser.add_argument('-O', '--show-offsets', action='store_true',1138                        help='show instruction offsets')1139    parser.add_argument('-P', '--show-positions', action='store_true',1140                        help='show instruction positions')1141    parser.add_argument('-S', '--specialized', action='store_true',1142                        help='show specialized bytecode')1143    parser.add_argument('infile', nargs='?', default='-')1144    args = parser.parse_args(args=args)1145    if args.infile == '-':1146        name = '<stdin>'1147        source = sys.stdin.buffer.read()1148    else:1149        name = args.infile1150        with open(args.infile, 'rb') as infile:1151            source = infile.read()1152    code = compile(source, name, "exec")1153    dis(code, show_caches=args.show_caches, adaptive=args.specialized,1154        show_offsets=args.show_offsets, show_positions=args.show_positions)1155 1156if __name__ == "__main__":1157    main()1158 
codekingpro/portable-devtools · Team Ai