From 1e55497b3a6c831919e8f01ddf02673098ac121a Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Thu, 16 Jul 2026 14:57:46 -0400 Subject: [PATCH] host_env: Don't truncate two byte wchar_t Casting a u32 to a u16 would truncate the value. It's safer to use Wtf::encode_wide which already handles converting a CodePoint to a u16. I removed vec_into_bytes because it was only used in two places. The function was unsound for T but sound in the way RustPython used it (POD to POD, less strict alignment). AI disclosure: I linted this code with AI. I found this issue by accident while working on another patch in which I introduced a similar mistake. AI caught that mistake, so I linted the original code to cross-check it. I wrote the code myself in both instances. Assisted-by: Codex:gpt-5.4 --- crates/host_env/src/ctypes.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/host_env/src/ctypes.rs b/crates/host_env/src/ctypes.rs index 2dfc8cbe29d..bbb3e4afe60 100644 --- a/crates/host_env/src/ctypes.rs +++ b/crates/host_env/src/ctypes.rs @@ -440,7 +440,7 @@ pub fn read_pointer_from_buffer(buffer: &[u8]) -> usize { pub const WCHAR_SIZE: usize = core::mem::size_of::(); #[inline] -pub fn wchar_from_bytes(bytes: &[u8]) -> Option { +pub const fn wchar_from_bytes(bytes: &[u8]) -> Option { if bytes.len() < WCHAR_SIZE { return None; } @@ -524,20 +524,17 @@ pub fn encode_wtf8_to_wchar_padded(s: &Wtf8, size: usize) -> Vec { } pub fn wchar_null_terminated_bytes(s: &Wtf8) -> Vec { - let wchars: Vec = s - .code_points() - .map(|cp| cp.to_u32() as WChar) - .chain(core::iter::once(0)) - .collect(); - vec_into_bytes(wchars) -} - -pub fn vec_into_bytes(vec: Vec) -> Vec { - let len = vec.len() * core::mem::size_of::(); - let cap = vec.capacity() * core::mem::size_of::(); - let ptr = vec.as_ptr() as *mut u8; - core::mem::forget(vec); - unsafe { Vec::from_raw_parts(ptr, len, cap) } + if size_of::() == 2 { + // We can't cast u32 to WChar because it would truncate the value on platforms where WChar + // is two bytes. Wtf8::encode_wide does all of the hard work for us, so all we have to do + // is split the bytes. + utf16z_bytes(s) + } else { + s.code_points() + .flat_map(|cp| (cp.to_u32() as WChar).to_ne_bytes()) + .chain((0 as WChar).to_ne_bytes()) + .collect() + } } pub enum IntegerValue { @@ -1108,7 +1105,10 @@ pub fn simple_storage_value_to_bytes_endian( } pub fn utf16z_bytes(s: &Wtf8) -> Vec { - vec_into_bytes::(s.encode_wide().chain(core::iter::once(0)).collect()) + s.encode_wide() + .flat_map(|cp| cp.to_ne_bytes()) + .chain(0u16.to_ne_bytes()) + .collect() } pub fn null_terminated_bytes(bytes: &[u8]) -> Vec {