Skip to content

Commit

Permalink
Implement wcslen
Browse files Browse the repository at this point in the history
  • Loading branch information
tyilo committed May 6, 2024
1 parent ea156ea commit af6dbb7
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 9 deletions.
43 changes: 34 additions & 9 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -977,29 +977,46 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
Ok((true, string_length))
}

/// Read a sequence of u16 until the first null terminator.
fn read_wide_str(&self, mut ptr: Pointer<Option<Provenance>>) -> InterpResult<'tcx, Vec<u16>> {
/// Helper function to read a sequence of unsigned integers of the given size and alignment
/// until the first null terminator.
fn read_c_str_with_char_size<T>(
&self,
mut ptr: Pointer<Option<Provenance>>,
size: Size,
align: Align,
) -> InterpResult<'tcx, Vec<T>>
where
T: TryFrom<u128>,
<T as TryFrom<u128>>::Error: std::fmt::Debug,
{
assert_ne!(size, Size::ZERO);

let this = self.eval_context_ref();
let size2 = Size::from_bytes(2);
this.check_ptr_align(ptr, Align::from_bytes(2).unwrap())?;

this.check_ptr_align(ptr, align)?;

let mut wchars = Vec::new();
loop {
// FIXME: We are re-getting the allocation each time around the loop.
// Would be nice if we could somehow "extend" an existing AllocRange.
let alloc = this.get_ptr_alloc(ptr, size2)?.unwrap(); // not a ZST, so we will get a result
let wchar = alloc.read_integer(alloc_range(Size::ZERO, size2))?.to_u16()?;
if wchar == 0 {
let alloc = this.get_ptr_alloc(ptr, size)?.unwrap(); // not a ZST, so we will get a result
let wchar_int = alloc.read_integer(alloc_range(Size::ZERO, size))?.to_bits(size)?;
if wchar_int == 0 {
break;
} else {
wchars.push(wchar);
ptr = ptr.offset(size2, this)?;
wchars.push(wchar_int.try_into().unwrap());
ptr = ptr.offset(size, this)?;
}
}

Ok(wchars)
}

/// Read a sequence of u16 until the first null terminator.
fn read_wide_str(&self, ptr: Pointer<Option<Provenance>>) -> InterpResult<'tcx, Vec<u16>> {
self.read_c_str_with_char_size(ptr, Size::from_bytes(2), Align::from_bytes(2).unwrap())
}

/// Helper function to write a sequence of u16 with an added 0x0000-terminator, which is what
/// the Windows APIs usually handle. This function returns `Ok((false, length))` without trying
/// to write if `size` is not large enough to fit the contents of `os_string` plus a null
Expand Down Expand Up @@ -1032,6 +1049,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
Ok((true, string_length))
}

/// Read a sequence of wchar_t until the first null terminator.
/// Always returns a `Vec<u32>` no matter the size of `wchar_t`.
fn read_wchar_t_str(&self, ptr: Pointer<Option<Provenance>>) -> InterpResult<'tcx, Vec<u32>> {
let this = self.eval_context_ref();
let wchar_t = this.libc_ty_layout("wchar_t");
self.read_c_str_with_char_size(ptr, wchar_t.size, wchar_t.align.abi)
}

/// Check that the ABI is what we expect.
fn check_abi<'a>(&self, abi: Abi, exp_abi: Abi) -> InterpResult<'a, ()> {
if abi != exp_abi {
Expand Down
10 changes: 10 additions & 0 deletions src/shims/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,16 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
dest,
)?;
}
"wcslen" => {
let [ptr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
// This reads at least 1 byte, so we are already enforcing that this is a valid pointer.
let n = this.read_wchar_t_str(ptr)?.len();
this.write_scalar(
Scalar::from_target_usize(u64::try_from(n).unwrap(), this),
dest,
)?;
}
"memcpy" => {
let [ptr_dest, ptr_src, n] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
Expand Down
20 changes: 20 additions & 0 deletions tests/pass-dep/wcslen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
fn to_c_wchar_t_str(s: &str) -> Vec<libc::wchar_t> {
let mut r = Vec::<libc::wchar_t>::new();
for c in s.bytes() {
if c == 0 {
panic!("can't contain a null character");
}
if c >= 128 {
panic!("only ASCII supported");
}
r.push(c.into());
}
r.push(0);
r
}

pub fn main() {
let s = to_c_wchar_t_str("Rust");
let len = unsafe { libc::wcslen(s.as_ptr()) };
assert_eq!(len, 4);
}

0 comments on commit af6dbb7

Please sign in to comment.