Skip to main content

rtea/
tcl.rs

1//! This module wraps all of the TCL functions that need to static references
2//! for object lifecycle management task to work.  Specifically, these are
3//! functions which may need to be called in contexts where an interpreter
4//! pointer is either not known (objects & object types) or immediately
5//! available (methods on objects).
6
7use std::ffi::c_void;
8use std::mem::ManuallyDrop;
9use std::ops::{Deref, DerefMut};
10use std::os::raw::c_char;
11
12use crate::Interpreter;
13use crate::ObjectType;
14use crate::RawObject;
15
16pub(crate) static mut ALLOC: Option<extern "C" fn(usize) -> *mut c_void> = None;
17pub(crate) static mut REALLOC: Option<extern "C" fn(*mut c_void, usize) -> *mut c_void> = None;
18pub(crate) static mut FREE: Option<extern "C" fn(*mut c_void)> = None;
19
20pub(crate) static mut NEW_OBJ: Option<extern "C" fn() -> *mut RawObject> = None;
21pub(crate) static mut DUPLICATE_OBJ: Option<extern "C" fn(*mut RawObject) -> *mut RawObject> = None;
22pub(crate) static mut INCR_REF_COUNT: Option<extern "C" fn(*mut RawObject)> = None;
23pub(crate) static mut DECR_REF_COUNT: Option<extern "C" fn(*mut RawObject)> = None;
24pub(crate) static mut IS_SHARED: Option<extern "C" fn(*mut RawObject) -> i32> = None;
25pub(crate) static mut INVALIDATE_STRING_REP: Option<extern "C" fn(*mut RawObject)> = None;
26pub(crate) static mut GET_STRING: Option<extern "C" fn(*mut RawObject) -> *mut c_char> = None;
27
28pub(crate) static mut GET_OBJ_TYPE: Option<extern "C" fn(*const c_char) -> *const ObjectType> =
29    None;
30pub(crate) static mut CONVERT_TO_TYPE: Option<
31    extern "C" fn(*const Interpreter, *mut RawObject, *const ObjectType) -> i32,
32> = None;
33
34pub(crate) static mut NEW_STRING_OBJ: Option<
35    extern "C" fn(*const c_char, usize) -> *mut RawObject,
36> = None;
37
38pub(crate) static mut SET_STRING_OBJ: Option<extern "C" fn(*mut RawObject, *const c_char, usize)> =
39    None;
40
41/// An owned buffer of Tcl-managed memory.
42///
43/// `TclBuf` is a RAII wrapper around a heap allocation made by Tcl's
44/// allocator.  When dropped it returns the memory to Tcl via `Tcl_Free`.
45///
46/// When the buffer must be handed to Tcl directly (e.g. assigning
47/// `RawObject::bytes`), call [`into_raw_parts`](TclBuf::into_raw_parts) to
48/// relinquish ownership without freeing the allocation.
49pub struct TclBuf {
50    ptr: *mut u8,
51    /// The usable length of the buffer, **excluding** any null terminator.
52    len: usize,
53}
54
55impl TclBuf {
56    /// Constructs a `TclBuf` from a raw pointer and length.
57    ///
58    /// # Safety
59    ///
60    /// `ptr` must point to a live allocation made by Tcl's allocator of at
61    /// least `len` bytes, and the caller must transfer ownership to this
62    /// `TclBuf`.
63    pub(crate) unsafe fn from_raw_parts(ptr: *mut u8, len: usize) -> Self {
64        Self { ptr, len }
65    }
66
67    /// Returns the number of usable bytes in the buffer.
68    pub fn len(&self) -> usize {
69        self.len
70    }
71
72    /// Returns `true` if the buffer contains no usable bytes.
73    pub fn is_empty(&self) -> bool {
74        self.len == 0
75    }
76
77    /// Relinquishes ownership of the allocation, returning the raw pointer
78    /// and length so they can be passed to Tcl directly.
79    ///
80    /// The returned pointer is cast to `*mut c_char` as required by Tcl's
81    /// `Tcl_Obj::bytes` field.  The caller is responsible for ensuring the
82    /// memory is eventually freed (typically Tcl does this automatically when
83    /// it invalidates a string representation).
84    pub fn into_raw_parts(self) -> (*mut c_char, usize) {
85        let md = ManuallyDrop::new(self);
86        (md.ptr as *mut c_char, md.len)
87    }
88}
89
90impl Drop for TclBuf {
91    fn drop(&mut self) {
92        unsafe { FREE.expect("module must have been initialized")(self.ptr as *mut c_void) }
93    }
94}
95
96impl Deref for TclBuf {
97    type Target = [u8];
98    fn deref(&self) -> &[u8] {
99        unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
100    }
101}
102
103impl DerefMut for TclBuf {
104    fn deref_mut(&mut self) -> &mut [u8] {
105        unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
106    }
107}
108
109// Safety: the underlying allocation is not aliased and Tcl's allocator is
110// thread-safe, so TclBuf can safely be moved across threads.
111unsafe impl Send for TclBuf {}
112
113/// Allocates a null-terminated copy of `rust_str` in Tcl-managed memory and
114/// returns it as a [`TclBuf`].
115///
116/// The returned buffer's [`len`](TclBuf::len) reflects the string's byte
117/// length, **not** including the null terminator.  Use
118/// [`into_raw_parts`](TclBuf::into_raw_parts) when the raw pointer and length
119/// are needed for Tcl's object API.
120pub fn tcl_string(rust_str: &str) -> TclBuf {
121    let alloc_len = rust_str.len() + 1; // +1 for null terminator
122    unsafe {
123        let ptr = ALLOC.expect("module not initialized")(alloc_len) as *mut u8;
124        let slice = std::slice::from_raw_parts_mut(ptr, alloc_len);
125        slice[..rust_str.len()].copy_from_slice(rust_str.as_bytes());
126        *slice.last_mut().expect("alloc_len is always >= 1") = 0;
127        TclBuf::from_raw_parts(ptr, rust_str.len())
128    }
129}