ffi_support/ffistr.rs
1/* Copyright 2018-2019 Mozilla Foundation
2 *
3 * Licensed under the Apache License (Version 2.0), or the MIT license,
4 * (the "Licenses") at your option. You may not use this file except in
5 * compliance with one of the Licenses. You may obtain copies of the
6 * Licenses at:
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 * http://opensource.org/licenses/MIT
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the Licenses is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the Licenses for the specific language governing permissions and
15 * limitations under the Licenses. */
16
17use std::ffi::CStr;
18use std::marker::PhantomData;
19use std::os::raw::c_char;
20
21/// `FfiStr<'a>` is a safe (`#[repr(transparent)]`) wrapper around a
22/// nul-terminated `*const c_char` (e.g. a C string). Conceptually, it is
23/// similar to [`std::ffi::CStr`], except that it may be used in the signatures
24/// of extern "C" functions.
25///
26/// Functions accepting strings should use this instead of accepting a C string
27/// directly. This allows us to write those functions using safe code without
28/// allowing safe Rust to cause memory unsafety.
29///
30/// A single function for constructing these from Rust ([`FfiStr::from_raw`])
31/// has been provided. Most of the time, this should not be necessary, and users
32/// should accept `FfiStr` in the parameter list directly.
33///
34/// ## Caveats
35///
36/// An effort has been made to make this struct hard to misuse, however it is
37/// still possible, if the `'static` lifetime is manually specified in the
38/// struct. E.g.
39///
40/// ```rust,no_run
41/// # use ffi_support::FfiStr;
42/// // NEVER DO THIS
43/// #[no_mangle]
44/// extern "C" fn never_do_this(s: FfiStr<'static>) {
45/// // save `s` somewhere, and access it after this
46/// // function returns.
47/// }
48/// ```
49///
50/// Instead, one of the following patterns should be used:
51///
52/// ```
53/// # use ffi_support::FfiStr;
54/// #[no_mangle]
55/// extern "C" fn valid_use_1(s: FfiStr<'_>) {
56/// // Use of `s` after this function returns is impossible
57/// }
58/// // Alternative:
59/// #[no_mangle]
60/// extern "C" fn valid_use_2(s: FfiStr) {
61/// // Use of `s` after this function returns is impossible
62/// }
63/// ```
64#[repr(transparent)]
65pub struct FfiStr<'a> {
66 cstr: *const c_char,
67 _boo: PhantomData<&'a ()>,
68}
69
70impl<'a> FfiStr<'a> {
71 /// Construct an `FfiStr` from a raw pointer.
72 ///
73 /// This should not be needed most of the time, and users should instead
74 /// accept `FfiStr` in function parameter lists.
75 ///
76 /// # Safety
77 ///
78 /// Dereferences a pointer and is thus unsafe.
79 #[inline]
80 pub unsafe fn from_raw(ptr: *const c_char) -> Self {
81 Self {
82 cstr: ptr,
83 _boo: PhantomData,
84 }
85 }
86
87 /// Construct a FfiStr from a `std::ffi::CStr`. This is provided for
88 /// completeness, as a safe method of producing an `FfiStr` in Rust.
89 #[inline]
90 pub fn from_cstr(cstr: &'a CStr) -> Self {
91 Self {
92 cstr: cstr.as_ptr(),
93 _boo: PhantomData,
94 }
95 }
96
97 /// Get an `&str` out of the `FfiStr`. This will panic in any case that
98 /// [`FfiStr::as_opt_str`] would return `None` (e.g. null pointer or invalid
99 /// UTF-8).
100 ///
101 /// If the string should be optional, you should use [`FfiStr::as_opt_str`]
102 /// instead. If an owned string is desired, use [`FfiStr::into_string`] or
103 /// [`FfiStr::into_opt_string`].
104 #[inline]
105 pub fn as_str(&self) -> &'a str {
106 self.as_opt_str()
107 .expect("Unexpected null string pointer passed to rust")
108 }
109
110 /// Get an `Option<&str>` out of the `FfiStr`. If this stores a null
111 /// pointer, then None will be returned. If a string containing invalid
112 /// UTF-8 was passed, then an error will be logged and `None` will be
113 /// returned.
114 ///
115 /// If the string is a required argument, use [`FfiStr::as_str`], or
116 /// [`FfiStr::into_string`] instead. If `Option<String>` is desired, use
117 /// [`FfiStr::into_opt_string`] (which will handle invalid UTF-8 by
118 /// replacing with the replacement character).
119 pub fn as_opt_str(&self) -> Option<&'a str> {
120 if self.cstr.is_null() {
121 return None;
122 }
123 unsafe {
124 match std::ffi::CStr::from_ptr(self.cstr).to_str() {
125 Ok(s) => Some(s),
126 Err(e) => {
127 log::error!("Invalid UTF-8 was passed to rust! {:?}", e);
128 None
129 }
130 }
131 }
132 }
133
134 /// Get an `Option<String>` out of the `FfiStr`. Returns `None` if this
135 /// `FfiStr` holds a null pointer. Note that unlike [`FfiStr::as_opt_str`],
136 /// invalid UTF-8 is replaced with the replacement character instead of
137 /// causing us to return None.
138 ///
139 /// If the string should be mandatory, you should use
140 /// [`FfiStr::into_string`] instead. If an owned string is not needed, you
141 /// may want to use [`FfiStr::as_str`] or [`FfiStr::as_opt_str`] instead,
142 /// (however, note the differences in how invalid UTF-8 is handled, should
143 /// this be relevant to your use).
144 pub fn into_opt_string(self) -> Option<String> {
145 if !self.cstr.is_null() {
146 unsafe { Some(CStr::from_ptr(self.cstr).to_string_lossy().to_string()) }
147 } else {
148 None
149 }
150 }
151
152 /// Get a `String` out of a `FfiStr`. This function is essential a
153 /// convenience wrapper for `ffi_str.into_opt_string().unwrap()`, with a
154 /// message that indicates that a null argument was passed to rust when it
155 /// should be mandatory. As with [`FfiStr::into_opt_string`], invalid UTF-8
156 /// is replaced with the replacement character if encountered.
157 ///
158 /// If the string should *not* be mandatory, you should use
159 /// [`FfiStr::into_opt_string`] instead. If an owned string is not needed,
160 /// you may want to use [`FfiStr::as_str`] or [`FfiStr::as_opt_str`]
161 /// instead, (however, note the differences in how invalid UTF-8 is handled,
162 /// should this be relevant to your use).
163 #[inline]
164 pub fn into_string(self) -> String {
165 self.into_opt_string()
166 .expect("Unexpected null string pointer passed to rust")
167 }
168}
169
170impl<'a> std::fmt::Debug for FfiStr<'a> {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 if let Some(s) = self.as_opt_str() {
173 write!(f, "FfiStr({:?})", s)
174 } else {
175 write!(f, "FfiStr(null)")
176 }
177 }
178}
179
180// Conversions...
181
182impl<'a> From<FfiStr<'a>> for String {
183 #[inline]
184 fn from(f: FfiStr<'a>) -> Self {
185 f.into_string()
186 }
187}
188
189impl<'a> From<FfiStr<'a>> for Option<String> {
190 #[inline]
191 fn from(f: FfiStr<'a>) -> Self {
192 f.into_opt_string()
193 }
194}
195
196impl<'a> From<FfiStr<'a>> for Option<&'a str> {
197 #[inline]
198 fn from(f: FfiStr<'a>) -> Self {
199 f.as_opt_str()
200 }
201}
202
203impl<'a> From<FfiStr<'a>> for &'a str {
204 #[inline]
205 fn from(f: FfiStr<'a>) -> Self {
206 f.as_str()
207 }
208}
209
210// TODO: `AsRef<str>`?
211
212// Comparisons...
213
214// Compare FfiStr with eachother
215impl<'a> PartialEq for FfiStr<'a> {
216 #[inline]
217 fn eq(&self, other: &FfiStr<'a>) -> bool {
218 self.as_opt_str() == other.as_opt_str()
219 }
220}
221
222// Compare FfiStr with str
223impl<'a> PartialEq<str> for FfiStr<'a> {
224 #[inline]
225 fn eq(&self, other: &str) -> bool {
226 self.as_opt_str() == Some(other)
227 }
228}
229
230// Compare FfiStr with &str
231impl<'a, 'b> PartialEq<&'b str> for FfiStr<'a> {
232 #[inline]
233 fn eq(&self, other: &&'b str) -> bool {
234 self.as_opt_str() == Some(*other)
235 }
236}
237
238// rhs/lhs swap version of above
239impl<'a> PartialEq<FfiStr<'a>> for str {
240 #[inline]
241 fn eq(&self, other: &FfiStr<'a>) -> bool {
242 Some(self) == other.as_opt_str()
243 }
244}
245
246// rhs/lhs swap...
247impl<'a, 'b> PartialEq<FfiStr<'a>> for &'b str {
248 #[inline]
249 fn eq(&self, other: &FfiStr<'a>) -> bool {
250 Some(*self) == other.as_opt_str()
251 }
252}