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
This commit is contained in:
2026-07-18 08:48:41 +02:00
parent 3ab73f46df
commit 9df7b55d8f
2 changed files with 36 additions and 2 deletions
+8
View File
@@ -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);
+28 -2
View File
@@ -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;