summaryrefslogtreecommitdiffstats
path: root/third_party/rust/ffi-support/src/ffistr.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/ffi-support/src/ffistr.rs')
-rw-r--r--third_party/rust/ffi-support/src/ffistr.rs252
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()
+ }
+}