Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions docs/src/config/common-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ This is useful for CI fuzzing or for testing. The limit can be set with:

### Set Snapshot Restore Interval

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

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

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

Discovered solutions, including timeouts, exceptions, and
breakpoint-triggered solutions, always restore the initial snapshot before
the next iteration resumes if one exists. `snapshot_restore_interval`
controls only the restore behavior for normal iteration boundaries.

### Adding Tokens From Target Software

The fuzzer has a mutator which will insert, remove, and mutate tokens in testcases. This
Expand Down
16 changes: 14 additions & 2 deletions src/haps/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ enum IterationCount {
Solution,
}

enum SnapshotRestoreMode {
PolicyControlled,
Always,
}

impl Tsffs {
fn on_simulation_stopped_magic_start(&mut self, magic_number: MagicNumber) -> Result<()> {
if !self.have_initial_snapshot() {
Expand Down Expand Up @@ -106,6 +111,7 @@ impl Tsffs {
&mut self,
exit_kind: ExitKind,
iteration_count: IterationCount,
snapshot_restore_mode: SnapshotRestoreMode,
missing_start_info_message: &str,
) -> Result<IterationControl> {
// 1) Count this iteration as complete.
Expand Down Expand Up @@ -155,8 +161,11 @@ impl Tsffs {
// 4) Publish this iteration result back to the fuzzer loop.
fuzzer_tx.send(exit_kind)?;

// 5) Restore to initial snapshot according to the configured restore interval.
if self.should_restore_snapshot_this_iteration() {
// 5) Restore to initial snapshot according to the stop-specific restore policy.
if match snapshot_restore_mode {
SnapshotRestoreMode::PolicyControlled => self.should_restore_snapshot_this_iteration(),
SnapshotRestoreMode::Always => true,
} {
Comment on lines +164 to +168
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new SnapshotRestoreMode::Always behavior means solution/timeout stops will restore the initial snapshot even when snapshot_restore_interval is configured to never/periodic restore. That appears to contradict the existing interface documentation that says solution() restores according to snapshot_restore_interval (see src/interfaces/fuzz.rs docs). Please update the relevant docs (and/or config semantics) to match this new stop-specific restore policy so users aren’t misled.

Copilot uses AI. Check for mistakes.
self.restore_initial_snapshot()?;
}

Expand Down Expand Up @@ -206,6 +215,7 @@ impl Tsffs {
if let IterationControl::StopRequested = self.finish_iteration(
ExitKind::Ok,
IterationCount::NoCount,
SnapshotRestoreMode::PolicyControlled,
"Missing start buffer or size, not writing testcase.",
)? {
return Ok(());
Expand Down Expand Up @@ -375,6 +385,7 @@ impl Tsffs {
if let IterationControl::StopRequested = self.finish_iteration(
ExitKind::Ok,
IterationCount::NoCount,
SnapshotRestoreMode::PolicyControlled,
"Missing start buffer or size, not writing testcase. This may be due to using manual no-buffer harnessing.",
)? {
return Ok(());
Expand Down Expand Up @@ -436,6 +447,7 @@ impl Tsffs {
if let IterationControl::StopRequested = self.finish_iteration(
exit_kind,
iteration_count,
SnapshotRestoreMode::Always,
"Missing start buffer or size, not writing testcase.",
)? {
return Ok(());
Expand Down
5 changes: 2 additions & 3 deletions src/interfaces/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,8 @@ impl Tsffs {

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

Expand Down
8 changes: 5 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,12 +251,14 @@ pub(crate) struct Tsffs {
/// is saved as a solution.
pub timeout: f64,
#[class(attribute(optional, default = SnapshotRestorePolicy::Always))]
/// Snapshot restore policy between iterations.
/// Snapshot restore policy for normal iteration boundaries.
///
/// Accepted values:
/// - `1` restores on every iteration (default)
/// - `N > 1` restores every N iterations
/// - `1` restores on every normal iteration (default)
/// - `N > 1` restores every N iterations based on the global iteration count
/// - `0` disables restores after startup
///
/// Solution iterations always restore the initial snapshot before resuming if one exists.
pub snapshot_restore_interval: SnapshotRestorePolicy,
#[class(attribute(optional, default = true))]
/// Whether the fuzzer should start on compiled-in harnesses. If set to `True`, the fuzzer
Expand Down
Loading