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
This commit is contained in:
2026-07-12 09:17:12 +02:00
parent 57a2f3bef4
commit 48206ed6c0
+24 -5
View File
@@ -516,6 +516,14 @@ fn parse_i32(value: &str, name: &str) -> Result<i32, String> {
.map_err(|_| format!("invalid {name}: {value:?}"))
}
fn parse_compression_level(value: &str) -> Result<i32, String> {
/* 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<i32, String> {
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"]);