Another major API change, out of stack space is not an Err

It, ahem "should not" be possible to exhaust lua stack space in normal usage,
and causing stack errors to be Err is slightly obnoxious.  I have been wanting
to make this change for a while, and removing the callback API from tables makes
this sensible *I think*.

I can think of a couple of ways that this is not technically true, but I think
that they are acceptable, or should be handled differently.

One, you can make arbitrarily sized LuaVariadic values.  I think this is maybe a
bug already, because there is an argument limit in Lua which is lower than the
stack limit.  I'm not sure what happens there, but if it is a stack based panic,
(or any panic?) it is a bug.

Two, I believe that if you recurse over and over between lua -> rust -> lua ->
rust etc, and call rlua API functions, you might get a stack panic.  I think for
trusted lua code, this is morally equivalent to a regular stack overflow in
plain rust, which is already.. well it's not a panic but it's some kind of safe
crash I'm not sure, so I think this is acceptable.  For *untrusted* lua code,
this could theoretically be a problem if the API provided a callback that would
call back into lua, then some lua script could force a stack based panic.  There
are so many concerns with untrusted lua code, and this library is NOT safe
enough yet for untrusted code (it doesn't even provide an option to limit lua to
the safe API subset yet!), so this is not currently an issue.  When the library
provides support for "safe lua", it should come with big warnings anyway, and
being able to force a stack panic is pretty minor in comparison.

I think if there are other ways to cause unbounded stack usage, that it is a
bug, or there can be an error just for that situation, like argument count
limits.

This commit also fixes several stupid bugs with tests, stack checking, and
panics.
This commit is contained in:
kyren 2017-06-25 16:52:32 -04:00
parent bf9bf849c2
commit d3b311fe49
7 changed files with 271 additions and 187 deletions

View file

@ -18,7 +18,7 @@ fn examples() -> LuaResult<()> {
// code is loaded. This API is based heavily around internal mutation (just // code is loaded. This API is based heavily around internal mutation (just
// like lua itself). // like lua itself).
let globals = lua.globals()?; let globals = lua.globals();
globals.set("string_var", "hello")?; globals.set("string_var", "hello")?;
globals.set("int_var", 42)?; globals.set("int_var", 42)?;
@ -43,13 +43,13 @@ fn examples() -> LuaResult<()> {
// You can create and manage lua tables // You can create and manage lua tables
let array_table = lua.create_table()?; let array_table = lua.create_table();
array_table.set(1, "one")?; array_table.set(1, "one")?;
array_table.set(2, "two")?; array_table.set(2, "two")?;
array_table.set(3, "three")?; array_table.set(3, "three")?;
assert_eq!(array_table.len()?, 3); assert_eq!(array_table.len()?, 3);
let map_table = lua.create_table()?; let map_table = lua.create_table();
map_table.set("one", 1)?; map_table.set("one", 1)?;
map_table.set("two", 2)?; map_table.set("two", 2)?;
map_table.set("three", 3)?; map_table.set("three", 3)?;
@ -104,7 +104,7 @@ fn examples() -> LuaResult<()> {
// be inferred as part of the function signature due to the same // be inferred as part of the function signature due to the same
// lifetime type signature limitations. // lifetime type signature limitations.
lua.pack(list1 == list2) lua.pack(list1 == list2)
})?; });
globals.set("check_equal", check_equal)?; globals.set("check_equal", check_equal)?;
// You can also accept variadic arguments to rust functions // You can also accept variadic arguments to rust functions
@ -112,7 +112,7 @@ fn examples() -> LuaResult<()> {
let strings = lua.unpack::<LuaVariadic<String>>(args)?.0; let strings = lua.unpack::<LuaVariadic<String>>(args)?.0;
// (This is quadratic!, it's just an example!) // (This is quadratic!, it's just an example!)
lua.pack(strings.iter().fold("".to_owned(), |a, b| a + b)) lua.pack(strings.iter().fold("".to_owned(), |a, b| a + b))
})?; });
globals.set("join", join)?; globals.set("join", join)?;
assert_eq!( assert_eq!(
@ -153,7 +153,7 @@ fn examples() -> LuaResult<()> {
let vec2_constructor = lua.create_function(|lua, args| { let vec2_constructor = lua.create_function(|lua, args| {
let hlist_pat![x, y] = lua.unpack::<HList![f32, f32]>(args)?; let hlist_pat![x, y] = lua.unpack::<HList![f32, f32]>(args)?;
lua.pack(Vec2(x, y)) lua.pack(Vec2(x, y))
})?; });
globals.set("vec2", vec2_constructor)?; globals.set("vec2", vec2_constructor)?;
assert!(lua.eval::<f32>("(vec2(1, 2) + vec2(2, 2)):magnitude()")? - 5.0 < f32::EPSILON); assert!(lua.eval::<f32>("(vec2(1, 2) + vec2(2, 2)):magnitude()")? - 5.0 < f32::EPSILON);

View file

@ -98,7 +98,7 @@ impl<'lua> FromLua<'lua> for LuaUserData<'lua> {
impl<'lua, T: LuaUserDataType> ToLua<'lua> for T { impl<'lua, T: LuaUserDataType> ToLua<'lua> for T {
fn to_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> { fn to_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
lua.create_userdata(self).map(LuaValue::UserData) Ok(LuaValue::UserData(lua.create_userdata(self)))
} }
} }
@ -167,7 +167,7 @@ impl<'lua> FromLua<'lua> for LightUserData {
impl<'lua> ToLua<'lua> for String { impl<'lua> ToLua<'lua> for String {
fn to_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> { fn to_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
Ok(LuaValue::String(lua.create_string(&self)?)) Ok(LuaValue::String(lua.create_string(&self)))
} }
} }
@ -179,7 +179,7 @@ impl<'lua> FromLua<'lua> for String {
impl<'lua, 'a> ToLua<'lua> for &'a str { impl<'lua, 'a> ToLua<'lua> for &'a str {
fn to_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> { fn to_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
Ok(LuaValue::String(lua.create_string(self)?)) Ok(LuaValue::String(lua.create_string(self)))
} }
} }

View file

@ -19,9 +19,6 @@ pub enum LuaError {
ToLuaConversionError(String), ToLuaConversionError(String),
/// A generic Lua -> Rust conversion error. /// A generic Lua -> Rust conversion error.
FromLuaConversionError(String), FromLuaConversionError(String),
/// Insufficient Lua stack space, only generated from rust when calling
/// `lua_checkstack`.
StackOverflow,
/// A `LuaThread` was resumed and the coroutine was no longer active. /// A `LuaThread` was resumed and the coroutine was no longer active.
CoroutineInactive, CoroutineInactive,
/// A `LuaUserData` is not the expected type in a borrow. /// A `LuaUserData` is not the expected type in a borrow.
@ -57,7 +54,6 @@ impl fmt::Display for LuaError {
&LuaError::FromLuaConversionError(ref msg) => { &LuaError::FromLuaConversionError(ref msg) => {
write!(fmt, "Error converting lua type to rust: {}", msg) write!(fmt, "Error converting lua type to rust: {}", msg)
} }
&LuaError::StackOverflow => write!(fmt, "Lua out of stack space"),
&LuaError::CoroutineInactive => write!(fmt, "Cannot resume inactive coroutine"), &LuaError::CoroutineInactive => write!(fmt, "Cannot resume inactive coroutine"),
&LuaError::UserDataTypeMismatch => write!(fmt, "Userdata not expected type"), &LuaError::UserDataTypeMismatch => write!(fmt, "Userdata not expected type"),
&LuaError::UserDataBorrowError => write!(fmt, "Userdata already mutably borrowed"), &LuaError::UserDataBorrowError => write!(fmt, "Userdata already mutably borrowed"),
@ -79,7 +75,6 @@ impl Error for LuaError {
&LuaError::ErrorError(_) => "lua error handling error", &LuaError::ErrorError(_) => "lua error handling error",
&LuaError::ToLuaConversionError(_) => "conversion error to lua", &LuaError::ToLuaConversionError(_) => "conversion error to lua",
&LuaError::FromLuaConversionError(_) => "conversion error from lua", &LuaError::FromLuaConversionError(_) => "conversion error from lua",
&LuaError::StackOverflow => "lua stack overflow",
&LuaError::CoroutineInactive => "lua coroutine inactive", &LuaError::CoroutineInactive => "lua coroutine inactive",
&LuaError::UserDataTypeMismatch => "lua userdata type mismatch", &LuaError::UserDataTypeMismatch => "lua userdata type mismatch",
&LuaError::UserDataBorrowError => "lua userdata already mutably borrowed", &LuaError::UserDataBorrowError => "lua userdata already mutably borrowed",

View file

@ -2,6 +2,7 @@
extern crate hlist_macro; extern crate hlist_macro;
pub mod ffi; pub mod ffi;
#[macro_use]
mod util; mod util;
mod error; mod error;
mod lua; mod lua;

View file

@ -185,7 +185,7 @@ impl<'lua> LuaString<'lua> {
/// # use rlua::*; /// # use rlua::*;
/// # fn main() { /// # fn main() {
/// let lua = Lua::new(); /// let lua = Lua::new();
/// let globals = lua.globals().unwrap(); /// let globals = lua.globals();
/// ///
/// let version: LuaString = globals.get("_VERSION").unwrap(); /// let version: LuaString = globals.get("_VERSION").unwrap();
/// assert!(version.to_str().unwrap().contains("Lua")); /// assert!(version.to_str().unwrap().contains("Lua"));
@ -197,8 +197,8 @@ impl<'lua> LuaString<'lua> {
pub fn to_str(&self) -> LuaResult<&str> { pub fn to_str(&self) -> LuaResult<&str> {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_guard(lua.state, 0, || { stack_err_guard(lua.state, 0, || {
check_stack(lua.state, 1)?; check_stack(lua.state, 1);
lua.push_ref(lua.state, &self.0); lua.push_ref(lua.state, &self.0);
assert_eq!(ffi::lua_type(lua.state, -1), ffi::LUA_TSTRING); assert_eq!(ffi::lua_type(lua.state, -1), ffi::LUA_TSTRING);
let s = CStr::from_ptr(ffi::lua_tostring(lua.state, -1)) let s = CStr::from_ptr(ffi::lua_tostring(lua.state, -1))
@ -227,7 +227,7 @@ impl<'lua> LuaTable<'lua> {
let key = key.to_lua(lua)?; let key = key.to_lua(lua)?;
let value = value.to_lua(lua)?; let value = value.to_lua(lua)?;
unsafe { unsafe {
check_stack(lua.state, 3)?; check_stack(lua.state, 3);
lua.push_ref(lua.state, &self.0); lua.push_ref(lua.state, &self.0);
lua.push_value(lua.state, key); lua.push_value(lua.state, key);
lua.push_value(lua.state, value); lua.push_value(lua.state, value);
@ -248,7 +248,7 @@ impl<'lua> LuaTable<'lua> {
let lua = self.0.lua; let lua = self.0.lua;
let key = key.to_lua(lua)?; let key = key.to_lua(lua)?;
unsafe { unsafe {
check_stack(lua.state, 2)?; check_stack(lua.state, 2);
lua.push_ref(lua.state, &self.0); lua.push_ref(lua.state, &self.0);
lua.push_value(lua.state, key.to_lua(lua)?); lua.push_value(lua.state, key.to_lua(lua)?);
let res = error_guard(lua.state, 2, 0, |state| { let res = error_guard(lua.state, 2, 0, |state| {
@ -266,7 +266,7 @@ impl<'lua> LuaTable<'lua> {
let lua = self.0.lua; let lua = self.0.lua;
let key = key.to_lua(lua)?; let key = key.to_lua(lua)?;
unsafe { unsafe {
check_stack(lua.state, 2)?; check_stack(lua.state, 2);
lua.push_ref(lua.state, &self.0); lua.push_ref(lua.state, &self.0);
lua.push_value(lua.state, key); lua.push_value(lua.state, key);
error_guard(lua.state, 2, 0, |state| { error_guard(lua.state, 2, 0, |state| {
@ -282,8 +282,8 @@ impl<'lua> LuaTable<'lua> {
pub fn raw_set<K: ToLua<'lua>, V: ToLua<'lua>>(&self, key: K, value: V) -> LuaResult<()> { pub fn raw_set<K: ToLua<'lua>, V: ToLua<'lua>>(&self, key: K, value: V) -> LuaResult<()> {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_guard(lua.state, 0, || { stack_err_guard(lua.state, 0, || {
check_stack(lua.state, 3)?; check_stack(lua.state, 3);
lua.push_ref(lua.state, &self.0); lua.push_ref(lua.state, &self.0);
lua.push_value(lua.state, key.to_lua(lua)?); lua.push_value(lua.state, key.to_lua(lua)?);
lua.push_value(lua.state, value.to_lua(lua)?); lua.push_value(lua.state, value.to_lua(lua)?);
@ -298,8 +298,8 @@ impl<'lua> LuaTable<'lua> {
pub fn raw_get<K: ToLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> LuaResult<V> { pub fn raw_get<K: ToLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> LuaResult<V> {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_guard(lua.state, 0, || { stack_err_guard(lua.state, 0, || {
check_stack(lua.state, 2)?; check_stack(lua.state, 2);
lua.push_ref(lua.state, &self.0); lua.push_ref(lua.state, &self.0);
lua.push_value(lua.state, key.to_lua(lua)?); lua.push_value(lua.state, key.to_lua(lua)?);
ffi::lua_gettable(lua.state, -2); ffi::lua_gettable(lua.state, -2);
@ -318,7 +318,7 @@ impl<'lua> LuaTable<'lua> {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
error_guard(lua.state, 0, 0, |state| { error_guard(lua.state, 0, 0, |state| {
check_stack(state, 1)?; check_stack(state, 1);
lua.push_ref(state, &self.0); lua.push_ref(state, &self.0);
Ok(ffi::luaL_len(state, -1)) Ok(ffi::luaL_len(state, -1))
}) })
@ -327,15 +327,15 @@ impl<'lua> LuaTable<'lua> {
/// Returns the result of the Lua `#` operator, without invoking the /// Returns the result of the Lua `#` operator, without invoking the
/// `__len` metamethod. /// `__len` metamethod.
pub fn raw_len(&self) -> LuaResult<LuaInteger> { pub fn raw_len(&self) -> LuaInteger {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_guard(lua.state, 0, || { stack_guard(lua.state, 0, || {
check_stack(lua.state, 1)?; check_stack(lua.state, 1);
lua.push_ref(lua.state, &self.0); lua.push_ref(lua.state, &self.0);
let len = ffi::lua_rawlen(lua.state, -1); let len = ffi::lua_rawlen(lua.state, -1);
ffi::lua_pop(lua.state, 1); ffi::lua_pop(lua.state, 1);
Ok(len as LuaInteger) len as LuaInteger
}) })
} }
} }
@ -389,9 +389,7 @@ where
let lua = self.table.lua; let lua = self.table.lua;
unsafe { unsafe {
if let Err(e) = check_stack(lua.state, 4) { check_stack(lua.state, 4);
return Some(Err(e));
}
lua.push_ref(lua.state, &self.table); lua.push_ref(lua.state, &self.table);
lua.push_ref(lua.state, &next_key); lua.push_ref(lua.state, &next_key);
@ -446,9 +444,7 @@ where
let lua = self.table.lua; let lua = self.table.lua;
unsafe { unsafe {
if let Err(e) = check_stack(lua.state, 2) { check_stack(lua.state, 2);
return Some(Err(e));
}
lua.push_ref(lua.state, &self.table); lua.push_ref(lua.state, &self.table);
match error_guard( match error_guard(
@ -489,10 +485,10 @@ impl<'lua> LuaFunction<'lua> {
pub fn call<A: ToLuaMulti<'lua>, R: FromLuaMulti<'lua>>(&self, args: A) -> LuaResult<R> { pub fn call<A: ToLuaMulti<'lua>, R: FromLuaMulti<'lua>>(&self, args: A) -> LuaResult<R> {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_guard(lua.state, 0, || { stack_err_guard(lua.state, 0, || {
let args = args.to_lua_multi(lua)?; let args = args.to_lua_multi(lua)?;
let nargs = args.len() as c_int; let nargs = args.len() as c_int;
check_stack(lua.state, nargs + 3)?; check_stack(lua.state, nargs + 3);
let stack_start = ffi::lua_gettop(lua.state); let stack_start = ffi::lua_gettop(lua.state);
lua.push_ref(lua.state, &self.0); lua.push_ref(lua.state, &self.0);
@ -532,7 +528,7 @@ impl<'lua> LuaFunction<'lua> {
/// ///
/// # fn main() { /// # fn main() {
/// let lua = Lua::new(); /// let lua = Lua::new();
/// let globals = lua.globals().unwrap(); /// let globals = lua.globals();
/// ///
/// // Bind the argument `123` to Lua's `tostring` function /// // Bind the argument `123` to Lua's `tostring` function
/// let tostring: LuaFunction = globals.get("tostring").unwrap(); /// let tostring: LuaFunction = globals.get("tostring").unwrap();
@ -548,7 +544,7 @@ impl<'lua> LuaFunction<'lua> {
let nargs = ffi::lua_gettop(state); let nargs = ffi::lua_gettop(state);
let nbinds = ffi::lua_tointeger(state, ffi::lua_upvalueindex(2)) as c_int; let nbinds = ffi::lua_tointeger(state, ffi::lua_upvalueindex(2)) as c_int;
check_stack(state, nbinds + 1).expect("not enough space to handle bound arguments"); check_stack(state, nbinds + 1);
ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1)); ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1));
ffi::lua_insert(state, 1); ffi::lua_insert(state, 1);
@ -565,11 +561,11 @@ impl<'lua> LuaFunction<'lua> {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_guard(lua.state, 0, || { stack_err_guard(lua.state, 0, || {
let args = args.to_lua_multi(lua)?; let args = args.to_lua_multi(lua)?;
let nargs = args.len() as c_int; let nargs = args.len() as c_int;
check_stack(lua.state, nargs + 2)?; check_stack(lua.state, nargs + 2);
lua.push_ref(lua.state, &self.0); lua.push_ref(lua.state, &self.0);
ffi::lua_pushinteger(lua.state, nargs as ffi::lua_Integer); ffi::lua_pushinteger(lua.state, nargs as ffi::lua_Integer);
for arg in args { for arg in args {
@ -653,8 +649,8 @@ impl<'lua> LuaThread<'lua> {
{ {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_guard(lua.state, 0, || { stack_err_guard(lua.state, 0, || {
check_stack(lua.state, 1)?; check_stack(lua.state, 1);
lua.push_ref(lua.state, &self.0); lua.push_ref(lua.state, &self.0);
let thread_state = ffi::lua_tothread(lua.state, -1); let thread_state = ffi::lua_tothread(lua.state, -1);
@ -668,7 +664,7 @@ impl<'lua> LuaThread<'lua> {
let args = args.to_lua_multi(lua)?; let args = args.to_lua_multi(lua)?;
let nargs = args.len() as c_int; let nargs = args.len() as c_int;
check_stack(thread_state, nargs)?; check_stack(thread_state, nargs);
for arg in args { for arg in args {
lua.push_value(thread_state, arg); lua.push_value(thread_state, arg);
@ -690,11 +686,11 @@ impl<'lua> LuaThread<'lua> {
} }
/// Gets the status of the thread. /// Gets the status of the thread.
pub fn status(&self) -> LuaResult<LuaThreadStatus> { pub fn status(&self) -> LuaThreadStatus {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_guard(lua.state, 0, || { stack_guard(lua.state, 0, || {
check_stack(lua.state, 1)?; check_stack(lua.state, 1);
lua.push_ref(lua.state, &self.0); lua.push_ref(lua.state, &self.0);
let thread_state = ffi::lua_tothread(lua.state, -1); let thread_state = ffi::lua_tothread(lua.state, -1);
@ -702,11 +698,11 @@ impl<'lua> LuaThread<'lua> {
let status = ffi::lua_status(thread_state); let status = ffi::lua_status(thread_state);
if status != ffi::LUA_OK && status != ffi::LUA_YIELD { if status != ffi::LUA_OK && status != ffi::LUA_YIELD {
Ok(LuaThreadStatus::Error) LuaThreadStatus::Error
} else if status == ffi::LUA_YIELD || ffi::lua_gettop(thread_state) > 0 { } else if status == ffi::LUA_YIELD || ffi::lua_gettop(thread_state) > 0 {
Ok(LuaThreadStatus::Active) LuaThreadStatus::Active
} else { } else {
Ok(LuaThreadStatus::Dead) LuaThreadStatus::Dead
} }
}) })
} }
@ -892,12 +888,8 @@ pub struct LuaUserData<'lua>(LuaRef<'lua>);
impl<'lua> LuaUserData<'lua> { impl<'lua> LuaUserData<'lua> {
/// Checks whether `T` is the type of this userdata. /// Checks whether `T` is the type of this userdata.
pub fn is<T: LuaUserDataType>(&self) -> LuaResult<bool> { pub fn is<T: LuaUserDataType>(&self) -> bool {
match self.inspect(|_: &RefCell<T>| Ok(())) { self.inspect(|_: &RefCell<T>| ()).is_some()
Ok(_) => Ok(true),
Err(LuaError::UserDataTypeMismatch) => Ok(false),
Err(err) => Err(err),
}
} }
/// Borrow this userdata out of the internal RefCell that is held in lua. /// Borrow this userdata out of the internal RefCell that is held in lua.
@ -906,7 +898,7 @@ impl<'lua> LuaUserData<'lua> {
Ok( Ok(
cell.try_borrow().map_err(|_| LuaError::UserDataBorrowError)?, cell.try_borrow().map_err(|_| LuaError::UserDataBorrowError)?,
) )
}) }).ok_or(LuaError::UserDataTypeMismatch)?
} }
/// Borrow mutably this userdata out of the internal RefCell that is held in /// Borrow mutably this userdata out of the internal RefCell that is held in
@ -916,24 +908,25 @@ impl<'lua> LuaUserData<'lua> {
Ok(cell.try_borrow_mut().map_err( Ok(cell.try_borrow_mut().map_err(
|_| LuaError::UserDataBorrowMutError, |_| LuaError::UserDataBorrowMutError,
)?) )?)
}) }).ok_or(LuaError::UserDataTypeMismatch)?
} }
fn inspect<'a, T, R, F>(&'a self, func: F) -> LuaResult<R> fn inspect<'a, T, R, F>(&'a self, func: F) -> Option<R>
where where
T: LuaUserDataType, T: LuaUserDataType,
F: FnOnce(&'a RefCell<T>) -> LuaResult<R>, F: FnOnce(&'a RefCell<T>) -> R,
{ {
unsafe { unsafe {
let lua = self.0.lua; let lua = self.0.lua;
stack_guard(lua.state, 0, move || { stack_guard(lua.state, 0, move || {
check_stack(lua.state, 3)?; check_stack(lua.state, 3);
lua.push_ref(lua.state, &self.0); lua.push_ref(lua.state, &self.0);
let userdata = ffi::lua_touserdata(lua.state, -1); let userdata = ffi::lua_touserdata(lua.state, -1);
assert!(!userdata.is_null()); lua_assert!(lua.state, !userdata.is_null());
assert!( lua_assert!(
lua.state,
ffi::lua_getmetatable(lua.state, -1) != 0, ffi::lua_getmetatable(lua.state, -1) != 0,
"LuaUserData missing metatable" "LuaUserData missing metatable"
); );
@ -941,16 +934,17 @@ impl<'lua> LuaUserData<'lua> {
ffi::lua_rawgeti( ffi::lua_rawgeti(
lua.state, lua.state,
ffi::LUA_REGISTRYINDEX, ffi::LUA_REGISTRYINDEX,
lua.userdata_metatable::<T>()? as ffi::lua_Integer, lua.userdata_metatable::<T>() as ffi::lua_Integer,
); );
if ffi::lua_rawequal(lua.state, -1, -2) == 0 { if ffi::lua_rawequal(lua.state, -1, -2) == 0 {
return Err(LuaError::UserDataTypeMismatch); ffi::lua_pop(lua.state, 3);
None
} else {
let res = func(&*(userdata as *const RefCell<T>));
ffi::lua_pop(lua.state, 3);
Some(res)
} }
let res = func(&*(userdata as *const RefCell<T>));
ffi::lua_pop(lua.state, 3);
res
}) })
} }
} }
@ -980,9 +974,12 @@ impl Lua {
pub fn new() -> Lua { pub fn new() -> Lua {
unsafe { unsafe {
let state = ffi::luaL_newstate(); let state = ffi::luaL_newstate();
ffi::luaL_openlibs(state);
stack_guard(state, 0, || { stack_guard(state, 0, || {
ffi::luaL_openlibs(state);
// Create the userdata registry table
ffi::lua_pushlightuserdata( ffi::lua_pushlightuserdata(
state, state,
&LUA_USERDATA_REGISTRY_KEY as *const u8 as *mut c_void, &LUA_USERDATA_REGISTRY_KEY as *const u8 as *mut c_void,
@ -1003,10 +1000,9 @@ impl Lua {
ffi::lua_setmetatable(state, -2); ffi::lua_setmetatable(state, -2);
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX); ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
Ok(())
}).unwrap();
stack_guard(state, 0, || { // Create the function metatable
ffi::lua_pushlightuserdata( ffi::lua_pushlightuserdata(
state, state,
&FUNCTION_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void, &FUNCTION_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
@ -1023,10 +1019,9 @@ impl Lua {
ffi::lua_rawset(state, -3); ffi::lua_rawset(state, -3);
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX); ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
Ok(())
}).unwrap();
stack_guard(state, 0, || { // Override pcall / xpcall
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS);
push_string(state, "pcall"); push_string(state, "pcall");
@ -1038,8 +1033,7 @@ impl Lua {
ffi::lua_rawset(state, -3); ffi::lua_rawset(state, -3);
ffi::lua_pop(state, 1); ffi::lua_pop(state, 1);
Ok(()) });
}).unwrap();
Lua { Lua {
state, state,
@ -1057,7 +1051,7 @@ impl Lua {
/// Equivalent to Lua's `load` function. /// Equivalent to Lua's `load` function.
pub fn load(&self, source: &str, name: Option<&str>) -> LuaResult<LuaFunction> { pub fn load(&self, source: &str, name: Option<&str>) -> LuaResult<LuaFunction> {
unsafe { unsafe {
stack_guard(self.state, 0, || { stack_err_guard(self.state, 0, || {
handle_error( handle_error(
self.state, self.state,
if let Some(name) = name { if let Some(name) = name {
@ -1105,7 +1099,7 @@ impl Lua {
/// to. Otherwise, returns the values returned by the chunk (if any). /// to. Otherwise, returns the values returned by the chunk (if any).
pub fn eval<'lua, R: FromLuaMulti<'lua>>(&'lua self, source: &str) -> LuaResult<R> { pub fn eval<'lua, R: FromLuaMulti<'lua>>(&'lua self, source: &str) -> LuaResult<R> {
unsafe { unsafe {
stack_guard(self.state, 0, || { stack_err_guard(self.state, 0, || {
// First, try interpreting the lua as an expression by adding // First, try interpreting the lua as an expression by adding
// "return", then as a statement. This is the same thing the // "return", then as a statement. This is the same thing the
// actual lua repl does. // actual lua repl does.
@ -1133,23 +1127,23 @@ impl Lua {
} }
/// Pass a `&str` slice to Lua, creating and returning a interned Lua string. /// Pass a `&str` slice to Lua, creating and returning a interned Lua string.
pub fn create_string(&self, s: &str) -> LuaResult<LuaString> { pub fn create_string(&self, s: &str) -> LuaString {
unsafe { unsafe {
stack_guard(self.state, 0, || { stack_guard(self.state, 0, || {
check_stack(self.state, 1)?; check_stack(self.state, 1);
ffi::lua_pushlstring(self.state, s.as_ptr() as *const c_char, s.len()); ffi::lua_pushlstring(self.state, s.as_ptr() as *const c_char, s.len());
Ok(LuaString(self.pop_ref(self.state))) LuaString(self.pop_ref(self.state))
}) })
} }
} }
/// Creates and returns a new table. /// Creates and returns a new table.
pub fn create_table(&self) -> LuaResult<LuaTable> { pub fn create_table(&self) -> LuaTable {
unsafe { unsafe {
stack_guard(self.state, 0, || { stack_guard(self.state, 0, || {
check_stack(self.state, 1)?; check_stack(self.state, 1);
ffi::lua_newtable(self.state); ffi::lua_newtable(self.state);
Ok(LuaTable(self.pop_ref(self.state))) LuaTable(self.pop_ref(self.state))
}) })
} }
} }
@ -1162,8 +1156,8 @@ impl Lua {
I: IntoIterator<Item = (K, V)>, I: IntoIterator<Item = (K, V)>,
{ {
unsafe { unsafe {
stack_guard(self.state, 0, || { stack_err_guard(self.state, 0, || {
check_stack(self.state, 3)?; check_stack(self.state, 3);
ffi::lua_newtable(self.state); ffi::lua_newtable(self.state);
for (k, v) in cont { for (k, v) in cont {
@ -1186,7 +1180,7 @@ impl Lua {
} }
/// Wraps a Rust function or closure, creating a callable Lua function handle to it. /// Wraps a Rust function or closure, creating a callable Lua function handle to it.
pub fn create_function<F>(&self, func: F) -> LuaResult<LuaFunction> pub fn create_function<F>(&self, func: F) -> LuaFunction
where where
F: 'static + for<'a> FnMut(&'a Lua, LuaMultiValue<'a>) -> LuaResult<LuaMultiValue<'a>>, F: 'static + for<'a> FnMut(&'a Lua, LuaMultiValue<'a>) -> LuaResult<LuaMultiValue<'a>>,
{ {
@ -1196,27 +1190,27 @@ impl Lua {
/// Wraps a Lua function into a new thread (or coroutine). /// Wraps a Lua function into a new thread (or coroutine).
/// ///
/// Equivalent to `coroutine.create`. /// Equivalent to `coroutine.create`.
pub fn create_thread<'lua>(&'lua self, func: LuaFunction<'lua>) -> LuaResult<LuaThread<'lua>> { pub fn create_thread<'lua>(&'lua self, func: LuaFunction<'lua>) -> LuaThread<'lua> {
unsafe { unsafe {
stack_guard(self.state, 0, move || { stack_guard(self.state, 0, move || {
check_stack(self.state, 1)?; check_stack(self.state, 1);
let thread_state = ffi::lua_newthread(self.state); let thread_state = ffi::lua_newthread(self.state);
self.push_ref(thread_state, &func.0); self.push_ref(thread_state, &func.0);
Ok(LuaThread(self.pop_ref(self.state))) LuaThread(self.pop_ref(self.state))
}) })
} }
} }
/// Create a Lua userdata object from a custom userdata type. /// Create a Lua userdata object from a custom userdata type.
pub fn create_userdata<T>(&self, data: T) -> LuaResult<LuaUserData> pub fn create_userdata<T>(&self, data: T) -> LuaUserData
where where
T: LuaUserDataType, T: LuaUserDataType,
{ {
unsafe { unsafe {
stack_guard(self.state, 0, move || { stack_guard(self.state, 0, move || {
check_stack(self.state, 2)?; check_stack(self.state, 2);
let data = RefCell::new(data); let data = RefCell::new(data);
let data_userdata = let data_userdata =
@ -1227,22 +1221,24 @@ impl Lua {
ffi::lua_rawgeti( ffi::lua_rawgeti(
self.state, self.state,
ffi::LUA_REGISTRYINDEX, ffi::LUA_REGISTRYINDEX,
self.userdata_metatable::<T>()? as ffi::lua_Integer, self.userdata_metatable::<T>() as ffi::lua_Integer,
); );
ffi::lua_setmetatable(self.state, -2); ffi::lua_setmetatable(self.state, -2);
Ok(LuaUserData(self.pop_ref(self.state))) LuaUserData(self.pop_ref(self.state))
}) })
} }
} }
/// Returns a handle to the global environment. /// Returns a handle to the global environment.
pub fn globals(&self) -> LuaResult<LuaTable> { pub fn globals(&self) -> LuaTable {
unsafe { unsafe {
check_stack(self.state, 1)?; stack_guard(self.state, 0, move || {
ffi::lua_rawgeti(self.state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); check_stack(self.state, 1);
Ok(LuaTable(self.pop_ref(self.state))) ffi::lua_rawgeti(self.state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS);
LuaTable(self.pop_ref(self.state))
})
} }
} }
@ -1254,9 +1250,10 @@ impl Lua {
LuaValue::String(s) => Ok(s), LuaValue::String(s) => Ok(s),
v => unsafe { v => unsafe {
stack_guard(self.state, 0, || { stack_guard(self.state, 0, || {
check_stack(self.state, 1)?; check_stack(self.state, 1);
self.push_value(self.state, v); self.push_value(self.state, v);
if ffi::lua_tostring(self.state, -1).is_null() { if ffi::lua_tostring(self.state, -1).is_null() {
ffi::lua_pop(self.state, 1);
Err(LuaError::FromLuaConversionError( Err(LuaError::FromLuaConversionError(
"cannot convert lua value to string".to_owned(), "cannot convert lua value to string".to_owned(),
)) ))
@ -1277,16 +1274,16 @@ impl Lua {
LuaValue::Integer(i) => Ok(i), LuaValue::Integer(i) => Ok(i),
v => unsafe { v => unsafe {
stack_guard(self.state, 0, || { stack_guard(self.state, 0, || {
check_stack(self.state, 1)?; check_stack(self.state, 1);
self.push_value(self.state, v); self.push_value(self.state, v);
let mut isint = 0; let mut isint = 0;
let i = ffi::lua_tointegerx(self.state, -1, &mut isint); let i = ffi::lua_tointegerx(self.state, -1, &mut isint);
ffi::lua_pop(self.state, 1);
if isint == 0 { if isint == 0 {
Err(LuaError::FromLuaConversionError( Err(LuaError::FromLuaConversionError(
"cannot convert lua value to integer".to_owned(), "cannot convert lua value to integer".to_owned(),
)) ))
} else { } else {
ffi::lua_pop(self.state, 1);
Ok(i) Ok(i)
} }
}) })
@ -1303,16 +1300,16 @@ impl Lua {
LuaValue::Number(n) => Ok(n), LuaValue::Number(n) => Ok(n),
v => unsafe { v => unsafe {
stack_guard(self.state, 0, || { stack_guard(self.state, 0, || {
check_stack(self.state, 1)?; check_stack(self.state, 1);
self.push_value(self.state, v); self.push_value(self.state, v);
let mut isnum = 0; let mut isnum = 0;
let n = ffi::lua_tonumberx(self.state, -1, &mut isnum); let n = ffi::lua_tonumberx(self.state, -1, &mut isnum);
ffi::lua_pop(self.state, 1);
if isnum == 0 { if isnum == 0 {
Err(LuaError::FromLuaConversionError( Err(LuaError::FromLuaConversionError(
"cannot convert lua value to number".to_owned(), "cannot convert lua value to number".to_owned(),
)) ))
} else { } else {
ffi::lua_pop(self.state, 1);
Ok(n) Ok(n)
} }
}) })
@ -1345,7 +1342,7 @@ impl Lua {
T::from_lua_multi(value, self) T::from_lua_multi(value, self)
} }
fn create_callback_function(&self, func: LuaCallback) -> LuaResult<LuaFunction> { fn create_callback_function(&self, func: LuaCallback) -> LuaFunction {
unsafe extern "C" fn callback_call_impl(state: *mut ffi::lua_State) -> c_int { unsafe extern "C" fn callback_call_impl(state: *mut ffi::lua_State) -> c_int {
callback_error(state, || { callback_error(state, || {
let lua = Lua { let lua = Lua {
@ -1376,7 +1373,7 @@ impl Lua {
unsafe { unsafe {
stack_guard(self.state, 0, move || { stack_guard(self.state, 0, move || {
check_stack(self.state, 2)?; check_stack(self.state, 2);
let func_userdata = let func_userdata =
ffi::lua_newuserdata(self.state, mem::size_of::<LuaCallback>()) as ffi::lua_newuserdata(self.state, mem::size_of::<LuaCallback>()) as
@ -1392,7 +1389,7 @@ impl Lua {
ffi::lua_pushcclosure(self.state, callback_call_impl, 1); ffi::lua_pushcclosure(self.state, callback_call_impl, 1);
Ok(LuaFunction(self.pop_ref(self.state))) LuaFunction(self.pop_ref(self.state))
}) })
} }
} }
@ -1496,7 +1493,7 @@ impl Lua {
ffi::LUA_TTHREAD => LuaValue::Thread(LuaThread(self.pop_ref(state))), ffi::LUA_TTHREAD => LuaValue::Thread(LuaThread(self.pop_ref(state))),
_ => panic!("LUA_TNONE in pop_value"), _ => unreachable!("internal error: LUA_TNONE in pop_value"),
} }
} }
@ -1527,7 +1524,7 @@ impl Lua {
} }
} }
unsafe fn userdata_metatable<T: LuaUserDataType>(&self) -> LuaResult<c_int> { unsafe fn userdata_metatable<T: LuaUserDataType>(&self) -> c_int {
// Used if both an __index metamethod is set and regular methods, checks methods table // Used if both an __index metamethod is set and regular methods, checks methods table
// first, then __index metamethod. // first, then __index metamethod.
unsafe extern "C" fn meta_index_impl(state: *mut ffi::lua_State) -> c_int { unsafe extern "C" fn meta_index_impl(state: *mut ffi::lua_State) -> c_int {
@ -1547,7 +1544,7 @@ impl Lua {
} }
stack_guard(self.state, 0, move || { stack_guard(self.state, 0, move || {
check_stack(self.state, 3)?; check_stack(self.state, 5);
ffi::lua_pushlightuserdata( ffi::lua_pushlightuserdata(
self.state, self.state,
@ -1560,7 +1557,7 @@ impl Lua {
ffi::lua_pop(self.state, 1); ffi::lua_pop(self.state, 1);
match map.entry(TypeId::of::<T>()) { match map.entry(TypeId::of::<T>()) {
HashMapEntry::Occupied(entry) => Ok(*entry.get()), HashMapEntry::Occupied(entry) => *entry.get(),
HashMapEntry::Vacant(entry) => { HashMapEntry::Vacant(entry) => {
ffi::lua_newtable(self.state); ffi::lua_newtable(self.state);
@ -1577,12 +1574,11 @@ impl Lua {
push_string(self.state, "__index"); push_string(self.state, "__index");
ffi::lua_newtable(self.state); ffi::lua_newtable(self.state);
check_stack(self.state, methods.methods.len() as c_int * 2)?;
for (k, m) in methods.methods { for (k, m) in methods.methods {
push_string(self.state, &k); push_string(self.state, &k);
self.push_value( self.push_value(
self.state, self.state,
LuaValue::Function(self.create_callback_function(m)?), LuaValue::Function(self.create_callback_function(m)),
); );
ffi::lua_rawset(self.state, -3); ffi::lua_rawset(self.state, -3);
} }
@ -1590,7 +1586,6 @@ impl Lua {
ffi::lua_rawset(self.state, -3); ffi::lua_rawset(self.state, -3);
} }
check_stack(self.state, methods.meta_methods.len() as c_int * 2)?;
for (k, m) in methods.meta_methods { for (k, m) in methods.meta_methods {
if k == LuaMetaMethod::Index && has_methods { if k == LuaMetaMethod::Index && has_methods {
push_string(self.state, "__index"); push_string(self.state, "__index");
@ -1598,7 +1593,7 @@ impl Lua {
ffi::lua_gettable(self.state, -3); ffi::lua_gettable(self.state, -3);
self.push_value( self.push_value(
self.state, self.state,
LuaValue::Function(self.create_callback_function(m)?), LuaValue::Function(self.create_callback_function(m)),
); );
ffi::lua_pushcclosure(self.state, meta_index_impl, 2); ffi::lua_pushcclosure(self.state, meta_index_impl, 2);
ffi::lua_rawset(self.state, -3); ffi::lua_rawset(self.state, -3);
@ -1631,7 +1626,7 @@ impl Lua {
push_string(self.state, name); push_string(self.state, name);
self.push_value( self.push_value(
self.state, self.state,
LuaValue::Function(self.create_callback_function(m)?), LuaValue::Function(self.create_callback_function(m)),
); );
ffi::lua_rawset(self.state, -3); ffi::lua_rawset(self.state, -3);
} }
@ -1647,7 +1642,7 @@ impl Lua {
let id = ffi::luaL_ref(self.state, ffi::LUA_REGISTRYINDEX); let id = ffi::luaL_ref(self.state, ffi::LUA_REGISTRYINDEX);
entry.insert(id); entry.insert(id);
Ok(id) id
} }
} }
}) })

View file

@ -9,7 +9,7 @@ use super::*;
#[test] #[test]
fn test_set_get() { fn test_set_get() {
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals().unwrap(); let globals = lua.globals();
globals.set("foo", "bar").unwrap(); globals.set("foo", "bar").unwrap();
globals.set("baz", "baf").unwrap(); globals.set("baz", "baf").unwrap();
assert_eq!(globals.get::<_, String>("foo").unwrap(), "bar"); assert_eq!(globals.get::<_, String>("foo").unwrap(), "bar");
@ -29,7 +29,7 @@ fn test_load() {
#[test] #[test]
fn test_exec() { fn test_exec() {
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals().unwrap(); let globals = lua.globals();
lua.exec::<()>( lua.exec::<()>(
r#" r#"
res = 'foo'..'bar' res = 'foo'..'bar'
@ -76,9 +76,9 @@ fn test_eval() {
#[test] #[test]
fn test_table() { fn test_table() {
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals().unwrap(); let globals = lua.globals();
globals.set("table", lua.create_table().unwrap()).unwrap(); globals.set("table", lua.create_table()).unwrap();
let table1: LuaTable = globals.get("table").unwrap(); let table1: LuaTable = globals.get("table").unwrap();
let table2: LuaTable = globals.get("table").unwrap(); let table2: LuaTable = globals.get("table").unwrap();
@ -164,7 +164,7 @@ fn test_table() {
#[test] #[test]
fn test_function() { fn test_function() {
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals().unwrap(); let globals = lua.globals();
lua.exec::<()>( lua.exec::<()>(
r#" r#"
function concat(arg1, arg2) function concat(arg1, arg2)
@ -184,7 +184,7 @@ fn test_function() {
#[test] #[test]
fn test_bind() { fn test_bind() {
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals().unwrap(); let globals = lua.globals();
lua.exec::<()>( lua.exec::<()>(
r#" r#"
function concat(...) function concat(...)
@ -211,7 +211,7 @@ fn test_bind() {
#[test] #[test]
fn test_rust_function() { fn test_rust_function() {
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals().unwrap(); let globals = lua.globals();
lua.exec::<()>( lua.exec::<()>(
r#" r#"
function lua_function() function lua_function()
@ -225,7 +225,7 @@ fn test_rust_function() {
).unwrap(); ).unwrap();
let lua_function = globals.get::<_, LuaFunction>("lua_function").unwrap(); let lua_function = globals.get::<_, LuaFunction>("lua_function").unwrap();
let rust_function = lua.create_function(|lua, _| lua.pack("hello")).unwrap(); let rust_function = lua.create_function(|lua, _| lua.pack("hello"));
globals.set("rust_function", rust_function).unwrap(); globals.set("rust_function", rust_function).unwrap();
assert_eq!(lua_function.call::<_, String>(()).unwrap(), "hello"); assert_eq!(lua_function.call::<_, String>(()).unwrap(), "hello");
@ -241,13 +241,13 @@ fn test_user_data() {
let lua = Lua::new(); let lua = Lua::new();
let userdata1 = lua.create_userdata(UserData1(1)).unwrap(); let userdata1 = lua.create_userdata(UserData1(1));
let userdata2 = lua.create_userdata(UserData2(Box::new(2))).unwrap(); let userdata2 = lua.create_userdata(UserData2(Box::new(2)));
assert!(userdata1.is::<UserData1>().unwrap()); assert!(userdata1.is::<UserData1>());
assert!(!userdata1.is::<UserData2>().unwrap()); assert!(!userdata1.is::<UserData2>());
assert!(userdata2.is::<UserData2>().unwrap()); assert!(userdata2.is::<UserData2>());
assert!(!userdata2.is::<UserData1>().unwrap()); assert!(!userdata2.is::<UserData1>());
assert_eq!(userdata1.borrow::<UserData1>().unwrap().0, 1); assert_eq!(userdata1.borrow::<UserData1>().unwrap().0, 1);
assert_eq!(*userdata2.borrow::<UserData2>().unwrap().0, 2); assert_eq!(*userdata2.borrow::<UserData2>().unwrap().0, 2);
@ -268,8 +268,8 @@ fn test_methods() {
} }
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals().unwrap(); let globals = lua.globals();
let userdata = lua.create_userdata(UserData(42)).unwrap(); let userdata = lua.create_userdata(UserData(42));
globals.set("userdata", userdata.clone()).unwrap(); globals.set("userdata", userdata.clone()).unwrap();
lua.exec::<()>( lua.exec::<()>(
r#" r#"
@ -320,7 +320,7 @@ fn test_metamethods() {
} }
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals().unwrap(); let globals = lua.globals();
globals.set("userdata1", UserData(7)).unwrap(); globals.set("userdata1", UserData(7)).unwrap();
globals.set("userdata2", UserData(3)).unwrap(); globals.set("userdata2", UserData(3)).unwrap();
assert_eq!(lua.eval::<UserData>("userdata1 + userdata2").unwrap().0, 10); assert_eq!(lua.eval::<UserData>("userdata1 + userdata2").unwrap().0, 10);
@ -333,7 +333,7 @@ fn test_metamethods() {
#[test] #[test]
fn test_scope() { fn test_scope() {
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals().unwrap(); let globals = lua.globals();
lua.exec::<()>( lua.exec::<()>(
r#" r#"
touter = { touter = {
@ -369,7 +369,7 @@ fn test_scope() {
#[test] #[test]
fn test_lua_multi() { fn test_lua_multi() {
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals().unwrap(); let globals = lua.globals();
lua.exec::<()>( lua.exec::<()>(
r#" r#"
function concat(arg1, arg2) function concat(arg1, arg2)
@ -401,7 +401,7 @@ fn test_lua_multi() {
#[test] #[test]
fn test_coercion() { fn test_coercion() {
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals().unwrap(); let globals = lua.globals();
lua.exec::<()>( lua.exec::<()>(
r#" r#"
int = 123 int = 123
@ -438,7 +438,7 @@ fn test_error() {
} }
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals().unwrap(); let globals = lua.globals();
lua.exec::<()>( lua.exec::<()>(
r#" r#"
function no_error() function no_error()
@ -492,8 +492,7 @@ fn test_error() {
None, None,
).unwrap(); ).unwrap();
let rust_error_function = lua.create_function(|_, _| Err(TestError.to_lua_err())) let rust_error_function = lua.create_function(|_, _| Err(TestError.to_lua_err()));
.unwrap();
globals globals
.set("rust_error_function", rust_error_function) .set("rust_error_function", rust_error_function)
.unwrap(); .unwrap();
@ -546,6 +545,8 @@ fn test_error() {
match catch_unwind(|| -> LuaResult<()> { match catch_unwind(|| -> LuaResult<()> {
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals();
lua.exec::<()>( lua.exec::<()>(
r#" r#"
function rust_panic() function rust_panic()
@ -556,7 +557,7 @@ fn test_error() {
)?; )?;
let rust_panic_function = lua.create_function(|_, _| { let rust_panic_function = lua.create_function(|_, _| {
panic!("expected panic, this panic should be caught in rust") panic!("expected panic, this panic should be caught in rust")
})?; });
globals.set("rust_panic_function", rust_panic_function)?; globals.set("rust_panic_function", rust_panic_function)?;
let rust_panic = globals.get::<_, LuaFunction>("rust_panic")?; let rust_panic = globals.get::<_, LuaFunction>("rust_panic")?;
@ -570,6 +571,8 @@ fn test_error() {
match catch_unwind(|| -> LuaResult<()> { match catch_unwind(|| -> LuaResult<()> {
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals();
lua.exec::<()>( lua.exec::<()>(
r#" r#"
function rust_panic() function rust_panic()
@ -580,7 +583,7 @@ fn test_error() {
)?; )?;
let rust_panic_function = lua.create_function(|_, _| { let rust_panic_function = lua.create_function(|_, _| {
panic!("expected panic, this panic should be caught in rust") panic!("expected panic, this panic should be caught in rust")
})?; });
globals.set("rust_panic_function", rust_panic_function)?; globals.set("rust_panic_function", rust_panic_function)?;
let rust_panic = globals.get::<_, LuaFunction>("rust_panic")?; let rust_panic = globals.get::<_, LuaFunction>("rust_panic")?;
@ -608,19 +611,19 @@ fn test_thread() {
end end
"#, "#,
).unwrap(), ).unwrap(),
).unwrap(); );
assert_eq!(thread.status().unwrap(), LuaThreadStatus::Active); assert_eq!(thread.status(), LuaThreadStatus::Active);
assert_eq!(thread.resume::<_, i64>(0).unwrap(), 0); assert_eq!(thread.resume::<_, i64>(0).unwrap(), 0);
assert_eq!(thread.status().unwrap(), LuaThreadStatus::Active); assert_eq!(thread.status(), LuaThreadStatus::Active);
assert_eq!(thread.resume::<_, i64>(1).unwrap(), 1); assert_eq!(thread.resume::<_, i64>(1).unwrap(), 1);
assert_eq!(thread.status().unwrap(), LuaThreadStatus::Active); assert_eq!(thread.status(), LuaThreadStatus::Active);
assert_eq!(thread.resume::<_, i64>(2).unwrap(), 3); assert_eq!(thread.resume::<_, i64>(2).unwrap(), 3);
assert_eq!(thread.status().unwrap(), LuaThreadStatus::Active); assert_eq!(thread.status(), LuaThreadStatus::Active);
assert_eq!(thread.resume::<_, i64>(3).unwrap(), 6); assert_eq!(thread.resume::<_, i64>(3).unwrap(), 6);
assert_eq!(thread.status().unwrap(), LuaThreadStatus::Active); assert_eq!(thread.status(), LuaThreadStatus::Active);
assert_eq!(thread.resume::<_, i64>(4).unwrap(), 10); assert_eq!(thread.resume::<_, i64>(4).unwrap(), 10);
assert_eq!(thread.status().unwrap(), LuaThreadStatus::Dead); assert_eq!(thread.status(), LuaThreadStatus::Dead);
let accumulate = lua.create_thread( let accumulate = lua.create_thread(
lua.eval::<LuaFunction>( lua.eval::<LuaFunction>(
@ -632,15 +635,15 @@ fn test_thread() {
end end
"#, "#,
).unwrap(), ).unwrap(),
).unwrap(); );
for i in 0..4 { for i in 0..4 {
accumulate.resume::<_, ()>(i).unwrap(); accumulate.resume::<_, ()>(i).unwrap();
} }
assert_eq!(accumulate.resume::<_, i64>(4).unwrap(), 10); assert_eq!(accumulate.resume::<_, i64>(4).unwrap(), 10);
assert_eq!(accumulate.status().unwrap(), LuaThreadStatus::Active); assert_eq!(accumulate.status(), LuaThreadStatus::Active);
assert!(accumulate.resume::<_, ()>("error").is_err()); assert!(accumulate.resume::<_, ()>("error").is_err());
assert_eq!(accumulate.status().unwrap(), LuaThreadStatus::Error); assert_eq!(accumulate.status(), LuaThreadStatus::Error);
let thread = lua.eval::<LuaThread>( let thread = lua.eval::<LuaThread>(
r#" r#"
@ -651,7 +654,7 @@ fn test_thread() {
end) end)
"#, "#,
).unwrap(); ).unwrap();
assert_eq!(thread.status().unwrap(), LuaThreadStatus::Active); assert_eq!(thread.status(), LuaThreadStatus::Active);
assert_eq!(thread.resume::<_, i64>(()).unwrap(), 42); assert_eq!(thread.resume::<_, i64>(()).unwrap(), 42);
let thread: LuaThread = lua.eval( let thread: LuaThread = lua.eval(
@ -678,7 +681,7 @@ fn test_thread() {
#[test] #[test]
fn test_lightuserdata() { fn test_lightuserdata() {
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals().unwrap(); let globals = lua.globals();
lua.exec::<()>( lua.exec::<()>(
r#" r#"
function id(a) function id(a)
@ -698,7 +701,7 @@ fn test_lightuserdata() {
#[test] #[test]
fn test_table_error() { fn test_table_error() {
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals().unwrap(); let globals = lua.globals();
lua.exec::<()>( lua.exec::<()>(
r#" r#"
table = {} table = {}
@ -723,21 +726,20 @@ fn test_table_error() {
assert!(bad_table.len().is_err()); assert!(bad_table.len().is_err());
assert!(bad_table.raw_set(1, 1).is_ok()); assert!(bad_table.raw_set(1, 1).is_ok());
assert!(bad_table.raw_get::<_, i32>(1).is_ok()); assert!(bad_table.raw_get::<_, i32>(1).is_ok());
assert_eq!(bad_table.raw_len().unwrap(), 1); assert_eq!(bad_table.raw_len(), 1);
} }
#[test] #[test]
fn test_result_conversions() { fn test_result_conversions() {
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals().unwrap(); let globals = lua.globals();
let err = lua.create_function(|lua, _| { let err = lua.create_function(|lua, _| {
lua.pack(Result::Err::<String, _>( lua.pack(Result::Err::<String, _>(
"only through failure can we succeed".to_lua_err(), "only through failure can we succeed".to_lua_err(),
)) ))
}).unwrap(); });
let ok = lua.create_function(|lua, _| lua.pack(Result::Ok::<_, LuaError>("!".to_owned()))) let ok = lua.create_function(|lua, _| lua.pack(Result::Ok::<_, LuaError>("!".to_owned())));
.unwrap();
globals.set("err", err).unwrap(); globals.set("err", err).unwrap();
globals.set("ok", ok).unwrap(); globals.set("ok", ok).unwrap();
@ -759,7 +761,7 @@ fn test_result_conversions() {
#[test] #[test]
fn test_num_conversion() { fn test_num_conversion() {
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals().unwrap(); let globals = lua.globals();
globals.set("n", "1.0").unwrap(); globals.set("n", "1.0").unwrap();
assert_eq!(globals.get::<_, i64>("n").unwrap(), 1); assert_eq!(globals.get::<_, i64>("n").unwrap(), 1);

View file

@ -16,50 +16,138 @@ macro_rules! cstr {
); );
} }
pub unsafe fn check_stack(state: *mut ffi::lua_State, amount: c_int) -> LuaResult<()> { // A panic that clears the given lua stack before panicking
if ffi::lua_checkstack(state, amount) == 0 { macro_rules! lua_panic {
Err(LuaError::StackOverflow) ($state:expr) => {
} else { {
Ok(()) $crate::ffi::lua_settop($state, 0);
} panic!("rlua internal error");
}
};
($state:expr, $msg:expr) => {
{
$crate::ffi::lua_settop($state, 0);
panic!(concat!("rlua: ", $msg));
}
};
($state:expr, $fmt:expr, $($arg:tt)+) => {
{
$crate::ffi::lua_settop($state, 0);
panic!(concat!("rlua: ", $fmt), $($arg)+);
}
};
} }
// Run an operation on a lua_State and automatically clean up the stack before returning. Takes // An assert that clears the given lua stack before panicking
// the lua_State, the expected stack size change, and an operation to run. If the operation macro_rules! lua_assert {
// results in success, then the stack is inspected to make sure the change in stack size matches ($state:expr, $cond:expr) => {
// the expected change and otherwise this is a logic error and will panic. If the operation if !$cond {
// results in an error, the stack is shrunk to the value before the call. If the operation $crate::ffi::lua_settop($state, 0);
// results in an error and the stack is smaller than the value before the call, then this is panic!("rlua internal error");
// unrecoverable and this will panic. }
pub unsafe fn stack_guard<F, R>(state: *mut ffi::lua_State, change: c_int, op: F) -> LuaResult<R> };
($state:expr, $cond:expr, $msg:expr) => {
if !$cond {
$crate::ffi::lua_settop($state, 0);
panic!(concat!("rlua: ", $msg));
}
};
($state:expr, $cond:expr, $fmt:expr, $($arg:tt)+) => {
if !$cond {
$crate::ffi::lua_settop($state, 0);
panic!(concat!("rlua: ", $fmt), $($arg)+);
}
};
}
// Checks that Lua has enough free stack space for future stack operations.
// On failure, this will clear the stack and panic.
pub unsafe fn check_stack(state: *mut ffi::lua_State, amount: c_int) {
lua_assert!(
state,
ffi::lua_checkstack(state, amount) != 0,
"out of stack space"
);
}
// Run an operation on a lua_State and check that the stack change is what is
// expected. If the stack change does not match, clears the stack and panics.
pub unsafe fn stack_guard<F, R>(state: *mut ffi::lua_State, change: c_int, op: F) -> R
where
F: FnOnce() -> R,
{
let expected = ffi::lua_gettop(state) + change;
lua_assert!(
state,
expected >= 0,
"internal stack error: too many values would be popped"
);
let res = op();
let top = ffi::lua_gettop(state);
lua_assert!(
state,
ffi::lua_gettop(state) == expected,
"internal stack error: expected stack to be {}, got {}",
expected,
top
);
res
}
// Run an operation on a lua_State and automatically clean up the stack before
// returning. Takes the lua_State, the expected stack size change, and an
// operation to run. If the operation results in success, then the stack is
// inspected to make sure the change in stack size matches the expected change
// and otherwise this is a logic error and will panic. If the operation results
// in an error, the stack is shrunk to the value before the call. If the
// operation results in an error and the stack is smaller than the value before
// the call, then this is unrecoverable and this will panic. If this function
// panics, it will clear the stack before panicking.
pub unsafe fn stack_err_guard<F, R>(
state: *mut ffi::lua_State,
change: c_int,
op: F,
) -> LuaResult<R>
where where
F: FnOnce() -> LuaResult<R>, F: FnOnce() -> LuaResult<R>,
{ {
let expected = ffi::lua_gettop(state) + change; let expected = ffi::lua_gettop(state) + change;
assert!( lua_assert!(
state,
expected >= 0, expected >= 0,
"lua stack error, too many values would be popped" "internal stack error: too many values would be popped"
); );
let res = op(); let res = op();
let top = ffi::lua_gettop(state); let top = ffi::lua_gettop(state);
if res.is_ok() { if res.is_ok() {
assert_eq!( lua_assert!(
ffi::lua_gettop(state), state,
expected, ffi::lua_gettop(state) == expected,
"lua stack error, expected stack to be {}, got {}", "internal stack error: expected stack to be {}, got {}",
expected, expected,
top top
); );
} else { } else {
assert!( lua_assert!(
state,
top >= expected, top >= expected,
"lua stack error, {} too many values popped", "internal stack error: {} too many values popped",
top - expected top - expected
); );
if top > expected { if top > expected {
ffi::lua_settop(state, expected); ffi::lua_settop(state, expected);
} }
} }
res res
} }
@ -127,9 +215,12 @@ pub unsafe fn handle_error(state: *mut ffi::lua_State, err: c_int) -> LuaResult<
} else if is_wrapped_panic(state, -1) { } else if is_wrapped_panic(state, -1) {
let userdata = ffi::lua_touserdata(state, -1); let userdata = ffi::lua_touserdata(state, -1);
let panic = &mut *(userdata as *mut WrappedPanic); let panic = &mut *(userdata as *mut WrappedPanic);
resume_unwind(panic.0.take().expect( if let Some(p) = panic.0.take() {
"internal error: panic was resumed twice", ffi::lua_settop(state, 0);
)) resume_unwind(p);
} else {
lua_panic!(state, "internal error: panic was resumed twice")
}
} else { } else {
let err_string = let err_string =
@ -174,7 +265,7 @@ pub unsafe fn handle_error(state: *mut ffi::lua_State, err: c_int) -> LuaResult<
println!("Lua error during __gc, aborting!"); println!("Lua error during __gc, aborting!");
process::abort() process::abort()
} }
_ => panic!("unrecognized lua error code"), _ => lua_panic!(state, "internal error: unrecognized lua error code"),
}) })
} }
} }