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 17 use std::ffi::CStr; 18 use std::marker::PhantomData; 19 use 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)] 65 pub struct FfiStr<'a> { 66 cstr: *const c_char, 67 _boo: PhantomData<&'a ()>, 68 } 69 70 impl<'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] from_raw(ptr: *const c_char) -> Self80 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] from_cstr(cstr: &'a CStr) -> Self90 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] as_str(&self) -> &'a str105 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). as_opt_str(&self) -> Option<&'a str>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). into_opt_string(self) -> Option<String>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] into_string(self) -> String164 pub fn into_string(self) -> String { 165 self.into_opt_string() 166 .expect("Unexpected null string pointer passed to rust") 167 } 168 } 169 170 impl<'a> std::fmt::Debug for FfiStr<'a> { fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result171 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 182 impl<'a> From<FfiStr<'a>> for String { 183 #[inline] from(f: FfiStr<'a>) -> Self184 fn from(f: FfiStr<'a>) -> Self { 185 f.into_string() 186 } 187 } 188 189 impl<'a> From<FfiStr<'a>> for Option<String> { 190 #[inline] from(f: FfiStr<'a>) -> Self191 fn from(f: FfiStr<'a>) -> Self { 192 f.into_opt_string() 193 } 194 } 195 196 impl<'a> From<FfiStr<'a>> for Option<&'a str> { 197 #[inline] from(f: FfiStr<'a>) -> Self198 fn from(f: FfiStr<'a>) -> Self { 199 f.as_opt_str() 200 } 201 } 202 203 impl<'a> From<FfiStr<'a>> for &'a str { 204 #[inline] from(f: FfiStr<'a>) -> Self205 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 215 impl<'a> PartialEq for FfiStr<'a> { 216 #[inline] eq(&self, other: &FfiStr<'a>) -> bool217 fn eq(&self, other: &FfiStr<'a>) -> bool { 218 self.as_opt_str() == other.as_opt_str() 219 } 220 } 221 222 // Compare FfiStr with str 223 impl<'a> PartialEq<str> for FfiStr<'a> { 224 #[inline] eq(&self, other: &str) -> bool225 fn eq(&self, other: &str) -> bool { 226 self.as_opt_str() == Some(other) 227 } 228 } 229 230 // Compare FfiStr with &str 231 impl<'a, 'b> PartialEq<&'b str> for FfiStr<'a> { 232 #[inline] eq(&self, other: &&'b str) -> bool233 fn eq(&self, other: &&'b str) -> bool { 234 self.as_opt_str() == Some(*other) 235 } 236 } 237 238 // rhs/lhs swap version of above 239 impl<'a> PartialEq<FfiStr<'a>> for str { 240 #[inline] eq(&self, other: &FfiStr<'a>) -> bool241 fn eq(&self, other: &FfiStr<'a>) -> bool { 242 Some(self) == other.as_opt_str() 243 } 244 } 245 246 // rhs/lhs swap... 247 impl<'a, 'b> PartialEq<FfiStr<'a>> for &'b str { 248 #[inline] eq(&self, other: &FfiStr<'a>) -> bool249 fn eq(&self, other: &FfiStr<'a>) -> bool { 250 Some(*self) == other.as_opt_str() 251 } 252 } 253