xref: /llvm-project/cross-project-tests/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py (revision ca92bdfa3ef8f9a1cc97167fc96601f8bd7b436b)
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"""Interface for communicating with the Visual Studio debugger via DTE."""
81364750dSJames Henderson
91364750dSJames Hendersonimport abc
101364750dSJames Hendersonimport os
111364750dSJames Hendersonimport sys
12832b91fcSOrlando Cazalet-Hyamsfrom enum import IntEnum
1373a01952SStephen Tozerfrom pathlib import PurePath, Path
147e46a721SStephen Tozerfrom collections import defaultdict, namedtuple
151364750dSJames Henderson
167e46a721SStephen Tozerfrom dex.command.CommandBase import StepExpectInfo
177e46a721SStephen Tozerfrom dex.debugger.DebuggerBase import DebuggerBase, watch_is_active
181364750dSJames Hendersonfrom dex.dextIR import FrameIR, LocIR, StepIR, StopReason, ValueIR
191364750dSJames Hendersonfrom dex.dextIR import StackFrame, SourceLocation, ProgramState
201364750dSJames Hendersonfrom dex.utils.Exceptions import Error, LoadDebuggerException
216779376eSStephen Tozerfrom dex.utils.Imports import load_module
221364750dSJames Hendersonfrom dex.utils.ReturnCode import ReturnCode
231364750dSJames Henderson
241364750dSJames Hendersondef _load_com_module():
251364750dSJames Henderson    try:
266779376eSStephen Tozer        return load_module(
2723309d7dSStephen Tozer            "ComInterface",
2823309d7dSStephen Tozer            os.path.join(os.path.dirname(__file__), "windows"),
2923309d7dSStephen Tozer            "ComInterface.py",
30f98ee40fSTobias Hieta        )
311364750dSJames Henderson    except ImportError as e:
321364750dSJames Henderson        raise LoadDebuggerException(e, sys.exc_info())
331364750dSJames Henderson
341364750dSJames Henderson
351364750dSJames Henderson# VSBreakpoint(path: PurePath, line: int, col: int, cond: str).  This is enough
361364750dSJames Henderson# info to identify breakpoint equivalence in visual studio based on the
371364750dSJames Henderson# properties we set through dexter currently.
38f98ee40fSTobias HietaVSBreakpoint = namedtuple("VSBreakpoint", "path, line, col, cond")
391364750dSJames Henderson
40f98ee40fSTobias Hieta
41832b91fcSOrlando Cazalet-Hyams# Visual Studio events.
42832b91fcSOrlando Cazalet-Hyams# https://learn.microsoft.com/en-us/dotnet/api/envdte.dbgeventreason?view=visualstudiosdk-2022
43832b91fcSOrlando Cazalet-Hyamsclass DbgEvent(IntEnum):
44832b91fcSOrlando Cazalet-Hyams    dbgEventReasonNone = 1
45832b91fcSOrlando Cazalet-Hyams    dbgEventReasonGo = 2
46832b91fcSOrlando Cazalet-Hyams    dbgEventReasonAttachProgram = 3
47832b91fcSOrlando Cazalet-Hyams    dbgEventReasonDetachProgram = 4
48832b91fcSOrlando Cazalet-Hyams    dbgEventReasonLaunchProgram = 5
49832b91fcSOrlando Cazalet-Hyams    dbgEventReasonEndProgram = 6
50832b91fcSOrlando Cazalet-Hyams    dbgEventReasonStopDebugging = 7
51832b91fcSOrlando Cazalet-Hyams    dbgEventReasonStep = 8
52832b91fcSOrlando Cazalet-Hyams    dbgEventReasonBreakpoint = 9
53832b91fcSOrlando Cazalet-Hyams    dbgEventReasonExceptionThrown = 10
54832b91fcSOrlando Cazalet-Hyams    dbgEventReasonExceptionNotHandled = 11
55832b91fcSOrlando Cazalet-Hyams    dbgEventReasonUserBreak = 12
56832b91fcSOrlando Cazalet-Hyams    dbgEventReasonContextSwitch = 13
57832b91fcSOrlando Cazalet-Hyams
58832b91fcSOrlando Cazalet-Hyams    first = dbgEventReasonNone
59832b91fcSOrlando Cazalet-Hyams    last = dbgEventReasonContextSwitch
60832b91fcSOrlando Cazalet-Hyams
61f98ee40fSTobias Hietaclass VisualStudio(
62f98ee40fSTobias Hieta    DebuggerBase, metaclass=abc.ABCMeta
63f98ee40fSTobias Hieta):  # pylint: disable=abstract-method
641364750dSJames Henderson    # Constants for results of Debugger.CurrentMode
651364750dSJames Henderson    # (https://msdn.microsoft.com/en-us/library/envdte.debugger.currentmode.aspx)
661364750dSJames Henderson    dbgDesignMode = 1
671364750dSJames Henderson    dbgBreakMode = 2
681364750dSJames Henderson    dbgRunMode = 3
691364750dSJames Henderson
701364750dSJames Henderson    def __init__(self, *args):
711364750dSJames Henderson        self.com_module = None
721364750dSJames Henderson        self._debugger = None
731364750dSJames Henderson        self._solution = None
741364750dSJames Henderson        self._fn_step = None
751364750dSJames Henderson        self._fn_go = None
761364750dSJames Henderson        # The next available unique breakpoint id. Use self._get_next_id().
771364750dSJames Henderson        self._next_bp_id = 0
781364750dSJames Henderson        # VisualStudio appears to common identical breakpoints. That is, if you
791364750dSJames Henderson        # ask for a breakpoint that already exists the Breakpoints list will
801364750dSJames Henderson        # not grow. DebuggerBase requires all breakpoints have a unique id,
811364750dSJames Henderson        # even for duplicates, so we'll need to do some bookkeeping.  Map
821364750dSJames Henderson        # {VSBreakpoint: list(id)} where id is the unique dexter-side id for
831364750dSJames Henderson        # the requested breakpoint.
841364750dSJames Henderson        self._vs_to_dex_ids = defaultdict(list)
851364750dSJames Henderson        # Map {id: VSBreakpoint} where id is unique and VSBreakpoint identifies
861364750dSJames Henderson        # a breakpoint in Visual Studio. There may be many ids mapped to a
871364750dSJames Henderson        # single VSBreakpoint. Use self._vs_to_dex_ids to find (dexter)
881364750dSJames Henderson        # breakpoints mapped to the same visual studio breakpoint.
891364750dSJames Henderson        self._dex_id_to_vs = {}
901364750dSJames Henderson
911364750dSJames Henderson        super(VisualStudio, self).__init__(*args)
921364750dSJames Henderson
9375b31692SStephen Tozer    def _create_solution(self):
94f98ee40fSTobias Hieta        self._solution.Create(self.context.working_directory.path, "DexterSolution")
9575b31692SStephen Tozer        try:
9675b31692SStephen Tozer            self._solution.AddFromFile(self._project_file)
9775b31692SStephen Tozer        except OSError:
9875b31692SStephen Tozer            raise LoadDebuggerException(
99f98ee40fSTobias Hieta                "could not debug the specified executable", sys.exc_info()
100f98ee40fSTobias Hieta            )
10175b31692SStephen Tozer
10275b31692SStephen Tozer    def _load_solution(self):
10375b31692SStephen Tozer        try:
10475b31692SStephen Tozer            self._solution.Open(self.context.options.vs_solution)
10575b31692SStephen Tozer        except:
10675b31692SStephen Tozer            raise LoadDebuggerException(
107f98ee40fSTobias Hieta                "could not load specified vs solution at {}".format(
108f98ee40fSTobias Hieta                    self.context.options.vs_solution
109f98ee40fSTobias Hieta                ),
110f98ee40fSTobias Hieta                sys.exc_info(),
111f98ee40fSTobias Hieta            )
11275b31692SStephen Tozer
1131364750dSJames Henderson    def _custom_init(self):
1141364750dSJames Henderson        try:
1151364750dSJames Henderson            self._debugger = self._interface.Debugger
1161364750dSJames Henderson            self._debugger.HexDisplayMode = False
1171364750dSJames Henderson
118f98ee40fSTobias Hieta            self._interface.MainWindow.Visible = self.context.options.show_debugger
1191364750dSJames Henderson
1201364750dSJames Henderson            self._solution = self._interface.Solution
12175b31692SStephen Tozer            if self.context.options.vs_solution is None:
12275b31692SStephen Tozer                self._create_solution()
12375b31692SStephen Tozer            else:
12475b31692SStephen Tozer                self._load_solution()
1251364750dSJames Henderson
1261364750dSJames Henderson            self._fn_step = self._debugger.StepInto
1271364750dSJames Henderson            self._fn_go = self._debugger.Go
1281364750dSJames Henderson
1291364750dSJames Henderson        except AttributeError as e:
1301364750dSJames Henderson            raise LoadDebuggerException(str(e), sys.exc_info())
1311364750dSJames Henderson
1321364750dSJames Henderson    def _custom_exit(self):
1331364750dSJames Henderson        if self._interface:
1341364750dSJames Henderson            self._interface.Quit()
1351364750dSJames Henderson
1361364750dSJames Henderson    @property
1371364750dSJames Henderson    def _project_file(self):
1381364750dSJames Henderson        return self.context.options.executable
1391364750dSJames Henderson
1401364750dSJames Henderson    @abc.abstractproperty
1411364750dSJames Henderson    def _dte_version(self):
1421364750dSJames Henderson        pass
1431364750dSJames Henderson
1441364750dSJames Henderson    @property
1451364750dSJames Henderson    def _location(self):
1461364750dSJames Henderson        # TODO: Find a better way of determining path, line and column info
1471364750dSJames Henderson        # that doesn't require reading break points. This method requires
1481364750dSJames Henderson        # all lines to have a break point on them.
1491364750dSJames Henderson        bp = self._debugger.BreakpointLastHit
1501364750dSJames Henderson        return {
151f98ee40fSTobias Hieta            "path": getattr(bp, "File", None),
152f98ee40fSTobias Hieta            "lineno": getattr(bp, "FileLine", None),
153f98ee40fSTobias Hieta            "column": getattr(bp, "FileColumn", None),
1541364750dSJames Henderson        }
1551364750dSJames Henderson
1561364750dSJames Henderson    @property
1571364750dSJames Henderson    def _mode(self):
1581364750dSJames Henderson        return self._debugger.CurrentMode
1591364750dSJames Henderson
1601364750dSJames Henderson    def _load_interface(self):
1611364750dSJames Henderson        self.com_module = _load_com_module()
1621364750dSJames Henderson        return self.com_module.DTE(self._dte_version)
1631364750dSJames Henderson
1641364750dSJames Henderson    @property
1651364750dSJames Henderson    def version(self):
1661364750dSJames Henderson        try:
1671364750dSJames Henderson            return self._interface.Version
1681364750dSJames Henderson        except AttributeError:
1691364750dSJames Henderson            return None
1701364750dSJames Henderson
1711364750dSJames Henderson    def clear_breakpoints(self):
1721364750dSJames Henderson        for bp in self._debugger.Breakpoints:
1731364750dSJames Henderson            bp.Delete()
1741364750dSJames Henderson        self._vs_to_dex_ids.clear()
1751364750dSJames Henderson        self._dex_id_to_vs.clear()
1761364750dSJames Henderson
1771364750dSJames Henderson    def _add_breakpoint(self, file_, line):
178f98ee40fSTobias Hieta        return self._add_conditional_breakpoint(file_, line, "")
1791364750dSJames Henderson
1801364750dSJames Henderson    def _get_next_id(self):
1811364750dSJames Henderson        # "Generate" a new unique id for the breakpoint.
1821364750dSJames Henderson        id = self._next_bp_id
1831364750dSJames Henderson        self._next_bp_id += 1
1841364750dSJames Henderson        return id
1851364750dSJames Henderson
1861364750dSJames Henderson    def _add_conditional_breakpoint(self, file_, line, condition):
1871364750dSJames Henderson        col = 1
1881364750dSJames Henderson        vsbp = VSBreakpoint(PurePath(file_), line, col, condition)
1891364750dSJames Henderson        new_id = self._get_next_id()
1901364750dSJames Henderson
1911364750dSJames Henderson        # Do we have an exact matching breakpoint already?
1921364750dSJames Henderson        if vsbp in self._vs_to_dex_ids:
1931364750dSJames Henderson            self._vs_to_dex_ids[vsbp].append(new_id)
1941364750dSJames Henderson            self._dex_id_to_vs[new_id] = vsbp
1951364750dSJames Henderson            return new_id
1961364750dSJames Henderson
1971364750dSJames Henderson        # Breakpoint doesn't exist already. Add it now.
1981364750dSJames Henderson        count_before = self._debugger.Breakpoints.Count
199f98ee40fSTobias Hieta        self._debugger.Breakpoints.Add("", file_, line, col, condition)
2001364750dSJames Henderson        # Our internal representation of VS says that the breakpoint doesn't
2011364750dSJames Henderson        # already exist so we do not expect this operation to fail here.
2021364750dSJames Henderson        assert count_before < self._debugger.Breakpoints.Count
2031364750dSJames Henderson        # We've added a new breakpoint, record its id.
2041364750dSJames Henderson        self._vs_to_dex_ids[vsbp].append(new_id)
2051364750dSJames Henderson        self._dex_id_to_vs[new_id] = vsbp
2061364750dSJames Henderson        return new_id
2071364750dSJames Henderson
2081364750dSJames Henderson    def get_triggered_breakpoint_ids(self):
209f98ee40fSTobias Hieta        """Returns a set of opaque ids for just-triggered breakpoints."""
2101364750dSJames Henderson        bps_hit = self._debugger.AllBreakpointsLastHit
2111364750dSJames Henderson        bp_id_list = []
2121364750dSJames Henderson        # Intuitively, AllBreakpointsLastHit breakpoints are the last hit
2131364750dSJames Henderson        # _bound_ breakpoints. A bound breakpoint's parent holds the info of
2141364750dSJames Henderson        # the breakpoint the user requested. Our internal state tracks the user
2151364750dSJames Henderson        # requested breakpoints so we look at the Parent of these triggered
2161364750dSJames Henderson        # breakpoints to determine which have been hit.
2171364750dSJames Henderson        for bp in bps_hit:
2181364750dSJames Henderson            # All bound breakpoints should have the user-defined breakpoint as
2191364750dSJames Henderson            # a parent.
2201364750dSJames Henderson            assert bp.Parent
221f98ee40fSTobias Hieta            vsbp = VSBreakpoint(
222f98ee40fSTobias Hieta                PurePath(bp.Parent.File),
223f98ee40fSTobias Hieta                bp.Parent.FileLine,
224f98ee40fSTobias Hieta                bp.Parent.FileColumn,
225f98ee40fSTobias Hieta                bp.Parent.Condition,
226f98ee40fSTobias Hieta            )
2271364750dSJames Henderson            try:
2281364750dSJames Henderson                ids = self._vs_to_dex_ids[vsbp]
2291364750dSJames Henderson            except KeyError:
2301364750dSJames Henderson                pass
2311364750dSJames Henderson            else:
2321364750dSJames Henderson                bp_id_list += ids
2331364750dSJames Henderson        return set(bp_id_list)
2341364750dSJames Henderson
235b3f14802Sgbtozers    def delete_breakpoints(self, ids):
236b3f14802Sgbtozers        """Delete breakpoints by their ids.
2371364750dSJames Henderson
2381364750dSJames Henderson        Raises a KeyError if no breakpoint with this id exists.
2391364750dSJames Henderson        """
240b3f14802Sgbtozers        vsbp_set = set()
241b3f14802Sgbtozers        for id in ids:
2421364750dSJames Henderson            vsbp = self._dex_id_to_vs[id]
2431364750dSJames Henderson
2441364750dSJames Henderson            # Remove our id from the associated list of dex ids.
2451364750dSJames Henderson            self._vs_to_dex_ids[vsbp].remove(id)
2461364750dSJames Henderson            del self._dex_id_to_vs[id]
2471364750dSJames Henderson
2481364750dSJames Henderson            # Bail if there are other uses of this vsbp.
2491364750dSJames Henderson            if len(self._vs_to_dex_ids[vsbp]) > 0:
250b3f14802Sgbtozers                continue
2511364750dSJames Henderson            # Otherwise find and delete it.
252b3f14802Sgbtozers            vsbp_set.add(vsbp)
253b3f14802Sgbtozers
254b3f14802Sgbtozers        vsbp_to_del_count = len(vsbp_set)
255b3f14802Sgbtozers
2561364750dSJames Henderson        for bp in self._debugger.Breakpoints:
257b3f14802Sgbtozers            # We're looking at the user-set breakpoints so there should be no
2581364750dSJames Henderson            # Parent.
259*ca92bdfaSEisuke Kawashima            assert bp.Parent is None
260f98ee40fSTobias Hieta            this_vsbp = VSBreakpoint(
261f98ee40fSTobias Hieta                PurePath(bp.File), bp.FileLine, bp.FileColumn, bp.Condition
262f98ee40fSTobias Hieta            )
263b3f14802Sgbtozers            if this_vsbp in vsbp_set:
2641364750dSJames Henderson                bp.Delete()
265b3f14802Sgbtozers                vsbp_to_del_count -= 1
266b3f14802Sgbtozers                if vsbp_to_del_count == 0:
2671364750dSJames Henderson                    break
268b3f14802Sgbtozers        if vsbp_to_del_count:
269f98ee40fSTobias Hieta            raise KeyError("did not find breakpoint to be deleted")
2701364750dSJames Henderson
2713a094d8bSJeremy Morse    def _fetch_property(self, props, name):
2723a094d8bSJeremy Morse        num_props = props.Count
2733a094d8bSJeremy Morse        result = None
2743a094d8bSJeremy Morse        for x in range(1, num_props + 1):
2753a094d8bSJeremy Morse            item = props.Item(x)
2763a094d8bSJeremy Morse            if item.Name == name:
2773a094d8bSJeremy Morse                return item
2783a094d8bSJeremy Morse        assert False, "Couldn't find property {}".format(name)
2793a094d8bSJeremy Morse
2803a094d8bSJeremy Morse    def launch(self, cmdline):
28173a01952SStephen Tozer        exe_path = Path(self.context.options.executable)
28273a01952SStephen Tozer        self.context.logger.note(f"VS: Using executable: '{exe_path}'")
283f98ee40fSTobias Hieta        cmdline_str = " ".join(cmdline)
2842e7f3393SStephen Tozer        if self.context.options.target_run_args:
2852e7f3393SStephen Tozer            cmdline_str += f" {self.context.options.target_run_args}"
28673a01952SStephen Tozer        if cmdline_str:
28773a01952SStephen Tozer            self.context.logger.note(f"VS: Using executable args: '{cmdline_str}'")
2883a094d8bSJeremy Morse
2893a094d8bSJeremy Morse        # In a slightly baroque manner, lookup the VS project that runs when
2903a094d8bSJeremy Morse        # you click "run", and set its command line options to the desired
2913a094d8bSJeremy Morse        # command line options.
292f98ee40fSTobias Hieta        startup_proj_name = str(
293f98ee40fSTobias Hieta            self._fetch_property(self._interface.Solution.Properties, "StartupProject")
294f98ee40fSTobias Hieta        )
2953a094d8bSJeremy Morse        project = self._fetch_property(self._interface.Solution, startup_proj_name)
296f98ee40fSTobias Hieta        ActiveConfiguration = self._fetch_property(
297f98ee40fSTobias Hieta            project.Properties, "ActiveConfiguration"
298f98ee40fSTobias Hieta        ).Object
2993a094d8bSJeremy Morse        ActiveConfiguration.DebugSettings.CommandArguments = cmdline_str
300e1c0e7e5SStephen Tozer        ConfigurationName = ActiveConfiguration.ConfigurationName
301e1c0e7e5SStephen Tozer        SolConfig = self._fetch_property(
302e1c0e7e5SStephen Tozer            self._interface.Solution.SolutionBuild.SolutionConfigurations,
303e1c0e7e5SStephen Tozer            ConfigurationName,
304e1c0e7e5SStephen Tozer        )
305e1c0e7e5SStephen Tozer        for Context in SolConfig.SolutionContexts:
306e1c0e7e5SStephen Tozer            Context.ShouldBuild = False
3073a094d8bSJeremy Morse
30873a01952SStephen Tozer        self.context.logger.note("Launching VS debugger...")
3096376c5b9SStephen Tozer        self._fn_go(False)
3101364750dSJames Henderson
3111364750dSJames Henderson    def step(self):
3126376c5b9SStephen Tozer        self._fn_step(False)
3131364750dSJames Henderson
3141364750dSJames Henderson    def go(self) -> ReturnCode:
3156376c5b9SStephen Tozer        self._fn_go(False)
3161364750dSJames Henderson        return ReturnCode.OK
3171364750dSJames Henderson
3181364750dSJames Henderson    def set_current_stack_frame(self, idx: int = 0):
3191364750dSJames Henderson        thread = self._debugger.CurrentThread
3201364750dSJames Henderson        stack_frames = thread.StackFrames
3211364750dSJames Henderson        try:
3221364750dSJames Henderson            stack_frame = stack_frames[idx]
3231364750dSJames Henderson            self._debugger.CurrentStackFrame = stack_frame.raw
3241364750dSJames Henderson        except IndexError:
325f98ee40fSTobias Hieta            raise Error(
326f98ee40fSTobias Hieta                "attempted to access stack frame {} out of {}".format(
327f98ee40fSTobias Hieta                    idx, len(stack_frames)
328f98ee40fSTobias Hieta                )
329f98ee40fSTobias Hieta            )
3301364750dSJames Henderson
331832b91fcSOrlando Cazalet-Hyams    def _translate_stop_reason(self, reason):
332832b91fcSOrlando Cazalet-Hyams        if reason == DbgEvent.dbgEventReasonNone:
333832b91fcSOrlando Cazalet-Hyams            return None
334832b91fcSOrlando Cazalet-Hyams        if reason == DbgEvent.dbgEventReasonBreakpoint:
335832b91fcSOrlando Cazalet-Hyams            return StopReason.BREAKPOINT
336832b91fcSOrlando Cazalet-Hyams        if reason == DbgEvent.dbgEventReasonStep:
337832b91fcSOrlando Cazalet-Hyams            return StopReason.STEP
338832b91fcSOrlando Cazalet-Hyams        if reason == DbgEvent.dbgEventReasonEndProgram:
339832b91fcSOrlando Cazalet-Hyams            return StopReason.PROGRAM_EXIT
340832b91fcSOrlando Cazalet-Hyams        if reason == DbgEvent.dbgEventReasonExceptionNotHandled:
341832b91fcSOrlando Cazalet-Hyams            return StopReason.ERROR
342832b91fcSOrlando Cazalet-Hyams        assert reason <= DbgEvent.last and reason >= DbgEvent.first
343832b91fcSOrlando Cazalet-Hyams        return StopReason.OTHER
344832b91fcSOrlando Cazalet-Hyams
3451364750dSJames Henderson    def _get_step_info(self, watches, step_index):
3461364750dSJames Henderson        thread = self._debugger.CurrentThread
3471364750dSJames Henderson        stackframes = thread.StackFrames
3481364750dSJames Henderson
3491364750dSJames Henderson        frames = []
3501364750dSJames Henderson        state_frames = []
3511364750dSJames Henderson
3527e46a721SStephen Tozer        loc = LocIR(**self._location)
3537e46a721SStephen Tozer        valid_loc_for_watch = loc.path and os.path.exists(loc.path)
3547e46a721SStephen Tozer
3551364750dSJames Henderson        for idx, sf in enumerate(stackframes):
3561364750dSJames Henderson            frame = FrameIR(
3571364750dSJames Henderson                function=self._sanitize_function_name(sf.FunctionName),
358f98ee40fSTobias Hieta                is_inlined=sf.FunctionName.startswith("[Inline Frame]"),
359f98ee40fSTobias Hieta                loc=LocIR(path=None, lineno=None, column=None),
360f98ee40fSTobias Hieta            )
3611364750dSJames Henderson
362f98ee40fSTobias Hieta            fname = frame.function or ""  # pylint: disable=no-member
3631364750dSJames Henderson            if any(name in fname for name in self.frames_below_main):
3641364750dSJames Henderson                break
3651364750dSJames Henderson
366f98ee40fSTobias Hieta            state_frame = StackFrame(
367f98ee40fSTobias Hieta                function=frame.function, is_inlined=frame.is_inlined, watches={}
368f98ee40fSTobias Hieta            )
3691364750dSJames Henderson
3707e46a721SStephen Tozer            if valid_loc_for_watch and idx == 0:
3717e46a721SStephen Tozer                for watch_info in watches:
3727e46a721SStephen Tozer                    if watch_is_active(watch_info, loc.path, idx, loc.lineno):
3737e46a721SStephen Tozer                        watch_expr = watch_info.expression
374f98ee40fSTobias Hieta                        state_frame.watches[watch_expr] = self.evaluate_expression(
375f98ee40fSTobias Hieta                            watch_expr, idx
376f98ee40fSTobias Hieta                        )
3771364750dSJames Henderson
3781364750dSJames Henderson            state_frames.append(state_frame)
3791364750dSJames Henderson            frames.append(frame)
3801364750dSJames Henderson
3811364750dSJames Henderson        if frames:
3821364750dSJames Henderson            frames[0].loc = loc
3831364750dSJames Henderson            state_frames[0].location = SourceLocation(**self._location)
3841364750dSJames Henderson
385832b91fcSOrlando Cazalet-Hyams        stop_reason = self._translate_stop_reason(self._debugger.LastBreakReason)
3861364750dSJames Henderson        program_state = ProgramState(frames=state_frames)
3871364750dSJames Henderson
3881364750dSJames Henderson        return StepIR(
389f98ee40fSTobias Hieta            step_index=step_index,
390f98ee40fSTobias Hieta            frames=frames,
391832b91fcSOrlando Cazalet-Hyams            stop_reason=stop_reason,
392f98ee40fSTobias Hieta            program_state=program_state,
393f98ee40fSTobias Hieta        )
3941364750dSJames Henderson
3951364750dSJames Henderson    @property
3961364750dSJames Henderson    def is_running(self):
3971364750dSJames Henderson        return self._mode == VisualStudio.dbgRunMode
3981364750dSJames Henderson
3991364750dSJames Henderson    @property
4001364750dSJames Henderson    def is_finished(self):
4011364750dSJames Henderson        return self._mode == VisualStudio.dbgDesignMode
4021364750dSJames Henderson
4031364750dSJames Henderson    @property
4041364750dSJames Henderson    def frames_below_main(self):
4051364750dSJames Henderson        return [
406f98ee40fSTobias Hieta            "[Inline Frame] invoke_main",
407f98ee40fSTobias Hieta            "__scrt_common_main_seh",
408f98ee40fSTobias Hieta            "__tmainCRTStartup",
409f98ee40fSTobias Hieta            "mainCRTStartup",
4101364750dSJames Henderson        ]
4111364750dSJames Henderson
4121364750dSJames Henderson    def evaluate_expression(self, expression, frame_idx=0) -> ValueIR:
4137e46a721SStephen Tozer        if frame_idx != 0:
4141364750dSJames Henderson            self.set_current_stack_frame(frame_idx)
4151364750dSJames Henderson        result = self._debugger.GetExpression(expression)
4167e46a721SStephen Tozer        if frame_idx != 0:
4171364750dSJames Henderson            self.set_current_stack_frame(0)
4181364750dSJames Henderson        value = result.Value
4191364750dSJames Henderson
420f98ee40fSTobias Hieta        is_optimized_away = any(
421f98ee40fSTobias Hieta            s in value
422f98ee40fSTobias Hieta            for s in [
423f98ee40fSTobias Hieta                "Variable is optimized away and not available",
424f98ee40fSTobias Hieta                "Value is not available, possibly due to optimization",
425f98ee40fSTobias Hieta            ]
426f98ee40fSTobias Hieta        )
4271364750dSJames Henderson
428f98ee40fSTobias Hieta        is_irretrievable = any(
429f98ee40fSTobias Hieta            s in value
430f98ee40fSTobias Hieta            for s in [
431f98ee40fSTobias Hieta                "???",
432f98ee40fSTobias Hieta                "<Unable to read memory>",
433f98ee40fSTobias Hieta            ]
434f98ee40fSTobias Hieta        )
4351364750dSJames Henderson
4361364750dSJames Henderson        # an optimized away value is still counted as being able to be
4371364750dSJames Henderson        # evaluated.
438f98ee40fSTobias Hieta        could_evaluate = result.IsValidValue or is_optimized_away or is_irretrievable
4391364750dSJames Henderson
4401364750dSJames Henderson        return ValueIR(
4411364750dSJames Henderson            expression=expression,
4421364750dSJames Henderson            value=value,
4431364750dSJames Henderson            type_name=result.Type,
4441364750dSJames Henderson            error_string=None,
4451364750dSJames Henderson            is_optimized_away=is_optimized_away,
4461364750dSJames Henderson            could_evaluate=could_evaluate,
4471364750dSJames Henderson            is_irretrievable=is_irretrievable,
4481364750dSJames Henderson        )
449