@@ -40,6 +40,20 @@ def check_jump_targets(self, code):
40
40
self .fail (f'{ instr .opname } at { instr .offset } '
41
41
f'jumps to { tgt .opname } at { tgt .offset } ' )
42
42
43
+ def check_lnotab (self , code ):
44
+ "Check that the lnotab byte offsets are sensible."
45
+ code = dis ._get_code_object (code )
46
+ lnotab = list (dis .findlinestarts (code ))
47
+ # Don't bother checking if the line info is sensible, because
48
+ # most of the line info we can get at comes from lnotab.
49
+ min_bytecode = min (t [0 ] for t in lnotab )
50
+ max_bytecode = max (t [0 ] for t in lnotab )
51
+ self .assertGreaterEqual (min_bytecode , 0 )
52
+ self .assertLess (max_bytecode , len (code .co_code ))
53
+ # This could conceivably test more (and probably should, as there
54
+ # aren't very many tests of lnotab), if peepholer wasn't scheduled
55
+ # to be replaced anyway.
56
+
43
57
def test_unot (self ):
44
58
# UNARY_NOT POP_JUMP_IF_FALSE --> POP_JUMP_IF_TRUE'
45
59
def unot (x ):
@@ -48,6 +62,7 @@ def unot(x):
48
62
self .assertNotInBytecode (unot , 'UNARY_NOT' )
49
63
self .assertNotInBytecode (unot , 'POP_JUMP_IF_FALSE' )
50
64
self .assertInBytecode (unot , 'POP_JUMP_IF_TRUE' )
65
+ self .check_lnotab (unot )
51
66
52
67
def test_elim_inversion_of_is_or_in (self ):
53
68
for line , cmp_op in (
@@ -58,6 +73,7 @@ def test_elim_inversion_of_is_or_in(self):
58
73
):
59
74
code = compile (line , '' , 'single' )
60
75
self .assertInBytecode (code , 'COMPARE_OP' , cmp_op )
76
+ self .check_lnotab (code )
61
77
62
78
def test_global_as_constant (self ):
63
79
# LOAD_GLOBAL None/True/False --> LOAD_CONST None/True/False
@@ -75,13 +91,15 @@ def h():
75
91
for func , elem in ((f , None ), (g , True ), (h , False )):
76
92
self .assertNotInBytecode (func , 'LOAD_GLOBAL' )
77
93
self .assertInBytecode (func , 'LOAD_CONST' , elem )
94
+ self .check_lnotab (func )
78
95
79
96
def f ():
80
97
'Adding a docstring made this test fail in Py2.5.0'
81
98
return None
82
99
83
100
self .assertNotInBytecode (f , 'LOAD_GLOBAL' )
84
101
self .assertInBytecode (f , 'LOAD_CONST' , None )
102
+ self .check_lnotab (f )
85
103
86
104
def test_while_one (self ):
87
105
# Skip over: LOAD_CONST trueconst POP_JUMP_IF_FALSE xx
@@ -93,6 +111,7 @@ def f():
93
111
self .assertNotInBytecode (f , elem )
94
112
for elem in ('JUMP_ABSOLUTE' ,):
95
113
self .assertInBytecode (f , elem )
114
+ self .check_lnotab (f )
96
115
97
116
def test_pack_unpack (self ):
98
117
for line , elem in (
@@ -104,6 +123,7 @@ def test_pack_unpack(self):
104
123
self .assertInBytecode (code , elem )
105
124
self .assertNotInBytecode (code , 'BUILD_TUPLE' )
106
125
self .assertNotInBytecode (code , 'UNPACK_TUPLE' )
126
+ self .check_lnotab (code )
107
127
108
128
def test_folding_of_tuples_of_constants (self ):
109
129
for line , elem in (
@@ -116,6 +136,7 @@ def test_folding_of_tuples_of_constants(self):
116
136
code = compile (line ,'' ,'single' )
117
137
self .assertInBytecode (code , 'LOAD_CONST' , elem )
118
138
self .assertNotInBytecode (code , 'BUILD_TUPLE' )
139
+ self .check_lnotab (code )
119
140
120
141
# Long tuples should be folded too.
121
142
code = compile (repr (tuple (range (10000 ))),'' ,'single' )
@@ -124,6 +145,7 @@ def test_folding_of_tuples_of_constants(self):
124
145
load_consts = [instr for instr in dis .get_instructions (code )
125
146
if instr .opname == 'LOAD_CONST' ]
126
147
self .assertEqual (len (load_consts ), 2 )
148
+ self .check_lnotab (code )
127
149
128
150
# Bug 1053819: Tuple of constants misidentified when presented with:
129
151
# . . . opcode_with_arg 100 unary_opcode BUILD_TUPLE 1 . . .
@@ -141,6 +163,7 @@ def crater():
141
163
0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ,
142
164
0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ,
143
165
],)
166
+ self .check_lnotab (crater )
144
167
145
168
def test_folding_of_lists_of_constants (self ):
146
169
for line , elem in (
@@ -153,6 +176,7 @@ def test_folding_of_lists_of_constants(self):
153
176
code = compile (line , '' , 'single' )
154
177
self .assertInBytecode (code , 'LOAD_CONST' , elem )
155
178
self .assertNotInBytecode (code , 'BUILD_LIST' )
179
+ self .check_lnotab (code )
156
180
157
181
def test_folding_of_sets_of_constants (self ):
158
182
for line , elem in (
@@ -166,6 +190,7 @@ def test_folding_of_sets_of_constants(self):
166
190
code = compile (line , '' , 'single' )
167
191
self .assertNotInBytecode (code , 'BUILD_SET' )
168
192
self .assertInBytecode (code , 'LOAD_CONST' , elem )
193
+ self .check_lnotab (code )
169
194
170
195
# Ensure that the resulting code actually works:
171
196
def f (a ):
@@ -176,9 +201,11 @@ def g(a):
176
201
177
202
self .assertTrue (f (3 ))
178
203
self .assertTrue (not f (4 ))
204
+ self .check_lnotab (f )
179
205
180
206
self .assertTrue (not g (3 ))
181
207
self .assertTrue (g (4 ))
208
+ self .check_lnotab (g )
182
209
183
210
184
211
def test_folding_of_binops_on_constants (self ):
@@ -203,41 +230,50 @@ def test_folding_of_binops_on_constants(self):
203
230
self .assertInBytecode (code , 'LOAD_CONST' , elem )
204
231
for instr in dis .get_instructions (code ):
205
232
self .assertFalse (instr .opname .startswith ('BINARY_' ))
233
+ self .check_lnotab (code )
206
234
207
235
# Verify that unfoldables are skipped
208
236
code = compile ('a=2+"b"' , '' , 'single' )
209
237
self .assertInBytecode (code , 'LOAD_CONST' , 2 )
210
238
self .assertInBytecode (code , 'LOAD_CONST' , 'b' )
239
+ self .check_lnotab (code )
211
240
212
241
# Verify that large sequences do not result from folding
213
242
code = compile ('a="x"*10000' , '' , 'single' )
214
243
self .assertInBytecode (code , 'LOAD_CONST' , 10000 )
215
244
self .assertNotIn ("x" * 10000 , code .co_consts )
245
+ self .check_lnotab (code )
216
246
code = compile ('a=1<<1000' , '' , 'single' )
217
247
self .assertInBytecode (code , 'LOAD_CONST' , 1000 )
218
248
self .assertNotIn (1 << 1000 , code .co_consts )
249
+ self .check_lnotab (code )
219
250
code = compile ('a=2**1000' , '' , 'single' )
220
251
self .assertInBytecode (code , 'LOAD_CONST' , 1000 )
221
252
self .assertNotIn (2 ** 1000 , code .co_consts )
253
+ self .check_lnotab (code )
222
254
223
255
def test_binary_subscr_on_unicode (self ):
224
256
# valid code get optimized
225
257
code = compile ('"foo"[0]' , '' , 'single' )
226
258
self .assertInBytecode (code , 'LOAD_CONST' , 'f' )
227
259
self .assertNotInBytecode (code , 'BINARY_SUBSCR' )
260
+ self .check_lnotab (code )
228
261
code = compile ('"\u0061 \uffff "[1]' , '' , 'single' )
229
262
self .assertInBytecode (code , 'LOAD_CONST' , '\uffff ' )
230
263
self .assertNotInBytecode (code ,'BINARY_SUBSCR' )
264
+ self .check_lnotab (code )
231
265
232
266
# With PEP 393, non-BMP char get optimized
233
267
code = compile ('"\U00012345 "[0]' , '' , 'single' )
234
268
self .assertInBytecode (code , 'LOAD_CONST' , '\U00012345 ' )
235
269
self .assertNotInBytecode (code , 'BINARY_SUBSCR' )
270
+ self .check_lnotab (code )
236
271
237
272
# invalid code doesn't get optimized
238
273
# out of range
239
274
code = compile ('"fuu"[10]' , '' , 'single' )
240
275
self .assertInBytecode (code , 'BINARY_SUBSCR' )
276
+ self .check_lnotab (code )
241
277
242
278
def test_folding_of_unaryops_on_constants (self ):
243
279
for line , elem in (
@@ -252,13 +288,15 @@ def test_folding_of_unaryops_on_constants(self):
252
288
self .assertInBytecode (code , 'LOAD_CONST' , elem )
253
289
for instr in dis .get_instructions (code ):
254
290
self .assertFalse (instr .opname .startswith ('UNARY_' ))
291
+ self .check_lnotab (code )
255
292
256
293
# Check that -0.0 works after marshaling
257
294
def negzero ():
258
295
return - (1.0 - 1.0 )
259
296
260
- for instr in dis .get_instructions (code ):
297
+ for instr in dis .get_instructions (negzero ):
261
298
self .assertFalse (instr .opname .startswith ('UNARY_' ))
299
+ self .check_lnotab (negzero )
262
300
263
301
# Verify that unfoldables are skipped
264
302
for line , elem , opname in (
@@ -268,6 +306,7 @@ def negzero():
268
306
code = compile (line , '' , 'single' )
269
307
self .assertInBytecode (code , 'LOAD_CONST' , elem )
270
308
self .assertInBytecode (code , opname )
309
+ self .check_lnotab (code )
271
310
272
311
def test_elim_extra_return (self ):
273
312
# RETURN LOAD_CONST None RETURN --> RETURN
@@ -277,6 +316,7 @@ def f(x):
277
316
returns = [instr for instr in dis .get_instructions (f )
278
317
if instr .opname == 'RETURN_VALUE' ]
279
318
self .assertEqual (len (returns ), 1 )
319
+ self .check_lnotab (f )
280
320
281
321
def test_elim_jump_to_return (self ):
282
322
# JUMP_FORWARD to RETURN --> RETURN
@@ -290,6 +330,7 @@ def f(cond, true_value, false_value):
290
330
returns = [instr for instr in dis .get_instructions (f )
291
331
if instr .opname == 'RETURN_VALUE' ]
292
332
self .assertEqual (len (returns ), 2 )
333
+ self .check_lnotab (f )
293
334
294
335
def test_elim_jump_to_uncond_jump (self ):
295
336
# POP_JUMP_IF_FALSE to JUMP_FORWARD --> POP_JUMP_IF_FALSE to non-jump
@@ -302,6 +343,7 @@ def f():
302
343
else :
303
344
baz ()
304
345
self .check_jump_targets (f )
346
+ self .check_lnotab (f )
305
347
306
348
def test_elim_jump_to_uncond_jump2 (self ):
307
349
# POP_JUMP_IF_FALSE to JUMP_ABSOLUTE --> POP_JUMP_IF_FALSE to non-jump
@@ -312,6 +354,7 @@ def f():
312
354
or d ):
313
355
a = foo ()
314
356
self .check_jump_targets (f )
357
+ self .check_lnotab (f )
315
358
316
359
def test_elim_jump_to_uncond_jump3 (self ):
317
360
# Intentionally use two-line expressions to test issue37213.
@@ -320,18 +363,21 @@ def f(a, b, c):
320
363
return ((a and b )
321
364
and c )
322
365
self .check_jump_targets (f )
366
+ self .check_lnotab (f )
323
367
self .assertEqual (count_instr_recursively (f , 'JUMP_IF_FALSE_OR_POP' ), 2 )
324
368
# JUMP_IF_TRUE_OR_POP to JUMP_IF_TRUE_OR_POP --> JUMP_IF_TRUE_OR_POP to non-jump
325
369
def f (a , b , c ):
326
370
return ((a or b )
327
371
or c )
328
372
self .check_jump_targets (f )
373
+ self .check_lnotab (f )
329
374
self .assertEqual (count_instr_recursively (f , 'JUMP_IF_TRUE_OR_POP' ), 2 )
330
375
# JUMP_IF_FALSE_OR_POP to JUMP_IF_TRUE_OR_POP --> POP_JUMP_IF_FALSE to non-jump
331
376
def f (a , b , c ):
332
377
return ((a and b )
333
378
or c )
334
379
self .check_jump_targets (f )
380
+ self .check_lnotab (f )
335
381
self .assertNotInBytecode (f , 'JUMP_IF_FALSE_OR_POP' )
336
382
self .assertInBytecode (f , 'JUMP_IF_TRUE_OR_POP' )
337
383
self .assertInBytecode (f , 'POP_JUMP_IF_FALSE' )
@@ -340,6 +386,7 @@ def f(a, b, c):
340
386
return ((a or b )
341
387
and c )
342
388
self .check_jump_targets (f )
389
+ self .check_lnotab (f )
343
390
self .assertNotInBytecode (f , 'JUMP_IF_TRUE_OR_POP' )
344
391
self .assertInBytecode (f , 'JUMP_IF_FALSE_OR_POP' )
345
392
self .assertInBytecode (f , 'POP_JUMP_IF_TRUE' )
@@ -360,6 +407,7 @@ def f(cond1, cond2):
360
407
returns = [instr for instr in dis .get_instructions (f )
361
408
if instr .opname == 'RETURN_VALUE' ]
362
409
self .assertLessEqual (len (returns ), 6 )
410
+ self .check_lnotab (f )
363
411
364
412
def test_elim_jump_after_return2 (self ):
365
413
# Eliminate dead code: jumps immediately after returns can't be reached
@@ -374,13 +422,15 @@ def f(cond1, cond2):
374
422
returns = [instr for instr in dis .get_instructions (f )
375
423
if instr .opname == 'RETURN_VALUE' ]
376
424
self .assertLessEqual (len (returns ), 2 )
425
+ self .check_lnotab (f )
377
426
378
427
def test_make_function_doesnt_bail (self ):
379
428
def f ():
380
429
def g ()-> 1 + 1 :
381
430
pass
382
431
return g
383
432
self .assertNotInBytecode (f , 'BINARY_ADD' )
433
+ self .check_lnotab (f )
384
434
385
435
def test_constant_folding (self ):
386
436
# Issue #11244: aggressive constant folding.
@@ -401,24 +451,28 @@ def test_constant_folding(self):
401
451
self .assertFalse (instr .opname .startswith ('UNARY_' ))
402
452
self .assertFalse (instr .opname .startswith ('BINARY_' ))
403
453
self .assertFalse (instr .opname .startswith ('BUILD_' ))
454
+ self .check_lnotab (code )
404
455
405
456
def test_in_literal_list (self ):
406
457
def containtest ():
407
458
return x in [a , b ]
408
459
self .assertEqual (count_instr_recursively (containtest , 'BUILD_LIST' ), 0 )
460
+ self .check_lnotab (containtest )
409
461
410
462
def test_iterate_literal_list (self ):
411
463
def forloop ():
412
464
for x in [a , b ]:
413
465
pass
414
466
self .assertEqual (count_instr_recursively (forloop , 'BUILD_LIST' ), 0 )
467
+ self .check_lnotab (forloop )
415
468
416
469
def test_condition_with_binop_with_bools (self ):
417
470
def f ():
418
471
if True or False :
419
472
return 1
420
473
return 0
421
474
self .assertEqual (f (), 1 )
475
+ self .check_lnotab (f )
422
476
423
477
def test_if_with_if_expression (self ):
424
478
# Check bpo-37289
@@ -427,6 +481,19 @@ def f(x):
427
481
return True
428
482
return False
429
483
self .assertTrue (f (True ))
484
+ self .check_lnotab (f )
485
+
486
+ def test_trailing_nops (self ):
487
+ # Check the lnotab of a function that even after trivial
488
+ # optimization has trailing nops, which the lnotab adjustment has to
489
+ # handle properly (bpo-38115).
490
+ def f (x ):
491
+ while 1 :
492
+ return 3
493
+ while 1 :
494
+ return 5
495
+ return 6
496
+ self .check_lnotab (f )
430
497
431
498
432
499
class TestBuglets (unittest .TestCase ):
0 commit comments