diff --git a/d.diff b/d.diff deleted file mode 100644 --- a/d.diff +++ /dev/null @@ -1,712 +0,0 @@ -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: -+ # () == () -+ 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')