about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/miri/src/helpers.rs43
-rw-r--r--src/tools/miri/src/shims/foreign_items.rs10
-rw-r--r--src/tools/miri/tests/pass-dep/wcslen.rs20
3 files changed, 64 insertions, 9 deletions
diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs
index 40c2008ac94..795f21ddb9b 100644
--- a/src/tools/miri/src/helpers.rs
+++ b/src/tools/miri/src/helpers.rs
@@ -982,29 +982,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
@@ -1037,6 +1054,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 {
diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs
index c51a27b7458..28028479ac3 100644
--- a/src/tools/miri/src/shims/foreign_items.rs
+++ b/src/tools/miri/src/shims/foreign_items.rs
@@ -657,6 +657,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)?;
diff --git a/src/tools/miri/tests/pass-dep/wcslen.rs b/src/tools/miri/tests/pass-dep/wcslen.rs
new file mode 100644
index 00000000000..c5c9d992479
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/wcslen.rs
@@ -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);
+}