mlua/src/util.rs

663 lines
22 KiB
Rust
Raw Normal View History

2017-05-21 19:50:59 -04:00
use std::mem;
Big API incompatible error change, remove dependency on error_chain The current situation with error_chain is less than ideal, and there are lots of conflicting interests that are impossible to meet at once. Here is an unorganized brain dump of the current situation, stay awhile and listen! This change was triggered ultimately by the desire to make LuaError implement Clone, and this is currently impossible with error_chain. LuaError must implement Clone to be a proper lua citizen that can live as userdata within a lua runtime, because there is no way to limit what the lua runtime can do with a received error. Currently, this is solved by there being a rule that the error will "expire" if the error is passed back into rust, and this is very sub-optimal. In fact, one could easily imagine a scenario where lua is for example memoizing some function, and if the function has ever errored in the past the function should continue returning the same error, and this situation immediately fails with this restriciton in place. Additionally, there are other more minor problems with error_chain which make the API less good than it could be, or limit how we can use error_chain. This change has already solved a small bug in a Chucklefish project, where the conversion from an external error type (Borrow[Mut]Error) was allowed but not intended for user code, and was accidentally used. Additionally, pattern matching on error_chain errors, which should be common when dealing with Lua, is less convenient than a hand rolled error type. So, if we decide not to use error_chain, we now have a new set of problems if we decide interoperability with error_chain is important. The first problem we run into is that there are two natural bounds for wrapped errors that we would pick, (Error + Send + Sync), or just Error, and neither of them will interoperate well with error_chain. (Error + Send + Sync) means we can't wrap error chain errors into LuaError::ExternalError (they're missing the Sync bound), and having the bounds be just Error means the opposite, that we can't hold a LuaError inside an error_chain error. We could just decide that interoperability with error_chain is the most important qualification, and pick (Error + Send), but this causes a DIFFERENT set of problems. The rust ecosystem has the two primary error bounds as Error or (Error + Send + Sync), and there are Into impls from &str / String to Box<Error + Send + Sync> for example, but NOT (Error + Send). This means that we are forced to manually recreate the conversions from &str / String to LuaError rather than relying on a single Into<Box<Error + Send + Sync>> bound, but this means that string conversions have a different set of methods than other error types for external error conversion. I have not been able to figure out an API that I am happy with that uses the (Error + Send) bound. Box<Error> is obnoxious because not having errors implement Send causes needless problems in a multithreaded context, so that leaves (Error + Send + Sync). This is actually a completely reasonable bound for external errors, and has the nice String Into impls that we would want, the ONLY problem is that it is a pain to interoperate with the current version of error_chain. It would be nice to be able to specify the traits that an error generated by the error_chain macro would implement, and this is apparently in progress in the error_chain library. This would solve both the problem with not being able to implement Clone and the problems with (Error + Send) bounds. I am not convinced that this library should go back to using error_chain when that functionality is in stable error_chain though, because of the other minor usability problems with using error_chain. In that theoretical situation, the downside of NOT using error_chain is simply that there would not be automatic stacktraces of LuaError. This is not a huge problem, because stack traces of lua errors are not extremely useful, and for external errors it is not too hard to create a different version of the LuaExternalResult / LuaExternalError traits and do conversion from an error_chain type into a type that will print the stacktrace on display, or use downcasting in the error causes. So in summary, this library is no longer using error_chain, and probably will not use it again in the future. Currently this means that to interoperate with error_chain, you should use error_chain 0.8.1, which derives Sync on errors, or wait for a version that supports user defined trait derives. In the future when error_chain supports user defined trait derives, users may have to take an extra step to make wrapped external errors print the stacktrace that they capture. This change works, but is not entirely complete. There is no error documentation yet, and the change brought to a head an ugly module organization problem. There will be more commits for documentation and reorganization, then a new stable version of rlua.
2017-06-24 18:11:56 -04:00
use std::ptr;
use std::process;
Big API incompatible error change, remove dependency on error_chain The current situation with error_chain is less than ideal, and there are lots of conflicting interests that are impossible to meet at once. Here is an unorganized brain dump of the current situation, stay awhile and listen! This change was triggered ultimately by the desire to make LuaError implement Clone, and this is currently impossible with error_chain. LuaError must implement Clone to be a proper lua citizen that can live as userdata within a lua runtime, because there is no way to limit what the lua runtime can do with a received error. Currently, this is solved by there being a rule that the error will "expire" if the error is passed back into rust, and this is very sub-optimal. In fact, one could easily imagine a scenario where lua is for example memoizing some function, and if the function has ever errored in the past the function should continue returning the same error, and this situation immediately fails with this restriciton in place. Additionally, there are other more minor problems with error_chain which make the API less good than it could be, or limit how we can use error_chain. This change has already solved a small bug in a Chucklefish project, where the conversion from an external error type (Borrow[Mut]Error) was allowed but not intended for user code, and was accidentally used. Additionally, pattern matching on error_chain errors, which should be common when dealing with Lua, is less convenient than a hand rolled error type. So, if we decide not to use error_chain, we now have a new set of problems if we decide interoperability with error_chain is important. The first problem we run into is that there are two natural bounds for wrapped errors that we would pick, (Error + Send + Sync), or just Error, and neither of them will interoperate well with error_chain. (Error + Send + Sync) means we can't wrap error chain errors into LuaError::ExternalError (they're missing the Sync bound), and having the bounds be just Error means the opposite, that we can't hold a LuaError inside an error_chain error. We could just decide that interoperability with error_chain is the most important qualification, and pick (Error + Send), but this causes a DIFFERENT set of problems. The rust ecosystem has the two primary error bounds as Error or (Error + Send + Sync), and there are Into impls from &str / String to Box<Error + Send + Sync> for example, but NOT (Error + Send). This means that we are forced to manually recreate the conversions from &str / String to LuaError rather than relying on a single Into<Box<Error + Send + Sync>> bound, but this means that string conversions have a different set of methods than other error types for external error conversion. I have not been able to figure out an API that I am happy with that uses the (Error + Send) bound. Box<Error> is obnoxious because not having errors implement Send causes needless problems in a multithreaded context, so that leaves (Error + Send + Sync). This is actually a completely reasonable bound for external errors, and has the nice String Into impls that we would want, the ONLY problem is that it is a pain to interoperate with the current version of error_chain. It would be nice to be able to specify the traits that an error generated by the error_chain macro would implement, and this is apparently in progress in the error_chain library. This would solve both the problem with not being able to implement Clone and the problems with (Error + Send) bounds. I am not convinced that this library should go back to using error_chain when that functionality is in stable error_chain though, because of the other minor usability problems with using error_chain. In that theoretical situation, the downside of NOT using error_chain is simply that there would not be automatic stacktraces of LuaError. This is not a huge problem, because stack traces of lua errors are not extremely useful, and for external errors it is not too hard to create a different version of the LuaExternalResult / LuaExternalError traits and do conversion from an error_chain type into a type that will print the stacktrace on display, or use downcasting in the error causes. So in summary, this library is no longer using error_chain, and probably will not use it again in the future. Currently this means that to interoperate with error_chain, you should use error_chain 0.8.1, which derives Sync on errors, or wait for a version that supports user defined trait derives. In the future when error_chain supports user defined trait derives, users may have to take an extra step to make wrapped external errors print the stacktrace that they capture. This change works, but is not entirely complete. There is no error documentation yet, and the change brought to a head an ugly module organization problem. There will be more commits for documentation and reorganization, then a new stable version of rlua.
2017-06-24 18:11:56 -04:00
use std::sync::Arc;
2017-05-21 19:50:59 -04:00
use std::ffi::CStr;
use std::any::Any;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::{catch_unwind, resume_unwind, UnwindSafe};
use ffi;
use error::{Result, Error};
2017-05-21 19:50:59 -04:00
macro_rules! cstr {
($s:expr) => (
concat!($s, "\0") as *const str as *const [c_char] as *const c_char
);
}
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.
2017-06-25 16:52:32 -04:00
// A panic that clears the given lua stack before panicking
macro_rules! lua_panic {
($state:expr) => {
{
$crate::ffi::lua_settor($state, 0);
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.
2017-06-25 16:52:32 -04:00
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)+);
}
};
}
// 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
}
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.
2017-06-25 16:52:32 -04:00
// 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.
2017-07-24 10:40:00 -04:00
pub unsafe fn stack_err_guard<F, R>(state: *mut ffi::lua_State, change: c_int, op: F) -> Result<R>
2017-06-15 10:26:39 -04:00
where
F: FnOnce() -> Result<R>,
2017-05-21 19:50:59 -04:00
{
let expected = ffi::lua_gettop(state) + change;
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.
2017-06-25 16:52:32 -04:00
lua_assert!(
state,
2017-06-15 10:26:39 -04:00
expected >= 0,
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.
2017-06-25 16:52:32 -04:00
"internal stack error: too many values would be popped"
2017-06-15 10:26:39 -04:00
);
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.
2017-06-25 16:52:32 -04:00
2017-05-21 19:50:59 -04:00
let res = op();
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.
2017-06-25 16:52:32 -04:00
2017-05-21 19:50:59 -04:00
let top = ffi::lua_gettop(state);
if res.is_ok() {
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.
2017-06-25 16:52:32 -04:00
lua_assert!(
state,
ffi::lua_gettop(state) == expected,
"internal stack error: expected stack to be {}, got {}",
2017-06-15 10:26:39 -04:00
expected,
top
);
2017-05-21 19:50:59 -04:00
} else {
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.
2017-06-25 16:52:32 -04:00
lua_assert!(
state,
2017-06-15 10:26:39 -04:00
top >= expected,
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.
2017-06-25 16:52:32 -04:00
"internal stack error: {} too many values popped",
2017-06-15 10:26:39 -04:00
top - expected
);
2017-05-21 19:50:59 -04:00
if top > expected {
ffi::lua_settop(state, expected);
}
}
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.
2017-06-25 16:52:32 -04:00
2017-05-21 19:50:59 -04:00
res
}
// Protected version of lua_gettable, uses 3 stack spaces, does not call checkstack.
pub unsafe fn pgettable(state: *mut ffi::lua_State, index: c_int) -> Result<c_int> {
unsafe extern "C" fn gettable(state: *mut ffi::lua_State) -> c_int {
ffi::lua_gettable(state, -2);
1
2017-05-21 19:50:59 -04:00
}
let table_index = ffi::lua_absindex(state, index);
ffi::lua_pushcfunction(state, gettable);
ffi::lua_pushvalue(state, table_index);
ffi::lua_pushvalue(state, -3);
ffi::lua_remove(state, -4);
handle_error(state, pcall_with_traceback(state, 2, 1))?;
Ok(ffi::lua_type(state, -1))
}
// Protected version of lua_settable, uses 4 stack spaces, does not call checkstack.
pub unsafe fn psettable(state: *mut ffi::lua_State, index: c_int) -> Result<()> {
unsafe extern "C" fn settable(state: *mut ffi::lua_State) -> c_int {
ffi::lua_settable(state, -3);
0
}
let table_index = ffi::lua_absindex(state, index);
ffi::lua_pushcfunction(state, settable);
ffi::lua_pushvalue(state, table_index);
ffi::lua_pushvalue(state, -4);
ffi::lua_pushvalue(state, -4);
ffi::lua_remove(state, -5);
ffi::lua_remove(state, -5);
handle_error(state, pcall_with_traceback(state, 3, 0))?;
Ok(())
}
// Protected version of luaL_len, uses 2 stack spaces, does not call checkstack.
pub unsafe fn plen(state: *mut ffi::lua_State, index: c_int) -> Result<ffi::lua_Integer> {
unsafe extern "C" fn len(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushinteger(state, ffi::luaL_len(state, -1));
1
}
let table_index = ffi::lua_absindex(state, index);
ffi::lua_pushcfunction(state, len);
ffi::lua_pushvalue(state, table_index);
handle_error(state, pcall_with_traceback(state, 1, 1))?;
let len = ffi::lua_tointeger(state, -1);
ffi::lua_pop(state, 1);
Ok(len)
}
// Protected version of lua_geti, uses 3 stack spaces, does not call checkstack.
2017-07-24 10:40:00 -04:00
pub unsafe fn pgeti(
state: *mut ffi::lua_State,
index: c_int,
i: ffi::lua_Integer,
) -> Result<c_int> {
unsafe extern "C" fn geti(state: *mut ffi::lua_State) -> c_int {
let i = ffi::lua_tointeger(state, -1);
ffi::lua_geti(state, -2, i);
1
}
let table_index = ffi::lua_absindex(state, index);
ffi::lua_pushcfunction(state, geti);
ffi::lua_pushvalue(state, table_index);
ffi::lua_pushinteger(state, i);
handle_error(state, pcall_with_traceback(state, 2, 1))?;
Ok(ffi::lua_type(state, -1))
}
// Protected version of lua_next, uses 3 stack spaces, does not call checkstack.
pub unsafe fn pnext(state: *mut ffi::lua_State, index: c_int) -> Result<c_int> {
unsafe extern "C" fn next(state: *mut ffi::lua_State) -> c_int {
2017-07-24 10:40:00 -04:00
if ffi::lua_next(state, -2) == 0 { 0 } else { 2 }
}
let table_index = ffi::lua_absindex(state, index);
ffi::lua_pushcfunction(state, next);
ffi::lua_pushvalue(state, table_index);
ffi::lua_pushvalue(state, -3);
ffi::lua_remove(state, -4);
let stack_start = ffi::lua_gettop(state) - 3;
handle_error(state, pcall_with_traceback(state, 2, ffi::LUA_MULTRET))?;
let nresults = ffi::lua_gettop(state) - stack_start;
2017-07-24 10:40:00 -04:00
if nresults == 0 { Ok(0) } else { Ok(1) }
2017-05-21 19:50:59 -04:00
}
// If the return code indicates an error, pops the error off of the stack and
2017-06-25 01:10:26 -04:00
// returns Err. If the error is actually a WrappedPaic, clears the current lua
// stack continues the panic. If the error on the top of the stack is actually
// a WrappedError, just returns it. Otherwise, interprets the error as the
// appropriate lua error.
pub unsafe fn handle_error(state: *mut ffi::lua_State, err: c_int) -> Result<()> {
if err == ffi::LUA_OK || err == ffi::LUA_YIELD {
Ok(())
} else {
if let Some(err) = pop_wrapped_error(state) {
Err(err)
2017-06-24 22:26:35 -04:00
} else if is_wrapped_panic(state, -1) {
let panic = &mut *get_userdata::<WrappedPanic>(state, -1);
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.
2017-06-25 16:52:32 -04:00
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")
}
2017-06-24 22:26:35 -04:00
} else {
let err_string =
if let Some(s) = ffi::lua_tolstring(state, -1, ptr::null_mut()).as_ref() {
2017-06-25 01:10:26 -04:00
CStr::from_ptr(s)
.to_str()
.unwrap_or_else(|_| "<unprintable error>")
.to_owned()
} else {
2017-06-24 22:26:35 -04:00
"<unprintable error>".to_owned()
};
ffi::lua_pop(state, 1);
Err(match err {
ffi::LUA_ERRRUN => Error::RuntimeError(err_string),
2017-06-24 22:26:35 -04:00
ffi::LUA_ERRSYNTAX => {
2017-06-30 15:17:53 -04:00
// This seems terrible, but as far as I can tell, this is exactly what the stock
// lua repl does.
2017-06-24 22:26:35 -04:00
if err_string.ends_with("<eof>") {
Error::IncompleteStatement(err_string)
2017-06-24 22:26:35 -04:00
} else {
Error::SyntaxError(err_string)
2017-06-24 22:26:35 -04:00
}
}
ffi::LUA_ERRERR => Error::ErrorError(err_string),
2017-06-24 22:26:35 -04:00
ffi::LUA_ERRMEM => {
// This is not impossible to hit, but this library is not set up
// to handle this properly. Lua does a longjmp on out of memory
// (like all lua errors), but it can do this from a huge number
// of lua functions, and it is extremely difficult to set up the
// pcall protection for every lua function that might allocate.
// If lua does this in an unprotected context, it will abort
// anyway, so the best we can do right now is guarantee an abort
// even in a protected context.
println!("Lua memory error, aborting!");
process::abort()
}
ffi::LUA_ERRGCMM => {
// This should be impossible, or at least is indicative of an
// internal bug. Similarly to LUA_ERRMEM, this could indicate a
// longjmp out of rust code, so we just abort.
println!("Lua error during __gc, aborting!");
process::abort()
}
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.
2017-06-25 16:52:32 -04:00
_ => lua_panic!(state, "internal error: unrecognized lua error code"),
2017-06-24 22:26:35 -04:00
})
}
}
}
2017-05-21 19:50:59 -04:00
pub unsafe fn push_string(state: *mut ffi::lua_State, s: &str) {
ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len());
}
pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T) -> *mut T {
let ud = ffi::lua_newuserdata(state, mem::size_of::<Option<T>>()) as *mut Option<T>;
ptr::write(ud, None);
*ud = Some(t);
(*ud).as_mut().unwrap()
}
pub unsafe fn get_userdata<T>(state: *mut ffi::lua_State, index: c_int) -> *mut T {
let ud = ffi::lua_touserdata(state, index) as *mut Option<T>;
lua_assert!(state, !ud.is_null());
(*ud).as_mut().expect("access of expired userdata")
}
pub unsafe extern "C" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
2017-05-21 19:50:59 -04:00
match catch_unwind(|| {
*(ffi::lua_touserdata(state, 1) as *mut Option<T>) = None;
2017-06-15 10:26:39 -04:00
0
}) {
2017-05-21 19:50:59 -04:00
Ok(r) => r,
Err(p) => {
push_wrapped_panic(state, p);
2017-05-21 19:50:59 -04:00
ffi::lua_error(state)
}
}
}
2017-06-24 22:26:35 -04:00
// 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 handle_error back on
2017-06-24 22:26:35 -04:00
// the rust side, it will resume the panic.
2017-05-21 19:50:59 -04:00
pub unsafe fn callback_error<R, F>(state: *mut ffi::lua_State, f: F) -> R
2017-06-15 10:26:39 -04:00
where
F: FnOnce() -> Result<R> + UnwindSafe,
2017-05-21 19:50:59 -04:00
{
match catch_unwind(f) {
Ok(Ok(r)) => r,
Ok(Err(err)) => {
push_wrapped_error(state, err);
2017-05-21 19:50:59 -04:00
ffi::lua_error(state)
}
Err(p) => {
push_wrapped_panic(state, p);
2017-05-21 19:50:59 -04:00
ffi::lua_error(state)
}
}
}
Big API incompatible error change, remove dependency on error_chain The current situation with error_chain is less than ideal, and there are lots of conflicting interests that are impossible to meet at once. Here is an unorganized brain dump of the current situation, stay awhile and listen! This change was triggered ultimately by the desire to make LuaError implement Clone, and this is currently impossible with error_chain. LuaError must implement Clone to be a proper lua citizen that can live as userdata within a lua runtime, because there is no way to limit what the lua runtime can do with a received error. Currently, this is solved by there being a rule that the error will "expire" if the error is passed back into rust, and this is very sub-optimal. In fact, one could easily imagine a scenario where lua is for example memoizing some function, and if the function has ever errored in the past the function should continue returning the same error, and this situation immediately fails with this restriciton in place. Additionally, there are other more minor problems with error_chain which make the API less good than it could be, or limit how we can use error_chain. This change has already solved a small bug in a Chucklefish project, where the conversion from an external error type (Borrow[Mut]Error) was allowed but not intended for user code, and was accidentally used. Additionally, pattern matching on error_chain errors, which should be common when dealing with Lua, is less convenient than a hand rolled error type. So, if we decide not to use error_chain, we now have a new set of problems if we decide interoperability with error_chain is important. The first problem we run into is that there are two natural bounds for wrapped errors that we would pick, (Error + Send + Sync), or just Error, and neither of them will interoperate well with error_chain. (Error + Send + Sync) means we can't wrap error chain errors into LuaError::ExternalError (they're missing the Sync bound), and having the bounds be just Error means the opposite, that we can't hold a LuaError inside an error_chain error. We could just decide that interoperability with error_chain is the most important qualification, and pick (Error + Send), but this causes a DIFFERENT set of problems. The rust ecosystem has the two primary error bounds as Error or (Error + Send + Sync), and there are Into impls from &str / String to Box<Error + Send + Sync> for example, but NOT (Error + Send). This means that we are forced to manually recreate the conversions from &str / String to LuaError rather than relying on a single Into<Box<Error + Send + Sync>> bound, but this means that string conversions have a different set of methods than other error types for external error conversion. I have not been able to figure out an API that I am happy with that uses the (Error + Send) bound. Box<Error> is obnoxious because not having errors implement Send causes needless problems in a multithreaded context, so that leaves (Error + Send + Sync). This is actually a completely reasonable bound for external errors, and has the nice String Into impls that we would want, the ONLY problem is that it is a pain to interoperate with the current version of error_chain. It would be nice to be able to specify the traits that an error generated by the error_chain macro would implement, and this is apparently in progress in the error_chain library. This would solve both the problem with not being able to implement Clone and the problems with (Error + Send) bounds. I am not convinced that this library should go back to using error_chain when that functionality is in stable error_chain though, because of the other minor usability problems with using error_chain. In that theoretical situation, the downside of NOT using error_chain is simply that there would not be automatic stacktraces of LuaError. This is not a huge problem, because stack traces of lua errors are not extremely useful, and for external errors it is not too hard to create a different version of the LuaExternalResult / LuaExternalError traits and do conversion from an error_chain type into a type that will print the stacktrace on display, or use downcasting in the error causes. So in summary, this library is no longer using error_chain, and probably will not use it again in the future. Currently this means that to interoperate with error_chain, you should use error_chain 0.8.1, which derives Sync on errors, or wait for a version that supports user defined trait derives. In the future when error_chain supports user defined trait derives, users may have to take an extra step to make wrapped external errors print the stacktrace that they capture. This change works, but is not entirely complete. There is no error documentation yet, and the change brought to a head an ugly module organization problem. There will be more commits for documentation and reorganization, then a new stable version of rlua.
2017-06-24 18:11:56 -04:00
// ffi::lua_pcall with a message handler that gives a nice traceback. If the
// caught error is actually a Error, will simply pass the error along. Does
Big API incompatible error change, remove dependency on error_chain The current situation with error_chain is less than ideal, and there are lots of conflicting interests that are impossible to meet at once. Here is an unorganized brain dump of the current situation, stay awhile and listen! This change was triggered ultimately by the desire to make LuaError implement Clone, and this is currently impossible with error_chain. LuaError must implement Clone to be a proper lua citizen that can live as userdata within a lua runtime, because there is no way to limit what the lua runtime can do with a received error. Currently, this is solved by there being a rule that the error will "expire" if the error is passed back into rust, and this is very sub-optimal. In fact, one could easily imagine a scenario where lua is for example memoizing some function, and if the function has ever errored in the past the function should continue returning the same error, and this situation immediately fails with this restriciton in place. Additionally, there are other more minor problems with error_chain which make the API less good than it could be, or limit how we can use error_chain. This change has already solved a small bug in a Chucklefish project, where the conversion from an external error type (Borrow[Mut]Error) was allowed but not intended for user code, and was accidentally used. Additionally, pattern matching on error_chain errors, which should be common when dealing with Lua, is less convenient than a hand rolled error type. So, if we decide not to use error_chain, we now have a new set of problems if we decide interoperability with error_chain is important. The first problem we run into is that there are two natural bounds for wrapped errors that we would pick, (Error + Send + Sync), or just Error, and neither of them will interoperate well with error_chain. (Error + Send + Sync) means we can't wrap error chain errors into LuaError::ExternalError (they're missing the Sync bound), and having the bounds be just Error means the opposite, that we can't hold a LuaError inside an error_chain error. We could just decide that interoperability with error_chain is the most important qualification, and pick (Error + Send), but this causes a DIFFERENT set of problems. The rust ecosystem has the two primary error bounds as Error or (Error + Send + Sync), and there are Into impls from &str / String to Box<Error + Send + Sync> for example, but NOT (Error + Send). This means that we are forced to manually recreate the conversions from &str / String to LuaError rather than relying on a single Into<Box<Error + Send + Sync>> bound, but this means that string conversions have a different set of methods than other error types for external error conversion. I have not been able to figure out an API that I am happy with that uses the (Error + Send) bound. Box<Error> is obnoxious because not having errors implement Send causes needless problems in a multithreaded context, so that leaves (Error + Send + Sync). This is actually a completely reasonable bound for external errors, and has the nice String Into impls that we would want, the ONLY problem is that it is a pain to interoperate with the current version of error_chain. It would be nice to be able to specify the traits that an error generated by the error_chain macro would implement, and this is apparently in progress in the error_chain library. This would solve both the problem with not being able to implement Clone and the problems with (Error + Send) bounds. I am not convinced that this library should go back to using error_chain when that functionality is in stable error_chain though, because of the other minor usability problems with using error_chain. In that theoretical situation, the downside of NOT using error_chain is simply that there would not be automatic stacktraces of LuaError. This is not a huge problem, because stack traces of lua errors are not extremely useful, and for external errors it is not too hard to create a different version of the LuaExternalResult / LuaExternalError traits and do conversion from an error_chain type into a type that will print the stacktrace on display, or use downcasting in the error causes. So in summary, this library is no longer using error_chain, and probably will not use it again in the future. Currently this means that to interoperate with error_chain, you should use error_chain 0.8.1, which derives Sync on errors, or wait for a version that supports user defined trait derives. In the future when error_chain supports user defined trait derives, users may have to take an extra step to make wrapped external errors print the stacktrace that they capture. This change works, but is not entirely complete. There is no error documentation yet, and the change brought to a head an ugly module organization problem. There will be more commits for documentation and reorganization, then a new stable version of rlua.
2017-06-24 18:11:56 -04:00
// not call checkstack, and uses 2 extra stack spaces.
2017-06-15 10:26:39 -04:00
pub unsafe fn pcall_with_traceback(
state: *mut ffi::lua_State,
nargs: c_int,
nresults: c_int,
) -> c_int {
2017-05-21 19:50:59 -04:00
unsafe extern "C" fn message_handler(state: *mut ffi::lua_State) -> c_int {
if let Some(error) = pop_wrapped_error(state) {
2017-06-24 22:26:35 -04:00
ffi::luaL_traceback(state, state, ptr::null(), 0);
let traceback = CStr::from_ptr(ffi::lua_tolstring(state, -1, ptr::null_mut()))
.to_str()
2017-06-25 01:10:26 -04:00
.unwrap_or_else(|_| "<could not capture traceback>")
2017-06-24 22:26:35 -04:00
.to_owned();
push_wrapped_error(state, Error::CallbackError {
traceback,
cause: Arc::new(error),
});
ffi::lua_remove(state, -2);
2017-06-24 22:26:35 -04:00
} else if !is_wrapped_panic(state, 1) {
let s = ffi::lua_tolstring(state, 1, ptr::null_mut());
if !s.is_null() {
ffi::luaL_traceback(state, state, s, 0);
} else {
ffi::luaL_traceback(state, state, cstr!("<unprintable lua error>"), 0);
2017-05-21 19:50:59 -04:00
}
ffi::lua_remove(state, -2);
2017-05-21 19:50:59 -04:00
}
1
}
let msgh_position = ffi::lua_gettop(state) - nargs;
ffi::lua_pushcfunction(state, message_handler);
ffi::lua_insert(state, msgh_position);
let ret = ffi::lua_pcall(state, nargs, nresults, msgh_position);
ffi::lua_remove(state, msgh_position);
ret
}
2017-06-15 10:26:39 -04:00
pub unsafe fn resume_with_traceback(
state: *mut ffi::lua_State,
from: *mut ffi::lua_State,
nargs: c_int,
) -> c_int {
let res = ffi::lua_resume(state, from, nargs);
if res != ffi::LUA_OK && res != ffi::LUA_YIELD {
if let Some(error) = pop_wrapped_error(state) {
ffi::luaL_traceback(state, state, ptr::null(), 0);
let traceback = CStr::from_ptr(ffi::lua_tolstring(state, -1, ptr::null_mut()))
2017-06-24 22:26:35 -04:00
.to_str()
2017-06-25 01:10:26 -04:00
.unwrap_or_else(|_| "<could not capture traceback>")
2017-06-24 22:26:35 -04:00
.to_owned();
push_wrapped_error(state, Error::CallbackError {
traceback,
cause: Arc::new(error),
});
ffi::lua_remove(state, -2);
2017-06-24 22:26:35 -04:00
} else if !is_wrapped_panic(state, 1) {
let s = ffi::lua_tolstring(state, 1, ptr::null_mut());
if !s.is_null() {
ffi::luaL_traceback(state, state, s, 0);
2017-06-24 22:26:35 -04:00
} else {
ffi::luaL_traceback(state, state, cstr!("<unprintable lua error>"), 0);
}
ffi::lua_remove(state, -2);
}
}
res
}
2017-05-21 19:50:59 -04:00
// A variant of pcall that does not allow lua to catch panic errors from callback_error
pub unsafe extern "C" fn safe_pcall(state: *mut ffi::lua_State) -> c_int {
if ffi::lua_pcall(state, ffi::lua_gettop(state) - 1, ffi::LUA_MULTRET, 0) != ffi::LUA_OK {
2017-06-24 22:26:35 -04:00
if is_wrapped_panic(state, -1) {
2017-05-21 19:50:59 -04:00
ffi::lua_error(state);
}
ffi::lua_pushboolean(state, 0);
ffi::lua_insert(state, -2);
2
} else {
ffi::lua_pushboolean(state, 1);
ffi::lua_insert(state, 1);
ffi::lua_gettop(state)
2017-05-21 19:50:59 -04:00
}
}
// A variant of xpcall that does not allow lua to catch panic errors 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 {
2017-06-24 22:26:35 -04:00
if is_wrapped_panic(state, -1) {
2017-05-21 19:50:59 -04:00
1
} else {
ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1));
ffi::lua_insert(state, 1);
ffi::lua_call(state, ffi::lua_gettop(state) - 1, ffi::LUA_MULTRET);
ffi::lua_gettop(state)
}
}
ffi::lua_pushvalue(state, 2);
ffi::lua_pushcclosure(state, xpcall_msgh, 1);
ffi::lua_copy(state, 1, 2);
ffi::lua_insert(state, 1);
let res = ffi::lua_pcall(state, ffi::lua_gettop(state) - 2, ffi::LUA_MULTRET, 1);
if res != ffi::LUA_OK {
2017-06-24 22:26:35 -04:00
if is_wrapped_panic(state, -1) {
2017-05-21 19:50:59 -04:00
ffi::lua_error(state);
}
ffi::lua_pushboolean(state, 0);
ffi::lua_insert(state, -2);
2
} else {
ffi::lua_pushboolean(state, 1);
ffi::lua_insert(state, 1);
ffi::lua_gettop(state)
2017-05-21 19:50:59 -04:00
}
}
/// Does not call checkstack, uses 1 stack space
pub unsafe fn main_state(state: *mut ffi::lua_State) -> *mut ffi::lua_State {
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_MAINTHREAD);
let main_state = ffi::lua_tothread(state, -1);
ffi::lua_pop(state, 1);
main_state
}
pub struct WrappedError(pub Error);
2017-06-25 01:10:26 -04:00
pub struct WrappedPanic(pub Option<Box<Any + Send>>);
2017-06-24 22:26:35 -04:00
// Pushes a WrappedError::Error to the top of the stack
pub unsafe fn push_wrapped_error(state: *mut ffi::lua_State, err: Error) {
unsafe extern "C" fn error_tostring(state: *mut ffi::lua_State) -> c_int {
callback_error(state, || if is_wrapped_error(state, -1) {
let error = &*get_userdata::<WrappedError>(state, -1);
2017-06-24 22:26:35 -04:00
push_string(state, &error.0.to_string());
ffi::lua_remove(state, -2);
Ok(1)
} else {
2017-08-01 13:36:26 -04:00
panic!("internal error: userdata mismatch in Error metamethod");
})
}
ffi::luaL_checkstack(state, 2, ptr::null());
2017-05-21 19:50:59 -04:00
push_userdata(state, WrappedError(err));
2017-05-21 19:50:59 -04:00
get_error_metatable(state);
if ffi::lua_isnil(state, -1) != 0 {
ffi::lua_pop(state, 1);
ffi::luaL_checkstack(state, 7, ptr::null());
2017-05-21 19:50:59 -04:00
ffi::lua_newtable(state);
2017-06-15 10:26:39 -04:00
ffi::lua_pushlightuserdata(
state,
&ERROR_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
);
2017-05-21 19:50:59 -04:00
ffi::lua_pushvalue(state, -2);
push_string(state, "__gc");
ffi::lua_pushcfunction(state, userdata_destructor::<WrappedError>);
2017-05-21 19:50:59 -04:00
ffi::lua_settable(state, -3);
push_string(state, "__tostring");
ffi::lua_pushcfunction(state, error_tostring);
ffi::lua_settable(state, -3);
2017-05-21 19:50:59 -04:00
push_string(state, "__metatable");
ffi::lua_pushboolean(state, 0);
ffi::lua_settable(state, -3);
ffi::lua_settable(state, ffi::LUA_REGISTRYINDEX);
}
ffi::lua_setmetatable(state, -2);
}
2017-06-24 22:26:35 -04:00
// Pushes a WrappedError::Panic to the top of the stack
pub unsafe fn push_wrapped_panic(state: *mut ffi::lua_State, panic: Box<Any + Send>) {
ffi::luaL_checkstack(state, 2, ptr::null());
push_userdata(state, WrappedPanic(Some(panic)));
2017-06-24 22:26:35 -04:00
get_panic_metatable(state);
if ffi::lua_isnil(state, -1) != 0 {
ffi::lua_pop(state, 1);
ffi::luaL_checkstack(state, 7, ptr::null());
ffi::lua_newtable(state);
ffi::lua_pushlightuserdata(
state,
&PANIC_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
);
ffi::lua_pushvalue(state, -2);
push_string(state, "__gc");
ffi::lua_pushcfunction(state, userdata_destructor::<WrappedPanic>);
2017-06-24 22:26:35 -04:00
ffi::lua_settable(state, -3);
push_string(state, "__metatable");
ffi::lua_pushboolean(state, 0);
ffi::lua_settable(state, -3);
ffi::lua_settable(state, ffi::LUA_REGISTRYINDEX);
}
ffi::lua_setmetatable(state, -2);
}
// Pops a WrappedError off of the top of the stack, if it is a WrappedError. If
// it is not a WrappedError, returns None and does not pop anything.
pub unsafe fn pop_wrapped_error(state: *mut ffi::lua_State) -> Option<Error> {
2017-06-24 22:26:35 -04:00
if ffi::lua_gettop(state) == 0 || !is_wrapped_error(state, -1) {
None
} else {
let err = &*get_userdata::<WrappedError>(state, -1);
let err = err.0.clone();
ffi::lua_pop(state, 1);
Some(err)
2017-06-24 22:26:35 -04:00
}
}
2017-06-24 22:26:35 -04:00
// Checks if the value at the given index is a WrappedError
pub unsafe fn is_wrapped_error(state: *mut ffi::lua_State, index: c_int) -> bool {
2017-06-15 10:26:39 -04:00
assert_ne!(
ffi::lua_checkstack(state, 2),
0,
2017-06-24 22:26:35 -04:00
"somehow not enough stack space to check if a value is a WrappedError"
2017-06-15 10:26:39 -04:00
);
2017-05-21 19:50:59 -04:00
let index = ffi::lua_absindex(state, index);
let userdata = ffi::lua_touserdata(state, index);
if userdata.is_null() {
2017-06-24 22:26:35 -04:00
return false;
2017-05-21 19:50:59 -04:00
}
if ffi::lua_getmetatable(state, index) == 0 {
2017-06-24 22:26:35 -04:00
return false;
2017-05-21 19:50:59 -04:00
}
get_error_metatable(state);
2017-06-24 22:26:35 -04:00
let res = ffi::lua_rawequal(state, -1, -2) != 0;
2017-05-21 19:50:59 -04:00
ffi::lua_pop(state, 2);
2017-06-24 22:26:35 -04:00
res
}
// Checks if the value at the given index is a WrappedPanic
pub unsafe fn is_wrapped_panic(state: *mut ffi::lua_State, index: c_int) -> bool {
assert_ne!(
ffi::lua_checkstack(state, 2),
0,
"somehow not enough stack space to check if a value is a wrapped panic"
);
let index = ffi::lua_absindex(state, index);
let userdata = ffi::lua_touserdata(state, index);
if userdata.is_null() {
return false;
}
Big API incompatible error change, remove dependency on error_chain The current situation with error_chain is less than ideal, and there are lots of conflicting interests that are impossible to meet at once. Here is an unorganized brain dump of the current situation, stay awhile and listen! This change was triggered ultimately by the desire to make LuaError implement Clone, and this is currently impossible with error_chain. LuaError must implement Clone to be a proper lua citizen that can live as userdata within a lua runtime, because there is no way to limit what the lua runtime can do with a received error. Currently, this is solved by there being a rule that the error will "expire" if the error is passed back into rust, and this is very sub-optimal. In fact, one could easily imagine a scenario where lua is for example memoizing some function, and if the function has ever errored in the past the function should continue returning the same error, and this situation immediately fails with this restriciton in place. Additionally, there are other more minor problems with error_chain which make the API less good than it could be, or limit how we can use error_chain. This change has already solved a small bug in a Chucklefish project, where the conversion from an external error type (Borrow[Mut]Error) was allowed but not intended for user code, and was accidentally used. Additionally, pattern matching on error_chain errors, which should be common when dealing with Lua, is less convenient than a hand rolled error type. So, if we decide not to use error_chain, we now have a new set of problems if we decide interoperability with error_chain is important. The first problem we run into is that there are two natural bounds for wrapped errors that we would pick, (Error + Send + Sync), or just Error, and neither of them will interoperate well with error_chain. (Error + Send + Sync) means we can't wrap error chain errors into LuaError::ExternalError (they're missing the Sync bound), and having the bounds be just Error means the opposite, that we can't hold a LuaError inside an error_chain error. We could just decide that interoperability with error_chain is the most important qualification, and pick (Error + Send), but this causes a DIFFERENT set of problems. The rust ecosystem has the two primary error bounds as Error or (Error + Send + Sync), and there are Into impls from &str / String to Box<Error + Send + Sync> for example, but NOT (Error + Send). This means that we are forced to manually recreate the conversions from &str / String to LuaError rather than relying on a single Into<Box<Error + Send + Sync>> bound, but this means that string conversions have a different set of methods than other error types for external error conversion. I have not been able to figure out an API that I am happy with that uses the (Error + Send) bound. Box<Error> is obnoxious because not having errors implement Send causes needless problems in a multithreaded context, so that leaves (Error + Send + Sync). This is actually a completely reasonable bound for external errors, and has the nice String Into impls that we would want, the ONLY problem is that it is a pain to interoperate with the current version of error_chain. It would be nice to be able to specify the traits that an error generated by the error_chain macro would implement, and this is apparently in progress in the error_chain library. This would solve both the problem with not being able to implement Clone and the problems with (Error + Send) bounds. I am not convinced that this library should go back to using error_chain when that functionality is in stable error_chain though, because of the other minor usability problems with using error_chain. In that theoretical situation, the downside of NOT using error_chain is simply that there would not be automatic stacktraces of LuaError. This is not a huge problem, because stack traces of lua errors are not extremely useful, and for external errors it is not too hard to create a different version of the LuaExternalResult / LuaExternalError traits and do conversion from an error_chain type into a type that will print the stacktrace on display, or use downcasting in the error causes. So in summary, this library is no longer using error_chain, and probably will not use it again in the future. Currently this means that to interoperate with error_chain, you should use error_chain 0.8.1, which derives Sync on errors, or wait for a version that supports user defined trait derives. In the future when error_chain supports user defined trait derives, users may have to take an extra step to make wrapped external errors print the stacktrace that they capture. This change works, but is not entirely complete. There is no error documentation yet, and the change brought to a head an ugly module organization problem. There will be more commits for documentation and reorganization, then a new stable version of rlua.
2017-06-24 18:11:56 -04:00
2017-06-24 22:26:35 -04:00
if ffi::lua_getmetatable(state, index) == 0 {
return false;
2017-05-21 19:50:59 -04:00
}
2017-06-24 22:26:35 -04:00
get_panic_metatable(state);
let res = ffi::lua_rawequal(state, -1, -2) != 0;
ffi::lua_pop(state, 2);
return res;
2017-05-21 19:50:59 -04:00
}
2017-06-24 22:26:35 -04:00
pub unsafe fn get_error_metatable(state: *mut ffi::lua_State) -> c_int {
2017-06-15 10:26:39 -04:00
ffi::lua_pushlightuserdata(
state,
&ERROR_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
);
2017-05-21 19:50:59 -04:00
ffi::lua_gettable(state, ffi::LUA_REGISTRYINDEX)
}
2017-06-24 22:26:35 -04:00
pub unsafe fn get_panic_metatable(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushlightuserdata(
state,
&PANIC_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
);
ffi::lua_gettable(state, ffi::LUA_REGISTRYINDEX)
}
static ERROR_METATABLE_REGISTRY_KEY: u8 = 0;
static PANIC_METATABLE_REGISTRY_KEY: u8 = 0;