xref: /llvm-project/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py (revision f98ee40f4b5d7474fc67e82824bf6abbaedb7b1c)
11364750dSJames Henderson# DExTer : Debugging Experience Tester
21364750dSJames Henderson# ~~~~~~   ~         ~~         ~   ~~
31364750dSJames Henderson#
41364750dSJames Henderson# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
51364750dSJames Henderson# See https://llvm.org/LICENSE.txt for license information.
61364750dSJames Henderson# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
71364750dSJames Henderson"""Base class for all debugger interface implementations."""
81364750dSJames Henderson
91364750dSJames Hendersonimport abc
101364750dSJames Hendersonimport os
111364750dSJames Hendersonimport sys
121364750dSJames Hendersonimport traceback
131364750dSJames Hendersonimport unittest
141364750dSJames Henderson
151364750dSJames Hendersonfrom types import SimpleNamespace
167e46a721SStephen Tozerfrom dex.command.CommandBase import StepExpectInfo
171364750dSJames Hendersonfrom dex.dextIR import DebuggerIR, FrameIR, LocIR, StepIR, ValueIR
181364750dSJames Hendersonfrom dex.utils.Exceptions import DebuggerException
191364750dSJames Hendersonfrom dex.utils.ReturnCode import ReturnCode
201364750dSJames Henderson
21*f98ee40fSTobias Hieta
227e46a721SStephen Tozerdef watch_is_active(watch_info: StepExpectInfo, path, frame_idx, line_no):
237e46a721SStephen Tozer    _, watch_path, watch_frame_idx, watch_line_range = watch_info
247e46a721SStephen Tozer    # If this watch should only be active for a specific file...
257e46a721SStephen Tozer    if watch_path and os.path.isfile(watch_path):
267e46a721SStephen Tozer        # If the current path does not match the expected file, this watch is
277e46a721SStephen Tozer        # not active.
28*f98ee40fSTobias Hieta        if not (path and os.path.isfile(path) and os.path.samefile(path, watch_path)):
297e46a721SStephen Tozer            return False
307e46a721SStephen Tozer    if watch_frame_idx != frame_idx:
317e46a721SStephen Tozer        return False
327e46a721SStephen Tozer    if watch_line_range and line_no not in list(watch_line_range):
337e46a721SStephen Tozer        return False
347e46a721SStephen Tozer    return True
351364750dSJames Henderson
36*f98ee40fSTobias Hieta
371364750dSJames Hendersonclass DebuggerBase(object, metaclass=abc.ABCMeta):
381364750dSJames Henderson    def __init__(self, context):
391364750dSJames Henderson        self.context = context
401364750dSJames Henderson        # Note: We can't already read values from options
411364750dSJames Henderson        # as DebuggerBase is created before we initialize options
421364750dSJames Henderson        # to read potential_debuggers.
431364750dSJames Henderson        self.options = self.context.options
441364750dSJames Henderson
451364750dSJames Henderson        self._interface = None
461364750dSJames Henderson        self.has_loaded = False
4775b31692SStephen Tozer        self._loading_error = None
481364750dSJames Henderson        try:
491364750dSJames Henderson            self._interface = self._load_interface()
501364750dSJames Henderson            self.has_loaded = True
511364750dSJames Henderson        except DebuggerException:
521364750dSJames Henderson            self._loading_error = sys.exc_info()
531364750dSJames Henderson
541364750dSJames Henderson    def __enter__(self):
551364750dSJames Henderson        try:
561364750dSJames Henderson            self._custom_init()
571364750dSJames Henderson            self.clear_breakpoints()
581364750dSJames Henderson        except DebuggerException:
591364750dSJames Henderson            self._loading_error = sys.exc_info()
601364750dSJames Henderson        return self
611364750dSJames Henderson
621364750dSJames Henderson    def __exit__(self, *args):
631364750dSJames Henderson        self._custom_exit()
641364750dSJames Henderson
651364750dSJames Henderson    def _custom_init(self):
661364750dSJames Henderson        pass
671364750dSJames Henderson
681364750dSJames Henderson    def _custom_exit(self):
691364750dSJames Henderson        pass
701364750dSJames Henderson
711364750dSJames Henderson    @property
721364750dSJames Henderson    def debugger_info(self):
731364750dSJames Henderson        return DebuggerIR(name=self.name, version=self.version)
741364750dSJames Henderson
751364750dSJames Henderson    @property
761364750dSJames Henderson    def is_available(self):
771364750dSJames Henderson        return self.has_loaded and self.loading_error is None
781364750dSJames Henderson
791364750dSJames Henderson    @property
801364750dSJames Henderson    def loading_error(self):
81*f98ee40fSTobias Hieta        return str(self._loading_error[1]) if self._loading_error is not None else None
821364750dSJames Henderson
831364750dSJames Henderson    @property
841364750dSJames Henderson    def loading_error_trace(self):
851364750dSJames Henderson        if not self._loading_error:
861364750dSJames Henderson            return None
871364750dSJames Henderson
881364750dSJames Henderson        tb = traceback.format_exception(*self._loading_error)
891364750dSJames Henderson
901364750dSJames Henderson        if self._loading_error[1].orig_exception is not None:
911364750dSJames Henderson            orig_exception = traceback.format_exception(
92*f98ee40fSTobias Hieta                *self._loading_error[1].orig_exception
93*f98ee40fSTobias Hieta            )
941364750dSJames Henderson
95*f98ee40fSTobias Hieta            if "".join(orig_exception) not in "".join(tb):
96*f98ee40fSTobias Hieta                tb.extend(["\n"])
971364750dSJames Henderson                tb.extend(orig_exception)
981364750dSJames Henderson
99*f98ee40fSTobias Hieta        tb = "".join(tb).splitlines(True)
1001364750dSJames Henderson        return tb
1011364750dSJames Henderson
1021364750dSJames Henderson    def _sanitize_function_name(self, name):  # pylint: disable=no-self-use
1031364750dSJames Henderson        """If the function name returned by the debugger needs any post-
1041364750dSJames Henderson        processing to make it fit (for example, if it includes a byte offset),
1051364750dSJames Henderson        do that here.
1061364750dSJames Henderson        """
1071364750dSJames Henderson        return name
1081364750dSJames Henderson
1091364750dSJames Henderson    @abc.abstractmethod
1101364750dSJames Henderson    def _load_interface(self):
1111364750dSJames Henderson        pass
1121364750dSJames Henderson
1131364750dSJames Henderson    @classmethod
1141364750dSJames Henderson    def get_option_name(cls):
1151364750dSJames Henderson        """Short name that will be used on the command line to specify this
1161364750dSJames Henderson        debugger.
1171364750dSJames Henderson        """
1181364750dSJames Henderson        raise NotImplementedError()
1191364750dSJames Henderson
1201364750dSJames Henderson    @classmethod
1211364750dSJames Henderson    def get_name(cls):
1221364750dSJames Henderson        """Full name of this debugger."""
1231364750dSJames Henderson        raise NotImplementedError()
1241364750dSJames Henderson
1251364750dSJames Henderson    @property
1261364750dSJames Henderson    def name(self):
1271364750dSJames Henderson        return self.__class__.get_name()
1281364750dSJames Henderson
1291364750dSJames Henderson    @property
1301364750dSJames Henderson    def option_name(self):
1311364750dSJames Henderson        return self.__class__.get_option_name()
1321364750dSJames Henderson
1331364750dSJames Henderson    @abc.abstractproperty
1341364750dSJames Henderson    def version(self):
1351364750dSJames Henderson        pass
1361364750dSJames Henderson
1371364750dSJames Henderson    @abc.abstractmethod
1381364750dSJames Henderson    def clear_breakpoints(self):
1391364750dSJames Henderson        pass
1401364750dSJames Henderson
1411364750dSJames Henderson    def add_breakpoint(self, file_, line):
1421364750dSJames Henderson        """Returns a unique opaque breakpoint id.
1431364750dSJames Henderson
1441364750dSJames Henderson        The ID type depends on the debugger being used, but will probably be
1451364750dSJames Henderson        an int.
1461364750dSJames Henderson        """
1471364750dSJames Henderson        return self._add_breakpoint(self._external_to_debug_path(file_), line)
1481364750dSJames Henderson
1491364750dSJames Henderson    @abc.abstractmethod
1501364750dSJames Henderson    def _add_breakpoint(self, file_, line):
151*f98ee40fSTobias Hieta        """Returns a unique opaque breakpoint id."""
1521364750dSJames Henderson        pass
1531364750dSJames Henderson
1541364750dSJames Henderson    def add_conditional_breakpoint(self, file_, line, condition):
1551364750dSJames Henderson        """Returns a unique opaque breakpoint id.
1561364750dSJames Henderson
1571364750dSJames Henderson        The ID type depends on the debugger being used, but will probably be
1581364750dSJames Henderson        an int.
1591364750dSJames Henderson        """
1601364750dSJames Henderson        return self._add_conditional_breakpoint(
161*f98ee40fSTobias Hieta            self._external_to_debug_path(file_), line, condition
162*f98ee40fSTobias Hieta        )
1631364750dSJames Henderson
1641364750dSJames Henderson    @abc.abstractmethod
1651364750dSJames Henderson    def _add_conditional_breakpoint(self, file_, line, condition):
166*f98ee40fSTobias Hieta        """Returns a unique opaque breakpoint id."""
1671364750dSJames Henderson        pass
1681364750dSJames Henderson
1691364750dSJames Henderson    @abc.abstractmethod
170b3f14802Sgbtozers    def delete_breakpoints(self, ids):
171b3f14802Sgbtozers        """Delete a set of breakpoints by ids.
1721364750dSJames Henderson
173b3f14802Sgbtozers        Raises a KeyError if, for any id, no breakpoint with that id exists.
1741364750dSJames Henderson        """
1751364750dSJames Henderson        pass
1761364750dSJames Henderson
1771364750dSJames Henderson    @abc.abstractmethod
1781364750dSJames Henderson    def get_triggered_breakpoint_ids(self):
179*f98ee40fSTobias Hieta        """Returns a set of opaque ids for just-triggered breakpoints."""
1801364750dSJames Henderson        pass
1811364750dSJames Henderson
1821364750dSJames Henderson    @abc.abstractmethod
1831364750dSJames Henderson    def launch(self):
1841364750dSJames Henderson        pass
1851364750dSJames Henderson
1861364750dSJames Henderson    @abc.abstractmethod
1871364750dSJames Henderson    def step(self):
1881364750dSJames Henderson        pass
1891364750dSJames Henderson
1901364750dSJames Henderson    @abc.abstractmethod
1911364750dSJames Henderson    def go(self) -> ReturnCode:
1921364750dSJames Henderson        pass
1931364750dSJames Henderson
1941364750dSJames Henderson    def get_step_info(self, watches, step_index):
1951364750dSJames Henderson        step_info = self._get_step_info(watches, step_index)
1961364750dSJames Henderson        for frame in step_info.frames:
1971364750dSJames Henderson            frame.loc.path = self._debug_to_external_path(frame.loc.path)
1981364750dSJames Henderson        return step_info
1991364750dSJames Henderson
2001364750dSJames Henderson    @abc.abstractmethod
2011364750dSJames Henderson    def _get_step_info(self, watches, step_index):
2021364750dSJames Henderson        pass
2031364750dSJames Henderson
2041364750dSJames Henderson    @abc.abstractproperty
2051364750dSJames Henderson    def is_running(self):
2061364750dSJames Henderson        pass
2071364750dSJames Henderson
2081364750dSJames Henderson    @abc.abstractproperty
2091364750dSJames Henderson    def is_finished(self):
2101364750dSJames Henderson        pass
2111364750dSJames Henderson
2121364750dSJames Henderson    @abc.abstractproperty
2131364750dSJames Henderson    def frames_below_main(self):
2141364750dSJames Henderson        pass
2151364750dSJames Henderson
2161364750dSJames Henderson    @abc.abstractmethod
2171364750dSJames Henderson    def evaluate_expression(self, expression, frame_idx=0) -> ValueIR:
2181364750dSJames Henderson        pass
2191364750dSJames Henderson
2201364750dSJames Henderson    def _external_to_debug_path(self, path):
2211364750dSJames Henderson        if not self.options.debugger_use_relative_paths:
2221364750dSJames Henderson            return path
2231364750dSJames Henderson        root_dir = self.options.source_root_dir
2241364750dSJames Henderson        if not root_dir or not path:
2251364750dSJames Henderson            return path
2261364750dSJames Henderson        assert path.startswith(root_dir)
2271364750dSJames Henderson        return path[len(root_dir) :].lstrip(os.path.sep)
2281364750dSJames Henderson
2291364750dSJames Henderson    def _debug_to_external_path(self, path):
2301364750dSJames Henderson        if not self.options.debugger_use_relative_paths:
2311364750dSJames Henderson            return path
2321364750dSJames Henderson        if not path or not self.options.source_root_dir:
2331364750dSJames Henderson            return path
2341364750dSJames Henderson        for file in self.options.source_files:
2351364750dSJames Henderson            if path.endswith(self._external_to_debug_path(file)):
2361364750dSJames Henderson                return file
2371364750dSJames Henderson        return path
2381364750dSJames Henderson
239*f98ee40fSTobias Hieta
2401364750dSJames Hendersonclass TestDebuggerBase(unittest.TestCase):
2411364750dSJames Henderson    class MockDebugger(DebuggerBase):
2421364750dSJames Henderson        def __init__(self, context, *args):
2431364750dSJames Henderson            super().__init__(context, *args)
2441364750dSJames Henderson            self.step_info = None
2451364750dSJames Henderson            self.breakpoint_file = None
2461364750dSJames Henderson
2471364750dSJames Henderson        def _add_breakpoint(self, file, line):
2481364750dSJames Henderson            self.breakpoint_file = file
2491364750dSJames Henderson
2501364750dSJames Henderson        def _get_step_info(self, watches, step_index):
2511364750dSJames Henderson            return self.step_info
2521364750dSJames Henderson
2531364750dSJames Henderson    def __init__(self, *args):
2541364750dSJames Henderson        super().__init__(*args)
2551364750dSJames Henderson        TestDebuggerBase.MockDebugger.__abstractmethods__ = set()
256*f98ee40fSTobias Hieta        self.options = SimpleNamespace(source_root_dir="", source_files=[])
2571364750dSJames Henderson        context = SimpleNamespace(options=self.options)
2581364750dSJames Henderson        self.dbg = TestDebuggerBase.MockDebugger(context)
2591364750dSJames Henderson
2601364750dSJames Henderson    def _new_step(self, paths):
2611364750dSJames Henderson        frames = [
2621364750dSJames Henderson            FrameIR(
2631364750dSJames Henderson                function=None,
2641364750dSJames Henderson                is_inlined=False,
265*f98ee40fSTobias Hieta                loc=LocIR(path=path, lineno=0, column=0),
266*f98ee40fSTobias Hieta            )
267*f98ee40fSTobias Hieta            for path in paths
2681364750dSJames Henderson        ]
2691364750dSJames Henderson        return StepIR(step_index=0, stop_reason=None, frames=frames)
2701364750dSJames Henderson
2711364750dSJames Henderson    def _step_paths(self, step):
2721364750dSJames Henderson        return [frame.loc.path for frame in step.frames]
2731364750dSJames Henderson
2741364750dSJames Henderson    def test_add_breakpoint_no_source_root_dir(self):
2751364750dSJames Henderson        self.options.debugger_use_relative_paths = True
276*f98ee40fSTobias Hieta        self.options.source_root_dir = ""
277*f98ee40fSTobias Hieta        path = os.path.join(os.path.sep + "root", "some_file")
2787c781621STom Weaver        self.dbg.add_breakpoint(path, 12)
2797c781621STom Weaver        self.assertEqual(path, self.dbg.breakpoint_file)
2801364750dSJames Henderson
2811364750dSJames Henderson    def test_add_breakpoint_with_source_root_dir(self):
2821364750dSJames Henderson        self.options.debugger_use_relative_paths = True
283*f98ee40fSTobias Hieta        self.options.source_root_dir = os.path.sep + "my_root"
284*f98ee40fSTobias Hieta        path = os.path.join(self.options.source_root_dir, "some_file")
2857c781621STom Weaver        self.dbg.add_breakpoint(path, 12)
286*f98ee40fSTobias Hieta        self.assertEqual("some_file", self.dbg.breakpoint_file)
2871364750dSJames Henderson
2881364750dSJames Henderson    def test_add_breakpoint_with_source_root_dir_slash_suffix(self):
2891364750dSJames Henderson        self.options.debugger_use_relative_paths = True
290*f98ee40fSTobias Hieta        self.options.source_root_dir = os.path.sep + "my_root" + os.path.sep
291*f98ee40fSTobias Hieta        path = os.path.join(self.options.source_root_dir, "some_file")
2927c781621STom Weaver        self.dbg.add_breakpoint(path, 12)
293*f98ee40fSTobias Hieta        self.assertEqual("some_file", self.dbg.breakpoint_file)
2941364750dSJames Henderson
2951364750dSJames Henderson    def test_get_step_info_no_source_root_dir(self):
2961364750dSJames Henderson        self.options.debugger_use_relative_paths = True
297*f98ee40fSTobias Hieta        path = os.path.join(os.path.sep + "root", "some_file")
2987c781621STom Weaver        self.dbg.step_info = self._new_step([path])
299*f98ee40fSTobias Hieta        self.assertEqual([path], self._step_paths(self.dbg.get_step_info([], 0)))
3001364750dSJames Henderson
3011364750dSJames Henderson    def test_get_step_info_no_frames(self):
3021364750dSJames Henderson        self.options.debugger_use_relative_paths = True
303*f98ee40fSTobias Hieta        self.options.source_root_dir = os.path.sep + "my_root"
3041364750dSJames Henderson        self.dbg.step_info = self._new_step([])
305*f98ee40fSTobias Hieta        self.assertEqual([], self._step_paths(self.dbg.get_step_info([], 0)))
3061364750dSJames Henderson
3071364750dSJames Henderson    def test_get_step_info(self):
3081364750dSJames Henderson        self.options.debugger_use_relative_paths = True
309*f98ee40fSTobias Hieta        self.options.source_root_dir = os.path.sep + "my_root"
310*f98ee40fSTobias Hieta        path = os.path.join(self.options.source_root_dir, "some_file")
3117c781621STom Weaver        self.options.source_files = [path]
312*f98ee40fSTobias Hieta        other_path = os.path.join(os.path.sep + "other", "file")
313*f98ee40fSTobias Hieta        dbg_path = os.path.join(os.path.sep + "dbg", "some_file")
314*f98ee40fSTobias Hieta        self.dbg.step_info = self._new_step([None, other_path, dbg_path])
315*f98ee40fSTobias Hieta        self.assertEqual(
316*f98ee40fSTobias Hieta            [None, other_path, path], self._step_paths(self.dbg.get_step_info([], 0))
317*f98ee40fSTobias Hieta        )
318