@@ -230,9 +230,7 @@ def func(): pass
230
230
co .co_name ,
231
231
co .co_qualname ,
232
232
co .co_firstlineno ,
233
- co .co_lnotab ,
234
- co .co_endlinetable ,
235
- co .co_columntable ,
233
+ co .co_linetable ,
236
234
co .co_exceptiontable ,
237
235
co .co_freevars ,
238
236
co .co_cellvars )
@@ -273,8 +271,6 @@ def func2():
273
271
("co_filename" , "newfilename" ),
274
272
("co_name" , "newname" ),
275
273
("co_linetable" , code2 .co_linetable ),
276
- ("co_endlinetable" , code2 .co_endlinetable ),
277
- ("co_columntable" , code2 .co_columntable ),
278
274
):
279
275
with self .subTest (attr = attr , value = value ):
280
276
new_code = code .replace (** {attr : value })
@@ -311,9 +307,7 @@ def func():
311
307
co .co_name ,
312
308
co .co_qualname ,
313
309
co .co_firstlineno ,
314
- co .co_lnotab ,
315
- co .co_endlinetable ,
316
- co .co_columntable ,
310
+ co .co_linetable ,
317
311
co .co_exceptiontable ,
318
312
co .co_freevars ,
319
313
co .co_cellvars ,
@@ -391,14 +385,17 @@ def test_co_positions_artificial_instructions(self):
391
385
)
392
386
393
387
def test_endline_and_columntable_none_when_no_debug_ranges (self ):
394
- # Make sure that if `-X no_debug_ranges` is used, the endlinetable and
395
- # columntable are None.
388
+ # Make sure that if `-X no_debug_ranges` is used, there is
389
+ # minimal debug info
396
390
code = textwrap .dedent ("""
397
391
def f():
398
392
pass
399
393
400
- assert f.__code__.co_endlinetable is None
401
- assert f.__code__.co_columntable is None
394
+ positions = f.__code__.co_positions()
395
+ for line, end_line, column, end_column in positions:
396
+ assert line == end_line
397
+ assert column is None
398
+ assert end_column is None
402
399
""" )
403
400
assert_python_ok ('-X' , 'no_debug_ranges' , '-c' , code )
404
401
@@ -408,8 +405,11 @@ def test_endline_and_columntable_none_when_no_debug_ranges_env(self):
408
405
def f():
409
406
pass
410
407
411
- assert f.__code__.co_endlinetable is None
412
- assert f.__code__.co_columntable is None
408
+ positions = f.__code__.co_positions()
409
+ for line, end_line, column, end_column in positions:
410
+ assert line == end_line
411
+ assert column is None
412
+ assert end_column is None
413
413
""" )
414
414
assert_python_ok ('-c' , code , PYTHONNODEBUGRANGES = '1' )
415
415
@@ -421,35 +421,10 @@ def func():
421
421
x = 1
422
422
new_code = func .__code__ .replace (co_linetable = b'' )
423
423
positions = new_code .co_positions ()
424
- next (positions ) # Skip RESUME at start
425
424
for line , end_line , column , end_column in positions :
426
425
self .assertIsNone (line )
427
426
self .assertEqual (end_line , new_code .co_firstlineno + 1 )
428
427
429
- @requires_debug_ranges ()
430
- def test_co_positions_empty_endlinetable (self ):
431
- def func ():
432
- x = 1
433
- new_code = func .__code__ .replace (co_endlinetable = b'' )
434
- positions = new_code .co_positions ()
435
- next (positions ) # Skip RESUME at start
436
- for line , end_line , column , end_column in positions :
437
- self .assertEqual (line , new_code .co_firstlineno + 1 )
438
- self .assertIsNone (end_line )
439
-
440
- @requires_debug_ranges ()
441
- def test_co_positions_empty_columntable (self ):
442
- def func ():
443
- x = 1
444
- new_code = func .__code__ .replace (co_columntable = b'' )
445
- positions = new_code .co_positions ()
446
- next (positions ) # Skip RESUME at start
447
- for line , end_line , column , end_column in positions :
448
- self .assertEqual (line , new_code .co_firstlineno + 1 )
449
- self .assertEqual (end_line , new_code .co_firstlineno + 1 )
450
- self .assertIsNone (column )
451
- self .assertIsNone (end_column )
452
-
453
428
454
429
def isinterned (s ):
455
430
return s is sys .intern (('_' + s + '_' )[1 :- 1 ])
@@ -527,6 +502,122 @@ def callback(code):
527
502
self .assertFalse (bool (coderef ()))
528
503
self .assertTrue (self .called )
529
504
505
+ # Python implementation of location table parsing algorithm
506
+ def read (it ):
507
+ return next (it )
508
+
509
+ def read_varint (it ):
510
+ b = read (it )
511
+ val = b & 63 ;
512
+ shift = 0 ;
513
+ while b & 64 :
514
+ b = read (it )
515
+ shift += 6
516
+ val |= (b & 63 ) << shift
517
+ return val
518
+
519
+ def read_signed_varint (it ):
520
+ uval = read_varint (it )
521
+ if uval & 1 :
522
+ return - (uval >> 1 )
523
+ else :
524
+ return uval >> 1
525
+
526
+ def parse_location_table (code ):
527
+ line = code .co_firstlineno
528
+ it = iter (code .co_linetable )
529
+ while True :
530
+ try :
531
+ first_byte = read (it )
532
+ except StopIteration :
533
+ return
534
+ code = (first_byte >> 3 ) & 15
535
+ length = (first_byte & 7 ) + 1
536
+ if code == 15 :
537
+ yield (code , length , None , None , None , None )
538
+ elif code == 14 :
539
+ line_delta = read_signed_varint (it )
540
+ line += line_delta
541
+ end_line = line + read_varint (it )
542
+ col = read_varint (it )
543
+ if col == 0 :
544
+ col = None
545
+ else :
546
+ col -= 1
547
+ end_col = read_varint (it )
548
+ if end_col == 0 :
549
+ end_col = None
550
+ else :
551
+ end_col -= 1
552
+ yield (code , length , line , end_line , col , end_col )
553
+ elif code == 13 : # No column
554
+ line_delta = read_signed_varint (it )
555
+ line += line_delta
556
+ yield (code , length , line , line , None , None )
557
+ elif code in (10 , 11 , 12 ): # new line
558
+ line_delta = code - 10
559
+ line += line_delta
560
+ column = read (it )
561
+ end_column = read (it )
562
+ yield (code , length , line , line , column , end_column )
563
+ else :
564
+ assert (0 <= code < 10 )
565
+ second_byte = read (it )
566
+ column = code << 3 | (second_byte >> 4 )
567
+ yield (code , length , line , line , column , column + (second_byte & 15 ))
568
+
569
+ def positions_from_location_table (code ):
570
+ for _ , length , line , end_line , col , end_col in parse_location_table (code ):
571
+ for _ in range (length ):
572
+ yield (line , end_line , col , end_col )
573
+
574
+ def misshappen ():
575
+ """
576
+
577
+
578
+
579
+
580
+
581
+ """
582
+ x = (
583
+
584
+
585
+ 4
586
+
587
+ +
588
+
589
+ y
590
+
591
+ )
592
+ y = (
593
+ a
594
+ +
595
+ b
596
+ +
597
+
598
+ d
599
+ )
600
+ return q if (
601
+
602
+ x
603
+
604
+ ) else p
605
+
606
+
607
+ class CodeLocationTest (unittest .TestCase ):
608
+
609
+ def check_positions (self , func ):
610
+ pos1 = list (func .__code__ .co_positions ())
611
+ pos2 = list (positions_from_location_table (func .__code__ ))
612
+ for l1 , l2 in zip (pos1 , pos2 ):
613
+ self .assertEqual (l1 , l2 )
614
+ self .assertEqual (len (pos1 ), len (pos2 ))
615
+
616
+
617
+ def test_positions (self ):
618
+ self .check_positions (parse_location_table )
619
+ self .check_positions (misshappen )
620
+
530
621
531
622
if check_impl_detail (cpython = True ) and ctypes is not None :
532
623
py = ctypes .pythonapi
0 commit comments