Compare commits

...

21 Commits

Author SHA1 Message Date
Alex Orlenko 83ed442bf9 v0.8.3 2022-08-02 12:43:15 +01:00
Alex Orlenko bf6708ba58 Fallback to Lua internal allocator if unable to create Lua VM with Rust one.
This should fix #194
2022-08-02 10:35:39 +01:00
Alex Orlenko 0cd724f63b Fix Lua assertion when inspecting another thread stack.
The thread can be dead and it's not safe to call __tostring metamethod (if present) on error object.
Fixes #195
2022-08-01 22:07:39 +01:00
Alex Orlenko 5330b900fd Check that Lua state is non-null in init_from_ptr() 2022-08-01 15:21:14 +01:00
Alex Orlenko ee32dc33f3 Check that lua_newstate() returns non-null Lua state 2022-08-01 12:56:36 +01:00
Alex Orlenko ab029b087d Close to-be-closed variables for Lua 5.4 when using call_async functions
Fixes #192
2022-07-31 15:33:02 +01:00
Alex Orlenko 185fee956d v0.8.2 2022-07-25 14:23:25 +01:00
Alex Orlenko 4fc69be5f6 Update rustyline dev dependency 2022-07-25 14:14:01 +01:00
Alex Orlenko 3ec076693a Add FAQ 2022-07-24 11:59:04 +01:00
Alex Orlenko 95adf8df8e Add __iter to destructed metatable (luau) 2022-07-24 11:55:36 +01:00
Alex Orlenko 4a25eab257 Merge pull request #189 from hack3ric/master
`Function::bind`: simply clone the function if args are empty `MultiValue`
2022-07-22 00:28:18 +01:00
Alex Orlenko 40fe937878 Fix a bug in Function::bind when args and binds are empty
This leads to a Lua assertion due to using wrong stack index
2022-07-22 00:24:53 +01:00
Eric Long a6b178328d Merge branch 'master' of https://github.com/hack3ric/mlua 2022-07-21 20:55:28 +08:00
Eric Long f3f173fcb6 Function::bind: simply clone the function if args are empty MultiValue 2022-07-21 20:54:49 +08:00
Alex Orlenko d3b48cf2f3 Use Luau tags to mark userdata objects as destructed 2022-07-18 10:38:22 +01:00
Alex Orlenko f9ff6116db Use MaybeUninit instead of hack in protect_lua_closure 2022-07-17 11:33:51 +01:00
Alex Orlenko f75af6d75f Fix clippy warnings 2022-07-17 11:11:30 +01:00
Alex Orlenko 059e41bafb Optimize WrappedFailure userdata detection.
This is done by comparing a metatable pointer to previously saved one (it never changes).
2022-07-17 11:00:46 +01:00
Alex Orlenko f7ee6dc635 Add lua_xpush to 5.1-5.4 2022-07-17 11:00:46 +01:00
Alex Orlenko 0919ff21c9 Merge pull request #181 from hack3ric/master
Add `MultiValue::get` & add clear error in `Index`
2022-07-01 10:10:16 +01:00
Eric Long 553251761f Add MultiValue::get & add clear error in Index 2022-07-01 01:59:40 +08:00
19 changed files with 289 additions and 64 deletions
+12
View File
@@ -1,3 +1,15 @@
## v0.8.3
- Close to-be-closed variables for Lua 5.4 when using call_async functions (#192)
- Fixed Lua assertion when inspecting another thread stack. (#195)
- Use more reliable way to create LuaJIT VM (which can fail if use Rust allocator on non-x86 platforms)
## v0.8.2
- Performance optimizations in handling UserData
- Minimal Luau updated to 0.536
- Fixed bug in `Function::bind` when passing empty binds and no arguments (#189)
## v0.8.1
- Added `Lua::create_proxy` for accessing to UserData static fields and functions without instance
+3 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.8.1" # remember to update html_root_url and mlua_derive
version = "0.8.3" # remember to update html_root_url and mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
edition = "2021"
repository = "https://github.com/khvzak/mlua"
@@ -58,10 +58,10 @@ cc = { version = "1.0" }
pkg-config = { version = "0.3.17" }
lua-src = { version = ">= 544.0.0, < 550.0.0", optional = true }
luajit-src = { version = ">= 210.4.0, < 220.0.0", optional = true }
luau0-src = { version = "0.3.2", optional = true }
luau0-src = { version = "0.3.6", optional = true }
[dev-dependencies]
rustyline = "9.0"
rustyline = "10.0"
criterion = { version = "0.3.4", features = ["html_reports", "async_tokio"] }
trybuild = "1.0"
futures = "0.3.5"
+21
View File
@@ -0,0 +1,21 @@
# mlua FAQ
This file is for general questions that don't fit into the README or crate docs.
## Loading a C module fails with error `undefined symbol: lua_xxx`. How to fix?
Add the following rustflags to your [.cargo/config](http://doc.crates.io/config.html) in order to properly export Lua symbols:
```toml
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-args=-rdynamic"]
[target.x86_64-apple-darwin]
rustflags = ["-C", "link-args=-rdynamic"]
```
## I want to add support for a Lua VM fork to mlua. Do you accept pull requests?
Adding new feature flag to support a Lua VM fork is a major step that requires huge effort to maintain it.
Regular updates, testing, checking compatibility, etc.
That's why I don't plan to support new Lua VM forks or other languages in mlua.
+5 -1
View File
@@ -11,7 +11,11 @@
[codecov.io]: https://codecov.io/gh/khvzak/mlua
[MSRV]: https://img.shields.io/badge/rust-1.56+-brightgreen.svg?&logo=rust
[Guided Tour](examples/guided_tour.rs)
[Guided Tour] | [Benchmarks] | [FAQ]
[Guided Tour]: examples/guided_tour.rs
[Benchmarks]: https://github.com/khvzak/script-bench-rs
[FAQ]: FAQ.md
`mlua` is bindings to [Lua](https://www.lua.org) programming language for Rust with a goal to provide
_safe_ (as far as it's possible), high level, easy to use, practical and flexible API.
+1 -1
View File
@@ -5,7 +5,7 @@ use rustyline::Editor;
fn main() {
let lua = Lua::new();
let mut editor = Editor::<()>::new();
let mut editor = Editor::<()>::new().expect("Failed to make rustyline editor");
loop {
let mut prompt = "> ";
+6
View File
@@ -324,6 +324,12 @@ pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char {
lua_tolstring(L, i, ptr::null_mut())
}
#[inline(always)]
pub unsafe fn lua_xpush(from: *mut lua_State, to: *mut lua_State, idx: c_int) {
lua_pushvalue(from, idx);
lua_xmove(from, to, 1);
}
//
// Debug API
//
+6
View File
@@ -410,6 +410,12 @@ pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char {
lua_tolstring(L, i, ptr::null_mut())
}
#[inline(always)]
pub unsafe fn lua_xpush(from: *mut lua_State, to: *mut lua_State, idx: c_int) {
lua_pushvalue(from, idx);
lua_xmove(from, to, 1);
}
//
// Debug API
//
+6
View File
@@ -434,6 +434,12 @@ pub unsafe fn lua_replace(L: *mut lua_State, idx: c_int) {
lua_pop(L, 1)
}
#[inline(always)]
pub unsafe fn lua_xpush(from: *mut lua_State, to: *mut lua_State, idx: c_int) {
lua_pushvalue(from, idx);
lua_xmove(from, to, 1);
}
//
// Debug API
//
+6
View File
@@ -455,6 +455,12 @@ pub unsafe fn lua_replace(L: *mut lua_State, idx: c_int) {
lua_pop(L, 1)
}
#[inline(always)]
pub unsafe fn lua_xpush(from: *mut lua_State, to: *mut lua_State, idx: c_int) {
lua_pushvalue(from, idx);
lua_xmove(from, to, 1);
}
#[inline(always)]
pub unsafe fn lua_newuserdata(L: *mut lua_State, sz: usize) -> *mut c_void {
lua_newuserdatauv(L, sz, 1)
+7 -1
View File
@@ -260,6 +260,7 @@ extern "C" {
pub fn lua_concat(L: *mut lua_State, n: c_int);
// TODO: lua_encodepointer
pub fn lua_clock() -> c_double;
pub fn lua_setuserdatatag(L: *mut lua_State, idx: c_int, tag: c_int);
pub fn lua_setuserdatadtor(
L: *mut lua_State,
tag: c_int,
@@ -437,7 +438,12 @@ extern "C" {
pub fn lua_setupvalue(L: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char;
pub fn lua_singlestep(L: *mut lua_State, enabled: c_int);
pub fn lua_breakpoint(L: *mut lua_State, funcindex: c_int, line: c_int, enabled: c_int);
pub fn lua_breakpoint(
L: *mut lua_State,
funcindex: c_int,
line: c_int,
enabled: c_int,
) -> c_int;
pub fn lua_getcoverage(
L: *mut lua_State,
+7 -1
View File
@@ -198,7 +198,9 @@ impl<'lua> Function<'lua> {
for i in 0..nbinds {
ffi::lua_pushvalue(state, ffi::lua_upvalueindex(i + 2));
}
ffi::lua_rotate(state, 1, nbinds);
if nargs > 0 {
ffi::lua_rotate(state, 1, nbinds);
}
nargs + nbinds
}
@@ -208,6 +210,10 @@ impl<'lua> Function<'lua> {
let args = args.to_lua_multi(lua)?;
let nargs = args.len() as c_int;
if nargs == 0 {
return Ok(self.clone());
}
if nargs + 1 > ffi::LUA_MAX_UPVALUES {
return Err(Error::BindError);
}
+1 -1
View File
@@ -72,7 +72,7 @@
//! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
// mlua types in rustdoc of other crates get linked to here.
#![doc(html_root_url = "https://docs.rs/mlua/0.8.1")]
#![doc(html_root_url = "https://docs.rs/mlua/0.8.3")]
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
#![doc(test(attr(deny(warnings))))]
+39 -19
View File
@@ -25,7 +25,7 @@ use crate::string::String;
use crate::table::Table;
use crate::thread::Thread;
use crate::types::{
Callback, CallbackUpvalue, DestructedUserdataMT, Integer, LightUserData, LuaRef, MaybeSend,
Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData, LuaRef, MaybeSend,
Number, RegistryKey,
};
use crate::userdata::{AnyUserData, UserData, UserDataCell};
@@ -111,6 +111,9 @@ pub(crate) struct ExtraData {
#[cfg(feature = "async")]
recycled_thread_cache: Vec<c_int>,
// Address of `WrappedFailure` metatable
wrapped_failure_mt_ptr: *const c_void,
// Index of `Option<Waker>` userdata on the ref thread
#[cfg(feature = "async")]
ref_waker_idx: c_int,
@@ -434,12 +437,19 @@ impl Lua {
let use_rust_allocator = !(cfg!(feature = "luajit") && cfg!(not(feature = "vendored")));
let (state, mem_info) = if use_rust_allocator {
let mem_info = Box::into_raw(Box::new(MemoryInfo::default()));
let state = ffi::lua_newstate(allocator, mem_info as *mut c_void);
let mut mem_info = Box::into_raw(Box::new(MemoryInfo::default()));
let mut state = ffi::lua_newstate(allocator, mem_info as *mut c_void);
// If state is null (it's possible for LuaJIT on non-x86 arch) then switch to Lua internal allocator
if state.is_null() {
drop(Box::from_raw(mem_info));
mem_info = ptr::null_mut();
state = ffi::luaL_newstate();
}
(state, mem_info)
} else {
(ffi::luaL_newstate(), ptr::null_mut())
};
assert!(!state.is_null(), "Failed to instantiate Lua VM");
ffi::luaL_requiref(state, cstr!("_G"), ffi::luaopen_base, 1);
ffi::lua_pop(state, 1);
@@ -493,6 +503,7 @@ impl Lua {
/// by calling this function again.
#[allow(clippy::missing_safety_doc)]
pub unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> Lua {
assert!(!state.is_null(), "Lua state is NULL");
let main_state = get_main_state(state).unwrap_or(state);
let main_state_top = ffi::lua_gettop(main_state);
@@ -538,6 +549,13 @@ impl Lua {
"Error while creating ref thread",
);
let wrapped_failure_mt_ptr = {
get_gc_metatable::<WrappedFailure>(state);
let ptr = ffi::lua_topointer(state, -1);
ffi::lua_pop(state, 1);
ptr
};
// Create empty Waker slot on the ref thread
#[cfg(feature = "async")]
let ref_waker_idx = {
@@ -568,6 +586,7 @@ impl Lua {
multivalue_cache: Vec::with_capacity(MULTIVALUE_CACHE_SIZE),
#[cfg(feature = "async")]
recycled_thread_cache: Vec::new(),
wrapped_failure_mt_ptr,
#[cfg(feature = "async")]
ref_waker_idx,
#[cfg(not(feature = "luau"))]
@@ -591,13 +610,13 @@ impl Lua {
"Error while storing extra data",
);
// Register `DestructedUserdataMT` type
// Register `DestructedUserdata` type
get_destructed_userdata_metatable(main_state);
let destructed_mt_ptr = ffi::lua_topointer(main_state, -1);
let destructed_mt_typeid = Some(TypeId::of::<DestructedUserdataMT>());
let destructed_ud_typeid = TypeId::of::<DestructedUserdata>();
(*extra.get())
.registered_userdata_mt
.insert(destructed_mt_ptr, destructed_mt_typeid);
.insert(destructed_mt_ptr, Some(destructed_ud_typeid));
ffi::lua_pop(main_state, 1);
mlua_debug_assert!(
@@ -1701,15 +1720,16 @@ impl Lua {
all(feature = "luajit", feature = "vendored"),
feature = "luau",
))]
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) {
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) -> bool {
let extra = &mut *self.extra.get();
let thread_state = ffi::lua_tothread(extra.ref_thread, thread.0.index);
if extra.recycled_thread_cache.len() < extra.recycled_thread_cache.capacity() {
let thread_state = ffi::lua_tothread(extra.ref_thread, thread.0.index);
#[cfg(feature = "lua54")]
let status = ffi::lua_resetthread(thread_state);
#[cfg(feature = "lua54")]
if status != ffi::LUA_OK {
return;
// Error object is on top, drop it
ffi::lua_settop(thread_state, 0);
}
#[cfg(all(feature = "luajit", feature = "vendored"))]
ffi::lua_resetthread(self.state, thread_state);
@@ -1717,7 +1737,9 @@ impl Lua {
ffi::lua_resetthread(thread_state);
extra.recycled_thread_cache.push(thread.0.index);
thread.0.index = 0;
return true;
}
false
}
/// Create a Lua userdata object from a custom userdata type.
@@ -2293,6 +2315,8 @@ impl Lua {
// Uses 2 stack spaces, does not call checkstack
pub(crate) unsafe fn pop_value(&self) -> Value {
let state = self.state;
let extra = &mut *self.extra.get();
match ffi::lua_type(state, -1) {
ffi::LUA_TNIL => {
ffi::lua_pop(state, 1);
@@ -2353,9 +2377,11 @@ impl Lua {
ffi::LUA_TFUNCTION => Value::Function(Function(self.pop_ref())),
ffi::LUA_TUSERDATA => {
let wrapped_failure_mt_ptr = extra.wrapped_failure_mt_ptr;
// We must prevent interaction with userdata types other than UserData OR a WrappedError.
// WrappedPanics are automatically resumed.
match get_gc_userdata::<WrappedFailure>(state, -1).as_mut() {
match get_gc_userdata::<WrappedFailure>(state, -1, wrapped_failure_mt_ptr).as_mut()
{
Some(WrappedFailure::Error(err)) => {
let err = err.clone();
ffi::lua_pop(state, 1);
@@ -2394,12 +2420,6 @@ impl Lua {
"Lua instance passed Value created from a different main Lua state"
);
let extra = &*self.extra.get();
#[cfg(not(feature = "luau"))]
{
ffi::lua_pushvalue(extra.ref_thread, lref.index);
ffi::lua_xmove(extra.ref_thread, self.state, 1);
}
#[cfg(feature = "luau")]
ffi::lua_xpush(extra.ref_thread, self.state, lref.index);
}
@@ -2578,7 +2598,7 @@ impl Lua {
let extra = &*self.extra.get();
match extra.registered_userdata_mt.get(&mt_ptr) {
Some(&type_id) if type_id == Some(TypeId::of::<DestructedUserdataMT>()) => {
Some(&type_id) if type_id == Some(TypeId::of::<DestructedUserdata>()) => {
Err(Error::UserDataDestructed)
}
Some(&type_id) => Ok(type_id),
@@ -2952,14 +2972,14 @@ struct StateGuard<'a>(&'a mut LuaInner, *mut ffi::lua_State);
impl<'a> StateGuard<'a> {
fn new(inner: &'a mut LuaInner, mut state: *mut ffi::lua_State) -> Self {
mem::swap(&mut (*inner).state, &mut state);
mem::swap(&mut inner.state, &mut state);
Self(inner, state)
}
}
impl<'a> Drop for StateGuard<'a> {
fn drop(&mut self) {
mem::swap(&mut (*self.0).state, &mut self.1);
mem::swap(&mut self.0.state, &mut self.1);
}
}
+17 -4
View File
@@ -4,7 +4,7 @@ use std::os::raw::c_int;
use crate::error::{Error, Result};
use crate::ffi;
use crate::types::LuaRef;
use crate::util::{check_stack, error_traceback, pop_error, StackGuard};
use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard};
use crate::value::{FromLuaMulti, ToLuaMulti};
#[cfg(any(
@@ -136,8 +136,12 @@ impl<'lua> Thread<'lua> {
let ret = ffi::lua_resume(thread_state, lua.state, nargs, &mut nresults as *mut c_int);
if ret != ffi::LUA_OK && ret != ffi::LUA_YIELD {
protect_lua!(lua.state, 0, 0, |_| error_traceback(thread_state))?;
return Err(pop_error(thread_state, ret));
check_stack(lua.state, 3)?;
protect_lua!(lua.state, 0, 1, |state| error_traceback_thread(
state,
thread_state
))?;
return Err(pop_error(lua.state, ret));
}
let mut results = args; // Reuse MultiValue container
@@ -357,7 +361,16 @@ impl<'lua, R> Drop for AsyncThread<'lua, R> {
fn drop(&mut self) {
if self.recycle {
unsafe {
self.thread.0.lua.recycle_thread(&mut self.thread);
let lua = self.thread.0.lua;
// For Lua 5.4 this also closes all pending to-be-closed variables
if !lua.recycle_thread(&mut self.thread) {
#[cfg(feature = "lua54")]
if self.thread.status() == ThreadStatus::Error {
let thread_state =
lua.ref_thread_exec(|t| ffi::lua_tothread(t, self.thread.0.index));
ffi::lua_resetthread(thread_state);
}
}
}
}
}
+1 -1
View File
@@ -83,7 +83,7 @@ pub trait MaybeSend {}
#[cfg(not(feature = "send"))]
impl<T> MaybeSend for T {}
pub(crate) struct DestructedUserdataMT;
pub(crate) struct DestructedUserdata;
/// An auto generated key into the Lua registry.
///
+52 -30
View File
@@ -1,6 +1,7 @@
use std::any::{Any, TypeId};
use std::ffi::CStr;
use std::fmt::Write;
use std::mem::MaybeUninit;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
use std::sync::Arc;
@@ -136,26 +137,21 @@ where
F: Fn(*mut ffi::lua_State) -> R,
R: Copy,
{
union URes<R: Copy> {
uninit: (),
init: R,
}
struct Params<F, R: Copy> {
function: F,
result: URes<R>,
result: MaybeUninit<R>,
nresults: c_int,
}
unsafe extern "C" fn do_call<F, R>(state: *mut ffi::lua_State) -> c_int
where
R: Copy,
F: Fn(*mut ffi::lua_State) -> R,
R: Copy,
{
let params = ffi::lua_touserdata(state, -1) as *mut Params<F, R>;
ffi::lua_pop(state, 1);
(*params).result.init = ((*params).function)(state);
(*params).result.write(((*params).function)(state));
if (*params).nresults == ffi::LUA_MULTRET {
ffi::lua_gettop(state)
@@ -174,7 +170,7 @@ where
let mut params = Params {
function: f,
result: URes { uninit: () },
result: MaybeUninit::uninit(),
nresults,
};
@@ -185,7 +181,7 @@ where
if ret == ffi::LUA_OK {
// `LUA_OK` is only returned when the `do_call` function has completed successfully, so
// `params.result` is definitely initialized.
Ok(params.result.init)
Ok(params.result.assume_init())
} else {
Err(pop_error(state, ret))
}
@@ -203,7 +199,7 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
"pop_error called with non-error return code"
);
match get_gc_userdata::<WrappedFailure>(state, -1).as_mut() {
match get_gc_userdata::<WrappedFailure>(state, -1, ptr::null()).as_mut() {
Some(WrappedFailure::Error(err)) => {
ffi::lua_pop(state, 1);
err.clone()
@@ -314,10 +310,7 @@ pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T, protect: bool)
#[inline]
pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<()> {
unsafe extern "C" fn destructor<T>(ud: *mut c_void) {
let ud = ud as *mut T;
if *(ud.offset(1) as *mut u8) == 0 {
ptr::drop_in_place(ud);
}
ptr::drop_in_place(ud as *mut T);
}
let size = mem::size_of::<T>() + 1;
@@ -329,7 +322,6 @@ pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T, protect: bool)
ffi::lua_newuserdatadtor(state, size, destructor::<T>) as *mut T
};
ptr::write(ud, t);
*(ud.offset(1) as *mut u8) = 0; // Mark as not destructed
Ok(())
}
@@ -373,10 +365,12 @@ pub unsafe fn take_userdata<T>(state: *mut ffi::lua_State) -> T {
get_destructed_userdata_metatable(state);
ffi::lua_setmetatable(state, -2);
let ud = get_userdata::<T>(state, -1);
// Update userdata tag to disable destructor and mark as destructed
#[cfg(feature = "luau")]
ffi::lua_setuserdatatag(state, -1, 1);
ffi::lua_pop(state, 1);
if cfg!(feature = "luau") {
*(ud.offset(1) as *mut u8) = 1; // Mark as destructed
}
ptr::read(ud)
}
@@ -394,16 +388,28 @@ pub unsafe fn push_gc_userdata<T: Any>(
}
// Uses 2 stack spaces, does not call checkstack
pub unsafe fn get_gc_userdata<T: Any>(state: *mut ffi::lua_State, index: c_int) -> *mut T {
pub unsafe fn get_gc_userdata<T: Any>(
state: *mut ffi::lua_State,
index: c_int,
mt_ptr: *const c_void,
) -> *mut T {
let ud = ffi::lua_touserdata(state, index) as *mut T;
if ud.is_null() || ffi::lua_getmetatable(state, index) == 0 {
return ptr::null_mut();
}
get_gc_metatable::<T>(state);
let res = ffi::lua_rawequal(state, -1, -2);
ffi::lua_pop(state, 2);
if res == 0 {
return ptr::null_mut();
if !mt_ptr.is_null() {
let ud_mt_ptr = ffi::lua_topointer(state, -1);
ffi::lua_pop(state, 1);
if !ptr::eq(ud_mt_ptr, mt_ptr) {
return ptr::null_mut();
}
} else {
get_gc_metatable::<T>(state);
let res = ffi::lua_rawequal(state, -1, -2);
ffi::lua_pop(state, 2);
if res == 0 {
return ptr::null_mut();
}
}
ud
}
@@ -679,7 +685,7 @@ pub unsafe extern "C" fn error_traceback(state: *mut ffi::lua_State) -> c_int {
return 1;
}
if get_gc_userdata::<WrappedFailure>(state, -1).is_null() {
if get_gc_userdata::<WrappedFailure>(state, -1, ptr::null()).is_null() {
let s = ffi::luaL_tolstring(state, -1, ptr::null_mut());
if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 {
ffi::luaL_traceback(state, state, s, 0);
@@ -690,6 +696,20 @@ pub unsafe extern "C" fn error_traceback(state: *mut ffi::lua_State) -> c_int {
1
}
// A variant of `error_traceback` that can safely inspect another (yielded) thread stack
pub unsafe fn error_traceback_thread(state: *mut ffi::lua_State, thread: *mut ffi::lua_State) {
// Move error object to the main thread to safely call `__tostring` metamethod if present
ffi::lua_xmove(thread, state, 1);
if get_gc_userdata::<WrappedFailure>(state, -1, ptr::null()).is_null() {
let s = ffi::luaL_tolstring(state, -1, ptr::null_mut());
if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 {
ffi::luaL_traceback(state, thread, s, 0);
ffi::lua_remove(state, -2);
}
}
}
// A variant of `pcall` that does not allow Lua to catch Rust panics from `callback_error`.
pub unsafe extern "C" fn safe_pcall(state: *mut ffi::lua_State) -> c_int {
ffi::luaL_checkstack(state, 2, ptr::null());
@@ -706,7 +726,7 @@ pub unsafe extern "C" fn safe_pcall(state: *mut ffi::lua_State) -> c_int {
ffi::lua_gettop(state)
} else {
if let Some(WrappedFailure::Panic(_)) =
get_gc_userdata::<WrappedFailure>(state, -1).as_ref()
get_gc_userdata::<WrappedFailure>(state, -1, ptr::null()).as_ref()
{
ffi::lua_error(state);
}
@@ -722,7 +742,7 @@ pub unsafe extern "C" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int {
ffi::luaL_checkstack(state, 2, ptr::null());
if let Some(WrappedFailure::Panic(_)) =
get_gc_userdata::<WrappedFailure>(state, -1).as_ref()
get_gc_userdata::<WrappedFailure>(state, -1, ptr::null()).as_ref()
{
1
} else {
@@ -752,7 +772,7 @@ pub unsafe extern "C" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int {
ffi::lua_gettop(state) - 1
} else {
if let Some(WrappedFailure::Panic(_)) =
get_gc_userdata::<WrappedFailure>(state, -1).as_ref()
get_gc_userdata::<WrappedFailure>(state, -1, ptr::null()).as_ref()
{
ffi::lua_error(state);
}
@@ -836,7 +856,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
callback_error(state, |_| {
check_stack(state, 3)?;
let err_buf = match get_gc_userdata::<WrappedFailure>(state, -1).as_ref() {
let err_buf = match get_gc_userdata::<WrappedFailure>(state, -1, ptr::null()).as_ref() {
Some(WrappedFailure::Error(error)) => {
let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key);
@@ -936,6 +956,8 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
"__pairs",
#[cfg(any(feature = "lua53", feature = "lua52", feature = "luajit52"))]
"__ipairs",
#[cfg(feature = "luau")]
"__iter",
#[cfg(feature = "lua54")]
"__close",
] {
+15 -2
View File
@@ -239,7 +239,7 @@ impl<'a, 'lua> IntoIterator for &'a MultiValue<'lua> {
#[inline]
fn into_iter(self) -> Self::IntoIter {
(&self.0).iter().rev()
self.0.iter().rev()
}
}
@@ -248,7 +248,15 @@ impl<'lua> Index<usize> for MultiValue<'lua> {
#[inline]
fn index(&self, index: usize) -> &Self::Output {
&self.0[self.0.len() - index - 1]
if let Some(result) = self.get(index) {
result
} else {
panic!(
"index out of bounds: the len is {} but the index is {}",
self.len(),
index
)
}
}
}
@@ -266,6 +274,11 @@ impl<'lua> MultiValue<'lua> {
v
}
#[inline]
pub fn get(&self, index: usize) -> Option<&Value<'lua>> {
self.0.get(self.0.len() - index - 1)
}
#[inline]
pub(crate) fn reserve(&mut self, size: usize) {
self.0.reserve(size);
+78
View File
@@ -174,6 +174,38 @@ async fn test_async_return_async_closure() -> Result<()> {
Ok(())
}
#[cfg(feature = "lua54")]
#[tokio::test]
async fn test_async_lua54_to_be_closed() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("close_count", 0)?;
let code = r#"
local t <close> = setmetatable({}, {
__close = function()
close_count = close_count + 1
end
})
error "test"
"#;
let f = lua.load(code).into_function()?;
// Test close using call_async
let _ = f.call_async::<_, ()>(()).await;
assert_eq!(globals.get::<_, usize>("close_count")?, 1);
// Don't close by default when awaiting async threads
let co = lua.create_thread(f.clone())?;
let _ = co.clone().into_async::<_, ()>(()).await;
assert_eq!(globals.get::<_, usize>("close_count")?, 1);
let _ = co.reset(f);
assert_eq!(globals.get::<_, usize>("close_count")?, 2);
Ok(())
}
#[tokio::test]
async fn test_async_thread_stream() -> Result<()> {
let lua = Lua::new();
@@ -278,6 +310,28 @@ async fn test_async_table() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn test_async_thread_cache() -> Result<()> {
let options = LuaOptions::new().thread_cache_size(4);
let lua = Lua::new_with(StdLib::ALL_SAFE, options)?;
let error_f = lua.create_async_function(|_, ()| async move {
Delay::new(Duration::from_millis(10)).await;
Err::<(), _>(Error::RuntimeError("test".to_string()))
})?;
let sleep = lua.create_async_function(|_, n| async move {
Delay::new(Duration::from_millis(n)).await;
Ok(format!("elapsed:{}ms", n))
})?;
assert!(error_f.call_async::<_, ()>(()).await.is_err());
// Next call should use cached thread
assert_eq!(sleep.call_async::<_, String>(3).await?, "elapsed:3ms");
Ok(())
}
#[tokio::test]
async fn test_async_userdata() -> Result<()> {
#[derive(Clone)]
@@ -372,6 +426,30 @@ async fn test_async_userdata() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn test_async_thread_error() -> Result<()> {
struct MyUserData;
impl UserData for MyUserData {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method("__tostring", |_, _this, ()| Ok("myuserdata error"))
}
}
let lua = Lua::new();
let result = lua
.load("function x(...) error(...) end x(...)")
.set_name("chunk")?
.call_async::<_, ()>(MyUserData)
.await;
assert!(
matches!(result, Err(Error::RuntimeError(cause)) if cause.contains("myuserdata error")),
"improper error traceback from dead thread"
);
Ok(())
}
#[tokio::test]
async fn test_async_scope() -> Result<()> {
let ref lua = Lua::new();
+6
View File
@@ -42,11 +42,17 @@ fn test_bind() -> Result<()> {
concat = concat.bind("foo")?;
concat = concat.bind("bar")?;
concat = concat.bind(("baz", "baf"))?;
assert_eq!(concat.call::<_, String>(())?, "foobarbazbaf");
assert_eq!(
concat.call::<_, String>(("hi", "wut"))?,
"foobarbazbafhiwut"
);
let mut concat2 = globals.get::<_, Function>("concat")?;
concat2 = concat2.bind(())?;
assert_eq!(concat2.call::<_, String>(())?, "");
assert_eq!(concat2.call::<_, String>(("ab", "cd"))?, "abcd");
Ok(())
}