Skip to content

Commit 597d680

Browse files
authored
Merge pull request #1368 from Free-codaer/add-loop-guard
feat: add loop guard for repeated identical pause states
2 parents 2a670fa + e1e6b70 commit 597d680

3 files changed

Lines changed: 92 additions & 5 deletions

File tree

docs/instruction-stepping.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ When in instruction stepping mode, the following commands are available:
7070
- `c`, `continue` - Continue execution until completion
7171
- `q`, `quit`, `exit` - Exit instruction stepping mode
7272

73+
### Loop Guard (Repeated Pause Detection)
74+
75+
If the debugger repeatedly pauses at the exact same instruction without making any forward progress (e.g., hitting the same breakpoint infinitely), it will automatically intercept the loop after a threshold (5 repetitions) and warn you.
76+
You will receive an actionable error suggestion, and if the repeated pauses are intentional, you can simply issue your step or continue command again to proceed for another cycle.
77+
7378
### Example Session
7479

7580
```

src/debugger/engine.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,16 @@ impl DebuggerEngine {
212212
self.source_map.as_ref()?.lookup(wasm_offset)
213213
}
214214

215+
fn check_repeated_pause(&mut self) -> Result<()> {
216+
if self.stepper.pause_repeat_count() >= 5 {
217+
self.stepper.reset_pause_count();
218+
return Err(miette::miette!(
219+
"Repeated identical pause state detected (no progress). If this is intentional, you may issue the continue or step command again."
220+
));
221+
}
222+
Ok(())
223+
}
224+
215225
/// Enable instruction-level debugging.
216226
pub fn enable_instruction_debug(&mut self, wasm_bytes: &[u8]) -> Result<()> {
217227
self.try_load_source_map(wasm_bytes);
@@ -387,6 +397,7 @@ impl DebuggerEngine {
387397
if let Ok(state) = self.state.lock() {
388398
state.call_stack().display();
389399
}
400+
self.check_repeated_pause()?;
390401
}
391402

392403
result
@@ -536,6 +547,9 @@ impl DebuggerEngine {
536547
state.set_pause_reason(PauseReason::EndOfExecution);
537548
}
538549
}
550+
if stepped {
551+
self.check_repeated_pause()?;
552+
}
539553
Ok(stepped)
540554
}
541555

@@ -558,6 +572,9 @@ impl DebuggerEngine {
558572
state.set_pause_reason(PauseReason::EndOfExecution);
559573
}
560574
}
575+
if stepped {
576+
self.check_repeated_pause()?;
577+
}
561578
Ok(stepped)
562579
}
563580

@@ -580,6 +597,9 @@ impl DebuggerEngine {
580597
state.set_pause_reason(PauseReason::EndOfExecution);
581598
}
582599
}
600+
if stepped {
601+
self.check_repeated_pause()?;
602+
}
583603
Ok(stepped)
584604
}
585605

@@ -608,6 +628,9 @@ impl DebuggerEngine {
608628
state.set_pause_reason(PauseReason::EndOfExecution);
609629
}
610630
}
631+
if paused {
632+
self.check_repeated_pause()?;
633+
}
611634
Ok(StepOverResult { paused, location })
612635
}
613636

@@ -630,6 +653,9 @@ impl DebuggerEngine {
630653
state.set_pause_reason(PauseReason::EndOfExecution);
631654
}
632655
}
656+
if stepped {
657+
self.check_repeated_pause()?;
658+
}
633659
Ok(stepped)
634660
}
635661

@@ -652,6 +678,9 @@ impl DebuggerEngine {
652678
state.set_pause_reason(PauseReason::EndOfExecution);
653679
}
654680
}
681+
if stepped {
682+
self.check_repeated_pause()?;
683+
}
655684
Ok(stepped)
656685
}
657686

src/debugger/stepper.rs

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ pub struct Stepper {
99
active: bool,
1010
step_mode: StepMode,
1111
pause_next: bool,
12+
last_pause_triple: Option<(usize, usize, StepMode)>,
13+
pause_repeat_count: usize,
1214
}
1315

1416
impl Stepper {
@@ -17,6 +19,8 @@ impl Stepper {
1719
active: false,
1820
step_mode: StepMode::StepInto,
1921
pause_next: false,
22+
last_pause_triple: None,
23+
pause_repeat_count: 0,
2024
}
2125
}
2226

@@ -41,13 +45,42 @@ impl Stepper {
4145
self.step_mode
4246
}
4347

48+
pub fn pause_repeat_count(&self) -> usize {
49+
self.pause_repeat_count
50+
}
51+
52+
pub fn reset_pause_count(&mut self) {
53+
self.pause_repeat_count = 0;
54+
}
55+
56+
fn track_pause(&mut self, debug_state: &DebugState) {
57+
if let Some(inst) = debug_state.current_instruction() {
58+
let current_triple = (
59+
inst.offset,
60+
debug_state.instruction_pointer().call_stack_depth(),
61+
self.step_mode,
62+
);
63+
64+
if self.last_pause_triple == Some(current_triple) {
65+
self.pause_repeat_count += 1;
66+
} else {
67+
self.last_pause_triple = Some(current_triple);
68+
self.pause_repeat_count = 0;
69+
}
70+
}
71+
}
72+
4473
pub fn step_into(&mut self, debug_state: &mut DebugState) -> bool {
4574
if !self.active {
4675
return false;
4776
}
4877
self.step_mode = StepMode::StepInto;
4978
debug_state.start_instruction_stepping(StepMode::StepInto);
50-
debug_state.next_instruction().is_some()
79+
let stepped = debug_state.next_instruction().is_some();
80+
if stepped {
81+
self.track_pause(debug_state);
82+
}
83+
stepped
5184
}
5285

5386
pub fn step_over(&mut self, debug_state: &mut DebugState) -> bool {
@@ -56,7 +89,11 @@ impl Stepper {
5689
}
5790
self.step_mode = StepMode::StepOver;
5891
debug_state.start_instruction_stepping(StepMode::StepOver);
59-
self.advance_to_depth(debug_state, false)
92+
let stepped = self.advance_to_depth(debug_state, false);
93+
if stepped {
94+
self.track_pause(debug_state);
95+
}
96+
stepped
6097
}
6198

6299
/// Step over to the next distinct source line within the same call frame.
@@ -96,6 +133,7 @@ impl Stepper {
96133
};
97134

98135
if is_different_line {
136+
self.track_pause(debug_state);
99137
return true;
100138
}
101139
}
@@ -109,7 +147,11 @@ impl Stepper {
109147
}
110148
self.step_mode = StepMode::StepOut;
111149
debug_state.start_instruction_stepping(StepMode::StepOut);
112-
self.advance_to_depth(debug_state, true)
150+
let stepped = self.advance_to_depth(debug_state, true);
151+
if stepped {
152+
self.track_pause(debug_state);
153+
}
154+
stepped
113155
}
114156

115157
pub fn step_block(&mut self, debug_state: &mut DebugState) -> bool {
@@ -118,14 +160,22 @@ impl Stepper {
118160
}
119161
self.step_mode = StepMode::StepBlock;
120162
debug_state.start_instruction_stepping(StepMode::StepBlock);
121-
self.find_next_control_flow(debug_state)
163+
let stepped = self.find_next_control_flow(debug_state);
164+
if stepped {
165+
self.track_pause(debug_state);
166+
}
167+
stepped
122168
}
123169

124170
pub fn step_back(&mut self, debug_state: &mut DebugState) -> bool {
125171
if !self.active {
126172
return false;
127173
}
128-
debug_state.previous_instruction().is_some()
174+
let stepped = debug_state.previous_instruction().is_some();
175+
if stepped {
176+
self.track_pause(debug_state);
177+
}
178+
stepped
129179
}
130180

131181
pub fn continue_execution(&mut self, debug_state: &mut DebugState) {
@@ -155,6 +205,7 @@ impl Stepper {
155205
return false;
156206
}
157207
if self.should_pause(instruction, debug_state) {
208+
self.track_pause(debug_state);
158209
self.pause_next = false;
159210
return true;
160211
}
@@ -164,6 +215,8 @@ impl Stepper {
164215
pub fn reset(&mut self) {
165216
self.active = false;
166217
self.pause_next = false;
218+
self.last_pause_triple = None;
219+
self.pause_repeat_count = 0;
167220
}
168221

169222
fn advance_to_depth(&self, debug_state: &mut DebugState, strictly_lower: bool) -> bool {

0 commit comments

Comments
 (0)