55
66from __future__ import annotations
77
8- import dis
98import functools
109import inspect
1110import os
1918from typing import (
2019 Any ,
2120 Callable ,
22- Iterable ,
2321 NewType ,
2422 Optional ,
2523 cast ,
2624)
2725
2826from coverage import env
27+ from coverage .bytecode import TBranchTrails , branch_trails
2928from coverage .debug import short_filename , short_stack
3029from coverage .misc import isolate_module
3130from coverage .types import (
3231 AnyCallable ,
33- TArc ,
3432 TFileDisposition ,
3533 TLineNo ,
34+ TOffset ,
3635 TShouldStartContextFn ,
3736 TShouldTraceFn ,
3837 TTraceData ,
5857DISABLE_TYPE = NewType ("DISABLE_TYPE" , object )
5958MonitorReturn = Optional [DISABLE_TYPE ]
6059DISABLE = 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" ])
7360
7461
7562if LOG : # pragma: debugging
@@ -181,131 +168,6 @@ def _decorator(meth: AnyCallable) -> AnyCallable:
181168 return _decorator
182169
183170
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-
309171@dataclass
310172class CodeInfo :
311173 """The information we want about each code object."""
@@ -321,10 +183,7 @@ class CodeInfo:
321183 # ([offset, offset, ...], (from_line, to_line)),
322184 # ]
323185 # 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
328187
329188
330189def bytes_to_lines (code : CodeType ) -> dict [TOffset , TLineNo ]:
@@ -571,7 +430,7 @@ def sysmon_branch_either(
571430 if not code_info .branch_trails :
572431 if self .stats is not None :
573432 self .stats ["branch_trails" ] += 1
574- populate_branch_trails (code , code_info )
433+ code_info . branch_trails = branch_trails (code )
575434 # log(f"branch_trails for {code}:\n {code_info.branch_trails}")
576435 added_arc = False
577436 dest_info = code_info .branch_trails .get (instruction_offset )
0 commit comments