diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/ffi-support/src/ffistr.rs | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/ffi-support/src/ffistr.rs')
-rw-r--r-- | third_party/rust/ffi-support/src/ffistr.rs | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/third_party/rust/ffi-support/src/ffistr.rs b/third_party/rust/ffi-support/src/ffistr.rs new file mode 100644 index 0000000000..fb60e1f72c --- /dev/null +++ b/third_party/rust/ffi-support/src/ffistr.rs @@ -0,0 +1,252 @@ +/* Copyright 2018-2019 Mozilla Foundation + * + * Licensed under the Apache License (Version 2.0), or the MIT license, + * (the "Licenses") at your option. You may not use this file except in + * compliance with one of the Licenses. You may obtain copies of the + * Licenses at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licenses is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licenses for the specific language governing permissions and + * limitations under the Licenses. */ + +use std::ffi::CStr; +use std::marker::PhantomData; +use std::os::raw::c_char; + +/// `FfiStr<'a>` is a safe (`#[repr(transparent)]`) wrapper around a +/// nul-terminated `*const c_char` (e.g. a C string). Conceptually, it is +/// similar to [`std::ffi::CStr`], except that it may be used in the signatures +/// of extern "C" functions. +/// +/// Functions accepting strings should use this instead of accepting a C string +/// directly. This allows us to write those functions using safe code without +/// allowing safe Rust to cause memory unsafety. +/// +/// A single function for constructing these from Rust ([`FfiStr::from_raw`]) +/// has been provided. Most of the time, this should not be necessary, and users +/// should accept `FfiStr` in the parameter list directly. +/// +/// ## Caveats +/// +/// An effort has been made to make this struct hard to misuse, however it is +/// still possible, if the `'static` lifetime is manually specified in the +/// struct. E.g. +/// +/// ```rust,no_run +/// # use ffi_support::FfiStr; +/// // NEVER DO THIS +/// #[no_mangle] +/// extern "C" fn never_do_this(s: FfiStr<'static>) { +/// // save `s` somewhere, and access it after this +/// // function returns. +/// } +/// ``` +/// +/// Instead, one of the following patterns should be used: +/// +/// ``` +/// # use ffi_support::FfiStr; +/// #[no_mangle] +/// extern "C" fn valid_use_1(s: FfiStr<'_>) { +/// // Use of `s` after this function returns is impossible +/// } +/// // Alternative: +/// #[no_mangle] +/// extern "C" fn valid_use_2(s: FfiStr) { +/// // Use of `s` after this function returns is impossible +/// } +/// ``` +#[repr(transparent)] +pub struct FfiStr<'a> { + cstr: *const c_char, + _boo: PhantomData<&'a ()>, +} + +impl<'a> FfiStr<'a> { + /// Construct an `FfiStr` from a raw pointer. + /// + /// This should not be needed most of the time, and users should instead + /// accept `FfiStr` in function parameter lists. + /// + /// # Safety + /// + /// Dereferences a pointer and is thus unsafe. + #[inline] + pub unsafe fn from_raw(ptr: *const c_char) -> Self { + Self { + cstr: ptr, + _boo: PhantomData, + } + } + + /// Construct a FfiStr from a `std::ffi::CStr`. This is provided for + /// completeness, as a safe method of producing an `FfiStr` in Rust. + #[inline] + pub fn from_cstr(cstr: &'a CStr) -> Self { + Self { + cstr: cstr.as_ptr(), + _boo: PhantomData, + } + } + + /// Get an `&str` out of the `FfiStr`. This will panic in any case that + /// [`FfiStr::as_opt_str`] would return `None` (e.g. null pointer or invalid + /// UTF-8). + /// + /// If the string should be optional, you should use [`FfiStr::as_opt_str`] + /// instead. If an owned string is desired, use [`FfiStr::into_string`] or + /// [`FfiStr::into_opt_string`]. + #[inline] + pub fn as_str(&self) -> &'a str { + self.as_opt_str() + .expect("Unexpected null string pointer passed to rust") + } + + /// Get an `Option<&str>` out of the `FfiStr`. If this stores a null + /// pointer, then None will be returned. If a string containing invalid + /// UTF-8 was passed, then an error will be logged and `None` will be + /// returned. + /// + /// If the string is a required argument, use [`FfiStr::as_str`], or + /// [`FfiStr::into_string`] instead. If `Option<String>` is desired, use + /// [`FfiStr::into_opt_string`] (which will handle invalid UTF-8 by + /// replacing with the replacement character). + pub fn as_opt_str(&self) -> Option<&'a str> { + if self.cstr.is_null() { + return None; + } + unsafe { + match std::ffi::CStr::from_ptr(self.cstr).to_str() { + Ok(s) => Some(s), + Err(e) => { + log::error!("Invalid UTF-8 was passed to rust! {:?}", e); + None + } + } + } + } + + /// Get an `Option<String>` out of the `FfiStr`. Returns `None` if this + /// `FfiStr` holds a null pointer. Note that unlike [`FfiStr::as_opt_str`], + /// invalid UTF-8 is replaced with the replacement character instead of + /// causing us to return None. + /// + /// If the string should be mandatory, you should use + /// [`FfiStr::into_string`] instead. If an owned string is not needed, you + /// may want to use [`FfiStr::as_str`] or [`FfiStr::as_opt_str`] instead, + /// (however, note the differences in how invalid UTF-8 is handled, should + /// this be relevant to your use). + pub fn into_opt_string(self) -> Option<String> { + if !self.cstr.is_null() { + unsafe { Some(CStr::from_ptr(self.cstr).to_string_lossy().to_string()) } + } else { + None + } + } + + /// Get a `String` out of a `FfiStr`. This function is essential a + /// convenience wrapper for `ffi_str.into_opt_string().unwrap()`, with a + /// message that indicates that a null argument was passed to rust when it + /// should be mandatory. As with [`FfiStr::into_opt_string`], invalid UTF-8 + /// is replaced with the replacement character if encountered. + /// + /// If the string should *not* be mandatory, you should use + /// [`FfiStr::into_opt_string`] instead. If an owned string is not needed, + /// you may want to use [`FfiStr::as_str`] or [`FfiStr::as_opt_str`] + /// instead, (however, note the differences in how invalid UTF-8 is handled, + /// should this be relevant to your use). + #[inline] + pub fn into_string(self) -> String { + self.into_opt_string() + .expect("Unexpected null string pointer passed to rust") + } +} + +impl<'a> std::fmt::Debug for FfiStr<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(s) = self.as_opt_str() { + write!(f, "FfiStr({:?})", s) + } else { + write!(f, "FfiStr(null)") + } + } +} + +// Conversions... + +impl<'a> From<FfiStr<'a>> for String { + #[inline] + fn from(f: FfiStr<'a>) -> Self { + f.into_string() + } +} + +impl<'a> From<FfiStr<'a>> for Option<String> { + #[inline] + fn from(f: FfiStr<'a>) -> Self { + f.into_opt_string() + } +} + +impl<'a> From<FfiStr<'a>> for Option<&'a str> { + #[inline] + fn from(f: FfiStr<'a>) -> Self { + f.as_opt_str() + } +} + +impl<'a> From<FfiStr<'a>> for &'a str { + #[inline] + fn from(f: FfiStr<'a>) -> Self { + f.as_str() + } +} + +// TODO: `AsRef<str>`? + +// Comparisons... + +// Compare FfiStr with eachother +impl<'a> PartialEq for FfiStr<'a> { + #[inline] + fn eq(&self, other: &FfiStr<'a>) -> bool { + self.as_opt_str() == other.as_opt_str() + } +} + +// Compare FfiStr with str +impl<'a> PartialEq<str> for FfiStr<'a> { + #[inline] + fn eq(&self, other: &str) -> bool { + self.as_opt_str() == Some(other) + } +} + +// Compare FfiStr with &str +impl<'a, 'b> PartialEq<&'b str> for FfiStr<'a> { + #[inline] + fn eq(&self, other: &&'b str) -> bool { + self.as_opt_str() == Some(*other) + } +} + +// rhs/lhs swap version of above +impl<'a> PartialEq<FfiStr<'a>> for str { + #[inline] + fn eq(&self, other: &FfiStr<'a>) -> bool { + Some(self) == other.as_opt_str() + } +} + +// rhs/lhs swap... +impl<'a, 'b> PartialEq<FfiStr<'a>> for &'b str { + #[inline] + fn eq(&self, other: &FfiStr<'a>) -> bool { + Some(*self) == other.as_opt_str() + } +} |