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}