Index: debuginfo-tests/dexter/Commands.md =================================================================== --- debuginfo-tests/dexter/Commands.md +++ debuginfo-tests/dexter/Commands.md @@ -173,6 +173,34 @@ [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) Index: debuginfo-tests/dexter/dex/command/ParseCommand.py =================================================================== --- debuginfo-tests/dexter/dex/command/ParseCommand.py +++ debuginfo-tests/dexter/dex/command/ParseCommand.py @@ -24,6 +24,7 @@ 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 @@ DexExpectWatchType.get_name() : DexExpectWatchType, DexExpectWatchValue.get_name() : DexExpectWatchValue, DexLabel.get_name() : DexLabel, + DexLimitSteps.get_name() : DexLimitSteps, DexUnreachable.get_name() : DexUnreachable, DexWatch.get_name() : DexWatch } Index: debuginfo-tests/dexter/dex/command/commands/DexLimitSteps.py =================================================================== --- /dev/null +++ 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 Index: debuginfo-tests/dexter/dex/debugger/DebuggerBase.py =================================================================== --- debuginfo-tests/dexter/dex/debugger/DebuggerBase.py +++ debuginfo-tests/dexter/dex/debugger/DebuggerBase.py @@ -120,6 +120,14 @@ 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 Index: debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py =================================================================== --- /dev/null +++ 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) Index: debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ControllerHelpers.py =================================================================== --- /dev/null +++ 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 Index: debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DebuggerControllerBase.py =================================================================== --- debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DebuggerControllerBase.py +++ 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 Index: debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DefaultController.py =================================================================== --- debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DefaultController.py +++ 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 @@ 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() Index: debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py =================================================================== --- debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py +++ debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py @@ -77,11 +77,21 @@ 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) Index: debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py =================================================================== --- debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py +++ debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py @@ -105,9 +105,48 @@ 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: Index: debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py =================================================================== --- debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py +++ debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py @@ -82,6 +82,9 @@ @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 @@ 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() Index: debuginfo-tests/dexter/dex/tools/test/Tool.py =================================================================== --- debuginfo-tests/dexter/dex/tools/test/Tool.py +++ debuginfo-tests/dexter/dex/tools/test/Tool.py @@ -16,6 +16,7 @@ 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 @@ 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): Index: debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_check_json_step_count.cpp =================================================================== --- /dev/null +++ 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') Index: debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_expect_loop.cpp =================================================================== --- /dev/null +++ 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') Index: debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_expect_value.cpp =================================================================== --- /dev/null +++ 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') Index: debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_overlapping_ranges.cpp =================================================================== --- /dev/null +++ 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') Index: debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_same_line_conditional.cpp =================================================================== --- /dev/null +++ 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')