1use std::any::Any;
2use std::fmt::Write as _;
3use std::mem::MaybeUninit;
4use std::os::raw::{c_int, c_void};
5use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
6use std::ptr;
7use std::sync::Arc;
8
9use crate::error::{Error, Result};
10use crate::memory::MemoryState;
11use crate::util::{
12 check_stack, get_internal_metatable, get_internal_userdata, init_internal_metatable,
13 push_internal_userdata, push_string, push_table, rawset_field, to_string, TypeKey,
14 DESTRUCTED_USERDATA_METATABLE,
15};
16
17static WRAPPED_FAILURE_TYPE_KEY: u8 = 0;
18
19pub(crate) enum WrappedFailure {
20 None,
21 Error(Error),
22 Panic(Option<Box<dyn Any + Send + 'static>>),
23}
24
25impl TypeKey for WrappedFailure {
26 #[inline(always)]
27 fn type_key() -> *const c_void {
28 &WRAPPED_FAILURE_TYPE_KEY as *const u8 as *const c_void
29 }
30}
31
32impl WrappedFailure {
33 pub(crate) unsafe fn new_userdata(state: *mut ffi::lua_State) -> *mut Self {
34 #[cfg(feature = "luau")]
35 let ud = ffi::lua_newuserdata_t::<Self>(state);
36 #[cfg(not(feature = "luau"))]
37 let ud = ffi::lua_newuserdata(state, std::mem::size_of::<Self>()) as *mut Self;
38 ptr::write(ud, WrappedFailure::None);
39 ud
40 }
41}
42
43unsafe fn callback_error<F, R>(state: *mut ffi::lua_State, f: F) -> R
55where
56 F: FnOnce(c_int) -> Result<R>,
57{
58 let nargs = ffi::lua_gettop(state);
59
60 let extra_stack = if nargs < 2 { 2 - nargs } else { 1 };
62 ffi::luaL_checkstack(
63 state,
64 extra_stack,
65 cstr!("not enough stack space for callback error handling"),
66 );
67
68 let ud = WrappedFailure::new_userdata(state);
71 ffi::lua_rotate(state, 1, 1);
72
73 match catch_unwind(AssertUnwindSafe(|| f(nargs))) {
74 Ok(Ok(r)) => {
75 ffi::lua_remove(state, 1);
76 r
77 }
78 Ok(Err(err)) => {
79 ffi::lua_settop(state, 1);
80
81 let traceback = if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 {
83 ffi::luaL_traceback(state, state, ptr::null(), 0);
84 let traceback = to_string(state, -1);
85 ffi::lua_pop(state, 1);
86 traceback
87 } else {
88 "<not enough stack space for traceback>".to_string()
89 };
90 let cause = Arc::new(err);
91 let wrapped_error = WrappedFailure::Error(Error::CallbackError { traceback, cause });
92 ptr::write(ud, wrapped_error);
93 get_internal_metatable::<WrappedFailure>(state);
94 ffi::lua_setmetatable(state, -2);
95
96 ffi::lua_error(state)
97 }
98 Err(p) => {
99 ffi::lua_settop(state, 1);
100 ptr::write(ud, WrappedFailure::Panic(Some(p)));
101 get_internal_metatable::<WrappedFailure>(state);
102 ffi::lua_setmetatable(state, -2);
103 ffi::lua_error(state)
104 }
105 }
106}
107
108pub(crate) unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
115 mlua_debug_assert!(
116 err_code != ffi::LUA_OK && err_code != ffi::LUA_YIELD,
117 "pop_error called with non-error return code"
118 );
119
120 match get_internal_userdata::<WrappedFailure>(state, -1, ptr::null()).as_mut() {
121 Some(WrappedFailure::Error(err)) => {
122 ffi::lua_pop(state, 1);
123 err.clone()
124 }
125 Some(WrappedFailure::Panic(panic)) => {
126 if let Some(p) = panic.take() {
127 resume_unwind(p);
128 } else {
129 Error::PreviouslyResumedPanic
130 }
131 }
132 _ => {
133 let err_string = to_string(state, -1);
134 ffi::lua_pop(state, 1);
135
136 match err_code {
137 ffi::LUA_ERRRUN => Error::RuntimeError(err_string),
138 ffi::LUA_ERRSYNTAX => {
139 Error::SyntaxError {
140 incomplete_input: err_string.ends_with("<eof>") || err_string.ends_with("'<eof>'"),
143 message: err_string,
144 }
145 }
146 ffi::LUA_ERRERR => {
147 Error::RuntimeError(err_string)
152 }
153 ffi::LUA_ERRMEM => Error::MemoryError(err_string),
154 #[cfg(any(feature = "lua53", feature = "lua52"))]
155 ffi::LUA_ERRGCMM => Error::GarbageCollectorError(err_string),
156 _ => mlua_panic!("unrecognized lua error code"),
157 }
158 }
159 }
160}
161
162pub(crate) unsafe fn protect_lua_call(
169 state: *mut ffi::lua_State,
170 nargs: c_int,
171 f: unsafe extern "C-unwind" fn(*mut ffi::lua_State) -> c_int,
172) -> Result<()> {
173 let stack_start = ffi::lua_gettop(state) - nargs;
174
175 MemoryState::relax_limit_with(state, || {
176 ffi::lua_pushcfunction(state, error_traceback);
177 ffi::lua_pushcfunction(state, f);
178 });
179 if nargs > 0 {
180 ffi::lua_rotate(state, stack_start + 1, 2);
181 }
182
183 let ret = ffi::lua_pcall(state, nargs, ffi::LUA_MULTRET, stack_start + 1);
184 ffi::lua_remove(state, stack_start + 1);
185
186 if ret == ffi::LUA_OK {
187 Ok(())
188 } else {
189 Err(pop_error(state, ret))
190 }
191}
192
193pub(crate) unsafe fn protect_lua_closure<F, R>(
201 state: *mut ffi::lua_State,
202 nargs: c_int,
203 nresults: c_int,
204 f: F,
205) -> Result<R>
206where
207 F: Fn(*mut ffi::lua_State) -> R,
208 R: Copy,
209{
210 struct Params<F, R: Copy> {
211 function: F,
212 result: MaybeUninit<R>,
213 nresults: c_int,
214 }
215
216 unsafe extern "C-unwind" fn do_call<F, R>(state: *mut ffi::lua_State) -> c_int
217 where
218 F: Fn(*mut ffi::lua_State) -> R,
219 R: Copy,
220 {
221 let params = ffi::lua_touserdata(state, -1) as *mut Params<F, R>;
222 ffi::lua_pop(state, 1);
223
224 (*params).result.write(((*params).function)(state));
225
226 if (*params).nresults == ffi::LUA_MULTRET {
227 ffi::lua_gettop(state)
228 } else {
229 (*params).nresults
230 }
231 }
232
233 let stack_start = ffi::lua_gettop(state) - nargs;
234
235 MemoryState::relax_limit_with(state, || {
236 ffi::lua_pushcfunction(state, error_traceback);
237 ffi::lua_pushcfunction(state, do_call::<F, R>);
238 });
239 if nargs > 0 {
240 ffi::lua_rotate(state, stack_start + 1, 2);
241 }
242
243 let mut params = Params {
244 function: f,
245 result: MaybeUninit::uninit(),
246 nresults,
247 };
248
249 ffi::lua_pushlightuserdata(state, &mut params as *mut Params<F, R> as *mut c_void);
250 let ret = ffi::lua_pcall(state, nargs + 1, nresults, stack_start + 1);
251 ffi::lua_remove(state, stack_start + 1);
252
253 if ret == ffi::LUA_OK {
254 Ok(params.result.assume_init())
257 } else {
258 Err(pop_error(state, ret))
259 }
260}
261
262pub(crate) unsafe extern "C-unwind" fn error_traceback(state: *mut ffi::lua_State) -> c_int {
263 #[cfg(feature = "luau")]
266 if MemoryState::limit_reached(state) {
267 return 0;
268 }
269
270 if ffi::lua_checkstack(state, 2) == 0 {
271 return 1;
274 }
275
276 if get_internal_userdata::<WrappedFailure>(state, -1, ptr::null()).is_null() {
277 let s = ffi::luaL_tolstring(state, -1, ptr::null_mut());
278 if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 {
279 ffi::luaL_traceback(state, state, s, 0);
280 ffi::lua_remove(state, -2);
281 }
282 }
283
284 1
285}
286
287pub(crate) unsafe fn error_traceback_thread(state: *mut ffi::lua_State, thread: *mut ffi::lua_State) {
289 ffi::lua_xmove(thread, state, 1);
291
292 if get_internal_userdata::<WrappedFailure>(state, -1, ptr::null()).is_null() {
293 let s = ffi::luaL_tolstring(state, -1, ptr::null_mut());
294 if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 {
295 ffi::luaL_traceback(state, thread, s, 0);
296 ffi::lua_remove(state, -2);
297 }
298 }
299}
300
301pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
303 check_stack(state, 7)?;
304
305 static ERROR_PRINT_BUFFER_KEY: u8 = 0;
308
309 unsafe extern "C-unwind" fn error_tostring(state: *mut ffi::lua_State) -> c_int {
310 callback_error(state, |_| {
311 check_stack(state, 3)?;
312
313 let err_buf = match get_internal_userdata::<WrappedFailure>(state, -1, ptr::null()).as_ref() {
314 Some(WrappedFailure::Error(error)) => {
315 let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void;
316 ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key);
317 let err_buf = ffi::lua_touserdata(state, -1) as *mut String;
318 ffi::lua_pop(state, 2);
319
320 (*err_buf).clear();
321 let _ = write!(&mut (*err_buf), "{error}");
325 Ok(err_buf)
326 }
327 Some(WrappedFailure::Panic(Some(ref panic))) => {
328 let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void;
329 ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key);
330 let err_buf = ffi::lua_touserdata(state, -1) as *mut String;
331 (*err_buf).clear();
332 ffi::lua_pop(state, 2);
333
334 if let Some(msg) = panic.downcast_ref::<&str>() {
335 let _ = write!(&mut (*err_buf), "{msg}");
336 } else if let Some(msg) = panic.downcast_ref::<String>() {
337 let _ = write!(&mut (*err_buf), "{msg}");
338 } else {
339 let _ = write!(&mut (*err_buf), "<panic>");
340 };
341 Ok(err_buf)
342 }
343 Some(WrappedFailure::Panic(None)) => Err(Error::PreviouslyResumedPanic),
344 _ => {
345 Err(Error::UserDataTypeMismatch)
347 }
348 }?;
349
350 push_string(state, (*err_buf).as_bytes(), true)?;
351 (*err_buf).clear();
352
353 Ok(1)
354 })
355 }
356
357 init_internal_metatable::<WrappedFailure>(
358 state,
359 Some(|state| {
360 ffi::lua_pushcfunction(state, error_tostring);
361 rawset_field(state, -2, "__tostring")
362 }),
363 )?;
364
365 unsafe extern "C-unwind" fn destructed_error(state: *mut ffi::lua_State) -> c_int {
368 callback_error(state, |_| Err(Error::CallbackDestructed))
369 }
370
371 push_table(state, 0, 26, true)?;
372 ffi::lua_pushcfunction(state, destructed_error);
373 for &method in &[
374 "__add",
375 "__sub",
376 "__mul",
377 "__div",
378 "__mod",
379 "__pow",
380 "__unm",
381 #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
382 "__idiv",
383 #[cfg(any(feature = "lua54", feature = "lua53"))]
384 "__band",
385 #[cfg(any(feature = "lua54", feature = "lua53"))]
386 "__bor",
387 #[cfg(any(feature = "lua54", feature = "lua53"))]
388 "__bxor",
389 #[cfg(any(feature = "lua54", feature = "lua53"))]
390 "__bnot",
391 #[cfg(any(feature = "lua54", feature = "lua53"))]
392 "__shl",
393 #[cfg(any(feature = "lua54", feature = "lua53"))]
394 "__shr",
395 "__concat",
396 "__len",
397 "__eq",
398 "__lt",
399 "__le",
400 "__index",
401 "__newindex",
402 "__call",
403 "__tostring",
404 #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))]
405 "__pairs",
406 #[cfg(any(feature = "lua53", feature = "lua52", feature = "luajit52"))]
407 "__ipairs",
408 #[cfg(feature = "luau")]
409 "__iter",
410 #[cfg(feature = "lua54")]
411 "__close",
412 ] {
413 ffi::lua_pushvalue(state, -1);
414 rawset_field(state, -3, method)?;
415 }
416 ffi::lua_pop(state, 1);
417
418 protect_lua!(state, 1, 0, fn(state) {
419 let destructed_mt_key = &DESTRUCTED_USERDATA_METATABLE as *const u8 as *const c_void;
420 ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, destructed_mt_key);
421 })?;
422
423 init_internal_metatable::<String>(state, None)?;
425 push_internal_userdata(state, String::new(), true)?;
426 protect_lua!(state, 1, 0, fn(state) {
427 let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void;
428 ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key);
429 })?;
430
431 Ok(())
432}