From b6bb982e626289b317a57b10f8166686b67f4c2a Mon Sep 17 00:00:00 2001 From: Joshua Timmons Date: Sun, 14 Jun 2026 15:58:38 -0400 Subject: [PATCH 1/2] fix: panic on short/unfoldable sequences in traceback --- src/core/fold.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/core/fold.rs b/src/core/fold.rs index 0f55c34..4b808b5 100644 --- a/src/core/fold.rs +++ b/src/core/fold.rs @@ -175,6 +175,9 @@ pub fn fold(seq: &str, temp: f64) -> Result, FoldError> { // traceback renders labels from the (upper-cased) sequence, matching cache() let upper = seq.to_uppercase(); let s = upper.as_bytes(); + if s.is_empty() { + return Ok(Vec::new()); + } Ok(traceback(s, 0, s.len() - 1, &v_cache, &w_cache)) } @@ -764,10 +767,10 @@ fn traceback(s: &[u8], mut i: usize, mut j: usize, v_cache: &Cache, w_cache: &Ca let n = s.len(); let s_w = w_cache[i][j].clone(); if s_w.tag != Desc::Hairpin { - while w_cache[i + 1][j] == s_w { + while i + 1 < n && w_cache[i + 1][j] == s_w { i += 1; } - while w_cache[i][j - 1] == s_w { + while j > 0 && w_cache[i][j - 1] == s_w { j -= 1; } } From 7833da71fe617b8a05048014628a0ff2c2127644 Mon Sep 17 00:00:00 2001 From: Joshua Timmons Date: Sun, 14 Jun 2026 16:00:19 -0400 Subject: [PATCH 2/2] test: fuzz short DNA sequences to catch traceback panics --- src/core/fold.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/core/fold.rs b/src/core/fold.rs index 4b808b5..997e702 100644 --- a/src/core/fold.rs +++ b/src/core/fold.rs @@ -910,4 +910,27 @@ mod tests { ); } } + + /// Exhaustively fold every DNA sequence up to a small length and assert + /// nothing panics. Guards against regressions like the out-of-bounds + /// traceback on short/unfoldable sequences (issue #31). + #[test] + fn test_fuzz_no_panic() { + const ALPHABET: &[u8] = b"ACGT"; + for len in 0..=8usize { + for mut code in 0..ALPHABET.len().pow(len as u32) { + let mut seq = String::with_capacity(len); + for _ in 0..len { + seq.push(ALPHABET[code % ALPHABET.len()] as char); + code /= ALPHABET.len(); + } + let g = dg(&seq, 37.0).expect("dg should not error"); + // sequences too short to form a hairpin are undefined (-inf), + // matching seqfold 0.9.0 + if (1..5).contains(&len) { + assert!(g.is_infinite() && g < 0.0, "{:?} -> {}", seq, g); + } + } + } + } }