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