From 061f84f702703f99c73f38fb2b8260c2dc753bc8 Mon Sep 17 00:00:00 2001 From: Michael Pfaff Date: Sun, 16 Jul 2023 14:39:35 -0400 Subject: [PATCH] Turn more comments into doc comments, improve debuggability --- mlua-sys/Cargo.toml | 3 +- mlua-sys/src/lib.rs | 8 +- mlua-sys/src/luau/compat.rs | 86 +++++++++++- mlua-sys/src/luau/lgc.rs | 77 ++++++++++ mlua-sys/src/luau/lobject.rs | 263 +++++++++++++++++++++++++++++++++++ mlua-sys/src/luau/lstate.rs | 216 ++++++++++++++++++++++++++++ mlua-sys/src/luau/ltable.rs | 5 + mlua-sys/src/luau/lua.rs | 41 +++--- mlua-sys/src/luau/luau.rs | 15 ++ mlua-sys/src/luau/mod.rs | 10 ++ mlua-sys/src/macros.rs | 10 ++ src/function.rs | 4 +- src/lib.rs | 2 +- src/lua.rs | 95 ++++++++----- src/luau.rs | 4 +- src/macros.rs | 10 +- src/multi.rs | 34 +++++ src/thread.rs | 8 +- src/types.rs | 2 +- src/util/mod.rs | 154 ++++++++++---------- src/util/short_names.rs | 2 +- src/value.rs | 6 + tests/function.rs | 2 +- 23 files changed, 898 insertions(+), 159 deletions(-) create mode 100644 mlua-sys/src/luau/lgc.rs create mode 100644 mlua-sys/src/luau/lobject.rs create mode 100644 mlua-sys/src/luau/lstate.rs create mode 100644 mlua-sys/src/luau/ltable.rs create mode 100644 mlua-sys/src/luau/luau.rs diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index ceabe97..29838a2 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -39,4 +39,5 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 546.0.0, < 550.0.0", optional = true } luajit-src = { version = ">= 210.4.0, < 220.0.0", optional = true } -luau0-src = { version = "0.5.10", optional = true } +#luau0-src = { version = "0.5.10", optional = true } +luau0-src = { path = "../../luau-src-rs", optional = true } diff --git a/mlua-sys/src/lib.rs b/mlua-sys/src/lib.rs index ea4967a..2fc9e17 100644 --- a/mlua-sys/src/lib.rs +++ b/mlua-sys/src/lib.rs @@ -7,16 +7,16 @@ use std::os::raw::c_int; -#[cfg(any(feature = "lua54", doc))] +#[cfg(any(feature = "lua54"))] pub use lua54::*; -#[cfg(any(feature = "lua53", doc))] +#[cfg(any(feature = "lua53"))] pub use lua53::*; -#[cfg(any(feature = "lua52", doc))] +#[cfg(any(feature = "lua52"))] pub use lua52::*; -#[cfg(any(feature = "lua51", feature = "luajit", doc))] +#[cfg(any(feature = "lua51", feature = "luajit"))] pub use lua51::*; #[cfg(any(feature = "luau", doc))] diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index bd777b5..7621768 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -30,6 +30,9 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) -> if level == 0 || lua_istable(L, -1) == 0 { return 0; // not found } + if lua_checkstack(L, 3) == 0 { + return 0; + } lua_pushnil(L); // start 'next' loop while lua_next(L, -2) != 0 { @@ -42,9 +45,8 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) -> return 1; } else if compat53_findfield(L, objidx, level - 1) != 0 { // try recursively - lua_remove(L, -2); // remove table (but keep name) lua_pushliteral(L, "."); - lua_insert(L, -2); // place '.' between the two names + lua_replace(L, -2); // remove table (but keep name), place '.' between the two names lua_concat(L, 3); return 1; } @@ -57,8 +59,9 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) -> unsafe fn compat53_pushglobalfuncname( L: *mut lua_State, level: c_int, - ar: *mut lua_Debug, + ar: &mut lua_Debug, ) -> c_int { + debug_assert_ne!(lua_checkstack(L, 2), 0); let top = lua_gettop(L); // push function lua_getinfo(L, level, cstr!("f"), ar); @@ -73,10 +76,11 @@ unsafe fn compat53_pushglobalfuncname( } } -unsafe fn compat53_pushfuncname(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) { - if !(*ar).name.is_null() { +unsafe fn compat53_pushfuncname(L: *mut lua_State, level: c_int, ar: &mut lua_Debug) { + debug_assert_ne!(lua_checkstack(L, 1), 0); + if !ar.name.is_null() { // is there a name? - lua_pushfstring(L, cstr!("function '%s'"), (*ar).name); + lua_pushfstring(L, cstr!("function '%s'"), ar.name); } else if compat53_pushglobalfuncname(L, level, ar) != 0 { lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1)); lua_remove(L, -2); // remove name @@ -85,6 +89,17 @@ unsafe fn compat53_pushfuncname(L: *mut lua_State, level: c_int, ar: *mut lua_De } } +unsafe fn compat53_pushfuncname_heap(_L: *mut lua_State, _level: c_int, ar: &mut lua_Debug, buf: &mut String) { + use std::fmt::Write; + + if !ar.name.is_null() { + // is there a name? + _ = write!(buf, "function {:?}", CStr::from_ptr(ar.name)); + } else { + buf.push('?'); + } +} + // // lua ported functions // @@ -416,10 +431,22 @@ pub unsafe fn luaL_traceback( 0 }; + debug_assert_ne!(lua_checkstack(L, 1), 0); + + //let stack_req = if msg.is_null() { 0 } else { 1 } + 4 * (mark+1); + let stack_req = if msg.is_null() { 0 } else { 1 } + 4; + + if lua_checkstack(L, stack_req) == 0 { + lua_pushliteral(L, "[not enough stack space]"); + return; + } + if !msg.is_null() { lua_pushfstring(L, cstr!("%s\n"), msg); } + lua_pushliteral(L, "stack traceback:"); + while lua_getinfo(L1, level, cstr!(""), &mut ar) != 0 { if level + 1 == mark { // too many levels? @@ -440,6 +467,51 @@ pub unsafe fn luaL_traceback( lua_concat(L, lua_gettop(L) - top); } +pub unsafe fn luaL_traceback_heap( + L: *mut lua_State, + L1: *mut lua_State, + msg: Option, + mut level: c_int, +) -> String { + use std::fmt::Write; + + let mut ar: lua_Debug = mem::zeroed(); + let numlevels = lua_stackdepth(L); + let mark = if numlevels > COMPAT53_LEVELS1 + COMPAT53_LEVELS2 { + COMPAT53_LEVELS1 + } else { + 0 + }; + + let mut buf = msg.unwrap_or_default(); + if !buf.is_empty() { + buf.push('\n'); + } + buf.push_str("stack traceback:"); + while lua_getinfo(L1, level, cstr!(""), &mut ar) != 0 { + if level + 1 == mark { + // too many levels? + buf.push_str("\n\t..."); // add a '...' + level = numlevels - COMPAT53_LEVELS2; // and skip to last ones + } else { + lua_getinfo(L1, level, cstr!("sln"), &mut ar); + let src = CStr::from_ptr(ar.short_src); + if let Ok(src) = src.to_str() { + _ = write!(buf, "\n\t{}:", src); + } else { + _ = write!(buf, "\n\t{:?}:", src); + } + if ar.currentline > 0 { + _ = write!(buf, "{}:", ar.currentline); + } + buf.push_str(" in "); + compat53_pushfuncname_heap(L, level, &mut ar, &mut buf); + } + level += 1; + } + buf +} + pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) -> *const c_char { idx = lua_absindex(L, idx); if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 { @@ -508,7 +580,7 @@ pub unsafe fn luaL_requiref( luaL_getsubtable(L, LUA_REGISTRYINDEX, cstr!("_LOADED")); if lua_getfield(L, -1, modname) == LUA_TNIL { lua_pop(L, 1); - lua_pushcfunction(L, openf); + lua_pushcfunction(L, openf, b"open\0".as_ptr().cast()); lua_pushstring(L, modname); lua_call(L, 1, 1); lua_pushvalue(L, -1); diff --git a/mlua-sys/src/luau/lgc.rs b/mlua-sys/src/luau/lgc.rs new file mode 100644 index 0000000..16ac417 --- /dev/null +++ b/mlua-sys/src/luau/lgc.rs @@ -0,0 +1,77 @@ +use crate::*; + +const MASK_WHITE0: u8 = 0b1; +const MASK_WHITE1: u8 = 0b10; +const MASK_WHITE: u8 = MASK_WHITE0 | MASK_WHITE1; +const MASK_BLACK: u8 = 0b100; +const MASK_FIXED: u8 = 0b1000; + +/// Possible states of the Garbage Collector +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +enum GCState { + Pause = 0, + Propagate = 1, + PropagateAgain = 2, + Atomic = 3, + Sweep = 4, +} + +#[inline(always)] +unsafe fn is_black(o: GCObjectPtr) -> bool { + (&*o.0).gch.marked & MASK_BLACK != 0 +} + +#[inline(always)] +unsafe fn is_white(o: GCObjectPtr) -> bool { + (&*o.0).gch.marked & MASK_WHITE != 0 +} + +/*#[inline(always)] +unsafe fn is_dead(g: *mut global_State, o: *mut GCObject) -> bool { + (&*o).gch.marked & (MASK_WHITE | MASK_FIXED) == otherwhite(g) & MASK_WHITE +} + +#[inline(always)] +unsafe fn otherwhite(g: *mut global_State) -> u8 { + (&*g).currentwhite ^ MASK_WHITE +} + +#[inline(always)] +fn black2gray(o: *mut GCObject) { + (&mut *o).gch.marked &= !MASK_BLACK; +} + +pub unsafe fn luaC_barrierback(L: *mut lua_State, o: *mut GCObject, gclist: *mut *mut GCObject) { + let g = (&*L).global; + debug_assert!(is_black(o) && !is_dead(g, o) != 0); + debug_assert_ne!(g.gcstate, GCState::Pause); + + black2gray(o); // make object gray (again) + *gclist = (&*g).grayagain; + (&mut *g).grayagain = o; +}*/ + +extern "C" { + pub fn luaC_barrierback(L: *mut lua_State, o: GCObjectPtr, gclist: *mut *mut GCObject); + + pub fn luaC_barriertable(L: *mut lua_State, t: *mut Table, v: GCObjectPtr); +} + +#[inline(always)] +pub unsafe fn luaC_threadbarrier(L: *mut lua_State) { + if is_black(L.into()) { + luaC_barrierback(L, L.into(), &mut (&mut *L).gc_list as *mut _); + } +} + +#[inline(always)] +pub unsafe fn luaC_barriert(L: *mut lua_State, t: *mut Table, v: *mut TValue) { + if (*v).is_collectable() { + debug_assert_eq!((*v).tt, (*(*v).value.gc).gch.tt.into(), "types do not match"); + let v = (*v).value.gc.into(); + if is_black(v) && is_white(v) { + luaC_barriertable(L, t, v); + } + } +} diff --git a/mlua-sys/src/luau/lobject.rs b/mlua-sys/src/luau/lobject.rs new file mode 100644 index 0000000..be80258 --- /dev/null +++ b/mlua-sys/src/luau/lobject.rs @@ -0,0 +1,263 @@ +use std::ffi::{c_char, c_float, c_int, c_void}; +use std::mem::ManuallyDrop; +use std::ptr::NonNull; + +use crate::{lua_CFunction, lua_Continuation, lua_Integer, lua_Number, GCObject, LUA_TTABLE, LUA_TNIL, LUA_TNUMBER, LUA_TLIGHTUSERDATA, LUA_TSTRING}; + +#[cfg(not(feature = "luau-vector4"))] +pub const LUA_VECTOR_SIZE: usize = 3; +#[cfg(feature = "luau-vector4")] +pub const LUA_VECTOR_SIZE: usize = 4; + +pub const LUA_EXTRA_SIZE: usize = LUA_VECTOR_SIZE - 2; + +opaque! { + pub type LuaNode; + + pub type Proto; + + pub type TString; + + pub type Udata; + + pub type UpVal; +} + +pub type StkId = *mut TValue; + +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct GCheader { + pub tt: u8, + pub marked: u8, + pub memcat: u8, +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub union Value { + pub gc: *mut GCObject, + /// Light user data + pub p: *mut c_void, + /// Number + pub n: lua_Number, + pub b: c_int, + /// Vector + /// + /// `v[0]`, `v[1]` live here; `v[2]`, possibly `v[3]` lives in TValue::extra + pub v: [c_float; 2], + bits: u64, +} + +impl std::fmt::Debug for Value { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:b}", unsafe { self.bits }) + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct TValue { + pub value: Value, + pub extra: [c_int; LUA_EXTRA_SIZE], + pub tt: c_int, +} + +impl TValue { + #[inline(always)] + fn check_type(&self, tt: c_int) { + debug_assert_eq!(self.tt, tt, "Wrong type"); + } + + #[inline(always)] + pub unsafe fn as_table(&self) -> *mut Table { + self.check_type(LUA_TTABLE); + unsafe { self.value.gc.cast() } + } + + #[inline(always)] + pub unsafe fn as_light_user_data(&self) -> *mut c_void { + self.check_type(LUA_TLIGHTUSERDATA); + unsafe { self.value.p } + } + + #[inline(always)] + pub unsafe fn as_number(&self) -> lua_Number { + self.check_type(LUA_TNUMBER); + unsafe { self.value.n } + } + + /// The only check if for rounding error, not type. + #[inline(always)] + pub unsafe fn try_as_integer(&self) -> Option { + let n = self.as_number(); + let n_int = n as lua_Integer; + if (n - n_int as lua_Number).abs() < lua_Number::EPSILON { + Some(n_int) + } else { + None + } + } + + #[inline(always)] + pub unsafe fn as_integer(&self) -> lua_Integer { + self.as_number() as lua_Integer + } + + #[inline(always)] + pub fn is_collectable(&self) -> bool { + self.tt >= LUA_TSTRING + } +} + +#[repr(C)] +pub struct Closure { + pub tt: u8, + pub marked: u8, + pub memcat: u8, + + pub is_c: u8, + pub n_upvals: u8, + pub stack_size: u8, + pub preload: u8, + + pub gc_list: *mut GCObject, + pub env: *mut Table, + + pub body: ClosureBody, +} + +impl Closure { + #[inline(always)] + pub fn is_c(&self) -> bool { + self.is_c != 0 + } +} + +impl std::fmt::Debug for Closure { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct("Closure") + .field("tt", &self.tt) + .field("marked", &self.marked) + .field("memcat", &self.memcat) + + .field("is_c", &self.is_c) + .field("n_upvals", &self.n_upvals) + .field("stack_size", &self.stack_size) + .field("preload", &self.preload) + + .field("gc_List", &self.gc_list) + .field("env", &self.env) + + .field("body", if self.is_c() { + unsafe { &self.body.c } + } else { + unsafe { &self.body.l } + }) + .finish() + } +} + +#[repr(C)] +pub union ClosureBody { + pub c: ManuallyDrop, + pub l: ManuallyDrop, +} + +#[repr(C)] +pub struct ClosureC { + pub f: lua_CFunction, + pub cont: lua_Continuation, + pub debugname: *const c_char, + pub upvals: [TValue; 1], +} + +impl std::fmt::Debug for ClosureC { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct("ClosureC") + .field("f", &self.f) + .field("cont", &self.cont) + .field("debugname", unsafe { &std::ffi::CStr::from_ptr(self.debugname) }) + .field("upvals", &std::ptr::addr_of!(self.upvals)) + .finish() + } +} + +#[repr(C)] +pub struct ClosureL { + pub p: *mut Proto, + pub uprefs: [TValue; 1], +} + +impl std::fmt::Debug for ClosureL { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct("ClosureL") + .field("p", &self.p) + .field("uprefs", &std::ptr::addr_of!(self.uprefs)) + .finish() + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct Table { + pub tt: u8, + pub marked: u8, + pub memcat: u8, + + /// 1<

>, + /// array part + pub array: Option>, + /// dict part. If there is no node, the value will be the address of [`crate::luaH_dummynode`]. + pub node: NonNull, + pub gc_list: *mut GCObject, +} + +impl Table { + #[inline(always)] + pub unsafe fn array(&self) -> &[TValue] { + debug_assert!(self.size_array >= 0); + debug_assert_eq!(self.array.is_none(), self.size_array == 0, "inconsistent array properties: null pointer with non-zero length"); + match (self.array, self.size_array) { + (None, 0) => &[], + // in debug_assertions this will be caught, otherwise, allow the optimizer to ignore it + (Some(_), 0) | (None, _) => std::hint::unreachable_unchecked(), + (Some(ptr), len) => std::slice::from_raw_parts(ptr.as_ptr(), len as _) + } + } + + #[inline(always)] + pub unsafe fn array_mut(&mut self) -> &mut [TValue] { + debug_assert!(self.size_array >= 0); + debug_assert_eq!(self.array.is_none(), self.size_array == 0, "inconsistent array properties: null pointer with non-zero length"); + match (self.array, self.size_array) { + (None, 0) => &mut [], + // in debug_assertions this will be caught, otherwise, allow the optimizer to ignore it + (Some(_), 0) | (None, _) => std::hint::unreachable_unchecked(), + (Some(ptr), len) => std::slice::from_raw_parts_mut(ptr.as_ptr(), len as _) + } + } + + #[inline(always)] + pub unsafe fn array_lsize(&self) -> usize { + self.array().iter().rposition(|v| v.tt != LUA_TNIL).map(|i| i+1).unwrap_or(0) + } +} diff --git a/mlua-sys/src/luau/lstate.rs b/mlua-sys/src/luau/lstate.rs new file mode 100644 index 0000000..edda7a5 --- /dev/null +++ b/mlua-sys/src/luau/lstate.rs @@ -0,0 +1,216 @@ +use std::ffi::{c_void, c_int, c_ushort, c_uint}; +use std::mem::ManuallyDrop; +use std::ptr::{addr_of_mut, NonNull}; + +use crate::{Closure, GCheader, Proto, StkId, Table, TString, TValue, Udata, UpVal}; + +opaque! { + pub type global_State; + + pub type Instruction; +} + +extern "C" { + pub static luaO_nilobject_: TValue; +} + +#[inline(always)] +pub fn luaO_nilobject() -> NonNull { + unsafe { NonNull::new_unchecked(std::ptr::addr_of!(luaO_nilobject_).cast_mut()) } +} + +/// Union of all collectible objects +#[repr(C)] +pub union GCObject +{ + pub gch: ManuallyDrop, + pub ts: ManuallyDrop, + pub u: ManuallyDrop, + pub cl: ManuallyDrop, + pub h: ManuallyDrop, + pub p: ManuallyDrop, + pub uv: ManuallyDrop, + /// thread + pub th: ManuallyDrop, +} + +impl std::fmt::Debug for GCObject { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "GCObject({:?})", unsafe { self.gch }) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct GCObjectPtr(pub *mut GCObject); + +impl GCObjectPtr { + pub fn hdr(self) -> *mut GCheader { + unsafe { addr_of_mut!(*(*self.0).gch) } + } +} + +impl From for *mut GCObject { + #[inline(always)] + fn from(value: GCObjectPtr) -> Self { + value.0 + } +} + +impl From<*mut GCObject> for GCObjectPtr { + #[inline(always)] + fn from(value: *mut GCObject) -> Self { + Self(value) + } +} + +impl From<*mut GCheader> for GCObjectPtr { + #[inline(always)] + fn from(value: *mut GCheader) -> Self { + unsafe { GCObjectPtr(std::mem::transmute(value)) } + } +} + +impl From<*mut TString> for GCObjectPtr { + #[inline(always)] + fn from(value: *mut TString) -> Self { + unsafe { GCObjectPtr(std::mem::transmute(value)) } + } +} + +impl From<*mut Udata> for GCObjectPtr { + #[inline(always)] + fn from(value: *mut Udata) -> Self { + unsafe { GCObjectPtr(std::mem::transmute(value)) } + } +} + +impl From<*mut Closure> for GCObjectPtr { + #[inline(always)] + fn from(value: *mut Closure) -> Self { + unsafe { GCObjectPtr(std::mem::transmute(value)) } + } +} + +impl From<*mut Table> for GCObjectPtr { + #[inline(always)] + fn from(value: *mut Table) -> Self { + unsafe { GCObjectPtr(std::mem::transmute(value)) } + } +} + +impl From<*mut Proto> for GCObjectPtr { + #[inline(always)] + fn from(value: *mut Proto) -> Self { + unsafe { GCObjectPtr(std::mem::transmute(value)) } + } +} + +impl From<*mut UpVal> for GCObjectPtr { + #[inline(always)] + fn from(value: *mut UpVal) -> Self { + unsafe { GCObjectPtr(std::mem::transmute(value)) } + } +} + +impl From<*mut lua_State> for GCObjectPtr { + #[inline(always)] + fn from(value: *mut lua_State) -> Self { + unsafe { GCObjectPtr(std::mem::transmute(value)) } + } +} + +/// See docs at +#[derive(Debug)] +#[repr(C)] +pub struct CallInfo { + /// base for this function. + pub base: StkId, + /// function index in the stack + pub func: StkId, + /// top for this function + /// + /// Seems to indicate the allocated stack size, not used. + pub top: StkId, + + pub saved_pc: *const Instruction, + + /// expected number of results from this function + pub n_results: c_int, + + /// call frame flags, see LUA_CALLINFO_* + pub flags: c_uint, +} + +/// should the interpreter return after returning from this callinfo? first frame must have this set +pub const LUA_CALLINFO_RETURN: c_uint = 1 << 0; +/// should the error thrown during execution get handled by continuation from this callinfo? func must be C +pub const LUA_CALLINFO_HANDLE: c_uint = 1 << 1; +/// should this function be executed using execution callback for native code +pub const LUA_CALLINFO_NATIVE: c_uint = 1 << 2; + +/// A raw Lua state associated with a thread. +#[derive(Debug)] +#[repr(C)] +pub struct lua_State { + pub tt: u8, + pub marked: u8, + pub memcat: u8, + + pub status: u8, + + /// memory category that is used for new GC object allocations + pub active_memcat: u8, + + /// thread is currently executing, stack may be mutated without barriers + pub is_active: bool, + /// call debugstep hook after each instruction + pub single_step: bool, + + + /// first free slot in the stack + pub top: StkId, + /// base of current function + pub base: StkId, + pub global: *mut global_State, + /// call info for current function + pub ci: *mut CallInfo, + /// last free slot in the stack + pub stack_last: StkId, + /// stack base + pub stack: StkId, + + + /// points after end of ci array + pub end_ci: *mut CallInfo, + /// array of [`CallInfo`]s + pub base_ci: *mut CallInfo, + + + /// The current allocated size of the entire thread stack. + pub stack_size: c_int, + /// size of array [`base_ci'] + pub size_ci: c_int, + + + /// number of nested C calls + pub n_c_calls: c_ushort, + /// nested C calls when resuming coroutine + pub base_c_calls: c_ushort, + + /// when table operations or `INDEX`/`NEWINDEX` is invoked from Luau, what is the expected slot for lookup? + pub cached_slot: c_int, + + /// table of globals + pub globals: *mut Table, + /// list of open upvalues in this stack + pub open_upval: *mut UpVal, + pub gc_list: *mut GCObject, + + /// when invoked from Luau using `NAMECALL`, what method do we need to invoke? + pub namecall: *mut TString, + + pub userdata: *mut c_void, + + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} diff --git a/mlua-sys/src/luau/ltable.rs b/mlua-sys/src/luau/ltable.rs new file mode 100644 index 0000000..82b67ed --- /dev/null +++ b/mlua-sys/src/luau/ltable.rs @@ -0,0 +1,5 @@ +use crate::LuaNode; + +extern "C" { + pub static luaH_dummynode: LuaNode; +} diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 24a2335..9c59f46 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -1,6 +1,5 @@ //! Contains definitions from `lua.h`. -use std::marker::{PhantomData, PhantomPinned}; use std::os::raw::{c_char, c_double, c_float, c_int, c_uint, c_void}; use std::ptr; @@ -31,13 +30,9 @@ pub const LUA_ERRRUN: c_int = 2; pub const LUA_ERRSYNTAX: c_int = 3; pub const LUA_ERRMEM: c_int = 4; pub const LUA_ERRERR: c_int = 5; +pub const LUA_BREAK: c_int = 6; -/// A raw Lua state associated with a thread. -#[repr(C)] -pub struct lua_State { - _data: [u8; 0], - _marker: PhantomData<(*mut u8, PhantomPinned)>, -} +pub use super::lstate::lua_State; // // Basic types @@ -381,25 +376,19 @@ pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) { lua_pushlstring_(L, c_str.as_ptr(), c_str.as_bytes().len()) } -pub unsafe fn lua_pushcfunction(L: *mut lua_State, f: lua_CFunction) { - lua_pushcclosurek(L, f, ptr::null(), 0, None) +#[inline(always)] +pub unsafe fn lua_pushcfunction(L: *mut lua_State, f: lua_CFunction, debug_name: *const c_char) { + lua_pushcclosurek(L, f, debug_name, 0, None) } -pub unsafe fn lua_pushcfunctiond(L: *mut lua_State, f: lua_CFunction, debugname: *const c_char) { - lua_pushcclosurek(L, f, debugname, 0, None) -} - -pub unsafe fn lua_pushcclosure(L: *mut lua_State, f: lua_CFunction, nup: c_int) { - lua_pushcclosurek(L, f, ptr::null(), nup, None) -} - -pub unsafe fn lua_pushcclosured( +#[inline(always)] +pub unsafe fn lua_pushcclosure( L: *mut lua_State, f: lua_CFunction, - debugname: *const c_char, - nup: c_int, + debug_name: *const c_char, + n_up: c_int, ) { - lua_pushcclosurek(L, f, debugname, nup, None) + lua_pushcclosurek(L, f, debug_name, n_up, None) } #[inline(always)] @@ -516,3 +505,13 @@ pub struct lua_Callbacks { extern "C" { pub fn lua_callbacks(L: *mut lua_State) -> *mut lua_Callbacks; } + +#[inline(always)] +pub unsafe fn real_index2addr(l: *mut lua_State, idx: i32) -> ptr::NonNull { + debug_assert!(idx > 0, "Invalid stack index: {idx}"); + let l = &*l; + let o = l.base.add(idx as usize - 1); + debug_assert!(o < (&*l.ci).top); + debug_assert!(o < l.top); + ptr::NonNull::new_unchecked(o) +} diff --git a/mlua-sys/src/luau/luau.rs b/mlua-sys/src/luau/luau.rs new file mode 100644 index 0000000..ef0ce52 --- /dev/null +++ b/mlua-sys/src/luau/luau.rs @@ -0,0 +1,15 @@ +use std::ffi::{c_char, c_int, c_void}; + +use crate::lua_State; + +pub type luau_AssertHandler = unsafe extern "C" fn(expr: *const c_char, file: *const c_char, line: c_int, function: *const c_char) -> c_int; + +pub type luau_Pfunc = unsafe extern "C" fn(L: *mut lua_State, ud: *mut c_void); + +extern "C" { + #[cfg(debug_assertions)] + #[link_name = "_ZZN4Luau13assertHandlerEvE7handler"] + pub static mut luau_assertHandler: luau_AssertHandler; + + pub fn luaD_pcall(L: *mut lua_State, func: luau_Pfunc, ud: *mut c_void, old_top: isize, ef: isize); +} diff --git a/mlua-sys/src/luau/mod.rs b/mlua-sys/src/luau/mod.rs index b36fee5..4d4b8f2 100644 --- a/mlua-sys/src/luau/mod.rs +++ b/mlua-sys/src/luau/mod.rs @@ -2,14 +2,24 @@ pub use compat::*; pub use lauxlib::*; +pub use lgc::*; +pub use lobject::*; +pub use lstate::*; +pub use ltable::*; pub use lua::*; pub use luacode::*; pub use luacodegen::*; pub use lualib::*; +pub use luau::*; pub mod compat; pub mod lauxlib; +pub mod lgc; +pub mod lobject; +pub mod lstate; +pub mod ltable; pub mod lua; pub mod luacode; pub mod luacodegen; pub mod lualib; +pub mod luau; diff --git a/mlua-sys/src/macros.rs b/mlua-sys/src/macros.rs index df79a79..caf366f 100644 --- a/mlua-sys/src/macros.rs +++ b/mlua-sys/src/macros.rs @@ -5,3 +5,13 @@ macro_rules! cstr { as *const ::std::os::raw::c_char }; } + +macro_rules! opaque { + ($($vis:vis type $name:ident;)*) => {$( + #[repr(C)] + $vis struct $name { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, + } + )*}; +} diff --git a/src/function.rs b/src/function.rs index 39f9c13..7274f9d 100644 --- a/src/function.rs +++ b/src/function.rs @@ -145,7 +145,7 @@ impl<'lua> Function<'lua> { let _sg = StackGuard::new(state); check_stack(state, nargs + 3)?; - MemoryState::relax_limit_with(state, || ffi::lua_pushcfunction(state, error_traceback)); + MemoryState::relax_limit_with(state, || ffi::lua_pushcfunction(state, error_traceback, "mlua::util::error_traceback\0".as_ptr().cast())); let stack_start = ffi::lua_gettop(state); lua.push_ref(&self.0); for arg in args.drain_all() { @@ -278,7 +278,7 @@ impl<'lua> Function<'lua> { lua.push_value(arg)?; } protect_lua!(state, nargs + 1, 1, fn(state) { - ffi::lua_pushcclosure(state, args_wrapper_impl, ffi::lua_gettop(state)); + ffi::lua_pushcclosure(state, args_wrapper_impl, "mlua::Function::bind::args_wrapper_impl\0".as_ptr().cast(), ffi::lua_gettop(state)); })?; Function(lua.pop_ref()) diff --git a/src/lib.rs b/src/lib.rs index 17d4cf0..f7ce978 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,7 +85,7 @@ mod error; pub mod ffi; mod function; mod hook; -mod lua; +pub mod lua; #[cfg(feature = "luau")] mod luau; mod memory; diff --git a/src/lua.rs b/src/lua.rs index 015d846..5141872 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -78,7 +78,7 @@ pub struct LuaInner { } // Data associated with the Lua. -pub(crate) struct ExtraData { +pub struct ExtraData { // Same layout as `Lua` inner: MaybeUninit>, @@ -110,7 +110,7 @@ pub(crate) struct ExtraData { thread_pool: Vec, // Address of `WrappedFailure` metatable - wrapped_failure_mt_ptr: *const c_void, + pub wrapped_failure_mt_ptr: *const c_void, // Waker for polling futures #[cfg(feature = "async")] @@ -412,6 +412,7 @@ impl Lua { (*extra).libs |= libs; if !options.catch_rust_panics { + eprintln!("overriding pcall"); mlua_expect!( (|| -> Result<()> { let _sg = StackGuard::new(state); @@ -421,10 +422,10 @@ impl Lua { #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] ffi::lua_pushvalue(state, ffi::LUA_GLOBALSINDEX); - ffi::lua_pushcfunction(state, safe_pcall); + ffi::lua_pushcfunction(state, safe_pcall, "mlua::safe_pcall\0".as_ptr().cast()); rawset_field(state, -2, "pcall")?; - ffi::lua_pushcfunction(state, safe_xpcall); + ffi::lua_pushcfunction(state, safe_xpcall, "mlua::safe_xpcall\0".as_ptr().cast()); rawset_field(state, -2, "xpcall")?; Ok(()) @@ -1559,10 +1560,15 @@ impl Lua { /// # Safety /// This function is unsafe because provides a way to execute unsafe C function. #[inline] - pub unsafe fn create_c_function(&self, func: ffi::lua_CFunction) -> Result { + pub unsafe fn create_c_function( + &self, + func: ffi::lua_CFunction, + cont: Option, + debug_name: Option<&'static CStr>, + ) -> Result { let state = self.state(); check_stack(state, 1)?; - ffi::lua_pushcfunction(state, func); + ffi::lua_pushcclosurek(state, func, debug_name.map(CStr::as_ptr).unwrap_or(std::ptr::null()), 0, cont); Ok(Function(self.pop_ref())) } @@ -1574,10 +1580,18 @@ impl Lua { pub unsafe fn create_c_closure<'lua>( &'lua self, func: ffi::lua_CFunction, + cont: Option, + debug_name: Option<&'static CStr>, upvalues: impl IntoLuaMulti<'lua>, ) -> Result { let state = self.state(); + if matches!(upvalues.lua_multi_len(), Some(0)) { + return self.create_c_function(func, cont, debug_name); + } let upvalues = upvalues.into_lua_multi(self)?; + if upvalues.len() == 0 { + return self.create_c_function(func, cont, debug_name); + } let Ok(n) = i32::try_from(upvalues.len()) else { return Err(Error::BadArgument { to: Some("create_c_closure".to_owned()), pos: 3, name: Some("upvalues".to_owned()), cause: Arc::new(Error::BindError) }) }; @@ -1586,7 +1600,7 @@ impl Lua { for upvalue in upvalues { self.push_value(upvalue)?; } - ffi::lua_pushcclosure(state, func, n); + ffi::lua_pushcclosurek(state, func, debug_name.map(CStr::as_ptr).unwrap_or(std::ptr::null()), n, cont); Ok(Function(self.pop_ref())) } @@ -2433,26 +2447,10 @@ impl Lua { ffi::LUA_TFUNCTION => Value::Function(Function(self.pop_ref())), ffi::LUA_TUSERDATA => { - let wrapped_failure_mt_ptr = (*self.extra.get()).wrapped_failure_mt_ptr; - // We must prevent interaction with userdata types other than UserData OR a WrappedError. - // WrappedPanics are automatically resumed. - match get_gc_userdata::(state, -1, wrapped_failure_mt_ptr).as_mut() - { - Some(WrappedFailure::Error(err)) => { - let err = err.clone(); - ffi::lua_pop(state, 1); - Value::Error(err) - } - Some(WrappedFailure::Panic(panic)) => { - if let Some(panic) = panic.take() { - ffi::lua_pop(state, 1); - resume_unwind(panic); - } - // Previously resumed panic? - ffi::lua_pop(state, 1); - Nil - } - _ => Value::UserData(AnyUserData(self.pop_ref())), + match (*self.extra.get()).pop_wrapped_failure(state) { + Some(Some(e)) => Value::Error(e), + Some(None) => Nil, + None => Value::UserData(AnyUserData(self.pop_ref())), } } @@ -2800,10 +2798,10 @@ impl Lua { push_gc_userdata(state, CallbackUpvalue { data: func, extra }, protect)?; if protect { protect_lua!(state, 1, 1, fn(state) { - ffi::lua_pushcclosure(state, call_callback, 1); + ffi::lua_pushcclosure(state, call_callback, "mlua::call_callback\0".as_ptr().cast(), 1); })?; } else { - ffi::lua_pushcclosure(state, call_callback, 1); + ffi::lua_pushcclosure(state, call_callback, "mlua::call_callback\0".as_ptr().cast(), 1); } Ok(Function(self.pop_ref())) @@ -2852,10 +2850,10 @@ impl Lua { push_gc_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?; if protect { protect_lua!(state, 1, 1, fn(state) { - ffi::lua_pushcclosure(state, poll_future, 1); + ffi::lua_pushcclosure(state, poll_future, "mlua::poll_future\0".as_ptr().cast(), 1); })?; } else { - ffi::lua_pushcclosure(state, poll_future, 1); + ffi::lua_pushcclosure(state, poll_future, "mlua::poll_future\0".as_ptr().cast(), 1); } Ok(1) @@ -2909,10 +2907,10 @@ impl Lua { push_gc_userdata(state, upvalue, protect)?; if protect { protect_lua!(state, 1, 1, fn(state) { - ffi::lua_pushcclosure(state, call_callback, 1); + ffi::lua_pushcclosure(state, call_callback, "mlua::call_callback\0".as_ptr().cast(), 1); })?; } else { - ffi::lua_pushcclosure(state, call_callback, 1); + ffi::lua_pushcclosure(state, call_callback, "mlua::call_callback\0".as_ptr().cast(), 1); } Function(self.pop_ref()) @@ -2933,7 +2931,7 @@ impl Lua { env.set("get_poll", get_poll)?; env.set("yield", coroutine.get::<_, Function>("yield")?)?; unsafe { - env.set("unpack", self.create_c_function(unpack)?)?; + env.set("unpack", self.create_c_function(unpack, None, Some(CStr::from_bytes_with_nul_unchecked(b"unpack\0")))?)?; } env.set("pending", { LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut c_void) @@ -3142,6 +3140,31 @@ impl ExtraData { pub(crate) fn mem_state(&self) -> NonNull { self.mem_state.unwrap() } + + /// Uses 2 stack spaces, does not call checkstack + #[inline] + pub unsafe fn pop_wrapped_failure(&self, state: *mut ffi::lua_State) -> Option> { + // We must prevent interaction with userdata types other than UserData OR a WrappedError. + // WrappedPanics are automatically resumed. + match get_gc_userdata::(state, -1, self.wrapped_failure_mt_ptr).as_mut() + { + Some(WrappedFailure::Error(err)) => { + let err = err.clone(); + ffi::lua_pop(state, 1); + Some(Some(err)) + } + Some(WrappedFailure::Panic(panic)) => { + if let Some(panic) = panic.take() { + ffi::lua_pop(state, 1); + resume_unwind(panic); + } + // Previously resumed panic? + ffi::lua_pop(state, 1); + Some(None) + } + _ => None, + } + } } struct StateGuard<'a>(&'a LuaInner, *mut ffi::lua_State); @@ -3160,12 +3183,12 @@ impl<'a> Drop for StateGuard<'a> { } #[cfg(feature = "luau")] -unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData { +pub unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData { (*ffi::lua_callbacks(state)).userdata as *mut ExtraData } #[cfg(not(feature = "luau"))] -unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData { +pub unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData { let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void; if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, extra_key) != ffi::LUA_TUSERDATA { // `ExtraData` can be null only when Lua state is foreign. diff --git a/src/luau.rs b/src/luau.rs index 3a9f379..b3e28e7 100644 --- a/src/luau.rs +++ b/src/luau.rs @@ -17,10 +17,10 @@ impl Lua { globals.raw_set( "collectgarbage", - self.create_c_function(lua_collectgarbage)?, + self.create_c_function(lua_collectgarbage, None, Some(CStr::from_bytes_with_nul_unchecked(b"collectgarbage\0")))?, )?; globals.raw_set("require", self.create_function(lua_require)?)?; - globals.raw_set("vector", self.create_c_function(lua_vector)?)?; + globals.raw_set("vector", self.create_c_function(lua_vector, None, Some(CStr::from_bytes_with_nul_unchecked(b"vector\0")))?)?; // Set `_VERSION` global to include version number // The environment variable `LUAU_VERSION` set by the build script diff --git a/src/macros.rs b/src/macros.rs index 143a4c8..0a7776c 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -105,8 +105,13 @@ macro_rules! require_module_feature { } macro_rules! protect_lua { + (@debug_name) => { + concat!("protect_lua[", file!(), ":", stringify!(line!()), ":", stringify!(column!()), "]") + .as_ptr().cast() + }; + ($state:expr, $nargs:expr, $nresults:expr, $f:expr) => { - crate::util::protect_lua_closure($state, $nargs, $nresults, $f) + crate::util::protect_lua_closure($state, $nargs, $nresults, $f, $crate::macros::protect_lua!(@debug_name)) }; ($state:expr, $nargs:expr, $nresults:expr, fn($state_inner:ident) $code:expr) => {{ @@ -120,6 +125,7 @@ macro_rules! protect_lua { } } - crate::util::protect_lua_call($state, $nargs, do_call) + crate::util::protect_lua_call($state, $nargs, do_call, $crate::macros::protect_lua!(@debug_name)) }}; } +pub(crate) use protect_lua; diff --git a/src/multi.rs b/src/multi.rs index d2f5908..0575e15 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -30,6 +30,11 @@ impl<'lua, T: IntoLua<'lua>> IntoLuaMulti<'lua> for T { v.push_front(self.into_lua(lua)?); Ok(v) } + + #[inline(always)] + fn lua_multi_len(&self) -> Option { + Some(1) + } } impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for T { @@ -58,6 +63,11 @@ impl<'lua> IntoLuaMulti<'lua> for MultiValue<'lua> { fn into_lua_multi(self, _: &'lua Lua) -> Result> { Ok(self) } + + #[inline(always)] + fn lua_multi_len(&self) -> Option { + Some(self.len()) + } } impl<'lua> FromLuaMulti<'lua> for MultiValue<'lua> { @@ -145,6 +155,11 @@ impl<'lua, T: IntoLua<'lua>> IntoLuaMulti<'lua> for Variadic { values.refill(self.0.into_iter().map(|e| e.into_lua(lua)))?; Ok(values) } + + #[inline(always)] + fn lua_multi_len(&self) -> Option { + Some(self.0.len()) + } } impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for Variadic { @@ -167,6 +182,11 @@ macro_rules! impl_tuple { fn into_lua_multi(self, lua: &'lua Lua) -> Result> { Ok(MultiValue::new_or_pooled(lua)) } + + #[inline(always)] + fn lua_multi_len(&self) -> Option { + Some(0) + } } impl<'lua> FromLuaMulti<'lua> for () { @@ -193,6 +213,11 @@ macro_rules! impl_tuple { push_reverse!(results, $($name.into_lua(lua)?,)*); Ok(results) } + + #[inline(always)] + fn lua_multi_len(&self) -> Option { + Some(count!($last $($name)*)) + } } impl<'lua, $($name,)* $last> FromLuaMulti<'lua> for ($($name,)* $last,) @@ -236,6 +261,15 @@ macro_rules! push_reverse { ($multi_value:expr,) => (); } +macro_rules! count { + ($a:ident) => { + 1 + }; + ($a:ident $($b:ident)+) => { + 1 + count!($($b)+) + } +} + impl_tuple!(); impl_tuple!(A); impl_tuple!(A B); diff --git a/src/thread.rs b/src/thread.rs index 000c0b3..e2ac46b 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -60,8 +60,8 @@ pub struct Thread<'lua>(pub(crate) LuaRef<'lua>); /// /// Requires `feature = "async"` /// -/// [`Future`]: futures_core::future::Future -/// [`Stream`]: futures_core::stream::Stream +/// [`Future`]: std::future::Future +/// [`Stream`]: futures_util::stream::Stream #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[must_use = "futures do nothing unless you `.await` or poll them"] @@ -284,8 +284,8 @@ impl<'lua> Thread<'lua> { /// /// Requires `feature = "async"` /// - /// [`Future`]: futures_core::future::Future - /// [`Stream`]: futures_core::stream::Stream + /// [`Future`]: std::future::Future + /// [`Stream`]: futures_util::stream::Stream /// [`resume()`]: https://www.lua.org/manual/5.4/manual.html#lua_resume /// /// # Examples diff --git a/src/types.rs b/src/types.rs index 7dc39b2..590b0ba 100644 --- a/src/types.rs +++ b/src/types.rs @@ -307,7 +307,7 @@ impl<'lua> LuaRef<'lua> { impl<'lua> fmt::Debug for LuaRef<'lua> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Ref({})", self.index) + write!(f, "Ref({}, {:p})", self.index, self.to_pointer()) } } diff --git a/src/util/mod.rs b/src/util/mod.rs index 14a8e97..e9146bf 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -23,8 +23,8 @@ static METATABLE_CACHE: Lazy> = Lazy::new(|| { map }); -// Checks that Lua has enough free stack space for future stack operations. On failure, this will -// panic with an internal error message. +/// Checks that Lua has enough free stack space for future stack operations. On failure, this will +/// panic with an internal error message. #[inline] pub unsafe fn assert_stack(state: *mut ffi::lua_State, amount: c_int) { // TODO: This should only be triggered when there is a logic error in `mlua`. In the future, @@ -36,7 +36,7 @@ pub unsafe fn assert_stack(state: *mut ffi::lua_State, amount: c_int) { ); } -// Checks that Lua has enough free stack space and returns `Error::StackError` on failure. +/// Checks that Lua has enough free stack space and returns `Error::StackError` on failure. #[inline] pub unsafe fn check_stack(state: *mut ffi::lua_State, amount: c_int) -> Result<()> { if ffi::lua_checkstack(state, amount) == 0 { @@ -78,22 +78,23 @@ impl Drop for StackGuard { } } -// Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way. -// Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a -// limited lua stack. `nargs` is the same as the the parameter to `lua_pcall`, and `nresults` is -// always `LUA_MULTRET`. Provided function must *not* panic, and since it will generally be lonjmping, -// should not contain any values that implements Drop. -// Internally uses 2 extra stack spaces, and does not call checkstack. +/// Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way. +/// Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a +/// limited lua stack. `nargs` is the same as the the parameter to `lua_pcall`, and `nresults` is +/// always `LUA_MULTRET`. Provided function must *not* panic, and since it will generally be lonjmping, +/// should not contain any values that implements Drop. +/// Internally uses 2 extra stack spaces, and does not call checkstack. pub unsafe fn protect_lua_call( state: *mut ffi::lua_State, nargs: c_int, f: unsafe extern "C" fn(*mut ffi::lua_State) -> c_int, + debug_name: *const c_char, ) -> Result<()> { let stack_start = ffi::lua_gettop(state) - nargs; MemoryState::relax_limit_with(state, || { - ffi::lua_pushcfunction(state, error_traceback); - ffi::lua_pushcfunction(state, f); + ffi::lua_pushcfunction(state, error_traceback, "mlua::util::error_traceback\0".as_ptr().cast()); + ffi::lua_pushcfunction(state, f, debug_name); }); if nargs > 0 { ffi::lua_rotate(state, stack_start + 1, 2); @@ -109,18 +110,19 @@ pub unsafe fn protect_lua_call( } } -// Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way. -// Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a -// limited lua stack. `nargs` and `nresults` are similar to the parameters of `lua_pcall`, but the -// given function return type is not the return value count, instead the inner function return -// values are assumed to match the `nresults` param. Provided function must *not* panic, and since it -// will generally be lonjmping, should not contain any values that implements Drop. -// Internally uses 3 extra stack spaces, and does not call checkstack. +/// Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way. +/// Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a +/// limited lua stack. `nargs` and `nresults` are similar to the parameters of `lua_pcall`, but the +/// given function return type is not the return value count, instead the inner function return +/// values are assumed to match the `nresults` param. Provided function must *not* panic, and since it +/// will generally be lonjmping, should not contain any values that implements Drop. +/// Internally uses 3 extra stack spaces, and does not call checkstack. pub unsafe fn protect_lua_closure( state: *mut ffi::lua_State, nargs: c_int, nresults: c_int, f: F, + debug_name: *const c_char, ) -> Result where F: Fn(*mut ffi::lua_State) -> R, @@ -152,8 +154,8 @@ where let stack_start = ffi::lua_gettop(state) - nargs; MemoryState::relax_limit_with(state, || { - ffi::lua_pushcfunction(state, error_traceback); - ffi::lua_pushcfunction(state, do_call::); + ffi::lua_pushcfunction(state, error_traceback, "mlua::util::error_traceback\0".as_ptr().cast()); + ffi::lua_pushcfunction(state, do_call::, debug_name); }); if nargs > 0 { ffi::lua_rotate(state, stack_start + 1, 2); @@ -178,12 +180,12 @@ where } } -// Pops an error off of the stack and returns it. The specific behavior depends on the type of the -// error at the top of the stack: -// 1) If the error is actually a WrappedPanic, this will continue the panic. -// 2) If the error on the top of the stack is actually a WrappedError, just returns it. -// 3) Otherwise, interprets the error as the appropriate lua error. -// Uses 2 stack spaces, does not call checkstack. +/// Pops an error off of the stack and returns it. The specific behavior depends on the type of the +/// error at the top of the stack: +/// 1) If the error is actually a WrappedPanic, this will continue the panic. +/// 2) If the error on the top of the stack is actually a WrappedError, just returns it. +/// 3) Otherwise, interprets the error as the appropriate lua error. +/// Uses 2 stack spaces, does not call checkstack. pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error { mlua_debug_assert!( err_code != ffi::LUA_OK && err_code != ffi::LUA_YIELD, @@ -233,7 +235,7 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error { } } -// Uses 3 (or 1 if unprotected) stack spaces, does not call checkstack. +/// Uses 3 (or 1 if unprotected) stack spaces, does not call checkstack. #[inline(always)] pub unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: bool) -> Result<()> { // Always use protected mode if the string is too long @@ -247,7 +249,7 @@ pub unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: bool) - } } -// Uses 3 stack spaces, does not call checkstack. +/// Uses 3 stack spaces, does not call checkstack. #[inline] pub unsafe fn push_table( state: *mut ffi::lua_State, @@ -263,7 +265,7 @@ pub unsafe fn push_table( } } -// Uses 4 stack spaces, does not call checkstack. +/// Uses 4 stack spaces, does not call checkstack. pub unsafe fn rawset_field(state: *mut ffi::lua_State, table: c_int, field: &str) -> Result<()> { ffi::lua_pushvalue(state, table); protect_lua!(state, 2, 0, |state| { @@ -273,7 +275,7 @@ pub unsafe fn rawset_field(state: *mut ffi::lua_State, table: c_int, field: &str }) } -// Internally uses 3 stack spaces, does not call checkstack. +/// Internally uses 3 stack spaces, does not call checkstack. #[cfg(not(feature = "luau"))] #[inline] pub unsafe fn push_userdata(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<()> { @@ -288,7 +290,7 @@ pub unsafe fn push_userdata(state: *mut ffi::lua_State, t: T, protect: bool) Ok(()) } -// Internally uses 3 stack spaces, does not call checkstack. +/// Internally uses 3 stack spaces, does not call checkstack. #[cfg(feature = "luau")] #[inline] pub unsafe fn push_userdata(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<()> { @@ -309,7 +311,7 @@ pub unsafe fn push_userdata(state: *mut ffi::lua_State, t: T, protect: bool) Ok(()) } -// Internally uses 3 stack spaces, does not call checkstack. +/// Internally uses 3 stack spaces, does not call checkstack. #[cfg(feature = "lua54")] #[inline] pub unsafe fn push_userdata_uv( @@ -336,10 +338,10 @@ pub unsafe fn get_userdata(state: *mut ffi::lua_State, index: c_int) -> *mut ud } -// Pops the userdata off of the top of the stack and returns it to rust, invalidating the lua -// userdata and gives it the special "destructed" userdata metatable. Userdata must not have been -// previously invalidated, and this method does not check for this. -// Uses 1 extra stack space and does not call checkstack. +/// Pops the userdata off of the top of the stack and returns it to rust, invalidating the lua +/// userdata and gives it the special "destructed" userdata metatable. Userdata must not have been +/// previously invalidated, and this method does not check for this. +/// Uses 1 extra stack space and does not call checkstack. pub unsafe fn take_userdata(state: *mut ffi::lua_State) -> T { // We set the metatable of userdata on __gc to a special table with no __gc method and with // metamethods that trigger an error on access. We do this so that it will not be double @@ -357,8 +359,8 @@ pub unsafe fn take_userdata(state: *mut ffi::lua_State) -> T { ptr::read(ud) } -// Pushes the userdata and attaches a metatable with __gc method. -// Internally uses 3 stack spaces, does not call checkstack. +/// Pushes the userdata and attaches a metatable with __gc method. +/// Internally uses 3 stack spaces, does not call checkstack. #[inline] pub unsafe fn push_gc_userdata( state: *mut ffi::lua_State, @@ -371,7 +373,7 @@ pub unsafe fn push_gc_userdata( Ok(()) } -// Uses 2 stack spaces, does not call checkstack +/// Uses 2 stack spaces, does not call checkstack pub unsafe fn get_gc_userdata( state: *mut ffi::lua_State, index: c_int, @@ -467,9 +469,9 @@ unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<() if ret != ffi::LUA_OK { ffi::lua_error(state); } - ffi::lua_pushcfunction(state, lua_error_impl); - ffi::lua_pushcfunction(state, lua_isfunction_impl); - ffi::lua_pushcfunction(state, lua_istable_impl); + ffi::lua_pushcfunction(state, lua_error_impl, "mlua::util::lua_error_impl\0".as_ptr().cast()); + ffi::lua_pushcfunction(state, lua_isfunction_impl, "mlua::util::lua_isfunction_impl\0".as_ptr().cast()); + ffi::lua_pushcfunction(state, lua_istable_impl, "mlua::util::lua_istable_impl\0".as_ptr().cast()); ffi::lua_call(state, 3, 1); #[cfg(feature = "luau-jit")] @@ -521,8 +523,8 @@ pub unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Re if ret != ffi::LUA_OK { ffi::lua_error(state); } - ffi::lua_pushcfunction(state, lua_error_impl); - ffi::lua_pushcfunction(state, lua_isfunction_impl); + ffi::lua_pushcfunction(state, lua_error_impl, "mlua::util::lua_error_impl\0".as_ptr().cast()); + ffi::lua_pushcfunction(state, lua_isfunction_impl, "mlua::util::lua_isfunction_impl\0".as_ptr().cast()); ffi::lua_call(state, 2, 1); #[cfg(feature = "luau-jit")] @@ -536,14 +538,14 @@ pub unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Re }) } -// Populates the given table with the appropriate members to be a userdata metatable for the given type. -// This function takes the given table at the `metatable` index, and adds an appropriate `__gc` member -// to it for the given type and a `__metatable` entry to protect the table from script access. -// The function also, if given a `field_getters` or `methods` tables, will create an `__index` metamethod -// (capturing previous one) to lookup in `field_getters` first, then `methods` and falling back to the -// captured `__index` if no matches found. -// The same is also applicable for `__newindex` metamethod and `field_setters` table. -// Internally uses 9 stack spaces and does not call checkstack. +/// Populates the given table with the appropriate members to be a userdata metatable for the given type. +/// This function takes the given table at the `metatable` index, and adds an appropriate `__gc` member +/// to it for the given type and a `__metatable` entry to protect the table from script access. +/// The function also, if given a `field_getters` or `methods` tables, will create an `__index` metamethod +/// (capturing previous one) to lookup in `field_getters` first, then `methods` and falling back to the +/// captured `__index` if no matches found. +/// The same is also applicable for `__newindex` metamethod and `field_setters` table. +/// Internally uses 9 stack spaces and does not call checkstack. pub unsafe fn init_userdata_metatable( state: *mut ffi::lua_State, metatable: c_int, @@ -618,17 +620,17 @@ pub unsafe extern "C" fn userdata_destructor(state: *mut ffi::lua_State) -> c 0 } -// In the context of a lua callback, this will call the given function and if the given function -// returns an error, *or if the given function panics*, this will result in a call to `lua_error` (a -// longjmp). The error or panic is wrapped in such a way that when calling `pop_error` back on -// the Rust side, it will resume the panic. -// -// This function assumes the structure of the stack at the beginning of a callback, that the only -// elements on the stack are the arguments to the callback. -// -// This function uses some of the bottom of the stack for error handling, the given callback will be -// given the number of arguments available as an argument, and should return the number of returns -// as normal, but cannot assume that the arguments available start at 0. +/// In the context of a lua callback, this will call the given function and if the given function +/// returns an error, *or if the given function panics*, this will result in a call to `lua_error` (a +/// longjmp). The error or panic is wrapped in such a way that when calling `pop_error` back on +/// the Rust side, it will resume the panic. +/// +/// This function assumes the structure of the stack at the beginning of a callback, that the only +/// elements on the stack are the arguments to the callback. +/// +/// This function uses some of the bottom of the stack for error handling, the given callback will be +/// given the number of arguments available as an argument, and should return the number of returns +/// as normal, but cannot assume that the arguments available start at 0. #[inline] pub unsafe fn callback_error(state: *mut ffi::lua_State, f: F) -> R where @@ -709,7 +711,7 @@ pub unsafe extern "C" fn error_traceback(state: *mut ffi::lua_State) -> c_int { 1 } -// A variant of `error_traceback` that can safely inspect another (yielded) thread stack +/// A variant of `error_traceback` that can safely inspect another (yielded) thread stack pub unsafe fn error_traceback_thread(state: *mut ffi::lua_State, thread: *mut ffi::lua_State) { // Move error object to the main thread to safely call `__tostring` metamethod if present ffi::lua_xmove(thread, state, 1); @@ -723,7 +725,7 @@ pub unsafe fn error_traceback_thread(state: *mut ffi::lua_State, thread: *mut ff } } -// A variant of `pcall` that does not allow Lua to catch Rust panics from `callback_error`. +/// A variant of `pcall` that does not allow Lua to catch Rust panics from `callback_error`. pub unsafe extern "C" fn safe_pcall(state: *mut ffi::lua_State) -> c_int { ffi::luaL_checkstack(state, 2, ptr::null()); @@ -749,7 +751,7 @@ pub unsafe extern "C" fn safe_pcall(state: *mut ffi::lua_State) -> c_int { } } -// A variant of `xpcall` that does not allow Lua to catch Rust panics from `callback_error`. +/// A variant of `xpcall` that does not allow Lua to catch Rust panics from `callback_error`. pub unsafe extern "C" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int { unsafe extern "C" fn xpcall_msgh(state: *mut ffi::lua_State) -> c_int { ffi::luaL_checkstack(state, 2, ptr::null()); @@ -775,7 +777,7 @@ pub unsafe extern "C" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int { } ffi::lua_pushvalue(state, 2); - ffi::lua_pushcclosure(state, xpcall_msgh, 1); + ffi::lua_pushcclosure(state, xpcall_msgh, "mlua::util::xpcall_msgh\0".as_ptr().cast(), 1); ffi::lua_copy(state, 1, 2); ffi::lua_replace(state, 1); @@ -795,8 +797,8 @@ pub unsafe extern "C" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int { } } -// Returns Lua main thread for Lua >= 5.2 or checks that the passed thread is main for Lua 5.1. -// Does not call lua_checkstack, uses 1 stack space. +/// Returns Lua main thread for Lua >= 5.2 or checks that the passed thread is main for Lua 5.1. +/// Does not call lua_checkstack, uses 1 stack space. pub unsafe fn get_main_state(state: *mut ffi::lua_State) -> Option<*mut ffi::lua_State> { #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] { @@ -820,8 +822,8 @@ pub unsafe fn get_main_state(state: *mut ffi::lua_State) -> Option<*mut ffi::lua Some(ffi::lua_mainthread(state)) } -// Initialize the internal (with __gc method) metatable for a type T. -// Uses 6 stack spaces and calls checkstack. +/// Initialize the internal (with __gc method) metatable for a type T. +/// Uses 6 stack spaces and calls checkstack. pub unsafe fn init_gc_metatable( state: *mut ffi::lua_State, customize_fn: Option Result<()>>, @@ -860,7 +862,7 @@ pub unsafe fn get_gc_metatable(state: *mut ffi::lua_State) { ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, ref_addr as *const c_void); } -// Initialize the error, panic, and destructed userdata metatables. +/// Initialize the error, panic, and destructed userdata metatables. pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> { check_stack(state, 7)?; @@ -917,7 +919,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> { init_gc_metatable::( state, Some(|state| { - ffi::lua_pushcfunction(state, error_tostring); + ffi::lua_pushcfunction(state, error_tostring, "mlua::util::error_tostring\0".as_ptr().cast()); rawset_field(state, -2, "__tostring") }), )?; @@ -929,7 +931,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> { } push_table(state, 0, 26, true)?; - ffi::lua_pushcfunction(state, destructed_error); + ffi::lua_pushcfunction(state, destructed_error, "mlua::util::destructed_error\0".as_ptr().cast()); for &method in &[ "__add", "__sub", @@ -1019,8 +1021,8 @@ impl WrappedFailure { } } -// Converts the given lua value to a string in a reasonable format without causing a Lua error or -// panicking. +/// Converts the given lua value to a string in a reasonable format without causing a Lua error or +/// panicking. pub(crate) unsafe fn to_string(state: *mut ffi::lua_State, index: c_int) -> String { match ffi::lua_type(state, index) { ffi::LUA_TNONE => "".to_string(), diff --git a/src/util/short_names.rs b/src/util/short_names.rs index a50c2bf..6fddce4 100644 --- a/src/util/short_names.rs +++ b/src/util/short_names.rs @@ -1,5 +1,5 @@ //! Mostly copied from bevy_utils -//! https://github.com/bevyengine/bevy/blob/main/crates/bevy_utils/src/short_names.rs +//! use std::any::type_name; diff --git a/src/value.rs b/src/value.rs index a1c8de4..7319b1b 100644 --- a/src/value.rs +++ b/src/value.rs @@ -500,6 +500,12 @@ impl<'lua> MultiValue<'lua> { pub trait IntoLuaMulti<'lua> { /// Performs the conversion. fn into_lua_multi(self, lua: &'lua Lua) -> Result>; + + /// The exact length of the [`MultiValue`] produced by [`IntoLuaMulti::into_lua_multi`], if known. + #[inline(always)] + fn lua_multi_len(&self) -> Option { + None + } } /// Trait for types that can be created from an arbitrary number of Lua values. diff --git a/tests/function.rs b/tests/function.rs index 234227e..de1f76c 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -92,7 +92,7 @@ fn test_c_function() -> Result<()> { 0 } - let func = unsafe { lua.create_c_function(c_function)? }; + let func = unsafe { lua.create_c_function(c_function, None)? }; func.call(())?; assert_eq!(lua.globals().get::<_, bool>("c_function")?, true);