From 2665f457e2de70c51ab150ec31a9761e489c1a1c Mon Sep 17 00:00:00 2001 From: Geoffry Song Date: Wed, 19 Aug 2026 17:15:14 -0700 Subject: [PATCH] Do not use &mut [u8] to point to uninitialized string_buffer --- src/charutils.rs | 27 ++++++++++++++++----------- src/impls/avx2/deser.rs | 10 +++++++--- src/impls/native/deser.rs | 13 ++++++++++--- src/impls/neon/deser.rs | 14 ++++++++++---- src/impls/portable/deser.rs | 9 ++++++--- src/impls/simd128/deser.rs | 9 ++++++--- src/impls/sse42/deser.rs | 10 +++++++--- src/lib.rs | 23 ++++++++++------------- src/stage2.rs | 8 +++++--- src/stringparse.rs | 3 ++- 10 files changed, 79 insertions(+), 47 deletions(-) diff --git a/src/charutils.rs b/src/charutils.rs index d5f605ec..3a11f967 100644 --- a/src/charutils.rs +++ b/src/charutils.rs @@ -1,3 +1,5 @@ +use std::mem::MaybeUninit; + use crate::safer_unchecked::GetSaferUnchecked; const STRUCTURAL_OR_WHITESPACE_NEGATED: [u32; 256] = [ @@ -83,30 +85,33 @@ pub fn hex_to_u32_nocheck(src: &[u8]) -> u32 { // #[cfg_attr(not(feature = "no-inline"), inline)] #[allow(clippy::cast_possible_truncation)] -pub fn codepoint_to_utf8(cp: u32, c: &mut [u8]) -> usize { +pub fn codepoint_to_utf8(cp: u32, c: &mut [MaybeUninit]) -> usize { unsafe { if cp <= 0x7F { - *c.get_kinda_unchecked_mut(0) = cp as u8; + c.get_kinda_unchecked_mut(0).write(cp as u8); 1 // ascii } else if cp <= 0x7FF { - *c.get_kinda_unchecked_mut(0) = ((cp >> 6) + 192) as u8; - *c.get_kinda_unchecked_mut(1) = ((cp & 63) + 128) as u8; + c.get_kinda_unchecked_mut(0).write(((cp >> 6) + 192) as u8); + c.get_kinda_unchecked_mut(1).write(((cp & 63) + 128) as u8); 2 // universal plane // Surrogates are treated elsewhere... //} //else if (0xd800 <= cp && cp <= 0xdfff) { // return 0; // surrogates // could put assert here } else if cp <= 0xFFFF { - *c.get_kinda_unchecked_mut(0) = ((cp >> 12) + 224) as u8; - *c.get_kinda_unchecked_mut(1) = (((cp >> 6) & 63) + 128) as u8; - *c.get_kinda_unchecked_mut(2) = ((cp & 63) + 128) as u8; + c.get_kinda_unchecked_mut(0).write(((cp >> 12) + 224) as u8); + c.get_kinda_unchecked_mut(1) + .write((((cp >> 6) & 63) + 128) as u8); + c.get_kinda_unchecked_mut(2).write(((cp & 63) + 128) as u8); 3 } else if cp <= 0x0010_FFFF { // if you know you have a valid code point, this is not needed - *c.get_kinda_unchecked_mut(0) = ((cp >> 18) + 240) as u8; - *c.get_kinda_unchecked_mut(1) = (((cp >> 12) & 63) + 128) as u8; - *c.get_kinda_unchecked_mut(2) = (((cp >> 6) & 63) + 128) as u8; - *c.get_kinda_unchecked_mut(3) = ((cp & 63) + 128) as u8; + c.get_kinda_unchecked_mut(0).write(((cp >> 18) + 240) as u8); + c.get_kinda_unchecked_mut(1) + .write((((cp >> 12) & 63) + 128) as u8); + c.get_kinda_unchecked_mut(2) + .write((((cp >> 6) & 63) + 128) as u8); + c.get_kinda_unchecked_mut(3).write(((cp & 63) + 128) as u8); 4 } else { // will return 0 when the code point was too large. diff --git a/src/impls/avx2/deser.rs b/src/impls/avx2/deser.rs index cf978d0f..ce22abdd 100644 --- a/src/impls/avx2/deser.rs +++ b/src/impls/avx2/deser.rs @@ -4,6 +4,8 @@ use std::arch::x86 as arch; #[cfg(target_arch = "x86_64")] use std::arch::x86_64 as arch; +use std::mem::MaybeUninit; + use arch::{ __m256i, _mm256_cmpeq_epi8, _mm256_loadu_si256, _mm256_movemask_epi8, _mm256_set1_epi8, _mm256_storeu_si256, @@ -27,7 +29,7 @@ use crate::{ pub(crate) unsafe fn parse_str<'invoke, 'de>( input: SillyWrapper<'de>, data: &'invoke [u8], - buffer: &'invoke mut [u8], + buffer: &'invoke mut [MaybeUninit], mut idx: usize, ) -> Result<&'de str> { unsafe { @@ -129,7 +131,7 @@ pub(crate) unsafe fn parse_str<'invoke, 'de>( dst_i += quote_dist as usize; input .add(idx + len) - .copy_from_nonoverlapping(buffer.as_ptr(), dst_i); + .copy_from_nonoverlapping(buffer.as_ptr().cast::(), dst_i); let v = std::str::from_utf8_unchecked(std::slice::from_raw_parts( input.add(idx), len + dst_i, @@ -174,7 +176,9 @@ pub(crate) unsafe fn parse_str<'invoke, 'de>( InvalidEscape, )); } - *buffer.get_kinda_unchecked_mut(dst_i + bs_dist as usize) = escape_result; + buffer + .get_kinda_unchecked_mut(dst_i + bs_dist as usize) + .write(escape_result); src_i += bs_dist as usize + 2; dst_i += bs_dist as usize + 1; } diff --git a/src/impls/native/deser.rs b/src/impls/native/deser.rs index 1bfd47e3..af69349f 100644 --- a/src/impls/native/deser.rs +++ b/src/impls/native/deser.rs @@ -1,3 +1,5 @@ +use std::mem::MaybeUninit; + use crate::{ Deserializer, ErrorType, Result, SillyWrapper, safer_unchecked::GetSaferUnchecked, @@ -8,7 +10,7 @@ use crate::{ pub(crate) unsafe fn parse_str<'invoke, 'de>( input: SillyWrapper<'de>, data: &'invoke [u8], - _buffer: &'invoke mut [u8], + _buffer: &'invoke mut [MaybeUninit], idx: usize, ) -> Result<&'de str> { use ErrorType::{InvalidEscape, InvalidUnicodeCodepoint}; @@ -118,10 +120,15 @@ mod test { let mut input = input.to_vec(); let mut input2 = input.clone(); input2.append(vec![0; SIMDJSON_PADDING * 2].as_mut()); - let mut buffer = vec![0; 1024]; + let mut buffer = Vec::with_capacity(1024); let r = unsafe { - super::parse_str(input.as_mut_ptr().into(), &input2, buffer.as_mut_slice(), 0)? + super::parse_str( + input.as_mut_ptr().into(), + &input2, + buffer.spare_capacity_mut(), + 0, + )? }; Ok(String::from(r)) } diff --git a/src/impls/neon/deser.rs b/src/impls/neon/deser.rs index e1932ded..af07b054 100644 --- a/src/impls/neon/deser.rs +++ b/src/impls/neon/deser.rs @@ -10,6 +10,7 @@ use std::arch::aarch64::{ uint8x16_t, vandq_u8, vceqq_u8, vgetq_lane_u32, vld1q_u8, vmovq_n_u8, vpaddq_u8, vreinterpretq_u32_u8, }; +use std::mem::MaybeUninit; #[cfg_attr(not(feature = "no-inline"), inline)] fn find_bs_bits_and_quote_bits(v0: uint8x16_t, v1: uint8x16_t) -> (u32, u32) { @@ -45,7 +46,7 @@ fn find_bs_bits_and_quote_bits(v0: uint8x16_t, v1: uint8x16_t) -> (u32, u32) { pub(crate) fn parse_str<'invoke, 'de>( input: SillyWrapper<'de>, data: &'invoke [u8], - buffer: &'invoke mut [u8], + buffer: &'invoke mut [MaybeUninit], mut idx: usize, ) -> Result<&'de str> { use ErrorType::{InvalidEscape, InvalidUnicodeCodepoint}; @@ -121,9 +122,12 @@ pub(crate) fn parse_str<'invoke, 'de>( }; unsafe { + // TODO: this should use `write_copy_of_slice` on Rust 1.93+ buffer .get_kinda_unchecked_mut(dst_i..dst_i + 32) - .copy_from_slice(src.get_kinda_unchecked(src_i..src_i + 32)); + .as_mut_ptr() + .cast::() + .copy_from_nonoverlapping(src.get_kinda_unchecked(src_i..src_i + 32).as_ptr(), 32); } // store to dest unconditionally - we can overwrite the bits we don't like @@ -147,7 +151,7 @@ pub(crate) fn parse_str<'invoke, 'de>( unsafe { input .add(idx + len) - .copy_from_nonoverlapping(buffer.as_ptr(), dst_i); + .copy_from_nonoverlapping(buffer.as_ptr().cast::(), dst_i); let v = std::str::from_utf8_unchecked(std::slice::from_raw_parts( input.add(idx), len + dst_i, @@ -196,7 +200,9 @@ pub(crate) fn parse_str<'invoke, 'de>( )); } unsafe { - *buffer.get_kinda_unchecked_mut(dst_i + bs_dist as usize) = escape_result; + buffer + .get_kinda_unchecked_mut(dst_i + bs_dist as usize) + .write(escape_result); } src_i += bs_dist as usize + 2; dst_i += bs_dist as usize + 1; diff --git a/src/impls/portable/deser.rs b/src/impls/portable/deser.rs index 0323b31f..d15d7809 100644 --- a/src/impls/portable/deser.rs +++ b/src/impls/portable/deser.rs @@ -1,3 +1,4 @@ +use std::mem::MaybeUninit; use std::simd::{SimdPartialEq, ToBitMask, u8x32}; use crate::{ @@ -10,7 +11,7 @@ use crate::{ pub(crate) unsafe fn parse_str<'invoke, 'de>( input: SillyWrapper<'de>, data: &'invoke [u8], - buffer: &'invoke mut [u8], + buffer: &'invoke mut [MaybeUninit], mut idx: usize, ) -> Result<&'de str> { let input = input.input; @@ -102,7 +103,7 @@ pub(crate) unsafe fn parse_str<'invoke, 'de>( dst_i += quote_dist as usize; input .add(idx + len) - .copy_from_nonoverlapping(buffer.as_ptr(), dst_i); + .copy_from_nonoverlapping(buffer.as_ptr().cast::(), dst_i); let v = std::str::from_utf8_unchecked(std::slice::from_raw_parts( input.add(idx), len + dst_i, @@ -152,7 +153,9 @@ pub(crate) unsafe fn parse_str<'invoke, 'de>( InvalidEscape, )); } - *buffer.get_kinda_unchecked_mut(dst_i + bs_dist as usize) = escape_result; + buffer + .get_kinda_unchecked_mut(dst_i + bs_dist as usize) + .write(escape_result); src_i += bs_dist as usize + 2; dst_i += bs_dist as usize + 1; } diff --git a/src/impls/simd128/deser.rs b/src/impls/simd128/deser.rs index cb9e3023..db73a30d 100644 --- a/src/impls/simd128/deser.rs +++ b/src/impls/simd128/deser.rs @@ -1,4 +1,5 @@ use std::arch::wasm32::{u8x16_bitmask, u8x16_eq, u8x16_splat, v128, v128_load, v128_store}; +use std::mem::MaybeUninit; use crate::{ Deserializer, Result, SillyWrapper, @@ -17,7 +18,7 @@ use crate::{ pub(crate) fn parse_str<'invoke, 'de>( input: SillyWrapper<'de>, data: &'invoke [u8], - buffer: &'invoke mut [u8], + buffer: &'invoke mut [MaybeUninit], mut idx: usize, ) -> Result<&'de str> { use ErrorType::{InvalidEscape, InvalidUnicodeCodepoint}; @@ -115,7 +116,7 @@ pub(crate) fn parse_str<'invoke, 'de>( unsafe { input .add(idx + len) - .copy_from_nonoverlapping(buffer.as_ptr(), dst_i); + .copy_from_nonoverlapping(buffer.as_ptr().cast::(), dst_i); let v = std::str::from_utf8_unchecked(std::slice::from_raw_parts( input.add(idx), len + dst_i, @@ -164,7 +165,9 @@ pub(crate) fn parse_str<'invoke, 'de>( )); } unsafe { - *buffer.get_kinda_unchecked_mut(dst_i + bs_dist as usize) = escape_result; + buffer + .get_kinda_unchecked_mut(dst_i + bs_dist as usize) + .write(escape_result); } src_i += bs_dist as usize + 2; dst_i += bs_dist as usize + 1; diff --git a/src/impls/sse42/deser.rs b/src/impls/sse42/deser.rs index c169c5a2..a4f45845 100644 --- a/src/impls/sse42/deser.rs +++ b/src/impls/sse42/deser.rs @@ -4,6 +4,8 @@ use std::arch::x86 as arch; #[cfg(target_arch = "x86_64")] use std::arch::x86_64 as arch; +use std::mem::MaybeUninit; + use crate::{ Deserializer, Result, SillyWrapper, error::ErrorType, @@ -20,7 +22,7 @@ use arch::{ pub(crate) unsafe fn parse_str<'invoke, 'de>( input: SillyWrapper<'de>, data: &'invoke [u8], - buffer: &'invoke mut [u8], + buffer: &'invoke mut [MaybeUninit], mut idx: usize, ) -> Result<&'de str> { unsafe { @@ -120,7 +122,7 @@ pub(crate) unsafe fn parse_str<'invoke, 'de>( dst_i += quote_dist as usize; input .add(idx + len) - .copy_from_nonoverlapping(buffer.as_ptr(), dst_i); + .copy_from_nonoverlapping(buffer.as_ptr().cast::(), dst_i); let v = std::str::from_utf8_unchecked(std::slice::from_raw_parts( input.add(idx), len + dst_i, @@ -165,7 +167,9 @@ pub(crate) unsafe fn parse_str<'invoke, 'de>( InvalidEscape, )); } - *buffer.get_kinda_unchecked_mut(dst_i + bs_dist as usize) = escape_result; + buffer + .get_kinda_unchecked_mut(dst_i + bs_dist as usize) + .write(escape_result); src_i += bs_dist as usize + 2; dst_i += bs_dist as usize + 1; } diff --git a/src/lib.rs b/src/lib.rs index 1ddf3874..03f0ba19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,6 +68,7 @@ mod stage2; /// simd-json JSON-DOM value pub mod value; +use std::mem::MaybeUninit; use std::{alloc::dealloc, mem}; pub use value_trait::StaticNode; @@ -357,7 +358,7 @@ type FnRaw = *mut (); type ParseStrFn = for<'invoke, 'de> unsafe fn( SillyWrapper<'de>, &'invoke [u8], - &'invoke mut [u8], + &'invoke mut [MaybeUninit], usize, ) -> std::result::Result<&'de str, error::Error>; #[cfg(all( @@ -514,7 +515,7 @@ impl<'de> Deserializer<'de> { pub(crate) unsafe fn parse_str_<'invoke>( input: *mut u8, data: &'invoke [u8], - buffer: &'invoke mut [u8], + buffer: &'invoke mut [MaybeUninit], idx: usize, ) -> Result<&'de str> where @@ -538,7 +539,7 @@ impl<'de> Deserializer<'de> { pub(crate) unsafe fn parse_str_<'invoke>( input: *mut u8, data: &'invoke [u8], - buffer: &'invoke mut [u8], + buffer: &'invoke mut [MaybeUninit], idx: usize, ) -> Result<&'de str> where @@ -552,7 +553,7 @@ impl<'de> Deserializer<'de> { pub(crate) unsafe fn parse_str_<'invoke>( input: *mut u8, data: &'invoke [u8], - buffer: &'invoke mut [u8], + buffer: &'invoke mut [MaybeUninit], idx: usize, ) -> Result<&'de str> where @@ -571,7 +572,7 @@ impl<'de> Deserializer<'de> { pub(crate) unsafe fn parse_str_<'invoke>( input: *mut u8, data: &'invoke [u8], - buffer: &'invoke mut [u8], + buffer: &'invoke mut [MaybeUninit], idx: usize, ) -> Result<&'de str> { let input: SillyWrapper<'de> = SillyWrapper::from(input); @@ -588,7 +589,7 @@ impl<'de> Deserializer<'de> { pub(crate) unsafe fn parse_str_<'invoke>( input: *mut u8, data: &'invoke [u8], - buffer: &'invoke mut [u8], + buffer: &'invoke mut [MaybeUninit], idx: usize, ) -> Result<&'de str> { let input: SillyWrapper<'de> = SillyWrapper::from(input); @@ -600,7 +601,7 @@ impl<'de> Deserializer<'de> { pub(crate) unsafe fn parse_str_<'invoke>( input: *mut u8, data: &'invoke [u8], - buffer: &'invoke mut [u8], + buffer: &'invoke mut [MaybeUninit], idx: usize, ) -> Result<&'de str> { let input: SillyWrapper = SillyWrapper::from(input); @@ -611,7 +612,7 @@ impl<'de> Deserializer<'de> { pub(crate) unsafe fn parse_str_<'invoke>( input: *mut u8, data: &'invoke [u8], - buffer: &'invoke mut [u8], + buffer: &'invoke mut [MaybeUninit], idx: usize, ) -> Result<&'de str> { let input: SillyWrapper<'de> = SillyWrapper::from(input); @@ -876,10 +877,6 @@ impl<'de> Deserializer<'de> { buffer.string_buffer.clear(); buffer.string_buffer.reserve(len + SIMDJSON_PADDING); - unsafe { - buffer.string_buffer.set_len(len + SIMDJSON_PADDING); - }; - let input_buffer = &mut buffer.input_buffer; if input_buffer.capacity() < simd_safe_len { *input_buffer = AlignedBuf::with_capacity(simd_safe_len); @@ -907,7 +904,7 @@ impl<'de> Deserializer<'de> { Self::build_tape( input, input_buffer, - &mut buffer.string_buffer, + buffer.string_buffer.spare_capacity_mut(), &buffer.structural_indexes, &mut buffer.stage2_stack, buffer.max_depth, diff --git a/src/stage2.rs b/src/stage2.rs index d580a851..4ff2da41 100644 --- a/src/stage2.rs +++ b/src/stage2.rs @@ -1,4 +1,6 @@ #![allow(dead_code)] +use std::mem::MaybeUninit; + use crate::charutils::is_not_structural_or_whitespace; #[allow(unused_imports)] use crate::macros::unlikely; @@ -107,7 +109,7 @@ impl<'de> Deserializer<'de> { pub(crate) fn build_tape( input: &'de mut [u8], input2: &[u8], - buffer: &mut [u8], + buffer: &mut [MaybeUninit], structural_indexes: &[u32], stack: &mut Vec, max_depth: usize, @@ -735,10 +737,10 @@ mod test { let mut input = Vec::from(&br#""{\"arg\":\"test\"}""#[..]); let mut input2 = input.clone(); input2.append(vec![0; SIMDJSON_PADDING * 2].as_mut()); - let mut buffer = vec![0; 1024]; + let mut buffer = Vec::with_capacity(1024); let s = unsafe { - Deserializer::parse_str_(input.as_mut_ptr(), &input2, buffer.as_mut_slice(), 0)? + Deserializer::parse_str_(input.as_mut_ptr(), &input2, buffer.spare_capacity_mut(), 0)? }; assert_eq!(r#"{"arg":"test"}"#, s); Ok(()) diff --git a/src/stringparse.rs b/src/stringparse.rs index f35a9f5e..39b87604 100644 --- a/src/stringparse.rs +++ b/src/stringparse.rs @@ -1,3 +1,4 @@ +use std::mem::MaybeUninit; use std::ops::Range; use crate::charutils::{codepoint_to_utf8, hex_to_u32_nocheck}; @@ -31,7 +32,7 @@ const LOW_SURROGATES: Range = 0xdc00..0xe000; #[allow(dead_code)] pub(crate) fn handle_unicode_codepoint( src_ptr: &[u8], - dst_ptr: &mut [u8], + dst_ptr: &mut [MaybeUninit], ) -> Result<(usize, usize), ErrorType> { let (code_point, src_offset) = get_unicode_codepoint(src_ptr)?; let offset: usize = codepoint_to_utf8(code_point, dst_ptr);