From 9df7b55d8f1260237324cdc857c631ff2eda6727 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sat, 18 Jul 2026 08:48:41 +0200 Subject: [PATCH] fix(compress): restore C fallback for unsupported CCtx strategies ZSTD_compressCCtx was selecting the Rust frame path for every compression level. For source sizes that resolve to lazy or optimal strategies, that path fell through to fast matching and could produce a different frame, including the CCtx reuse regression caught by fuzzer test 56. Query the source-size-dependent C parameters before preparing the context. Keep fast and double-fast strategies on the existing Rust path, while routing all other strategies through ZSTD_compress_usingDict with no dictionary so the original simple API initializes requested parameters and C match state. Test Plan: - `make -B -C tests -j2 fuzzer` -- passed - `./tests/fuzzer -s4560 -t56 -i56 -v` -- passed - `cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression` -- 253 passed - required clippy normal, benches, and tests checks -- passed - `cargo +nightly fmt --manifest-path rust/Cargo.toml` -- passed - `git diff --check` and `git diff --cached --check` -- passed --- lib/compress/zstd_compress.c | 8 ++++++++ rust/src/zstd_compress.rs | 30 ++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 6b1650b41..53502d9c0 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -50,6 +50,7 @@ size_t ZSTD_rust_resetCCtxForSimpleCompression(void* cctx); size_t ZSTD_rust_prepareCCtxForSimpleCompression(void* cctx, size_t srcSize, int compressionLevel); +int ZSTD_rust_compressCCtxStrategy(size_t srcSize, int compressionLevel); size_t ZSTD_rust_resetCCtxForSimpleCompressionSession(void* cctx); void ZSTD_rust_markSimpleCompression2Complete(void* cctx); size_t ZSTD_compress2_c(ZSTD_CCtx* cctx, @@ -3837,6 +3838,13 @@ size_t ZSTD_compress_usingDict(ZSTD_CCtx* cctx, /* The Rust simple API still needs the C-owned context reset, but does not * cross the private context layout. */ +int ZSTD_rust_compressCCtxStrategy(size_t srcSize, int compressionLevel) +{ + ZSTD_parameters const params = ZSTD_getParams_internal( + compressionLevel, srcSize, 0, ZSTD_cpm_noAttachDict); + return (int)params.cParams.strategy; +} + size_t ZSTD_rust_resetCCtxForSimpleCompression(void* cctx) { return ZSTD_CCtx_reset((ZSTD_CCtx*)cctx, ZSTD_reset_session_and_parameters); diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index a2226214f..a7b080eca 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -40,9 +40,20 @@ unsafe extern "C" { src_size: usize, compression_level: c_int, ) -> usize; + fn ZSTD_rust_compressCCtxStrategy(src_size: usize, compression_level: c_int) -> c_int; fn ZSTD_rust_resetCCtxForSimpleCompressionSession(cctx: *mut c_void) -> usize; fn ZSTD_rust_markSimpleCompression2Complete(cctx: *mut c_void); fn ZSTD_rust_simpleCompress2Level(cctx: *const c_void) -> c_int; + fn ZSTD_compress_usingDict( + cctx: *mut c_void, + dst: *mut c_void, + dst_capacity: usize, + src: *const c_void, + src_size: usize, + dict: *const c_void, + dict_size: usize, + compression_level: c_int, + ) -> usize; fn ZSTD_rust_simpleCompressStream2Level(cctx: *const c_void) -> c_int; fn ZSTD_compress2_c( cctx: *mut c_void, @@ -565,8 +576,8 @@ pub unsafe extern "C" fn ZSTD_compress( /// /// The public contract deliberately ignores all advanced context parameters. /// C performs the context reset because the private `ZSTD_CCtx_s` layout is -/// still configuration-dependent; the frame compressor itself is entirely -/// Rust-owned and does not inspect the context. +/// still configuration-dependent. Strategies not yet implemented by the +/// Rust frame compressor use the original C simple API before that reset. #[no_mangle] pub unsafe extern "C" fn ZSTD_compressCCtx( cctx: *mut c_void, @@ -581,6 +592,21 @@ pub unsafe extern "C" fn ZSTD_compressCCtx( } #[cfg(not(test))] { + let strategy = unsafe { ZSTD_rust_compressCCtxStrategy(src_size, compression_level) }; + if strategy != ZSTD_FAST && strategy != ZSTD_DFAST { + return unsafe { + ZSTD_compress_usingDict( + cctx, + dst, + dst_capacity, + src, + src_size, + ptr::null(), + 0, + compression_level, + ) + }; + } let reset = unsafe { ZSTD_rust_resetCCtxForSimpleCompression(cctx) }; if ERR_isError(reset) { return reset;