Compare commits

..

18 Commits

Author SHA1 Message Date
Alex Orlenko 58953e5635 v0.11.0-beta.3 2025-06-23 23:23:07 +01:00
Alex Orlenko 6406de405d mlua-sys: v0.8.1 2025-06-23 23:22:53 +01:00
Alex Orlenko 727096dd3b Handle OOM error during luau_load (Luau >= 0.679) 2025-06-23 23:22:36 +01:00
Alex Orlenko 2445230759 Update CHANGELOG 2025-06-23 22:44:36 +01:00
Alex Orlenko 1cd2bdc808 Ignore test_async_require on windows 2025-06-23 16:37:46 +01:00
Alex Orlenko f8ed33a2aa Fix tests 2025-06-23 16:23:53 +01:00
Alex Orlenko 25955893e0 (Luau Require) Resolve Lua file path relative to the current directory
and unrelated to Rust source file location.
When a Lua file is required inside a Rust file (in a chunk), we should resolve the Lua file relative to the current directory,
instead of relative to the Rust chunk path.
The Rust file location is an internal information that does not exist when the compiled binary runs.
Fixes #605
2025-06-23 15:53:35 +01:00
Alex Orlenko 3f0c69b70b Fix logic to terminate futures on drop.
The underlying Lua thread must stay in yielded state rather than finished.
2025-06-17 15:25:21 +01:00
Alex Orlenko 9da98d42c7 Move ref_stack_pop into ExtraData method. 2025-06-16 22:25:26 +01:00
Alex Orlenko f539f60987 Fix Function::deep_clone() method (Luau).
The `lua_clonefunction` function can fail (and trigger GC) so we need to return Result instead of allowing longjmp
2025-06-16 21:41:29 +01:00
Alex Orlenko aa187e6663 Increase REF_STACK_RESERVE to 3 slots 2025-06-16 21:37:16 +01:00
Alex Orlenko ec10bf2a39 Revert 2e0e86dab2 (Update stack_value helper) 2025-06-16 21:33:47 +01:00
Alex Orlenko 3ea80b763d cargo fmt 2025-06-16 11:21:51 +01:00
Alex Orlenko 2e0e86dab2 Update stack_value helper.
It uses zero stack spaces in Luau, and ref thread for `WrappedError` check in ther Lua versions.
Close #597
2025-06-16 11:12:53 +01:00
Alex Orlenko 0ac7aebb27 Update Lua::sandbox doc 2025-06-13 15:51:31 +01:00
Alex Orlenko 634e5d4550 Reduce collectgarbage options in sandboxed mode
See https://luau.org/sandbox#library
2025-06-13 15:47:54 +01:00
Alex Orlenko 05d6c20520 One more mismatched-lifetime-syntaxes 2025-06-12 23:44:47 +01:00
Alex Orlenko 63e7cfd31b Satisfy mismatched-lifetime-syntaxes lint (nightly)
See rust-lang/rust#141787
2025-06-12 16:30:48 +01:00
24 changed files with 194 additions and 105 deletions
+7
View File
@@ -1,3 +1,10 @@
## v0.11.0-beta.3 (Jun 23, 2025)
- Luau in sandboxed mode has reduced options in `collectgarbage` function (to follow the official doc)
- `Function::deep_clone` now returns `Result<Function>` as this operation can trigger memory errors
- Luau "Require" resolves included Lua files relative to the current directory (#605)
- Fixed bug when finalizing `AsyncThread` on drop (`call_async` methods family)
## v0.11.0-beta.2 (Jun 12, 2025)
- Lua 5.4 updated to 5.4.8
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.11.0-beta.2" # remember to update mlua_derive
version = "0.11.0-beta.3" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
rust-version = "1.79.0"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua-sys"
version = "0.8.0"
version = "0.8.1"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
rust-version = "1.71"
edition = "2021"
+12 -5
View File
@@ -388,7 +388,7 @@ pub unsafe fn luaL_loadbufferenv(
}
}
if chunk_is_text {
let status = if chunk_is_text {
if env < 0 {
env -= 1;
}
@@ -397,14 +397,21 @@ pub unsafe fn luaL_loadbufferenv(
ptr::write(data_ud, data);
// By deferring the `free(data)` to the userdata destructor, we ensure that
// even if `luau_load` throws an error, the `data` is still released.
let ok = luau_load(L, name, data, size, env) == 0;
let status = luau_load(L, name, data, size, env);
lua_replace(L, -2); // replace data with the result
if !ok {
return LUA_ERRSYNTAX;
status
} else {
luau_load(L, name, data, size, env)
};
if status != 0 {
if lua_isstring(L, -1) != 0 && CStr::from_ptr(lua_tostring(L, -1)) == c"not enough memory" {
// A case for Luau >= 0.679
return LUA_ERRMEM;
}
} else if luau_load(L, name, data, size, env) != 0 {
return LUA_ERRSYNTAX;
}
LUA_OK
}
+14 -6
View File
@@ -492,16 +492,24 @@ impl Function {
/// This function returns shallow clone (same handle) for Rust/C functions.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn deep_clone(&self) -> Self {
pub fn deep_clone(&self) -> Result<Self> {
let lua = self.0.lua.lock();
let ref_thread = lua.ref_thread();
let state = lua.state();
unsafe {
if ffi::lua_iscfunction(ref_thread, self.0.index) != 0 {
return self.clone();
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
lua.push_ref(&self.0);
if ffi::lua_iscfunction(state, -1) != 0 {
return Ok(self.clone());
}
ffi::lua_clonefunction(ref_thread, self.0.index);
Function(lua.pop_ref_thread())
if lua.unlikely_memory_error() {
ffi::lua_clonefunction(state, -1);
} else {
protect_lua!(state, 1, 1, fn(state) ffi::lua_clonefunction(state, -1))?;
}
Ok(Function(lua.pop_ref()))
}
}
}
+2 -2
View File
@@ -86,7 +86,7 @@ impl<'a> Debug<'a> {
}
/// Corresponds to the `n` what mask.
pub fn names(&self) -> DebugNames {
pub fn names(&self) -> DebugNames<'_> {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
@@ -113,7 +113,7 @@ impl<'a> Debug<'a> {
}
/// Corresponds to the `S` what mask.
pub fn source(&self) -> DebugSource {
pub fn source(&self) -> DebugSource<'_> {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
+7 -6
View File
@@ -5,7 +5,7 @@ use std::ptr;
use crate::chunk::ChunkMode;
use crate::error::Result;
use crate::function::Function;
use crate::state::{callback_error_ext, Lua};
use crate::state::{callback_error_ext, ExtraData, Lua};
use crate::traits::{FromLuaMulti, IntoLua};
pub use require::{NavigateError, Require, TextRequirer};
@@ -45,16 +45,17 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
let option = ffi::luaL_optstring(state, 1, cstr!("collect"));
let option = CStr::from_ptr(option);
let arg = ffi::luaL_optinteger(state, 2, 0);
let is_sandboxed = (*ExtraData::get(state)).sandboxed;
match option.to_str() {
Ok("collect") => {
Ok("collect") if !is_sandboxed => {
ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0);
0
}
Ok("stop") => {
Ok("stop") if !is_sandboxed => {
ffi::lua_gc(state, ffi::LUA_GCSTOP, 0);
0
}
Ok("restart") => {
Ok("restart") if !is_sandboxed => {
ffi::lua_gc(state, ffi::LUA_GCRESTART, 0);
0
}
@@ -64,12 +65,12 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
ffi::lua_pushnumber(state, kbytes + kbytes_rem / 1024.0);
1
}
Ok("step") => {
Ok("step") if !is_sandboxed => {
let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg as _);
ffi::lua_pushboolean(state, res);
1
}
Ok("isrunning") => {
Ok("isrunning") if !is_sandboxed => {
let res = ffi::lua_gc(state, ffi::LUA_GCISRUNNING, 0);
ffi::lua_pushboolean(state, res);
1
+4 -2
View File
@@ -200,9 +200,11 @@ impl Require for TextRequirer {
let chunk_path = Self::normalize_path(chunk_name.as_ref());
if chunk_path.extension() == Some("rs".as_ref()) {
// Special case for Rust source files, reset to the current directory
let chunk_filename = chunk_path.file_name().unwrap();
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
self.abs_path = Self::normalize_path(&cwd.join(&chunk_path));
self.rel_path = chunk_path;
self.abs_path = Self::normalize_path(&cwd.join(chunk_filename));
self.rel_path = ([Component::CurDir, Component::Normal(chunk_filename)].into_iter()).collect();
self.module_path = PathBuf::new();
return Ok(());
+7 -6
View File
@@ -486,6 +486,7 @@ impl Lua {
/// - Set globals to read-only (and activates safeenv)
/// - Setup local environment table that performs writes locally and proxies reads to the global
/// environment.
/// - Allow only `count` mode in `collectgarbage` function.
///
/// # Examples
///
@@ -876,7 +877,7 @@ impl Lua {
/// not count in the stack).
///
/// [`Debug`]: crate::hook::Debug
pub fn inspect_stack(&self, level: usize) -> Option<Debug> {
pub fn inspect_stack(&self, level: usize) -> Option<Debug<'_>> {
let lua = self.lock();
unsafe {
let mut ar: ffi::lua_Debug = mem::zeroed();
@@ -1955,7 +1956,7 @@ impl Lua {
/// Panics if the data object of type `T` is currently mutably borrowed. Multiple immutable
/// reads can be taken out at the same time.
#[track_caller]
pub fn app_data_ref<T: 'static>(&self) -> Option<AppDataRef<T>> {
pub fn app_data_ref<T: 'static>(&self) -> Option<AppDataRef<'_, T>> {
let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() };
extra.app_data.borrow(Some(guard))
@@ -1963,7 +1964,7 @@ impl Lua {
/// Tries to get a reference to an application data object stored by [`Lua::set_app_data`] of
/// type `T`.
pub fn try_app_data_ref<T: 'static>(&self) -> StdResult<Option<AppDataRef<T>>, BorrowError> {
pub fn try_app_data_ref<T: 'static>(&self) -> StdResult<Option<AppDataRef<'_, T>>, BorrowError> {
let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() };
extra.app_data.try_borrow(Some(guard))
@@ -1976,7 +1977,7 @@ impl Lua {
///
/// Panics if the data object of type `T` is currently borrowed.
#[track_caller]
pub fn app_data_mut<T: 'static>(&self) -> Option<AppDataRefMut<T>> {
pub fn app_data_mut<T: 'static>(&self) -> Option<AppDataRefMut<'_, T>> {
let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() };
extra.app_data.borrow_mut(Some(guard))
@@ -1984,7 +1985,7 @@ impl Lua {
/// Tries to get a mutable reference to an application data object stored by
/// [`Lua::set_app_data`] of type `T`.
pub fn try_app_data_mut<T: 'static>(&self) -> StdResult<Option<AppDataRefMut<T>>, BorrowMutError> {
pub fn try_app_data_mut<T: 'static>(&self) -> StdResult<Option<AppDataRefMut<'_, T>>, BorrowMutError> {
let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() };
extra.app_data.try_borrow_mut(Some(guard))
@@ -2058,7 +2059,7 @@ impl Lua {
}
#[inline(always)]
pub(crate) fn lock(&self) -> ReentrantMutexGuard<RawLua> {
pub(crate) fn lock(&self) -> ReentrantMutexGuard<'_, RawLua> {
let rawlua = self.raw.lock();
#[cfg(feature = "luau")]
if unsafe { (*rawlua.extra.get()).running_gc } {
+31 -3
View File
@@ -28,7 +28,7 @@ use super::{Lua, WeakLua};
static EXTRA_REGISTRY_KEY: u8 = 0;
const WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY: usize = 64;
const REF_STACK_RESERVE: c_int = 2;
const REF_STACK_RESERVE: c_int = 3;
/// Data associated with the Lua state.
pub(crate) struct ExtraData {
@@ -89,7 +89,7 @@ pub(crate) struct ExtraData {
#[cfg(feature = "luau")]
pub(crate) running_gc: bool,
#[cfg(feature = "luau")]
pub(super) sandboxed: bool,
pub(crate) sandboxed: bool,
#[cfg(feature = "luau")]
pub(super) compiler: Option<Compiler>,
#[cfg(feature = "luau-jit")]
@@ -212,7 +212,7 @@ impl ExtraData {
self.weak.write(WeakLua(XRc::downgrade(raw)));
}
pub(super) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
#[cfg(feature = "luau")]
if cfg!(not(feature = "module")) {
// In the main app we can use `lua_callbacks` to access ExtraData
@@ -259,4 +259,32 @@ impl ExtraData {
pub(super) unsafe fn weak(&self) -> &WeakLua {
self.weak.assume_init_ref()
}
/// Pops a reference from top of the auxiliary stack and move it to a first free slot.
pub(super) unsafe fn ref_stack_pop(&mut self) -> c_int {
if let Some(free) = self.ref_free.pop() {
ffi::lua_replace(self.ref_thread, free);
return free;
}
// Try to grow max stack size
if self.ref_stack_top >= self.ref_stack_size {
let mut inc = self.ref_stack_size; // Try to double stack size
while inc > 0 && ffi::lua_checkstack(self.ref_thread, inc) == 0 {
inc /= 2;
}
if inc == 0 {
// Pop item on top of the stack to avoid stack leaking and successfully run destructors
// during unwinding.
ffi::lua_pop(self.ref_thread, 1);
let top = self.ref_stack_top;
// It is a user error to create too many references to exhaust the Lua max stack size
// for the ref thread.
panic!("cannot create a Lua reference, out of auxiliary stack space (used {top} slots)");
}
self.ref_stack_size += inc;
}
self.ref_stack_top += 1;
self.ref_stack_top
}
}
+13 -9
View File
@@ -11,7 +11,7 @@ use crate::chunk::ChunkMode;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::memory::{MemoryState, ALLOCATOR};
use crate::state::util::{callback_error_ext, ref_stack_pop};
use crate::state::util::callback_error_ext;
use crate::stdlib::StdLib;
use crate::string::String;
use crate::table::Table;
@@ -316,7 +316,7 @@ impl RawLua {
/// Private version of [`Lua::app_data_ref`]
#[track_caller]
#[inline]
pub(crate) fn priv_app_data_ref<T: 'static>(&self) -> Option<AppDataRef<T>> {
pub(crate) fn priv_app_data_ref<T: 'static>(&self) -> Option<AppDataRef<'_, T>> {
let extra = unsafe { &*self.extra.get() };
extra.app_data_priv.borrow(None)
}
@@ -324,7 +324,7 @@ impl RawLua {
/// Private version of [`Lua::app_data_mut`]
#[track_caller]
#[inline]
pub(crate) fn priv_app_data_mut<T: 'static>(&self) -> Option<AppDataRefMut<T>> {
pub(crate) fn priv_app_data_mut<T: 'static>(&self) -> Option<AppDataRefMut<'_, T>> {
let extra = unsafe { &*self.extra.get() };
extra.app_data_priv.borrow_mut(None)
}
@@ -694,7 +694,7 @@ impl RawLua {
/// Pops a value from the Lua stack.
///
/// Uses 2 stack spaces, does not call `checkstack`.
/// Uses up to 1 stack spaces, does not call `checkstack`.
pub(crate) unsafe fn pop_value(&self) -> Value {
let value = self.stack_value(-1, None);
ffi::lua_pop(self.state(), 1);
@@ -703,7 +703,7 @@ impl RawLua {
/// Returns value at given stack index without popping it.
///
/// Uses 2 stack spaces, does not call checkstack.
/// Uses up to 1 stack spaces, does not call `checkstack`.
pub(crate) unsafe fn stack_value(&self, idx: c_int, type_hint: Option<c_int>) -> Value {
let state = self.state();
match type_hint.unwrap_or_else(|| ffi::lua_type(state, idx)) {
@@ -816,21 +816,21 @@ impl RawLua {
#[inline]
pub(crate) unsafe fn pop_ref(&self) -> ValueRef {
ffi::lua_xmove(self.state(), self.ref_thread(), 1);
let index = ref_stack_pop(self.extra.get());
let index = (*self.extra.get()).ref_stack_pop();
ValueRef::new(self, index)
}
// Same as `pop_ref` but assumes the value is already on the reference thread
#[inline]
pub(crate) unsafe fn pop_ref_thread(&self) -> ValueRef {
let index = ref_stack_pop(self.extra.get());
let index = (*self.extra.get()).ref_stack_pop();
ValueRef::new(self, index)
}
#[inline]
pub(crate) unsafe fn clone_ref(&self, vref: &ValueRef) -> ValueRef {
ffi::lua_pushvalue(self.ref_thread(), vref.index);
let index = ref_stack_pop(self.extra.get());
let index = (*self.extra.get()).ref_stack_pop();
ValueRef::new(self, index)
}
@@ -1262,7 +1262,7 @@ impl RawLua {
if nargs == 1 && ffi::lua_tolightuserdata(state, -1) == Lua::poll_terminate().0 {
// Destroy the future and terminate the Lua thread
(*upvalue).data.take();
ffi::lua_pushinteger(state, 0);
ffi::lua_pushinteger(state, -1);
return Ok(1);
}
@@ -1347,6 +1347,10 @@ impl RawLua {
return res
elseif nres == 2 then
return res, res2
elseif nres < 0 then
-- Negative `nres` means that the future is terminated
-- It must stay yielded and never be resumed again
yield()
else
return unpack(res, nres)
end
+1 -29
View File
@@ -89,7 +89,7 @@ where
PreallocatedFailure::New(_) => {
ffi::lua_rotate(state, 1, -1);
ffi::lua_xmove(state, ref_thread, 1);
let index = ref_stack_pop(extra);
let index = (*extra).ref_stack_pop();
(*extra).wrapped_failure_pool.push(index);
(*extra).wrapped_failure_top += 1;
}
@@ -150,31 +150,3 @@ where
}
}
}
pub(super) unsafe fn ref_stack_pop(extra: *mut ExtraData) -> c_int {
let extra = &mut *extra;
if let Some(free) = extra.ref_free.pop() {
ffi::lua_replace(extra.ref_thread, free);
return free;
}
// Try to grow max stack size
if extra.ref_stack_top >= extra.ref_stack_size {
let mut inc = extra.ref_stack_size; // Try to double stack size
while inc > 0 && ffi::lua_checkstack(extra.ref_thread, inc) == 0 {
inc /= 2;
}
if inc == 0 {
// Pop item on top of the stack to avoid stack leaking and successfully run destructors
// during unwinding.
ffi::lua_pop(extra.ref_thread, 1);
let top = extra.ref_stack_top;
// It is a user error to create enough references to exhaust the Lua max stack size for
// the ref thread.
panic!("cannot create a Lua reference, out of auxiliary stack space (used {top} slots)");
}
extra.ref_stack_size += inc;
}
extra.ref_stack_top += 1;
extra.ref_stack_top
}
+3 -3
View File
@@ -43,7 +43,7 @@ impl String {
/// # }
/// ```
#[inline]
pub fn to_str(&self) -> Result<BorrowedStr> {
pub fn to_str(&self) -> Result<BorrowedStr<'_>> {
BorrowedStr::try_from(self)
}
@@ -102,12 +102,12 @@ impl String {
/// # }
/// ```
#[inline]
pub fn as_bytes(&self) -> BorrowedBytes {
pub fn as_bytes(&self) -> BorrowedBytes<'_> {
BorrowedBytes::from(self)
}
/// Get the bytes that make up this string, including the trailing nul byte.
pub fn as_bytes_with_nul(&self) -> BorrowedBytes {
pub fn as_bytes_with_nul(&self) -> BorrowedBytes<'_> {
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self);
// Include the trailing nul byte (it's always present but excluded by default)
let buf = unsafe { slice::from_raw_parts((*buf).as_ptr(), (*buf).len() + 1) };
+2 -2
View File
@@ -613,7 +613,7 @@ impl Table {
/// ```
///
/// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next
pub fn pairs<K: FromLua, V: FromLua>(&self) -> TablePairs<K, V> {
pub fn pairs<K: FromLua, V: FromLua>(&self) -> TablePairs<'_, K, V> {
TablePairs {
guard: self.0.lua.lock(),
table: self,
@@ -678,7 +678,7 @@ impl Table {
/// # Ok(())
/// # }
/// ```
pub fn sequence_values<V: FromLua>(&self) -> TableSequence<V> {
pub fn sequence_values<V: FromLua>(&self) -> TableSequence<'_, V> {
TableSequence {
guard: self.0.lua.lock(),
table: self,
+1
View File
@@ -529,6 +529,7 @@ impl<R> Drop for AsyncThread<R> {
// The thread is dropped while yielded, resume it with the "terminate" signal
ffi::lua_pushlightuserdata(self.thread.1, crate::Lua::poll_terminate().0);
if let Ok((new_status, _)) = self.thread.resume_inner(&lua, 1) {
// `new_status` should always be `ThreadStatusInner::Yielded(0)`
status = new_status;
}
}
+4 -4
View File
@@ -43,7 +43,7 @@ impl AppData {
#[inline]
#[track_caller]
pub(crate) fn borrow<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRef<T>> {
pub(crate) fn borrow<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRef<'_, T>> {
match self.try_borrow(guard) {
Ok(data) => data,
Err(err) => panic!("already mutably borrowed: {err:?}"),
@@ -53,7 +53,7 @@ impl AppData {
pub(crate) fn try_borrow<T: 'static>(
&self,
guard: Option<LuaGuard>,
) -> Result<Option<AppDataRef<T>>, BorrowError> {
) -> Result<Option<AppDataRef<'_, T>>, BorrowError> {
let data = unsafe { &*self.container.get() }
.get(&TypeId::of::<T>())
.map(|c| c.try_borrow())
@@ -74,7 +74,7 @@ impl AppData {
#[inline]
#[track_caller]
pub(crate) fn borrow_mut<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRefMut<T>> {
pub(crate) fn borrow_mut<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRefMut<'_, T>> {
match self.try_borrow_mut(guard) {
Ok(data) => data,
Err(err) => panic!("already borrowed: {err:?}"),
@@ -84,7 +84,7 @@ impl AppData {
pub(crate) fn try_borrow_mut<T: 'static>(
&self,
guard: Option<LuaGuard>,
) -> Result<Option<AppDataRefMut<T>>, BorrowMutError> {
) -> Result<Option<AppDataRefMut<'_, T>>, BorrowMutError> {
let data = unsafe { &*self.container.get() }
.get(&TypeId::of::<T>())
.map(|c| c.try_borrow_mut())
+1 -1
View File
@@ -31,7 +31,7 @@ mod inner {
}
#[inline(always)]
pub(crate) fn lock(&self) -> ReentrantMutexGuard<T> {
pub(crate) fn lock(&self) -> ReentrantMutexGuard<'_, T> {
ReentrantMutexGuard(&self.0)
}
+1 -1
View File
@@ -1008,7 +1008,7 @@ impl UserDataMetatable {
/// The pairs are wrapped in a [`Result`], since they are lazily converted to `V` type.
///
/// [`Result`]: crate::Result
pub fn pairs<V: FromLua>(&self) -> UserDataMetatablePairs<V> {
pub fn pairs<V: FromLua>(&self) -> UserDataMetatablePairs<'_, V> {
UserDataMetatablePairs(self.0.pairs())
}
}
+1 -1
View File
@@ -73,7 +73,7 @@ pub(crate) unsafe fn init_internal_metatable<T: TypeKey>(
Ok(())
}
// Uses 2 stack spaces, does not call checkstack
// Uses up to 1 stack space, does not call `checkstack`
pub(crate) unsafe fn get_internal_userdata<T: TypeKey>(
state: *mut ffi::lua_State,
index: c_int,
+2 -2
View File
@@ -357,7 +357,7 @@ impl Value {
/// If the value is a Lua [`String`], try to convert it to [`BorrowedStr`] or return `None`
/// otherwise.
#[inline]
pub fn as_str(&self) -> Option<BorrowedStr> {
pub fn as_str(&self) -> Option<BorrowedStr<'_>> {
self.as_string().and_then(|s| s.to_str().ok())
}
@@ -484,7 +484,7 @@ impl Value {
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
#[doc(hidden)]
pub fn to_serializable(&self) -> SerializableValue {
pub fn to_serializable(&self) -> SerializableValue<'_> {
SerializableValue::new(self, Default::default(), None)
}
+30
View File
@@ -610,6 +610,36 @@ async fn test_async_task() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn test_async_task_abort() -> Result<()> {
let lua = Lua::new();
let sleep = lua.create_async_function(move |_lua, n: u64| async move {
sleep_ms(n).await;
Ok(())
})?;
lua.globals().set("sleep", sleep)?;
let local = tokio::task::LocalSet::new();
local
.run_until(async {
let lua2 = lua.clone();
let jh = tokio::task::spawn_local(async move {
lua2.load("sleep(200) result = 'done'")
.exec_async()
.await
.unwrap();
});
sleep_ms(100).await; // Wait for the task to start
jh.abort();
})
.await;
local.await;
assert_eq!(lua.globals().get::<Value>("result")?, Value::Nil);
Ok(())
}
#[tokio::test]
#[cfg(not(feature = "luau"))]
async fn test_async_hook() -> Result<()> {
+2 -2
View File
@@ -306,7 +306,7 @@ fn test_function_deep_clone() -> Result<()> {
lua.globals().set("a", 1)?;
let func1 = lua.load("a += 1; return a").into_function()?;
let func2 = func1.deep_clone();
let func2 = func1.deep_clone()?;
assert_ne!(func1.to_pointer(), func2.to_pointer());
assert_eq!(func1.call::<i32>(())?, 2);
@@ -314,7 +314,7 @@ fn test_function_deep_clone() -> Result<()> {
// Check that for Rust functions deep_clone is just a clone
let rust_func = lua.create_function(|_, ()| Ok(42))?;
let rust_func2 = rust_func.deep_clone();
let rust_func2 = rust_func.deep_clone()?;
assert_eq!(rust_func.to_pointer(), rust_func2.to_pointer());
Ok(())
+13
View File
@@ -196,6 +196,14 @@ fn test_sandbox() -> Result<()> {
co.sandbox()?;
assert_eq!(co.resume::<Option<i32>>(())?, Some(123));
// collectgarbage should be restricted in sandboxed mode
let collectgarbage = lua.globals().get::<Function>("collectgarbage")?;
for arg in ["collect", "stop", "restart", "step", "isrunning"] {
let err = collectgarbage.call::<()>(arg).err().unwrap().to_string();
assert!(err.contains("collectgarbage called with invalid option"));
}
assert!(collectgarbage.call::<u64>("count").unwrap() > 0);
lua.sandbox(false)?;
// Previously set variable `global` should be cleared now
@@ -205,6 +213,11 @@ fn test_sandbox() -> Result<()> {
let table = lua.globals().get::<Table>("table")?;
table.set("test", "test")?;
// collectgarbage should work now
for arg in ["collect", "stop", "restart", "count", "step", "isrunning"] {
collectgarbage.call::<()>(arg).unwrap();
}
Ok(())
}
+34 -19
View File
@@ -105,63 +105,69 @@ fn test_require_without_config() {
let lua = Lua::new();
// RequireSimpleRelativePath
let res = run_require(&lua, "./require/without_config/dependency").unwrap();
let res = run_require(&lua, "./tests/luau/require/without_config/dependency").unwrap();
assert_eq!("result from dependency", get_str(&res, 1));
// RequireSimpleRelativePathWithinPcall
let res = run_require_pcall(&lua, "./require/without_config/dependency").unwrap();
let res = run_require_pcall(&lua, "./tests/luau/require/without_config/dependency").unwrap();
assert!(res[0].as_boolean().unwrap());
assert_eq!("result from dependency", get_str(&res[1], 1));
// RequireRelativeToRequiringFile
let res = run_require(&lua, "./require/without_config/module").unwrap();
let res = run_require(&lua, "./tests/luau/require/without_config/module").unwrap();
assert_eq!("result from dependency", get_str(&res, 1));
assert_eq!("required into module", get_str(&res, 2));
// RequireLua
let res = run_require(&lua, "./require/without_config/lua_dependency").unwrap();
let res = run_require(&lua, "./tests/luau/require/without_config/lua_dependency").unwrap();
assert_eq!("result from lua_dependency", get_str(&res, 1));
// RequireInitLuau
let res = run_require(&lua, "./require/without_config/luau").unwrap();
let res = run_require(&lua, "./tests/luau/require/without_config/luau").unwrap();
assert_eq!("result from init.luau", get_str(&res, 1));
// RequireInitLua
let res = run_require(&lua, "./require/without_config/lua").unwrap();
let res = run_require(&lua, "./tests/luau/require/without_config/lua").unwrap();
assert_eq!("result from init.lua", get_str(&res, 1));
// RequireSubmoduleUsingSelfIndirectly
let res = run_require(&lua, "./require/without_config/nested_module_requirer").unwrap();
let res = run_require(&lua, "./tests/luau/require/without_config/nested_module_requirer").unwrap();
assert_eq!("result from submodule", get_str(&res, 1));
// RequireSubmoduleUsingSelfDirectly
let res = run_require(&lua, "./require/without_config/nested").unwrap();
let res = run_require(&lua, "./tests/luau/require/without_config/nested").unwrap();
assert_eq!("result from submodule", get_str(&res, 1));
// CannotRequireInitLuauDirectly
let res = run_require(&lua, "./require/without_config/nested/init");
let res = run_require(&lua, "./tests/luau/require/without_config/nested/init");
assert!(res.is_err());
assert!((res.unwrap_err().to_string()).contains("could not resolve child component \"init\""));
// RequireNestedInits
let res = run_require(&lua, "./require/without_config/nested_inits_requirer").unwrap();
let res = run_require(&lua, "./tests/luau/require/without_config/nested_inits_requirer").unwrap();
assert_eq!("result from nested_inits/init", get_str(&res, 1));
assert_eq!("required into module", get_str(&res, 2));
// RequireWithFileAmbiguity
let res = run_require(&lua, "./require/without_config/ambiguous_file_requirer");
let res = run_require(
&lua,
"./tests/luau/require/without_config/ambiguous_file_requirer",
);
assert!(res.is_err());
assert!((res.unwrap_err().to_string())
.contains("could not resolve child component \"dependency\" (ambiguous)"));
// RequireWithDirectoryAmbiguity
let res = run_require(&lua, "./require/without_config/ambiguous_directory_requirer");
let res = run_require(
&lua,
"./tests/luau/require/without_config/ambiguous_directory_requirer",
);
assert!(res.is_err());
assert!((res.unwrap_err().to_string())
.contains("could not resolve child component \"dependency\" (ambiguous)"));
// CheckCachedResult
let res = run_require(&lua, "./require/without_config/validate_cache").unwrap();
let res = run_require(&lua, "./tests/luau/require/without_config/validate_cache").unwrap();
assert!(res.is_table());
}
@@ -170,15 +176,19 @@ fn test_require_with_config() {
let lua = Lua::new();
// RequirePathWithAlias
let res = run_require(&lua, "./require/with_config/src/alias_requirer").unwrap();
let res = run_require(&lua, "./tests/luau/require/with_config/src/alias_requirer").unwrap();
assert_eq!("result from dependency", get_str(&res, 1));
// RequirePathWithParentAlias
let res = run_require(&lua, "./require/with_config/src/parent_alias_requirer").unwrap();
let res = run_require(&lua, "./tests/luau/require/with_config/src/parent_alias_requirer").unwrap();
assert_eq!("result from other_dependency", get_str(&res, 1));
// RequirePathWithAliasPointingToDirectory
let res = run_require(&lua, "./require/with_config/src/directory_alias_requirer").unwrap();
let res = run_require(
&lua,
"./tests/luau/require/with_config/src/directory_alias_requirer",
)
.unwrap();
assert_eq!("result from subdirectory_dependency", get_str(&res, 1));
// RequireAliasThatDoesNotExist
@@ -192,7 +202,7 @@ fn test_require_with_config() {
assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias"));
}
#[cfg(feature = "async")]
#[cfg(all(feature = "async", not(windows)))]
#[tokio::test]
async fn test_async_require() -> Result<()> {
let lua = Lua::new();
@@ -215,14 +225,19 @@ async fn test_async_require() -> Result<()> {
Ok(())
})?,
)?;
lua.globals().set("tmp_dir", temp_dir.path().to_str().unwrap())?;
lua.globals().set(
"curr_dir_components",
std::env::current_dir().unwrap().components().count(),
)?;
lua.load(
r#"
local result = require("./async_chunk")
local path_to_root = string.rep("/..", curr_dir_components - 1)
local result = require(`.{path_to_root}{tmp_dir}/async_chunk`)
assert(result == "result_after_async_sleep")
"#,
)
.set_name(format!("@{}", temp_dir.path().join("require.rs").display()))
.exec_async()
.await
}