diff --git a/docs/src/config/common-options.md b/docs/src/config/common-options.md index 2a082c7e..5ccdf476 100644 --- a/docs/src/config/common-options.md +++ b/docs/src/config/common-options.md @@ -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 @@ -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 diff --git a/src/haps/mod.rs b/src/haps/mod.rs index 9986201e..56266901 100644 --- a/src/haps/mod.rs +++ b/src/haps/mod.rs @@ -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() { @@ -106,6 +111,7 @@ impl Tsffs { &mut self, exit_kind: ExitKind, iteration_count: IterationCount, + snapshot_restore_mode: SnapshotRestoreMode, missing_start_info_message: &str, ) -> Result { // 1) Count this iteration as complete. @@ -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, + } { self.restore_initial_snapshot()?; } @@ -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(()); @@ -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(()); @@ -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(()); diff --git a/src/interfaces/fuzz.rs b/src/interfaces/fuzz.rs index b75ba072..140db0f6 100644 --- a/src/interfaces/fuzz.rs +++ b/src/interfaces/fuzz.rs @@ -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()?; diff --git a/src/lib.rs b/src/lib.rs index 343a0ed7..ff46020a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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