+++ /dev/null
-diff --git a/debuginfo-tests/dexter/Commands.md b/debuginfo-tests/dexter/Commands.md
-index c30a0d7214c..2e2fecfed92 100644
---- a/debuginfo-tests/dexter/Commands.md
-+++ b/debuginfo-tests/dexter/Commands.md
-@@ -173,6 +173,34 @@ Expect the source line this is found on will never be stepped on to.
- [TODO]
-
-
-+----
-+## DexLimitSteps
-+ DexLimitSteps(expr, *values [, **from_line=1],[,**to_line=Max]
-+ [,**on_line])
-+
-+ Args:
-+ expr (str): variable or value to compare.
-+
-+ Arg list:
-+ values (str): At least one potential value the expr may evaluate to.
-+
-+ Keyword args:
-+ from_line (int): Define the start of the limited step range.
-+ to_line (int): Define the end of the limited step range.
-+ on_line (int): Define a range with length 1 starting and ending on the
-+ same line.
-+
-+### Description
-+Define a limited stepping range that is predicated on a condition. When
-+'(expr) == (values[n])', set a range of temporary, unconditional break points within
-+the test file defined by the range from_line and to_line or on_line.
-+
-+The condition is only evaluated on the line 'from_line' or 'on_line'. If the
-+condition is not true at the start of the range, the whole range is ignored.
-+
-+DexLimitSteps commands are useful for reducing the amount of steps gathered in
-+large test cases that would normally take much longer to complete.
-+
- ----
- ## DexLabel
- DexLabel(name)
-diff --git a/debuginfo-tests/dexter/dex/command/ParseCommand.py b/debuginfo-tests/dexter/dex/command/ParseCommand.py
-index 4cc9ae12592..8246ea9e3cf 100644
---- a/debuginfo-tests/dexter/dex/command/ParseCommand.py
-+++ b/debuginfo-tests/dexter/dex/command/ParseCommand.py
-@@ -24,6 +24,7 @@ from dex.command.commands.DexExpectStepOrder import DexExpectStepOrder
- from dex.command.commands.DexExpectWatchType import DexExpectWatchType
- from dex.command.commands.DexExpectWatchValue import DexExpectWatchValue
- from dex.command.commands.DexLabel import DexLabel
-+from dex.command.commands.DexLimitSteps import DexLimitSteps
- from dex.command.commands.DexUnreachable import DexUnreachable
- from dex.command.commands.DexWatch import DexWatch
- from dex.utils import Timer
-@@ -42,6 +43,7 @@ def _get_valid_commands():
- DexExpectWatchType.get_name() : DexExpectWatchType,
- DexExpectWatchValue.get_name() : DexExpectWatchValue,
- DexLabel.get_name() : DexLabel,
-+ DexLimitSteps.get_name() : DexLimitSteps,
- DexUnreachable.get_name() : DexUnreachable,
- DexWatch.get_name() : DexWatch
- }
-diff --git a/debuginfo-tests/dexter/dex/command/commands/DexLimitSteps.py b/debuginfo-tests/dexter/dex/command/commands/DexLimitSteps.py
-new file mode 100644
-index 00000000000..d66401b5599
---- /dev/null
-+++ b/debuginfo-tests/dexter/dex/command/commands/DexLimitSteps.py
-@@ -0,0 +1,54 @@
-+# 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
-+"""A Command that enables test writers to specify a limited number of break
-+points using an start condition and range.
-+"""
-+
-+from dex.command.CommandBase import CommandBase
-+
-+class DexLimitSteps(CommandBase):
-+ def __init__(self, *args, **kwargs):
-+ self.expression = args[0]
-+ self.values = [str(arg) for arg in args[1:]]
-+ try:
-+ on_line = kwargs.pop('on_line')
-+ self.from_line = on_line
-+ self.to_line = on_line
-+ except KeyError:
-+ self.from_line = kwargs.pop('from_line', 1)
-+ self.to_line = kwargs.pop('to_line', 999999)
-+ if kwargs:
-+ raise TypeError('unexpected named args: {}'.format(
-+ ', '.join(kwargs)))
-+ super(DexLimitSteps, self).__init__()
-+
-+ def resolve_label(self, label_line_pair):
-+ label, lineno = label_line_pair
-+ if isinstance(self.from_line, str):
-+ if self.from_line == label:
-+ self.from_line = lineno
-+ if isinstance(self.to_line, str):
-+ if self.to_line == label:
-+ self.to_line = lineno
-+
-+ def has_labels(self):
-+ return len(self.get_label_args()) > 0
-+
-+ def get_label_args(self):
-+ return [label for label in (self.from_line, self.to_line)
-+ if isinstance(label, str)]
-+
-+ def eval(self):
-+ raise NotImplementedError('DexLimitSteps commands cannot be evaled.')
-+
-+ @staticmethod
-+ def get_name():
-+ return __class__.__name__
-+
-+ @staticmethod
-+ def get_subcommands() -> dict:
-+ return None
-diff --git a/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py b/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py
-index 2261396b94b..12f4f4ab7a0 100644
---- a/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py
-+++ b/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py
-@@ -120,6 +120,14 @@ class DebuggerBase(object, metaclass=abc.ABCMeta):
- def add_breakpoint(self, file_, line):
- pass
-
-+ @abc.abstractmethod
-+ def add_conditional_breakpoint(self, file_, line, condition):
-+ pass
-+
-+ @abc.abstractmethod
-+ def delete_conditional_breakpoint(self, file_, line, condition):
-+ pass
-+
- @abc.abstractmethod
- def launch(self):
- pass
-diff --git a/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py b/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py
-new file mode 100644
-index 00000000000..4e4327b53f8
---- /dev/null
-+++ b/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py
-@@ -0,0 +1,127 @@
-+# 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
-+"""Conditional Controller Class for DExTer.-"""
-+
-+
-+import os
-+import time
-+from collections import defaultdict
-+from itertools import chain
-+
-+from dex.debugger.DebuggerControllers.ControllerHelpers import in_source_file, update_step_watches
-+from dex.debugger.DebuggerControllers.DebuggerControllerBase import DebuggerControllerBase
-+from dex.debugger.DebuggerBase import DebuggerBase
-+from dex.utils.Exceptions import DebuggerException
-+
-+
-+class ConditionalBpRange:
-+ """Represents a conditional range of breakpoints within a source file descending from
-+ one line to another."""
-+
-+ def __init__(self, expression: str, path: str, range_from: int, range_to: int, values: list):
-+ self.expression = expression
-+ self.path = path
-+ self.range_from = range_from
-+ self.range_to = range_to
-+ self.conditional_values = values
-+
-+ def get_conditional_expression_list(self):
-+ conditional_list = []
-+ for value in self.conditional_values:
-+ # (<expression>) == (<value>)
-+ conditional_expression = '({}) == ({})'.format(self.expression, value)
-+ conditional_list.append(conditional_expression)
-+ return conditional_list
-+
-+
-+class ConditionalController(DebuggerControllerBase):
-+ def __init__(self, context, step_collection):
-+ self.context = context
-+ self.step_collection = step_collection
-+ self._conditional_bps = None
-+ self._watches = set()
-+ self._step_index = 0
-+ self._build_conditional_bps()
-+ self._path_and_line_to_conditional_bp = defaultdict(list)
-+ self._pause_between_steps = context.options.pause_between_steps
-+ self._max_steps = context.options.max_steps
-+
-+ def _build_conditional_bps(self):
-+ commands = self.step_collection.commands
-+ self._conditional_bps = []
-+ try:
-+ limit_commands = commands['DexLimitSteps']
-+ for lc in limit_commands:
-+ conditional_bp = ConditionalBpRange(
-+ lc.expression,
-+ lc.path,
-+ lc.from_line,
-+ lc.to_line,
-+ lc.values)
-+ self._conditional_bps.append(conditional_bp)
-+ except KeyError:
-+ raise DebuggerException('Missing DexLimitSteps commands, cannot conditionally step.')
-+
-+ def _set_conditional_bps(self):
-+ # When we break in the debugger we need a quick and easy way to look up
-+ # which conditional bp we've breaked on.
-+ for cbp in self._conditional_bps:
-+ conditional_bp_list = self._path_and_line_to_conditional_bp[(cbp.path, cbp.range_from)]
-+ conditional_bp_list.append(cbp)
-+
-+ # Set break points only on the first line of any conditional range, we'll set
-+ # more break points for a range when the condition is satisfied.
-+ for cbp in self._conditional_bps:
-+ for cond_expr in cbp.get_conditional_expression_list():
-+ self.debugger.add_conditional_breakpoint(cbp.path, cbp.range_from, cond_expr)
-+
-+ def _conditional_met(self, cbp):
-+ for cond_expr in cbp.get_conditional_expression_list():
-+ valueIR = self.debugger.evaluate_expression(cond_expr)
-+ if valueIR.type_name == 'bool' and valueIR.value == 'true':
-+ return True
-+ return False
-+
-+ def _run_debugger_custom(self):
-+ # TODO: Add conditional and unconditional breakpoint support to dbgeng.
-+ if self.debugger.get_name() == 'dbgeng':
-+ raise DebuggerException('DexLimitSteps commands are not supported by dbgeng')
-+
-+ self.step_collection.clear_steps()
-+ self._set_conditional_bps()
-+
-+ for command_obj in chain.from_iterable(self.step_collection.commands.values()):
-+ self._watches.update(command_obj.get_watches())
-+
-+ self.debugger.launch()
-+ time.sleep(self._pause_between_steps)
-+ while not self.debugger.is_finished:
-+ while self.debugger.is_running:
-+ pass
-+
-+ step_info = self.debugger.get_step_info(self._watches, self._step_index)
-+ if step_info.current_frame:
-+ self._step_index += 1
-+ update_step_watches(step_info, self._watches, self.step_collection.commands)
-+ self.step_collection.new_step(self.context, step_info)
-+
-+ loc = step_info.current_location
-+ conditional_bp_key = (loc.path, loc.lineno)
-+ if conditional_bp_key in self._path_and_line_to_conditional_bp:
-+
-+ conditional_bps = self._path_and_line_to_conditional_bp[conditional_bp_key]
-+ for cbp in conditional_bps:
-+ if self._conditional_met(cbp):
-+ # Unconditional range should ignore first line as that's the
-+ # conditional bp we just hit and should be inclusive of final line
-+ for line in range(cbp.range_from + 1, cbp.range_to + 1):
-+ self.debugger.add_conditional_breakpoint(cbp.path, line, condition='')
-+
-+ # Clear any uncondtional break points at this loc.
-+ self.debugger.delete_conditional_breakpoint(file_=loc.path, line=loc.lineno, condition='')
-+ self.debugger.go()
-+ time.sleep(self._pause_between_steps)
-diff --git a/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ControllerHelpers.py b/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ControllerHelpers.py
-new file mode 100644
-index 00000000000..adac7674aff
---- /dev/null
-+++ b/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ControllerHelpers.py
-@@ -0,0 +1,37 @@
-+# 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
-+
-+import os
-+from itertools import chain
-+
-+def in_source_file(source_files, step_info):
-+ if not step_info.current_frame:
-+ return False
-+ if not step_info.current_location.path:
-+ return False
-+ if not os.path.exists(step_info.current_location.path):
-+ return False
-+ return any(os.path.samefile(step_info.current_location.path, f) \
-+ for f in source_files)
-+
-+def update_step_watches(step_info, watches, commands):
-+ watch_cmds = ['DexUnreachable', 'DexExpectStepOrder']
-+ towatch = chain.from_iterable(commands[x]
-+ for x in watch_cmds
-+ if x in commands)
-+ try:
-+ # Iterate over all watches of the types named in watch_cmds
-+ for watch in towatch:
-+ loc = step_info.current_location
-+ if (os.path.exists(loc.path)
-+ and os.path.samefile(watch.path, loc.path)
-+ and watch.lineno == loc.lineno):
-+ result = watch.eval(step_info)
-+ step_info.watches.update(result)
-+ break
-+ except KeyError:
-+ pass
-diff --git a/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DebuggerControllerBase.py b/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DebuggerControllerBase.py
-index ff98baa2d0e..87b13fc7f3a 100644
---- a/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DebuggerControllerBase.py
-+++ b/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DebuggerControllerBase.py
-@@ -4,7 +4,7 @@
- # 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
--"""Default class for controlling debuggers."""
-+"""Abstract Base class for controlling debuggers."""
-
- import abc
-
-diff --git a/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DefaultController.py b/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DefaultController.py
-index 0077a19e601..c41a3eff0d3 100644
---- a/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DefaultController.py
-+++ b/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DefaultController.py
-@@ -4,61 +4,37 @@
- # 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
--"""Base class for controlling debuggers."""
-+"""Default class for controlling debuggers."""
-
- from itertools import chain
- import os
- import time
-
- from dex.debugger.DebuggerControllers.DebuggerControllerBase import DebuggerControllerBase
--from dex.utils.Exceptions import DebuggerException
-+from dex.debugger.DebuggerControllers.ControllerHelpers import in_source_file, update_step_watches
-+from dex.utils.Exceptions import DebuggerException, LoadDebuggerException
-
- class DefaultController(DebuggerControllerBase):
- def __init__(self, context, step_collection):
- self.context = context
- self.step_collection = step_collection
-+ self.source_files = self.context.options.source_files
- self.watches = set()
- self.step_index = 0
-
-- def _update_step_watches(self, step_info):
-- watch_cmds = ['DexUnreachable', 'DexExpectStepOrder']
-- towatch = chain.from_iterable(self.step_collection.commands[x]
-- for x in watch_cmds
-- if x in self.step_collection.commands)
-- try:
-- # Iterate over all watches of the types named in watch_cmds
-- for watch in towatch:
-- loc = step_info.current_location
-- if (os.path.exists(loc.path)
-- and os.path.samefile(watch.path, loc.path)
-- and watch.lineno == loc.lineno):
-- result = watch.eval(step_info)
-- step_info.watches.update(result)
-- break
-- except KeyError:
-- pass
--
- def _break_point_all_lines(self):
- for s in self.context.options.source_files:
- with open(s, 'r') as fp:
- num_lines = len(fp.readlines())
- for line in range(1, num_lines + 1):
-- self.debugger.add_breakpoint(s, line)
--
-- def _in_source_file(self, step_info):
-- if not step_info.current_frame:
-- return False
-- if not step_info.current_location.path:
-- return False
-- if not os.path.exists(step_info.current_location.path):
-- return False
-- return any(os.path.samefile(step_info.current_location.path, f) \
-- for f in self.context.options.source_files)
-+ try:
-+ self.debugger.add_breakpoint(s, line)
-+ except DebuggerException:
-+ raise LoadDebuggerException(DebuggerException.msg)
-
- def _run_debugger_custom(self):
- self.step_collection.debugger = self.debugger.debugger_info
- self._break_point_all_lines()
--
- self.debugger.launch()
-
- for command_obj in chain.from_iterable(self.step_collection.commands.values()):
-@@ -76,10 +52,10 @@ class DefaultController(DebuggerControllerBase):
- step_info = self.debugger.get_step_info(self.watches, self.step_index)
-
- if step_info.current_frame:
-- self._update_step_watches(step_info)
-+ update_step_watches(step_info, self.watches, self.step_collection.commands)
- self.step_collection.new_step(self.context, step_info)
-
-- if self._in_source_file(step_info):
-+ if in_source_file(self.source_files, step_info):
- self.debugger.step()
- else:
- self.debugger.go()
-diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py
-index 0afc748aecb..d812fd974f7 100644
---- a/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py
-+++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py
-@@ -77,11 +77,21 @@ class DbgEng(DebuggerBase):
- self.client.Control.RemoveBreakpoint(x)
-
- def add_breakpoint(self, file_, line):
-- # This is something to implement in the future -- as it stands, Dexter
-- # doesn't test for such things as "I can set a breakpoint on this line".
-- # This is only called AFAICT right now to ensure we break on every step.
-+ # Breakpoint setting/deleting is not supported by dbgeng at this moment
-+ # but is something that should be considered in the future.
-+ # TODO: this method is called in the DefaultController but has no effect.
- pass
-
-+ def add_conditional_breakpoint(self, file_, line, condition):
-+ # breakpoint setting/deleting is not supported by dbgeng at this moment
-+ # but is something that should be considered in the future.
-+ raise NotImplementedError('add_conditional_breakpoint is not yet implemented by dbgeng')
-+
-+ def delete_conditional_breakpoint(self, file_, line, condition):
-+ # breakpoint setting/deleting is not supported by dbgeng at this moment
-+ # but is something that should be considered in the future.
-+ raise NotImplementedError('delete_conditional_breakpoint is not yet implemented by dbgeng')
-+
- def launch(self):
- # We are, by this point, already launched.
- self.step_info = probe_process.probe_state(self.client)
-diff --git a/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py b/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py
-index a943431c888..c7bb74681d9 100644
---- a/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py
-+++ b/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py
-@@ -105,9 +105,48 @@ class LLDB(DebuggerBase):
-
- def add_breakpoint(self, file_, line):
- if not self._target.BreakpointCreateByLocation(file_, line):
-- raise LoadDebuggerException(
-+ raise DebuggerException(
- 'could not add breakpoint [{}:{}]'.format(file_, line))
-
-+ def add_conditional_breakpoint(self, file_, line, condition):
-+ bp = self._target.BreakpointCreateByLocation(file_, line)
-+ if bp:
-+ bp.SetCondition(condition)
-+ else:
-+ raise DebuggerException(
-+ 'could not add breakpoint [{}:{}]'.format(file_, line))
-+
-+ def delete_conditional_breakpoint(self, file_, line, condition):
-+ bp_count = self._target.GetNumBreakpoints()
-+ bps = [self._target.GetBreakpointAtIndex(ix) for ix in range(0, bp_count)]
-+
-+ for bp in bps:
-+ bp_cond = bp.GetCondition()
-+ bp_cond = bp_cond if bp_cond is not None else ''
-+
-+ if bp_cond != condition:
-+ continue
-+
-+ # If one of the bound bp locations for this bp is bound to the same
-+ # line in file_ above, then delete the entire parent bp and all
-+ # bp locs.
-+ # https://lldb.llvm.org/python_reference/lldb.SBBreakpoint-class.html
-+ for breakpoint_location in bp:
-+ sb_address = breakpoint_location.GetAddress()
-+
-+ sb_line_entry = sb_address.GetLineEntry()
-+ bl_line = sb_line_entry.GetLine()
-+
-+ sb_file_entry = sb_line_entry.GetFileSpec()
-+ bl_dir = sb_file_entry.GetDirectory()
-+ bl_file_name = sb_file_entry.GetFilename()
-+
-+ bl_file_path = os.path.join(bl_dir, bl_file_name)
-+
-+ if bl_file_path == file_ and bl_line == line:
-+ self._target.BreakpointDelete(bp.GetID())
-+ break
-+
- def launch(self):
- self._process = self._target.LaunchSimple(None, None, os.getcwd())
- if not self._process or self._process.GetNumThreads() == 0:
-diff --git a/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py b/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py
-index b9816f84f72..40a902bd205 100644
---- a/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py
-+++ b/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py
-@@ -82,6 +82,9 @@ class VisualStudio(DebuggerBase, metaclass=abc.ABCMeta): # pylint: disable=abst
-
- @property
- def _location(self):
-+ #TODO: Find a better way of determining path, line and column info
-+ # that doesn't require reading break points. This method requires
-+ # all lines to have a break point on them.
- bp = self._debugger.BreakpointLastHit
- return {
- 'path': getattr(bp, 'File', None),
-@@ -111,8 +114,20 @@ class VisualStudio(DebuggerBase, metaclass=abc.ABCMeta): # pylint: disable=abst
- def add_breakpoint(self, file_, line):
- self._debugger.Breakpoints.Add('', file_, line)
-
-+ def add_conditional_breakpoint(self, file_, line, condition):
-+ column = 1
-+ self._debugger.Breakpoints.Add('', file_, line, column, condition)
-+
-+ def delete_conditional_breakpoint(self, file_, line, condition):
-+ for bp in self._debugger.Breakpoints:
-+ for bound_bp in bp.Children:
-+ if (bound_bp.File == file_ and bound_bp.FileLine == line and
-+ bound_bp.Condition == condition):
-+ bp.Delete()
-+ break
-+
- def launch(self):
-- self.step()
-+ self._fn_go()
-
- def step(self):
- self._fn_step()
-diff --git a/debuginfo-tests/dexter/dex/tools/test/Tool.py b/debuginfo-tests/dexter/dex/tools/test/Tool.py
-index a615c8cad90..43191fd44bd 100644
---- a/debuginfo-tests/dexter/dex/tools/test/Tool.py
-+++ b/debuginfo-tests/dexter/dex/tools/test/Tool.py
-@@ -16,6 +16,7 @@ from dex.builder import run_external_build_script
- from dex.command.ParseCommand import get_command_infos
- from dex.debugger.Debuggers import run_debugger_subprocess
- from dex.debugger.DebuggerControllers.DefaultController import DefaultController
-+from dex.debugger.DebuggerControllers.ConditionalController import ConditionalController
- from dex.dextIR.DextIR import DextIR
- from dex.heuristic import Heuristic
- from dex.tools import TestToolBase
-@@ -136,9 +137,15 @@ class Tool(TestToolBase):
- executable_path=self.context.options.executable,
- source_paths=self.context.options.source_files,
- dexter_version=self.context.version)
-+
- step_collection.commands = get_command_infos(
- self.context.options.source_files)
-- debugger_controller = DefaultController(self.context, step_collection)
-+
-+ if 'DexLimitSteps' in step_collection.commands:
-+ debugger_controller = ConditionalController(self.context, step_collection)
-+ else:
-+ debugger_controller = DefaultController(self.context, step_collection)
-+
- return debugger_controller
-
- def _get_steps(self, builderIR):
-diff --git a/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_check_json_step_count.cpp b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_check_json_step_count.cpp
-new file mode 100644
-index 00000000000..45683fced2d
---- /dev/null
-+++ b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_check_json_step_count.cpp
-@@ -0,0 +1,20 @@
-+// Purpose:
-+// Check number of step lines are correctly reported in json output.
-+//
-+// REQUIRES: system-linux
-+//
-+// RUN: %dexter_regression_test --verbose -- %s | FileCheck %s
-+// CHECK: limit_steps_check_json_step_count.cpp
-+// CHECK: ## BEGIN ##
-+// CHECK-COUNT-3: json_step_count.cpp",
-+
-+int main() {
-+ int result = 0;
-+ for(int ix = 0; ix != 10; ++ix) {
-+ int index = ix;
-+ result += index; // DexLabel('check')
-+ }
-+}
-+
-+// DexExpectWatchValue('index', 2, 7, 9, on_line='check')
-+// DexLimitSteps('ix', 2, 7, 9, on_line='check')
-diff --git a/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_expect_loop.cpp b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_expect_loop.cpp
-new file mode 100644
-index 00000000000..5946fa6ba46
---- /dev/null
-+++ b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_expect_loop.cpp
-@@ -0,0 +1,20 @@
-+// Purpose:
-+// Check the DexLimit steps only gathers step info for 2 iterations of a
-+// for loop.
-+//
-+// REQUIRES: system-linux
-+//
-+// RUN: %dexter_regression_test -- %s | FileCheck %s
-+// CHECK: limit_steps_expect_loop.cpp:
-+
-+int main(const int argc, const char * argv[]) {
-+ unsigned int sum = 1;
-+ for(unsigned int ix = 0; ix != 5; ++ix) {
-+ unsigned thing_to_add = ix + ix - ix; // DexLabel('start')
-+ sum += ix; // DexLabel('end')
-+ }
-+ return sum;
-+}
-+
-+// DexLimitSteps('ix', 0, 3, from_line='start', to_line='end')
-+// DexExpectWatchValue('ix', 0, 3, from_line='start', to_line='end')
-diff --git a/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_expect_value.cpp b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_expect_value.cpp
-new file mode 100644
-index 00000000000..2715e28d66b
---- /dev/null
-+++ b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_expect_value.cpp
-@@ -0,0 +1,18 @@
-+// Purpose:
-+// Ensure that limited stepping breaks for all expected values.
-+//
-+// REQUIRES: system-linux
-+//
-+// RUN: %dexter_regression_test -- %s | FileCheck %s
-+// CHECK: limit_steps_expect_value.cpp
-+
-+int main() {
-+ int i = 0;
-+ i = 1; // DexLabel('from')
-+ i = 2;
-+ i = 3;
-+ return 0; // DexLabel('long_range')
-+}
-+
-+// DexLimitSteps('i', '0', from_line='from', to_line='long_range')
-+// DexExpectWatchValue('i', 0, 1, 2, 3, from_line='from', to_line='long_range')
-diff --git a/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_overlapping_ranges.cpp b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_overlapping_ranges.cpp
-new file mode 100644
-index 00000000000..3200fe0979b
---- /dev/null
-+++ b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_overlapping_ranges.cpp
-@@ -0,0 +1,36 @@
-+// Purpose:
-+// Ensure that multiple overlapping \DexLimitSteps ranges do not interfere.
-+//
-+// REQUIRES: system-linux
-+//
-+// RUN: %dexter_regression_test -- %s | FileCheck %s
-+// CHECK: limit_steps_overlapping_ranges.cpp
-+
-+int main() {
-+ int val1;
-+ int val2;
-+ int placeholder;
-+ for (int ix = 0; ix != 10; ++ix) {
-+ placeholder=val1+val2; // DexLabel('from')
-+ if (ix == 0) {
-+ val1 = ix;
-+ val2 = ix; // DexLabel('val1_check')
-+ placeholder=val1+val2; // DexLabel('val1_check_to')
-+ }
-+ else if (ix == 2) {
-+ val2 = ix;
-+ val1 = ix; // DexLabel('val2_check')
-+ placeholder=val1+val2; // DexLabel('val2_check_to')
-+ }
-+ placeholder=val1+val2; // DexLabel('to')
-+ }
-+ return val1 + val2;
-+}
-+
-+// DexExpectWatchValue('ix', 0, 2, 5, from_line='from', to_line='to')
-+// DexExpectWatchValue('val1', 0, from_line='val1_check', to_line='val1_check_to')
-+// DexExpectWatchValue('val2', 2, from_line='val2_check', to_line='val2_check_to')
-+
-+// DexLimitSteps('ix', 5, from_line='from', to_line='to')
-+// DexLimitSteps('val1', 0, from_line='val1_check', to_line='val1_check_to')
-+// DexLimitSteps('val2', 2, from_line='val2_check', to_line='val2_check_to')
-diff --git a/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_same_line_conditional.cpp b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_same_line_conditional.cpp
-new file mode 100644
-index 00000000000..060ff0d5fe7
---- /dev/null
-+++ b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_same_line_conditional.cpp
-@@ -0,0 +1,26 @@
-+// Purpose:
-+// Test that LimitStep commands can exist on the same from line.
-+//
-+// REQUIRES: system-linux
-+//
-+// RUN: %dexter_regression_test -- %s | FileCheck %s
-+// CHECK: limit_steps_same_line_conditional.cpp
-+
-+int main() {
-+ int val1 = 0;
-+
-+ int placeholder;
-+ for(int ix = 0; ix != 4; ++ix) {
-+ val1 = ix;
-+ placeholder = ix; // DexLabel('from')
-+ placeholder = ix;
-+ val1 += 2; // DexLabel('to')
-+ placeholder = ix; // DexLabel('extended_to')
-+ }
-+ return val1 + placeholder;
-+}
-+
-+// DexExpectWatchValue('val1', 0, 1, 3, from_line='from', to_line='extended_to')
-+
-+// DexLimitSteps('ix', 0, from_line='from', to_line='to')
-+// DexLimitSteps('ix', 1, from_line='from', to_line='extended_to')