Skip to content

Commit e35a549

Browse files
committed
[dexter] Remove requirement for a condition in DexLimitSteps
Currently the DexLimitSteps command requires at least one condition. This patch lets users elide the condition to specify that the breakpoint range should always be activated when the leading line is stepped on. This patch also updates the terminology used in the `ConditionalController` class from the terms 'conditional' and 'unconditional' to 'leading' and 'trailing' when referring to the breakpoints in the DexLimitSteps range because the leading breakpoint can now be unconditional. Reviewed By: chrisjackson Differential Revision: https://reviews.llvm.org/D101438
1 parent a11117a commit e35a549

File tree

5 files changed

+100
-37
lines changed

5 files changed

+100
-37
lines changed

debuginfo-tests/dexter/Commands.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ Expect the source line this is found on will never be stepped on to.
177177

178178
----
179179
## DexLimitSteps
180-
DexLimitSteps(expr, *values [, **from_line=1],[,**to_line=Max]
180+
DexLimitSteps([expr, *values][, **from_line=1][,**to_line=Max]
181181
[,**on_line])
182182

183183
Args:
@@ -193,12 +193,14 @@ Expect the source line this is found on will never be stepped on to.
193193
same line.
194194

195195
### Description
196-
Define a limited stepping range that is predicated on a condition. When
197-
'(expr) == (values[n])', set a range of temporary, unconditional break points within
198-
the test file defined by the range from_line and to_line or on_line.
196+
Define a limited stepping range that may be predicated on a condition. When the
197+
leading line is stepped on and any condition '(expr) == (values[n])' is true or
198+
there are no conditions, set a range of temporary breakpoints within the test
199+
file defined by the range 'from_line' and 'to_line' or 'on_line'.
199200

200201
The condition is only evaluated on the line 'from_line' or 'on_line'. If the
201-
condition is not true at the start of the range, the whole range is ignored.
202+
condition is not true at the start of the range, or that line is never stepped
203+
onto, the whole range is ignored.
202204

203205
DexLimitSteps commands are useful for reducing the amount of steps gathered in
204206
large test cases that would normally take much longer to complete.

debuginfo-tests/dexter/dex/command/commands/DexLimitSteps.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@
1212

1313
class DexLimitSteps(CommandBase):
1414
def __init__(self, *args, **kwargs):
15-
self.expression = args[0]
16-
self.values = [str(arg) for arg in args[1:]]
15+
if len(args) == 0:
16+
self.expression = None
17+
self.values = []
18+
elif len(args) == 1:
19+
raise TypeError("expected 0 or at least 2 positional arguments")
20+
else:
21+
self.expression = args[0]
22+
self.values = [str(arg) for arg in args[1:]]
1723
try:
1824
on_line = kwargs.pop('on_line')
1925
self.from_line = on_line

debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,20 @@
1818
from dex.utils.Exceptions import DebuggerException
1919

2020

21-
class ConditionalBpRange:
22-
"""Represents a conditional range of breakpoints within a source file descending from
23-
one line to another."""
21+
class BreakpointRange:
22+
"""A range of breakpoints and a set of conditions.
23+
24+
The leading breakpoint (on line `range_from`) is always active.
25+
26+
When the leading breakpoint is hit the trailing range should be activated
27+
when `expression` evaluates to any value in `values`. If there are no
28+
conditions (`expression` is None) then the trailing breakpoint range should
29+
always be activated upon hitting the leading breakpoint.
30+
31+
Args:
32+
expression: None for no conditions, or a str expression to compare
33+
against `values`.
34+
"""
2435

2536
def __init__(self, expression: str, path: str, range_from: int, range_to: int, values: list):
2637
self.expression = expression
@@ -29,6 +40,9 @@ def __init__(self, expression: str, path: str, range_from: int, range_to: int, v
2940
self.range_to = range_to
3041
self.conditional_values = values
3142

43+
def has_conditions(self):
44+
return self.expression != None
45+
3246
def get_conditional_expression_list(self):
3347
conditional_list = []
3448
for value in self.conditional_values:
@@ -42,48 +56,54 @@ class ConditionalController(DebuggerControllerBase):
4256
def __init__(self, context, step_collection):
4357
self.context = context
4458
self.step_collection = step_collection
45-
self._conditional_bp_ranges = None
46-
self._build_conditional_bp_ranges()
59+
self._bp_ranges = None
60+
self._build_bp_ranges()
4761
self._watches = set()
4862
self._step_index = 0
4963
self._pause_between_steps = context.options.pause_between_steps
5064
self._max_steps = context.options.max_steps
51-
# Map {id: ConditionalBpRange}
52-
self._conditional_bp_handles = {}
65+
# Map {id: BreakpointRange}
66+
self._leading_bp_handles = {}
5367

54-
def _build_conditional_bp_ranges(self):
68+
def _build_bp_ranges(self):
5569
commands = self.step_collection.commands
56-
self._conditional_bp_ranges = []
70+
self._bp_ranges = []
5771
try:
5872
limit_commands = commands['DexLimitSteps']
5973
for lc in limit_commands:
60-
conditional_bp = ConditionalBpRange(
74+
bpr = BreakpointRange(
6175
lc.expression,
6276
lc.path,
6377
lc.from_line,
6478
lc.to_line,
6579
lc.values)
66-
self._conditional_bp_ranges.append(conditional_bp)
80+
self._bp_ranges.append(bpr)
6781
except KeyError:
6882
raise DebuggerException('Missing DexLimitSteps commands, cannot conditionally step.')
6983

70-
def _set_conditional_bps(self):
71-
# Set a conditional breakpoint for each ConditionalBpRange and build a
72-
# map of {id: ConditionalBpRange}.
73-
for cbp in self._conditional_bp_ranges:
74-
for cond_expr in cbp.get_conditional_expression_list():
75-
id = self.debugger.add_conditional_breakpoint(cbp.path,
76-
cbp.range_from,
77-
cond_expr)
78-
self._conditional_bp_handles[id] = cbp
84+
def _set_leading_bps(self):
85+
# Set a leading breakpoint for each BreakpointRange, building a
86+
# map of {leading bp id: BreakpointRange}.
87+
for bpr in self._bp_ranges:
88+
if bpr.has_conditions():
89+
# Add a conditional breakpoint for each condition.
90+
for cond_expr in bpr.get_conditional_expression_list():
91+
id = self.debugger.add_conditional_breakpoint(bpr.path,
92+
bpr.range_from,
93+
cond_expr)
94+
self._leading_bp_handles[id] = bpr
95+
else:
96+
# Add an unconditional breakpoint.
97+
id = self.debugger.add_breakpoint(bpr.path, bpr.range_from)
98+
self._leading_bp_handles[id] = bpr
7999

80100
def _run_debugger_custom(self):
81101
# TODO: Add conditional and unconditional breakpoint support to dbgeng.
82102
if self.debugger.get_name() == 'dbgeng':
83103
raise DebuggerException('DexLimitSteps commands are not supported by dbgeng')
84104

85105
self.step_collection.clear_steps()
86-
self._set_conditional_bps()
106+
self._set_leading_bps()
87107

88108
for command_obj in chain.from_iterable(self.step_collection.commands.values()):
89109
self._watches.update(command_obj.get_watches())
@@ -103,20 +123,20 @@ def _run_debugger_custom(self):
103123
bp_to_delete = []
104124
for bp_id in self.debugger.get_triggered_breakpoint_ids():
105125
try:
106-
# See if this is one of our conditional breakpoints.
107-
cbp = self._conditional_bp_handles[bp_id]
126+
# See if this is one of our leading breakpoints.
127+
bpr = self._leading_bp_handles[bp_id]
108128
except KeyError:
109-
# This is an unconditional bp. Mark it for removal.
129+
# This is a trailing bp. Mark it for removal.
110130
bp_to_delete.append(bp_id)
111131
continue
112-
# Add a range of unconditional breakpoints covering the lines
132+
# Add a range of trailing breakpoints covering the lines
113133
# requested in the DexLimitSteps command. Ignore first line as
114-
# that's the conditional bp we just hit and include the final
115-
# line.
116-
for line in range(cbp.range_from + 1, cbp.range_to + 1):
117-
self.debugger.add_breakpoint(cbp.path, line)
134+
# that's covered by the leading bp we just hit and include the
135+
# final line.
136+
for line in range(bpr.range_from + 1, bpr.range_to + 1):
137+
self.debugger.add_breakpoint(bpr.path, line)
118138

119-
# Remove any unconditional breakpoints we just hit.
139+
# Remove any trailing breakpoints we just hit.
120140
for bp_id in bp_to_delete:
121141
self.debugger.delete_breakpoint(bp_id)
122142

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Purpose:
2+
// Test that \DexLimitSteps can be used without a condition (i.e. the
3+
// breakpoint range is set any time from_line is stepped on).
4+
//
5+
// REQUIRES: system-linux
6+
//
7+
// RUN: %dexter_regression_test -- %s | FileCheck %s
8+
// CHECK: unconditional.cpp
9+
10+
int glob;
11+
int main() {
12+
int test = 0;
13+
for (test = 1; test < 4; test++) {
14+
glob += test; // DexLabel('from')
15+
glob += test; // DexLabel('to')
16+
}
17+
return test; // test = 4
18+
}
19+
20+
// DexLimitSteps(from_line='from', to_line='to')
21+
//// Unconditionally limit dexter's view of the program from line 'from' to
22+
//// 'to'. Check for test=0, 1, 2 so that the test will fail if dexter sees
23+
//// test=0 or test=4.
24+
// DexExpectWatchValue('test', 1, 2, 3)
25+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Purpose:
2+
// Check that specifying an expression without any values to compare against
3+
// in a \DexLimitSteps command results in a useful error message.
4+
// Use --binary switch to trick dexter into skipping the build step.
5+
//
6+
// REQUIRES: system-linux
7+
// RUN: not %dexter_base test --binary %s --debugger 'lldb' -- %s | FileCheck %s
8+
// CHECK: parser error:{{.*}}err_limit_steps_no_values.cpp(10): expected 0 or at least 2 positional arguments
9+
10+
// DexLimitSteps('test')

0 commit comments

Comments
 (0)