Skip to content

Commit 5f2bab7

Browse files
authored
fix: Always restore snapshot after solutions (#305)
1 parent 6ba05f3 commit 5f2bab7

File tree

4 files changed

+29
-10
lines changed

4 files changed

+29
-10
lines changed

docs/src/config/common-options.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ This is useful for CI fuzzing or for testing. The limit can be set with:
207207

208208
### Set Snapshot Restore Interval
209209

210-
By default, TSFFS restores the initial snapshot at every iteration boundary.
210+
By default, TSFFS restores the initial snapshot at every normal iteration boundary.
211211
This can be changed to support semi-persistent or fully persistent execution:
212212

213213
```python
@@ -221,9 +221,15 @@ This can be changed to support semi-persistent or fully persistent execution:
221221
@tsffs.snapshot_restore_interval = 0
222222
```
223223

224-
Values greater than 1 restore every N iterations, where N is the configured value.
224+
Values greater than 1 restore every N iterations, where N is the configured
225+
value. This cadence is based on the global iteration count.
225226
This option only accepts integer values (`0`, `1`, or `N > 1`).
226227

228+
Discovered solutions, including timeouts, exceptions, and
229+
breakpoint-triggered solutions, always restore the initial snapshot before
230+
the next iteration resumes if one exists. `snapshot_restore_interval`
231+
controls only the restore behavior for normal iteration boundaries.
232+
227233
### Adding Tokens From Target Software
228234

229235
The fuzzer has a mutator which will insert, remove, and mutate tokens in testcases. This

src/haps/mod.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ enum IterationCount {
3333
Solution,
3434
}
3535

36+
enum SnapshotRestoreMode {
37+
PolicyControlled,
38+
Always,
39+
}
40+
3641
impl Tsffs {
3742
fn on_simulation_stopped_magic_start(&mut self, magic_number: MagicNumber) -> Result<()> {
3843
if !self.have_initial_snapshot() {
@@ -106,6 +111,7 @@ impl Tsffs {
106111
&mut self,
107112
exit_kind: ExitKind,
108113
iteration_count: IterationCount,
114+
snapshot_restore_mode: SnapshotRestoreMode,
109115
missing_start_info_message: &str,
110116
) -> Result<IterationControl> {
111117
// 1) Count this iteration as complete.
@@ -155,8 +161,11 @@ impl Tsffs {
155161
// 4) Publish this iteration result back to the fuzzer loop.
156162
fuzzer_tx.send(exit_kind)?;
157163

158-
// 5) Restore to initial snapshot according to the configured restore interval.
159-
if self.should_restore_snapshot_this_iteration() {
164+
// 5) Restore to initial snapshot according to the stop-specific restore policy.
165+
if match snapshot_restore_mode {
166+
SnapshotRestoreMode::PolicyControlled => self.should_restore_snapshot_this_iteration(),
167+
SnapshotRestoreMode::Always => true,
168+
} {
160169
self.restore_initial_snapshot()?;
161170
}
162171

@@ -206,6 +215,7 @@ impl Tsffs {
206215
if let IterationControl::StopRequested = self.finish_iteration(
207216
ExitKind::Ok,
208217
IterationCount::NoCount,
218+
SnapshotRestoreMode::PolicyControlled,
209219
"Missing start buffer or size, not writing testcase.",
210220
)? {
211221
return Ok(());
@@ -375,6 +385,7 @@ impl Tsffs {
375385
if let IterationControl::StopRequested = self.finish_iteration(
376386
ExitKind::Ok,
377387
IterationCount::NoCount,
388+
SnapshotRestoreMode::PolicyControlled,
378389
"Missing start buffer or size, not writing testcase. This may be due to using manual no-buffer harnessing.",
379390
)? {
380391
return Ok(());
@@ -436,6 +447,7 @@ impl Tsffs {
436447
if let IterationControl::StopRequested = self.finish_iteration(
437448
exit_kind,
438449
iteration_count,
450+
SnapshotRestoreMode::Always,
439451
"Missing start buffer or size, not writing testcase.",
440452
)? {
441453
return Ok(());

src/interfaces/fuzz.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,8 @@ impl Tsffs {
237237

238238
/// Interface method to manually signal to stop execution with a solution condition.
239239
/// When this method is called, the current testcase execution will be stopped as if
240-
/// it had finished executing with an exception or timeout, and the state will be
241-
/// restored according to `snapshot_restore_interval` (restoring every iteration by
242-
/// default).
240+
/// it had finished executing with an exception or timeout, and, if an initial
241+
/// snapshot exists, it will be restored before the next iteration resumes.
243242
pub fn solution(&mut self, id: u64, message: *mut c_char) -> Result<()> {
244243
let message = unsafe { CStr::from_ptr(message) }.to_str()?;
245244

src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,12 +251,14 @@ pub(crate) struct Tsffs {
251251
/// is saved as a solution.
252252
pub timeout: f64,
253253
#[class(attribute(optional, default = SnapshotRestorePolicy::Always))]
254-
/// Snapshot restore policy between iterations.
254+
/// Snapshot restore policy for normal iteration boundaries.
255255
///
256256
/// Accepted values:
257-
/// - `1` restores on every iteration (default)
258-
/// - `N > 1` restores every N iterations
257+
/// - `1` restores on every normal iteration (default)
258+
/// - `N > 1` restores every N iterations based on the global iteration count
259259
/// - `0` disables restores after startup
260+
///
261+
/// Solution iterations always restore the initial snapshot before resuming if one exists.
260262
pub snapshot_restore_interval: SnapshotRestorePolicy,
261263
#[class(attribute(optional, default = true))]
262264
/// Whether the fuzzer should start on compiled-in harnesses. If set to `True`, the fuzzer

0 commit comments

Comments
 (0)