Skip to main content

gc_alloc/
cstring.rs

1use std::{
2    ffi::{CStr, c_char},
3    fmt::Write,
4    ptr::NonNull,
5};
6
7use crate::{GcToken, gc};
8
9pub fn from_str(_token: &impl GcToken, s: &str) -> Result<GcCString, NulError> {
10    if let Some(pos) = s.find('\0') {
11        return Err(NulError(pos));
12    }
13    let ptr = alloc(s.len() + 1);
14    unsafe { std::ptr::copy_nonoverlapping(s.as_ptr() as *const c_char, ptr.as_ptr(), s.len()) };
15    unsafe { std::ptr::write(ptr.as_ptr().add(s.len()), 0) };
16    Ok(GcCString(unsafe { CStr::from_ptr(ptr.as_ptr()) }.into()))
17}
18
19pub fn from_cstr(_token: &impl GcToken, s: &CStr) -> GcCString {
20    let bytes = s.to_bytes_with_nul();
21    let ptr = alloc(bytes.len());
22    unsafe {
23        std::ptr::copy_nonoverlapping(bytes.as_ptr() as *const c_char, ptr.as_ptr(), bytes.len())
24    };
25    GcCString(unsafe { CStr::from_ptr(ptr.as_ptr()) }.into())
26}
27
28pub fn from_iter<I: IntoIterator<Item = char>>(
29    token: &impl GcToken,
30    iter: I,
31) -> Result<GcCString, I::IntoIter> {
32    let mut iter = iter.into_iter();
33    let (lower, _) = iter.size_hint();
34
35    let mut formatter = Formatter::with_capacity(token, lower);
36    while let Some(c) = iter.next() {
37        if formatter.write_char(c).is_err() {
38            return Err(iter);
39        }
40    }
41    Ok(formatter.finish())
42}
43
44pub struct Formatter {
45    buf: NonNull<c_char>,
46    len: usize,
47    cap: usize,
48}
49
50impl Formatter {
51    pub fn new(_token: &impl GcToken) -> Self {
52        Self {
53            buf: NonNull::dangling(),
54            len: 0,
55            cap: 0,
56        }
57    }
58
59    pub fn with_capacity(_token: &impl GcToken, cap: usize) -> Self {
60        let buf = if cap == 0 {
61            NonNull::dangling()
62        } else {
63            alloc(cap)
64        };
65        Self { buf, len: 0, cap }
66    }
67}
68
69impl Write for Formatter {
70    #[inline]
71    fn write_str(&mut self, s: &str) -> std::fmt::Result {
72        if s.find('\0').is_some() {
73            return Err(std::fmt::Error);
74        }
75
76        let mut cap = self.cap;
77        while self.len + s.len() > cap {
78            cap = cap.max(1).checked_mul(2).expect("Capacity overflow");
79        }
80        if cap != self.cap {
81            let new_buf = alloc(cap + 1);
82            unsafe { std::ptr::copy_nonoverlapping(self.buf.as_ptr(), new_buf.as_ptr(), self.len) };
83            self.buf = new_buf;
84            self.cap = cap;
85        }
86        unsafe {
87            std::ptr::copy_nonoverlapping(
88                s.as_ptr() as *const c_char,
89                self.buf.add(self.len).as_ptr(),
90                s.len(),
91            )
92        };
93        self.len += s.len();
94        Ok(())
95    }
96}
97
98impl Formatter {
99    pub fn finish(self) -> GcCString {
100        if self.len == 0 {
101            return GcCString(c"".into());
102        }
103        unsafe {
104            std::ptr::write(self.buf.add(self.len).as_ptr(), 0);
105            GcCString(CStr::from_ptr(self.buf.as_ptr() as *const i8).into())
106        }
107    }
108}
109
110#[macro_export]
111#[doc(hidden)]
112macro_rules! cformat {
113    ($token:expr, $($arg:tt)*) => {{
114        let mut formatter = $crate::cstring::Formatter::new($token);
115        std::fmt::write(&mut formatter, format_args!($($arg)*)).expect("Formatting failed");
116        formatter.finish()
117    }};
118}
119
120pub use cformat as format;
121
122pub struct GcCString(NonNull<CStr>);
123
124impl GcCString {
125    pub fn as_ptr(&self) -> *mut CStr {
126        self.0.as_ptr()
127    }
128
129    pub fn as_ref<'gc>(&self, _token: &'gc impl GcToken) -> &'gc CStr {
130        unsafe { &*self.as_ptr() }
131    }
132
133    #[allow(clippy::mut_from_ref)]
134    pub fn as_mut<'gc>(&mut self, _token: &'gc impl GcToken) -> &'gc mut CStr {
135        unsafe { &mut *self.as_ptr() }
136    }
137
138    /// # Safety
139    /// The returned reference cannot be used in a thread that is not registered with the GC.
140    pub unsafe fn as_ref_unconstrained(&self) -> &'static mut CStr {
141        unsafe { &mut *self.as_ptr() }
142    }
143}
144
145#[derive(Clone, Copy, PartialEq, Eq, Debug)]
146pub struct NulError(usize);
147
148impl NulError {
149    pub fn nul_position(&self) -> usize {
150        self.0
151    }
152}
153
154impl std::fmt::Display for NulError {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        write!(f, "nul byte found in provided data at position: {}", self.0)
157    }
158}
159
160impl std::error::Error for NulError {}
161
162fn alloc(cap: usize) -> NonNull<c_char> {
163    let ptr = unsafe { gc::GC_malloc_atomic(cap) as *mut c_char };
164    std::ptr::NonNull::new(ptr).expect("GC_malloc_atomic failed")
165}