diff --git a/src/parameter/range_step.rs b/src/parameter/range_step.rs index 88ed76597..d9ba20701 100644 --- a/src/parameter/range_step.rs +++ b/src/parameter/range_step.rs @@ -1,4 +1,4 @@ -use std::convert::TryInto; +use std::convert::{TryFrom, TryInto}; use std::ops::{Add, AddAssign, Div, Sub}; use crate::error::ParameterScanError; @@ -86,6 +86,18 @@ fn range_step_size_hint(start: T, end: T, step: T) -> (usize, Option return (usize::MAX, None); } + // Integer scans are stored as i32. Computing `(end - start + 1) / step` + // in i32 overflows for a full-width span (`i32::MIN..=i32::MAX`) and for + // ranges where `end - start` itself fits but `+ 1` does not (`0..=i32::MAX`). + // Those ranges are far above MAX_PARAMETERS; evaluate them in i64 so we + // can reject them instead of panicking in debug / wrapping in release. + if let (Number::Int(s), Number::Int(e), Number::Int(st)) = + (start.into(), end.into(), step.into()) + { + let steps = (i64::from(e) - i64::from(s) + 1) / i64::from(st); + return usize::try_from(steps).map_or((usize::MAX, None), |u| (u, Some(u))); + } + let steps = (end - start + T::from(1)) / step; steps .into() @@ -181,4 +193,23 @@ mod tests { "Parameter range is too large" ); } + + #[test] + fn full_i32_span_is_too_large_not_overflow() { + let result = RangeStep::new(i32::MIN, i32::MAX, 1); + assert_eq!( + format!("{}", result.unwrap_err()), + "Parameter range is too large" + ); + } + + #[test] + fn i32_add_overflow_span_is_too_large_not_overflow() { + // `(end - start + 1)` overflows i32 even though `end - start` does not. + let result = RangeStep::new(0, i32::MAX, 1); + assert_eq!( + format!("{}", result.unwrap_err()), + "Parameter range is too large" + ); + } } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 7b7cc2ff9..5777ef118 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1283,6 +1283,19 @@ fn unused_parameters_are_shown_in_benchmark_name() { ); } +#[test] +fn parameter_scan_full_i32_range_is_rejected() { + hyperfine() + .arg("--parameter-scan") + .arg("x") + .arg("-2147483648") + .arg("2147483647") + .arg("true") + .assert() + .failure() + .stderr(predicate::str::contains("Parameter range is too large")); +} + #[cfg(windows)] #[test] fn windows_quote_args() {