diff --git a/src/context_diff.rs b/src/context_diff.rs index 7ddffea..305c9e4 100644 --- a/src/context_diff.rs +++ b/src/context_diff.rs @@ -53,6 +53,11 @@ fn make_diff( ) -> Vec { let mut line_number_expected = 1; let mut line_number_actual = 1; + // `context_size` comes straight from the command line and may be enormous. + // Neither file can have more lines than it has bytes, so a larger request + // already means "the whole file"; clamping keeps the arithmetic below in + // range and the preallocation proportional to the input. + let context_size = context_size.min(expected.len() + actual.len() + 1); let mut context_queue: VecDeque<&[u8]> = VecDeque::with_capacity(context_size); let mut lines_since_mismatch = context_size + 1; let mut results = Vec::new(); diff --git a/src/params.rs b/src/params.rs index 74ef3e3..87fcccc 100644 --- a/src/params.rs +++ b/src/params.rs @@ -276,7 +276,9 @@ fn match_context_diff_params( .or(captures.name("num3")); if let Some(numvalue) = num { if !numvalue.as_str().is_empty() { - context_count = Some(numvalue.as_str().parse::().unwrap()); + // GNU accepts a count larger than the machine can represent and + // clamps it, so saturate rather than panicking on overflow. + context_count = Some(numvalue.as_str().parse::().unwrap_or(usize::MAX)); } } if param == "-C" { @@ -320,7 +322,9 @@ fn match_unified_diff_params( .or(captures.name("num3")); if let Some(numvalue) = num { if !numvalue.as_str().is_empty() { - context_count = Some(numvalue.as_str().parse::().unwrap()); + // GNU accepts a count larger than the machine can represent and + // clamps it, so saturate rather than panicking on overflow. + context_count = Some(numvalue.as_str().parse::().unwrap_or(usize::MAX)); } } if param == "-U" { @@ -916,4 +920,24 @@ mod tests { .is_err()); } } + + #[test] + fn oversized_context_count_saturates() { + // A digit run too large for usize must clamp rather than panic. + for arg in [ + "-u99999999999999999999", + "-c99999999999999999999", + "--unified=99999999999999999999", + "--context=99999999999999999999", + ] { + let params = parse_params( + [os("diff"), os(arg), os("foo"), os("bar")] + .iter() + .cloned() + .peekable(), + ) + .unwrap(); + assert_eq!(params.context_count, usize::MAX); + } + } } diff --git a/src/unified_diff.rs b/src/unified_diff.rs index 27773b1..252a0f4 100644 --- a/src/unified_diff.rs +++ b/src/unified_diff.rs @@ -44,6 +44,11 @@ fn make_diff( ) -> Vec { let mut line_number_expected = 1; let mut line_number_actual = 1; + // `context_size` comes straight from the command line and may be enormous. + // Neither file can have more lines than it has bytes, so a larger request + // already means "the whole file"; clamping keeps the arithmetic below in + // range and the preallocation proportional to the input. + let context_size = context_size.min(expected.len() + actual.len() + 1); let mut context_queue: VecDeque<&[u8]> = VecDeque::with_capacity(context_size); let mut lines_since_mismatch = context_size + 1; let mut results = Vec::new(); diff --git a/tests/integration.rs b/tests/integration.rs index 12aabb8..e87b2c8 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -224,6 +224,35 @@ mod diff { Ok(()) } + #[test] + fn oversized_context_count() -> Result<(), Box> { + let mut file1 = NamedTempFile::new()?; + file1.write_all("a\nb\nc\n".as_bytes())?; + let mut file2 = NamedTempFile::new()?; + file2.write_all("a\nX\nc\n".as_bytes())?; + + // A context count past usize, and one that fits but would overflow the + // queue's capacity in bytes. Both used to abort. + for option in [ + "-u99999999999999999999", + "-c99999999999999999999", + "--unified=99999999999999999999", + "--context=99999999999999999999", + "-U600000000000000000", + "-C600000000000000000", + ] { + let mut cmd = cargo_bin_cmd!("diffutils"); + cmd.arg("diff") + .arg(option) + .arg(file1.path()) + .arg(file2.path()); + cmd.assert() + .code(predicate::eq(1)) + .stdout(predicate::str::contains("X")); + } + Ok(()) + } + #[test] fn read_from_stdin() -> Result<(), Box> { let mut file1 = NamedTempFile::new()?;