Compare commits

...

32 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
Alex Orlenko b46b476f80 v0.8.1 2022-06-29 15:41:22 +01:00
Alex Orlenko 70e16b51ae Set source name to full file path in Luau require 2022-06-29 11:25:16 +01:00
Alex Orlenko 9596b97faa Update Lua::create_userdata doc about sharing metatable.
Closes #175
2022-06-29 00:25:26 +01:00
Alex Orlenko 8cbb3d8fae Merge pull request #180 from khvzak/userdata_proxy
Add Lua::create_proxy for easy access to UserData static fields and f…
2022-06-29 00:05:59 +01:00
Alex Orlenko e7f494530f Add Lua::create_proxy for easy access to UserData static fields and functions
Closes #178
2022-06-28 23:03:29 +01:00
Alex Orlenko 3746c3614f Merge pull request #179 from hack3ric/master
Implement utilities for MultiValue
2022-06-28 22:56:28 +01:00
Alex Orlenko 9af1aaf889 Make clippy happy 2022-06-28 21:28:48 +01:00
Eric Long c20eb20a59 Implement utilities for MultiValue 2022-06-29 01:21:32 +08:00
Alex Orlenko 04ba93137c Add Table::to_pointer() and String::to_pointer() functions 2022-06-27 14:58:48 +01:00
Alex Orlenko 113f91ace3 Derive Default for Lua::MemoryInfo 2022-06-27 13:57:15 +01:00
Alex Orlenko ff0d923aae Don't use custom allocator for non-vendored LuaJIT (fixes #176) 2022-06-27 13:04:32 +01:00
28 changed files with 458 additions and 94 deletions
+18
View File
@@ -1,3 +1,21 @@
## 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
- Added `Table::to_pointer()` and `String::to_pointer()` functions
- Bugfixes and improvements (#176 #179)
## v0.8.0
Changes since 0.7.4
- Roblox Luau support
+3 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.8.0" # 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 = "> ";
+1 -1
View File
@@ -484,7 +484,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
return ChunkMode::Binary;
}
#[cfg(feature = "luau")]
if *source.get(0).unwrap_or(&u8::MAX) < b'\n' {
if *source.first().unwrap_or(&u8::MAX) < b'\n' {
return ChunkMode::Binary;
}
ChunkMode::Text
+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.0")]
#![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))))]
+93 -25
View File
@@ -25,11 +25,11 @@ 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};
use crate::userdata_impl::{StaticUserDataFields, StaticUserDataMethods};
use crate::userdata_impl::{StaticUserDataFields, StaticUserDataMethods, UserDataProxy};
use crate::util::{
self, assert_stack, callback_error, check_stack, get_destructed_userdata_metatable,
get_gc_metatable, get_gc_userdata, get_main_state, get_userdata, init_error_registry,
@@ -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,
@@ -126,7 +129,7 @@ pub(crate) struct ExtraData {
sandboxed: bool,
}
#[cfg_attr(any(feature = "lua51", feature = "luajit"), allow(dead_code))]
#[derive(Default)]
struct MemoryInfo {
used_memory: isize,
memory_limit: isize,
@@ -430,12 +433,23 @@ impl Lua {
new_ptr
}
let mem_info = Box::into_raw(Box::new(MemoryInfo {
used_memory: 0,
memory_limit: 0,
}));
// Skip Rust allocator for non-vendored LuaJIT (see https://github.com/khvzak/mlua/issues/176)
let use_rust_allocator = !(cfg!(feature = "luajit") && cfg!(not(feature = "vendored")));
let state = ffi::lua_newstate(allocator, mem_info as *mut c_void);
let (state, mem_info) = if use_rust_allocator {
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);
@@ -489,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);
@@ -534,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 = {
@@ -564,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"))]
@@ -587,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!(
@@ -1697,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);
@@ -1713,10 +1737,15 @@ 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.
///
/// All userdata instances of type `T` shares the same metatable.
#[inline]
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: 'static + MaybeSend + UserData,
@@ -1729,6 +1758,7 @@ impl Lua {
/// Requires `feature = "serialize"`
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[inline]
pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: 'static + MaybeSend + UserData + Serialize,
@@ -1736,6 +1766,46 @@ impl Lua {
unsafe { self.make_userdata(UserDataCell::new_ser(data)) }
}
/// Create a Lua userdata "proxy" object from a custom userdata type.
///
/// Proxy object is an empty userdata object that has `T` metatable attached.
/// The main purpose of this object is to provide access to static fields and functions
/// without creating an instance of type `T`.
///
/// You can get or set uservalues on this object but you cannot borrow any Rust type.
///
/// # Examples
///
/// ```
/// # use mlua::{Lua, Result, UserData, UserDataFields, UserDataMethods};
/// # fn main() -> Result<()> {
/// # let lua = Lua::new();
/// struct MyUserData(i32);
///
/// impl UserData for MyUserData {
/// fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
/// fields.add_field_method_get("val", |_, this| Ok(this.0));
/// }
///
/// fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
/// methods.add_function("new", |_, value: i32| Ok(MyUserData(value)));
/// }
/// }
///
/// lua.globals().set("MyUserData", lua.create_proxy::<MyUserData>()?)?;
///
/// lua.load("assert(MyUserData.new(321).val == 321)").exec()?;
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn create_proxy<T>(&self) -> Result<AnyUserData>
where
T: 'static + UserData,
{
unsafe { self.make_userdata(UserDataCell::new(UserDataProxy::<T>(PhantomData))) }
}
/// Returns a handle to the global environment.
pub fn globals(&self) -> Table {
unsafe {
@@ -2245,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);
@@ -2305,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);
@@ -2346,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);
}
@@ -2530,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),
@@ -2866,7 +2934,7 @@ impl Lua {
let _sg = StackGuard::new(state);
assert_stack(state, 1);
let extra = extra_data(state)?;
let inner = &*(*extra.get()).inner.as_ref().unwrap();
let inner = (*extra.get()).inner.as_ref().unwrap();
Some(Lua(Arc::clone(inner)))
}
@@ -2904,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);
}
}
+5 -3
View File
@@ -85,10 +85,12 @@ fn lua_require(lua: &Lua, name: Option<std::string::String>) -> Result<Value> {
search_path = "?.luau;?.lua".into();
}
let mut source = None;
let (mut source, mut source_name) = (None, String::new());
for path in search_path.split(';') {
if let Ok(buf) = std::fs::read(path.replacen('?', &name, 1)) {
let file_path = path.replacen('?', &name, 1);
if let Ok(buf) = std::fs::read(&file_path) {
source = Some(buf);
source_name = file_path;
break;
}
}
@@ -96,7 +98,7 @@ fn lua_require(lua: &Lua, name: Option<std::string::String>) -> Result<Value> {
let value = lua
.load(&source)
.set_name(&format!("={}", name))?
.set_name(&format!("={}", source_name))?
.set_mode(ChunkMode::Text)
.call::<_, Value>(())?;
+1
View File
@@ -597,6 +597,7 @@ impl<'lua, 'scope> Drop for Scope<'lua, 'scope> {
}
}
#[allow(clippy::type_complexity)]
enum NonStaticMethod<'lua, T> {
Method(Box<dyn Fn(&'lua Lua, &T, MultiValue<'lua>) -> Result<MultiValue<'lua>>>),
MethodMut(Box<dyn FnMut(&'lua Lua, &mut T, MultiValue<'lua>) -> Result<MultiValue<'lua>>>),
+2 -7
View File
@@ -8,7 +8,6 @@ use rustc_hash::FxHashSet;
use serde::de::{self, IntoDeserializer};
use crate::error::{Error, Result};
use crate::ffi;
use crate::table::{Table, TablePairs, TableSequence};
use crate::value::Value;
@@ -563,9 +562,7 @@ impl RecursionGuard {
#[inline]
fn new(table: &Table, visited: &Rc<RefCell<FxHashSet<*const c_void>>>) -> Self {
let visited = Rc::clone(visited);
let lua = table.0.lua;
let ptr =
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, table.0.index)) };
let ptr = table.to_pointer();
visited.borrow_mut().insert(ptr);
RecursionGuard { ptr, visited }
}
@@ -585,9 +582,7 @@ fn check_value_if_skip(
) -> Result<bool> {
match value {
Value::Table(table) => {
let lua = table.0.lua;
let ptr =
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, table.0.index)) };
let ptr = table.to_pointer();
if visited.borrow().contains(&ptr) {
if options.deny_recursive_tables {
return Err(de::Error::custom("recursive table detected"));
+12
View File
@@ -1,5 +1,6 @@
use std::borrow::{Borrow, Cow};
use std::hash::{Hash, Hasher};
use std::os::raw::c_void;
use std::string::String as StdString;
use std::{slice, str};
@@ -112,6 +113,17 @@ impl<'lua> String<'lua> {
slice::from_raw_parts(data as *const u8, size + 1)
}
}
/// Converts the string to a generic C pointer.
///
/// There is no way to convert the pointer back to its original value.
///
/// Typically this function is used only for hashing and debug information.
#[inline]
pub fn to_pointer(&self) -> *const c_void {
let lua = self.0.lua;
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, self.0.index)) }
}
}
impl<'lua> AsRef<[u8]> for String<'lua> {
+15 -3
View File
@@ -1,10 +1,11 @@
use std::marker::PhantomData;
use std::os::raw::c_void;
#[cfg(feature = "serialize")]
use {
rustc_hash::FxHashSet,
serde::ser::{self, Serialize, SerializeMap, SerializeSeq, Serializer},
std::{cell::RefCell, os::raw::c_void, result::Result as StdResult},
std::{cell::RefCell, result::Result as StdResult},
};
use crate::error::{Error, Result};
@@ -382,6 +383,18 @@ impl<'lua> Table<'lua> {
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_getreadonly(refthr, self.0.index) != 0) }
}
/// Converts the table to a generic C pointer.
///
/// Different tables will give different pointers.
/// There is no way to convert the pointer back to its original value.
///
/// Typically this function is used only for hashing and debug information.
#[inline]
pub fn to_pointer(&self) -> *const c_void {
let lua = self.0.lua;
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, self.0.index)) }
}
/// Consume this table and return an iterator over the pairs of the table.
///
/// This works like the Lua `pairs` function, but does not invoke the `__pairs` metamethod.
@@ -699,8 +712,7 @@ impl<'lua> Serialize for Table<'lua> {
static VISITED: RefCell<FxHashSet<*const c_void>> = RefCell::new(FxHashSet::default());
}
let lua = self.0.lua;
let ptr = unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, self.0.index)) };
let ptr = self.to_pointer();
let res = VISITED.with(|visited| {
{
let mut visited = visited.borrow_mut();
+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.
///
+5
View File
@@ -622,3 +622,8 @@ lua_userdata_impl!(Arc<RwLock<T>>);
lua_userdata_impl!(Arc<parking_lot::Mutex<T>>);
#[cfg(feature = "parking_lot")]
lua_userdata_impl!(Arc<parking_lot::RwLock<T>>);
// A special proxy object for UserData
pub(crate) struct UserDataProxy<T>(pub(crate) PhantomData<T>);
lua_userdata_impl!(UserDataProxy<T>);
+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",
] {
+32 -8
View File
@@ -1,4 +1,5 @@
use std::iter::{self, FromIterator};
use std::ops::Index;
use std::os::raw::c_void;
use std::{ptr, slice, str, vec};
@@ -103,13 +104,14 @@ impl<'lua> Value<'lua> {
/// There is no way to convert the pointer back to its original value.
///
/// Typically this function is used only for hashing and debug information.
#[inline]
pub fn to_pointer(&self) -> *const c_void {
unsafe {
match self {
Value::LightUserData(ud) => ud.0,
Value::String(String(v))
| Value::Table(Table(v))
| Value::Function(Function(v))
Value::Table(t) => t.to_pointer(),
Value::String(s) => s.to_pointer(),
Value::Function(Function(v))
| Value::Thread(Thread(v))
| Value::UserData(AnyUserData(v)) => v
.lua
@@ -237,7 +239,24 @@ impl<'a, 'lua> IntoIterator for &'a MultiValue<'lua> {
#[inline]
fn into_iter(self) -> Self::IntoIter {
(&self.0).iter().rev()
self.0.iter().rev()
}
}
impl<'lua> Index<usize> for MultiValue<'lua> {
type Output = Value<'lua>;
#[inline]
fn index(&self, index: usize) -> &Self::Output {
if let Some(result) = self.get(index) {
result
} else {
panic!(
"index out of bounds: the len is {} but the index is {}",
self.len(),
index
)
}
}
}
@@ -255,19 +274,24 @@ 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);
}
#[inline]
pub(crate) fn push_front(&mut self, value: Value<'lua>) {
self.0.push(value);
pub fn pop_front(&mut self) -> Option<Value<'lua>> {
self.0.pop()
}
#[inline]
pub(crate) fn pop_front(&mut self) -> Option<Value<'lua>> {
self.0.pop()
pub fn push_front(&mut self, value: Value<'lua>) {
self.0.push(value);
}
#[inline]
+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(())
}
+10 -4
View File
@@ -15,8 +15,11 @@ fn test_require() -> Result<()> {
fs::write(
temp_dir.path().join("module.luau"),
r#"
counter = counter or 0
return counter + 1
counter = (counter or 0) + 1
return {
counter = counter,
error = function() error("test") end,
}
"#,
)?;
@@ -24,9 +27,12 @@ fn test_require() -> Result<()> {
lua.load(
r#"
local module = require("module")
assert(module == 1)
assert(module.counter == 1)
module = require("module")
assert(module == 1)
assert(module.counter == 1)
local ok, err = pcall(module.error)
assert(not ok and string.find(err, "module.luau") ~= nil)
"#,
)
.exec()
+41
View File
@@ -656,3 +656,44 @@ fn test_userdata_wrapped() -> Result<()> {
Ok(())
}
#[test]
fn test_userdata_proxy() -> Result<()> {
struct MyUserData(i64);
impl UserData for MyUserData {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_function_get("static_field", |_, _| Ok(123));
fields.add_field_method_get("n", |_, this| Ok(this.0));
}
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_function("new", |_, n| Ok(Self(n)));
methods.add_method("plus", |_, this, n: i64| Ok(this.0 + n));
}
}
let lua = Lua::new();
let globals = lua.globals();
globals.set("MyUserData", lua.create_proxy::<MyUserData>()?)?;
lua.load(
r#"
assert(MyUserData.static_field == 123)
local data = MyUserData.new(321)
assert(data.static_field == 123)
assert(data.n == 321)
assert(data:plus(1) == 322)
-- Error when accessing the proxy object fields and methods that require instance
local ok = pcall(function() return MyUserData.n end)
assert(not ok)
ok = pcall(function() return MyUserData:plus(1) end)
assert(not ok)
"#,
)
.exec()
}