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
// like lua itself).
let globals = lua.globals()?;
let globals = lua.globals();
globals.set("string_var", "hello")?;
globals.set("int_var", 42)?;
@ -43,13 +43,13 @@ fn examples() -> LuaResult<()> {
// 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(2, "two")?;
array_table.set(3, "three")?;
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("two", 2)?;
map_table.set("three", 3)?;
@ -104,7 +104,7 @@ fn examples() -> LuaResult<()> {
// be inferred as part of the function signature due to the same
// lifetime type signature limitations.
lua.pack(list1 == list2)
})?;
});
globals.set("check_equal", check_equal)?;
// You can also accept variadic arguments to rust functions
@ -112,7 +112,7 @@ fn examples() -> LuaResult<()> {
let strings = lua.unpack::<LuaVariadic<String>>(args)?.0;
// (This is quadratic!, it's just an example!)
lua.pack(strings.iter().fold("".to_owned(), |a, b| a + b))
})?;
});
globals.set("join", join)?;
assert_eq!(
@ -153,7 +153,7 @@ fn examples() -> LuaResult<()> {
let vec2_constructor = lua.create_function(|lua, args| {
let hlist_pat![x, y] = lua.unpack::<HList![f32, f32]>(args)?;
lua.pack(Vec2(x, y))
})?;
});
globals.set("vec2", vec2_constructor)?;
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 {
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 {
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 {
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),
/// A generic Lua -> Rust conversion error.
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.
CoroutineInactive,
/// A `LuaUserData` is not the expected type in a borrow.
@ -57,7 +54,6 @@ impl fmt::Display for LuaError {
&LuaError::FromLuaConversionError(ref 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::UserDataTypeMismatch => write!(fmt, "Userdata not expected type"),
&LuaError::UserDataBorrowError => write!(fmt, "Userdata already mutably borrowed"),
@ -79,7 +75,6 @@ impl Error for LuaError {
&LuaError::ErrorError(_) => "lua error handling error",
&LuaError::ToLuaConversionError(_) => "conversion error to lua",
&LuaError::FromLuaConversionError(_) => "conversion error from lua",
&LuaError::StackOverflow => "lua stack overflow",
&LuaError::CoroutineInactive => "lua coroutine inactive",
&LuaError::UserDataTypeMismatch => "lua userdata type mismatch",
&LuaError::UserDataBorrowError => "lua userdata already mutably borrowed",

View file

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

View file

@ -185,7 +185,7 @@ impl<'lua> LuaString<'lua> {
/// # use rlua::*;
/// # fn main() {
/// let lua = Lua::new();
/// let globals = lua.globals().unwrap();
/// let globals = lua.globals();
///
/// let version: LuaString = globals.get("_VERSION").unwrap();
/// assert!(version.to_str().unwrap().contains("Lua"));
@ -197,8 +197,8 @@ impl<'lua> LuaString<'lua> {
pub fn to_str(&self) -> LuaResult<&str> {
let lua = self.0.lua;
unsafe {
stack_guard(lua.state, 0, || {
check_stack(lua.state, 1)?;
stack_err_guard(lua.state, 0, || {
check_stack(lua.state, 1);
lua.push_ref(lua.state, &self.0);
assert_eq!(ffi::lua_type(lua.state, -1), ffi::LUA_TSTRING);
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 value = value.to_lua(lua)?;
unsafe {
check_stack(lua.state, 3)?;
check_stack(lua.state, 3);
lua.push_ref(lua.state, &self.0);
lua.push_value(lua.state, key);
lua.push_value(lua.state, value);
@ -248,7 +248,7 @@ impl<'lua> LuaTable<'lua> {
let lua = self.0.lua;
let key = key.to_lua(lua)?;
unsafe {
check_stack(lua.state, 2)?;
check_stack(lua.state, 2);
lua.push_ref(lua.state, &self.0);
lua.push_value(lua.state, key.to_lua(lua)?);
let res = error_guard(lua.state, 2, 0, |state| {
@ -266,7 +266,7 @@ impl<'lua> LuaTable<'lua> {
let lua = self.0.lua;
let key = key.to_lua(lua)?;
unsafe {
check_stack(lua.state, 2)?;
check_stack(lua.state, 2);
lua.push_ref(lua.state, &self.0);
lua.push_value(lua.state, key);
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<()> {
let lua = self.0.lua;
unsafe {
stack_guard(lua.state, 0, || {
check_stack(lua.state, 3)?;
stack_err_guard(lua.state, 0, || {
check_stack(lua.state, 3);
lua.push_ref(lua.state, &self.0);
lua.push_value(lua.state, key.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> {
let lua = self.0.lua;
unsafe {
stack_guard(lua.state, 0, || {
check_stack(lua.state, 2)?;
stack_err_guard(lua.state, 0, || {
check_stack(lua.state, 2);
lua.push_ref(lua.state, &self.0);
lua.push_value(lua.state, key.to_lua(lua)?);
ffi::lua_gettable(lua.state, -2);
@ -318,7 +318,7 @@ impl<'lua> LuaTable<'lua> {
let lua = self.0.lua;
unsafe {
error_guard(lua.state, 0, 0, |state| {
check_stack(state, 1)?;
check_stack(state, 1);
lua.push_ref(state, &self.0);
Ok(ffi::luaL_len(state, -1))
})
@ -327,15 +327,15 @@ impl<'lua> LuaTable<'lua> {
/// Returns the result of the Lua `#` operator, without invoking the
/// `__len` metamethod.
pub fn raw_len(&self) -> LuaResult<LuaInteger> {
pub fn raw_len(&self) -> LuaInteger {
let lua = self.0.lua;
unsafe {
stack_guard(lua.state, 0, || {
check_stack(lua.state, 1)?;
check_stack(lua.state, 1);
lua.push_ref(lua.state, &self.0);
let len = ffi::lua_rawlen(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;
unsafe {
if let Err(e) = check_stack(lua.state, 4) {
return Some(Err(e));
}
check_stack(lua.state, 4);
lua.push_ref(lua.state, &self.table);
lua.push_ref(lua.state, &next_key);
@ -446,9 +444,7 @@ where
let lua = self.table.lua;
unsafe {
if let Err(e) = check_stack(lua.state, 2) {
return Some(Err(e));
}
check_stack(lua.state, 2);
lua.push_ref(lua.state, &self.table);
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> {
let lua = self.0.lua;
unsafe {
stack_guard(lua.state, 0, || {
stack_err_guard(lua.state, 0, || {
let args = args.to_lua_multi(lua)?;
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);
lua.push_ref(lua.state, &self.0);
@ -532,7 +528,7 @@ impl<'lua> LuaFunction<'lua> {
///
/// # fn main() {
/// let lua = Lua::new();
/// let globals = lua.globals().unwrap();
/// let globals = lua.globals();
///
/// // Bind the argument `123` to Lua's `tostring` function
/// let tostring: LuaFunction = globals.get("tostring").unwrap();
@ -548,7 +544,7 @@ impl<'lua> LuaFunction<'lua> {
let nargs = ffi::lua_gettop(state);
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_insert(state, 1);
@ -565,11 +561,11 @@ impl<'lua> LuaFunction<'lua> {
let lua = self.0.lua;
unsafe {
stack_guard(lua.state, 0, || {
stack_err_guard(lua.state, 0, || {
let args = args.to_lua_multi(lua)?;
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);
ffi::lua_pushinteger(lua.state, nargs as ffi::lua_Integer);
for arg in args {
@ -653,8 +649,8 @@ impl<'lua> LuaThread<'lua> {
{
let lua = self.0.lua;
unsafe {
stack_guard(lua.state, 0, || {
check_stack(lua.state, 1)?;
stack_err_guard(lua.state, 0, || {
check_stack(lua.state, 1);
lua.push_ref(lua.state, &self.0);
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 nargs = args.len() as c_int;
check_stack(thread_state, nargs)?;
check_stack(thread_state, nargs);
for arg in args {
lua.push_value(thread_state, arg);
@ -690,11 +686,11 @@ impl<'lua> LuaThread<'lua> {
}
/// Gets the status of the thread.
pub fn status(&self) -> LuaResult<LuaThreadStatus> {
pub fn status(&self) -> LuaThreadStatus {
let lua = self.0.lua;
unsafe {
stack_guard(lua.state, 0, || {
check_stack(lua.state, 1)?;
check_stack(lua.state, 1);
lua.push_ref(lua.state, &self.0);
let thread_state = ffi::lua_tothread(lua.state, -1);
@ -702,11 +698,11 @@ impl<'lua> LuaThread<'lua> {
let status = ffi::lua_status(thread_state);
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 {
Ok(LuaThreadStatus::Active)
LuaThreadStatus::Active
} else {
Ok(LuaThreadStatus::Dead)
LuaThreadStatus::Dead
}
})
}
@ -892,12 +888,8 @@ pub struct LuaUserData<'lua>(LuaRef<'lua>);
impl<'lua> LuaUserData<'lua> {
/// Checks whether `T` is the type of this userdata.
pub fn is<T: LuaUserDataType>(&self) -> LuaResult<bool> {
match self.inspect(|_: &RefCell<T>| Ok(())) {
Ok(_) => Ok(true),
Err(LuaError::UserDataTypeMismatch) => Ok(false),
Err(err) => Err(err),
}
pub fn is<T: LuaUserDataType>(&self) -> bool {
self.inspect(|_: &RefCell<T>| ()).is_some()
}
/// Borrow this userdata out of the internal RefCell that is held in lua.
@ -906,7 +898,7 @@ impl<'lua> LuaUserData<'lua> {
Ok(
cell.try_borrow().map_err(|_| LuaError::UserDataBorrowError)?,
)
})
}).ok_or(LuaError::UserDataTypeMismatch)?
}
/// 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(
|_| 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
T: LuaUserDataType,
F: FnOnce(&'a RefCell<T>) -> LuaResult<R>,
F: FnOnce(&'a RefCell<T>) -> R,
{
unsafe {
let lua = self.0.lua;
stack_guard(lua.state, 0, move || {
check_stack(lua.state, 3)?;
check_stack(lua.state, 3);
lua.push_ref(lua.state, &self.0);
let userdata = ffi::lua_touserdata(lua.state, -1);
assert!(!userdata.is_null());
assert!(
lua_assert!(lua.state, !userdata.is_null());
lua_assert!(
lua.state,
ffi::lua_getmetatable(lua.state, -1) != 0,
"LuaUserData missing metatable"
);
@ -941,16 +934,17 @@ impl<'lua> LuaUserData<'lua> {
ffi::lua_rawgeti(
lua.state,
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 {
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 {
unsafe {
let state = ffi::luaL_newstate();
ffi::luaL_openlibs(state);
stack_guard(state, 0, || {
ffi::luaL_openlibs(state);
// Create the userdata registry table
ffi::lua_pushlightuserdata(
state,
&LUA_USERDATA_REGISTRY_KEY as *const u8 as *mut c_void,
@ -1003,10 +1000,9 @@ impl Lua {
ffi::lua_setmetatable(state, -2);
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
Ok(())
}).unwrap();
stack_guard(state, 0, || {
// Create the function metatable
ffi::lua_pushlightuserdata(
state,
&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, ffi::LUA_REGISTRYINDEX);
Ok(())
}).unwrap();
stack_guard(state, 0, || {
// Override pcall / xpcall
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS);
push_string(state, "pcall");
@ -1038,8 +1033,7 @@ impl Lua {
ffi::lua_rawset(state, -3);
ffi::lua_pop(state, 1);
Ok(())
}).unwrap();
});
Lua {
state,
@ -1057,7 +1051,7 @@ impl Lua {
/// Equivalent to Lua's `load` function.
pub fn load(&self, source: &str, name: Option<&str>) -> LuaResult<LuaFunction> {
unsafe {
stack_guard(self.state, 0, || {
stack_err_guard(self.state, 0, || {
handle_error(
self.state,
if let Some(name) = name {
@ -1105,7 +1099,7 @@ impl Lua {
/// to. Otherwise, returns the values returned by the chunk (if any).
pub fn eval<'lua, R: FromLuaMulti<'lua>>(&'lua self, source: &str) -> LuaResult<R> {
unsafe {
stack_guard(self.state, 0, || {
stack_err_guard(self.state, 0, || {
// First, try interpreting the lua as an expression by adding
// "return", then as a statement. This is the same thing the
// actual lua repl does.
@ -1133,23 +1127,23 @@ impl Lua {
}
/// 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 {
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());
Ok(LuaString(self.pop_ref(self.state)))
LuaString(self.pop_ref(self.state))
})
}
}
/// Creates and returns a new table.
pub fn create_table(&self) -> LuaResult<LuaTable> {
pub fn create_table(&self) -> LuaTable {
unsafe {
stack_guard(self.state, 0, || {
check_stack(self.state, 1)?;
check_stack(self.state, 1);
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)>,
{
unsafe {
stack_guard(self.state, 0, || {
check_stack(self.state, 3)?;
stack_err_guard(self.state, 0, || {
check_stack(self.state, 3);
ffi::lua_newtable(self.state);
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.
pub fn create_function<F>(&self, func: F) -> LuaResult<LuaFunction>
pub fn create_function<F>(&self, func: F) -> LuaFunction
where
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).
///
/// 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 {
stack_guard(self.state, 0, move || {
check_stack(self.state, 1)?;
check_stack(self.state, 1);
let thread_state = ffi::lua_newthread(self.state);
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.
pub fn create_userdata<T>(&self, data: T) -> LuaResult<LuaUserData>
pub fn create_userdata<T>(&self, data: T) -> LuaUserData
where
T: LuaUserDataType,
{
unsafe {
stack_guard(self.state, 0, move || {
check_stack(self.state, 2)?;
check_stack(self.state, 2);
let data = RefCell::new(data);
let data_userdata =
@ -1227,22 +1221,24 @@ impl Lua {
ffi::lua_rawgeti(
self.state,
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);
Ok(LuaUserData(self.pop_ref(self.state)))
LuaUserData(self.pop_ref(self.state))
})
}
}
/// Returns a handle to the global environment.
pub fn globals(&self) -> LuaResult<LuaTable> {
pub fn globals(&self) -> LuaTable {
unsafe {
check_stack(self.state, 1)?;
ffi::lua_rawgeti(self.state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS);
Ok(LuaTable(self.pop_ref(self.state)))
stack_guard(self.state, 0, move || {
check_stack(self.state, 1);
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),
v => unsafe {
stack_guard(self.state, 0, || {
check_stack(self.state, 1)?;
check_stack(self.state, 1);
self.push_value(self.state, v);
if ffi::lua_tostring(self.state, -1).is_null() {
ffi::lua_pop(self.state, 1);
Err(LuaError::FromLuaConversionError(
"cannot convert lua value to string".to_owned(),
))
@ -1277,16 +1274,16 @@ impl Lua {
LuaValue::Integer(i) => Ok(i),
v => unsafe {
stack_guard(self.state, 0, || {
check_stack(self.state, 1)?;
check_stack(self.state, 1);
self.push_value(self.state, v);
let mut isint = 0;
let i = ffi::lua_tointegerx(self.state, -1, &mut isint);
ffi::lua_pop(self.state, 1);
if isint == 0 {
Err(LuaError::FromLuaConversionError(
"cannot convert lua value to integer".to_owned(),
))
} else {
ffi::lua_pop(self.state, 1);
Ok(i)
}
})
@ -1303,16 +1300,16 @@ impl Lua {
LuaValue::Number(n) => Ok(n),
v => unsafe {
stack_guard(self.state, 0, || {
check_stack(self.state, 1)?;
check_stack(self.state, 1);
self.push_value(self.state, v);
let mut isnum = 0;
let n = ffi::lua_tonumberx(self.state, -1, &mut isnum);
ffi::lua_pop(self.state, 1);
if isnum == 0 {
Err(LuaError::FromLuaConversionError(
"cannot convert lua value to number".to_owned(),
))
} else {
ffi::lua_pop(self.state, 1);
Ok(n)
}
})
@ -1345,7 +1342,7 @@ impl Lua {
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 {
callback_error(state, || {
let lua = Lua {
@ -1376,7 +1373,7 @@ impl Lua {
unsafe {
stack_guard(self.state, 0, move || {
check_stack(self.state, 2)?;
check_stack(self.state, 2);
let func_userdata =
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);
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))),
_ => 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
// first, then __index metamethod.
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 || {
check_stack(self.state, 3)?;
check_stack(self.state, 5);
ffi::lua_pushlightuserdata(
self.state,
@ -1560,7 +1557,7 @@ impl Lua {
ffi::lua_pop(self.state, 1);
match map.entry(TypeId::of::<T>()) {
HashMapEntry::Occupied(entry) => Ok(*entry.get()),
HashMapEntry::Occupied(entry) => *entry.get(),
HashMapEntry::Vacant(entry) => {
ffi::lua_newtable(self.state);
@ -1577,12 +1574,11 @@ impl Lua {
push_string(self.state, "__index");
ffi::lua_newtable(self.state);
check_stack(self.state, methods.methods.len() as c_int * 2)?;
for (k, m) in methods.methods {
push_string(self.state, &k);
self.push_value(
self.state,
LuaValue::Function(self.create_callback_function(m)?),
LuaValue::Function(self.create_callback_function(m)),
);
ffi::lua_rawset(self.state, -3);
}
@ -1590,7 +1586,6 @@ impl Lua {
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 {
if k == LuaMetaMethod::Index && has_methods {
push_string(self.state, "__index");
@ -1598,7 +1593,7 @@ impl Lua {
ffi::lua_gettable(self.state, -3);
self.push_value(
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_rawset(self.state, -3);
@ -1631,7 +1626,7 @@ impl Lua {
push_string(self.state, name);
self.push_value(
self.state,
LuaValue::Function(self.create_callback_function(m)?),
LuaValue::Function(self.create_callback_function(m)),
);
ffi::lua_rawset(self.state, -3);
}
@ -1647,7 +1642,7 @@ impl Lua {
let id = ffi::luaL_ref(self.state, ffi::LUA_REGISTRYINDEX);
entry.insert(id);
Ok(id)
id
}
}
})

View file

@ -9,7 +9,7 @@ use super::*;
#[test]
fn test_set_get() {
let lua = Lua::new();
let globals = lua.globals().unwrap();
let globals = lua.globals();
globals.set("foo", "bar").unwrap();
globals.set("baz", "baf").unwrap();
assert_eq!(globals.get::<_, String>("foo").unwrap(), "bar");
@ -29,7 +29,7 @@ fn test_load() {
#[test]
fn test_exec() {
let lua = Lua::new();
let globals = lua.globals().unwrap();
let globals = lua.globals();
lua.exec::<()>(
r#"
res = 'foo'..'bar'
@ -76,9 +76,9 @@ fn test_eval() {
#[test]
fn test_table() {
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 table2: LuaTable = globals.get("table").unwrap();
@ -164,7 +164,7 @@ fn test_table() {
#[test]
fn test_function() {
let lua = Lua::new();
let globals = lua.globals().unwrap();
let globals = lua.globals();
lua.exec::<()>(
r#"
function concat(arg1, arg2)
@ -184,7 +184,7 @@ fn test_function() {
#[test]
fn test_bind() {
let lua = Lua::new();
let globals = lua.globals().unwrap();
let globals = lua.globals();
lua.exec::<()>(
r#"
function concat(...)
@ -211,7 +211,7 @@ fn test_bind() {
#[test]
fn test_rust_function() {
let lua = Lua::new();
let globals = lua.globals().unwrap();
let globals = lua.globals();
lua.exec::<()>(
r#"
function lua_function()
@ -225,7 +225,7 @@ fn test_rust_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();
assert_eq!(lua_function.call::<_, String>(()).unwrap(), "hello");
@ -241,13 +241,13 @@ fn test_user_data() {
let lua = Lua::new();
let userdata1 = lua.create_userdata(UserData1(1)).unwrap();
let userdata2 = lua.create_userdata(UserData2(Box::new(2))).unwrap();
let userdata1 = lua.create_userdata(UserData1(1));
let userdata2 = lua.create_userdata(UserData2(Box::new(2)));
assert!(userdata1.is::<UserData1>().unwrap());
assert!(!userdata1.is::<UserData2>().unwrap());
assert!(userdata2.is::<UserData2>().unwrap());
assert!(!userdata2.is::<UserData1>().unwrap());
assert!(userdata1.is::<UserData1>());
assert!(!userdata1.is::<UserData2>());
assert!(userdata2.is::<UserData2>());
assert!(!userdata2.is::<UserData1>());
assert_eq!(userdata1.borrow::<UserData1>().unwrap().0, 1);
assert_eq!(*userdata2.borrow::<UserData2>().unwrap().0, 2);
@ -268,8 +268,8 @@ fn test_methods() {
}
let lua = Lua::new();
let globals = lua.globals().unwrap();
let userdata = lua.create_userdata(UserData(42)).unwrap();
let globals = lua.globals();
let userdata = lua.create_userdata(UserData(42));
globals.set("userdata", userdata.clone()).unwrap();
lua.exec::<()>(
r#"
@ -320,7 +320,7 @@ fn test_metamethods() {
}
let lua = Lua::new();
let globals = lua.globals().unwrap();
let globals = lua.globals();
globals.set("userdata1", UserData(7)).unwrap();
globals.set("userdata2", UserData(3)).unwrap();
assert_eq!(lua.eval::<UserData>("userdata1 + userdata2").unwrap().0, 10);
@ -333,7 +333,7 @@ fn test_metamethods() {
#[test]
fn test_scope() {
let lua = Lua::new();
let globals = lua.globals().unwrap();
let globals = lua.globals();
lua.exec::<()>(
r#"
touter = {
@ -369,7 +369,7 @@ fn test_scope() {
#[test]
fn test_lua_multi() {
let lua = Lua::new();
let globals = lua.globals().unwrap();
let globals = lua.globals();
lua.exec::<()>(
r#"
function concat(arg1, arg2)
@ -401,7 +401,7 @@ fn test_lua_multi() {
#[test]
fn test_coercion() {
let lua = Lua::new();
let globals = lua.globals().unwrap();
let globals = lua.globals();
lua.exec::<()>(
r#"
int = 123
@ -438,7 +438,7 @@ fn test_error() {
}
let lua = Lua::new();
let globals = lua.globals().unwrap();
let globals = lua.globals();
lua.exec::<()>(
r#"
function no_error()
@ -492,8 +492,7 @@ fn test_error() {
None,
).unwrap();
let rust_error_function = lua.create_function(|_, _| Err(TestError.to_lua_err()))
.unwrap();
let rust_error_function = lua.create_function(|_, _| Err(TestError.to_lua_err()));
globals
.set("rust_error_function", rust_error_function)
.unwrap();
@ -546,6 +545,8 @@ fn test_error() {
match catch_unwind(|| -> LuaResult<()> {
let lua = Lua::new();
let globals = lua.globals();
lua.exec::<()>(
r#"
function rust_panic()
@ -556,7 +557,7 @@ fn test_error() {
)?;
let rust_panic_function = lua.create_function(|_, _| {
panic!("expected panic, this panic should be caught in rust")
})?;
});
globals.set("rust_panic_function", rust_panic_function)?;
let rust_panic = globals.get::<_, LuaFunction>("rust_panic")?;
@ -570,6 +571,8 @@ fn test_error() {
match catch_unwind(|| -> LuaResult<()> {
let lua = Lua::new();
let globals = lua.globals();
lua.exec::<()>(
r#"
function rust_panic()
@ -580,7 +583,7 @@ fn test_error() {
)?;
let rust_panic_function = lua.create_function(|_, _| {
panic!("expected panic, this panic should be caught in rust")
})?;
});
globals.set("rust_panic_function", rust_panic_function)?;
let rust_panic = globals.get::<_, LuaFunction>("rust_panic")?;
@ -608,19 +611,19 @@ fn test_thread() {
end
"#,
).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.status().unwrap(), LuaThreadStatus::Active);
assert_eq!(thread.status(), LuaThreadStatus::Active);
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.status().unwrap(), LuaThreadStatus::Active);
assert_eq!(thread.status(), LuaThreadStatus::Active);
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.status().unwrap(), LuaThreadStatus::Dead);
assert_eq!(thread.status(), LuaThreadStatus::Dead);
let accumulate = lua.create_thread(
lua.eval::<LuaFunction>(
@ -632,15 +635,15 @@ fn test_thread() {
end
"#,
).unwrap(),
).unwrap();
);
for i in 0..4 {
accumulate.resume::<_, ()>(i).unwrap();
}
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_eq!(accumulate.status().unwrap(), LuaThreadStatus::Error);
assert_eq!(accumulate.status(), LuaThreadStatus::Error);
let thread = lua.eval::<LuaThread>(
r#"
@ -651,7 +654,7 @@ fn test_thread() {
end)
"#,
).unwrap();
assert_eq!(thread.status().unwrap(), LuaThreadStatus::Active);
assert_eq!(thread.status(), LuaThreadStatus::Active);
assert_eq!(thread.resume::<_, i64>(()).unwrap(), 42);
let thread: LuaThread = lua.eval(
@ -678,7 +681,7 @@ fn test_thread() {
#[test]
fn test_lightuserdata() {
let lua = Lua::new();
let globals = lua.globals().unwrap();
let globals = lua.globals();
lua.exec::<()>(
r#"
function id(a)
@ -698,7 +701,7 @@ fn test_lightuserdata() {
#[test]
fn test_table_error() {
let lua = Lua::new();
let globals = lua.globals().unwrap();
let globals = lua.globals();
lua.exec::<()>(
r#"
table = {}
@ -723,21 +726,20 @@ fn test_table_error() {
assert!(bad_table.len().is_err());
assert!(bad_table.raw_set(1, 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]
fn test_result_conversions() {
let lua = Lua::new();
let globals = lua.globals().unwrap();
let globals = lua.globals();
let err = lua.create_function(|lua, _| {
lua.pack(Result::Err::<String, _>(
"only through failure can we succeed".to_lua_err(),
))
}).unwrap();
let ok = lua.create_function(|lua, _| lua.pack(Result::Ok::<_, LuaError>("!".to_owned())))
.unwrap();
});
let ok = lua.create_function(|lua, _| lua.pack(Result::Ok::<_, LuaError>("!".to_owned())));
globals.set("err", err).unwrap();
globals.set("ok", ok).unwrap();
@ -759,7 +761,7 @@ fn test_result_conversions() {
#[test]
fn test_num_conversion() {
let lua = Lua::new();
let globals = lua.globals().unwrap();
let globals = lua.globals();
globals.set("n", "1.0").unwrap();
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<()> {
if ffi::lua_checkstack(state, amount) == 0 {
Err(LuaError::StackOverflow)
} else {
Ok(())
}
// A panic that clears the given lua stack before panicking
macro_rules! lua_panic {
($state:expr) => {
{
$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
// 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.
pub unsafe fn stack_guard<F, R>(state: *mut ffi::lua_State, change: c_int, op: F) -> LuaResult<R>
// An assert that clears the given lua stack before panicking
macro_rules! lua_assert {
($state:expr, $cond:expr) => {
if !$cond {
$crate::ffi::lua_settop($state, 0);
panic!("rlua internal error");
}
};
($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
F: FnOnce() -> LuaResult<R>,
{
let expected = ffi::lua_gettop(state) + change;
assert!(
lua_assert!(
state,
expected >= 0,
"lua stack error, too many values would be popped"
"internal stack error: too many values would be popped"
);
let res = op();
let top = ffi::lua_gettop(state);
if res.is_ok() {
assert_eq!(
ffi::lua_gettop(state),
expected,
"lua stack error, expected stack to be {}, got {}",
lua_assert!(
state,
ffi::lua_gettop(state) == expected,
"internal stack error: expected stack to be {}, got {}",
expected,
top
);
} else {
assert!(
lua_assert!(
state,
top >= expected,
"lua stack error, {} too many values popped",
"internal stack error: {} too many values popped",
top - expected
);
if top > expected {
ffi::lua_settop(state, expected);
}
}
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) {
let userdata = ffi::lua_touserdata(state, -1);
let panic = &mut *(userdata as *mut WrappedPanic);
resume_unwind(panic.0.take().expect(
"internal error: panic was resumed twice",
))
if let Some(p) = panic.0.take() {
ffi::lua_settop(state, 0);
resume_unwind(p);
} else {
lua_panic!(state, "internal error: panic was resumed twice")
}
} else {
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!");
process::abort()
}
_ => panic!("unrecognized lua error code"),
_ => lua_panic!(state, "internal error: unrecognized lua error code"),
})
}
}