# DExTer : Debugging Experience Tester # ~~~~~~ ~ ~~ ~ ~~ # # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception from collections import OrderedDict import os from typing import List from dex.dextIR.BuilderIR import BuilderIR from dex.dextIR.DebuggerIR import DebuggerIR from dex.dextIR.StepIR import StepIR, StepKind def _step_kind_func(context, step): if (step.current_location.path is None or not os.path.exists(step.current_location.path)): return StepKind.FUNC_UNKNOWN if any(os.path.samefile(step.current_location.path, f) for f in context.options.source_files): return StepKind.FUNC return StepKind.FUNC_EXTERNAL class DextIR: """A full Dexter test report. This is composed of all the other *IR classes. They are used together to record Dexter inputs and the resultant debugger steps, providing a single high level access container. The Heuristic class works with dexter commands and the generated DextIR to determine the debugging score for a given test. Args: commands: { name (str), commands (list[CommandIR]) """ def __init__(self, dexter_version: str, executable_path: str, source_paths: List[str], builder: BuilderIR = None, debugger: DebuggerIR = None, commands: OrderedDict = None): self.dexter_version = dexter_version self.executable_path = executable_path self.source_paths = source_paths self.builder = builder self.debugger = debugger self.commands = commands self.steps: List[StepIR] = [] def __str__(self): colors = 'rgby' st = '## BEGIN ##\n' color_idx = 0 for step in self.steps: if step.step_kind in (StepKind.FUNC, StepKind.FUNC_EXTERNAL, StepKind.FUNC_UNKNOWN): color_idx += 1 color = colors[color_idx % len(colors)] st += '<{}>{}\n'.format(color, step) st += '## END ({} step{}) ##\n'.format( self.num_steps, '' if self.num_steps == 1 else 's') return st @property def num_steps(self): return len(self.steps) def _get_prev_step_in_this_frame(self, step): """Find the most recent step in the same frame as `step`. Returns: StepIR or None if there is no previous step in this frame. """ return next((s for s in reversed(self.steps) if s.current_function == step.current_function and s.num_frames == step.num_frames), None) def _get_new_step_kind(self, context, step): if step.current_function is None: return StepKind.UNKNOWN if len(self.steps) == 0: return _step_kind_func(context, step) prev_step = self.steps[-1] if prev_step.current_function is None: return StepKind.UNKNOWN if prev_step.num_frames < step.num_frames: return _step_kind_func(context, step) if prev_step.num_frames > step.num_frames: frame_step = self._get_prev_step_in_this_frame(step) prev_step = frame_step if frame_step is not None else prev_step # If we're missing line numbers to compare then the step kind has to be UNKNOWN. if prev_step.current_location.lineno is None or step.current_location.lineno is None: return StepKind.UNKNOWN # We're in the same func as prev step, check lineo. if prev_step.current_location.lineno > step.current_location.lineno: return StepKind.VERTICAL_BACKWARD if prev_step.current_location.lineno < step.current_location.lineno: return StepKind.VERTICAL_FORWARD # We're on the same line as prev step, check column. if prev_step.current_location.column > step.current_location.column: return StepKind.HORIZONTAL_BACKWARD if prev_step.current_location.column < step.current_location.column: return StepKind.HORIZONTAL_FORWARD # This step is in exactly the same location as the prev step. return StepKind.SAME def new_step(self, context, step): assert isinstance(step, StepIR), type(step) step.step_kind = self._get_new_step_kind(context, step) self.steps.append(step) return step def clear_steps(self): self.steps.clear()