mirror of
https://github.com/mlua-rs/mlua
synced 2026-06-08 16:05:43 +00:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 12472de1d2 | |||
| a68708c12e | |||
| 4c0474d573 | |||
| 60e859f643 | |||
| 9ed0d90746 | |||
| 514ec24252 | |||
| 244e6c9c12 | |||
| 4749e3a22a | |||
| cf153f38de | |||
| c0a0983025 | |||
| 69ff0c5509 | |||
| bf79d6c212 | |||
| 0b9a85e183 | |||
| 59974d7bde | |||
| 61e846326c | |||
| 3547985bb0 | |||
| 4c92580201 | |||
| e3f34f319c | |||
| b16f3895a0 | |||
| a4c919231c | |||
| e4d6e92287 | |||
| 642201a7e0 | |||
| c36808b251 | |||
| 2022de2156 | |||
| 66e01548ce | |||
| 93b505cff9 | |||
| 44f5688c32 | |||
| 2bee5ed33a | |||
| 2d775695ef | |||
| 34476ebf53 | |||
| 5043447f23 | |||
| b879abc418 | |||
| a1e39a8620 | |||
| f5021daaea | |||
| ec0fb7614e | |||
| 04c0763146 | |||
| 7d0aa7c01b | |||
| 2a3980ef62 | |||
| adb979761e | |||
| 37d722d71c | |||
| 6e3af593f6 | |||
| 54c14cc5b8 | |||
| 7f0de5790a | |||
| 6488477bc4 | |||
| 53b7b5b70b |
+34
-11
@@ -18,7 +18,7 @@ jobs:
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
matrix:
|
||||
lua: [lua54, lua53, lua52, lua51, luajit]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
matrix:
|
||||
lua: [lua54, lua53, lua52, lua51, luajit]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
matrix:
|
||||
lua: [lua54, lua53, lua52, lua51]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
@@ -145,7 +145,7 @@ jobs:
|
||||
- os: ubuntu-22.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
@@ -166,14 +166,14 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-22.04, macos-latest]
|
||||
rust: [stable]
|
||||
lua: [lua54, lua53, lua52, lua51, luajit]
|
||||
lua: [lua54, lua53, lua52, lua51, luajit, luau]
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- os: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
@@ -197,7 +197,7 @@ jobs:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: msys2/setup-msys2@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust & Lua
|
||||
run: |
|
||||
pacman -S --noconfirm mingw-w64-x86_64-rust mingw-w64-x86_64-lua mingw-w64-x86_64-luajit mingw-w64-x86_64-pkg-config
|
||||
@@ -206,11 +206,34 @@ jobs:
|
||||
(cd tests/module && cargo build --release --features "${{ matrix.lua }}")
|
||||
(cd tests/module/loader && cargo test --release --features "${{ matrix.lua }}")
|
||||
|
||||
test_wasm32_emscripten:
|
||||
name: Test on wasm32-unknown-emscripten
|
||||
runs-on: ubuntu-22.04
|
||||
needs: build
|
||||
strategy:
|
||||
matrix:
|
||||
lua: [lua54, lua53, lua52, lua51, luau]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-emscripten
|
||||
- name: Install Emscripten
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends emscripten
|
||||
- name: Run ${{ matrix.lua }} tests
|
||||
run: |
|
||||
cargo test --tests --features "${{ matrix.lua }},vendored"
|
||||
cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
|
||||
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,parking_lot,unstable"
|
||||
|
||||
rustfmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
@@ -224,7 +247,7 @@ jobs:
|
||||
matrix:
|
||||
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
## v0.9.3
|
||||
|
||||
- WebAssembly support (`wasm32-unknown-emscripten` target)
|
||||
- Performance improvements (faster Lua function calls for lua51/jit/luau)
|
||||
|
||||
## v0.9.2
|
||||
|
||||
- Added binary modules support to Luau
|
||||
- Added Luau package module (uses `StdLib::PACKAGE`) with loaders (follows lua5.1 interface)
|
||||
- Added support of Luau 0.601+ buffer type (represented as userdata in Rust)
|
||||
- LuaJIT `cdata` type is also represented as userdata in Rust (instead of panic)
|
||||
- Vendored LuaJIT switched to rolling vanilla (from openresty)
|
||||
- Added `Table::for_each` method for fast table pairs traversal (faster than `pairs`)
|
||||
- Performance improvements around table traversal (and faster serialization)
|
||||
- Bug fixes and improvements
|
||||
|
||||
## v0.9.1
|
||||
|
||||
- impl Default for Lua
|
||||
|
||||
+18
-7
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mlua"
|
||||
version = "0.9.1" # remember to update mlua_derive
|
||||
version = "0.9.3" # remember to update mlua_derive
|
||||
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
|
||||
rust-version = "1.71"
|
||||
edition = "2021"
|
||||
@@ -32,7 +32,7 @@ lua52 = ["ffi/lua52"]
|
||||
lua51 = ["ffi/lua51"]
|
||||
luajit = ["ffi/luajit"]
|
||||
luajit52 = ["luajit", "ffi/luajit52"]
|
||||
luau = ["ffi/luau"]
|
||||
luau = ["ffi/luau", "libloading"]
|
||||
luau-jit = ["luau", "ffi/luau-codegen"]
|
||||
luau-vector4 = ["luau", "ffi/luau-vector4"]
|
||||
vendored = ["ffi/vendored"]
|
||||
@@ -51,31 +51,42 @@ num-traits = { version = "0.2.14" }
|
||||
rustc-hash = "1.0"
|
||||
futures-util = { version = "0.3", optional = true, default-features = false, features = ["std"] }
|
||||
serde = { version = "1.0", optional = true }
|
||||
erased-serde = { version = "0.3", optional = true }
|
||||
erased-serde = { version = "0.4", optional = true }
|
||||
serde-value = { version = "0.7", optional = true }
|
||||
parking_lot = { version = "0.12", optional = true }
|
||||
|
||||
ffi = { package = "mlua-sys", version = "0.3.2", path = "mlua-sys" }
|
||||
ffi = { package = "mlua-sys", version = "0.5.0", path = "mlua-sys" }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libloading = { version = "0.8", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rustyline = "12.0"
|
||||
criterion = { version = "0.5", features = ["async_tokio"] }
|
||||
trybuild = "1.0"
|
||||
futures = "0.3.5"
|
||||
hyper = { version = "0.14", features = ["client", "server"] }
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
tokio = { version = "1.0", features = ["macros", "rt", "time"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
maplit = "1.0"
|
||||
tempfile = "3"
|
||||
static_assertions = "1.0"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
|
||||
criterion = { version = "0.5", features = ["async_tokio"] }
|
||||
rustyline = "13.0"
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
|
||||
[[bench]]
|
||||
name = "benchmark"
|
||||
harness = false
|
||||
required-features = ["async"]
|
||||
|
||||
[[bench]]
|
||||
name = "serde"
|
||||
harness = false
|
||||
required-features = ["serialize"]
|
||||
|
||||
[[example]]
|
||||
name = "async_http_client"
|
||||
required-features = ["async", "macros"]
|
||||
|
||||
@@ -28,6 +28,8 @@ Started as `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT
|
||||
|
||||
`mlua` tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platform and cross-compilation to `aarch64` (other targets are also supported).
|
||||
|
||||
WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua versions excluding JIT.
|
||||
|
||||
[GitHub Actions]: https://github.com/khvzak/mlua/actions
|
||||
[Roblox Luau]: https://luau-lang.org
|
||||
|
||||
|
||||
@@ -84,6 +84,52 @@ fn table_get_set(c: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
fn table_traversal_pairs(c: &mut Criterion) {
|
||||
let lua = Lua::new();
|
||||
|
||||
c.bench_function("table traversal [pairs]", |b| {
|
||||
b.iter_batched(
|
||||
|| lua.globals(),
|
||||
|globals| {
|
||||
for kv in globals.pairs::<String, LuaValue>() {
|
||||
let (_k, _v) = kv.unwrap();
|
||||
}
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn table_traversal_for_each(c: &mut Criterion) {
|
||||
let lua = Lua::new();
|
||||
|
||||
c.bench_function("table traversal [for_each]", |b| {
|
||||
b.iter_batched(
|
||||
|| lua.globals(),
|
||||
|globals| globals.for_each::<String, LuaValue>(|_k, _v| Ok(())),
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn table_traversal_sequence(c: &mut Criterion) {
|
||||
let lua = Lua::new();
|
||||
|
||||
let table = lua.create_sequence_from(1..1000).unwrap();
|
||||
|
||||
c.bench_function("table traversal [sequence]", |b| {
|
||||
b.iter_batched(
|
||||
|| table.clone(),
|
||||
|table| {
|
||||
for v in table.sequence_values::<i32>() {
|
||||
let _i = v.unwrap();
|
||||
}
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn create_function(c: &mut Criterion) {
|
||||
let lua = Lua::new();
|
||||
|
||||
@@ -331,6 +377,9 @@ criterion_group! {
|
||||
create_array,
|
||||
create_string_table,
|
||||
table_get_set,
|
||||
table_traversal_pairs,
|
||||
table_traversal_for_each,
|
||||
table_traversal_sequence,
|
||||
create_function,
|
||||
call_lua_function,
|
||||
call_sum_callback,
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
||||
use std::time::Duration;
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
fn collect_gc_twice(lua: &Lua) {
|
||||
lua.gc_collect().unwrap();
|
||||
lua.gc_collect().unwrap();
|
||||
}
|
||||
|
||||
fn serialize_json(c: &mut Criterion) {
|
||||
let lua = Lua::new();
|
||||
|
||||
lua.globals()
|
||||
.set(
|
||||
"encode",
|
||||
LuaFunction::wrap(|_, t: LuaValue| Ok(serde_json::to_string(&t).unwrap())),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
c.bench_function("serialize table to json [10]", |b| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
collect_gc_twice(&lua);
|
||||
lua.load(
|
||||
r#"
|
||||
local encode = encode
|
||||
return function()
|
||||
for i = 1, 10 do
|
||||
encode({
|
||||
name = "Clark Kent",
|
||||
nickname = "Superman",
|
||||
address = {
|
||||
city = "Metropolis",
|
||||
},
|
||||
age = 32,
|
||||
superman = true,
|
||||
})
|
||||
end
|
||||
end
|
||||
"#,
|
||||
)
|
||||
.eval::<LuaFunction>()
|
||||
.unwrap()
|
||||
},
|
||||
|func| {
|
||||
func.call::<_, ()>(()).unwrap();
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group! {
|
||||
name = benches;
|
||||
config = Criterion::default()
|
||||
.sample_size(300)
|
||||
.measurement_time(Duration::from_secs(10))
|
||||
.noise_threshold(0.02);
|
||||
targets =
|
||||
serialize_json,
|
||||
}
|
||||
|
||||
criterion_main!(benches);
|
||||
@@ -19,7 +19,7 @@ impl UserData for BodyReader {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use mlua::{chunk, ExternalResult, Lua, LuaSerdeExt, Result};
|
||||
|
||||
#[tokio::main]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mlua-sys"
|
||||
version = "0.3.2"
|
||||
version = "0.5.0"
|
||||
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
|
||||
rust-version = "1.71"
|
||||
edition = "2021"
|
||||
@@ -38,6 +38,6 @@ module = []
|
||||
cc = "1.0"
|
||||
cfg-if = "1.0"
|
||||
pkg-config = "0.3.17"
|
||||
lua-src = { version = ">= 546.0.0, < 546.1.0", optional = true }
|
||||
luajit-src = { version = ">= 210.4.6, < 210.5.0", optional = true }
|
||||
luau0-src = { version = "0.7.0", optional = true }
|
||||
lua-src = { version = ">= 546.0.2, < 546.1.0", optional = true }
|
||||
luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true }
|
||||
luau0-src = { version = "0.7.11", optional = true }
|
||||
|
||||
@@ -4,6 +4,12 @@ use std::env;
|
||||
use std::ops::Bound;
|
||||
|
||||
pub fn probe_lua() {
|
||||
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||
|
||||
if target_arch == "wasm32" && cfg!(not(feature = "vendored")) {
|
||||
panic!("Please enable `vendored` feature to build for wasm32");
|
||||
}
|
||||
|
||||
let lib_dir = env::var("LUA_LIB").unwrap_or_default();
|
||||
let lua_lib = env::var("LUA_LIB_NAME").unwrap_or_default();
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ cfg_if::cfg_if! {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(all(feature = "luau", feature = "module"))]
|
||||
compile_error!("Luau does not support `module` mode");
|
||||
#[cfg(all(feature = "luau", feature = "module", windows))]
|
||||
compile_error!("Luau does not support `module` mode on Windows");
|
||||
|
||||
#[cfg(all(feature = "module", feature = "vendored"))]
|
||||
compile_error!("`vendored` and `module` features are mutually exclusive");
|
||||
|
||||
@@ -65,6 +65,7 @@ pub const SYS_MIN_ALIGN: usize = 8;
|
||||
target_arch = "sparc64",
|
||||
target_arch = "riscv64",
|
||||
target_arch = "wasm64",
|
||||
target_arch = "loongarch64",
|
||||
))]
|
||||
#[doc(hidden)]
|
||||
pub const SYS_MIN_ALIGN: usize = 16;
|
||||
|
||||
@@ -43,7 +43,7 @@ extern "C-unwind" {
|
||||
pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void;
|
||||
|
||||
pub fn luaL_where(L: *mut lua_State, lvl: c_int);
|
||||
pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> !;
|
||||
pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> c_int;
|
||||
|
||||
pub fn luaL_checkoption(
|
||||
L: *mut lua_State,
|
||||
|
||||
@@ -228,13 +228,23 @@ extern "C-unwind" {
|
||||
//
|
||||
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
|
||||
extern "C-unwind" {
|
||||
pub fn lua_error(L: *mut lua_State) -> !;
|
||||
#[link_name = "lua_error"]
|
||||
fn lua_error_(L: *mut lua_State) -> c_int;
|
||||
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int;
|
||||
pub fn lua_concat(L: *mut lua_State, n: c_int);
|
||||
pub fn lua_getallocf(L: *mut lua_State, ud: *mut *mut c_void) -> lua_Alloc;
|
||||
pub fn lua_setallocf(L: *mut lua_State, f: lua_Alloc, ud: *mut c_void);
|
||||
}
|
||||
|
||||
// lua_error does not return but is declared to return int, and Rust translates
|
||||
// ! to void which can cause link-time errors if the platform linker is aware
|
||||
// of return types and requires they match (for example: wasm does this).
|
||||
#[inline(always)]
|
||||
pub unsafe fn lua_error(L: *mut lua_State) -> ! {
|
||||
lua_error_(L);
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
//
|
||||
// Some useful macros (implemented as Rust functions)
|
||||
//
|
||||
|
||||
@@ -49,7 +49,7 @@ extern "C-unwind" {
|
||||
pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void;
|
||||
|
||||
pub fn luaL_where(L: *mut lua_State, lvl: c_int);
|
||||
pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> !;
|
||||
pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> c_int;
|
||||
|
||||
pub fn luaL_checkoption(
|
||||
L: *mut lua_State,
|
||||
|
||||
@@ -308,7 +308,8 @@ extern "C-unwind" {
|
||||
//
|
||||
// Miscellaneous functions
|
||||
//
|
||||
pub fn lua_error(L: *mut lua_State) -> !;
|
||||
#[link_name = "lua_error"]
|
||||
fn lua_error_(L: *mut lua_State) -> c_int;
|
||||
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int;
|
||||
pub fn lua_concat(L: *mut lua_State, n: c_int);
|
||||
pub fn lua_len(L: *mut lua_State, idx: c_int);
|
||||
@@ -316,6 +317,15 @@ extern "C-unwind" {
|
||||
pub fn lua_setallocf(L: *mut lua_State, f: lua_Alloc, ud: *mut c_void);
|
||||
}
|
||||
|
||||
// lua_error does not return but is declared to return int, and Rust translates
|
||||
// ! to void which can cause link-time errors if the platform linker is aware
|
||||
// of return types and requires they match (for example: wasm does this).
|
||||
#[inline(always)]
|
||||
pub unsafe fn lua_error(L: *mut lua_State) -> ! {
|
||||
lua_error_(L);
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
//
|
||||
// Some useful macros (implemented as Rust functions)
|
||||
//
|
||||
@@ -453,7 +463,12 @@ extern "C-unwind" {
|
||||
pub fn lua_upvalueid(L: *mut lua_State, fidx: c_int, n: c_int) -> *mut c_void;
|
||||
pub fn lua_upvaluejoin(L: *mut lua_State, fidx1: c_int, n1: c_int, fidx2: c_int, n2: c_int);
|
||||
|
||||
pub fn lua_sethook(L: *mut lua_State, func: Option<lua_Hook>, mask: c_int, count: c_int);
|
||||
pub fn lua_sethook(
|
||||
L: *mut lua_State,
|
||||
func: Option<lua_Hook>,
|
||||
mask: c_int,
|
||||
count: c_int,
|
||||
) -> c_int;
|
||||
pub fn lua_gethook(L: *mut lua_State) -> Option<lua_Hook>;
|
||||
pub fn lua_gethookmask(L: *mut lua_State) -> c_int;
|
||||
pub fn lua_gethookcount(L: *mut lua_State) -> c_int;
|
||||
|
||||
@@ -20,7 +20,7 @@ pub struct luaL_Reg {
|
||||
pub func: lua_CFunction,
|
||||
}
|
||||
|
||||
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
|
||||
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
|
||||
extern "C-unwind" {
|
||||
pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number, sz: usize);
|
||||
|
||||
@@ -51,7 +51,7 @@ extern "C-unwind" {
|
||||
pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void;
|
||||
|
||||
pub fn luaL_where(L: *mut lua_State, lvl: c_int);
|
||||
pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> !;
|
||||
pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> c_int;
|
||||
|
||||
pub fn luaL_checkoption(
|
||||
L: *mut lua_State,
|
||||
|
||||
@@ -314,7 +314,8 @@ extern "C-unwind" {
|
||||
//
|
||||
// Miscellaneous functions
|
||||
//
|
||||
pub fn lua_error(L: *mut lua_State) -> !;
|
||||
#[link_name = "lua_error"]
|
||||
fn lua_error_(L: *mut lua_State) -> c_int;
|
||||
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int;
|
||||
pub fn lua_concat(L: *mut lua_State, n: c_int);
|
||||
pub fn lua_len(L: *mut lua_State, idx: c_int);
|
||||
@@ -323,6 +324,15 @@ extern "C-unwind" {
|
||||
pub fn lua_setallocf(L: *mut lua_State, f: lua_Alloc, ud: *mut c_void);
|
||||
}
|
||||
|
||||
// lua_error does not return but is declared to return int, and Rust translates
|
||||
// ! to void which can cause link-time errors if the platform linker is aware
|
||||
// of return types and requires they match (for example: wasm does this).
|
||||
#[inline(always)]
|
||||
pub unsafe fn lua_error(L: *mut lua_State) -> ! {
|
||||
lua_error_(L);
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
//
|
||||
// Some useful macros (implemented as Rust functions)
|
||||
//
|
||||
|
||||
@@ -50,7 +50,7 @@ extern "C-unwind" {
|
||||
pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void;
|
||||
|
||||
pub fn luaL_where(L: *mut lua_State, lvl: c_int);
|
||||
pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> !;
|
||||
pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> c_int;
|
||||
|
||||
pub fn luaL_checkoption(
|
||||
L: *mut lua_State,
|
||||
|
||||
@@ -149,13 +149,21 @@ extern "C-unwind" {
|
||||
pub fn lua_tointegerx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Integer;
|
||||
pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int;
|
||||
pub fn lua_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char;
|
||||
pub fn lua_rawlen(L: *mut lua_State, idx: c_int) -> usize;
|
||||
#[link_name = "lua_rawlen"]
|
||||
fn lua_rawlen_(L: *mut lua_State, idx: c_int) -> lua_Unsigned;
|
||||
pub fn lua_tocfunction(L: *mut lua_State, idx: c_int) -> Option<lua_CFunction>;
|
||||
pub fn lua_touserdata(L: *mut lua_State, idx: c_int) -> *mut c_void;
|
||||
pub fn lua_tothread(L: *mut lua_State, idx: c_int) -> *mut lua_State;
|
||||
pub fn lua_topointer(L: *mut lua_State, idx: c_int) -> *const c_void;
|
||||
}
|
||||
|
||||
// lua_rawlen's return type changed from size_t to lua_Unsigned int in Lua 5.4.
|
||||
// This adapts the crate API to the new Lua ABI.
|
||||
#[inline(always)]
|
||||
pub unsafe fn lua_rawlen(L: *mut lua_State, idx: c_int) -> usize {
|
||||
lua_rawlen_(L, idx) as usize
|
||||
}
|
||||
|
||||
//
|
||||
// Comparison and arithmetic functions
|
||||
//
|
||||
@@ -336,7 +344,8 @@ extern "C-unwind" {
|
||||
//
|
||||
// Miscellaneous functions
|
||||
//
|
||||
pub fn lua_error(L: *mut lua_State) -> !;
|
||||
#[link_name = "lua_error"]
|
||||
fn lua_error_(L: *mut lua_State) -> c_int;
|
||||
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int;
|
||||
pub fn lua_concat(L: *mut lua_State, n: c_int);
|
||||
pub fn lua_len(L: *mut lua_State, idx: c_int);
|
||||
@@ -348,6 +357,15 @@ extern "C-unwind" {
|
||||
pub fn lua_closeslot(L: *mut lua_State, idx: c_int);
|
||||
}
|
||||
|
||||
// lua_error does not return but is declared to return int, and Rust translates
|
||||
// ! to void which can cause link-time errors if the platform linker is aware
|
||||
// of return types and requires they match (for example: wasm does this).
|
||||
#[inline(always)]
|
||||
pub unsafe fn lua_error(L: *mut lua_State) -> ! {
|
||||
lua_error_(L);
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
//
|
||||
// Some useful macros (implemented as Rust functions)
|
||||
//
|
||||
|
||||
@@ -52,6 +52,8 @@ extern "C-unwind" {
|
||||
pub fn luaL_newmetatable_(L: *mut lua_State, tname: *const c_char) -> c_int;
|
||||
pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void;
|
||||
|
||||
pub fn luaL_checkbuffer(L: *mut lua_State, narg: c_int, len: *mut usize) -> *mut c_void;
|
||||
|
||||
pub fn luaL_where(L: *mut lua_State, lvl: c_int);
|
||||
|
||||
#[link_name = "luaL_errorL"]
|
||||
@@ -152,5 +154,48 @@ pub unsafe fn luaL_sandbox(L: *mut lua_State, enabled: c_int) {
|
||||
}
|
||||
|
||||
//
|
||||
// TODO: Generic Buffer Manipulation
|
||||
// Generic Buffer Manipulation
|
||||
//
|
||||
|
||||
/// Buffer size used for on-stack string operations. This limit depends on native stack size.
|
||||
pub const LUA_BUFFERSIZE: usize = 512;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct luaL_Strbuf {
|
||||
p: *mut c_char, // current position in buffer
|
||||
end: *mut c_char, // end of the current buffer
|
||||
L: *mut lua_State,
|
||||
storage: *mut c_void, // TString
|
||||
buffer: [c_char; LUA_BUFFERSIZE],
|
||||
}
|
||||
|
||||
// For compatibility
|
||||
pub type luaL_Buffer = luaL_Strbuf;
|
||||
|
||||
extern "C-unwind" {
|
||||
pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Strbuf);
|
||||
pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Strbuf, size: usize) -> *mut c_char;
|
||||
pub fn luaL_prepbuffsize(B: *mut luaL_Strbuf, size: usize) -> *mut c_char;
|
||||
pub fn luaL_addlstring(B: *mut luaL_Strbuf, s: *const c_char, l: usize);
|
||||
pub fn luaL_addvalue(B: *mut luaL_Strbuf);
|
||||
pub fn luaL_addvalueany(B: *mut luaL_Strbuf, idx: c_int);
|
||||
pub fn luaL_pushresult(B: *mut luaL_Strbuf);
|
||||
pub fn luaL_pushresultsize(B: *mut luaL_Strbuf, size: usize);
|
||||
}
|
||||
|
||||
pub unsafe fn luaL_addchar(B: *mut luaL_Strbuf, c: c_char) {
|
||||
if (*B).p >= (*B).end {
|
||||
luaL_prepbuffsize(B, 1);
|
||||
}
|
||||
*(*B).p = c;
|
||||
(*B).p = (*B).p.add(1);
|
||||
}
|
||||
|
||||
pub unsafe fn luaL_addstring(B: *mut luaL_Strbuf, s: *const c_char) {
|
||||
// Calculate length of s
|
||||
let mut len = 0;
|
||||
while *s.add(len) != 0 {
|
||||
len += 1;
|
||||
}
|
||||
luaL_addlstring(B, s, len);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::marker::{PhantomData, PhantomPinned};
|
||||
use std::os::raw::{c_char, c_double, c_float, c_int, c_uint, c_void};
|
||||
use std::ptr;
|
||||
use std::{mem, ptr};
|
||||
|
||||
// Option for multiple returns in 'lua_pcall' and 'lua_call'
|
||||
pub const LUA_MULTRET: c_int = -1;
|
||||
@@ -55,6 +55,7 @@ pub const LUA_TTABLE: c_int = 6;
|
||||
pub const LUA_TFUNCTION: c_int = 7;
|
||||
pub const LUA_TUSERDATA: c_int = 8;
|
||||
pub const LUA_TTHREAD: c_int = 9;
|
||||
pub const LUA_TBUFFER: c_int = 10;
|
||||
|
||||
/// Guaranteed number of Lua stack slots available to a C function.
|
||||
pub const LUA_MINSTACK: c_int = 20;
|
||||
@@ -147,6 +148,7 @@ extern "C-unwind" {
|
||||
pub fn lua_touserdatatagged(L: *mut lua_State, idx: c_int, tag: c_int) -> *mut c_void;
|
||||
pub fn lua_userdatatag(L: *mut lua_State, idx: c_int) -> c_int;
|
||||
pub fn lua_tothread(L: *mut lua_State, idx: c_int) -> *mut lua_State;
|
||||
pub fn lua_tobuffer(L: *mut lua_State, idx: c_int, len: *mut usize) -> *mut c_void;
|
||||
pub fn lua_topointer(L: *mut lua_State, idx: c_int) -> *const c_void;
|
||||
|
||||
//
|
||||
@@ -181,6 +183,8 @@ extern "C-unwind" {
|
||||
pub fn lua_newuserdatatagged(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void;
|
||||
pub fn lua_newuserdatadtor(L: *mut lua_State, sz: usize, dtor: lua_Udestructor) -> *mut c_void;
|
||||
|
||||
pub fn lua_newbuffer(L: *mut lua_State, sz: usize) -> *mut c_void;
|
||||
|
||||
//
|
||||
// Get functions (Lua -> stack)
|
||||
//
|
||||
@@ -278,6 +282,7 @@ extern "C-unwind" {
|
||||
pub fn lua_getuserdatadtor(L: *mut lua_State, tag: c_int) -> Option<lua_Destructor>;
|
||||
pub fn lua_clonefunction(L: *mut lua_State, idx: c_int);
|
||||
pub fn lua_cleartable(L: *mut lua_State, idx: c_int);
|
||||
pub fn lua_getallocf(L: *mut lua_State, ud: *mut *mut c_void) -> lua_Alloc;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -325,6 +330,15 @@ pub unsafe fn lua_newuserdata(L: *mut lua_State, sz: usize) -> *mut c_void {
|
||||
lua_newuserdatatagged(L, sz, 0)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn lua_newuserdata_t<T>(L: *mut lua_State) -> *mut T {
|
||||
unsafe extern "C-unwind" fn destructor<T>(ud: *mut c_void) {
|
||||
ptr::drop_in_place(ud as *mut T);
|
||||
}
|
||||
|
||||
lua_newuserdatadtor(L, mem::size_of::<T>(), destructor::<T>) as *mut T
|
||||
}
|
||||
|
||||
// TODO: lua_strlen
|
||||
|
||||
#[inline(always)]
|
||||
@@ -362,6 +376,11 @@ pub unsafe fn lua_isthread(L: *mut lua_State, n: c_int) -> c_int {
|
||||
(lua_type(L, n) == LUA_TTHREAD) as c_int
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn lua_isbuffer(L: *mut lua_State, n: c_int) -> c_int {
|
||||
(lua_type(L, n) == LUA_TBUFFER) as c_int
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn lua_isnone(L: *mut lua_State, n: c_int) -> c_int {
|
||||
(lua_type(L, n) == LUA_TNONE) as c_int
|
||||
|
||||
@@ -9,6 +9,7 @@ pub const LUA_TABLIBNAME: &str = "table";
|
||||
pub const LUA_OSLIBNAME: &str = "os";
|
||||
pub const LUA_STRLIBNAME: &str = "string";
|
||||
pub const LUA_BITLIBNAME: &str = "bit32";
|
||||
pub const LUA_BUFFERLIBNAME: &str = "buffer";
|
||||
pub const LUA_UTF8LIBNAME: &str = "utf8";
|
||||
pub const LUA_MATHLIBNAME: &str = "math";
|
||||
pub const LUA_DBLIBNAME: &str = "debug";
|
||||
@@ -20,6 +21,7 @@ extern "C-unwind" {
|
||||
pub fn luaopen_os(L: *mut lua_State) -> c_int;
|
||||
pub fn luaopen_string(L: *mut lua_State) -> c_int;
|
||||
pub fn luaopen_bit32(L: *mut lua_State) -> c_int;
|
||||
pub fn luaopen_buffer(L: *mut lua_State) -> c_int;
|
||||
pub fn luaopen_utf8(L: *mut lua_State) -> c_int;
|
||||
pub fn luaopen_math(L: *mut lua_State) -> c_int;
|
||||
pub fn luaopen_debug(L: *mut lua_State) -> c_int;
|
||||
|
||||
+4
-1
@@ -182,7 +182,10 @@ impl<'lua> FromLua<'lua> for AnyUserData<'lua> {
|
||||
impl<'lua> IntoLua<'lua> for OwnedAnyUserData {
|
||||
#[inline]
|
||||
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
|
||||
Ok(Value::UserData(AnyUserData(lua.adopt_owned_ref(self.0))))
|
||||
Ok(Value::UserData(AnyUserData(
|
||||
lua.adopt_owned_ref(self.0),
|
||||
self.1,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-4
@@ -6,12 +6,11 @@ use std::slice;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::lua::Lua;
|
||||
use crate::memory::MemoryState;
|
||||
use crate::table::Table;
|
||||
use crate::types::{Callback, LuaRef, MaybeSend};
|
||||
use crate::util::{
|
||||
assert_stack, check_stack, error_traceback, linenumber_to_usize, pop_error, ptr_to_lossy_str,
|
||||
ptr_to_str, StackGuard,
|
||||
assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str,
|
||||
StackGuard,
|
||||
};
|
||||
use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti, Value};
|
||||
|
||||
@@ -131,7 +130,7 @@ impl<'lua> Function<'lua> {
|
||||
check_stack(state, 2)?;
|
||||
|
||||
// Push error handler
|
||||
MemoryState::relax_limit_with(state, || ffi::lua_pushcfunction(state, error_traceback));
|
||||
lua.push_error_traceback();
|
||||
let stack_start = ffi::lua_gettop(state);
|
||||
// Push function and the arguments
|
||||
lua.push_ref(&self.0);
|
||||
|
||||
+4
-2
@@ -353,8 +353,10 @@ impl HookTriggers {
|
||||
// Returns the `count` parameter to pass to `lua_sethook`, if applicable. Otherwise, zero is
|
||||
// returned.
|
||||
pub(crate) const fn count(&self) -> c_int {
|
||||
let Some(n) = self.every_nth_instruction else { return 0 };
|
||||
n as c_int
|
||||
match self.every_nth_instruction {
|
||||
Some(n) => n as c_int,
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+137
-104
@@ -3,15 +3,14 @@ use std::cell::{RefCell, UnsafeCell};
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::mem::{self, MaybeUninit};
|
||||
use std::ops::Deref;
|
||||
use std::os::raw::{c_char, c_int, c_void};
|
||||
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe, Location};
|
||||
use std::ptr::NonNull;
|
||||
use std::ptr;
|
||||
use std::result::Result as StdResult;
|
||||
use std::sync::atomic::{AtomicPtr, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{mem, ptr, str};
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
@@ -27,15 +26,15 @@ use crate::table::Table;
|
||||
use crate::thread::Thread;
|
||||
use crate::types::{
|
||||
AppData, AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer,
|
||||
LightUserData, LuaRef, MaybeSend, Number, RegistryKey,
|
||||
LightUserData, LuaRef, MaybeSend, Number, RegistryKey, SubtypeId,
|
||||
};
|
||||
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataCell};
|
||||
use crate::userdata_impl::{UserDataProxy, UserDataRegistry};
|
||||
use crate::util::{
|
||||
self, assert_stack, check_stack, get_destructed_userdata_metatable, get_gc_metatable,
|
||||
get_gc_userdata, get_main_state, get_userdata, init_error_registry, init_gc_metatable,
|
||||
init_userdata_metatable, pop_error, push_gc_userdata, push_string, push_table, rawset_field,
|
||||
safe_pcall, safe_xpcall, short_type_name, StackGuard, WrappedFailure,
|
||||
self, assert_stack, check_stack, error_traceback, get_destructed_userdata_metatable,
|
||||
get_gc_metatable, get_gc_userdata, get_main_state, get_userdata, init_error_registry,
|
||||
init_gc_metatable, init_userdata_metatable, pop_error, push_gc_userdata, push_string,
|
||||
push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name, StackGuard, WrappedFailure,
|
||||
};
|
||||
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value};
|
||||
|
||||
@@ -60,6 +59,7 @@ use {
|
||||
crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue},
|
||||
futures_util::future::{self, Future},
|
||||
futures_util::task::{noop_waker_ref, Context, Poll, Waker},
|
||||
std::ptr::NonNull,
|
||||
};
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
@@ -94,7 +94,6 @@ pub(crate) struct ExtraData {
|
||||
|
||||
safe: bool,
|
||||
libs: StdLib,
|
||||
mem_state: Option<NonNull<MemoryState>>,
|
||||
#[cfg(feature = "module")]
|
||||
skip_memory_check: bool,
|
||||
|
||||
@@ -244,11 +243,14 @@ impl Drop for Lua {
|
||||
impl Drop for LuaInner {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
#[cfg(feature = "luau")]
|
||||
{
|
||||
(*ffi::lua_callbacks(self.state())).userdata = ptr::null_mut();
|
||||
}
|
||||
let mem_state = MemoryState::get(self.main_state);
|
||||
|
||||
ffi::lua_close(self.main_state);
|
||||
|
||||
// Deallocate MemoryState
|
||||
if !mem_state.is_null() {
|
||||
drop(Box::from_raw(mem_state));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,9 +263,6 @@ impl Drop for ExtraData {
|
||||
}
|
||||
|
||||
*mlua_expect!(self.registry_unref_list.lock(), "unref list poisoned") = None;
|
||||
if let Some(mem_state) = self.mem_state {
|
||||
drop(unsafe { Box::from_raw(mem_state.as_ptr()) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,7 +340,6 @@ impl Lua {
|
||||
|
||||
let lua = unsafe { Self::inner_new(libs, options) };
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
if libs.contains(StdLib::PACKAGE) {
|
||||
mlua_expect!(lua.disable_c_modules(), "Error during disabling C modules");
|
||||
}
|
||||
@@ -359,23 +357,22 @@ impl Lua {
|
||||
///
|
||||
/// [`StdLib`]: crate::StdLib
|
||||
pub unsafe fn unsafe_new_with(libs: StdLib, options: LuaOptions) -> Lua {
|
||||
// Workaround to avoid stripping a few unused Lua symbols that could be imported
|
||||
// by C modules in unsafe mode
|
||||
let mut _symbols: Vec<*const extern "C-unwind" fn()> =
|
||||
vec![ffi::lua_isuserdata as _, ffi::lua_tocfunction as _];
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
_symbols.extend_from_slice(&[
|
||||
ffi::lua_atpanic as _,
|
||||
ffi::luaL_loadstring as _,
|
||||
ffi::luaL_openlibs as _,
|
||||
]);
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
|
||||
{
|
||||
// Workaround to avoid stripping a few unused Lua symbols that could be imported
|
||||
// by C modules in unsafe mode
|
||||
let mut _symbols: Vec<*const extern "C-unwind" fn()> = vec![
|
||||
ffi::lua_atpanic as _,
|
||||
ffi::lua_isuserdata as _,
|
||||
ffi::lua_tocfunction as _,
|
||||
ffi::luaL_loadstring as _,
|
||||
ffi::luaL_openlibs as _,
|
||||
];
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
|
||||
{
|
||||
_symbols.push(ffi::lua_getglobal as _);
|
||||
_symbols.push(ffi::lua_setglobal as _);
|
||||
_symbols.push(ffi::luaL_setfuncs as _);
|
||||
}
|
||||
_symbols.push(ffi::lua_getglobal as _);
|
||||
_symbols.push(ffi::lua_setglobal as _);
|
||||
_symbols.push(ffi::luaL_setfuncs as _);
|
||||
}
|
||||
|
||||
Self::inner_new(libs, options)
|
||||
@@ -383,12 +380,11 @@ impl Lua {
|
||||
|
||||
/// Creates a new Lua state with required `libs` and `options`
|
||||
unsafe fn inner_new(libs: StdLib, options: LuaOptions) -> Lua {
|
||||
let mut mem_state: *mut MemoryState = Box::into_raw(Box::default());
|
||||
let mem_state: *mut MemoryState = Box::into_raw(Box::default());
|
||||
let mut state = ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void);
|
||||
// If state is null then switch to Lua internal allocator
|
||||
if state.is_null() {
|
||||
drop(Box::from_raw(mem_state));
|
||||
mem_state = ptr::null_mut();
|
||||
state = ffi::luaL_newstate();
|
||||
}
|
||||
assert!(!state.is_null(), "Failed to instantiate Lua VM");
|
||||
@@ -404,7 +400,6 @@ impl Lua {
|
||||
|
||||
let lua = Lua::init_from_ptr(state);
|
||||
let extra = lua.extra.get();
|
||||
(*extra).mem_state = NonNull::new(mem_state);
|
||||
|
||||
mlua_expect!(
|
||||
load_from_std_lib(state, libs),
|
||||
@@ -440,7 +435,7 @@ impl Lua {
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
mlua_expect!(lua.prepare_luau_state(), "Error preparing Luau state");
|
||||
mlua_expect!(lua.configure_luau(), "Error configuring Luau");
|
||||
|
||||
lua
|
||||
}
|
||||
@@ -504,6 +499,13 @@ impl Lua {
|
||||
ptr
|
||||
};
|
||||
|
||||
// Store `error_traceback` function on the ref stack
|
||||
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
|
||||
{
|
||||
ffi::lua_pushcfunction(ref_thread, error_traceback);
|
||||
assert_eq!(ffi::lua_gettop(ref_thread), ExtraData::ERROR_TRACEBACK_IDX);
|
||||
}
|
||||
|
||||
// Create ExtraData
|
||||
let extra = Arc::new(UnsafeCell::new(ExtraData {
|
||||
inner: MaybeUninit::uninit(),
|
||||
@@ -514,7 +516,6 @@ impl Lua {
|
||||
app_data: AppData::default(),
|
||||
safe: false,
|
||||
libs: StdLib::NONE,
|
||||
mem_state: None,
|
||||
#[cfg(feature = "module")]
|
||||
skip_memory_check: false,
|
||||
ref_thread,
|
||||
@@ -547,14 +548,8 @@ impl Lua {
|
||||
|
||||
// Store it in the registry
|
||||
mlua_expect!(
|
||||
(|state| {
|
||||
push_gc_userdata(state, Arc::clone(&extra), true)?;
|
||||
protect_lua!(state, 1, 0, fn(state) {
|
||||
let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void;
|
||||
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, extra_key);
|
||||
})
|
||||
})(main_state),
|
||||
"Error while storing extra data",
|
||||
set_extra_data(main_state, &extra),
|
||||
"Error while storing extra data"
|
||||
);
|
||||
|
||||
// Register `DestructedUserdata` type
|
||||
@@ -572,13 +567,6 @@ impl Lua {
|
||||
);
|
||||
assert_stack(main_state, ffi::LUA_MINSTACK);
|
||||
|
||||
// Set Luau callbacks userdata to extra data
|
||||
// We can use global callbacks userdata since we don't allow C modules in Luau
|
||||
#[cfg(feature = "luau")]
|
||||
{
|
||||
(*ffi::lua_callbacks(main_state)).userdata = extra.get() as *mut c_void;
|
||||
}
|
||||
|
||||
let inner = Arc::new(LuaInner {
|
||||
state: AtomicPtr::new(state),
|
||||
main_state,
|
||||
@@ -598,7 +586,6 @@ impl Lua {
|
||||
///
|
||||
/// [`StdLib`]: crate::StdLib
|
||||
pub fn load_from_std_lib(&self, libs: StdLib) -> Result<()> {
|
||||
#[cfg(not(feature = "luau"))]
|
||||
let is_safe = unsafe { (*self.extra.get()).safe };
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
@@ -617,12 +604,9 @@ impl Lua {
|
||||
let res = unsafe { load_from_std_lib(self.main_state, libs) };
|
||||
|
||||
// If `package` library loaded into a safe lua state then disable C modules
|
||||
#[cfg(not(feature = "luau"))]
|
||||
{
|
||||
let curr_libs = unsafe { (*self.extra.get()).libs };
|
||||
if is_safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) {
|
||||
mlua_expect!(self.disable_c_modules(), "Error during disabling C modules");
|
||||
}
|
||||
let curr_libs = unsafe { (*self.extra.get()).libs };
|
||||
if is_safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) {
|
||||
mlua_expect!(self.disable_c_modules(), "Error during disabling C modules");
|
||||
}
|
||||
unsafe { (*self.extra.get()).libs |= libs };
|
||||
|
||||
@@ -1098,9 +1082,9 @@ impl Lua {
|
||||
/// Returns the amount of memory (in bytes) currently used inside this Lua state.
|
||||
pub fn used_memory(&self) -> usize {
|
||||
unsafe {
|
||||
match (*self.extra.get()).mem_state.map(|x| x.as_ref()) {
|
||||
Some(mem_state) => mem_state.used_memory(),
|
||||
None => {
|
||||
match MemoryState::get(self.main_state) {
|
||||
mem_state if !mem_state.is_null() => (*mem_state).used_memory(),
|
||||
_ => {
|
||||
// Get data from the Lua GC
|
||||
let used_kbytes = ffi::lua_gc(self.main_state, ffi::LUA_GCCOUNT, 0);
|
||||
let used_kbytes_rem = ffi::lua_gc(self.main_state, ffi::LUA_GCCOUNTB, 0);
|
||||
@@ -1119,9 +1103,9 @@ impl Lua {
|
||||
/// Does not work in module mode where Lua state is managed externally.
|
||||
pub fn set_memory_limit(&self, limit: usize) -> Result<usize> {
|
||||
unsafe {
|
||||
match (*self.extra.get()).mem_state.map(|mut x| x.as_mut()) {
|
||||
Some(mem_state) => Ok(mem_state.set_memory_limit(limit)),
|
||||
None => Err(Error::MemoryLimitNotAvailable),
|
||||
match MemoryState::get(self.main_state) {
|
||||
mem_state if !mem_state.is_null() => Ok((*mem_state).set_memory_limit(limit)),
|
||||
_ => Err(Error::MemoryLimitNotAvailable),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2435,17 +2419,22 @@ impl Lua {
|
||||
ffi::lua_pop(state, 1);
|
||||
Nil
|
||||
}
|
||||
_ => Value::UserData(AnyUserData(self.pop_ref())),
|
||||
_ => Value::UserData(AnyUserData(self.pop_ref(), SubtypeId::None)),
|
||||
}
|
||||
}
|
||||
|
||||
ffi::LUA_TTHREAD => Value::Thread(Thread::new(self.pop_ref())),
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
ffi::LUA_TBUFFER => {
|
||||
// Buffer is represented as a userdata type
|
||||
Value::UserData(AnyUserData(self.pop_ref(), SubtypeId::Buffer))
|
||||
}
|
||||
|
||||
#[cfg(feature = "luajit")]
|
||||
ffi::LUA_TCDATA => {
|
||||
ffi::lua_pop(state, 1);
|
||||
// TODO: Fix this in a next major release
|
||||
panic!("cdata objects cannot be handled by mlua yet");
|
||||
// CDATA is represented as a userdata type
|
||||
Value::UserData(AnyUserData(self.pop_ref(), SubtypeId::CData))
|
||||
}
|
||||
|
||||
_ => mlua_panic!("LUA_TNONE in pop_value"),
|
||||
@@ -2530,7 +2519,7 @@ impl Lua {
|
||||
}
|
||||
_ => {
|
||||
ffi::lua_xpush(state, self.ref_thread(), idx);
|
||||
Value::UserData(AnyUserData(self.pop_ref_thread()))
|
||||
Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::None))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2540,10 +2529,18 @@ impl Lua {
|
||||
Value::Thread(Thread::new(self.pop_ref_thread()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
ffi::LUA_TBUFFER => {
|
||||
// Buffer is represented as a userdata type
|
||||
ffi::lua_xpush(state, self.ref_thread(), idx);
|
||||
Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::Buffer))
|
||||
}
|
||||
|
||||
#[cfg(feature = "luajit")]
|
||||
ffi::LUA_TCDATA => {
|
||||
// TODO: Fix this in a next major release
|
||||
panic!("cdata objects cannot be handled by mlua yet");
|
||||
// CData is represented as a userdata type
|
||||
ffi::lua_xpush(state, self.ref_thread(), idx);
|
||||
Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::CData))
|
||||
}
|
||||
|
||||
_ => mlua_panic!("LUA_TNONE in pop_value"),
|
||||
@@ -2611,6 +2608,16 @@ impl Lua {
|
||||
LuaRef::new(self, index)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) unsafe fn push_error_traceback(&self) {
|
||||
let state = self.state();
|
||||
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
|
||||
ffi::lua_xpush(self.ref_thread(), state, ExtraData::ERROR_TRACEBACK_IDX);
|
||||
// Lua 5.2+ support light C functions that does not require extra allocations
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
|
||||
ffi::lua_pushcfunction(state, error_traceback);
|
||||
}
|
||||
|
||||
unsafe fn register_userdata_metatable<'lua, T: 'static>(
|
||||
&'lua self,
|
||||
mut registry: UserDataRegistry<'lua, T>,
|
||||
@@ -2667,6 +2674,7 @@ impl Lua {
|
||||
rawset_field(state, metatable_index, "__index")?;
|
||||
}
|
||||
_ => {
|
||||
ffi::lua_pop(state, 1);
|
||||
// Propagate fields to the field getters
|
||||
for (k, f) in registry.fields {
|
||||
registry.field_getters.push((k, f))
|
||||
@@ -2953,11 +2961,15 @@ impl Lua {
|
||||
let fut = &mut (*upvalue).data;
|
||||
let mut ctx = Context::from_waker(lua.waker());
|
||||
match fut.as_mut().poll(&mut ctx) {
|
||||
Poll::Pending => Ok(0),
|
||||
Poll::Pending => {
|
||||
ffi::lua_pushnil(state);
|
||||
let pending = &ASYNC_POLL_PENDING as *const u8 as *mut c_void;
|
||||
ffi::lua_pushlightuserdata(state, pending);
|
||||
Ok(2)
|
||||
}
|
||||
Poll::Ready(nresults) => {
|
||||
let nresults = nresults?;
|
||||
match nresults {
|
||||
0..=2 => {
|
||||
match nresults? {
|
||||
nresults @ 0..=2 => {
|
||||
// Fast path for up to 2 results without creating a table
|
||||
ffi::lua_pushinteger(state, nresults as _);
|
||||
if nresults > 0 {
|
||||
@@ -2965,7 +2977,7 @@ impl Lua {
|
||||
}
|
||||
Ok(nresults + 1)
|
||||
}
|
||||
_ => {
|
||||
nresults => {
|
||||
let results = MultiValue::from_stack_multi(nresults, lua)?;
|
||||
ffi::lua_pushinteger(state, nresults as _);
|
||||
lua.push_value(Value::Table(lua.create_sequence_from(results)?))?;
|
||||
@@ -3009,20 +3021,17 @@ impl Lua {
|
||||
|
||||
let coroutine = self.globals().get::<_, Table>("coroutine")?;
|
||||
|
||||
let env = self.create_table_with_capacity(0, 4)?;
|
||||
let env = self.create_table_with_capacity(0, 3)?;
|
||||
env.set("get_poll", get_poll)?;
|
||||
// Cache `yield` function
|
||||
env.set("yield", coroutine.get::<_, Function>("yield")?)?;
|
||||
unsafe {
|
||||
env.set("unpack", self.create_c_function(unpack)?)?;
|
||||
}
|
||||
env.set("pending", {
|
||||
LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut c_void)
|
||||
})?;
|
||||
|
||||
self.load(
|
||||
r#"
|
||||
local poll = get_poll(...)
|
||||
local pending, yield, unpack = pending, yield, unpack
|
||||
while true do
|
||||
local nres, res, res2 = poll()
|
||||
if nres ~= nil then
|
||||
@@ -3036,7 +3045,7 @@ impl Lua {
|
||||
return unpack(res, nres)
|
||||
end
|
||||
end
|
||||
yield(pending)
|
||||
yield(res) -- `res` is a "pending" value
|
||||
end
|
||||
"#,
|
||||
)
|
||||
@@ -3127,9 +3136,10 @@ impl Lua {
|
||||
ffi::lua_setuservalue(state, -2);
|
||||
}
|
||||
|
||||
Ok(AnyUserData(self.pop_ref()))
|
||||
Ok(AnyUserData(self.pop_ref(), SubtypeId::None))
|
||||
}
|
||||
|
||||
// Luau version located in `luau/mod.rs`
|
||||
#[cfg(not(feature = "luau"))]
|
||||
fn disable_c_modules(&self) -> Result<()> {
|
||||
let package: Table = self.globals().get("package")?;
|
||||
@@ -3168,16 +3178,13 @@ impl Lua {
|
||||
#[inline]
|
||||
pub(crate) unsafe fn unlikely_memory_error(&self) -> bool {
|
||||
// MemoryInfo is empty in module mode so we cannot predict memory limits
|
||||
(*self.extra.get())
|
||||
.mem_state
|
||||
.map(|x| x.as_ref().memory_limit() == 0)
|
||||
.unwrap_or_else(|| {
|
||||
// Alternatively, check the special flag (only for module mode)
|
||||
#[cfg(feature = "module")]
|
||||
return (*self.extra.get()).skip_memory_check;
|
||||
#[cfg(not(feature = "module"))]
|
||||
return false;
|
||||
})
|
||||
match MemoryState::get(self.main_state) {
|
||||
mem_state if !mem_state.is_null() => (*mem_state).memory_limit() == 0,
|
||||
#[cfg(feature = "module")]
|
||||
_ => (*self.extra.get()).skip_memory_check, // Check the special flag (only for module mode)
|
||||
#[cfg(not(feature = "module"))]
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
@@ -3223,11 +3230,9 @@ impl LuaInner {
|
||||
}
|
||||
|
||||
impl ExtraData {
|
||||
#[cfg(feature = "luau")]
|
||||
#[inline]
|
||||
pub(crate) fn mem_state(&self) -> NonNull<MemoryState> {
|
||||
self.mem_state.unwrap()
|
||||
}
|
||||
// Index of `error_traceback` function in auxiliary thread stack
|
||||
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
|
||||
const ERROR_TRACEBACK_IDX: c_int = 1;
|
||||
}
|
||||
|
||||
struct StateGuard<'a>(&'a LuaInner, *mut ffi::lua_State);
|
||||
@@ -3245,13 +3250,13 @@ impl<'a> Drop for StateGuard<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData {
|
||||
(*ffi::lua_callbacks(state)).userdata as *mut ExtraData
|
||||
}
|
||||
#[cfg(feature = "luau")]
|
||||
if cfg!(not(feature = "module")) {
|
||||
// In the main app we can use `lua_callbacks` to access ExtraData
|
||||
return (*ffi::lua_callbacks(state)).userdata as *mut _;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData {
|
||||
let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void;
|
||||
if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, extra_key) != ffi::LUA_TUSERDATA {
|
||||
// `ExtraData` can be null only when Lua state is foreign.
|
||||
@@ -3264,6 +3269,23 @@ unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData {
|
||||
(*extra_ptr).get()
|
||||
}
|
||||
|
||||
unsafe fn set_extra_data(
|
||||
state: *mut ffi::lua_State,
|
||||
extra: &Arc<UnsafeCell<ExtraData>>,
|
||||
) -> Result<()> {
|
||||
#[cfg(feature = "luau")]
|
||||
if cfg!(not(feature = "module")) {
|
||||
(*ffi::lua_callbacks(state)).userdata = extra.get() as *mut _;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
push_gc_userdata(state, Arc::clone(extra), true)?;
|
||||
protect_lua!(state, 1, 0, fn(state) {
|
||||
let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void;
|
||||
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, extra_key);
|
||||
})
|
||||
}
|
||||
|
||||
// Creates required entries in the metatable cache (see `util::METATABLE_CACHE`)
|
||||
pub(crate) fn init_metatable_cache(cache: &mut FxHashMap<TypeId, u8>) {
|
||||
cache.insert(TypeId::of::<Arc<UnsafeCell<ExtraData>>>(), 0);
|
||||
@@ -3502,6 +3524,12 @@ unsafe fn load_from_std_lib(state: *mut ffi::lua_State, libs: StdLib) -> Result<
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
if libs.contains(StdLib::BUFFER) {
|
||||
requiref(state, ffi::LUA_BUFFERLIBNAME, ffi::luaopen_buffer, 1)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
|
||||
if libs.contains(StdLib::MATH) {
|
||||
requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
@@ -3517,6 +3545,11 @@ unsafe fn load_from_std_lib(state: *mut ffi::lua_State, libs: StdLib) -> Result<
|
||||
requiref(state, ffi::LUA_LOADLIBNAME, ffi::luaopen_package, 1)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
#[cfg(feature = "luau")]
|
||||
if libs.contains(StdLib::PACKAGE) {
|
||||
let lua: &Lua = mem::transmute((*extra_data(state)).inner.assume_init_ref());
|
||||
crate::luau::register_package_module(lua)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "luajit")]
|
||||
{
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::{c_float, c_int};
|
||||
use std::string::String as StdString;
|
||||
|
||||
use crate::chunk::ChunkMode;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::error::Result;
|
||||
use crate::lua::Lua;
|
||||
use crate::table::Table;
|
||||
use crate::util::{check_stack, StackGuard};
|
||||
use crate::value::Value;
|
||||
|
||||
// Since Luau has some missing standard function, we re-implement them here
|
||||
// Since Luau has some missing standard functions, we re-implement them here
|
||||
|
||||
impl Lua {
|
||||
pub(crate) unsafe fn prepare_luau_state(&self) -> Result<()> {
|
||||
pub(crate) unsafe fn configure_luau(&self) -> Result<()> {
|
||||
let globals = self.globals();
|
||||
|
||||
globals.raw_set(
|
||||
"collectgarbage",
|
||||
self.create_c_function(lua_collectgarbage)?,
|
||||
)?;
|
||||
globals.raw_set("require", self.create_function(lua_require)?)?;
|
||||
globals.raw_set("vector", self.create_c_function(lua_vector)?)?;
|
||||
|
||||
// Set `_VERSION` global to include version number
|
||||
@@ -30,6 +24,11 @@ impl Lua {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn disable_c_modules(&self) -> Result<()> {
|
||||
package::disable_dylibs(self);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_int {
|
||||
@@ -69,58 +68,6 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
|
||||
}
|
||||
}
|
||||
|
||||
fn lua_require(lua: &Lua, name: Option<StdString>) -> Result<Value> {
|
||||
let name = name.ok_or_else(|| Error::runtime("invalid module name"))?;
|
||||
|
||||
// Find module in the cache
|
||||
let state = lua.state();
|
||||
let loaded = unsafe {
|
||||
let _sg = StackGuard::new(state);
|
||||
check_stack(state, 2)?;
|
||||
protect_lua!(state, 0, 1, fn(state) {
|
||||
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED"));
|
||||
})?;
|
||||
Table(lua.pop_ref())
|
||||
};
|
||||
if let Some(v) = loaded.raw_get(name.clone())? {
|
||||
return Ok(v);
|
||||
}
|
||||
|
||||
// Load file from filesystem
|
||||
let mut search_path = std::env::var("LUAU_PATH").unwrap_or_default();
|
||||
if search_path.is_empty() {
|
||||
search_path = "?.luau;?.lua".into();
|
||||
}
|
||||
|
||||
let (mut source, mut source_name) = (None, String::new());
|
||||
for path in search_path.split(';') {
|
||||
let file_path = path.replacen('?', &name, 1);
|
||||
if let Ok(buf) = std::fs::read(&file_path) {
|
||||
source = Some(buf);
|
||||
source_name = file_path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let source = source.ok_or_else(|| Error::runtime(format!("cannot find '{name}'")))?;
|
||||
|
||||
let value = lua
|
||||
.load(&source)
|
||||
.set_name(&format!("={source_name}"))
|
||||
.set_mode(ChunkMode::Text)
|
||||
.call::<_, Value>(())?;
|
||||
|
||||
// Save in the cache
|
||||
loaded.raw_set(
|
||||
name,
|
||||
match value.clone() {
|
||||
Value::Nil => Value::Boolean(true),
|
||||
v => v,
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
// Luau vector datatype constructor
|
||||
unsafe extern "C-unwind" fn lua_vector(state: *mut ffi::lua_State) -> c_int {
|
||||
let x = ffi::luaL_checknumber(state, 1) as c_float;
|
||||
@@ -135,3 +82,7 @@ unsafe extern "C-unwind" fn lua_vector(state: *mut ffi::lua_State) -> c_int {
|
||||
ffi::lua_pushvector(state, x, y, z, w);
|
||||
1
|
||||
}
|
||||
|
||||
pub(crate) use package::register_package_module;
|
||||
|
||||
mod package;
|
||||
@@ -0,0 +1,268 @@
|
||||
use std::ffi::CStr;
|
||||
use std::fmt::Write;
|
||||
use std::os::raw::c_int;
|
||||
use std::path::{PathBuf, MAIN_SEPARATOR_STR};
|
||||
use std::string::String as StdString;
|
||||
use std::{env, fs};
|
||||
|
||||
use crate::chunk::ChunkMode;
|
||||
use crate::error::Result;
|
||||
use crate::lua::Lua;
|
||||
use crate::table::Table;
|
||||
use crate::types::RegistryKey;
|
||||
use crate::value::{IntoLua, Value};
|
||||
|
||||
#[cfg(unix)]
|
||||
use {libloading::Library, rustc_hash::FxHashMap};
|
||||
|
||||
//
|
||||
// Luau package module
|
||||
//
|
||||
|
||||
#[cfg(unix)]
|
||||
const TARGET_MLUA_LUAU_ABI_VERSION: u32 = 1;
|
||||
|
||||
#[cfg(all(unix, feature = "module"))]
|
||||
#[no_mangle]
|
||||
#[used]
|
||||
pub static MLUA_LUAU_ABI_VERSION: u32 = TARGET_MLUA_LUAU_ABI_VERSION;
|
||||
|
||||
// We keep reference to the `package` table in registry under this key
|
||||
struct PackageKey(RegistryKey);
|
||||
|
||||
// We keep reference to the loaded dylibs in application data
|
||||
#[cfg(unix)]
|
||||
struct LoadedDylibs(FxHashMap<PathBuf, Library>);
|
||||
|
||||
#[cfg(unix)]
|
||||
impl std::ops::Deref for LoadedDylibs {
|
||||
type Target = FxHashMap<PathBuf, Library>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl std::ops::DerefMut for LoadedDylibs {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn register_package_module(lua: &Lua) -> Result<()> {
|
||||
// Create the package table and store it in app_data for later use (bypassing globals lookup)
|
||||
let package = lua.create_table()?;
|
||||
lua.set_app_data(PackageKey(lua.create_registry_value(package.clone())?));
|
||||
|
||||
// Set `package.path`
|
||||
let mut search_path = env::var("LUAU_PATH")
|
||||
.or_else(|_| env::var("LUA_PATH"))
|
||||
.unwrap_or_default();
|
||||
if search_path.is_empty() {
|
||||
search_path = "?.luau;?.lua".to_string();
|
||||
}
|
||||
package.raw_set("path", search_path)?;
|
||||
|
||||
// Set `package.cpath`
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let mut search_cpath = env::var("LUAU_CPATH")
|
||||
.or_else(|_| env::var("LUA_CPATH"))
|
||||
.unwrap_or_default();
|
||||
if search_cpath.is_empty() {
|
||||
if cfg!(any(target_os = "macos", target_os = "ios")) {
|
||||
search_cpath = "?.dylib".to_string();
|
||||
} else {
|
||||
search_cpath = "?.so".to_string();
|
||||
}
|
||||
}
|
||||
package.raw_set("cpath", search_cpath)?;
|
||||
}
|
||||
|
||||
// Set `package.loaded` (table with a list of loaded modules)
|
||||
let loaded = lua.create_table()?;
|
||||
package.raw_set("loaded", loaded.clone())?;
|
||||
lua.set_named_registry_value("_LOADED", loaded)?;
|
||||
|
||||
// Set `package.loaders`
|
||||
let loaders = lua.create_sequence_from([lua.create_function(lua_loader)?])?;
|
||||
package.raw_set("loaders", loaders.clone())?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
loaders.push(lua.create_function(dylib_loader)?)?;
|
||||
lua.set_app_data(LoadedDylibs(FxHashMap::default()));
|
||||
}
|
||||
lua.set_named_registry_value("_LOADERS", loaders)?;
|
||||
|
||||
// Register the module and `require` function in globals
|
||||
let globals = lua.globals();
|
||||
globals.raw_set("package", package)?;
|
||||
globals.raw_set("require", unsafe { lua.create_c_function(lua_require)? })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn disable_dylibs(lua: &Lua) {
|
||||
// Presence of `LoadedDylibs` in app data is used as a flag
|
||||
// to check whether binary modules are enabled
|
||||
#[cfg(unix)]
|
||||
lua.remove_app_data::<LoadedDylibs>();
|
||||
}
|
||||
|
||||
unsafe extern "C-unwind" fn lua_require(state: *mut ffi::lua_State) -> c_int {
|
||||
ffi::lua_settop(state, 1);
|
||||
let name = ffi::luaL_checkstring(state, 1);
|
||||
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); // _LOADED is at index 2
|
||||
if ffi::lua_rawgetfield(state, 2, name) != ffi::LUA_TNIL {
|
||||
return 1; // module is already loaded
|
||||
}
|
||||
ffi::lua_pop(state, 1); // remove nil
|
||||
|
||||
// load the module
|
||||
let err_buf = ffi::lua_newuserdata_t::<StdString>(state);
|
||||
err_buf.write(StdString::new());
|
||||
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADERS")); // _LOADERS is at index 3
|
||||
for i in 1.. {
|
||||
if ffi::lua_rawgeti(state, -1, i) == ffi::LUA_TNIL {
|
||||
// no more loaders?
|
||||
if (*err_buf).is_empty() {
|
||||
ffi::luaL_error(state, cstr!("module '%s' not found"), name);
|
||||
} else {
|
||||
let bytes = (*err_buf).as_bytes();
|
||||
let extra = ffi::lua_pushlstring(state, bytes.as_ptr() as *const _, bytes.len());
|
||||
ffi::luaL_error(state, cstr!("module '%s' not found:%s"), name, extra);
|
||||
}
|
||||
}
|
||||
ffi::lua_pushvalue(state, 1); // name arg
|
||||
ffi::lua_call(state, 1, 2); // call loader
|
||||
match ffi::lua_type(state, -2) {
|
||||
ffi::LUA_TFUNCTION => break, // loader found
|
||||
ffi::LUA_TSTRING => {
|
||||
// error message
|
||||
let msg = ffi::lua_tostring(state, -2);
|
||||
let msg = CStr::from_ptr(msg).to_string_lossy();
|
||||
_ = write!(&mut *err_buf, "\n\t{msg}");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
ffi::lua_pop(state, 2); // remove both results
|
||||
}
|
||||
ffi::lua_pushvalue(state, 1); // name is 1st argument to module loader
|
||||
ffi::lua_rotate(state, -2, 1); // loader data <-> name
|
||||
|
||||
// stack: ...; loader function; module name; loader data
|
||||
ffi::lua_call(state, 2, 1);
|
||||
// stack: ...; result from loader function
|
||||
if ffi::lua_isnil(state, -1) != 0 {
|
||||
ffi::lua_pop(state, 1);
|
||||
ffi::lua_pushboolean(state, 1); // use true as result
|
||||
}
|
||||
ffi::lua_pushvalue(state, -1); // make copy of entrypoint result
|
||||
ffi::lua_setfield(state, 2, name); /* _LOADED[name] = returned value */
|
||||
1
|
||||
}
|
||||
|
||||
/// Searches for the given `name` in the given `path`.
|
||||
///
|
||||
/// `path` is a string containing a sequence of templates separated by semicolons.
|
||||
fn package_searchpath(name: &str, search_path: &str, try_prefix: bool) -> Option<PathBuf> {
|
||||
let mut names = vec![name.replace('.', MAIN_SEPARATOR_STR)];
|
||||
if try_prefix && name.contains('.') {
|
||||
let prefix = name.split_once('.').map(|(prefix, _)| prefix).unwrap();
|
||||
names.push(prefix.to_string());
|
||||
}
|
||||
for path in search_path.split(';') {
|
||||
for name in &names {
|
||||
let file_path = PathBuf::from(path.replace('?', name));
|
||||
if let Ok(true) = fs::metadata(&file_path).map(|m| m.is_file()) {
|
||||
return Some(file_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
//
|
||||
// Module loaders
|
||||
//
|
||||
|
||||
/// Tries to load a lua (text) file
|
||||
fn lua_loader(lua: &Lua, modname: StdString) -> Result<Value> {
|
||||
let package = {
|
||||
let key = lua.app_data_ref::<PackageKey>().unwrap();
|
||||
lua.registry_value::<Table>(&key.0)
|
||||
}?;
|
||||
let search_path = package.get::<_, StdString>("path").unwrap_or_default();
|
||||
|
||||
if let Some(file_path) = package_searchpath(&modname, &search_path, false) {
|
||||
match fs::read(&file_path) {
|
||||
Ok(buf) => {
|
||||
return lua
|
||||
.load(&buf)
|
||||
.set_name(&format!("={}", file_path.display()))
|
||||
.set_mode(ChunkMode::Text)
|
||||
.into_function()
|
||||
.map(Value::Function);
|
||||
}
|
||||
Err(err) => {
|
||||
return format!("cannot open '{}': {err}", file_path.display()).into_lua(lua);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
|
||||
/// Tries to load a dynamic library
|
||||
#[cfg(unix)]
|
||||
fn dylib_loader(lua: &Lua, modname: StdString) -> Result<Value> {
|
||||
let package = {
|
||||
let key = lua.app_data_ref::<PackageKey>().unwrap();
|
||||
lua.registry_value::<Table>(&key.0)
|
||||
}?;
|
||||
let search_cpath = package.get::<_, StdString>("cpath").unwrap_or_default();
|
||||
|
||||
let find_symbol = |lib: &Library| unsafe {
|
||||
if let Ok(entry) = lib.get::<ffi::lua_CFunction>(format!("luaopen_{modname}\0").as_bytes())
|
||||
{
|
||||
return lua.create_c_function(*entry).map(Value::Function);
|
||||
}
|
||||
// Try all in one mode
|
||||
if let Ok(entry) = lib.get::<ffi::lua_CFunction>(
|
||||
format!("luaopen_{}\0", modname.replace('.', "_")).as_bytes(),
|
||||
) {
|
||||
return lua.create_c_function(*entry).map(Value::Function);
|
||||
}
|
||||
"cannot find module entrypoint".into_lua(lua)
|
||||
};
|
||||
|
||||
if let Some(file_path) = package_searchpath(&modname, &search_cpath, true) {
|
||||
let file_path = file_path.canonicalize()?;
|
||||
// Load the library and check for symbol
|
||||
unsafe {
|
||||
let mut loaded_dylibs = match lua.app_data_mut::<LoadedDylibs>() {
|
||||
Some(loaded_dylibs) => loaded_dylibs,
|
||||
None => return "dynamic libraries are disabled in safe mode".into_lua(lua),
|
||||
};
|
||||
// Check if it's already loaded
|
||||
if let Some(lib) = loaded_dylibs.get(&file_path) {
|
||||
return find_symbol(lib);
|
||||
}
|
||||
if let Ok(lib) = Library::new(&file_path) {
|
||||
// Check version
|
||||
let mod_version = lib.get::<*const u32>(b"MLUA_LUAU_ABI_VERSION");
|
||||
let mod_version = mod_version.map(|v| **v).unwrap_or_default();
|
||||
if mod_version != TARGET_MLUA_LUAU_ABI_VERSION {
|
||||
let err = format!("wrong module ABI version (expected {TARGET_MLUA_LUAU_ABI_VERSION}, got {mod_version})");
|
||||
return err.into_lua(lua);
|
||||
}
|
||||
let symbol = find_symbol(&lib);
|
||||
loaded_dylibs.insert(file_path, lib);
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
+24
-29
@@ -2,11 +2,9 @@ use std::alloc::{self, Layout};
|
||||
use std::os::raw::c_void;
|
||||
use std::ptr;
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
use crate::lua::ExtraData;
|
||||
|
||||
pub(crate) static ALLOCATOR: ffi::lua_Alloc = allocator;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub(crate) struct MemoryState {
|
||||
used_memory: isize,
|
||||
@@ -20,6 +18,21 @@ pub(crate) struct MemoryState {
|
||||
}
|
||||
|
||||
impl MemoryState {
|
||||
#[inline]
|
||||
pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
|
||||
let mut mem_state = ptr::null_mut();
|
||||
#[cfg(feature = "luau")]
|
||||
{
|
||||
ffi::lua_getallocf(state, &mut mem_state);
|
||||
mlua_assert!(!mem_state.is_null(), "Luau state has no allocator userdata");
|
||||
}
|
||||
#[cfg(not(feature = "luau"))]
|
||||
if ffi::lua_getallocf(state, &mut mem_state) != ALLOCATOR {
|
||||
mem_state = ptr::null_mut();
|
||||
}
|
||||
mem_state as *mut MemoryState
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn used_memory(&self) -> usize {
|
||||
self.used_memory as usize
|
||||
@@ -37,36 +50,21 @@ impl MemoryState {
|
||||
prev_limit as usize
|
||||
}
|
||||
|
||||
// This function is used primarily for calling `lua_pushcfunction` in lua5.1/jit
|
||||
// This function is used primarily for calling `lua_pushcfunction` in lua5.1/jit/luau
|
||||
// to bypass the memory limit (if set).
|
||||
#[cfg(any(feature = "lua51", feature = "luajit"))]
|
||||
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
|
||||
#[inline]
|
||||
pub(crate) unsafe fn relax_limit_with(state: *mut ffi::lua_State, f: impl FnOnce()) {
|
||||
let mut mem_state: *mut c_void = ptr::null_mut();
|
||||
if ffi::lua_getallocf(state, &mut mem_state) == ALLOCATOR {
|
||||
(*(mem_state as *mut MemoryState)).ignore_limit = true;
|
||||
let mem_state = Self::get(state);
|
||||
if !mem_state.is_null() {
|
||||
(*mem_state).ignore_limit = true;
|
||||
f();
|
||||
(*(mem_state as *mut MemoryState)).ignore_limit = false;
|
||||
(*mem_state).ignore_limit = false;
|
||||
} else {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
// Same as the above but for Luau
|
||||
// It does not have `lua_getallocf` function, so instead we use `lua_callbacks`
|
||||
#[cfg(feature = "luau")]
|
||||
#[inline]
|
||||
pub(crate) unsafe fn relax_limit_with(state: *mut ffi::lua_State, f: impl FnOnce()) {
|
||||
let extra = (*ffi::lua_callbacks(state)).userdata as *mut ExtraData;
|
||||
if extra.is_null() {
|
||||
return f();
|
||||
}
|
||||
let mem_state = (*extra).mem_state();
|
||||
(*mem_state.as_ptr()).ignore_limit = true;
|
||||
f();
|
||||
(*mem_state.as_ptr()).ignore_limit = false;
|
||||
}
|
||||
|
||||
// Does nothing apart from calling `f()`, we don't need to bypass any limits
|
||||
#[cfg(any(feature = "lua52", feature = "lua53", feature = "lua54"))]
|
||||
#[inline]
|
||||
@@ -76,12 +74,9 @@ impl MemoryState {
|
||||
|
||||
// Returns `true` if the memory limit was reached on the last memory operation
|
||||
#[cfg(feature = "luau")]
|
||||
#[inline]
|
||||
pub(crate) unsafe fn limit_reached(state: *mut ffi::lua_State) -> bool {
|
||||
let extra = (*ffi::lua_callbacks(state)).userdata as *mut ExtraData;
|
||||
if extra.is_null() {
|
||||
return false;
|
||||
}
|
||||
(*(*extra).mem_state().as_ptr()).limit_reached
|
||||
(*Self::get(state)).limit_reached
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -10,7 +10,7 @@ use serde::Serialize;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::function::Function;
|
||||
use crate::lua::Lua;
|
||||
use crate::types::{Callback, CallbackUpvalue, LuaRef, MaybeSend};
|
||||
use crate::types::{Callback, CallbackUpvalue, LuaRef, MaybeSend, SubtypeId};
|
||||
use crate::userdata::{
|
||||
AnyUserData, MetaMethod, UserData, UserDataCell, UserDataFields, UserDataMethods,
|
||||
};
|
||||
@@ -434,6 +434,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
|
||||
push_table(state, 0, fields_nrec, true)?;
|
||||
}
|
||||
for (k, f) in registry.fields {
|
||||
#[rustfmt::skip]
|
||||
let NonStaticMethod::Function(f) = f else { unreachable!() };
|
||||
mlua_assert!(f(lua, 0)? == 1, "field function must return one value");
|
||||
rawset_field(state, -2, &k)?;
|
||||
@@ -510,7 +511,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
|
||||
#[cfg(not(feature = "luau"))]
|
||||
std::ptr::write(ud_ptr as _, UserDataCell::new(data));
|
||||
ffi::lua_setmetatable(state, -2);
|
||||
let ud = AnyUserData(lua.pop_ref());
|
||||
let ud = AnyUserData(lua.pop_ref(), SubtypeId::None);
|
||||
lua.register_raw_userdata_metatable(mt_ptr, None);
|
||||
|
||||
#[cfg(any(feature = "lua51", feature = "luajit"))]
|
||||
|
||||
+15
-2
@@ -16,32 +16,44 @@ impl StdLib {
|
||||
feature = "luau"
|
||||
))]
|
||||
pub const COROUTINE: StdLib = StdLib(1);
|
||||
|
||||
/// [`table`](https://www.lua.org/manual/5.4/manual.html#6.6) library
|
||||
pub const TABLE: StdLib = StdLib(1 << 1);
|
||||
|
||||
/// [`io`](https://www.lua.org/manual/5.4/manual.html#6.8) library
|
||||
#[cfg(not(feature = "luau"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
|
||||
pub const IO: StdLib = StdLib(1 << 2);
|
||||
|
||||
/// [`os`](https://www.lua.org/manual/5.4/manual.html#6.9) library
|
||||
pub const OS: StdLib = StdLib(1 << 3);
|
||||
|
||||
/// [`string`](https://www.lua.org/manual/5.4/manual.html#6.4) library
|
||||
pub const STRING: StdLib = StdLib(1 << 4);
|
||||
|
||||
/// [`utf8`](https://www.lua.org/manual/5.4/manual.html#6.5) library
|
||||
///
|
||||
/// Requires `feature = "lua54/lua53/luau"`
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
|
||||
pub const UTF8: StdLib = StdLib(1 << 5);
|
||||
|
||||
/// [`bit`](https://www.lua.org/manual/5.2/manual.html#6.7) library
|
||||
///
|
||||
/// Requires `feature = "lua52/luajit/luau"`
|
||||
#[cfg(any(feature = "lua52", feature = "luajit", feature = "luau", doc))]
|
||||
pub const BIT: StdLib = StdLib(1 << 6);
|
||||
|
||||
/// [`math`](https://www.lua.org/manual/5.4/manual.html#6.7) library
|
||||
pub const MATH: StdLib = StdLib(1 << 7);
|
||||
|
||||
/// [`package`](https://www.lua.org/manual/5.4/manual.html#6.3) library
|
||||
#[cfg(not(feature = "luau"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
|
||||
pub const PACKAGE: StdLib = StdLib(1 << 8);
|
||||
|
||||
/// [`buffer`](https://luau-lang.org/library#buffer-library) library
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub const BUFFER: StdLib = StdLib(1 << 9);
|
||||
|
||||
/// [`jit`](http://luajit.org/ext_jit.html) library
|
||||
///
|
||||
/// Requires `feature = "luajit"`
|
||||
@@ -55,6 +67,7 @@ impl StdLib {
|
||||
#[cfg(any(feature = "luajit", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))]
|
||||
pub const FFI: StdLib = StdLib(1 << 30);
|
||||
|
||||
/// (**unsafe**) [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10) library
|
||||
pub const DEBUG: StdLib = StdLib(1 << 31);
|
||||
|
||||
|
||||
+89
-44
@@ -651,6 +651,34 @@ impl<'lua> Table<'lua> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterates over the pairs of the table, invoking the given closure on each pair.
|
||||
///
|
||||
/// This method is similar to [`Table::pairs`], but optimized for performance.
|
||||
/// It does not invoke the `__pairs` metamethod.
|
||||
pub fn for_each<K, V>(&self, mut f: impl FnMut(K, V) -> Result<()>) -> Result<()>
|
||||
where
|
||||
K: FromLua<'lua>,
|
||||
V: FromLua<'lua>,
|
||||
{
|
||||
let lua = self.0.lua;
|
||||
let state = lua.state();
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(state);
|
||||
check_stack(state, 5)?;
|
||||
|
||||
lua.push_ref(&self.0);
|
||||
ffi::lua_pushnil(state);
|
||||
while ffi::lua_next(state, -2) != 0 {
|
||||
let k = K::from_stack(-2, lua)?;
|
||||
let v = V::from_stack(-1, lua)?;
|
||||
f(k, v)?;
|
||||
// Keep key for next iteration
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Consume this table and return an iterator over all values in the sequence part of the table.
|
||||
///
|
||||
/// The iterator will yield all values `t[1]`, `t[2]` and so on, until a `nil` value is
|
||||
@@ -692,7 +720,7 @@ impl<'lua> Table<'lua> {
|
||||
pub fn sequence_values<V: FromLua<'lua>>(self) -> TableSequence<'lua, V> {
|
||||
TableSequence {
|
||||
table: self.0,
|
||||
index: Some(1),
|
||||
index: 1,
|
||||
len: None,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
@@ -712,15 +740,15 @@ impl<'lua> Table<'lua> {
|
||||
let len = len.unwrap_or_else(|| self.raw_len()) as Integer;
|
||||
TableSequence {
|
||||
table: self.0,
|
||||
index: Some(1),
|
||||
index: 1,
|
||||
len: Some(len),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets element value at position `idx` without invoking metamethods.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn raw_seti<V: IntoLua<'lua>>(&self, idx: usize, value: V) -> Result<()> {
|
||||
#[doc(hidden)]
|
||||
pub fn raw_seti<V: IntoLua<'lua>>(&self, idx: usize, value: V) -> Result<()> {
|
||||
#[cfg(feature = "luau")]
|
||||
self.check_readonly_write()?;
|
||||
|
||||
@@ -1079,21 +1107,48 @@ impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> {
|
||||
|
||||
// HashMap
|
||||
let mut map = serializer.serialize_map(None)?;
|
||||
let pairs = MapPairs::new(self.table.clone(), self.options.sort_keys)
|
||||
.map_err(serde::ser::Error::custom)?;
|
||||
for kv in pairs {
|
||||
let (key, value) = kv.map_err(serde::ser::Error::custom)?;
|
||||
let mut serialize_err = None;
|
||||
let mut process_pair = |key, value| {
|
||||
let skip_key = check_value_for_skip(&key, self.options, &self.visited)
|
||||
.map_err(serde::ser::Error::custom)?;
|
||||
.map_err(|err| Error::SerializeError(err.to_string()))?;
|
||||
let skip_value = check_value_for_skip(&value, self.options, &self.visited)
|
||||
.map_err(serde::ser::Error::custom)?;
|
||||
.map_err(|err| Error::SerializeError(err.to_string()))?;
|
||||
if skip_key || skip_value {
|
||||
continue;
|
||||
// continue iteration
|
||||
return Ok(());
|
||||
}
|
||||
map.serialize_entry(
|
||||
&SerializableValue::new(&key, options, Some(visited)),
|
||||
&SerializableValue::new(&value, options, Some(visited)),
|
||||
)?;
|
||||
)
|
||||
.map_err(|err| {
|
||||
serialize_err = Some(err);
|
||||
Error::SerializeError(String::new())
|
||||
})
|
||||
};
|
||||
|
||||
let res = if !self.options.sort_keys {
|
||||
// Fast track
|
||||
self.table.for_each(process_pair)
|
||||
} else {
|
||||
MapPairs::new(self.table.clone(), self.options.sort_keys)
|
||||
.map_err(serde::ser::Error::custom)?
|
||||
.try_for_each(|kv| {
|
||||
let (key, value) = kv?;
|
||||
process_pair(key, value)
|
||||
})
|
||||
};
|
||||
match res {
|
||||
Ok(_) => {}
|
||||
Err(Error::SerializeError(_)) if serialize_err.is_some() => {
|
||||
return Err(serialize_err.unwrap());
|
||||
}
|
||||
Err(Error::SerializeError(msg)) => {
|
||||
return Err(serde::ser::Error::custom(msg));
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(serde::ser::Error::custom(err.to_string()));
|
||||
}
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
@@ -1129,16 +1184,15 @@ where
|
||||
lua.push_ref(&self.table);
|
||||
lua.push_value(prev_key)?;
|
||||
|
||||
let next = protect_lua!(state, 2, ffi::LUA_MULTRET, |state| {
|
||||
ffi::lua_next(state, -2)
|
||||
})?;
|
||||
if next != 0 {
|
||||
let value = lua.pop_value();
|
||||
let key = lua.pop_value();
|
||||
// It must be safe to call `lua_next` unprotected as deleting a key from a table is
|
||||
// a permitted operation.
|
||||
// It fails only if the key is not found (never existed) which seems impossible scenario.
|
||||
if ffi::lua_next(state, -2) != 0 {
|
||||
let key = lua.stack_value(-2);
|
||||
Ok(Some((
|
||||
key.clone(),
|
||||
K::from_lua(key, lua)?,
|
||||
V::from_lua(value, lua)?,
|
||||
V::from_stack(-1, lua)?,
|
||||
)))
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -1166,7 +1220,7 @@ where
|
||||
/// [`Table::sequence_values`]: crate::Table::sequence_values
|
||||
pub struct TableSequence<'lua, V> {
|
||||
table: LuaRef<'lua>,
|
||||
index: Option<Integer>,
|
||||
index: Integer,
|
||||
len: Option<Integer>,
|
||||
_phantom: PhantomData<V>,
|
||||
}
|
||||
@@ -1178,31 +1232,22 @@ where
|
||||
type Item = Result<V>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(index) = self.index.take() {
|
||||
let lua = self.table.lua;
|
||||
let state = lua.state();
|
||||
|
||||
let res = (|| unsafe {
|
||||
let _sg = StackGuard::new(state);
|
||||
check_stack(state, 1)?;
|
||||
|
||||
lua.push_ref(&self.table);
|
||||
match ffi::lua_rawgeti(state, -1, index) {
|
||||
ffi::LUA_TNIL if index > self.len.unwrap_or(0) => Ok(None),
|
||||
_ => Ok(Some((index, lua.pop_value()))),
|
||||
}
|
||||
})();
|
||||
|
||||
match res {
|
||||
Ok(Some((index, r))) => {
|
||||
self.index = Some(index + 1);
|
||||
Some(V::from_lua(r, lua))
|
||||
}
|
||||
Ok(None) => None,
|
||||
Err(err) => Some(Err(err)),
|
||||
let lua = self.table.lua;
|
||||
let state = lua.state();
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(state);
|
||||
if let Err(err) = check_stack(state, 1) {
|
||||
return Some(Err(err));
|
||||
}
|
||||
|
||||
lua.push_ref(&self.table);
|
||||
match ffi::lua_rawgeti(state, -1, self.index) {
|
||||
ffi::LUA_TNIL if self.index > self.len.unwrap_or(0) => None,
|
||||
_ => {
|
||||
self.index += 1;
|
||||
Some(V::from_stack(-1, lua))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -239,7 +239,7 @@ impl<'lua> Thread<'lua> {
|
||||
///
|
||||
/// [Lua 5.4]: https://www.lua.org/manual/5.4/manual.html#lua_closethread
|
||||
#[cfg(any(feature = "lua54", feature = "luau"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "luau"))))]
|
||||
pub fn reset(&self, func: crate::function::Function<'lua>) -> Result<()> {
|
||||
let lua = self.0.lua;
|
||||
let thread_state = self.state();
|
||||
|
||||
@@ -29,6 +29,16 @@ pub type Integer = ffi::lua_Integer;
|
||||
/// Type of Lua floating point numbers.
|
||||
pub type Number = ffi::lua_Number;
|
||||
|
||||
// Represents different subtypes wrapped to AnyUserData
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub(crate) enum SubtypeId {
|
||||
None,
|
||||
#[cfg(feature = "luau")]
|
||||
Buffer,
|
||||
#[cfg(feature = "luajit")]
|
||||
CData,
|
||||
}
|
||||
|
||||
/// A "light" userdata value. Equivalent to an unmanaged raw pointer.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct LightUserData(pub *mut c_void);
|
||||
|
||||
+15
-7
@@ -22,7 +22,7 @@ use crate::function::Function;
|
||||
use crate::lua::Lua;
|
||||
use crate::string::String;
|
||||
use crate::table::{Table, TablePairs};
|
||||
use crate::types::{LuaRef, MaybeSend};
|
||||
use crate::types::{LuaRef, MaybeSend, SubtypeId};
|
||||
use crate::util::{check_stack, get_userdata, take_userdata, StackGuard};
|
||||
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value};
|
||||
use crate::UserDataRegistry;
|
||||
@@ -580,12 +580,12 @@ pub trait UserDataFields<'lua, T> {
|
||||
/// # use mlua::{Lua, Result, UserData};
|
||||
/// # fn main() -> Result<()> {
|
||||
/// # let lua = Lua::new();
|
||||
/// struct MyUserData(i32);
|
||||
/// struct MyUserData;
|
||||
///
|
||||
/// impl UserData for MyUserData {}
|
||||
///
|
||||
/// // `MyUserData` now implements `IntoLua`:
|
||||
/// lua.globals().set("myobject", MyUserData(123))?;
|
||||
/// lua.globals().set("myobject", MyUserData)?;
|
||||
///
|
||||
/// lua.load("assert(type(myobject) == 'userdata')").exec()?;
|
||||
/// # Ok(())
|
||||
@@ -791,7 +791,7 @@ impl<T> Deref for UserDataVariant<T> {
|
||||
/// [`is`]: crate::AnyUserData::is
|
||||
/// [`borrow`]: crate::AnyUserData::borrow
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>);
|
||||
pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>, pub(crate) SubtypeId);
|
||||
|
||||
/// Owned handle to an internal Lua userdata.
|
||||
///
|
||||
@@ -801,14 +801,14 @@ pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>);
|
||||
#[cfg(feature = "unstable")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OwnedAnyUserData(pub(crate) crate::types::LuaOwnedRef);
|
||||
pub struct OwnedAnyUserData(pub(crate) crate::types::LuaOwnedRef, pub(crate) SubtypeId);
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
impl OwnedAnyUserData {
|
||||
/// Get borrowed handle to the underlying Lua userdata.
|
||||
#[cfg_attr(feature = "send", allow(unused))]
|
||||
pub const fn to_ref(&self) -> AnyUserData {
|
||||
AnyUserData(self.0.to_ref())
|
||||
AnyUserData(self.0.to_ref(), self.1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1101,7 +1101,7 @@ impl<'lua> AnyUserData<'lua> {
|
||||
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
|
||||
#[inline]
|
||||
pub fn into_owned(self) -> OwnedAnyUserData {
|
||||
OwnedAnyUserData(self.0.into_owned())
|
||||
OwnedAnyUserData(self.0.into_owned(), self.1)
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
@@ -1112,6 +1112,14 @@ impl<'lua> AnyUserData<'lua> {
|
||||
|
||||
/// Returns a type name of this `UserData` (from a metatable field).
|
||||
pub(crate) fn type_name(&self) -> Result<Option<StdString>> {
|
||||
match self.1 {
|
||||
SubtypeId::None => {}
|
||||
#[cfg(feature = "luau")]
|
||||
SubtypeId::Buffer => return Ok(Some("buffer".to_owned())),
|
||||
#[cfg(feature = "luajit")]
|
||||
SubtypeId::CData => return Ok(Some("cdata".to_owned())),
|
||||
}
|
||||
|
||||
let lua = self.0.lua;
|
||||
let state = lua.state();
|
||||
unsafe {
|
||||
|
||||
@@ -218,7 +218,7 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> {
|
||||
MR: Future<Output = Result<R>> + 's,
|
||||
R: IntoLuaMulti<'lua>,
|
||||
{
|
||||
let name = get_function_name::<T>(name);
|
||||
let name = Arc::new(get_function_name::<T>(name));
|
||||
let method = Arc::new(method);
|
||||
|
||||
Box::new(move |lua, mut args| unsafe {
|
||||
@@ -312,7 +312,7 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> {
|
||||
MR: Future<Output = Result<R>> + 's,
|
||||
R: IntoLuaMulti<'lua>,
|
||||
{
|
||||
let name = get_function_name::<T>(name);
|
||||
let name = Arc::new(get_function_name::<T>(name));
|
||||
let method = Arc::new(method);
|
||||
|
||||
Box::new(move |lua, mut args| unsafe {
|
||||
|
||||
+13
-32
@@ -6,7 +6,7 @@ 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;
|
||||
use std::{mem, ptr, slice, str};
|
||||
use std::{ptr, slice, str};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use rustc_hash::FxHashMap;
|
||||
@@ -282,38 +282,23 @@ pub unsafe fn rawset_field(state: *mut ffi::lua_State, table: c_int, field: &str
|
||||
}
|
||||
|
||||
// Internally uses 3 stack spaces, does not call checkstack.
|
||||
#[cfg(not(feature = "luau"))]
|
||||
#[inline]
|
||||
pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<()> {
|
||||
#[cfg(not(feature = "luau"))]
|
||||
let ud = if protect {
|
||||
protect_lua!(state, 0, 1, |state| {
|
||||
ffi::lua_newuserdata(state, mem::size_of::<T>()) as *mut T
|
||||
ffi::lua_newuserdata(state, std::mem::size_of::<T>()) as *mut T
|
||||
})?
|
||||
} else {
|
||||
ffi::lua_newuserdata(state, mem::size_of::<T>()) as *mut T
|
||||
ffi::lua_newuserdata(state, std::mem::size_of::<T>()) as *mut T
|
||||
};
|
||||
ptr::write(ud, t);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Internally uses 3 stack spaces, does not call checkstack.
|
||||
#[cfg(feature = "luau")]
|
||||
#[inline]
|
||||
pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<()> {
|
||||
unsafe extern "C-unwind" fn destructor<T>(ud: *mut c_void) {
|
||||
ptr::drop_in_place(ud as *mut T);
|
||||
}
|
||||
|
||||
let size = mem::size_of::<T>();
|
||||
#[cfg(feature = "luau")]
|
||||
let ud = if protect {
|
||||
protect_lua!(state, 0, 1, |state| {
|
||||
ffi::lua_newuserdatadtor(state, size, destructor::<T>) as *mut T
|
||||
})?
|
||||
protect_lua!(state, 0, 1, |state| { ffi::lua_newuserdata_t::<T>(state) })?
|
||||
} else {
|
||||
ffi::lua_newuserdatadtor(state, size, destructor::<T>) as *mut T
|
||||
ffi::lua_newuserdata_t::<T>(state)
|
||||
};
|
||||
ptr::write(ud, t);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -328,10 +313,10 @@ pub unsafe fn push_userdata_uv<T>(
|
||||
) -> Result<()> {
|
||||
let ud = if protect {
|
||||
protect_lua!(state, 0, 1, |state| {
|
||||
ffi::lua_newuserdatauv(state, mem::size_of::<T>(), nuvalue) as *mut T
|
||||
ffi::lua_newuserdatauv(state, std::mem::size_of::<T>(), nuvalue) as *mut T
|
||||
})?
|
||||
} else {
|
||||
ffi::lua_newuserdatauv(state, mem::size_of::<T>(), nuvalue) as *mut T
|
||||
ffi::lua_newuserdatauv(state, std::mem::size_of::<T>(), nuvalue) as *mut T
|
||||
};
|
||||
ptr::write(ud, t);
|
||||
Ok(())
|
||||
@@ -1009,16 +994,10 @@ pub(crate) enum WrappedFailure {
|
||||
|
||||
impl WrappedFailure {
|
||||
pub(crate) unsafe fn new_userdata(state: *mut ffi::lua_State) -> *mut Self {
|
||||
let size = mem::size_of::<WrappedFailure>();
|
||||
#[cfg(feature = "luau")]
|
||||
let ud = {
|
||||
unsafe extern "C-unwind" fn destructor(p: *mut c_void) {
|
||||
ptr::drop_in_place(p as *mut WrappedFailure);
|
||||
}
|
||||
ffi::lua_newuserdatadtor(state, size, destructor) as *mut Self
|
||||
};
|
||||
let ud = ffi::lua_newuserdata_t::<Self>(state);
|
||||
#[cfg(not(feature = "luau"))]
|
||||
let ud = ffi::lua_newuserdata(state, size) as *mut Self;
|
||||
let ud = ffi::lua_newuserdata(state, std::mem::size_of::<Self>()) as *mut Self;
|
||||
ptr::write(ud, WrappedFailure::None);
|
||||
ud
|
||||
}
|
||||
@@ -1064,6 +1043,8 @@ pub(crate) unsafe fn to_string(state: *mut ffi::lua_State, index: c_int) -> Stri
|
||||
ffi::LUA_TFUNCTION => format!("<function {:?}>", ffi::lua_topointer(state, index)),
|
||||
ffi::LUA_TUSERDATA => format!("<userdata {:?}>", ffi::lua_topointer(state, index)),
|
||||
ffi::LUA_TTHREAD => format!("<thread {:?}>", ffi::lua_topointer(state, index)),
|
||||
#[cfg(feature = "luau")]
|
||||
ffi::LUA_TBUFFER => format!("<buffer {:?}>", ffi::lua_topointer(state, index)),
|
||||
_ => "<unknown>".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
+30
-4
@@ -24,7 +24,7 @@ use crate::lua::Lua;
|
||||
use crate::string::String;
|
||||
use crate::table::Table;
|
||||
use crate::thread::Thread;
|
||||
use crate::types::{Integer, LightUserData, Number};
|
||||
use crate::types::{Integer, LightUserData, Number, SubtypeId};
|
||||
use crate::userdata::AnyUserData;
|
||||
use crate::util::{check_stack, StackGuard};
|
||||
|
||||
@@ -88,7 +88,11 @@ impl<'lua> Value<'lua> {
|
||||
Value::Table(_) => "table",
|
||||
Value::Function(_) => "function",
|
||||
Value::Thread(_) => "thread",
|
||||
Value::UserData(_) => "userdata",
|
||||
Value::UserData(AnyUserData(_, SubtypeId::None)) => "userdata",
|
||||
#[cfg(feature = "luau")]
|
||||
Value::UserData(AnyUserData(_, SubtypeId::Buffer)) => "buffer",
|
||||
#[cfg(feature = "luajit")]
|
||||
Value::UserData(AnyUserData(_, SubtypeId::CData)) => "cdata",
|
||||
Value::Error(_) => "error",
|
||||
}
|
||||
}
|
||||
@@ -126,7 +130,7 @@ impl<'lua> Value<'lua> {
|
||||
| Value::Table(Table(r))
|
||||
| Value::Function(Function(r))
|
||||
| Value::Thread(Thread(r, ..))
|
||||
| Value::UserData(AnyUserData(r)) => r.to_pointer(),
|
||||
| Value::UserData(AnyUserData(r, ..)) => r.to_pointer(),
|
||||
_ => ptr::null(),
|
||||
}
|
||||
}
|
||||
@@ -148,7 +152,7 @@ impl<'lua> Value<'lua> {
|
||||
Value::Table(Table(r))
|
||||
| Value::Function(Function(r))
|
||||
| Value::Thread(Thread(r, ..))
|
||||
| Value::UserData(AnyUserData(r)) => unsafe {
|
||||
| Value::UserData(AnyUserData(r, ..)) => unsafe {
|
||||
let state = r.lua.state();
|
||||
let _guard = StackGuard::new(state);
|
||||
check_stack(state, 3)?;
|
||||
@@ -410,6 +414,28 @@ impl<'lua> Value<'lua> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the value is a Buffer wrapped in [`AnyUserData`].
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn is_buffer(&self) -> bool {
|
||||
self.as_userdata()
|
||||
.map(|ud| ud.1 == SubtypeId::Buffer)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns `true` if the value is a CData wrapped in [`AnyUserData`].
|
||||
#[cfg(any(feature = "luajit", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))]
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn is_cdata(&self) -> bool {
|
||||
self.as_userdata()
|
||||
.map(|ud| ud.1 == SubtypeId::CData)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Wrap reference to this Value into [`SerializableValue`].
|
||||
///
|
||||
/// This allows customizing serialization behavior using serde.
|
||||
|
||||
@@ -10,10 +10,17 @@ use mlua::{
|
||||
UserData, UserDataMethods, Value,
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
async fn sleep_ms(ms: u64) {
|
||||
tokio::time::sleep(Duration::from_millis(ms)).await;
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
async fn sleep_ms(_ms: u64) {
|
||||
// I was unable to make sleep() work in wasm32-emscripten target
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_async_function() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
@@ -7,6 +7,12 @@ use mlua::{Lua, Result};
|
||||
fn test_chunk_path() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
if cfg!(target_arch = "wasm32") {
|
||||
// TODO: figure out why emscripten fails on file operations
|
||||
// Also see https://github.com/rust-lang/rust/issues/119250
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
fs::write(
|
||||
temp_dir.path().join("module.lua"),
|
||||
|
||||
+3
-1
@@ -41,7 +41,9 @@ fn test_error_context() -> Result<()> {
|
||||
.context("some new context")
|
||||
})?;
|
||||
let res = func3.call::<_, ()>(()).err().unwrap();
|
||||
let Error::CallbackError { cause, .. } = &res else { unreachable!() };
|
||||
let Error::CallbackError { cause, .. } = &res else {
|
||||
unreachable!()
|
||||
};
|
||||
assert!(!res.to_string().contains("some context"));
|
||||
assert!(res.to_string().contains("some new context"));
|
||||
assert!(cause.downcast_ref::<io::Error>().is_some());
|
||||
|
||||
+4
-2
@@ -1,6 +1,5 @@
|
||||
#![cfg(not(feature = "luau"))]
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::{AtomicI64, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -170,10 +169,13 @@ fn test_hook_removal() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Having the code compiled (even not run) on macos and luajit causes a memory reference issue
|
||||
// See https://github.com/LuaJIT/LuaJIT/issues/1099
|
||||
#[cfg(not(all(feature = "luajit", target_os = "macos")))]
|
||||
#[test]
|
||||
fn test_hook_swap_within_hook() -> Result<()> {
|
||||
thread_local! {
|
||||
static TL_LUA: RefCell<Option<Lua>> = RefCell::new(None);
|
||||
static TL_LUA: std::cell::RefCell<Option<Lua>> = Default::default();
|
||||
}
|
||||
|
||||
TL_LUA.with(|tl| {
|
||||
|
||||
+66
-5
@@ -1,6 +1,5 @@
|
||||
#![cfg(feature = "luau")]
|
||||
|
||||
use std::env;
|
||||
use std::fmt::Debug;
|
||||
use std::fs;
|
||||
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||
@@ -8,7 +7,8 @@ use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use mlua::{
|
||||
Compiler, CoverageInfo, Error, Lua, Result, Table, ThreadStatus, Value, Vector, VmState,
|
||||
Compiler, CoverageInfo, Error, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value,
|
||||
Vector, VmState,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -23,7 +23,18 @@ fn test_version() -> Result<()> {
|
||||
|
||||
#[test]
|
||||
fn test_require() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
// Ensure that require() is not available if package module is not loaded
|
||||
let mut lua = Lua::new_with(StdLib::NONE, LuaOptions::default())?;
|
||||
assert!(lua.globals().get::<_, Option<Value>>("require")?.is_none());
|
||||
assert!(lua.globals().get::<_, Option<Value>>("package")?.is_none());
|
||||
|
||||
if cfg!(target_arch = "wasm32") {
|
||||
// TODO: figure out why emscripten fails on file operations
|
||||
// Also see https://github.com/rust-lang/rust/issues/119250
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
lua = Lua::new();
|
||||
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
fs::write(
|
||||
@@ -37,7 +48,10 @@ fn test_require() -> Result<()> {
|
||||
"#,
|
||||
)?;
|
||||
|
||||
env::set_var("LUAU_PATH", temp_dir.path().join("?.luau"));
|
||||
lua.globals()
|
||||
.get::<_, Table>("package")?
|
||||
.set("path", temp_dir.path().join("?.luau").to_string_lossy())?;
|
||||
|
||||
lua.load(
|
||||
r#"
|
||||
local module = require("module")
|
||||
@@ -49,7 +63,28 @@ fn test_require() -> Result<()> {
|
||||
assert(not ok and string.find(err, "module.luau") ~= nil)
|
||||
"#,
|
||||
)
|
||||
.exec()
|
||||
.exec()?;
|
||||
|
||||
// Require non-existent module
|
||||
match lua.load("require('non-existent')").exec() {
|
||||
Err(Error::RuntimeError(e)) if e.contains("module 'non-existent' not found") => {}
|
||||
r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"),
|
||||
}
|
||||
|
||||
// Require binary module in safe mode
|
||||
lua.globals()
|
||||
.get::<_, Table>("package")?
|
||||
.set("cpath", temp_dir.path().join("?.so").to_string_lossy())?;
|
||||
fs::write(temp_dir.path().join("dylib.so"), "")?;
|
||||
match lua.load("require('dylib')").exec() {
|
||||
Err(Error::RuntimeError(e)) if cfg!(unix) && e.contains("module 'dylib' not found") => {
|
||||
assert!(e.contains("dynamic libraries are disabled in safe mode"))
|
||||
}
|
||||
Err(Error::RuntimeError(e)) if e.contains("module 'dylib' not found") => {}
|
||||
r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "luau-vector4"))]
|
||||
@@ -407,3 +442,29 @@ fn test_coverage() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buffer() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let buf1 = lua
|
||||
.load(
|
||||
r#"
|
||||
local buf = buffer.fromstring("hello")
|
||||
assert(buffer.len(buf) == 5)
|
||||
return buf
|
||||
"#,
|
||||
)
|
||||
.eval::<Value>()?;
|
||||
assert!(buf1.is_userdata() && buf1.is_buffer());
|
||||
assert_eq!(buf1.type_name(), "buffer");
|
||||
|
||||
let buf2 = lua.load("buffer.fromstring('hello')").eval::<Value>()?;
|
||||
assert_ne!(buf1, buf2);
|
||||
|
||||
// Check that we can pass buffer type to Lua
|
||||
let func = lua.create_function(|_, buf: Value| return buf.to_string())?;
|
||||
assert!(func.call::<_, String>(buf1)?.starts_with("buffer:"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ lua53 = ["mlua/lua53"]
|
||||
lua52 = ["mlua/lua52"]
|
||||
lua51 = ["mlua/lua51"]
|
||||
luajit = ["mlua/luajit"]
|
||||
luau = ["mlua/luau"]
|
||||
|
||||
[dependencies]
|
||||
mlua = { path = "../..", features = ["module"] }
|
||||
|
||||
@@ -10,6 +10,7 @@ lua53 = ["mlua/lua53"]
|
||||
lua52 = ["mlua/lua52"]
|
||||
lua51 = ["mlua/lua51"]
|
||||
luajit = ["mlua/luajit"]
|
||||
luau = ["mlua/luau"]
|
||||
vendored = ["mlua/vendored"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::path::PathBuf;
|
||||
use mlua::{Lua, Result};
|
||||
|
||||
#[test]
|
||||
fn test_module() -> Result<()> {
|
||||
fn test_module_simple() -> Result<()> {
|
||||
let lua = make_lua()?;
|
||||
lua.load(
|
||||
r#"
|
||||
|
||||
+11
-2
@@ -73,13 +73,22 @@ fn test_static_lua_coroutine() -> Result<()> {
|
||||
async fn test_static_async() -> Result<()> {
|
||||
let lua = Lua::new().into_static();
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
async fn sleep_ms(ms: u64) {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(ms)).await;
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
async fn sleep_ms(_ms: u64) {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
let timer =
|
||||
lua.create_async_function(|_, (i, n, f): (u64, u64, mlua::Function)| async move {
|
||||
tokio::task::spawn_local(async move {
|
||||
let dur = std::time::Duration::from_millis(i);
|
||||
for _ in 0..n {
|
||||
tokio::task::spawn_local(f.call_async::<(), ()>(()));
|
||||
tokio::time::sleep(dur).await;
|
||||
sleep_ms(i).await;
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
|
||||
@@ -217,6 +217,74 @@ fn test_table_sequence_from() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_pairs() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let table = lua
|
||||
.load(
|
||||
r#"
|
||||
{
|
||||
foo = "bar",
|
||||
baz = "baf",
|
||||
[123] = 456,
|
||||
[789] = 101112,
|
||||
5,
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.eval::<Table>()?;
|
||||
|
||||
let table2 = table.clone();
|
||||
for (i, kv) in table.pairs::<String, Value>().enumerate() {
|
||||
let (k, _v) = kv.unwrap();
|
||||
match i {
|
||||
// Try to add a new key
|
||||
0 => table2.set("new_key", "new_value")?,
|
||||
// Try to delete the 2nd key
|
||||
1 => {
|
||||
table2.set(k, Value::Nil)?;
|
||||
lua.gc_collect()?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_for_each() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let table = lua
|
||||
.load(
|
||||
r#"
|
||||
{
|
||||
foo = "bar",
|
||||
baz = "baf",
|
||||
[123] = 456,
|
||||
[789] = 101112,
|
||||
5,
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.eval::<Table>()?;
|
||||
|
||||
let mut i = 0;
|
||||
table.for_each::<String, Value>(|k, _| {
|
||||
if i == 0 {
|
||||
// Delete first key
|
||||
table.set(k, Value::Nil)?;
|
||||
lua.gc_collect()?;
|
||||
}
|
||||
Ok(i += 1)
|
||||
})?;
|
||||
assert_eq!(i, 5);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_scope() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
+30
-13
@@ -1,4 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::iter::FromIterator;
|
||||
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||
use std::string::String as StdString;
|
||||
@@ -312,30 +313,29 @@ fn test_error() -> Result<()> {
|
||||
globals.set("rust_error_function", rust_error_function)?;
|
||||
|
||||
let no_error = globals.get::<_, Function>("no_error")?;
|
||||
let lua_error = globals.get::<_, Function>("lua_error")?;
|
||||
let rust_error = globals.get::<_, Function>("rust_error")?;
|
||||
let return_error = globals.get::<_, Function>("return_error")?;
|
||||
let return_string_error = globals.get::<_, Function>("return_string_error")?;
|
||||
let test_pcall = globals.get::<_, Function>("test_pcall")?;
|
||||
let understand_recursion = globals.get::<_, Function>("understand_recursion")?;
|
||||
|
||||
assert!(no_error.call::<_, ()>(()).is_ok());
|
||||
|
||||
let lua_error = globals.get::<_, Function>("lua_error")?;
|
||||
match lua_error.call::<_, ()>(()) {
|
||||
Err(Error::RuntimeError(_)) => {}
|
||||
Err(e) => panic!("error is not RuntimeError kind, got {:?}", e),
|
||||
_ => panic!("error not returned"),
|
||||
}
|
||||
|
||||
let rust_error = globals.get::<_, Function>("rust_error")?;
|
||||
match rust_error.call::<_, ()>(()) {
|
||||
Err(Error::CallbackError { .. }) => {}
|
||||
Err(e) => panic!("error is not CallbackError kind, got {:?}", e),
|
||||
_ => panic!("error not returned"),
|
||||
}
|
||||
|
||||
let return_error = globals.get::<_, Function>("return_error")?;
|
||||
match return_error.call::<_, Value>(()) {
|
||||
Ok(Value::Error(_)) => {}
|
||||
_ => panic!("Value::Error not returned"),
|
||||
}
|
||||
|
||||
let return_string_error = globals.get::<_, Function>("return_string_error")?;
|
||||
assert!(return_string_error.call::<_, Error>(()).is_ok());
|
||||
|
||||
match lua
|
||||
@@ -358,9 +358,14 @@ fn test_error() -> Result<()> {
|
||||
_ => panic!("error not returned"),
|
||||
}
|
||||
|
||||
let test_pcall = globals.get::<_, Function>("test_pcall")?;
|
||||
test_pcall.call::<_, ()>(())?;
|
||||
|
||||
assert!(understand_recursion.call::<_, ()>(()).is_err());
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let understand_recursion = globals.get::<_, Function>("understand_recursion")?;
|
||||
assert!(understand_recursion.call::<_, ()>(()).is_err());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -947,6 +952,7 @@ fn test_application_data() -> Result<()> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn test_recursion() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
@@ -966,14 +972,16 @@ fn test_recursion() -> Result<()> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn test_too_many_returns() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
let f = lua.create_function(|_, ()| Ok(Variadic::from_iter(1..1000000)))?;
|
||||
assert!(f.call::<_, Vec<u32>>(()).is_err());
|
||||
assert!(f.call::<_, Variadic<u32>>(()).is_err());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn test_too_many_arguments() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
lua.load("function test(...) end").exec()?;
|
||||
@@ -988,6 +996,7 @@ fn test_too_many_arguments() -> Result<()> {
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "luajit"))]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn test_too_many_recursions() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
@@ -1001,6 +1010,7 @@ fn test_too_many_recursions() -> Result<()> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn test_too_many_binds() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
let globals = lua.globals();
|
||||
@@ -1022,6 +1032,7 @@ fn test_too_many_binds() -> Result<()> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn test_ref_stack_exhaustion() {
|
||||
match catch_unwind(AssertUnwindSafe(|| -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
@@ -1325,10 +1336,10 @@ fn test_warnings() -> Result<()> {
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "luajit")]
|
||||
#[should_panic]
|
||||
fn test_luajit_cdata() {
|
||||
fn test_luajit_cdata() -> Result<()> {
|
||||
let lua = unsafe { Lua::unsafe_new() };
|
||||
let _v: Result<Value> = lua
|
||||
|
||||
let cdata = lua
|
||||
.load(
|
||||
r#"
|
||||
local ffi = require("ffi")
|
||||
@@ -1341,11 +1352,17 @@ fn test_luajit_cdata() {
|
||||
return ptr
|
||||
"#,
|
||||
)
|
||||
.eval();
|
||||
.eval::<Value>()?;
|
||||
assert!(cdata.is_userdata() && cdata.is_cdata());
|
||||
assert_eq!(cdata.type_name(), "cdata");
|
||||
assert!(cdata.to_string()?.starts_with("cdata<void *>:"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "send")]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn test_send() {
|
||||
let lua = Lua::new();
|
||||
std::thread::spawn(move || {
|
||||
|
||||
+30
-2
@@ -506,6 +506,9 @@ fn test_functions() -> Result<()> {
|
||||
|
||||
#[test]
|
||||
fn test_fields() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
let globals = lua.globals();
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct MyUserData(i64);
|
||||
|
||||
@@ -533,8 +536,6 @@ fn test_fields() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
let lua = Lua::new();
|
||||
let globals = lua.globals();
|
||||
globals.set("ud", MyUserData(7))?;
|
||||
lua.load(
|
||||
r#"
|
||||
@@ -555,6 +556,33 @@ fn test_fields() -> Result<()> {
|
||||
)
|
||||
.exec()?;
|
||||
|
||||
// Case: fields + __index metamethod (function)
|
||||
struct MyUserData2(i64);
|
||||
|
||||
impl UserData for MyUserData2 {
|
||||
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field("z", 0);
|
||||
fields.add_field_method_get("x", |_, data| Ok(data.0));
|
||||
}
|
||||
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(MetaMethod::Index, |_, _, name: StdString| match &*name {
|
||||
"y" => Ok(Some(-1)),
|
||||
_ => Ok(None),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
globals.set("ud", MyUserData2(1))?;
|
||||
lua.load(
|
||||
r#"
|
||||
assert(ud.x == 1)
|
||||
assert(ud.y == -1)
|
||||
assert(ud.z == 0)
|
||||
"#,
|
||||
)
|
||||
.exec()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user