1# DExTer : Debugging Experience Tester 2# ~~~~~~ ~ ~~ ~ ~~ 3# 4# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 5# See https://llvm.org/LICENSE.txt for license information. 6# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 7from collections import OrderedDict 8import os 9from typing import List 10 11from dex.dextIR.DebuggerIR import DebuggerIR 12from dex.dextIR.StepIR import StepIR, StepKind 13 14 15def _step_kind_func(context, step): 16 if step.current_location.path is None or not os.path.exists( 17 step.current_location.path 18 ): 19 return StepKind.FUNC_UNKNOWN 20 21 if any( 22 os.path.samefile(step.current_location.path, f) 23 for f in context.options.source_files 24 ): 25 return StepKind.FUNC 26 27 return StepKind.FUNC_EXTERNAL 28 29 30class DextIR: 31 """A full Dexter test report. 32 33 This is composed of all the other *IR classes. They are used together to 34 record Dexter inputs and the resultant debugger steps, providing a single 35 high level access container. 36 37 The Heuristic class works with dexter commands and the generated DextIR to 38 determine the debugging score for a given test. 39 40 Args: 41 commands: { name (str), commands (list[CommandIR]) 42 """ 43 44 def __init__( 45 self, 46 dexter_version: str, 47 executable_path: str, 48 source_paths: List[str], 49 debugger: DebuggerIR = None, 50 commands: OrderedDict = None, 51 ): 52 self.dexter_version = dexter_version 53 self.executable_path = executable_path 54 self.source_paths = source_paths 55 self.debugger = debugger 56 self.commands = commands 57 self.steps: List[StepIR] = [] 58 59 def __str__(self): 60 colors = "rgby" 61 st = "## BEGIN ##\n" 62 color_idx = 0 63 for step in self.steps: 64 if step.step_kind in ( 65 StepKind.FUNC, 66 StepKind.FUNC_EXTERNAL, 67 StepKind.FUNC_UNKNOWN, 68 ): 69 color_idx += 1 70 71 color = colors[color_idx % len(colors)] 72 st += "<{}>{}</>\n".format(color, step) 73 st += "## END ({} step{}) ##\n".format( 74 self.num_steps, "" if self.num_steps == 1 else "s" 75 ) 76 return st 77 78 @property 79 def num_steps(self): 80 return len(self.steps) 81 82 def _get_prev_step_in_this_frame(self, step): 83 """Find the most recent step in the same frame as `step`. 84 85 Returns: 86 StepIR or None if there is no previous step in this frame. 87 """ 88 return next( 89 ( 90 s 91 for s in reversed(self.steps) 92 if s.current_function == step.current_function 93 and s.num_frames == step.num_frames 94 ), 95 None, 96 ) 97 98 def _get_new_step_kind(self, context, step): 99 if step.current_function is None: 100 return StepKind.UNKNOWN 101 102 if len(self.steps) == 0: 103 return _step_kind_func(context, step) 104 105 prev_step = self.steps[-1] 106 107 if prev_step.current_function is None: 108 return StepKind.UNKNOWN 109 110 if prev_step.num_frames < step.num_frames: 111 return _step_kind_func(context, step) 112 113 if prev_step.num_frames > step.num_frames: 114 frame_step = self._get_prev_step_in_this_frame(step) 115 prev_step = frame_step if frame_step is not None else prev_step 116 117 # If we're missing line numbers to compare then the step kind has to be UNKNOWN. 118 if ( 119 prev_step.current_location.lineno is None 120 or step.current_location.lineno is None 121 ): 122 return StepKind.UNKNOWN 123 124 # We're in the same func as prev step, check lineo. 125 if prev_step.current_location.lineno > step.current_location.lineno: 126 return StepKind.VERTICAL_BACKWARD 127 128 if prev_step.current_location.lineno < step.current_location.lineno: 129 return StepKind.VERTICAL_FORWARD 130 131 # We're on the same line as prev step, check column. 132 if prev_step.current_location.column > step.current_location.column: 133 return StepKind.HORIZONTAL_BACKWARD 134 135 if prev_step.current_location.column < step.current_location.column: 136 return StepKind.HORIZONTAL_FORWARD 137 138 # This step is in exactly the same location as the prev step. 139 return StepKind.SAME 140 141 def new_step(self, context, step): 142 assert isinstance(step, StepIR), type(step) 143 step.step_kind = self._get_new_step_kind(context, step) 144 self.steps.append(step) 145 return step 146 147 def clear_steps(self): 148 self.steps.clear() 149