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.
This commit is contained in:
Karim Jerbi committed 2026-09-30 19:15:49 +01:00
1 parent e15d801011
commit b19253374c
1 file changed
+26 -1
+26 -1
View File
@@ -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(),
);
}
}