Skip to main content

spirv_cross2/
string.rs

1use crate::cell::AllocationDropGuard;
2use crate::SpirvCrossError;
3use std::borrow::Cow;
4use std::ffi::{c_char, CStr, CString};
5use std::fmt::{Debug, Display, Formatter};
6use std::ops::Deref;
7
8/// An immutable wrapper around a valid UTF-8 string whose memory contents
9/// may or may not be originating from FFI.
10///
11/// In most cases, users of this library do not need to worry about
12/// constructing a [`CompilerStr`]. All functions that take strings
13/// take `impl Into<CompilerStr<'_>>`, which converts automatically from
14/// [`&str`](str) and [`String`](String).
15///
16/// [`CompilerStr`] also implements [`Deref`](Deref) for [`&str`](str),
17/// so all immutable `str` methods are available.
18///
19/// # Allocation Behaviour
20/// If the string originated from FFI and is a valid nul-terminated
21/// C string, then the pointer to the string will be saved,
22/// such that when being read by FFI there are no extra allocations
23/// required.
24///
25/// If the provenance of the string is a `&str` with lifetime longer than `'a`,
26/// then an allocation will occur when passing the string to FFI.
27///
28/// If the provenance of the string is an owned Rust `String`, then an allocation
29/// will occur only if necessary to append a nul byte.
30///
31/// If the provenance of the string is a `&CStr`, or
32/// with lifetime longer than `'a`, then an allocation will not occur
33/// when passing the string to FFI.
34///
35/// Using [C-string literals](https://doc.rust-lang.org/edition-guide/rust-2021/c-string-literals.html)
36/// where possible can be used to avoid an allocation.
37pub struct CompilerStr<'a> {
38    pointer: Option<ContextPointer<'a>>,
39    cow: Cow<'a, str>,
40}
41
42// SAFETY: 
43// SpirvCrossContext is Send.
44// Once created, the ContextStr is immutable, so it is also sync.
45// cloning the string doesn't affect the memory, as long as it
46// is alive for 'a.
47//
48// * The pointed-to C string is immutable for the lifetime `'a` (enforced
49//   by the `from_ptr` contract and by tying `'a` to the immutable borrow
50//   of the `Compiler`, or by the drop-guard for `'static` strings).
51// * `AllocationDropGuard` only exposes `Clone` and `Drop` over the inner
52//   `Arc`; it never hands out a `&CrossAllocationCellInner`, so the
53//   context's interior mutability is never reached through a shared
54//   reference. 
55unsafe impl Send for CompilerStr<'_> {}
56unsafe impl Sync for CompilerStr<'_> {}
57
58impl Clone for CompilerStr<'_> {
59    fn clone(&self) -> Self {
60        Self {
61            pointer: self.pointer.clone(),
62            cow: self.cow.clone(),
63        }
64    }
65}
66
67pub(crate) enum ContextPointer<'a> {
68    FromContext {
69        // the lifetime of pointer should be 'a.
70        pointer: *const c_char,
71        context: AllocationDropGuard,
72    },
73    BorrowedCStr(&'a CStr),
74}
75
76impl ContextPointer<'_> {
77    pub const fn pointer(&self) -> *const c_char {
78        match self {
79            ContextPointer::FromContext { pointer, .. } => *pointer,
80            ContextPointer::BorrowedCStr(cstr) => cstr.as_ptr(),
81        }
82    }
83}
84
85impl Clone for ContextPointer<'_> {
86    fn clone(&self) -> Self {
87        match self {
88            ContextPointer::FromContext { pointer, context } => ContextPointer::FromContext {
89                pointer: *pointer,
90                context: context.clone(),
91            },
92            ContextPointer::BorrowedCStr(cstr) => ContextPointer::BorrowedCStr(cstr),
93        }
94    }
95}
96
97impl<'a> Display for CompilerStr<'a> {
98    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
99        write!(f, "{}", self.cow)
100    }
101}
102
103impl<'a> Debug for CompilerStr<'a> {
104    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
105        write!(f, "{:?}", self.cow)
106    }
107}
108
109pub(crate) enum MaybeOwnedCString<'a> {
110    Owned(CString),
111    Borrowed(ContextPointer<'a>),
112}
113
114impl MaybeOwnedCString<'_> {
115    /// Get a pointer to the C string.
116    ///
117    /// The pointer will be valid for the lifetime of `self`.
118    pub fn as_ptr(&self) -> *const c_char {
119        match self {
120            MaybeOwnedCString::Owned(c) => c.as_ptr(),
121            MaybeOwnedCString::Borrowed(ptr) => ptr.pointer(),
122        }
123    }
124}
125
126impl AsRef<str> for CompilerStr<'_> {
127    fn as_ref(&self) -> &str {
128        self.cow.as_ref()
129    }
130}
131
132impl Deref for CompilerStr<'_> {
133    type Target = str;
134
135    fn deref(&self) -> &Self::Target {
136        self.cow.deref()
137    }
138}
139
140impl<'a, 'b> PartialEq<CompilerStr<'a>> for CompilerStr<'b> {
141    fn eq(&self, other: &CompilerStr<'a>) -> bool {
142        self.cow.eq(&other.cow)
143    }
144}
145
146impl<'a, 'b> PartialEq<&'a str> for CompilerStr<'b> {
147    fn eq(&self, other: &&'a str) -> bool {
148        self.cow.eq(other)
149    }
150}
151
152impl<'b> PartialEq<CompilerStr<'b>> for &str {
153    fn eq(&self, other: &CompilerStr<'b>) -> bool {
154        self.eq(&other.cow)
155    }
156}
157
158impl PartialEq<str> for CompilerStr<'_> {
159    fn eq(&self, other: &str) -> bool {
160        self.cow.eq(other)
161    }
162}
163
164impl PartialEq<CompilerStr<'_>> for str {
165    fn eq(&self, other: &CompilerStr<'_>) -> bool {
166        self.eq(&other.cow)
167    }
168}
169
170impl PartialEq<CompilerStr<'_>> for String {
171    fn eq(&self, other: &CompilerStr<'_>) -> bool {
172        self.eq(&other.cow)
173    }
174}
175impl PartialEq<String> for CompilerStr<'_> {
176    fn eq(&self, other: &String) -> bool {
177        self.cow.eq(other)
178    }
179}
180
181impl Eq for CompilerStr<'_> {}
182
183impl From<String> for CompilerStr<'_> {
184    fn from(value: String) -> Self {
185        Self::from_string(value)
186    }
187}
188
189impl<'a> From<&'a str> for CompilerStr<'a> {
190    fn from(value: &'a str) -> Self {
191        Self::from_str(value)
192    }
193}
194
195impl<'a> From<&'a CStr> for CompilerStr<'a> {
196    fn from(value: &'a CStr) -> Self {
197        Self::from_cstr(value)
198    }
199}
200
201/// # Safety
202/// Returning `ContextStr<'a>` where `'a` is the lifetime of the
203/// [`SpirvCrossContext`](crate::SpirvCrossContext) is only correct if the
204/// string is borrow-owned by the context.
205///
206/// In most cases, the returned lifetime should be the lifetime of the mutable borrow,
207/// if returning a string from the [`Compiler`](crate::Compiler).
208///
209/// [`CompilerStr::from_ptr`] takes a context argument, and the context must be
210/// the source of provenance for the `ContextStr`.
211impl<'a> CompilerStr<'a> {
212    /// Wraps a raw C string with a safe C string wrapper.
213    ///
214    /// If the raw C string is valid UTF-8, a pointer to the string will be
215    /// kept around, which can be passed back to C at zero cost.
216    ///
217    /// # Safety
218    ///
219    /// * The memory pointed to by `ptr` must contain a valid nul terminator at the
220    ///   end of the string.
221    ///
222    /// * `ptr` must be valid for reads of bytes up to and including the nul terminator.
223    ///   This means in particular:
224    ///
225    ///     * The entire memory range of this `CStr` must be contained within a single allocated object!
226    ///     * `ptr` must be non-null even for a zero-length cstr.
227    ///
228    /// * The memory referenced by the returned `CStr` must not be mutated for
229    ///   the duration of lifetime `'a`.
230    ///
231    /// * The nul terminator must be within `isize::MAX` from `ptr`
232    ///
233    /// * The memory pointed to by `ptr` must be valid for the duration of the lifetime `'a`.
234    ///
235    ///  * THe provenance of `context.ptr` must be a superset of `ptr`.
236    /// # Caveat
237    ///
238    /// The lifetime for the returned slice is inferred from its usage. To prevent accidental misuse,
239    /// it's suggested to tie the lifetime to whichever source lifetime is safe in the context,
240    /// such as by providing a helper function taking the lifetime of a host value for the slice,
241    /// or by explicit annotation.
242    pub(crate) unsafe fn from_ptr<'b>(
243        ptr: *const c_char,
244        arena: AllocationDropGuard,
245    ) -> CompilerStr<'b>
246    where
247        'a: 'b,
248    {
249        let cstr = CStr::from_ptr(ptr);
250        let maybe = cstr.to_string_lossy();
251        if matches!(&maybe, &Cow::Borrowed(_)) {
252            Self {
253                pointer: Some(ContextPointer::FromContext {
254                    pointer: ptr,
255                    context: arena,
256                }),
257                cow: maybe,
258            }
259        } else {
260            Self {
261                pointer: None,
262                cow: maybe,
263            }
264        }
265    }
266
267    /// Wrap a Rust `&str`.
268    ///
269    /// This will allocate when exposing to C.
270    pub(crate) fn from_str(str: &'a str) -> Self {
271        Self {
272            pointer: None,
273            cow: Cow::Borrowed(str),
274        }
275    }
276
277    /// Wrap a Rust `String`.
278    ///
279    /// This will allocate when exposing to C.
280    pub(crate) fn from_string(str: String) -> Self {
281        Self {
282            pointer: None,
283            cow: Cow::Owned(str),
284        }
285    }
286
287    pub(crate) fn from_cstr(cstr: &'a CStr) -> Self {
288        Self {
289            pointer: Some(ContextPointer::BorrowedCStr(cstr)),
290            cow: cstr.to_string_lossy(),
291        }
292    }
293
294    /// Allocate if necessary, if not then return a pointer to the original cstring.
295    ///
296    /// The returned pointer will be valid for the lifetime `'a`.
297    pub(crate) fn into_cstring_ptr(self) -> Result<MaybeOwnedCString<'a>, SpirvCrossError> {
298        if let Some(ptr) = &self.pointer {
299            // this is either free or very cheap (Rc incr at most)
300            Ok(MaybeOwnedCString::Borrowed(ptr.clone()))
301        } else {
302            let cstring = match self.cow {
303                Cow::Borrowed(s) => CString::new(s.to_string()),
304                Cow::Owned(s) => CString::new(s),
305            };
306
307            match cstring {
308                Ok(cstring) => Ok(MaybeOwnedCString::Owned(cstring)),
309                Err(e) => {
310                    let string = e.into_vec();
311                    // SAFETY: This string *came* from UTF-8 as its source was the Cow,
312                    // which was preverified UTF-8.
313                    let string = unsafe { String::from_utf8_unchecked(string) };
314                    Err(SpirvCrossError::InvalidString(string))
315                }
316            }
317        }
318    }
319}
320
321#[cfg(test)]
322mod test {
323    use crate::string::CompilerStr;
324    use std::ffi::{c_char, CStr, CString};
325    use std::sync::Arc;
326
327    struct LifetimeContext(*mut c_char);
328    impl LifetimeContext {
329        pub fn new() -> Self {
330            let cstring = CString::new(String::from("hello")).unwrap().into_raw();
331
332            Self(cstring)
333        }
334    }
335
336    impl Drop for LifetimeContext {
337        fn drop(&mut self) {
338            unsafe {
339                drop(CString::from_raw(self.0));
340            }
341        }
342    }
343
344    // struct LifetimeTest<'a>(ContextRoot<'a, LifetimeContext>);
345    // impl<'a> LifetimeTest<'a> {
346    //     pub fn get(&self) -> ContextStr<'a, LifetimeContext> {
347    //         unsafe { ContextStr::from_ptr(self.0.as_ref().0, self.0.clone()) }
348    //     }
349    //
350    //     pub fn set(&mut self, cstr: ContextStr<'a, LifetimeContext>) {
351    //         println!("{:p}", cstr.into_cstring_ptr().unwrap().as_ptr())
352    //     }
353    // }
354    //
355    // #[test]
356    // fn test_string() {
357    //     // use std::borrow::BorrowMut;
358    //     let lc = LifetimeContext::new();
359    //     let ctx = ContextRoot::RefCounted(Arc::new(lc));
360    //
361    //     let mut lt = LifetimeTest(ctx);
362    //
363    //     // let mut lt = Rc::new(LifetimeTest(PhantomData));
364    //     let cstr = lt.get();
365    //     lt.set(cstr.clone());
366    //
367    //     let original_ptr = cstr.clone().into_cstring_ptr().unwrap().as_ptr();
368    //     drop(lt);
369    //
370    //     assert_eq!("hello", cstr.as_ref());
371    //     println!("{}", cstr);
372    //
373    //     assert_eq!(original_ptr as usize, cstr.as_ptr() as usize);
374    //     // lt.borrow_mut().set(cstr)
375    // }
376
377    // #[test]
378    // fn test_string_does_not_allocate() {
379    //     // one past the end
380    //     let mut test = String::with_capacity(6);
381    //     test.push_str("Hello");
382    //
383    //     let original_ptr = test.as_ptr() as usize;
384    //     let ctxstr = ContextStr::<LifetimeContext>::from(test);
385    //
386    //     let new_ptr = ctxstr.into_cstring_ptr().unwrap().as_ptr();
387    //     assert_eq!(original_ptr, new_ptr as usize);
388    //     // lt.borrow_mut().set(cstr)
389    // }
390    //
391    // #[test]
392    // fn test_str_does_allocate() {
393    //     let str = "Hello";
394    //     let original_ptr = str.as_ptr() as usize;
395    //     let ctxstr = ContextStr::<LifetimeContext>::from(str);
396    //
397    //     let new_ptr = ctxstr.into_cstring_ptr().unwrap().as_ptr();
398    //     assert_ne!(original_ptr, new_ptr as usize);
399    //     // lt.borrow_mut().set(cstr)
400    // }
401    //
402    // #[test]
403    // fn test_cstr_does_not_allocate() {
404    //     // can't use cstring literals until 1.77
405    //     let str = unsafe { CStr::from_ptr(b"Hello\0".as_ptr().cast()) };
406    //
407    //     let original_ptr = str.as_ptr() as usize;
408    //     let ctxstr = ContextStr::<LifetimeContext>::from(str);
409    //
410    //     let new_ptr = ctxstr.into_cstring_ptr().unwrap().as_ptr();
411    //     assert_eq!(original_ptr, new_ptr as usize);
412    //     // lt.borrow_mut().set(cstr)
413    // }
414}