From 48206ed6c0b6fa8aba3178db1a5455027221cf85 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sun, 12 Jul 2026 09:17:12 +0200 Subject: [PATCH] fix(cli): preserve unsigned fast-level parsing The C command-line parser accepts short-form levels through an unsigned 32-bit conversion before storing them in the signed compression-level field. The Rust parser used signed parsing for both ordinary levels and --fast, rejecting 4294967295 even though it represents the valid fast level -1. Mirror the conversion and retain the fast-level clamp for the attached form. Test Plan: - cargo test --manifest-path rust/cli/Cargo.toml --all-features - cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets -- -D warnings - make -C programs zstd - ./programs/zstd --fast -4294967295 tests/playTests.sh -o /tmp/zstd-fast-test - ./programs/zstd --fast=4294967295 tests/playTests.sh -o /tmp/zstd-fast-test Refs: tests/playTests.sh maximum fast-level cases --- rust/src/zstd_cli.rs | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/rust/src/zstd_cli.rs b/rust/src/zstd_cli.rs index 8504051ac..41115cfcf 100644 --- a/rust/src/zstd_cli.rs +++ b/rust/src/zstd_cli.rs @@ -516,6 +516,14 @@ fn parse_i32(value: &str, name: &str) -> Result { .map_err(|_| format!("invalid {name}: {value:?}")) } +fn parse_compression_level(value: &str) -> Result { + /* The C CLI reads short-form levels as U32 before storing them in its + * signed level field. Preserve that conversion for values such as + * 4294967295, which represents -1 on the supported two's-complement + * targets and is a valid fast-compression level. */ + Ok(parse_u32(value, "compression level")? as i32) +} + fn parse_worker_count(value: &str) -> Result { let workers = parse_i32(value, "thread count")?; if workers < 0 { @@ -803,14 +811,14 @@ fn parse_long_option( } "--fast" => { let mut level = match attached { - Some(value) => parse_i32(value, "fast level")?, + Some(value) => parse_u32(value, "fast level")?, None => 1, }; - if level <= 0 { + if level == 0 { return Err("fast level must be positive".to_owned()); } - level = level.min(MAX_FAST_ACCELERATION); - cli.level = -level; + level = level.min(MAX_FAST_ACCELERATION as u32); + cli.level = -(level as i32); Ok(None) } "--long" => { @@ -939,7 +947,7 @@ fn parse_short_options( while digits_end < bytes.len() && bytes[digits_end].is_ascii_digit() { digits_end += 1; } - cli.level = parse_i32(&value[offset..digits_end], "compression level")?; + cli.level = parse_compression_level(&value[offset..digits_end])?; offset = digits_end; continue; } @@ -1598,6 +1606,17 @@ mod tests { assert_eq!(cli.display_level, 1); } + #[test] + fn fast_levels_preserve_the_unsigned_32_bit_conversion() { + let cli = parse(&["zstd", "--fast", "-4294967295", "input"]); + + assert_eq!(cli.level, -1); + + let cli = parse(&["zstd", "--fast=4294967295", "input"]); + + assert_eq!(cli.level, -MAX_FAST_ACCELERATION); + } + #[test] fn short_option_equals_form_is_accepted() { let cli = parse(&["zstd", "-T=2", "-M=64M", "-B=1M", "input"]);