5
5
6
6
from __future__ import annotations
7
7
8
- import dis
9
8
import functools
10
9
import inspect
11
10
import os
19
18
from typing import (
20
19
Any ,
21
20
Callable ,
22
- Iterable ,
23
21
NewType ,
24
22
Optional ,
25
23
cast ,
26
24
)
27
25
28
26
from coverage import env
27
+ from coverage .bytecode import TBranchTrails , branch_trails
29
28
from coverage .debug import short_filename , short_stack
30
29
from coverage .misc import isolate_module
31
30
from coverage .types import (
32
31
AnyCallable ,
33
- TArc ,
34
32
TFileDisposition ,
35
33
TLineNo ,
34
+ TOffset ,
36
35
TShouldStartContextFn ,
37
36
TShouldTraceFn ,
38
37
TTraceData ,
58
57
DISABLE_TYPE = NewType ("DISABLE_TYPE" , object )
59
58
MonitorReturn = Optional [DISABLE_TYPE ]
60
59
DISABLE = cast (MonitorReturn , getattr (sys_monitoring , "DISABLE" , None ))
61
- TOffset = int
62
-
63
- ALWAYS_JUMPS : set [int ] = set ()
64
- RETURNS : set [int ] = set ()
65
-
66
- if env .PYBEHAVIOR .branch_right_left :
67
- ALWAYS_JUMPS .update (
68
- dis .opmap [name ]
69
- for name in ["JUMP_FORWARD" , "JUMP_BACKWARD" , "JUMP_BACKWARD_NO_INTERRUPT" ]
70
- )
71
-
72
- RETURNS .update (dis .opmap [name ] for name in ["RETURN_VALUE" , "RETURN_GENERATOR" ])
73
60
74
61
75
62
if LOG : # pragma: debugging
@@ -181,131 +168,6 @@ def _decorator(meth: AnyCallable) -> AnyCallable:
181
168
return _decorator
182
169
183
170
184
- class InstructionWalker :
185
- """Utility to step through trails of instructions.
186
-
187
- We have two reasons to need sequences of instructions from a code object:
188
- First, in strict sequence to visit all the instructions in the object.
189
- This is `walk(follow_jumps=False)`. Second, we want to follow jumps to
190
- understand how execution will flow: `walk(follow_jumps=True)`.
191
-
192
- """
193
-
194
- def __init__ (self , code : CodeType ) -> None :
195
- self .code = code
196
- self .insts : dict [TOffset , dis .Instruction ] = {}
197
-
198
- inst = None
199
- for inst in dis .get_instructions (code ):
200
- self .insts [inst .offset ] = inst
201
-
202
- assert inst is not None
203
- self .max_offset = inst .offset
204
-
205
- def walk (
206
- self , * , start_at : TOffset = 0 , follow_jumps : bool = True
207
- ) -> Iterable [dis .Instruction ]:
208
- """
209
- Yield instructions starting from `start_at`. Follow unconditional
210
- jumps if `follow_jumps` is true.
211
- """
212
- seen = set ()
213
- offset = start_at
214
- while offset < self .max_offset + 1 :
215
- if offset in seen :
216
- break
217
- seen .add (offset )
218
- if inst := self .insts .get (offset ):
219
- yield inst
220
- if follow_jumps and inst .opcode in ALWAYS_JUMPS :
221
- offset = inst .jump_target
222
- continue
223
- offset += 2
224
-
225
-
226
- def populate_branch_trails (code : CodeType , code_info : CodeInfo ) -> None :
227
- """
228
- Populate the `branch_trails` attribute on `code_info`.
229
-
230
- Instructions can have a jump_target, where they might jump to next. Some
231
- instructions with a jump_target are unconditional jumps (ALWAYS_JUMPS), so
232
- they aren't interesting to us, since they aren't the start of a branch
233
- possibility.
234
-
235
- Instructions that might or might not jump somewhere else are branch
236
- possibilities. For each of those, we track a trail of instructions. These
237
- are lists of instruction offsets, the next instructions that can execute.
238
- We follow the trail until we get to a new source line. That gives us the
239
- arc from the original instruction's line to the new source line.
240
-
241
- """
242
- # log(f"populate_branch_trails: {code}")
243
- iwalker = InstructionWalker (code )
244
- for inst in iwalker .walk (follow_jumps = False ):
245
- # log(f"considering {inst=}")
246
- if not inst .jump_target :
247
- # We only care about instructions with jump targets.
248
- # log("no jump_target")
249
- continue
250
- if inst .opcode in ALWAYS_JUMPS :
251
- # We don't care about unconditional jumps.
252
- # log("always jumps")
253
- continue
254
-
255
- from_line = inst .line_number
256
- if from_line is None :
257
- continue
258
-
259
- def walk_one_branch (
260
- start_at : TOffset , branch_kind : str
261
- ) -> tuple [list [TOffset ], TArc | None ]:
262
- # pylint: disable=cell-var-from-loop
263
- inst_offsets : list [TOffset ] = []
264
- to_line = None
265
- for inst2 in iwalker .walk (start_at = start_at ):
266
- inst_offsets .append (inst2 .offset )
267
- if inst2 .line_number and inst2 .line_number != from_line :
268
- to_line = inst2 .line_number
269
- break
270
- elif inst2 .jump_target and (inst2 .opcode not in ALWAYS_JUMPS ):
271
- # log(
272
- # f"stop: {inst2.jump_target=}, "
273
- # + f"{inst2.opcode=} ({dis.opname[inst2.opcode]}), "
274
- # + f"{ALWAYS_JUMPS=}"
275
- # )
276
- break
277
- elif inst2 .opcode in RETURNS :
278
- to_line = - code .co_firstlineno
279
- break
280
- if to_line is not None :
281
- # log(
282
- # f"possible branch from @{start_at}: "
283
- # + f"{inst_offsets}, {(from_line, to_line)} {code}"
284
- # )
285
- return inst_offsets , (from_line , to_line )
286
- else :
287
- # log(f"no possible branch from @{start_at}: {inst_offsets}")
288
- return [], None
289
-
290
- # Calculate two trails: one from the next instruction, and one from the
291
- # jump_target instruction.
292
- trails = [
293
- walk_one_branch (start_at = inst .offset + 2 , branch_kind = "not-taken" ),
294
- walk_one_branch (start_at = inst .jump_target , branch_kind = "taken" ),
295
- ]
296
- code_info .branch_trails [inst .offset ] = trails
297
-
298
- # Sometimes we get BRANCH_RIGHT or BRANCH_LEFT events from instructions
299
- # other than the original jump possibility instruction. Register each
300
- # trail under all of their offsets so we can pick up in the middle of a
301
- # trail if need be.
302
- for trail in trails :
303
- for offset in trail [0 ]:
304
- if offset not in code_info .branch_trails :
305
- code_info .branch_trails [offset ] = []
306
- code_info .branch_trails [offset ].append (trail )
307
-
308
-
309
171
@dataclass
310
172
class CodeInfo :
311
173
"""The information we want about each code object."""
@@ -321,10 +183,7 @@ class CodeInfo:
321
183
# ([offset, offset, ...], (from_line, to_line)),
322
184
# ]
323
185
# Two possible trails from the branch point, left and right.
324
- branch_trails : dict [
325
- TOffset ,
326
- list [tuple [list [TOffset ], TArc | None ]],
327
- ]
186
+ branch_trails : TBranchTrails
328
187
329
188
330
189
def bytes_to_lines (code : CodeType ) -> dict [TOffset , TLineNo ]:
@@ -571,7 +430,7 @@ def sysmon_branch_either(
571
430
if not code_info .branch_trails :
572
431
if self .stats is not None :
573
432
self .stats ["branch_trails" ] += 1
574
- populate_branch_trails (code , code_info )
433
+ code_info . branch_trails = branch_trails (code )
575
434
# log(f"branch_trails for {code}:\n {code_info.branch_trails}")
576
435
added_arc = False
577
436
dest_info = code_info .branch_trails .get (instruction_offset )
0 commit comments