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