Skip to content

Commit 1e7fb96

Browse files
committed
fix: make number conversions safe
1 parent 3c78d22 commit 1e7fb96

File tree

5 files changed

+70
-54
lines changed

5 files changed

+70
-54
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ license = "MIT"
66
publish = false
77

88
[dependencies]
9+
cast = { version = "0.3.0", features = ["std"] }
910
color-eyre = "=0.6.4"
1011
crossterm = { version = "=0.28.1", features = [
1112
"event-stream",

src/checker.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ pub async fn check_all(
4141
storage: ProxyStorage,
4242
#[cfg(feature = "tui")] tx: tokio::sync::mpsc::UnboundedSender<Event>,
4343
) -> color_eyre::Result<ProxyStorage> {
44-
let semaphore =
45-
Arc::new(tokio::sync::Semaphore::new(config.max_concurrent_checks));
44+
let semaphore = Arc::new(tokio::sync::Semaphore::new(
45+
config.max_concurrent_checks.min(tokio::sync::Semaphore::MAX_PERMITS),
46+
));
4647
let mut join_set = tokio::task::JoinSet::new();
4748
for proxy in storage {
4849
let config = Arc::clone(&config);

src/config.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,15 +173,14 @@ impl Config {
173173
let max_concurrent_checks =
174174
match rlimit::increase_nofile_limit(u64::MAX) {
175175
Ok(lim) => {
176-
#[expect(clippy::as_conversions)]
177-
#[expect(clippy::cast_possible_truncation)]
178-
if raw_config.max_concurrent_checks > (lim as usize) {
176+
let lim = cast::usize(lim);
177+
if raw_config.max_concurrent_checks > lim {
179178
log::warn!(
180179
"max_concurrent_checks config value is too high \
181180
for your OS. It will be ignored and {lim} will \
182181
be used."
183182
);
184-
lim as usize
183+
lim
185184
} else {
186185
raw_config.max_concurrent_checks
187186
}

src/ui/tui.rs

Lines changed: 56 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
#![expect(
2-
clippy::arithmetic_side_effects,
3-
clippy::as_conversions,
4-
clippy::cast_precision_loss,
52
clippy::indexing_slicing,
6-
clippy::integer_division_remainder_used,
73
clippy::missing_asserts_for_indexing,
84
clippy::wildcard_enum_match_arm
95
)]
@@ -124,6 +120,7 @@ async fn tick_event_listener(
124120
) -> Result<(), tokio::sync::mpsc::error::SendError<Event>> {
125121
let mut tick =
126122
tokio::time::interval(tokio::time::Duration::from_secs_f64(1.0 / FPS));
123+
#[expect(clippy::integer_division_remainder_used)]
127124
loop {
128125
tokio::select! {
129126
biased;
@@ -141,6 +138,7 @@ async fn crossterm_event_listener(
141138
tx: tokio::sync::mpsc::UnboundedSender<Event>,
142139
) -> Result<(), tokio::sync::mpsc::error::SendError<Event>> {
143140
let mut reader = crossterm::event::EventStream::new();
141+
#[expect(clippy::integer_division_remainder_used)]
144142
loop {
145143
tokio::select! {
146144
biased;
@@ -198,10 +196,13 @@ fn draw(f: &mut Frame, state: &AppState, logger_state: &TuiWidgetState) {
198196
f.render_widget(
199197
Gauge::default()
200198
.block(Block::bordered().title("GeoDB download"))
201-
.ratio(if state.geodb_total == 0 {
202-
1.0
203-
} else {
204-
(state.geodb_downloaded as f64) / (state.geodb_total as f64)
199+
.ratio({
200+
let total = cast::f64(state.geodb_total);
201+
if total == 0.0 {
202+
1.0
203+
} else {
204+
cast::f64(state.geodb_downloaded) / total
205+
}
205206
}),
206207
outer_layout[1],
207208
);
@@ -231,63 +232,57 @@ fn draw(f: &mut Frame, state: &AppState, logger_state: &TuiWidgetState) {
231232

232233
f.render_widget(
233234
Gauge::default()
234-
.ratio(if sources_total == 0 {
235-
0.0
236-
} else {
237-
sources_scraped as f64 / sources_total as f64
235+
.ratio({
236+
let total = cast::f64(sources_total);
237+
if total == 0.0 {
238+
1.0
239+
} else {
240+
cast::f64(sources_scraped) / total
241+
}
238242
})
239243
.block(Block::bordered().title("Scraping sources"))
240244
.label(format!("{sources_scraped}/{sources_total}")),
241245
layout[0],
242246
);
243247

244-
let proxies_checked =
245-
state.proxies_checked.get(proxy_type).copied().unwrap_or_default();
246-
let proxies_working =
247-
state.proxies_working.get(proxy_type).copied().unwrap_or_default();
248248
let proxies_total =
249249
state.proxies_total.get(proxy_type).copied().unwrap_or_default();
250-
250+
let proxies_checked =
251+
state.proxies_checked.get(proxy_type).copied().unwrap_or_default();
251252
f.render_widget(
252253
Gauge::default()
253-
.ratio(if proxies_total == 0 {
254-
0.0
255-
} else {
256-
proxies_checked as f64 / proxies_total as f64
254+
.ratio({
255+
let total = cast::f64(proxies_total);
256+
if total == 0.0 {
257+
1.0
258+
} else {
259+
cast::f64(proxies_checked) / total
260+
}
257261
})
258262
.block(Block::bordered().title("Checking proxies"))
259263
.label(format!("{proxies_checked}/{proxies_total}")),
260264
layout[1],
261265
);
262266

267+
let working_proxies_block = Block::bordered().title("Working proxies");
268+
f.render_widget(working_proxies_block.clone(), layout[2]);
269+
270+
let proxies_working =
271+
state.proxies_working.get(proxy_type).copied().unwrap_or_default();
263272
f.render_widget(
264-
Gauge::default()
265-
.ratio(if proxies_total == 0 {
266-
0.0
267-
} else {
268-
proxies_checked as f64 / proxies_total as f64
269-
})
270-
.block(
271-
Block::bordered()
272-
.title("Working proxies / checked proxies"),
273-
)
274-
.label(format!(
275-
"{}/{} ({:.1}%)",
276-
proxies_working,
277-
proxies_checked,
278-
if proxies_working != 0 {
279-
(proxies_working as f64 / proxies_checked as f64)
280-
* 100.0_f64
281-
} else {
282-
0.0_f64
283-
}
284-
)),
285-
layout[2],
273+
Line::from(format!(
274+
"{} ({:.1}%)",
275+
proxies_working,
276+
(cast::f64(proxies_working) / cast::f64(proxies_checked))
277+
* 100.0_f64
278+
))
279+
.alignment(Alignment::Center),
280+
working_proxies_block.inner(layout[2]),
286281
);
287282
}
288283

289284
let done = matches!(state.mode, AppMode::Done);
290-
let mut lines = Vec::with_capacity(2 + usize::from(done));
285+
let mut lines = Vec::with_capacity(usize::from(done).saturating_add(2));
291286
lines.push(Line::from("Up/PageUp/k - scroll logs up"));
292287
lines.push(Line::from("Down/PageDown/j - scroll logs down"));
293288
if done {
@@ -353,22 +348,35 @@ async fn handle_event(
353348
state.geodb_total = bytes.unwrap_or_default();
354349
}
355350
AppEvent::GeoDbDownloaded(bytes) => {
356-
state.geodb_downloaded += bytes;
351+
state.geodb_downloaded =
352+
state.geodb_downloaded.saturating_add(bytes);
357353
}
358354
AppEvent::SourcesTotal(proxy_type, amount) => {
359355
state.sources_total.insert(proxy_type, amount);
360356
}
361357
AppEvent::SourceScraped(proxy_type) => {
362-
*state.sources_scraped.entry(proxy_type).or_default() += 1;
358+
state
359+
.sources_scraped
360+
.entry(proxy_type)
361+
.and_modify(|c| *c = c.saturating_add(1))
362+
.or_insert(1);
363363
}
364364
AppEvent::TotalProxies(proxy_type, amount) => {
365365
state.proxies_total.insert(proxy_type, amount);
366366
}
367367
AppEvent::ProxyChecked(proxy_type) => {
368-
*state.proxies_checked.entry(proxy_type).or_default() += 1;
368+
state
369+
.proxies_checked
370+
.entry(proxy_type)
371+
.and_modify(|c| *c = c.saturating_add(1))
372+
.or_insert(1);
369373
}
370374
AppEvent::ProxyWorking(proxy_type) => {
371-
*state.proxies_working.entry(proxy_type).or_default() += 1;
375+
state
376+
.proxies_working
377+
.entry(proxy_type)
378+
.and_modify(|c| *c = c.saturating_add(1))
379+
.or_insert(1);
372380
}
373381
AppEvent::Done => {
374382
state.mode = if is_interactive().await {

0 commit comments

Comments
 (0)