@@ -80,7 +80,7 @@ def _try_compile(source, name):
80
80
return compile (source , name , 'exec' )
81
81
82
82
def dis (x = None , * , file = None , depth = None , show_caches = False , adaptive = False ,
83
- show_offsets = False ):
83
+ show_offsets = False , show_positions = False ):
84
84
"""Disassemble classes, methods, functions, and other compiled objects.
85
85
86
86
With no argument, disassemble the last traceback.
@@ -91,7 +91,7 @@ def dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False,
91
91
"""
92
92
if x is None :
93
93
distb (file = file , show_caches = show_caches , adaptive = adaptive ,
94
- show_offsets = show_offsets )
94
+ show_offsets = show_offsets , show_positions = show_positions )
95
95
return
96
96
# Extract functions from methods.
97
97
if hasattr (x , '__func__' ):
@@ -112,12 +112,12 @@ def dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False,
112
112
if isinstance (x1 , _have_code ):
113
113
print ("Disassembly of %s:" % name , file = file )
114
114
try :
115
- dis (x1 , file = file , depth = depth , show_caches = show_caches , adaptive = adaptive , show_offsets = show_offsets )
115
+ dis (x1 , file = file , depth = depth , show_caches = show_caches , adaptive = adaptive , show_offsets = show_offsets , show_positions = show_positions )
116
116
except TypeError as msg :
117
117
print ("Sorry:" , msg , file = file )
118
118
print (file = file )
119
119
elif hasattr (x , 'co_code' ): # Code object
120
- _disassemble_recursive (x , file = file , depth = depth , show_caches = show_caches , adaptive = adaptive , show_offsets = show_offsets )
120
+ _disassemble_recursive (x , file = file , depth = depth , show_caches = show_caches , adaptive = adaptive , show_offsets = show_offsets , show_positions = show_positions )
121
121
elif isinstance (x , (bytes , bytearray )): # Raw bytecode
122
122
labels_map = _make_labels_map (x )
123
123
label_width = 4 + len (str (len (labels_map )))
@@ -128,12 +128,12 @@ def dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False,
128
128
arg_resolver = ArgResolver (labels_map = labels_map )
129
129
_disassemble_bytes (x , arg_resolver = arg_resolver , formatter = formatter )
130
130
elif isinstance (x , str ): # Source code
131
- _disassemble_str (x , file = file , depth = depth , show_caches = show_caches , adaptive = adaptive , show_offsets = show_offsets )
131
+ _disassemble_str (x , file = file , depth = depth , show_caches = show_caches , adaptive = adaptive , show_offsets = show_offsets , show_positions = show_positions )
132
132
else :
133
133
raise TypeError ("don't know how to disassemble %s objects" %
134
134
type (x ).__name__ )
135
135
136
- def distb (tb = None , * , file = None , show_caches = False , adaptive = False , show_offsets = False ):
136
+ def distb (tb = None , * , file = None , show_caches = False , adaptive = False , show_offsets = False , show_positions = False ):
137
137
"""Disassemble a traceback (default: last traceback)."""
138
138
if tb is None :
139
139
try :
@@ -144,7 +144,7 @@ def distb(tb=None, *, file=None, show_caches=False, adaptive=False, show_offsets
144
144
except AttributeError :
145
145
raise RuntimeError ("no last traceback to disassemble" ) from None
146
146
while tb .tb_next : tb = tb .tb_next
147
- disassemble (tb .tb_frame .f_code , tb .tb_lasti , file = file , show_caches = show_caches , adaptive = adaptive , show_offsets = show_offsets )
147
+ 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 )
148
148
149
149
# The inspect module interrogates this dictionary to build its
150
150
# list of CO_* constants. It is also used by pretty_flags to
@@ -427,21 +427,25 @@ def __str__(self):
427
427
class Formatter :
428
428
429
429
def __init__ (self , file = None , lineno_width = 0 , offset_width = 0 , label_width = 0 ,
430
- line_offset = 0 , show_caches = False ):
430
+ line_offset = 0 , show_caches = False , * , show_positions = False ):
431
431
"""Create a Formatter
432
432
433
433
*file* where to write the output
434
- *lineno_width* sets the width of the line number field (0 omits it)
434
+ *lineno_width* sets the width of the source location field (0 omits it).
435
+ Should be large enough for a line number or full positions (depending
436
+ on the value of *show_positions*).
435
437
*offset_width* sets the width of the instruction offset field
436
438
*label_width* sets the width of the label field
437
439
*show_caches* is a boolean indicating whether to display cache lines
438
-
440
+ *show_positions* is a boolean indicating whether full positions should
441
+ be reported instead of only the line numbers.
439
442
"""
440
443
self .file = file
441
444
self .lineno_width = lineno_width
442
445
self .offset_width = offset_width
443
446
self .label_width = label_width
444
447
self .show_caches = show_caches
448
+ self .show_positions = show_positions
445
449
446
450
def print_instruction (self , instr , mark_as_current = False ):
447
451
self .print_instruction_line (instr , mark_as_current )
@@ -474,15 +478,27 @@ def print_instruction_line(self, instr, mark_as_current):
474
478
print (file = self .file )
475
479
476
480
fields = []
477
- # Column: Source code line number
481
+ # Column: Source code locations information
478
482
if lineno_width :
479
- if instr .starts_line :
480
- lineno_fmt = "%%%dd" if instr .line_number is not None else "%%%ds"
481
- lineno_fmt = lineno_fmt % lineno_width
482
- lineno = _NO_LINENO if instr .line_number is None else instr .line_number
483
- fields .append (lineno_fmt % lineno )
483
+ if self .show_positions :
484
+ # reporting positions instead of just line numbers
485
+ if instr_positions := instr .positions :
486
+ if all (p is None for p in instr_positions ):
487
+ positions_str = _NO_LINENO
488
+ else :
489
+ ps = tuple ('?' if p is None else p for p in instr_positions )
490
+ positions_str = f"{ ps [0 ]} :{ ps [2 ]} -{ ps [1 ]} :{ ps [3 ]} "
491
+ fields .append (f'{ positions_str :{lineno_width }} ' )
492
+ else :
493
+ fields .append (' ' * lineno_width )
484
494
else :
485
- fields .append (' ' * lineno_width )
495
+ if instr .starts_line :
496
+ lineno_fmt = "%%%dd" if instr .line_number is not None else "%%%ds"
497
+ lineno_fmt = lineno_fmt % lineno_width
498
+ lineno = _NO_LINENO if instr .line_number is None else instr .line_number
499
+ fields .append (lineno_fmt % lineno )
500
+ else :
501
+ fields .append (' ' * lineno_width )
486
502
# Column: Label
487
503
if instr .label is not None :
488
504
lbl = f"L{ instr .label } :"
@@ -769,17 +785,22 @@ def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=N
769
785
770
786
771
787
def disassemble (co , lasti = - 1 , * , file = None , show_caches = False , adaptive = False ,
772
- show_offsets = False ):
788
+ show_offsets = False , show_positions = False ):
773
789
"""Disassemble a code object."""
774
790
linestarts = dict (findlinestarts (co ))
775
791
exception_entries = _parse_exception_table (co )
792
+ if show_positions :
793
+ lineno_width = _get_positions_width (co )
794
+ else :
795
+ lineno_width = _get_lineno_width (linestarts )
776
796
labels_map = _make_labels_map (co .co_code , exception_entries = exception_entries )
777
797
label_width = 4 + len (str (len (labels_map )))
778
798
formatter = Formatter (file = file ,
779
- lineno_width = _get_lineno_width ( linestarts ) ,
799
+ lineno_width = lineno_width ,
780
800
offset_width = len (str (max (len (co .co_code ) - 2 , 9999 ))) if show_offsets else 0 ,
781
801
label_width = label_width ,
782
- show_caches = show_caches )
802
+ show_caches = show_caches ,
803
+ show_positions = show_positions )
783
804
arg_resolver = ArgResolver (co_consts = co .co_consts ,
784
805
names = co .co_names ,
785
806
varname_from_oparg = co ._varname_from_oparg ,
@@ -788,8 +809,8 @@ def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False,
788
809
exception_entries = exception_entries , co_positions = co .co_positions (),
789
810
original_code = co .co_code , arg_resolver = arg_resolver , formatter = formatter )
790
811
791
- def _disassemble_recursive (co , * , file = None , depth = None , show_caches = False , adaptive = False , show_offsets = False ):
792
- disassemble (co , file = file , show_caches = show_caches , adaptive = adaptive , show_offsets = show_offsets )
812
+ def _disassemble_recursive (co , * , file = None , depth = None , show_caches = False , adaptive = False , show_offsets = False , show_positions = False ):
813
+ disassemble (co , file = file , show_caches = show_caches , adaptive = adaptive , show_offsets = show_offsets , show_positions = show_positions )
793
814
if depth is None or depth > 0 :
794
815
if depth is not None :
795
816
depth = depth - 1
@@ -799,7 +820,7 @@ def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adap
799
820
print ("Disassembly of %r:" % (x ,), file = file )
800
821
_disassemble_recursive (
801
822
x , file = file , depth = depth , show_caches = show_caches ,
802
- adaptive = adaptive , show_offsets = show_offsets
823
+ adaptive = adaptive , show_offsets = show_offsets , show_positions = show_positions
803
824
)
804
825
805
826
@@ -832,6 +853,22 @@ def _get_lineno_width(linestarts):
832
853
lineno_width = len (_NO_LINENO )
833
854
return lineno_width
834
855
856
+ def _get_positions_width (code ):
857
+ # Positions are formatted as 'LINE:COL-ENDLINE:ENDCOL ' (note trailing space).
858
+ # A missing component appears as '?', and when all components are None, we
859
+ # render '_NO_LINENO'. thus the minimum width is 1 + len(_NO_LINENO).
860
+ #
861
+ # If all values are missing, positions are not printed (i.e. positions_width = 0).
862
+ has_value = False
863
+ values_width = 0
864
+ for positions in code .co_positions ():
865
+ has_value |= any (isinstance (p , int ) for p in positions )
866
+ width = sum (1 if p is None else len (str (p )) for p in positions )
867
+ values_width = max (width , values_width )
868
+ if has_value :
869
+ # 3 = number of separators in a normal format
870
+ return 1 + max (len (_NO_LINENO ), 3 + values_width )
871
+ return 0
835
872
836
873
def _disassemble_bytes (code , lasti = - 1 , linestarts = None ,
837
874
* , line_offset = 0 , exception_entries = (),
@@ -978,7 +1015,7 @@ class Bytecode:
978
1015
979
1016
Iterating over this yields the bytecode operations as Instruction instances.
980
1017
"""
981
- def __init__ (self , x , * , first_line = None , current_offset = None , show_caches = False , adaptive = False , show_offsets = False ):
1018
+ def __init__ (self , x , * , first_line = None , current_offset = None , show_caches = False , adaptive = False , show_offsets = False , show_positions = False ):
982
1019
self .codeobj = co = _get_code_object (x )
983
1020
if first_line is None :
984
1021
self .first_line = co .co_firstlineno
@@ -993,6 +1030,7 @@ def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False
993
1030
self .show_caches = show_caches
994
1031
self .adaptive = adaptive
995
1032
self .show_offsets = show_offsets
1033
+ self .show_positions = show_positions
996
1034
997
1035
def __iter__ (self ):
998
1036
co = self .codeobj
@@ -1036,16 +1074,19 @@ def dis(self):
1036
1074
with io .StringIO () as output :
1037
1075
code = _get_code_array (co , self .adaptive )
1038
1076
offset_width = len (str (max (len (code ) - 2 , 9999 ))) if self .show_offsets else 0
1039
-
1040
-
1077
+ if self .show_positions :
1078
+ lineno_width = _get_positions_width (co )
1079
+ else :
1080
+ lineno_width = _get_lineno_width (self ._linestarts )
1041
1081
labels_map = _make_labels_map (co .co_code , self .exception_entries )
1042
1082
label_width = 4 + len (str (len (labels_map )))
1043
1083
formatter = Formatter (file = output ,
1044
- lineno_width = _get_lineno_width ( self . _linestarts ) ,
1084
+ lineno_width = lineno_width ,
1045
1085
offset_width = offset_width ,
1046
1086
label_width = label_width ,
1047
1087
line_offset = self ._line_offset ,
1048
- show_caches = self .show_caches )
1088
+ show_caches = self .show_caches ,
1089
+ show_positions = self .show_positions )
1049
1090
1050
1091
arg_resolver = ArgResolver (co_consts = co .co_consts ,
1051
1092
names = co .co_names ,
@@ -1071,6 +1112,8 @@ def main():
1071
1112
help = 'show inline caches' )
1072
1113
parser .add_argument ('-O' , '--show-offsets' , action = 'store_true' ,
1073
1114
help = 'show instruction offsets' )
1115
+ parser .add_argument ('-P' , '--show-positions' , action = 'store_true' ,
1116
+ help = 'show instruction positions' )
1074
1117
parser .add_argument ('infile' , nargs = '?' , default = '-' )
1075
1118
args = parser .parse_args ()
1076
1119
if args .infile == '-' :
@@ -1081,7 +1124,7 @@ def main():
1081
1124
with open (args .infile , 'rb' ) as infile :
1082
1125
source = infile .read ()
1083
1126
code = compile (source , name , "exec" )
1084
- dis (code , show_caches = args .show_caches , show_offsets = args .show_offsets )
1127
+ dis (code , show_caches = args .show_caches , show_offsets = args .show_offsets , show_positions = args . show_positions )
1085
1128
1086
1129
if __name__ == "__main__" :
1087
1130
main ()
0 commit comments