diff --git a/src/bun_alloc/c_thunks.rs b/src/bun_alloc/c_thunks.rs index e01d1aab3314..b64aca8e32db 100644 --- a/src/bun_alloc/c_thunks.rs +++ b/src/bun_alloc/c_thunks.rs @@ -6,10 +6,10 @@ //! | brotli `brotli_alloc/free_func` | `(opaque, size: usize)` | `(opaque, ptr)` | //! | JSC `JSTypedArrayBytesDeallocator` | — | `(bytes, ctx)` | //! -//! The plain (non-zone-tagged) variants are free functions below; the -//! zone-tagged variants are minted per-label by [`c_thunks_for_zone!`]. +//! The zone-tagged zlib and brotli variants are minted per-label by +//! [`c_thunks_for_zone!`]. -use core::ffi::{c_uint, c_void}; +use core::ffi::c_void; use crate::default_alloc as raw; @@ -17,18 +17,6 @@ use crate::default_alloc as raw; // Plain default-allocator thunks (no heap-breakdown tagging) // ────────────────────────────────────────────────────────────────────────── -/// zlib `alloc_func` → default allocator `malloc(items * size)` (non-zeroing). -/// Null on failure, which zlib reports as `Z_MEM_ERROR`. -pub extern "C" fn mi_malloc_items(_: *mut c_void, items: c_uint, size: c_uint) -> *mut c_void { - raw::malloc(items as usize * size as usize) -} - -/// `(opaque, ptr)` → default allocator `free(ptr)`; opaque cookie ignored. -pub unsafe extern "C" fn mi_free_opaque(_: *mut c_void, ptr: *mut c_void) { - // SAFETY: ptr was allocated by the default allocator (or is null). - unsafe { raw::free(ptr) }; -} - /// JSC `JSTypedArrayBytesDeallocator` → default allocator `free(bytes)`; `ctx` ignored. pub unsafe extern "C" fn mi_free_bytes(bytes: *mut c_void, _ctx: *mut c_void) { // SAFETY: bytes was allocated by the default allocator (or is null). @@ -55,12 +43,18 @@ pub unsafe extern "C" fn mi_free_bytes(bytes: *mut c_void, _ctx: *mut c_void) { #[macro_export] macro_rules! c_thunks_for_zone { ($name:literal) => { + // One zone for the alloc and free thunks. `get_zone!` expands to its + // own `static` per use, so it must appear once. + fn zone() -> &'static $crate::heap_breakdown::Zone { + $crate::get_zone!($name) + } + pub extern "C" fn malloc_size( _: *mut ::core::ffi::c_void, len: usize, ) -> *mut ::core::ffi::c_void { if $crate::heap_breakdown::ENABLED { - return $crate::get_zone!($name) + return zone() .malloc_zone_malloc(len) .unwrap_or(::core::ptr::null_mut()); } @@ -79,7 +73,7 @@ macro_rules! c_thunks_for_zone { if $crate::heap_breakdown::ENABLED { // SAFETY: `data` was allocated by this zone in one of the // alloc thunks above. - unsafe { $crate::get_zone!($name).malloc_zone_free(data) }; + unsafe { zone().malloc_zone_free(data) }; return; } // SAFETY: data was allocated by the default allocator (or is null). diff --git a/src/runtime/node/zlib/NativeZlib.rs b/src/runtime/node/zlib/NativeZlib.rs index 18156aab9c1f..68650ba4e5c3 100644 --- a/src/runtime/node/zlib/NativeZlib.rs +++ b/src/runtime/node/zlib/NativeZlib.rs @@ -1,5 +1,4 @@ use core::ffi::{c_char, c_int}; -use core::mem; use bun_zlib as c; // bun.zlib — C zlib FFI (NodeMode, z_stream, ReturnCode, FlushValue, deflate*, inflate*) @@ -214,7 +213,7 @@ pub(crate) use _impl::NativeZlib; pub(crate) struct Context { pub(crate) mode: c::NodeMode, - pub(crate) state: c::z_stream, + pub(crate) state: c::ZStream, pub(crate) err: c::ReturnCode, pub flush: c::FlushValue, pub dictionary: Vec, @@ -225,7 +224,7 @@ impl Default for Context { fn default() -> Self { Self { mode: c::NodeMode::NONE, - state: bun_core::ffi::zeroed::(), + state: c::ZStream::new(), err: c::ReturnCode::Ok, flush: c::FlushValue::NoFlush, dictionary: Vec::new(), @@ -272,28 +271,14 @@ impl Context { match self.mode { NONE => unreachable!(), - // SAFETY: FFI — `state` is a valid #[repr(C)] z_stream; zlibVersion() returns a static C string. - DEFLATE | GZIP | DEFLATERAW => unsafe { - self.err = c::deflateInit2_( - &raw mut self.state, - level, - 8, - window_bits_actual, - mem_level, - strategy, - c::zlibVersion().cast(), - c_int::try_from(mem::size_of::()).expect("int cast"), - ); - }, - // SAFETY: FFI — `state` is a valid #[repr(C)] z_stream; zlibVersion() returns a static C string. - INFLATE | GUNZIP | UNZIP | INFLATERAW => unsafe { - self.err = c::inflateInit2_( - &raw mut self.state, - window_bits_actual, - c::zlibVersion().cast(), - c_int::try_from(mem::size_of::()).expect("int cast"), - ); - }, + DEFLATE | GZIP | DEFLATERAW => { + self.err = + self.state + .deflate_init(level, 8, window_bits_actual, mem_level, strategy); + } + INFLATE | GUNZIP | UNZIP | INFLATERAW => { + self.err = self.state.inflate_init(window_bits_actual); + } BROTLI_DECODE | BROTLI_ENCODE => unreachable!(), ZSTD_COMPRESS | ZSTD_DECOMPRESS => unreachable!(), } @@ -320,11 +305,11 @@ impl Context { match self.mode { // SAFETY: FFI — state is an initialized deflate stream; dict_ptr/dict_len borrow the Context-owned dictionary copy. DEFLATE | DEFLATERAW => unsafe { - self.err = c::deflateSetDictionary(&raw mut self.state, dict_ptr, dict_len); + self.err = c::deflateSetDictionary(self.state.as_ptr(), dict_ptr, dict_len); }, // SAFETY: FFI — state is an initialized inflate stream; dict_ptr/dict_len borrow the Context-owned dictionary copy. INFLATERAW => unsafe { - self.err = c::inflateSetDictionary(&raw mut self.state, dict_ptr, dict_len); + self.err = c::inflateSetDictionary(self.state.as_ptr(), dict_ptr, dict_len); }, _ => {} } @@ -344,7 +329,7 @@ impl Context { // are 0 (next_out non-null) so the internal deflate(Z_BLOCK) returns // Z_BUF_ERROR without touching any previously installed caller buffer. unsafe { - self.err = c::deflateParams(&raw mut self.state, level, strategy); + self.err = c::deflateParams(self.state.as_ptr(), level, strategy); } } _ => {} @@ -357,8 +342,8 @@ impl Context { fn error_for_message(&self, default: &'static core::ffi::CStr) -> Error { let mut message: *const c_char = default.as_ptr(); - if !self.state.err_msg.is_null() { - message = self.state.err_msg; + if !self.state.err_msg().is_null() { + message = self.state.err_msg(); } Error { msg: message, @@ -384,11 +369,11 @@ impl Context { match self.mode { // SAFETY: FFI — state was initialized as a deflate stream by deflateInit2_. DEFLATE | DEFLATERAW | GZIP => unsafe { - self.err = c::deflateReset(&raw mut self.state); + self.err = c::deflateReset(self.state.as_ptr()); }, // SAFETY: FFI — state was initialized as an inflate stream by inflateInit2_. INFLATE | INFLATERAW | GUNZIP => unsafe { - self.err = c::inflateReset(&raw mut self.state); + self.err = c::inflateReset(self.state.as_ptr()); }, _ => {} } @@ -399,22 +384,16 @@ impl Context { } pub(crate) fn set_buffers(&mut self, in_: Option<&[u8]>, out: Option<&mut [u8]>) { - self.state.avail_in = match &in_ { - Some(p) => u32::try_from(p.len()).expect("int cast"), - None => 0, - }; - self.state.next_in = match in_ { - Some(p) => p.as_ptr(), - None => core::ptr::null(), - }; - self.state.avail_out = match &out { - Some(p) => u32::try_from(p.len()).expect("int cast"), - None => 0, + let (next_in, avail_in) = match in_ { + Some(p) => (p.as_ptr(), u32::try_from(p.len()).expect("int cast")), + None => (core::ptr::null(), 0), }; - self.state.next_out = match out { - Some(p) => p.as_mut_ptr(), - None => core::ptr::null_mut(), + self.state.set_input(next_in, avail_in); + let (next_out, avail_out) = match out { + Some(p) => (p.as_mut_ptr(), u32::try_from(p.len()).expect("int cast")), + None => (core::ptr::null_mut(), 0), }; + self.state.set_output(next_out, avail_out); } pub(crate) fn flush_value_is_valid(flush: u32) -> bool { @@ -448,8 +427,8 @@ impl Context { return self.do_work_deflate(); } UNZIP => { - if self.state.avail_in > 0 { - next_expected_header_byte = Some(self.state.next_in.cast::()); + if self.state.avail_in() > 0 { + next_expected_header_byte = Some(self.state.next_in()); } if self.gzip_id_bytes_read == 0 { let Some(p) = next_expected_header_byte else { @@ -461,7 +440,7 @@ impl Context { // SAFETY: advancing within the input buffer; only dereferenced // below after confirming avail_in > 1. next_expected_header_byte = Some(unsafe { p.add(1) }); - if self.state.avail_in == 1 { + if self.state.avail_in() == 1 { // The only available byte was already read. return self.do_work_inflate(); } @@ -497,12 +476,12 @@ impl Context { fn do_work_deflate(&mut self) { // SAFETY: FFI — state is an initialized deflate stream. - self.err = unsafe { c::deflate(&raw mut self.state, self.flush) }; + self.err = unsafe { c::deflate(self.state.as_ptr(), self.flush) }; } fn do_work_inflate(&mut self) { // SAFETY: FFI — state is an initialized inflate stream. - self.err = unsafe { c::inflate(&raw mut self.state, self.flush) }; + self.err = unsafe { c::inflate(self.state.as_ptr(), self.flush) }; if self.mode != c::NodeMode::INFLATERAW && self.err == c::ReturnCode::NeedDict @@ -515,38 +494,38 @@ impl Context { (dict.as_ptr(), u32::try_from(dict.len()).expect("int cast")) }; // SAFETY: FFI — state is an initialized inflate stream; dict is the Context-owned copy. - self.err = unsafe { c::inflateSetDictionary(&raw mut self.state, dict_ptr, dict_len) }; + self.err = unsafe { c::inflateSetDictionary(self.state.as_ptr(), dict_ptr, dict_len) }; if self.err == c::ReturnCode::Ok { // SAFETY: FFI — state is an initialized inflate stream. - self.err = unsafe { c::inflate(&raw mut self.state, self.flush) }; + self.err = unsafe { c::inflate(self.state.as_ptr(), self.flush) }; } else if self.err == c::ReturnCode::DataError { self.err = c::ReturnCode::NeedDict; } } - while self.state.avail_in > 0 + while self.state.avail_in() > 0 && self.mode == c::NodeMode::GUNZIP && self.err == c::ReturnCode::StreamEnd // SAFETY: avail_in > 0 ⇒ next_in points to ≥1 readable byte. - && unsafe { *self.state.next_in } != 0 + && unsafe { *self.state.next_in() } != 0 { // Bytes remain in input buffer. Perhaps this is another compressed member in the same archive, or just trailing garbage. // Trailing zero bytes are okay, though, since they are frequently used for padding. let _ = self.reset(); // SAFETY: FFI — state was just re-initialized by reset(). - self.err = unsafe { c::inflate(&raw mut self.state, self.flush) }; + self.err = unsafe { c::inflate(self.state.as_ptr(), self.flush) }; } } pub(crate) fn update_write_result(&self, avail_in: &mut u32, avail_out: &mut u32) { - *avail_in = self.state.avail_in; - *avail_out = self.state.avail_out; + *avail_in = self.state.avail_in(); + *avail_out = self.state.avail_out(); } pub(crate) fn get_error_info(&self) -> Error { match self.err { c::ReturnCode::Ok | c::ReturnCode::BufError => { - if self.state.avail_out != 0 && self.flush == c::FlushValue::Finish { + if self.state.avail_out() != 0 && self.flush == c::FlushValue::Finish { return self.error_for_message(c"unexpected end of file"); } } @@ -571,11 +550,11 @@ impl Context { match self.mode { // SAFETY: FFI — state was initialized as a deflate stream by deflateInit2_. DEFLATE | DEFLATERAW | GZIP => unsafe { - status = c::deflateEnd(&raw mut self.state); + status = c::deflateEnd(self.state.as_ptr()); }, // SAFETY: FFI — state was initialized as an inflate stream by inflateInit2_. INFLATE | INFLATERAW | GUNZIP | UNZIP => unsafe { - status = c::inflateEnd(&raw mut self.state); + status = c::inflateEnd(self.state.as_ptr()); }, NONE => {} BROTLI_ENCODE | BROTLI_DECODE => {} diff --git a/src/runtime/webcore/CompressionStreamCoder.rs b/src/runtime/webcore/CompressionStreamCoder.rs index e368629302d5..bf37f9c1d6d4 100644 --- a/src/runtime/webcore/CompressionStreamCoder.rs +++ b/src/runtime/webcore/CompressionStreamCoder.rs @@ -102,9 +102,9 @@ enum Progress { } enum Backend { - Deflate(Box), + Deflate(zlib::ZStream), Inflate { - state: Box, + state: zlib::ZStream, /// Gzip only: after the first member ends, any further bytes must be /// another gzip member (RFC 1952 §2.2) — the decoder resets and /// continues. Deflate/deflate-raw have no such concatenation, so @@ -159,10 +159,10 @@ impl Drop for CompressionStreamCoder { unsafe { match &mut self.backend { Backend::Deflate(s) => { - zlib::deflateEnd(&raw mut **s); + zlib::deflateEnd(s.as_ptr()); } Backend::Inflate { state, .. } => { - zlib::inflateEnd(&raw mut **state); + zlib::inflateEnd(state.as_ptr()); } Backend::BrotliEncode(p) => brotli::BrotliEncoderDestroyInstance(p.as_ptr()), Backend::BrotliDecode(p) => brotli::BrotliDecoderDestroyInstance(p.as_ptr()), @@ -197,38 +197,23 @@ impl CompressionStreamCoder { ) -> Result, CodecError> { let backend = match (format, decompress) { (Format::Deflate | Format::DeflateRaw | Format::Gzip, false) => { - let mut s = Box::new(bun_core::ffi::zeroed::()); + let mut s = zlib::ZStream::new(); // Spec: "default compression level". Z_DEFAULT_COMPRESSION = -1. - // SAFETY: `s` is a zeroed, #[repr(C)] z_stream; zlibVersion() is - // a static C string. - let rc = unsafe { - zlib::deflateInit2_( - &raw mut *s, - level.unwrap_or(-1), - 8, // Z_DEFLATED - format.window_bits(), - 8, // default mem_level - 0, // Z_DEFAULT_STRATEGY - zlib::zlibVersion().cast(), - core::mem::size_of::() as c_int, - ) - }; + let rc = s.deflate_init( + level.unwrap_or(-1), + 8, // Z_DEFLATED + format.window_bits(), + 8, // default mem_level + 0, // Z_DEFAULT_STRATEGY + ); if rc != zlib::ReturnCode::Ok { return Err(CodecError::Message("failed to initialize deflate")); } Backend::Deflate(s) } (Format::Deflate | Format::DeflateRaw | Format::Gzip, true) => { - let mut s = Box::new(bun_core::ffi::zeroed::()); - // SAFETY: as above. - let rc = unsafe { - zlib::inflateInit2_( - &raw mut *s, - format.window_bits(), - zlib::zlibVersion().cast(), - core::mem::size_of::() as c_int, - ) - }; + let mut s = zlib::ZStream::new(); + let rc = s.inflate_init(format.window_bits()); if rc != zlib::ReturnCode::Ok { return Err(CodecError::Message("failed to initialize inflate")); } @@ -392,20 +377,18 @@ impl CompressionStreamCoder { } else { zlib::FlushValue::NoFlush }; - s.next_in = remaining.as_ptr(); - s.avail_in = take as u32; + s.set_input(remaining.as_ptr(), take as u32); let spare = spare(out, cap)?; - s.next_out = spare.as_mut_ptr().cast(); - s.avail_out = spare.len().min(u32::MAX as usize) as u32; - let before = s.avail_out; + let before = spare.len().min(u32::MAX as usize) as u32; + s.set_output(spare.as_mut_ptr().cast(), before); // SAFETY: `s` was initialized by `deflateInit2_`; next_in/ // avail_in borrow `remaining`, next_out/avail_out borrow // the Vec's spare capacity for this one call. - let rc = unsafe { zlib::deflate(&raw mut **s, flush) }; - let written = (before - s.avail_out) as usize; + let rc = unsafe { zlib::deflate(s.as_ptr(), flush) }; + let written = (before - s.avail_out()) as usize; // SAFETY: deflate wrote exactly `written` bytes. unsafe { out.set_len(out.len() + written) }; - let consumed = take - s.avail_in as usize; + let consumed = take - s.avail_in() as usize; remaining = &remaining[consumed..]; match rc { zlib::ReturnCode::Ok | zlib::ReturnCode::BufError => {} @@ -413,7 +396,7 @@ impl CompressionStreamCoder { zlib::ReturnCode::MemError => return Err(CodecError::OutOfMemory), _ => return Err(CodecError::Message("deflate failed")), } - if s.avail_out != 0 && remaining.is_empty() { + if s.avail_out() != 0 && remaining.is_empty() { return Ok(Progress::Done); } } @@ -429,7 +412,7 @@ impl CompressionStreamCoder { return Err(CodecError::TrailingJunk); } // SAFETY: `s` is an initialized inflate stream. - if unsafe { zlib::inflateReset(&raw mut **s) } != zlib::ReturnCode::Ok { + if unsafe { zlib::inflateReset(s.as_ptr()) } != zlib::ReturnCode::Ok { return Err(CodecError::Message("inflate failed")); } self.ended = false; @@ -452,19 +435,17 @@ impl CompressionStreamCoder { } else { zlib::FlushValue::NoFlush }; - s.next_in = remaining.as_ptr(); - s.avail_in = take as u32; + s.set_input(remaining.as_ptr(), take as u32); let spare = spare(out, cap)?; - s.next_out = spare.as_mut_ptr().cast(); - s.avail_out = spare.len().min(u32::MAX as usize) as u32; - let before = s.avail_out; + let before = spare.len().min(u32::MAX as usize) as u32; + s.set_output(spare.as_mut_ptr().cast(), before); // SAFETY: `s` was initialized by `inflateInit2_`; buffers // as in the deflate arm. - let rc = unsafe { zlib::inflate(&raw mut **s, flush) }; - let written = (before - s.avail_out) as usize; + let rc = unsafe { zlib::inflate(s.as_ptr(), flush) }; + let written = (before - s.avail_out()) as usize; // SAFETY: inflate wrote exactly `written` bytes. unsafe { out.set_len(out.len() + written) }; - let consumed = take - s.avail_in as usize; + let consumed = take - s.avail_in() as usize; remaining = &remaining[consumed..]; match rc { zlib::ReturnCode::Ok => {} @@ -482,7 +463,7 @@ impl CompressionStreamCoder { return Err(CodecError::TrailingJunk); } // SAFETY: `s` is an initialized inflate stream. - if unsafe { zlib::inflateReset(&raw mut **s) } != zlib::ReturnCode::Ok { + if unsafe { zlib::inflateReset(s.as_ptr()) } != zlib::ReturnCode::Ok { return Err(CodecError::Message("inflate failed")); } self.ended = false; @@ -494,7 +475,7 @@ impl CompressionStreamCoder { zlib::ReturnCode::MemError => return Err(CodecError::OutOfMemory), _ => return Err(CodecError::Message("inflate failed")), } - if s.avail_out != 0 && remaining.is_empty() { + if s.avail_out() != 0 && remaining.is_empty() { if finish && !self.ended { return Err(CodecError::Message("unexpected end of file")); } diff --git a/src/zlib/lib.rs b/src/zlib/lib.rs index eb2465dd5767..b9aee44c945b 100644 --- a/src/zlib/lib.rs +++ b/src/zlib/lib.rs @@ -1,7 +1,6 @@ // @link "deps/zlib/libz.a" -use core::ffi::{c_char, c_int, c_uint, c_void}; -use core::mem::size_of; +use core::ffi::{c_char, c_int, c_uint}; use bun_collections::VecExt as _; @@ -27,7 +26,7 @@ pub use bun_zlib_sys::shared::{Bytef, uInt, uLong, uLongf}; // typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size)); // typedef void (*free_func) OF((voidpf opaque, voidpf address)); -pub use bun_zlib_sys::shared::{z_stream, z_streamp}; +use bun_zlib_sys::shared::z_streamp; // typedef struct z_stream_s { // z_const Bytef *next_in; /* next input byte */ @@ -53,7 +52,10 @@ pub use bun_zlib_sys::shared::{z_stream, z_streamp}; pub use bun_zlib_sys::shared::{FlushValue, ReturnCode}; -use bun_zlib_sys::shared::{DataType, zStream_struct}; +use bun_zlib_sys::shared::zStream_struct; + +mod stream; +pub use stream::ZStream; // ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); @@ -146,22 +148,10 @@ pub enum ZlibError { bun_core::impl_tag_error!(ZlibError); -pub(crate) use bun_alloc::c_thunks::{ - mi_free_opaque as zlib_mi_free, mi_malloc_items as zlib_mi_malloc, -}; - -// zlib `alloc_func`/`free_func` thunks tagged with the "zlib" heap-breakdown zone. -#[allow(non_snake_case)] -mod ZlibAllocator { - bun_alloc::c_thunks_for_zone!("zlib"); - pub(crate) use malloc_items as alloc; -} - pub struct ZlibReaderArrayList<'a> { // We operate directly through `list_ptr` (a `&'a mut Vec`). pub(crate) list_ptr: &'a mut Vec, - pub(crate) zlib: zStream_struct, - // allocator field dropped (global mimalloc) + pub(crate) zlib: ZStream, pub(crate) state: ZlibReaderArrayListState, /// Decompression-bomb guard: `read_all` errors instead of growing the /// output past this many bytes. Defaults to unbounded. @@ -179,12 +169,12 @@ impl<'a> ZlibReaderArrayList<'a> { // always free with `inflateEnd` if self.state != ZlibReaderArrayListState::End { // SAFETY: zlib was initialized via inflateInit2_; safe to end. - unsafe { inflateEnd(&raw mut self.zlib) }; + unsafe { inflateEnd(self.zlib.as_ptr()) }; self.state = ZlibReaderArrayListState::End; } } - pub fn init(input: &'a [u8], list: &'a mut Vec) -> Result, ZlibError> { + pub fn init(input: &'a [u8], list: &'a mut Vec) -> Result { let options = Options { window_bits: 15 + 32, ..Default::default() @@ -197,79 +187,32 @@ impl<'a> ZlibReaderArrayList<'a> { input: &'a [u8], list: &'a mut Vec, options: Options, - ) -> Result, ZlibError> { - Self::init_with_options_and_list_allocator(input, list, options) - } - - // list_allocator/allocator params dropped (global mimalloc). - pub(crate) fn init_with_options_and_list_allocator( - input: &'a [u8], - list: &'a mut Vec, - options: Options, - ) -> Result, ZlibError> { - let mut zlib_reader = Box::new(Self { + ) -> Result { + let mut zlib_reader = Self { list_ptr: list, - zlib: bun_core::ffi::zeroed(), + zlib: ZStream::new(), state: ZlibReaderArrayListState::Uninitialized, max_output_size: usize::MAX, - }); + }; + zlib_reader + .zlib + .set_input(input.as_ptr(), input.len() as uInt); let list_len = zlib_reader.list_ptr.len(); - zlib_reader.zlib = zStream_struct { - next_in: input.as_ptr(), - avail_in: input.len() as uInt, - total_in: input.len() as _, - - next_out: zlib_reader.list_ptr.as_mut_ptr(), - avail_out: list_len as uInt, - total_out: list_len as _, - - err_msg: core::ptr::null(), - alloc_func: Some(ZlibAllocator::alloc), - free_func: Some(ZlibAllocator::free), + zlib_reader + .zlib + .set_output(zlib_reader.list_ptr.as_mut_ptr(), list_len as uInt); - internal_state: core::ptr::null_mut(), - user_data: (&raw mut *zlib_reader).cast::(), - - data_type: DataType::Unknown as c_int, - adler: 0, - reserved: 0, - }; - - // SAFETY: zlib_reader.zlib is fully initialized; version/size match the linked zlib. - match unsafe { - inflateInit2_( - &raw mut zlib_reader.zlib, - options.window_bits, - zlibVersion().cast::(), - size_of::() as c_int, - ) - } { + match zlib_reader.zlib.inflate_init(options.window_bits) { ReturnCode::Ok => Ok(zlib_reader), - ReturnCode::MemError => { - drop(zlib_reader); - Err(ZlibError::OutOfMemory) - } - ReturnCode::StreamError => { - drop(zlib_reader); - Err(ZlibError::InvalidArgument) - } - ReturnCode::VersionError => { - drop(zlib_reader); - Err(ZlibError::InvalidArgument) - } + ReturnCode::MemError => Err(ZlibError::OutOfMemory), + ReturnCode::StreamError | ReturnCode::VersionError => Err(ZlibError::InvalidArgument), _ => unreachable!(), } } pub fn error_message(&self) -> Option<&[u8]> { - if !self.zlib.err_msg.is_null() { - // SAFETY: err_msg is a NUL-terminated C string from zlib. - return Some( - unsafe { bun_core::ffi::cstr(self.zlib.err_msg.cast::()) }.to_bytes(), - ); - } - None + self.zlib.error_message() } pub fn read_all(&mut self, is_done: bool) -> Result<(), ZlibError> { @@ -306,8 +249,8 @@ impl<'a> ZlibReaderArrayList<'a> { // or no more space in the output buffer (see below about the // flush parameter). - if self.zlib.avail_out == 0 { - let produced = self.zlib.total_out as usize; + if self.zlib.avail_out() == 0 { + let produced = self.zlib.total_out() as usize; let remaining_budget = self.max_output_size.saturating_sub(produced); if remaining_budget == 0 { self.state = ZlibReaderArrayListState::Error; @@ -323,14 +266,14 @@ impl<'a> ZlibReaderArrayList<'a> { } // SAFETY: zlib writes the tail; len is truncated to `total_out` before any read. let (next_out, avail_out) = unsafe { self.list_ptr.reserve_expand_tail(0) }; - self.zlib.next_out = next_out; // Clamp so a single inflate call cannot write past `max_output_size`. - self.zlib.avail_out = avail_out.min(remaining_budget) as uInt; + self.zlib + .set_output(next_out, avail_out.min(remaining_budget) as uInt); } // Try to inflate even if avail_in is 0, as this could be a valid empty gzip stream // SAFETY: self.zlib was initialized via inflateInit2_. - let rc = unsafe { inflate(&raw mut self.zlib, FlushValue::NoFlush) }; + let rc = unsafe { inflate(self.zlib.as_ptr(), FlushValue::NoFlush) }; self.state = ZlibReaderArrayListState::Inflating; match rc { @@ -344,7 +287,7 @@ impl<'a> ZlibReaderArrayList<'a> { } ReturnCode::BufError => { // BufError with avail_in == 0 means we need more input data - if self.zlib.avail_in == 0 { + if self.zlib.avail_in() == 0 { if is_done { // Stream is truncated - we're at EOF but decoder needs more data self.state = ZlibReaderArrayListState::Error; @@ -370,7 +313,7 @@ impl<'a> ZlibReaderArrayList<'a> { Ok(()) })(); - let total_out = self.zlib.total_out as usize; + let total_out = self.zlib.total_out() as usize; if self.list_ptr.len() > total_out { self.list_ptr.truncate(total_out); } else if total_out < self.list_ptr.capacity() { @@ -656,8 +599,7 @@ impl NodeMode { pub struct ZlibCompressorArrayList<'a> { // We operate directly through `list_ptr` (a `&'a mut Vec`). pub(crate) list_ptr: &'a mut Vec, - pub(crate) zlib: zStream_struct, - // allocator field dropped (global mimalloc) + pub(crate) zlib: ZStream, pub(crate) state: ZlibCompressorArrayListState, } @@ -665,7 +607,7 @@ impl<'a> ZlibCompressorArrayList<'a> { pub(crate) fn end(&mut self) { if self.state != ZlibCompressorArrayListState::End { // SAFETY: zlib was initialized via deflateInit2_; safe to end. - unsafe { deflateEnd(&raw mut self.zlib) }; + unsafe { deflateEnd(self.zlib.as_ptr()) }; self.state = ZlibCompressorArrayListState::End; } } @@ -674,66 +616,33 @@ impl<'a> ZlibCompressorArrayList<'a> { input: &'a [u8], list: &'a mut Vec, options: Options, - ) -> Result, ZlibError> { - Self::init_with_list_allocator(input, list, options) - } - - // allocator/list_allocator params dropped (global mimalloc). - pub(crate) fn init_with_list_allocator( - input: &'a [u8], - list: &'a mut Vec, - options: Options, - ) -> Result, ZlibError> { - let mut zlib_reader = Box::new(Self { + ) -> Result { + let mut zlib_reader = Self { list_ptr: list, - zlib: bun_core::ffi::zeroed(), + zlib: ZStream::new(), state: ZlibCompressorArrayListState::Uninitialized, - }); - - let list_len = zlib_reader.list_ptr.len(); - zlib_reader.zlib = zStream_struct { - next_in: input.as_ptr(), - avail_in: input.len() as uInt, - total_in: input.len() as _, - - next_out: zlib_reader.list_ptr.as_mut_ptr(), - avail_out: list_len as uInt, - total_out: list_len as _, - - err_msg: core::ptr::null(), - alloc_func: Some(zlib_mi_malloc), - free_func: Some(zlib_mi_free), - - internal_state: core::ptr::null_mut(), - user_data: (&raw mut *zlib_reader).cast::(), - - data_type: DataType::Unknown as c_int, - adler: 0, - reserved: 0, }; - // SAFETY: zlib_reader.zlib is fully initialized; version/size match the linked zlib. - match unsafe { - deflateInit2_( - &raw mut zlib_reader.zlib, - options.level, - options.method, - if !options.gzip { - -options.window_bits - } else { - options.window_bits + 16 - }, - options.mem_level, - options.strategy, - zlibVersion().cast::(), - size_of::() as c_int, - ) - } { + zlib_reader + .zlib + .set_input(input.as_ptr(), input.len() as uInt); + + match zlib_reader.zlib.deflate_init( + options.level, + options.method, + if !options.gzip { + -options.window_bits + } else { + options.window_bits + 16 + }, + options.mem_level, + options.strategy, + ) { ReturnCode::Ok => { // SAFETY: zlib initialized; deflateBound returns upper bound on output. let bound = unsafe { deflateBound( - &raw mut zlib_reader.zlib, + zlib_reader.zlib.as_ptr(), uLong::try_from(input.len()).expect("int cast"), ) }; @@ -742,36 +651,19 @@ impl<'a> ZlibCompressorArrayList<'a> { .try_reserve_exact(bound as usize) .is_err() { - drop(zlib_reader); return Err(ZlibError::OutOfMemory); } Ok(zlib_reader) } - ReturnCode::MemError => { - drop(zlib_reader); - Err(ZlibError::OutOfMemory) - } - ReturnCode::StreamError => { - drop(zlib_reader); - Err(ZlibError::InvalidArgument) - } - ReturnCode::VersionError => { - drop(zlib_reader); - Err(ZlibError::InvalidArgument) - } + ReturnCode::MemError => Err(ZlibError::OutOfMemory), + ReturnCode::StreamError | ReturnCode::VersionError => Err(ZlibError::InvalidArgument), _ => unreachable!(), } } pub fn error_message(&self) -> Option<&[u8]> { - if !self.zlib.err_msg.is_null() { - // SAFETY: err_msg is a NUL-terminated C string from zlib. - return Some( - unsafe { bun_core::ffi::cstr(self.zlib.err_msg.cast::()) }.to_bytes(), - ); - } - None + self.zlib.error_message() } pub fn read_all(&mut self) -> Result<(), ZlibError> { @@ -787,15 +679,15 @@ impl<'a> ZlibCompressorArrayList<'a> { } let spare = self.list_ptr.spare_capacity_mut(); let room = spare.len().min(u32::MAX as usize); - self.zlib.next_out = spare.as_mut_ptr().cast::(); - self.zlib.avail_out = room as uInt; + self.zlib + .set_output(spare.as_mut_ptr().cast::(), room as uInt); // SAFETY: self.zlib was initialized via deflateInit2_; spare is valid for `room` bytes. - let rc = unsafe { deflate(&raw mut self.zlib, FlushValue::Finish) }; + let rc = unsafe { deflate(self.zlib.as_ptr(), FlushValue::Finish) }; self.state = ZlibCompressorArrayListState::Inflating; // `total_out` is 32 bits on Windows. - let produced = room - self.zlib.avail_out as usize; + let produced = room - self.zlib.avail_out() as usize; // SAFETY: zlib has initialized `produced` bytes at the start of spare. unsafe { bun_core::vec::commit_spare(self.list_ptr, produced) }; @@ -838,15 +730,11 @@ impl<'a> Drop for ZlibCompressorArrayList<'a> { // Unlike `Zlib*ArrayList<'a>` above, these hold only the z_stream and its // C-side `internal_state`. Input and output are passed per call, so callers // can hold a decoder across multiple chunks without lifetime erasure. -// -// The `z_stream` is boxed because zlib-ng's `inflate_state`/`deflate_state` -// store a back-pointer to it (checked in `inflateStateCheck` / -// `deflateStateCheck`), so it must not move after init. // ────────────────────────────────────────────────────────────────────────── /// RAII deflate (compression) stream. `deflateEnd` on drop. pub struct DeflateEncoder { - strm: Box, + strm: ZStream, } impl DeflateEncoder { @@ -857,22 +745,13 @@ impl DeflateEncoder { strategy: c_int, ) -> Result { let mut this = Self { - strm: Box::new(new_zstream()), + strm: ZStream::new(), }; - // SAFETY: strm is fully initialized; version/size match the linked zlib. - let rc = unsafe { - deflateInit2_( - &raw mut *this.strm, - level, - 8, // Z_DEFLATED - window_bits, - mem_level, - strategy, - zlibVersion().cast::(), - size_of::() as c_int, - ) - }; - match rc { + // 8 = Z_DEFLATED + match this + .strm + .deflate_init(level, 8, window_bits, mem_level, strategy) + { ReturnCode::Ok => Ok(this), ReturnCode::MemError => Err(ZlibError::OutOfMemory), _ => Err(ZlibError::InvalidArgument), @@ -881,12 +760,12 @@ impl DeflateEncoder { #[inline] pub fn avail_out(&self) -> u32 { - self.strm.avail_out as u32 + self.strm.avail_out() as u32 } pub fn reset(&mut self) -> ReturnCode { // SAFETY: strm was initialized via deflateInit2_. - unsafe { deflateReset(&raw mut *self.strm) } + unsafe { deflateReset(self.strm.as_ptr()) } } /// One `deflate()` call writing into `out`'s spare capacity. @@ -921,13 +800,13 @@ impl Drop for DeflateEncoder { // SAFETY: strm was initialized via deflateInit2_ (or init failed and // `internal_state` is null — `deflateEnd` on a null state is a // defined no-op returning Z_STREAM_ERROR). Freed exactly once here. - unsafe { deflateEnd(&raw mut *self.strm) }; + unsafe { deflateEnd(self.strm.as_ptr()) }; } } /// RAII inflate (decompression) stream. `inflateEnd` on drop. pub struct InflateDecoder { - strm: Box, + strm: ZStream, pub(crate) state: State, /// Decompression-bomb guard for [`decompress`](Self::decompress). pub(crate) max_output_size: usize, @@ -941,22 +820,13 @@ pub struct InflateDecoder { impl InflateDecoder { pub fn new(window_bits: c_int) -> Result { let mut this = Self { - strm: Box::new(new_zstream()), + strm: ZStream::new(), state: State::Uninitialized, max_output_size: usize::MAX, // `MAX_WBITS | 16` selects gzip-only decode (zlib manual). multi_member: (16..32).contains(&window_bits), }; - // SAFETY: strm is fully initialized; version/size match the linked zlib. - let rc = unsafe { - inflateInit2_( - &raw mut *this.strm, - window_bits, - zlibVersion().cast::(), - size_of::() as c_int, - ) - }; - match rc { + match this.strm.inflate_init(window_bits) { ReturnCode::Ok => Ok(this), ReturnCode::MemError => Err(ZlibError::OutOfMemory), _ => Err(ZlibError::InvalidArgument), @@ -965,12 +835,12 @@ impl InflateDecoder { #[inline] pub fn avail_out(&self) -> u32 { - self.strm.avail_out as u32 + self.strm.avail_out() as u32 } pub fn reset(&mut self) -> ReturnCode { // SAFETY: strm was initialized via inflateInit2_. - let rc = unsafe { inflateReset(&raw mut *self.strm) }; + let rc = unsafe { inflateReset(self.strm.as_ptr()) }; if rc == ReturnCode::Ok { self.state = State::Uninitialized; } @@ -1081,7 +951,7 @@ impl InflateDecoder { return Err(ZlibError::OutOfMemory); } ReturnCode::BufError => { - if input.is_empty() && self.strm.avail_in == 0 { + if input.is_empty() && self.strm.avail_in() == 0 { if is_done { self.state = State::Error; return Err(ZlibError::ZlibError); @@ -1114,32 +984,13 @@ impl Drop for InflateDecoder { // defined no-op returning Z_STREAM_ERROR). Freed exactly once here. // `state == End` only signals stream completion, not that the C // state was already freed. - unsafe { inflateEnd(&raw mut *self.strm) }; - } -} - -fn new_zstream() -> zStream_struct { - zStream_struct { - next_in: core::ptr::null(), - avail_in: 0, - total_in: 0, - next_out: core::ptr::null_mut(), - avail_out: 0, - total_out: 0, - err_msg: core::ptr::null(), - alloc_func: Some(ZlibAllocator::alloc), - free_func: Some(ZlibAllocator::free), - internal_state: core::ptr::null_mut(), - user_data: core::ptr::null_mut(), - data_type: DataType::Unknown as c_int, - adler: 0, - reserved: 0, + unsafe { inflateEnd(self.strm.as_ptr()) }; } } /// Shared body of [`DeflateEncoder::step`] / [`InflateDecoder::step`]. fn step( - strm: &mut zStream_struct, + strm: &mut ZStream, input: &[u8], out: &mut Vec, reserve: usize, @@ -1152,22 +1003,20 @@ fn step( } let in_len = input.len().min(u32::MAX as usize); - strm.next_in = input.as_ptr(); - strm.avail_in = in_len as uInt; + strm.set_input(input.as_ptr(), in_len as uInt); let spare = out.spare_capacity_mut(); let out_len = spare.len().min(limit).min(u32::MAX as usize); - strm.next_out = spare.as_mut_ptr().cast::(); - strm.avail_out = out_len as uInt; + strm.set_output(spare.as_mut_ptr().cast::(), out_len as uInt); // SAFETY: strm was initialized by deflateInit2_/inflateInit2_; input is // valid for `in_len` bytes; spare is valid write-only storage for // `out_len` bytes. zlib writes at most `out_len - avail_out` bytes. - let rc = unsafe { op(&raw mut *strm, flush) }; + let rc = unsafe { op(strm.as_ptr(), flush) }; - let produced = out_len - strm.avail_out as usize; + let produced = out_len - strm.avail_out() as usize; // SAFETY: zlib has initialized `produced` bytes at the start of spare. unsafe { bun_core::vec::commit_spare(out, produced) }; - let consumed = in_len - strm.avail_in as usize; + let consumed = in_len - strm.avail_in() as usize; (consumed, rc) } diff --git a/src/zlib/stream.rs b/src/zlib/stream.rs new file mode 100644 index 000000000000..e95753574f67 --- /dev/null +++ b/src/zlib/stream.rs @@ -0,0 +1,161 @@ +//! Owned `z_stream` reached only through a raw pointer: zlib-ng keeps its own +//! pointer to the stream from init on, so a `&mut` to it across a zlib call +//! is an aliasing violation (oven-sh/bun#46598). + +use core::ffi::{c_char, c_int}; +use core::mem::size_of; +use core::ptr::NonNull; + +use bun_zlib_sys::shared::{DataType, ReturnCode, uInt, uLong, zStream_struct}; + +#[allow(non_snake_case)] +mod ZlibAllocator { + bun_alloc::c_thunks_for_zone!("zlib"); + pub(crate) use malloc_items as alloc; +} + +/// Heap-pinned `z_stream`. The owner ends the zlib state before this drops. +pub struct ZStream { + ptr: NonNull, +} + +impl Default for ZStream { + fn default() -> Self { + Self::new() + } +} + +impl ZStream { + /// A zeroed stream with Bun's zlib allocator installed. + pub fn new() -> Self { + let ptr = bun_core::heap::alloc_nn(zStream_struct { + next_in: core::ptr::null(), + avail_in: 0, + total_in: 0, + next_out: core::ptr::null_mut(), + avail_out: 0, + total_out: 0, + err_msg: core::ptr::null(), + alloc_func: Some(ZlibAllocator::alloc), + free_func: Some(ZlibAllocator::free), + internal_state: core::ptr::null_mut(), + user_data: core::ptr::null_mut(), + data_type: DataType::Unknown as c_int, + adler: 0, + reserved: 0, + }); + Self { ptr } + } + + /// The pointer to hand to zlib. + #[inline] + pub fn as_ptr(&self) -> *mut zStream_struct { + self.ptr.as_ptr() + } + + /// `deflateInit2_` against the linked zlib's version and struct size. + pub fn deflate_init( + &mut self, + level: c_int, + method: c_int, + window_bits: c_int, + mem_level: c_int, + strategy: c_int, + ) -> ReturnCode { + // SAFETY: `ptr` is a live z_stream; version/size match the linked zlib. + unsafe { + crate::deflateInit2_( + self.as_ptr(), + level, + method, + window_bits, + mem_level, + strategy, + crate::zlibVersion().cast::(), + size_of::() as c_int, + ) + } + } + + /// `inflateInit2_` against the linked zlib's version and struct size. + pub fn inflate_init(&mut self, window_bits: c_int) -> ReturnCode { + // SAFETY: `ptr` is a live z_stream; version/size match the linked zlib. + unsafe { + crate::inflateInit2_( + self.as_ptr(), + window_bits, + crate::zlibVersion().cast::(), + size_of::() as c_int, + ) + } + } + + #[inline] + pub fn next_in(&self) -> *const u8 { + // SAFETY: `ptr` is a live allocation owned by `self`, read through + // the raw pointer only. + unsafe { (*self.as_ptr()).next_in } + } + + #[inline] + pub fn avail_in(&self) -> uInt { + // SAFETY: see `next_in`. + unsafe { (*self.as_ptr()).avail_in } + } + + #[inline] + pub fn avail_out(&self) -> uInt { + // SAFETY: see `next_in`. + unsafe { (*self.as_ptr()).avail_out } + } + + #[inline] + pub fn total_out(&self) -> uLong { + // SAFETY: see `next_in`. + unsafe { (*self.as_ptr()).total_out } + } + + #[inline] + pub fn err_msg(&self) -> *const c_char { + // SAFETY: see `next_in`. + unsafe { (*self.as_ptr()).err_msg } + } + + /// `msg` as bytes, if zlib set one. + pub fn error_message(&self) -> Option<&[u8]> { + let msg = self.err_msg(); + if msg.is_null() { + return None; + } + // SAFETY: zlib sets `msg` to a NUL-terminated static C string. + Some(unsafe { bun_core::ffi::cstr(msg) }.to_bytes()) + } + + /// Point `next_in`/`avail_in` at the next input chunk. + #[inline] + pub fn set_input(&mut self, next_in: *const u8, avail_in: uInt) { + // SAFETY: see `next_in`. + unsafe { + (*self.as_ptr()).next_in = next_in; + (*self.as_ptr()).avail_in = avail_in; + } + } + + /// Point `next_out`/`avail_out` at the next output window. + #[inline] + pub fn set_output(&mut self, next_out: *mut u8, avail_out: uInt) { + // SAFETY: see `set_input`. + unsafe { + (*self.as_ptr()).next_out = next_out; + (*self.as_ptr()).avail_out = avail_out; + } + } +} + +impl Drop for ZStream { + fn drop(&mut self) { + // SAFETY: `ptr` came from `heap::alloc_nn` in `new` and is freed once, + // here, after the owner ended the zlib state. + unsafe { bun_core::heap::destroy(self.ptr.as_ptr()) }; + } +}