llvm/cross-project-tests/debuginfo-tests/dexter/dex/dextIR/DextIR.py

# 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.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],
        debugger: DebuggerIR = None,
        commands: OrderedDict = None,
    ):
        self.dexter_version = dexter_version
        self.executable_path = executable_path
        self.source_paths = source_paths
        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()