From b19253374c795570fb9818ccaa93453b7f5b9c90 Mon Sep 17 00:00:00 2001 From: Karim Jerbi Date: Wed, 30 Sep 2026 06:36:07 +0100 Subject: [PATCH] fix(start): saturate the recent-limit plus step Bug: The keep-recent-files footer on the Start page builds its [-] [count] [+] buttons eagerly while constructing the widget, and the [+] step was written as plain shown + STEP while the [-] button next to it already used saturating_sub. shown comes from the input box, whose handler keeps every ASCII digit with no length cap - so typing 18446744073709551615 (2^64-1) parses to usize::MAX and the addition overflowed during view(), before the button was ever clicked. Debug builds panic with "attempt to add with overflow"; release builds (no overflow checks) wrap to a small number and request a nonsense limit. The clamp in set_recent_limit runs only after the value is computed. Reproduce: 1. Start page -> "keep recent files" box -> type 18446744073709551615. 2. Before: the next frame panics at widget construction (on render, no click needed); release builds wrap the step silently. 3. After: no panic; pressing Enter or [+] applies the clamped limit and the box resyncs to it. Fix: Mirror the [-] button: shown.saturating_add(STEP). At usize::MAX the message carries usize::MAX, which the handler's existing clamp-and-resync treats exactly like any other over-max entry, so both step buttons are now saturating and the release-build wrap is closed as well. on_submit has no arithmetic and needs no change. The regression test calls start_page_content directly with the hostile string - it fails at the same line instead of requiring a GUI session. --- src/app/view/mod.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index 3fcf1149a..d16be1ee0 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -4257,7 +4257,7 @@ pub(super) fn recent_files_panel<'a>( .style(step_style), count_box, button(crate::ui::icons::themed(crate::ui::icons::PLUS, 11.0)) - .on_press(Message::SetRecentLimit(shown + STEP)) + .on_press(Message::SetRecentLimit(shown.saturating_add(STEP))) .padding([3, 6]) .style(step_style), text(format!("/ {}", super::recent::RECENT_MAX)) @@ -4306,4 +4306,29 @@ mod tests { assert_eq!(bg, tab.scene.paper_bg_color); assert!(crate::ui::style::common::canvas_is_light(bg)); } + + /// The [+] step on the keep-recent-files box is built eagerly from an + /// uncapped user string (`RecentLimitInput` keeps every ASCII digit): + /// `18446744073709551615` parses to `usize::MAX`, and the plain + /// `shown + STEP` overflowed while the *widget was constructed* — + /// i.e. during `view()`, before any click. The [−] button next to it + /// already used `saturating_sub`. + #[test] + fn recent_limit_plus_step_survives_an_overflowing_input() { + let _ = start_page_content( + &[], + &[], + false, + &std::collections::HashMap::new(), + &[], + false, + &[], + &std::collections::HashMap::new(), + 50, + "18446744073709551615", + 800.0, + std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0)), + super::super::StartSection::default(), + ); + } }