diff --git a/lib/decompress/zstd_decompress.c b/lib/decompress/zstd_decompress.c index b9602d51e..546c27b7e 100644 --- a/lib/decompress/zstd_decompress.c +++ b/lib/decompress/zstd_decompress.c @@ -196,6 +196,14 @@ typedef char ZSTD_rust_no_forward_progress_projection_layout[ ? 1 : -1]; int ZSTD_rust_no_forward_progress_policy( const ZSTD_rustNoForwardProgressProjection* projection); +typedef struct { + int configured_mode; +} ZSTD_rustHeapModeProjection; +typedef char ZSTD_rust_heapmode_projection_layout[ + (offsetof(ZSTD_rustHeapModeProjection, configured_mode) == 0 + && sizeof(ZSTD_rustHeapModeProjection) == sizeof(int)) + ? 1 : -1]; +int ZSTD_rust_heapmode_policy(const ZSTD_rustHeapModeProjection* projection); typedef struct { unsigned configured_level; } ZSTD_rustLegacySupportProjection; @@ -333,7 +341,10 @@ int ZSTD_rust_no_forward_progress_max(void) int ZSTD_rust_heapmode(void) { - return ZSTD_HEAPMODE; + ZSTD_rustHeapModeProjection const projection = { + ZSTD_HEAPMODE + }; + return ZSTD_rust_heapmode_policy(&projection); } size_t ZSTD_rust_decompress_stack(void* dst, size_t dstCapacity, @@ -341,7 +352,7 @@ size_t ZSTD_rust_decompress_stack(void* dst, size_t dstCapacity, { ZSTD_DCtx dctx; ZSTD_rustDecompressStackProjection const projection = { - ZSTD_HEAPMODE, + ZSTD_rust_heapmode(), &dctx, sizeof(dctx), dst, diff --git a/rust/src/zstd_decompress.rs b/rust/src/zstd_decompress.rs index 477918d7a..a87ac47d8 100644 --- a/rust/src/zstd_decompress.rs +++ b/rust/src/zstd_decompress.rs @@ -321,6 +321,17 @@ const _: () = { assert!(size_of::() == size_of::()); }; +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct ZSTD_rustHeapModeProjection { + pub configured_mode: c_int, +} + +const _: () = { + assert!(offset_of!(ZSTD_rustHeapModeProjection, configured_mode) == 0); + assert!(size_of::() == size_of::()); +}; + const _: () = { assert!(offset_of!(ZSTD_rustDctxView, dctx) == 0); assert!(offset_of!(ZSTD_rustDctxView, static_size) == 29 * size_of::()); @@ -682,6 +693,21 @@ pub unsafe extern "C" fn ZSTD_rust_no_forward_progress_policy( projection.configured_max } +/// Normalize the C build's heap-mode setting to the one-shot branch policy. +/// +/// C supplies the compile-time value because it may be overridden by the +/// build. Rust owns the decision that values below one use the stack entry +/// path and values at or above one use an allocated DCtx. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_heapmode_policy( + projection: *const ZSTD_rustHeapModeProjection, +) -> c_int { + let Some(projection) = (unsafe { projection.as_ref() }) else { + return 0; + }; + c_int::from(projection.configured_mode >= 1) +} + #[cfg(test)] mod no_forward_progress_policy_tests { use super::*; @@ -706,6 +732,28 @@ mod no_forward_progress_policy_tests { } } +#[cfg(test)] +mod heapmode_policy_tests { + use super::*; + + #[test] + fn normalizes_stack_and_heap_modes() { + for configured_mode in [c_int::MIN, -1, 0] { + let projection = ZSTD_rustHeapModeProjection { configured_mode }; + assert_eq!(unsafe { ZSTD_rust_heapmode_policy(&projection) }, 0); + } + for configured_mode in [1, c_int::MAX] { + let projection = ZSTD_rustHeapModeProjection { configured_mode }; + assert_eq!(unsafe { ZSTD_rust_heapmode_policy(&projection) }, 1); + } + } + + #[test] + fn rejects_missing_heapmode_configuration() { + assert_eq!(unsafe { ZSTD_rust_heapmode_policy(ptr::null()) }, 0); + } +} + /// Normalize the C build's legacy-support threshold before dispatch. /// /// C supplies the active compile-time value because test builds intentionally