xref: /llvm-project/cross-project-tests/debuginfo-tests/dexter/dex/dextIR/DextIR.py (revision 45a40c163932d12b72b33bd1d8a84519392b5d39)
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