mlua_codemp_patch/util/
mod.rs

1use std::borrow::Cow;
2use std::ffi::CStr;
3use std::os::raw::{c_char, c_int};
4use std::{ptr, slice, str};
5
6use crate::error::{Error, Result};
7
8pub(crate) use error::{
9    error_traceback, error_traceback_thread, init_error_registry, pop_error, protect_lua_call,
10    protect_lua_closure, WrappedFailure,
11};
12pub(crate) use short_names::short_type_name;
13pub(crate) use types::TypeKey;
14pub(crate) use userdata::{
15    get_destructed_userdata_metatable, get_internal_metatable, get_internal_userdata, get_userdata,
16    init_internal_metatable, init_userdata_metatable, push_internal_userdata, take_userdata,
17    DESTRUCTED_USERDATA_METATABLE,
18};
19
20#[cfg(not(feature = "lua54"))]
21pub(crate) use userdata::push_userdata;
22#[cfg(feature = "lua54")]
23pub(crate) use userdata::push_userdata_uv;
24
25#[cfg(not(feature = "luau"))]
26pub(crate) use userdata::userdata_destructor;
27
28// Checks that Lua has enough free stack space for future stack operations. On failure, this will
29// panic with an internal error message.
30#[inline]
31pub(crate) unsafe fn assert_stack(state: *mut ffi::lua_State, amount: c_int) {
32    // TODO: This should only be triggered when there is a logic error in `mlua`. In the future,
33    // when there is a way to be confident about stack safety and test it, this could be enabled
34    // only when `cfg!(debug_assertions)` is true.
35    mlua_assert!(ffi::lua_checkstack(state, amount) != 0, "out of stack space");
36}
37
38// Checks that Lua has enough free stack space and returns `Error::StackError` on failure.
39#[inline]
40pub(crate) unsafe fn check_stack(state: *mut ffi::lua_State, amount: c_int) -> Result<()> {
41    if ffi::lua_checkstack(state, amount) == 0 {
42        Err(Error::StackError)
43    } else {
44        Ok(())
45    }
46}
47
48pub(crate) struct StackGuard {
49    state: *mut ffi::lua_State,
50    top: c_int,
51}
52
53impl StackGuard {
54    // Creates a StackGuard instance with record of the stack size, and on Drop will check the
55    // stack size and drop any extra elements. If the stack size at the end is *smaller* than at
56    // the beginning, this is considered a fatal logic error and will result in a panic.
57    #[inline]
58    pub(crate) unsafe fn new(state: *mut ffi::lua_State) -> StackGuard {
59        StackGuard {
60            state,
61            top: ffi::lua_gettop(state),
62        }
63    }
64
65    // Same as `new()`, but allows specifying the expected stack size at the end of the scope.
66    #[inline]
67    pub(crate) fn with_top(state: *mut ffi::lua_State, top: c_int) -> StackGuard {
68        StackGuard { state, top }
69    }
70}
71
72impl Drop for StackGuard {
73    fn drop(&mut self) {
74        unsafe {
75            let top = ffi::lua_gettop(self.state);
76            if top < self.top {
77                mlua_panic!("{} too many stack values popped", self.top - top)
78            }
79            if top > self.top {
80                ffi::lua_settop(self.state, self.top);
81            }
82        }
83    }
84}
85
86// Uses 3 (or 1 if unprotected) stack spaces, does not call checkstack.
87#[inline(always)]
88pub(crate) unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: bool) -> Result<()> {
89    // Always use protected mode if the string is too long
90    if protect || s.len() > (1 << 30) {
91        protect_lua!(state, 0, 1, |state| {
92            ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len());
93        })
94    } else {
95        ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len());
96        Ok(())
97    }
98}
99
100// Uses 3 stack spaces (when protect), does not call checkstack.
101#[cfg(feature = "luau")]
102#[inline(always)]
103pub(crate) unsafe fn push_buffer(state: *mut ffi::lua_State, b: &[u8], protect: bool) -> Result<()> {
104    let data = if protect {
105        protect_lua!(state, 0, 1, |state| ffi::lua_newbuffer(state, b.len()))?
106    } else {
107        ffi::lua_newbuffer(state, b.len())
108    };
109    let buf = slice::from_raw_parts_mut(data as *mut u8, b.len());
110    buf.copy_from_slice(b);
111    Ok(())
112}
113
114// Uses 3 stack spaces, does not call checkstack.
115#[inline]
116pub(crate) unsafe fn push_table(
117    state: *mut ffi::lua_State,
118    narr: usize,
119    nrec: usize,
120    protect: bool,
121) -> Result<()> {
122    let narr: c_int = narr.try_into().unwrap_or(c_int::MAX);
123    let nrec: c_int = nrec.try_into().unwrap_or(c_int::MAX);
124    if protect {
125        protect_lua!(state, 0, 1, |state| ffi::lua_createtable(state, narr, nrec))
126    } else {
127        ffi::lua_createtable(state, narr, nrec);
128        Ok(())
129    }
130}
131
132// Uses 4 stack spaces, does not call checkstack.
133pub(crate) unsafe fn rawset_field(state: *mut ffi::lua_State, table: c_int, field: &str) -> Result<()> {
134    ffi::lua_pushvalue(state, table);
135    protect_lua!(state, 2, 0, |state| {
136        ffi::lua_pushlstring(state, field.as_ptr() as *const c_char, field.len());
137        ffi::lua_rotate(state, -3, 2);
138        ffi::lua_rawset(state, -3);
139    })
140}
141
142// A variant of `pcall` that does not allow Lua to catch Rust panics from `callback_error`.
143pub(crate) unsafe extern "C-unwind" fn safe_pcall(state: *mut ffi::lua_State) -> c_int {
144    ffi::luaL_checkstack(state, 2, ptr::null());
145
146    let top = ffi::lua_gettop(state);
147    if top == 0 {
148        ffi::lua_pushstring(state, cstr!("not enough arguments to pcall"));
149        ffi::lua_error(state);
150    }
151
152    if ffi::lua_pcall(state, top - 1, ffi::LUA_MULTRET, 0) == ffi::LUA_OK {
153        ffi::lua_pushboolean(state, 1);
154        ffi::lua_insert(state, 1);
155        ffi::lua_gettop(state)
156    } else {
157        let wf_ud = get_internal_userdata::<WrappedFailure>(state, -1, ptr::null());
158        if let Some(WrappedFailure::Panic(_)) = wf_ud.as_ref() {
159            ffi::lua_error(state);
160        }
161        ffi::lua_pushboolean(state, 0);
162        ffi::lua_insert(state, -2);
163        2
164    }
165}
166
167// A variant of `xpcall` that does not allow Lua to catch Rust panics from `callback_error`.
168pub(crate) unsafe extern "C-unwind" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int {
169    unsafe extern "C-unwind" fn xpcall_msgh(state: *mut ffi::lua_State) -> c_int {
170        ffi::luaL_checkstack(state, 2, ptr::null());
171
172        let wf_ud = get_internal_userdata::<WrappedFailure>(state, -1, ptr::null());
173        if let Some(WrappedFailure::Panic(_)) = wf_ud.as_ref() {
174            1
175        } else {
176            ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1));
177            ffi::lua_insert(state, 1);
178            ffi::lua_call(state, ffi::lua_gettop(state) - 1, ffi::LUA_MULTRET);
179            ffi::lua_gettop(state)
180        }
181    }
182
183    ffi::luaL_checkstack(state, 2, ptr::null());
184
185    let top = ffi::lua_gettop(state);
186    if top < 2 {
187        ffi::lua_pushstring(state, cstr!("not enough arguments to xpcall"));
188        ffi::lua_error(state);
189    }
190
191    ffi::lua_pushvalue(state, 2);
192    ffi::lua_pushcclosure(state, xpcall_msgh, 1);
193    ffi::lua_copy(state, 1, 2);
194    ffi::lua_replace(state, 1);
195
196    if ffi::lua_pcall(state, ffi::lua_gettop(state) - 2, ffi::LUA_MULTRET, 1) == ffi::LUA_OK {
197        ffi::lua_pushboolean(state, 1);
198        ffi::lua_insert(state, 2);
199        ffi::lua_gettop(state) - 1
200    } else {
201        let wf_ud = get_internal_userdata::<WrappedFailure>(state, -1, ptr::null());
202        if let Some(WrappedFailure::Panic(_)) = wf_ud.as_ref() {
203            ffi::lua_error(state);
204        }
205        ffi::lua_pushboolean(state, 0);
206        ffi::lua_insert(state, -2);
207        2
208    }
209}
210
211// Returns Lua main thread for Lua >= 5.2 or checks that the passed thread is main for Lua 5.1.
212// Does not call lua_checkstack, uses 1 stack space.
213pub(crate) unsafe fn get_main_state(state: *mut ffi::lua_State) -> Option<*mut ffi::lua_State> {
214    #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
215    {
216        ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_MAINTHREAD);
217        let main_state = ffi::lua_tothread(state, -1);
218        ffi::lua_pop(state, 1);
219        Some(main_state)
220    }
221    #[cfg(any(feature = "lua51", feature = "luajit"))]
222    {
223        // Check the current state first
224        let is_main_state = ffi::lua_pushthread(state) == 1;
225        ffi::lua_pop(state, 1);
226        if is_main_state {
227            Some(state)
228        } else {
229            None
230        }
231    }
232    #[cfg(feature = "luau")]
233    Some(ffi::lua_mainthread(state))
234}
235
236// Converts the given lua value to a string in a reasonable format without causing a Lua error or
237// panicking.
238pub(crate) unsafe fn to_string(state: *mut ffi::lua_State, index: c_int) -> String {
239    match ffi::lua_type(state, index) {
240        ffi::LUA_TNONE => "<none>".to_string(),
241        ffi::LUA_TNIL => "<nil>".to_string(),
242        ffi::LUA_TBOOLEAN => (ffi::lua_toboolean(state, index) != 1).to_string(),
243        ffi::LUA_TLIGHTUSERDATA => {
244            format!("<lightuserdata {:?}>", ffi::lua_topointer(state, index))
245        }
246        ffi::LUA_TNUMBER => {
247            let mut isint = 0;
248            let i = ffi::lua_tointegerx(state, -1, &mut isint);
249            if isint == 0 {
250                ffi::lua_tonumber(state, index).to_string()
251            } else {
252                i.to_string()
253            }
254        }
255        #[cfg(feature = "luau")]
256        ffi::LUA_TVECTOR => {
257            let v = ffi::lua_tovector(state, index);
258            mlua_debug_assert!(!v.is_null(), "vector is null");
259            let (x, y, z) = (*v, *v.add(1), *v.add(2));
260            #[cfg(not(feature = "luau-vector4"))]
261            return format!("vector({x}, {y}, {z})");
262            #[cfg(feature = "luau-vector4")]
263            return format!("vector({x}, {y}, {z}, {w})", w = *v.add(3));
264        }
265        ffi::LUA_TSTRING => {
266            let mut size = 0;
267            // This will not trigger a 'm' error, because the reference is guaranteed to be of
268            // string type
269            let data = ffi::lua_tolstring(state, index, &mut size);
270            String::from_utf8_lossy(slice::from_raw_parts(data as *const u8, size)).into_owned()
271        }
272        ffi::LUA_TTABLE => format!("<table {:?}>", ffi::lua_topointer(state, index)),
273        ffi::LUA_TFUNCTION => format!("<function {:?}>", ffi::lua_topointer(state, index)),
274        ffi::LUA_TUSERDATA => format!("<userdata {:?}>", ffi::lua_topointer(state, index)),
275        ffi::LUA_TTHREAD => format!("<thread {:?}>", ffi::lua_topointer(state, index)),
276        #[cfg(feature = "luau")]
277        ffi::LUA_TBUFFER => format!("<buffer {:?}>", ffi::lua_topointer(state, index)),
278        #[cfg(feature = "luajit")]
279        ffi::LUA_TCDATA => format!("<cdata {:?}>", ffi::lua_topointer(state, index)),
280        _ => "<unknown>".to_string(),
281    }
282}
283
284pub(crate) unsafe fn ptr_to_str<'a>(input: *const c_char) -> Option<&'a str> {
285    if input.is_null() {
286        return None;
287    }
288    str::from_utf8(CStr::from_ptr(input).to_bytes()).ok()
289}
290
291pub(crate) unsafe fn ptr_to_lossy_str<'a>(input: *const c_char) -> Option<Cow<'a, str>> {
292    if input.is_null() {
293        return None;
294    }
295    Some(String::from_utf8_lossy(CStr::from_ptr(input).to_bytes()))
296}
297
298pub(crate) fn linenumber_to_usize(n: c_int) -> Option<usize> {
299    match n {
300        n if n < 0 => None,
301        n => Some(n as usize),
302    }
303}
304
305mod error;
306mod short_names;
307mod types;
308mod userdata;