diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 83fb932e7..5564c6676 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -322,6 +322,28 @@ typedef char ZSTD_rust_free_cctx_state_layout[ == 4 * sizeof(void*) && sizeof(ZSTD_rust_freeCCtxState) == 5 * sizeof(void*)) ? 1 : -1]; +typedef int (*ZSTD_rust_createCCtxValidateCustomMem_f)(void* context); +typedef void* (*ZSTD_rust_createCCtxAllocate_f)(void* context, size_t size); +typedef void (*ZSTD_rust_createCCtxInit_f)(void* context, void* cctx); +typedef struct { + void* callbackContext; + size_t cctxSize; + ZSTD_rust_createCCtxValidateCustomMem_f validateCustomMem; + ZSTD_rust_createCCtxAllocate_f allocate; + ZSTD_rust_createCCtxInit_f init; +} ZSTD_rust_createCCtxState; +void* ZSTD_rust_createCCtx(const ZSTD_rust_createCCtxState* state); +typedef char ZSTD_rust_create_cctx_state_layout[ + (offsetof(ZSTD_rust_createCCtxState, callbackContext) == 0 + && offsetof(ZSTD_rust_createCCtxState, cctxSize) == sizeof(void*) + && offsetof(ZSTD_rust_createCCtxState, validateCustomMem) + == 2 * sizeof(void*) + && offsetof(ZSTD_rust_createCCtxState, allocate) + == 3 * sizeof(void*) + && offsetof(ZSTD_rust_createCCtxState, init) + == 4 * sizeof(void*) + && sizeof(ZSTD_rust_createCCtxState) == 5 * sizeof(void*)) + ? 1 : -1]; typedef void (*ZSTD_rust_resetCCtxClearAllDicts_f)(void* context); typedef size_t (*ZSTD_rust_resetCCtxResetParams_f)(void* context); typedef struct { @@ -2053,16 +2075,33 @@ static void ZSTD_initCCtx(ZSTD_CCtx* cctx, ZSTD_customMem memManager) } } +static int ZSTD_rust_createCCtx_validateCustomMem(void* context) +{ + ZSTD_customMem const* const customMem = (const ZSTD_customMem*)context; + return ((!customMem->customAlloc) ^ (!customMem->customFree)) == 0; +} + +static void* ZSTD_rust_createCCtx_allocate(void* context, size_t size) +{ + return ZSTD_customMalloc(size, *(const ZSTD_customMem*)context); +} + +static void ZSTD_rust_createCCtx_init(void* context, void* cctx) +{ + ZSTD_initCCtx((ZSTD_CCtx*)cctx, *(const ZSTD_customMem*)context); +} + ZSTD_CCtx* ZSTD_createCCtx_advanced(ZSTD_customMem customMem) { + ZSTD_rust_createCCtxState state; ZSTD_STATIC_ASSERT(zcss_init==0); ZSTD_STATIC_ASSERT(ZSTD_CONTENTSIZE_UNKNOWN==(0ULL - 1)); - if ((!customMem.customAlloc) ^ (!customMem.customFree)) return NULL; - { ZSTD_CCtx* const cctx = (ZSTD_CCtx*)ZSTD_customMalloc(sizeof(ZSTD_CCtx), customMem); - if (!cctx) return NULL; - ZSTD_initCCtx(cctx, customMem); - return cctx; - } + state.callbackContext = &customMem; + state.cctxSize = sizeof(ZSTD_CCtx); + state.validateCustomMem = ZSTD_rust_createCCtx_validateCustomMem; + state.allocate = ZSTD_rust_createCCtx_allocate; + state.init = ZSTD_rust_createCCtx_init; + return (ZSTD_CCtx*)ZSTD_rust_createCCtx(&state); } ZSTD_CCtx* ZSTD_initStaticCCtx(void* workspace, size_t workspaceSize) diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index 4371d0fda..d794fc3ca 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -7,9 +7,9 @@ //! //! The layout-independent one-shot entry point in this module drives the //! already migrated compression leaves. `ZSTD_compressCCtx` uses the same -//! path after a narrow C-owned context reset; the public context lifecycle -//! remains C-owned because it shares the configuration-dependent private -//! `ZSTD_CCtx_s` layout. `ZSTD_compress2` and the complete-input simple +//! path after a narrow C-owned context reset; public context allocation now +//! runs through a Rust-owned boundary while C retains configuration-dependent +//! private `ZSTD_CCtx_s` initialization. `ZSTD_compress2` and the complete-input simple //! `ZSTD_compressStream2(..., ZSTD_e_end)` path dispatch through Rust while //! retaining the C implementation for advanced and partial-stream cases. @@ -1275,6 +1275,55 @@ pub unsafe extern "C" fn ZSTD_rust_refThreadPool( 0 } +type CreateCCtxValidateCustomMemFn = unsafe extern "C" fn(*mut c_void) -> c_int; +type CreateCCtxAllocateFn = unsafe extern "C" fn(*mut c_void, usize) -> *mut c_void; +type CreateCCtxInitFn = unsafe extern "C" fn(*mut c_void, *mut c_void); + +/// Explicit projection for the public `ZSTD_createCCtx_advanced` wrapper. +/// +/// Rust owns custom-memory validation, allocation failure, and +/// allocate-before-init ordering. C retains private `ZSTD_CCtx` initialization +/// behind the final callback. +#[repr(C)] +pub struct ZSTD_rust_createCCtxState { + callback_context: *mut c_void, + cctx_size: usize, + validate_custom_mem: CreateCCtxValidateCustomMemFn, + allocate: CreateCCtxAllocateFn, + init: CreateCCtxInitFn, +} + +const _: () = { + assert!(offset_of!(ZSTD_rust_createCCtxState, callback_context) == 0); + assert!(offset_of!(ZSTD_rust_createCCtxState, cctx_size) == size_of::()); + assert!(offset_of!(ZSTD_rust_createCCtxState, validate_custom_mem) == 2 * size_of::()); + assert!(offset_of!(ZSTD_rust_createCCtxState, allocate) == 3 * size_of::()); + assert!(offset_of!(ZSTD_rust_createCCtxState, init) == 4 * size_of::()); + assert!(size_of::() == size_of::<[usize; 5]>()); +}; + +/// Allocate and initialize a C-owned compression context. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_createCCtx( + state: *const ZSTD_rust_createCCtxState, +) -> *mut c_void { + if state.is_null() { + return ptr::null_mut(); + } + let state = unsafe { &*state }; + if state.callback_context.is_null() + || unsafe { (state.validate_custom_mem)(state.callback_context) == 0 } + { + return ptr::null_mut(); + } + let cctx = unsafe { (state.allocate)(state.callback_context, state.cctx_size) }; + if cctx.is_null() { + return ptr::null_mut(); + } + unsafe { (state.init)(state.callback_context, cctx) }; + cctx +} + type FreeCCtxContentFn = unsafe extern "C" fn(*mut c_void); type FreeCCtxObjectFn = unsafe extern "C" fn(*mut c_void); @@ -11904,6 +11953,98 @@ mod tests { assert_eq!(result, ERROR(ZstdErrorCode::Generic)); } + #[derive(Default)] + struct CreateCCtxTestContext { + events: Vec<&'static str>, + valid: c_int, + allocation: *mut c_void, + requested_size: usize, + initialized: *mut c_void, + } + + unsafe extern "C" fn create_cctx_test_validate(context: *mut c_void) -> c_int { + let context = unsafe { &mut *context.cast::() }; + context.events.push("validate"); + context.valid + } + + unsafe extern "C" fn create_cctx_test_allocate( + context: *mut c_void, + size: usize, + ) -> *mut c_void { + let context = unsafe { &mut *context.cast::() }; + context.events.push("allocate"); + context.requested_size = size; + context.allocation + } + + unsafe extern "C" fn create_cctx_test_init(context: *mut c_void, cctx: *mut c_void) { + let context = unsafe { &mut *context.cast::() }; + context.events.push("init"); + context.initialized = cctx; + } + + fn create_cctx_test_state(context: &mut CreateCCtxTestContext) -> ZSTD_rust_createCCtxState { + ZSTD_rust_createCCtxState { + callback_context: (context as *mut CreateCCtxTestContext).cast(), + cctx_size: 123, + validate_custom_mem: create_cctx_test_validate, + allocate: create_cctx_test_allocate, + init: create_cctx_test_init, + } + } + + #[test] + fn create_cctx_validates_allocates_and_initializes_in_order() { + let allocation = ptr::dangling_mut::(); + let mut context = CreateCCtxTestContext { + valid: 1, + allocation, + ..Default::default() + }; + let state = create_cctx_test_state(&mut context); + + let result = unsafe { ZSTD_rust_createCCtx(&state) }; + + assert_eq!(result, allocation); + assert_eq!(context.events, ["validate", "allocate", "init"]); + assert_eq!(context.requested_size, 123); + assert_eq!(context.initialized, allocation); + } + + #[test] + fn create_cctx_stops_before_allocation_for_invalid_memory_callbacks() { + let mut context = CreateCCtxTestContext { + valid: 0, + allocation: ptr::dangling_mut(), + ..Default::default() + }; + let state = create_cctx_test_state(&mut context); + + let result = unsafe { ZSTD_rust_createCCtx(&state) }; + + assert!(result.is_null()); + assert_eq!(context.events, ["validate"]); + assert_eq!(context.requested_size, 0); + assert!(context.initialized.is_null()); + } + + #[test] + fn create_cctx_stops_before_initialization_after_allocation_failure() { + let mut context = CreateCCtxTestContext { + valid: 1, + ..Default::default() + }; + let state = create_cctx_test_state(&mut context); + + let result = unsafe { ZSTD_rust_createCCtx(&state) }; + + assert!(result.is_null()); + assert_eq!(context.events, ["validate", "allocate"]); + assert_eq!(context.requested_size, 123); + assert!(context.initialized.is_null()); + } + #[derive(Default)] struct FreeCCtxTestContext { events: Vec<&'static str>,