From nobody Fri Oct 18 08:31:49 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1719846023; cv=none; d=zohomail.com; s=zohoarc; b=mHqq5Sz9uvVtHa84voHdY865fVO7/CtQVR5PhO0RgCF3HJMomXIKivk7gxD8d5BXLEfM3CSMqXAdCvRPIO6SMnzVQZczcCua8KOCwyOtBcwb3HIkD4uWb3U2NPuJ2fuZei4Zz59XGD1xXyWWsexiSf7HF13zSNSNgkMtjsBwn18= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1719846023; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=4FB4lTjJ2jdkYkhNKjtSbR2CEvqDwGBPr2HOuzmXi+Q=; b=AvXvBuT1L1hGs39CGPNULv7wYW0AYxXrxu1l6lE+IXgiQ+6HGgaBb1NV6Lo6kbU494XWuLfVS9oMX7VlU2YZ8Ia+ykrscUqcfXl+nxBEeQo0ycjKCxddhgPl1qX3RRpU1GiVaM9Iq6zJkmolVjeeUOAjiWW8qu9zKUDhBRvB0X8= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1719846023320372.8043948588762; Mon, 1 Jul 2024 08:00:23 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sOIUx-0003up-PT; Mon, 01 Jul 2024 10:59:19 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1sOIUv-0003p3-3E for qemu-devel@nongnu.org; Mon, 01 Jul 2024 10:59:17 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1sOIUs-0005Ah-0X for qemu-devel@nongnu.org; Mon, 01 Jul 2024 10:59:16 -0400 Received: from mail-wr1-f71.google.com (mail-wr1-f71.google.com [209.85.221.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-620-WgpDa7wbNZ61bhssyeVNAg-1; Mon, 01 Jul 2024 10:59:11 -0400 Received: by mail-wr1-f71.google.com with SMTP id ffacd0b85a97d-363edcf12a3so2052780f8f.3 for ; Mon, 01 Jul 2024 07:59:11 -0700 (PDT) Received: from avogadro.local ([151.95.101.29]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3675a0fba5asm10250857f8f.71.2024.07.01.07.59.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 01 Jul 2024 07:59:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1719845952; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=4FB4lTjJ2jdkYkhNKjtSbR2CEvqDwGBPr2HOuzmXi+Q=; b=Cu4ubbs9phv437Vxcn/SDP9+iXVsa+mAKkB61RJSNR7pvkGSY7ZbzGH3wR9gteGo5tv5y4 Sonc0cu2C4y78Yh90nqblul6yDJOkvwOSWjtW3mB4iJYf49qw0al4C+WdCM8VsbyDmfZ8m 97YHv0uLg1xmBMnfZ1r9+7NlDzdYyEQ= X-MC-Unique: WgpDa7wbNZ61bhssyeVNAg-1 X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1719845949; x=1720450749; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=4FB4lTjJ2jdkYkhNKjtSbR2CEvqDwGBPr2HOuzmXi+Q=; b=OVoPFI6z4C+XrJ1LQm4dAMMz6CqDZub87m426OphPmBJV3eH9L8F1CIWTT2CoxvYLZ k6MckvvBDWlbaDZkrpnuWw+9X9m8X24l7Wp9hZo6BvEJ1Vr4f/OViVUMk0kD+/H++F7j qwVQwAh2e79cZLdxV+NNbQQUVr1lN3nFN/oNl0e6nfnw69+lZ7EW9Vvtqzx7+6SWsLSI mG9OTvaR3ZVngVPG+UUx+rjAP+1VkgJtxskYzsBmp0KdmKoABz+KqGF9P11liZ+I+Jd9 COKSYrzZtETovDfYyXwopT+NkY+Hru1zcHhEe1rFqZ4TIEkdot12J+CcvwjWjHyR+Bym Skow== X-Gm-Message-State: AOJu0YyEZMcnLulSfBoMkimj2YuqbxL/NgkdYRXPuAt1fbMGnenKKFmY LighrUIADIQpRn9RBKxTf9TtqC84g7WNKmbRAkCC8FEqX66RNs0SbqJissYsvVfCQFPMhEN6It/ AWKB7WYraR42eVLLDyFqWVoqdBQ/RmBnXiGMM2f63vRyoViYxb+0OyhKDvXBUN17mHG+VF+B6Rr S9Vp9GOucQTmpDTNl3s2MfcoJHUu4LK38PdevI X-Received: by 2002:a05:6000:1447:b0:367:4dbb:ed4e with SMTP id ffacd0b85a97d-3677561da1bmr4170415f8f.0.1719845949201; Mon, 01 Jul 2024 07:59:09 -0700 (PDT) X-Google-Smtp-Source: AGHT+IEma+4/l+Pn62ItPvedLeZpIOdZx7y2TYZTPNuebsrDVIm7QiIfX7fwQ3nyIkvoKCnsvAYgew== X-Received: by 2002:a05:6000:1447:b0:367:4dbb:ed4e with SMTP id ffacd0b85a97d-3677561da1bmr4170391f8f.0.1719845948723; Mon, 01 Jul 2024 07:59:08 -0700 (PDT) From: Paolo Bonzini To: qemu-devel@nongnu.org Cc: Pierrick Bouvier , Manos Pitsidianakis , Peter Maydell , =?UTF-8?q?Alex=20Benn=C3=A9e?= , =?UTF-8?q?Daniel=20P=20=2E=20Berrang=C3=A9?= , =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= , Hanna Czenczek , Stefan Hajnoczi Subject: [PATCH 04/14] rust: add tests for util::foreign Date: Mon, 1 Jul 2024 16:58:36 +0200 Message-ID: <20240701145853.1394967-5-pbonzini@redhat.com> X-Mailer: git-send-email 2.45.2 In-Reply-To: <20240701145853.1394967-1-pbonzini@redhat.com> References: <20240701145853.1394967-1-pbonzini@redhat.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=170.10.133.124; envelope-from=pbonzini@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1719846029587100001 Content-Type: text/plain; charset="utf-8" Provide sample implementations in util::foreign for strings and elementary integer types, and use them to test the code. Signed-off-by: Paolo Bonzini --- 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 =3D "2021" =20 [dependencies] const-default =3D { version =3D "~1", features =3D ["derive"] } +libc =3D "^0" + +[dev-dependencies] +matches =3D ">=3D0" 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 =3D "Hello, world!".clone_to_foreign(); + /// unsafe { + /// String::free_foreign(foreign.into_inner()); + /// } + /// ``` unsafe fn free_foreign(p: *mut Self::Foreign); =20 /// Convert a native Rust object to a foreign C struct, copying @@ -119,6 +128,17 @@ pub trait IntoNative { /// /// `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 =3D "Hello, world!".to_string(); + /// let foreign =3D s.clone_to_foreign(); + /// let native: String =3D unsafe { + /// foreign.into_native() + /// // foreign is not leaked + /// }; + /// assert_eq!(s, native); + /// ``` unsafe fn into_native(self) -> T; } =20 @@ -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 =3D c"Hello, world!".as_ptr(); + /// let s =3D 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; =20 /// 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 =3D "Hello, world!"; + /// let foreign =3D 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 =3D Self::cloned_from_foreign(p); Self::free_foreign(p); @@ -176,6 +215,12 @@ impl OwnedPointer { =20 /// Safely create an `OwnedPointer` from one that has the same /// freeing function. + /// ``` + /// # use qemu::{CloneToForeign, OwnedPointer}; + /// let s =3D "Hello, world!"; + /// let foreign_str =3D s.clone_to_foreign(); + /// let foreign_string =3D OwnedPointer::::from(foreign_str); + /// # assert_eq!(foreign_string.into_native(), s); pub fn from(x: OwnedPointer) -> Self where U: CloneToForeign::Foreign> + ?S= ized, @@ -189,6 +234,12 @@ impl OwnedPointer { =20 /// Safely convert an `OwnedPointer` into one that has the same /// freeing function. + /// ``` + /// # use qemu::{CloneToForeign, OwnedPointer}; + /// let s =3D "Hello, world!"; + /// let foreign_str =3D s.clone_to_foreign(); + /// let foreign_string: OwnedPointer =3D foreign_str.into(); + /// # assert_eq!(foreign_string.into_native(), s); pub fn into(self) -> OwnedPointer where U: CloneToForeign::Foreign>, @@ -198,6 +249,16 @@ impl OwnedPointer { =20 /// 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 =3D "Hello, world!"; + /// let foreign =3D s.clone_to_foreign(); + /// let p =3D foreign.as_ptr(); + /// let len =3D unsafe { libc::strlen(p) }; + /// drop(foreign); + /// # assert_eq!(len, 13); + /// ``` pub fn as_ptr(&self) -> *const ::Foreign { self.ptr } @@ -208,6 +269,15 @@ impl OwnedPointer { =20 /// Return the pointer that is stored in the `OwnedPointer`, /// consuming the `OwnedPointer` but not freeing the pointer. + /// + /// ``` + /// # use qemu::CloneToForeign; + /// let s =3D "Hello, world!"; + /// let p =3D s.clone_to_foreign().into_inner(); + /// let len =3D unsafe { libc::strlen(p) }; + /// // p needs to be freed manually + /// # assert_eq!(len, 13); + /// ``` pub fn into_inner(mut self) -> *mut ::Foreign { let result =3D mem::replace(&mut self.ptr, ptr::null_mut()); mem::forget(self); @@ -218,6 +288,17 @@ impl OwnedPointer { impl OwnedPointer { /// 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 =3D "Hello, world!".to_string(); + /// let foreign =3D s.clone_to_foreign(); + /// let native: String =3D 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 OwnedP= ointer::new unsafe { T::from_foreign(self.into_inner()) } @@ -245,3 +326,378 @@ impl Drop for OwnedPointe= r { unsafe { T::free_foreign(p) } } } + +impl CloneToForeign for str { + type Foreign =3D c_char; + + unsafe fn free_foreign(ptr: *mut c_char) { + libc::free(ptr as *mut c_void); + } + + fn clone_to_foreign(&self) -> OwnedPointer { + // SAFETY: self.as_ptr() is guaranteed to point to self.len() byte= s; + // the destination is freshly allocated + unsafe { + let p =3D libc::malloc(self.len() + 1) as *mut c_char; + ptr::copy_nonoverlapping(self.as_ptr() as *const c_char, p, se= lf.len()); + *p.add(self.len()) =3D 0; + OwnedPointer::new(p) + } + } +} + +impl CloneToForeign for CStr { + type Foreign =3D c_char; + + unsafe fn free_foreign(ptr: *mut c_char) { + libc::free(ptr as *mut c_void); + } + + fn clone_to_foreign(&self) -> OwnedPointer { + // SAFETY: self.as_ptr() is guaranteed to point to self.len() byte= s; + // the destination is freshly allocated + unsafe { + let slice =3D self.to_bytes_with_nul(); + let p =3D libc::malloc(slice.len()) as *mut c_char; + ptr::copy_nonoverlapping(self.as_ptr() as *const c_char, p, sl= ice.len()); + OwnedPointer::new(p) + } + } +} + +impl CloneToForeign for String { + type Foreign =3D c_char; + + unsafe fn free_foreign(ptr: *mut c_char) { + libc::free(ptr as *mut c_void); + } + + fn clone_to_foreign(&self) -> OwnedPointer { + self.as_str().clone_to_foreign().into() + } +} + +impl FromForeign for String { + unsafe fn cloned_from_foreign(p: *const c_char) -> Self { + let cstr =3D CStr::from_ptr(p); + String::from_utf8_lossy(cstr.to_bytes()).into_owned() + } +} + +impl CloneToForeign for CString { + type Foreign =3D c_char; + + unsafe fn free_foreign(ptr: *mut c_char) { + libc::free(ptr as *mut c_void); + } + + fn clone_to_foreign(&self) -> OwnedPointer { + 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 =3D CStr::from_ptr(p); + cstr.to_string_lossy() + } +} + +macro_rules! foreign_copy_type { + ($rust_type:ty, $foreign_type:ty) =3D> { + impl CloneToForeign for $rust_type { + type Foreign =3D $foreign_type; + + unsafe fn free_foreign(ptr: *mut Self::Foreign) { + libc::free(ptr as *mut c_void); + } + + fn clone_to_foreign(&self) -> OwnedPointer { + // Safety: we are copying into a freshly-allocated block + unsafe { + let p =3D libc::malloc(mem::size_of::()) as *mut= Self::Foreign; + *p =3D *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 =3D $foreign_type; + + unsafe fn free_foreign(ptr: *mut Self::Foreign) { + libc::free(ptr as *mut c_void); + } + + fn clone_to_foreign(&self) -> OwnedPointer { + // SAFETY: self.as_ptr() is guaranteed to point to the sam= e number of bytes + // as the freshly allocated destination + unsafe { + let size =3D mem::size_of::(); + let p =3D 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 =3D 123i8; + let p =3D 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 =3D i.clone_to_foreign(); + unsafe { + assert_eq!(i, i8::from_foreign(p.into_inner())); + } + } + + #[test] + fn test_cloned_from_foreign_string_cow() { + let s =3D "Hello, world!".to_string(); + let cstr =3D c"Hello, world!"; + let cloned =3D unsafe { Cow::cloned_from_foreign(cstr.as_ptr()) }; + assert_eq!(s, cloned); + } + + #[test] + fn test_cloned_from_foreign_string() { + let s =3D "Hello, world!".to_string(); + let cstr =3D c"Hello, world!"; + let cloned =3D unsafe { String::cloned_from_foreign(cstr.as_ptr())= }; + assert_eq!(s, cloned); + } + + #[test] + fn test_cloned_from_foreign_cstring() { + let s =3D CString::new("Hello, world!").unwrap(); + let cloned =3D s.clone_to_foreign(); + let copy =3D 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 =3D "Hello, world!".to_string(); + let cloned =3D s.clone_to_foreign_ptr(); + let copy =3D unsafe { String::from_foreign(cloned) }; + assert_eq!(s, copy); + } + + #[test] + fn test_owned_pointer_default() { + let s: String =3D Default::default(); + let foreign: OwnedPointer =3D Default::default(); + let native =3D foreign.into_native(); + assert_eq!(s, native); + } + + #[test] + fn test_owned_pointer_into() { + let s =3D "Hello, world!".to_string(); + let cloned: OwnedPointer =3D s.clone_to_foreign().into(); + let copy =3D cloned.into_native(); + assert_eq!(s, copy); + } + + #[test] + fn test_owned_pointer_into_native() { + let s =3D "Hello, world!".to_string(); + let cloned =3D s.clone_to_foreign(); + let copy =3D cloned.into_native(); + assert_eq!(s, copy); + } + + #[test] + fn test_ptr_into_native() { + let s =3D "Hello, world!".to_string(); + let cloned =3D s.clone_to_foreign_ptr(); + let copy: String =3D unsafe { cloned.into_native() }; + assert_eq!(s, copy); + + // This is why type bounds are needed... they aren't for + // OwnedPointer::into_native + let cloned =3D s.clone_to_foreign_ptr(); + let copy: c_char =3D unsafe { cloned.into_native() }; + assert_eq!(s.as_bytes()[0], copy as u8); + } + + #[test] + fn test_clone_to_foreign_str() { + let s =3D "Hello, world!"; + let p =3D c"Hello, world!".as_ptr(); + let cloned =3D s.clone_to_foreign(); + unsafe { + let len =3D 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 =3D c"Hello, world!"; + let cloned =3D s.clone_to_foreign(); + unsafe { + let len =3D 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 =3D c"Hello, world!".as_ptr(); + for s in vec![ + Into::>::into("Hello, world!"), + Into::>::into("Hello, world!".to_string())] { + let cloned =3D s.clone_to_foreign(); + unsafe { + let len =3D 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 =3D b"Hello, world!\0"; + let cloned =3D s.clone_to_foreign(); + unsafe { + let len =3D 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 =3D CString::new("Hello, world!").unwrap(); + let cloned =3D 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 =3D "Hello, world!".to_string(); + let cstr =3D c"Hello, world!"; + let cloned =3D s.clone_to_foreign(); + unsafe { + let len =3D 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 =3D Some("Hello, world!".to_string()); + let cstr =3D c"Hello, world!"; + unsafe { + assert_eq!(Option::::cloned_from_foreign(cstr.as_ptr()= ), s); + assert_matches!(Option::::cloned_from_foreign(ptr::nul= l()), None); + assert_matches!(Option::::from_foreign(ptr::null_mut()= ), None); + } + } + + #[test] + fn test_box() { + // A box can be produced if the inner type has the capability. + let s =3D Box::new("Hello, world!".to_string()); + let cstr =3D c"Hello, world!"; + let cloned =3D unsafe { Box::::cloned_from_foreign(cstr.as= _ptr()) }; + assert_eq!(s, cloned); + + let s =3D Some(Box::new("Hello, world!".to_string())); + let cloned =3D unsafe { Option::>::cloned_from_foreign= (cstr.as_ptr()) }; + assert_eq!(s, cloned); + } +} --=20 2.45.2