[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH 04/14] rust: add tests for util::foreign
From: |
Paolo Bonzini |
Subject: |
[PATCH 04/14] rust: add tests for util::foreign |
Date: |
Mon, 1 Jul 2024 16:58:36 +0200 |
Provide sample implementations in util::foreign for strings and
elementary integer types, and use them to test the code.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
qemu/Cargo.toml | 4 +
qemu/src/util/foreign.rs | 456 +++++++++++++++++++++++++++++++++++++++
3 files changed, 474 insertions(+)
diff --git a/qemu/Cargo.toml b/qemu/Cargo.toml
index 18d0fa4..1100725 100644
--- a/qemu/Cargo.toml
+++ b/qemu/Cargo.toml
@@ -5,3 +5,7 @@ edition = "2021"
[dependencies]
const-default = { version = "~1", features = ["derive"] }
+libc = "^0"
+
+[dev-dependencies]
+matches = ">=0"
diff --git a/qemu/src/util/foreign.rs b/qemu/src/util/foreign.rs
index a591925..0b8b708 100644
--- a/qemu/src/util/foreign.rs
+++ b/qemu/src/util/foreign.rs
@@ -5,6 +5,7 @@
/// Similar to glib-rs but a bit simpler and possibly more
/// idiomatic.
use std::borrow::Cow;
+use std::ffi::{c_char, c_void, CStr, CString};
use std::fmt;
use std::fmt::Debug;
use std::mem;
@@ -22,6 +23,14 @@ pub trait CloneToForeign {
/// # Safety
///
/// `p` must be `NULL` or point to valid data.
+ ///
+ /// ```
+ /// # use qemu::CloneToForeign;
+ /// let foreign = "Hello, world!".clone_to_foreign();
+ /// unsafe {
+ /// String::free_foreign(foreign.into_inner());
+ /// }
+ /// ```
unsafe fn free_foreign(p: *mut Self::Foreign);
/// Convert a native Rust object to a foreign C struct, copying
@@ -119,6 +128,17 @@ pub trait IntoNative<T> {
///
/// `p` must point to valid data, or can be `NULL` if Self is an
/// `Option` type. It becomes invalid after the function returns.
+ ///
+ /// ```
+ /// # use qemu::{CloneToForeign, IntoNative};
+ /// let s = "Hello, world!".to_string();
+ /// let foreign = s.clone_to_foreign();
+ /// let native: String = unsafe {
+ /// foreign.into_native()
+ /// // foreign is not leaked
+ /// };
+ /// assert_eq!(s, native);
+ /// ```
unsafe fn into_native(self) -> T;
}
@@ -141,6 +161,15 @@ pub trait FromForeign: CloneToForeign + Sized {
///
/// `p` must point to valid data, or can be `NULL` is `Self` is an
/// `Option` type.
+ ///
+ /// ```
+ /// # use qemu::FromForeign;
+ /// let p = c"Hello, world!".as_ptr();
+ /// let s = unsafe {
+ /// String::cloned_from_foreign(p as *const std::ffi::c_char)
+ /// };
+ /// assert_eq!(s, "Hello, world!");
+ /// ```
unsafe fn cloned_from_foreign(p: *const Self::Foreign) -> Self;
/// Convert a C datum to a native Rust object, taking ownership of
@@ -152,6 +181,16 @@ pub trait FromForeign: CloneToForeign + Sized {
///
/// `p` must point to valid data, or can be `NULL` is `Self` is an
/// `Option` type. `p` becomes invalid after the function returns.
+ ///
+ /// ```
+ /// # use qemu::{CloneToForeign, FromForeign};
+ /// let s = "Hello, world!";
+ /// let foreign = s.clone_to_foreign();
+ /// unsafe {
+ /// assert_eq!(String::from_foreign(foreign.into_inner()), s);
+ /// }
+ /// // foreign is not leaked
+ /// ```
unsafe fn from_foreign(p: *mut Self::Foreign) -> Self {
let result = Self::cloned_from_foreign(p);
Self::free_foreign(p);
@@ -176,6 +215,12 @@ impl<T: CloneToForeign + ?Sized> OwnedPointer<T> {
/// Safely create an `OwnedPointer` from one that has the same
/// freeing function.
+ /// ```
+ /// # use qemu::{CloneToForeign, OwnedPointer};
+ /// let s = "Hello, world!";
+ /// let foreign_str = s.clone_to_foreign();
+ /// let foreign_string = OwnedPointer::<String>::from(foreign_str);
+ /// # assert_eq!(foreign_string.into_native(), s);
pub fn from<U>(x: OwnedPointer<U>) -> Self
where
U: CloneToForeign<Foreign = <T as CloneToForeign>::Foreign> + ?Sized,
@@ -189,6 +234,12 @@ impl<T: CloneToForeign + ?Sized> OwnedPointer<T> {
/// Safely convert an `OwnedPointer` into one that has the same
/// freeing function.
+ /// ```
+ /// # use qemu::{CloneToForeign, OwnedPointer};
+ /// let s = "Hello, world!";
+ /// let foreign_str = s.clone_to_foreign();
+ /// let foreign_string: OwnedPointer<String> = foreign_str.into();
+ /// # assert_eq!(foreign_string.into_native(), s);
pub fn into<U>(self) -> OwnedPointer<U>
where
U: CloneToForeign<Foreign = <T as CloneToForeign>::Foreign>,
@@ -198,6 +249,16 @@ impl<T: CloneToForeign + ?Sized> OwnedPointer<T> {
/// Return the pointer that is stored in the `OwnedPointer`. The
/// pointer is valid for as long as the `OwnedPointer` itself.
+ ///
+ /// ```
+ /// # use qemu::CloneToForeign;
+ /// let s = "Hello, world!";
+ /// let foreign = s.clone_to_foreign();
+ /// let p = foreign.as_ptr();
+ /// let len = unsafe { libc::strlen(p) };
+ /// drop(foreign);
+ /// # assert_eq!(len, 13);
+ /// ```
pub fn as_ptr(&self) -> *const <T as CloneToForeign>::Foreign {
self.ptr
}
@@ -208,6 +269,15 @@ impl<T: CloneToForeign + ?Sized> OwnedPointer<T> {
/// Return the pointer that is stored in the `OwnedPointer`,
/// consuming the `OwnedPointer` but not freeing the pointer.
+ ///
+ /// ```
+ /// # use qemu::CloneToForeign;
+ /// let s = "Hello, world!";
+ /// let p = s.clone_to_foreign().into_inner();
+ /// let len = unsafe { libc::strlen(p) };
+ /// // p needs to be freed manually
+ /// # assert_eq!(len, 13);
+ /// ```
pub fn into_inner(mut self) -> *mut <T as CloneToForeign>::Foreign {
let result = mem::replace(&mut self.ptr, ptr::null_mut());
mem::forget(self);
@@ -218,6 +288,17 @@ impl<T: CloneToForeign + ?Sized> OwnedPointer<T> {
impl<T: FromForeign + ?Sized> OwnedPointer<T> {
/// Convert a C datum to a native Rust object, taking ownership of
/// the pointer or Rust object (same as `from_glib_full` in `glib-rs`)
+ ///
+ /// ```
+ /// # use qemu::{CloneToForeign, IntoNative};
+ /// let s = "Hello, world!".to_string();
+ /// let foreign = s.clone_to_foreign();
+ /// let native: String = unsafe {
+ /// foreign.into_native()
+ /// // foreign is not leaked
+ /// };
+ /// assert_eq!(s, native);
+ /// ```
pub fn into_native(self) -> T {
// SAFETY: the pointer was passed to the unsafe constructor
OwnedPointer::new
unsafe { T::from_foreign(self.into_inner()) }
@@ -245,3 +326,378 @@ impl<T: CloneToForeign + ?Sized> Drop for OwnedPointer<T>
{
unsafe { T::free_foreign(p) }
}
}
+
+impl CloneToForeign for str {
+ type Foreign = c_char;
+
+ unsafe fn free_foreign(ptr: *mut c_char) {
+ libc::free(ptr as *mut c_void);
+ }
+
+ fn clone_to_foreign(&self) -> OwnedPointer<Self> {
+ // SAFETY: self.as_ptr() is guaranteed to point to self.len() bytes;
+ // the destination is freshly allocated
+ unsafe {
+ let p = libc::malloc(self.len() + 1) as *mut c_char;
+ ptr::copy_nonoverlapping(self.as_ptr() as *const c_char, p,
self.len());
+ *p.add(self.len()) = 0;
+ OwnedPointer::new(p)
+ }
+ }
+}
+
+impl CloneToForeign for CStr {
+ type Foreign = c_char;
+
+ unsafe fn free_foreign(ptr: *mut c_char) {
+ libc::free(ptr as *mut c_void);
+ }
+
+ fn clone_to_foreign(&self) -> OwnedPointer<Self> {
+ // SAFETY: self.as_ptr() is guaranteed to point to self.len() bytes;
+ // the destination is freshly allocated
+ unsafe {
+ let slice = self.to_bytes_with_nul();
+ let p = libc::malloc(slice.len()) as *mut c_char;
+ ptr::copy_nonoverlapping(self.as_ptr() as *const c_char, p,
slice.len());
+ OwnedPointer::new(p)
+ }
+ }
+}
+
+impl CloneToForeign for String {
+ type Foreign = c_char;
+
+ unsafe fn free_foreign(ptr: *mut c_char) {
+ libc::free(ptr as *mut c_void);
+ }
+
+ fn clone_to_foreign(&self) -> OwnedPointer<Self> {
+ self.as_str().clone_to_foreign().into()
+ }
+}
+
+impl FromForeign for String {
+ unsafe fn cloned_from_foreign(p: *const c_char) -> Self {
+ let cstr = CStr::from_ptr(p);
+ String::from_utf8_lossy(cstr.to_bytes()).into_owned()
+ }
+}
+
+impl CloneToForeign for CString {
+ type Foreign = c_char;
+
+ unsafe fn free_foreign(ptr: *mut c_char) {
+ libc::free(ptr as *mut c_void);
+ }
+
+ fn clone_to_foreign(&self) -> OwnedPointer<Self> {
+ self.as_c_str().clone_to_foreign().into()
+ }
+}
+
+impl FromForeign for CString {
+ unsafe fn cloned_from_foreign(p: *const c_char) -> Self {
+ CStr::from_ptr(p).to_owned()
+ }
+}
+
+impl FromForeign for Cow<'_, str>
+{
+ unsafe fn cloned_from_foreign(p: *const c_char) -> Self {
+ let cstr = CStr::from_ptr(p);
+ cstr.to_string_lossy()
+ }
+}
+
+macro_rules! foreign_copy_type {
+ ($rust_type:ty, $foreign_type:ty) => {
+ impl CloneToForeign for $rust_type {
+ type Foreign = $foreign_type;
+
+ unsafe fn free_foreign(ptr: *mut Self::Foreign) {
+ libc::free(ptr as *mut c_void);
+ }
+
+ fn clone_to_foreign(&self) -> OwnedPointer<Self> {
+ // Safety: we are copying into a freshly-allocated block
+ unsafe {
+ let p = libc::malloc(mem::size_of::<Self>()) as *mut
Self::Foreign;
+ *p = *self as Self::Foreign;
+ OwnedPointer::new(p)
+ }
+ }
+ }
+
+ impl FromForeign for $rust_type {
+ unsafe fn cloned_from_foreign(p: *const Self::Foreign) -> Self {
+ *p
+ }
+ }
+
+ impl CloneToForeign for [$rust_type] {
+ type Foreign = $foreign_type;
+
+ unsafe fn free_foreign(ptr: *mut Self::Foreign) {
+ libc::free(ptr as *mut c_void);
+ }
+
+ fn clone_to_foreign(&self) -> OwnedPointer<Self> {
+ // SAFETY: self.as_ptr() is guaranteed to point to the same
number of bytes
+ // as the freshly allocated destination
+ unsafe {
+ let size = mem::size_of::<Self::Foreign>();
+ let p = libc::malloc(self.len() * size) as *mut
Self::Foreign;
+ ptr::copy_nonoverlapping(self.as_ptr() as *const
Self::Foreign, p, self.len());
+ OwnedPointer::new(p)
+ }
+ }
+ }
+ };
+}
+foreign_copy_type!(i8, i8);
+foreign_copy_type!(u8, u8);
+foreign_copy_type!(i16, i16);
+foreign_copy_type!(u16, u16);
+foreign_copy_type!(i32, i32);
+foreign_copy_type!(u32, u32);
+foreign_copy_type!(i64, i64);
+foreign_copy_type!(u64, u64);
+foreign_copy_type!(isize, libc::ptrdiff_t);
+foreign_copy_type!(usize, libc::size_t);
+foreign_copy_type!(f32, f32);
+foreign_copy_type!(f64, f64);
+
+#[cfg(test)]
+mod tests {
+ #![allow(clippy::shadow_unrelated)]
+
+ use super::*;
+ use matches::assert_matches;
+ use std::ffi::c_void;
+
+ #[test]
+ fn test_foreign_int_convert() {
+ let i = 123i8;
+ let p = i.clone_to_foreign();
+ unsafe {
+ assert_eq!(i, *p.as_ptr());
+ assert_eq!(i, i8::cloned_from_foreign(p.as_ptr()));
+ }
+ assert_eq!(i, p.into_native());
+
+ let p = i.clone_to_foreign();
+ unsafe {
+ assert_eq!(i, i8::from_foreign(p.into_inner()));
+ }
+ }
+
+ #[test]
+ fn test_cloned_from_foreign_string_cow() {
+ let s = "Hello, world!".to_string();
+ let cstr = c"Hello, world!";
+ let cloned = unsafe { Cow::cloned_from_foreign(cstr.as_ptr()) };
+ assert_eq!(s, cloned);
+ }
+
+ #[test]
+ fn test_cloned_from_foreign_string() {
+ let s = "Hello, world!".to_string();
+ let cstr = c"Hello, world!";
+ let cloned = unsafe { String::cloned_from_foreign(cstr.as_ptr()) };
+ assert_eq!(s, cloned);
+ }
+
+ #[test]
+ fn test_cloned_from_foreign_cstring() {
+ let s = CString::new("Hello, world!").unwrap();
+ let cloned = s.clone_to_foreign();
+ let copy = unsafe { CString::cloned_from_foreign(cloned.as_ptr()) };
+ assert_ne!(copy.as_ptr(), cloned.as_ptr());
+ assert_ne!(copy.as_ptr(), s.as_ptr());
+ assert_eq!(copy, s);
+ }
+
+ #[test]
+ fn test_from_foreign_string() {
+ let s = "Hello, world!".to_string();
+ let cloned = s.clone_to_foreign_ptr();
+ let copy = unsafe { String::from_foreign(cloned) };
+ assert_eq!(s, copy);
+ }
+
+ #[test]
+ fn test_owned_pointer_default() {
+ let s: String = Default::default();
+ let foreign: OwnedPointer<String> = Default::default();
+ let native = foreign.into_native();
+ assert_eq!(s, native);
+ }
+
+ #[test]
+ fn test_owned_pointer_into() {
+ let s = "Hello, world!".to_string();
+ let cloned: OwnedPointer<String> = s.clone_to_foreign().into();
+ let copy = cloned.into_native();
+ assert_eq!(s, copy);
+ }
+
+ #[test]
+ fn test_owned_pointer_into_native() {
+ let s = "Hello, world!".to_string();
+ let cloned = s.clone_to_foreign();
+ let copy = cloned.into_native();
+ assert_eq!(s, copy);
+ }
+
+ #[test]
+ fn test_ptr_into_native() {
+ let s = "Hello, world!".to_string();
+ let cloned = s.clone_to_foreign_ptr();
+ let copy: String = unsafe { cloned.into_native() };
+ assert_eq!(s, copy);
+
+ // This is why type bounds are needed... they aren't for
+ // OwnedPointer::into_native
+ let cloned = s.clone_to_foreign_ptr();
+ let copy: c_char = unsafe { cloned.into_native() };
+ assert_eq!(s.as_bytes()[0], copy as u8);
+ }
+
+ #[test]
+ fn test_clone_to_foreign_str() {
+ let s = "Hello, world!";
+ let p = c"Hello, world!".as_ptr();
+ let cloned = s.clone_to_foreign();
+ unsafe {
+ let len = libc::strlen(cloned.as_ptr());
+ assert_eq!(len, s.len());
+ assert_eq!(
+ libc::memcmp(
+ cloned.as_ptr() as *const c_void,
+ p as *const c_void,
+ len + 1
+ ),
+ 0
+ );
+ }
+ }
+
+ #[test]
+ fn test_clone_to_foreign_cstr() {
+ let s: &CStr = c"Hello, world!";
+ let cloned = s.clone_to_foreign();
+ unsafe {
+ let len = libc::strlen(cloned.as_ptr());
+ assert_eq!(len, s.count_bytes());
+ assert_eq!(
+ libc::memcmp(
+ cloned.as_ptr() as *const c_void,
+ s.as_ptr() as *const c_void,
+ len + 1
+ ),
+ 0
+ );
+ }
+ }
+
+ #[test]
+ fn test_clone_to_foreign_string_cow() {
+ let p = c"Hello, world!".as_ptr();
+ for s in vec![
+ Into::<Cow<str>>::into("Hello, world!"),
+ Into::<Cow<str>>::into("Hello, world!".to_string())] {
+ let cloned = s.clone_to_foreign();
+ unsafe {
+ let len = libc::strlen(cloned.as_ptr());
+ assert_eq!(len, s.len());
+ assert_eq!(
+ libc::memcmp(
+ cloned.as_ptr() as *const c_void,
+ p as *const c_void,
+ len + 1
+ ),
+ 0
+ );
+ }
+ }
+ }
+
+ #[test]
+ fn test_clone_to_foreign_bytes() {
+ let s = b"Hello, world!\0";
+ let cloned = s.clone_to_foreign();
+ unsafe {
+ let len = libc::strlen(cloned.as_ptr() as *const c_char);
+ assert_eq!(len, s.len() - 1);
+ assert_eq!(
+ libc::memcmp(
+ cloned.as_ptr() as *const c_void,
+ s.as_ptr() as *const c_void,
+ len + 1
+ ),
+ 0
+ );
+ }
+ }
+
+ #[test]
+ fn test_clone_to_foreign_cstring() {
+ let s = CString::new("Hello, world!").unwrap();
+ let cloned = s.clone_to_foreign();
+ unsafe {
+ assert_ne!(s.as_ptr(), cloned.as_ptr());
+ assert_eq!(
+ libc::strcmp(
+ cloned.as_ptr() as *const c_char,
+ s.as_ptr() as *const c_char,
+ ),
+ 0
+ );
+ }
+ }
+
+ #[test]
+ fn test_clone_to_foreign_string() {
+ let s = "Hello, world!".to_string();
+ let cstr = c"Hello, world!";
+ let cloned = s.clone_to_foreign();
+ unsafe {
+ let len = libc::strlen(cloned.as_ptr());
+ assert_eq!(len, s.len());
+ assert_eq!(
+ libc::memcmp(
+ cloned.as_ptr() as *const c_void,
+ cstr.as_ptr() as *const c_void,
+ len + 1
+ ),
+ 0
+ );
+ }
+ }
+
+ #[test]
+ fn test_option() {
+ // An Option can be used to produce or convert NULL pointers
+ let s = Some("Hello, world!".to_string());
+ let cstr = c"Hello, world!";
+ unsafe {
+ assert_eq!(Option::<String>::cloned_from_foreign(cstr.as_ptr()),
s);
+
assert_matches!(Option::<String>::cloned_from_foreign(ptr::null()), None);
+ assert_matches!(Option::<String>::from_foreign(ptr::null_mut()),
None);
+ }
+ }
+
+ #[test]
+ fn test_box() {
+ // A box can be produced if the inner type has the capability.
+ let s = Box::new("Hello, world!".to_string());
+ let cstr = c"Hello, world!";
+ let cloned = unsafe {
Box::<String>::cloned_from_foreign(cstr.as_ptr()) };
+ assert_eq!(s, cloned);
+
+ let s = Some(Box::new("Hello, world!".to_string()));
+ let cloned = unsafe {
Option::<Box<String>>::cloned_from_foreign(cstr.as_ptr()) };
+ assert_eq!(s, cloned);
+ }
+}
--
2.45.2
- [PATCH 00/14] rust: example of bindings code for Rust in QEMU, Paolo Bonzini, 2024/07/01
- [PATCH 01/14] add skeleton, Paolo Bonzini, 2024/07/01
- [PATCH 02/14] set expectations, Paolo Bonzini, 2024/07/01
- [PATCH 04/14] rust: add tests for util::foreign,
Paolo Bonzini <=
- [PATCH 05/14] rust: define wrappers for Error, Paolo Bonzini, 2024/07/01
- [PATCH 03/14] rust: define traits and pointer wrappers to convert from/to C representations, Paolo Bonzini, 2024/07/01
- [PATCH 07/14] rust: define wrappers for methods of the QOM Object class, Paolo Bonzini, 2024/07/01
- [PATCH 08/14] rust: define wrappers for methods of the QOM Device class, Paolo Bonzini, 2024/07/01
- [PATCH 06/14] rust: define wrappers for basic QOM concepts, Paolo Bonzini, 2024/07/01
- [PATCH 09/14] rust: add idiomatic bindings to define Object subclasses, Paolo Bonzini, 2024/07/01
- [PATCH 10/14] rust: add idiomatic bindings to define Device subclasses, Paolo Bonzini, 2024/07/01