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}