Compare commits

..

60 Commits

Author SHA1 Message Date
Alex Orlenko 9caf3542d9 v0.10.3 2025-01-27 21:33:51 +00:00
Alex Orlenko cb45db05fa Update README/CHANGELOG 2025-01-27 21:33:24 +00:00
Alex Orlenko d1cb2a9a96 mlua-sys: v0.6.7 2025-01-27 21:20:47 +00:00
Alex Orlenko aa3f6ba46c Fix prototype of new Luau compiler options and methods 2025-01-27 21:19:31 +00:00
Alex Orlenko cc57bed4c8 Update Luau to 0.657 2025-01-27 14:19:40 +00:00
Radiant b5d38ab2e3 Set Default for LuaValue to be nil. (#512) 2025-01-19 23:52:58 +00:00
Alex Orlenko cd4091f64d Allow exhaustive match on Value.
It was not possible because `ValueRef` variant in `Value::Other` was private.
Closes #502 and #503
2024-12-10 23:37:19 +00:00
Alex Orlenko 91e069a77e Optimize (and simplify) protected mode for Rust function calls 2024-12-07 00:04:12 +00:00
Alex Orlenko cacd3dc70f Add Table::set_safeenv method (Luau) 2024-12-04 10:40:49 +00:00
Alex Orlenko 6f6cda0099 v0.10.2 2024-12-01 13:04:06 +00:00
Alex Orlenko d51ce86142 mlua-sys: v0.6.6 2024-12-01 12:56:39 +00:00
Alex Orlenko aa061bce6f mlua_derive: v0.10.1 2024-12-01 12:55:50 +00:00
Alex Orlenko 1c6b6ad801 Fix tests 2024-12-01 12:54:35 +00:00
Evie 031854fa2a Switch proc-macro-error to proc-macro-error2 (#493) 2024-11-30 22:37:40 +00:00
Alex Orlenko 5fd96c7908 Don't run GC finalizers on ref thread.
If this happen then we don't have full access to the ref thread stack.
Fixes #491
2024-11-30 01:00:31 +00:00
Alex Orlenko 7a3f19b857 Ensure that buffer with Luau compiled code is always freed 2024-11-29 13:36:51 +00:00
Alex Orlenko 55a5d7ef10 Protect Lua(u) during chunk loading if memory limit is enforced
Relates to #488
2024-11-27 00:34:21 +00:00
Alex Orlenko af31dbd180 Use c string literal in few places 2024-11-25 22:53:59 +00:00
Alex Orlenko 9ae3cb0a7c Add doc about possible chunk name prefixes 2024-11-24 12:00:06 +00:00
Alex Orlenko fc1c80c142 Mark Chunk::wrap as non-public 2024-11-22 20:03:17 +00:00
Alex Orlenko ee7ced6334 Add Chunk::wrap method 2024-11-22 14:40:52 +00:00
Alex Orlenko d8307d0e4c Reduce visibility of Wrapped* structs 2024-11-22 13:08:57 +00:00
Alex Orlenko bf9fcc5aca Simplify WrappedString 2024-11-22 13:06:50 +00:00
Alex Orlenko 7ce6b97da9 Add String::wrap method to wrap arbitrary AsRef<[u8]> 2024-11-22 11:48:20 +00:00
Alex Orlenko 4891a6ac10 Fix utf-8 test when bstr v1.11 switchted to LowerHex 2024-11-17 18:01:05 +00:00
Alex Orlenko 4ef0d583fc impl Send for UserDataTypeId (in send mode) 2024-11-16 01:56:18 +00:00
cos 30b0122f5d Make build script work with OpenBSD
On OpenBSD the pkg-config names takes a minimalist format. No dash, and
no separator between the major and minor version number. Build script is
thus adapted to have also these in their set of alt_probes.

Signed-off-by: Alex Orlenko <zxteam@protonmail.com>
2024-11-16 01:52:43 +00:00
cos c31c72076f Make build script work with FreeBSD
On FreeBSD the pkg-config names takes the format with a dash, rather
than without (e.g. lua-5.4, not lua5.4). Thus adapt build script to
iterate over an array of alt_probes until finding a match.

Signed-off-by: Alex Orlenko <zxteam@protonmail.com>
2024-11-16 01:49:02 +00:00
Alex Orlenko 3bfaee4ecc Delay "any" userdata metatable creation until first instance 2024-11-16 01:47:04 +00:00
Alex Orlenko 89b68e2a24 Remove generic from RawLua::push_userdata_metatable.
This should help to reduce amount of generated code.
2024-11-16 01:44:17 +00:00
Alex Orlenko cbf805f492 Avoid ptr->usize->ptr conversion to comply strict provenance 2024-11-15 21:47:19 +00:00
Alex Orlenko c926327a6a v0.10.1 2024-11-09 20:06:04 +00:00
Alex Orlenko 7c099500d0 mlua-sys: v0.6.5 2024-11-09 14:30:33 +00:00
Alex Orlenko 8c889cc353 Add String::display method 2024-11-09 14:24:43 +00:00
Alex Orlenko 958abd050e Update String::to_string_lossy doc 2024-11-09 14:10:45 +00:00
Alex Orlenko 92a8203e1c Fix formatting 2024-11-09 13:58:06 +00:00
Alex Orlenko b34b90eca3 Fix wrong formatting table with string keys that are numbers 2024-11-09 13:51:55 +00:00
Alex Orlenko 7aad0adcb4 Update links to luau.org 2024-11-09 12:48:15 +00:00
Alex Orlenko a3cd25db7a Support Luau 0.650 native vector library 2024-11-09 12:44:00 +00:00
Alex Orlenko a4bfeb7752 clippy 2024-11-09 12:38:05 +00:00
vhyrro 0fda512938 feat(table): improve pretty-printing for simple tables and lists (#478) 2024-11-09 12:08:49 +00:00
Alex Orlenko 58e0661086 Merge Scope::attach_destructor into Scope::seal_userdata 2024-11-08 15:04:46 +00:00
Alex Orlenko c7094d470f Add Scope::create_any_userdata to create Lua objects from any non-static Rust types. 2024-11-07 19:44:18 +00:00
Alex Orlenko a7d0691e10 Add AnyUserData::destroy method 2024-11-07 16:12:20 +00:00
Alex Orlenko 05778fbe6f Don't store and use wrong main Lua state in module mode (Lua 5.1/JIT only).
When mlua module is loaded from a non-main coroutine we store a reference to it to use later.
If the coroutine is destroyed by GC we can pass a wrong pointer to Lua that will trigger a segfault.
Instead, set main_state as Option and use current (active) state if needed.
Relates to #479
2024-11-04 15:48:22 +00:00
Alex Orlenko b34d67ec41 Update Luau to 0.650 (luau0-src 0.11.1) 2024-11-03 14:49:12 +00:00
Alex Orlenko 46ee7ea772 Update tarpaulin.toml 2024-11-03 12:54:21 +00:00
Alex Orlenko 15738dda1f Update tarpaulin.toml to include userdata-wrappers 2024-11-03 12:19:22 +00:00
Alex Orlenko 1f32754f05 Relax UserDataBorrowRef restrictions to allow recursive calls 2024-11-03 12:18:00 +00:00
Alex Orlenko c2eab173c5 Add userdata-wrappers feature
This feature allow to opt into `impl UserData` for `Rc<T>`/`Arc<T>`/`Rc<RefCell<T>>`/`Arc<Mutex<T>>` where `T: UserData`
Close #470
2024-11-03 11:48:47 +00:00
Alex Orlenko bb311349ec Switch between shared and exclusive lock for UserDataRef depending if T: Sync or not. 2024-11-02 10:38:25 +00:00
Alex Orlenko 928e1d9221 Revert &Scope to &mut Scope 2024-10-31 18:41:47 +00:00
Alex Orlenko 5b8681dcf2 Add Scope::add_destructor to attach custom destructors 2024-10-31 14:35:21 +00:00
Alex Orlenko 4e9a17707b Fix tests 2024-10-31 09:23:03 +00:00
Alex Orlenko 6066089cc1 Skip setting Send/Sync in non-send mode for UserDataCell 2024-10-30 23:32:13 +00:00
Alex Orlenko a8d5f23818 Add Lua::try_app_data_ref and Lua::try_app_data_mut 2024-10-30 15:22:55 +00:00
Alex Orlenko 5ec4e0338a Add From<Vec> and Into<Vec> support to MultiValue and Variadic types 2024-10-30 13:03:08 +00:00
Alex Orlenko d27d1365b5 Update v0.10 release notes (add breaking changes) 2024-10-30 00:42:31 +00:00
Alex Orlenko 76b896edcc Fix attaching __gc metamethod
Bug introdused in ddebf56
2024-10-29 22:56:29 +00:00
Alex Orlenko ddebf56b41 Defer metatable return on userdata creation until the end
Relates to #477
2024-10-29 21:22:34 +00:00
53 changed files with 1843 additions and 604 deletions
+12 -12
View File
@@ -27,8 +27,8 @@ jobs:
- name: Build ${{ matrix.lua }} vendored
run: |
cargo build --features "${{ matrix.lua }},vendored"
cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow"
cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,send"
cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers"
cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send"
shell: bash
- name: Build ${{ matrix.lua }} pkg-config
if: ${{ matrix.os == 'ubuntu-latest' }}
@@ -51,7 +51,7 @@ jobs:
toolchain: stable
target: aarch64-apple-darwin
- name: Cross-compile
run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros"
run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
build_aarch64_cross_ubuntu:
name: Cross-compile to aarch64-unknown-linux-gnu
@@ -72,7 +72,7 @@ jobs:
sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross
shell: bash
- name: Cross-compile
run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros"
run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
shell: bash
build_armv7_cross_ubuntu:
@@ -94,7 +94,7 @@ jobs:
sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross
shell: bash
- name: Cross-compile
run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros"
run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
shell: bash
test:
@@ -123,8 +123,8 @@ jobs:
- name: Run ${{ matrix.lua }} tests
run: |
cargo test --features "${{ matrix.lua }},vendored"
cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow"
cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,send"
cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers"
cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send"
shell: bash
- name: Run compile tests (macos lua54)
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }}
@@ -154,8 +154,8 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: Run ${{ matrix.lua }} tests with address sanitizer
run: |
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,send" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
shell: bash
env:
RUSTFLAGS: -Z sanitizer=address
@@ -181,7 +181,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: Run ${{ matrix.lua }} tests with forced memory limit
run: |
cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros"
cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
shell: bash
env:
RUSTFLAGS: --cfg=force_memory_limit
@@ -254,7 +254,7 @@ jobs:
- name: Run ${{ matrix.lua }} tests
run: |
cargo test --tests --features "${{ matrix.lua }},vendored"
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros"
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers"
rustfmt:
name: Rustfmt
@@ -281,4 +281,4 @@ jobs:
- uses: giraffate/clippy-action@v1
with:
reporter: 'github-pr-review'
clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow"
clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
+31
View File
@@ -1,3 +1,34 @@
## v0.10.2 (Jan 27th, 2025)
- Set `Default` for `Value` to be `Nil`
- Allow exhaustive match on `Value` (#502)
- Add `Table::set_safeenv` method (Luau)
## v0.10.2 (Dec 1st, 2024)
- Switch proc-macro-error to proc-macro-error2 (#493)
- Do not allow Lua to run GC finalizers on ref thread (#491)
- Fix chunks loading in Luau when memory limit is enforced (#488)
- Added `String::wrap` method to wrap arbitrary `AsRef<[u8]>` into `impl IntoLua`
- Better FreeBSD/OpenBSD support (thanks to cos)
- Delay "any" userdata metatable creation until first instance is created (#482)
- Reduce amount of generated code for `UserData` (less generics)
## v0.10.1 (Nov 9th, 2024)
- Minimal Luau updated to 0.650
- Added Luau native vector library support (this can change behavior if you use `vector` function!)
- Added Lua `String::display` method
- Improved pretty-printing for Lua tables (#478)
- Added `Scope::create_any_userdata` to create Lua objects from any non-`'static` Rust types
- Added `AnyUserData::destroy` method
- New `userdata-wrappers` feature to `impl UserData` for `Rc<T>`/`Arc<T>`/`Rc<RefCell<T>>`/`Arc<Mutex<T>>` (similar to v0.9)
- `UserDataRef` in `send` mode now uses shared lock if `T: Sync` (and exclusive lock otherwise)
- Added `Scope::add_destructor` to attach custom destructors
- Added `Lua::try_app_data_ref` and `Lua::try_app_data_mut` methods
- Added `From<Vec>` and `Into<Vec>` support to `MultiValue` and `Variadic` types
- Bug fixes and improvements (#477 #479)
## v0.10.0 (Oct 25th, 2024)
Changes since v0.10.0-rc.1
+4 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.10.0" # remember to update mlua_derive
version = "0.10.3" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
rust-version = "1.79.0"
edition = "2021"
@@ -43,9 +43,10 @@ error-send = []
serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value"]
macros = ["mlua_derive/macros"]
anyhow = ["dep:anyhow", "error-send"]
userdata-wrappers = []
[dependencies]
mlua_derive = { version = "=0.10.0", optional = true, path = "mlua_derive" }
mlua_derive = { version = "=0.10.1", optional = true, path = "mlua_derive" }
bstr = { version = "1.0", features = ["std"], default-features = false }
either = "1.0"
num-traits = { version = "0.2.14" }
@@ -57,7 +58,7 @@ serde-value = { version = "0.7", optional = true }
parking_lot = { version = "0.12", features = ["arc_lock"] }
anyhow = { version = "1.0", optional = true }
ffi = { package = "mlua-sys", version = "0.6.4", path = "mlua-sys" }
ffi = { package = "mlua-sys", version = "0.6.6", path = "mlua-sys" }
[target.'cfg(unix)'.dependencies]
libloading = { version = "0.8", optional = true }
+5 -4
View File
@@ -31,7 +31,7 @@ Started as `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT
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
[Roblox Luau]: https://luau.org
## Usage
@@ -57,6 +57,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl
* `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework
* `macros`: enable procedural macros (such as `chunk!`)
* `anyhow`: enable `anyhow::Error` conversion into Lua
* `userdata-wrappers`: opt into `impl UserData` for `Rc<T>`/`Arc<T>`/`Rc<RefCell<T>>`/`Arc<Mutex<T>>` where `T: UserData`
[5.4]: https://www.lua.org/manual/5.4/manual.html
[5.3]: https://www.lua.org/manual/5.3/manual.html
@@ -132,7 +133,7 @@ Add to `Cargo.toml` :
``` toml
[dependencies]
mlua = { version = "0.10.0", features = ["lua54", "vendored"] }
mlua = { version = "0.10", features = ["lua54", "vendored"] }
```
`main.rs`
@@ -167,7 +168,7 @@ Add to `Cargo.toml` :
crate-type = ["cdylib"]
[dependencies]
mlua = { version = "0.10.0", features = ["lua54", "module"] }
mlua = { version = "0.10", features = ["lua54", "module"] }
```
`lib.rs` :
@@ -290,7 +291,7 @@ Please check the [Luau Sandboxing] page if you are interested in running untrust
`mlua` provides `Lua::sandbox` method for enabling sandbox mode (Luau only).
[Luau Sandboxing]: https://luau-lang.org/sandbox
[Luau Sandboxing]: https://luau.org/sandbox
## License
+61
View File
@@ -132,3 +132,64 @@ let f = lua.create_function(|_, ()| {
Ok(())
})?;
```
### Breaking changes
#### Scope changes
The following `Scope` methods were changed:
- Removed `Scope::create_any_userdata`
- `Scope::create_nonstatic_userdata` is renamed to `Scope::create_userdata`
Instead, scope has comprehensive support for borrowed userdata: `create_any_userdata_ref`, `create_any_userdata_ref_mut`, `create_userdata_ref`, `create_userdata_ref_mut`.
`UserDataRef` and `UserDataRefMut` are no longer acceptable for scoped userdata access as they require owned underlying data.
In mlua v0.9 this can cause read-after-free bug in some edge cases.
To temporarily borrow underlying data, the `AnyUserData::borrow_scoped` and `AnyUserData::borrow_mut_scoped` methods were introduced:
```rust
let data = "hello".to_string();
lua.scope(|scope| {
let ud = scope.create_any_userdata_ref(&data)?;
// We can only borrow scoped userdata using this method
ud.borrow_scoped::<String, ()>(|s| {
assert_eq!(s, "hello");
})?;
Ok(())
})?;
```
Those methods work for scoped and regular userdata objects (but still require `T: 'static`).
#### String changes
Since `mlua::String` holds a weak reference to Lua without any guarantees about the lifetime of the underlying data, getting a `&str` or `&[u8]` from it is no longer safe.
Lua instance can be destroyed while reference to the data is still alive:
```rust
let lua = Lua::new();
let s: mlua::String = lua.create_string("hello, world")?; // only weak reference to Lua!
let s_ref: &str = s.to_str()?; // this is not safe!
drop(lua);
println!("{s_ref}"); // use after free!
```
To solve this issue, return types of `mlua::String::to_str` and `mlua::String::as_bytes` methods changed to `BorrowedStr` and `BorrowedBytes` respectively.
These new types hold a strong reference to the Lua instance and can be safely converted to `&str` or `&[u8]`:
```rust
let lua = Lua::new();
let s: mlua::String = lua.create_string("hello, world")?;
let s_ref: mlua::BorrowedStr = s.to_str()?; // The strong reference to Lua is held here
drop(lua);
println!("{s_ref}"); // ok
```
The good news is that `BorrowedStr` implements `Deref<Target = str>`/`AsRef<str>` as well as `Display`, `Debug`, `Eq`, `PartialEq` and other traits for easy usage.
The same applies to `BorrowedBytes`.
Unfortunately, `mlua::String::to_string_lossy` cannot return `Cow<'a, str>` anymore, because it requires a strong reference to Lua. It now returns Rust `String` instead.
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua-sys"
version = "0.6.4"
version = "0.6.7"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
rust-version = "1.71"
edition = "2021"
@@ -40,7 +40,7 @@ cfg-if = "1.0"
pkg-config = "0.3.17"
lua-src = { version = ">= 547.0.0, < 547.1.0", optional = true }
luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true }
luau0-src = { version = "0.11.0", optional = true }
luau0-src = { version = "0.12.0", optional = true }
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] }
+19 -9
View File
@@ -32,15 +32,19 @@ pub fn probe_lua() {
// Find using `pkg-config`
#[cfg(feature = "lua54")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.4", "5.5", Some("lua5.4"), "5.4");
let (incl_bound, excl_bound, alt_probe, ver) =
("5.4", "5.5", ["lua5.4", "lua-5.4", "lua54"], "5.4");
#[cfg(feature = "lua53")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.3", "5.4", Some("lua5.3"), "5.3");
let (incl_bound, excl_bound, alt_probe, ver) =
("5.3", "5.4", ["lua5.3", "lua-5.3", "lua53"], "5.3");
#[cfg(feature = "lua52")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.2", "5.3", Some("lua5.2"), "5.2");
let (incl_bound, excl_bound, alt_probe, ver) =
("5.2", "5.3", ["lua5.2", "lua-5.2", "lua52"], "5.2");
#[cfg(feature = "lua51")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.1", "5.2", Some("lua5.1"), "5.1");
let (incl_bound, excl_bound, alt_probe, ver) =
("5.1", "5.2", ["lua5.1", "lua-5.1", "lua51"], "5.1");
#[cfg(feature = "luajit")]
let (incl_bound, excl_bound, alt_probe, ver) = ("2.0.4", "2.2", None, "JIT");
let (incl_bound, excl_bound, alt_probe, ver) = ("2.0.4", "2.2", [], "JIT");
#[rustfmt::skip]
let mut lua = pkg_config::Config::new()
@@ -48,10 +52,16 @@ pub fn probe_lua() {
.cargo_metadata(true)
.probe(if cfg!(feature = "luajit") { "luajit" } else { "lua" });
if lua.is_err() && alt_probe.is_some() {
lua = pkg_config::Config::new()
.cargo_metadata(true)
.probe(alt_probe.unwrap());
if lua.is_err() {
for pkg in alt_probe {
lua = pkg_config::Config::new()
.cargo_metadata(true)
.probe(pkg);
if lua.is_ok() {
break;
}
}
}
lua.unwrap_or_else(|err| panic!("cannot find Lua{ver} using `pkg-config`: {err}"));
+13 -2
View File
@@ -326,12 +326,16 @@ pub unsafe fn luaL_loadbufferenv(
mut size: usize,
name: *const c_char,
mode: *const c_char,
env: c_int,
mut env: c_int,
) -> c_int {
extern "C" {
fn free(p: *mut c_void);
}
unsafe extern "C-unwind" fn data_dtor(data: *mut c_void) {
free(*(data as *mut *mut c_char) as *mut c_void);
}
let chunk_is_text = size == 0 || (*data as u8) >= b'\t';
if !mode.is_null() {
let modeb = CStr::from_ptr(mode).to_bytes();
@@ -345,9 +349,16 @@ pub unsafe fn luaL_loadbufferenv(
}
if chunk_is_text {
if env < 0 {
env -= 1;
}
let data_ud = lua_newuserdatadtor(L, mem::size_of::<*mut c_char>(), data_dtor) as *mut *mut c_char;
let data = luau_compile_(data, size, ptr::null_mut(), &mut size);
ptr::write(data_ud, data);
// By deferring the `free(data)` to the userdata destructor, we ensure that
// even if `luau_load` throws an error, the `data` is still released.
let ok = luau_load(L, name, data, size, env) == 0;
free(data as *mut c_void);
lua_replace(L, -2); // replace data with the result
if !ok {
return LUA_ERRSYNTAX;
}
+1
View File
@@ -185,6 +185,7 @@ extern "C-unwind" {
pub fn lua_pushlightuserdatatagged(L: *mut lua_State, p: *mut c_void, tag: c_int);
pub fn lua_newuserdatatagged(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void;
pub fn lua_newuserdatataggedwithmetatable(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;
+51
View File
@@ -1,5 +1,6 @@
//! Contains definitions from `luacode.h`.
use std::marker::{PhantomData, PhantomPinned};
use std::os::raw::{c_char, c_int, c_void};
use std::{ptr, slice};
@@ -15,6 +16,10 @@ pub struct lua_CompileOptions {
pub vectorType: *const c_char,
pub mutableGlobals: *const *const c_char,
pub userdataTypes: *const *const c_char,
pub librariesWithKnownMembers: *const *const c_char,
pub libraryMemberTypeCallback: Option<lua_LibraryMemberTypeCallback>,
pub libraryMemberConstantCallback: Option<lua_LibraryMemberConstantCallback>,
pub disabledBuiltins: *const *const c_char,
}
impl Default for lua_CompileOptions {
@@ -29,10 +34,56 @@ impl Default for lua_CompileOptions {
vectorType: ptr::null(),
mutableGlobals: ptr::null(),
userdataTypes: ptr::null(),
librariesWithKnownMembers: ptr::null(),
libraryMemberTypeCallback: None,
libraryMemberConstantCallback: None,
disabledBuiltins: ptr::null(),
}
}
}
#[repr(C)]
pub struct lua_CompileConstant {
_data: [u8; 0],
_marker: PhantomData<(*mut u8, PhantomPinned)>,
}
/// Type table tags
#[doc(hidden)]
#[repr(i32)]
#[non_exhaustive]
pub enum luau_BytecodeType {
Nil = 0,
Boolean,
Number,
String,
Table,
Function,
Thread,
UserData,
Vector,
Buffer,
Any = 15,
}
pub type lua_LibraryMemberTypeCallback =
unsafe extern "C-unwind" fn(library: *const c_char, member: *const c_char) -> c_int;
pub type lua_LibraryMemberConstantCallback = unsafe extern "C-unwind" fn(
library: *const c_char,
member: *const c_char,
constant: *mut lua_CompileConstant,
);
extern "C" {
pub fn luau_set_compile_constant_nil(cons: *mut lua_CompileConstant);
pub fn luau_set_compile_constant_boolean(cons: *mut lua_CompileConstant, b: c_int);
pub fn luau_set_compile_constant_number(cons: *mut lua_CompileConstant, n: f64);
pub fn luau_set_compile_constant_vector(cons: *mut lua_CompileConstant, x: f32, y: f32, z: f32, w: f32);
pub fn luau_set_compile_constant_string(cons: *mut lua_CompileConstant, s: *const c_char, l: usize);
}
extern "C-unwind" {
#[link_name = "luau_compile"]
pub fn luau_compile_(
+2
View File
@@ -13,6 +13,7 @@ pub const LUA_BUFFERLIBNAME: &str = "buffer";
pub const LUA_UTF8LIBNAME: &str = "utf8";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_VECLIBNAME: &str = "vector";
extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
@@ -25,6 +26,7 @@ extern "C-unwind" {
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;
pub fn luaopen_vector(L: *mut lua_State) -> c_int;
// open all builtin libraries
pub fn luaL_openlibs(L: *mut lua_State);
+3 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua_derive"
version = "0.10.0"
version = "0.10.1"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2021"
description = "Procedural macros for the mlua crate."
@@ -12,12 +12,12 @@ license = "MIT"
proc-macro = true
[features]
macros = ["proc-macro-error", "itertools", "regex", "once_cell"]
macros = ["proc-macro-error2", "itertools", "regex", "once_cell"]
[dependencies]
quote = "1.0"
proc-macro2 = { version = "1.0", features = ["span-locations"] }
proc-macro-error = { version = "1.0", optional = true }
proc-macro-error2 = { version = "2.0.1", optional = true }
syn = { version = "2.0", features = ["full"] }
itertools = { version = "0.13", optional = true }
regex = { version = "1.4", optional = true }
+1 -1
View File
@@ -7,7 +7,7 @@ use syn::{parse_macro_input, ItemFn, LitStr, Result};
#[cfg(feature = "macros")]
use {
crate::chunk::Chunk, proc_macro::TokenTree, proc_macro2::TokenStream as TokenStream2,
proc_macro_error::proc_macro_error,
proc_macro_error2::proc_macro_error,
};
#[derive(Default)]
+1 -1
View File
@@ -74,7 +74,7 @@ fn parse_pos(span: &Span) -> Option<(usize, usize)> {
fn fallback_span_pos(span: &Span) -> (Pos, Pos) {
let (start, end) = match parse_pos(span) {
Some(v) => v,
None => proc_macro_error::abort_call_site!("Cannot retrieve span information; please use nightly"),
None => proc_macro_error2::abort_call_site!("Cannot retrieve span information; please use nightly"),
};
(Pos::new(1, start), Pos::new(1, end))
}
+42 -2
View File
@@ -2,6 +2,8 @@ use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::CString;
use std::io::Result as IoResult;
use std::marker::PhantomData;
use std::panic::Location;
use std::path::{Path, PathBuf};
use std::string::String as StdString;
@@ -9,13 +11,16 @@ use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::{Lua, WeakLua};
use crate::table::Table;
use crate::traits::{FromLuaMulti, IntoLuaMulti};
use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::value::Value;
/// Trait for types [loadable by Lua] and convertible to a [`Chunk`]
///
/// [loadable by Lua]: https://www.lua.org/manual/5.4/manual.html#3.3.2
pub trait AsChunk<'a> {
/// Returns optional chunk name
///
/// See [`Chunk::set_name`] for possible name prefixes.
fn name(&self) -> Option<StdString> {
None
}
@@ -301,8 +306,13 @@ impl Compiler {
}
}
impl<'a> Chunk<'a> {
impl Chunk<'_> {
/// Sets the name of this chunk, which results in more informative error traces.
///
/// Possible name prefixes:
/// - `@` - file path (when truncation is needed, the end of the file path is kept, as this is
/// more useful for identifying the file)
/// - `=` - custom chunk name (when truncation is needed, the beginning of the name is kept)
pub fn set_name(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
self
@@ -561,3 +571,33 @@ impl<'a> Chunk<'a> {
buf
}
}
struct WrappedChunk<'a, T: AsChunk<'a>> {
chunk: T,
caller: &'static Location<'static>,
_marker: PhantomData<&'a T>,
}
impl<'a> Chunk<'a> {
/// Wraps a chunk of Lua code, returning an opaque type that implements [`IntoLua`] trait.
///
/// The resulted `IntoLua` implementation will convert the chunk into a Lua function without
/// executing it.
#[doc(hidden)]
#[track_caller]
pub fn wrap(chunk: impl AsChunk<'a> + 'a) -> impl IntoLua + 'a {
WrappedChunk {
chunk,
caller: Location::caller(),
_marker: PhantomData,
}
}
}
impl<'a, T: AsChunk<'a>> IntoLua for WrappedChunk<'a, T> {
fn into_lua(self, lua: &Lua) -> Result<Value> {
lua.load_with_location(self.chunk, self.caller)
.into_function()
.map(Value::Function)
}
}
+2 -2
View File
@@ -509,10 +509,10 @@ impl Function {
}
}
pub(crate) struct WrappedFunction(pub(crate) Callback);
struct WrappedFunction(pub(crate) Callback);
#[cfg(feature = "async")]
pub(crate) struct WrappedAsyncFunction(pub(crate) AsyncCallback);
struct WrappedAsyncFunction(pub(crate) AsyncCallback);
impl Function {
/// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`]
+1
View File
@@ -65,6 +65,7 @@
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(not(send), allow(clippy::arc_with_non_send_sync))]
#[macro_use]
mod macros;
+1 -17
View File
@@ -1,5 +1,5 @@
use std::ffi::CStr;
use std::os::raw::{c_float, c_int};
use std::os::raw::c_int;
use crate::error::Result;
use crate::state::Lua;
@@ -11,7 +11,6 @@ impl Lua {
let globals = self.globals();
globals.raw_set("collectgarbage", self.create_c_function(lua_collectgarbage)?)?;
globals.raw_set("vector", self.create_c_function(lua_vector)?)?;
// Set `_VERSION` global to include version number
// The environment variable `LUAU_VERSION` set by the build script
@@ -65,21 +64,6 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
}
}
// 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;
let y = ffi::luaL_checknumber(state, 2) as c_float;
let z = ffi::luaL_checknumber(state, 3) as c_float;
#[cfg(feature = "luau-vector4")]
let w = ffi::luaL_checknumber(state, 4) as c_float;
#[cfg(not(feature = "luau-vector4"))]
ffi::lua_pushvector(state, x, y, z);
#[cfg(feature = "luau-vector4")]
ffi::lua_pushvector(state, x, y, z, w);
1
}
pub(crate) use package::register_package_module;
mod package;
+62 -18
View File
@@ -124,6 +124,23 @@ impl MultiValue {
MultiValue(VecDeque::with_capacity(capacity))
}
/// Creates a `MultiValue` container from vector of values.
///
/// This methods needs *O*(*n*) data movement if the circular buffer doesn't happen to be at the
/// beginning of the allocation.
#[inline]
pub fn from_vec(vec: Vec<Value>) -> MultiValue {
vec.into()
}
/// Consumes the `MultiValue` and returns a vector of values.
///
/// This methods works in *O*(1) time and does not allocate any additional memory.
#[inline]
pub fn into_vec(self) -> Vec<Value> {
self.into()
}
#[inline]
pub(crate) fn from_lua_iter<T: IntoLua>(lua: &Lua, iter: impl IntoIterator<Item = T>) -> Result<Self> {
let iter = iter.into_iter();
@@ -135,6 +152,20 @@ impl MultiValue {
}
}
impl From<Vec<Value>> for MultiValue {
#[inline]
fn from(value: Vec<Value>) -> Self {
MultiValue(value.into())
}
}
impl From<MultiValue> for Vec<Value> {
#[inline]
fn from(value: MultiValue) -> Self {
value.0.into()
}
}
impl FromIterator<Value> for MultiValue {
#[inline]
fn from_iter<I: IntoIterator<Item = Value>>(iter: I) -> Self {
@@ -203,7 +234,7 @@ impl FromLuaMulti for MultiValue {
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone)]
#[derive(Default, Debug, Clone)]
pub struct Variadic<T>(Vec<T>);
impl<T> Variadic<T> {
@@ -211,11 +242,38 @@ impl<T> Variadic<T> {
pub const fn new() -> Variadic<T> {
Variadic(Vec::new())
}
/// Creates an empty `Variadic` container with space for at least `capacity` elements.
pub fn with_capacity(capacity: usize) -> Variadic<T> {
Variadic(Vec::with_capacity(capacity))
}
}
impl<T> Default for Variadic<T> {
fn default() -> Variadic<T> {
const { Variadic::new() }
impl<T> Deref for Variadic<T> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for Variadic<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T> From<Vec<T>> for Variadic<T> {
#[inline]
fn from(vec: Vec<T>) -> Self {
Variadic(vec)
}
}
impl<T> From<Variadic<T>> for Vec<T> {
#[inline]
fn from(value: Variadic<T>) -> Self {
value.0
}
}
@@ -234,20 +292,6 @@ impl<T> IntoIterator for Variadic<T> {
}
}
impl<T> Deref for Variadic<T> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for Variadic<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T: IntoLua> IntoLuaMulti for Variadic<T> {
#[inline]
fn into_lua_multi(self, lua: &Lua) -> Result<MultiValue> {
+112 -50
View File
@@ -1,7 +1,6 @@
use std::cell::RefCell;
use std::marker::PhantomData;
use std::mem;
use std::os::raw::c_void;
use crate::error::{Error, Result};
use crate::function::Function;
@@ -19,7 +18,9 @@ use crate::util::{
/// See [`Lua::scope`] for more details.
pub struct Scope<'scope, 'env: 'scope> {
lua: LuaGuard,
// Internal destructors run first, then user destructors (based on the declaration order)
destructors: Destructors<'env>,
user_destructors: UserDestructors<'env>,
_scope_invariant: PhantomData<&'scope mut &'scope ()>,
_env_invariant: PhantomData<&'env mut &'env ()>,
}
@@ -29,11 +30,14 @@ type DestructorCallback<'a> = Box<dyn FnOnce(&RawLua, ValueRef) -> Vec<Box<dyn F
// Implement Drop on Destructors instead of Scope to avoid compilation error
struct Destructors<'a>(RefCell<Vec<(ValueRef, DestructorCallback<'a>)>>);
struct UserDestructors<'a>(RefCell<Vec<Box<dyn FnOnce() + 'a>>>);
impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
pub(crate) fn new(lua: LuaGuard) -> Self {
Scope {
lua,
destructors: Destructors(RefCell::new(Vec::new())),
user_destructors: UserDestructors(RefCell::new(Vec::new())),
_scope_invariant: PhantomData,
_env_invariant: PhantomData,
}
@@ -84,11 +88,9 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
where
T: UserData + 'static,
{
unsafe {
let ud = self.lua.make_userdata(UserDataStorage::new_ref(data))?;
self.seal_userdata::<T>(&ud)?;
Ok(ud)
}
let ud = unsafe { self.lua.make_userdata(UserDataStorage::new_ref(data)) }?;
self.seal_userdata::<T>(&ud);
Ok(ud)
}
/// Creates a Lua userdata object from a mutable reference to custom userdata type.
@@ -100,11 +102,9 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
where
T: UserData + 'static,
{
unsafe {
let ud = self.lua.make_userdata(UserDataStorage::new_ref_mut(data))?;
self.seal_userdata::<T>(&ud)?;
Ok(ud)
}
let ud = unsafe { self.lua.make_userdata(UserDataStorage::new_ref_mut(data)) }?;
self.seal_userdata::<T>(&ud);
Ok(ud)
}
/// Creates a Lua userdata object from a reference to custom Rust type.
@@ -118,11 +118,9 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
where
T: 'static,
{
unsafe {
let ud = self.lua.make_any_userdata(UserDataStorage::new_ref(data))?;
self.seal_userdata::<T>(&ud)?;
Ok(ud)
}
let ud = unsafe { self.lua.make_any_userdata(UserDataStorage::new_ref(data)) }?;
self.seal_userdata::<T>(&ud);
Ok(ud)
}
/// Creates a Lua userdata object from a mutable reference to custom Rust type.
@@ -134,11 +132,9 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
where
T: 'static,
{
unsafe {
let ud = self.lua.make_any_userdata(UserDataStorage::new_ref_mut(data))?;
self.seal_userdata::<T>(&ud)?;
Ok(ud)
}
let ud = unsafe { self.lua.make_any_userdata(UserDataStorage::new_ref_mut(data)) }?;
self.seal_userdata::<T>(&ud);
Ok(ud)
}
/// Creates a Lua userdata object from a custom userdata type.
@@ -167,7 +163,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
// // We don't write the data to the userdata until pushing the metatable
// We don't write the data to the userdata until pushing the metatable
let protect = !self.lua.unlikely_memory_error();
#[cfg(feature = "luau")]
let ud_ptr = {
@@ -178,9 +174,9 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
let ud_ptr = util::push_uninit_userdata::<UserDataStorage<T>>(state, protect)?;
// Push the metatable and register it with no TypeId
let mut registry = UserDataRegistry::new_unique(ud_ptr as *const c_void);
let mut registry = UserDataRegistry::new_unique(self.lua.lua(), ud_ptr as *mut _);
T::register(&mut registry);
self.lua.push_userdata_metatable(registry)?;
self.lua.push_userdata_metatable(registry.into_raw())?;
let mt_ptr = ffi::lua_topointer(state, -1);
self.lua.register_userdata_metatable(mt_ptr, None);
@@ -190,31 +186,84 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
ffi::lua_setmetatable(state, -2);
let ud = AnyUserData(self.lua.pop_ref());
let destructor: DestructorCallback = Box::new(|rawlua, vref| {
let state = rawlua.state();
let _sg = StackGuard::new(state);
assert_stack(state, 2);
// Check that userdata is valid (very likely)
if rawlua.push_userdata_ref(&vref).is_err() {
return vec![];
}
// Deregister metatable
let mt_ptr = get_metatable_ptr(state, -1);
rawlua.deregister_userdata_metatable(mt_ptr);
let ud = take_userdata::<UserDataStorage<T>>(state);
vec![Box::new(move || drop(ud))]
});
self.destructors.0.borrow_mut().push((ud.0.clone(), destructor));
self.seal_userdata::<T>(&ud);
Ok(ud)
}
}
/// Creates a Lua userdata object from a custom Rust type.
///
/// Since the Rust type is not required to be static and implement [`UserData`] trait,
/// you need to provide a function to register fields or methods for the object.
///
/// See also [`Scope::create_userdata`] for more details about non-static limitations.
pub fn create_any_userdata<T>(
&'scope self,
data: T,
register: impl FnOnce(&mut UserDataRegistry<T>),
) -> Result<AnyUserData>
where
T: 'env,
{
let state = self.lua.state();
let ud = unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
// We don't write the data to the userdata until pushing the metatable
let protect = !self.lua.unlikely_memory_error();
#[cfg(feature = "luau")]
let ud_ptr = {
let data = UserDataStorage::new_scoped(data);
util::push_userdata::<UserDataStorage<T>>(state, data, protect)?
};
#[cfg(not(feature = "luau"))]
let ud_ptr = util::push_uninit_userdata::<UserDataStorage<T>>(state, protect)?;
// Push the metatable and register it with no TypeId
let mut registry = UserDataRegistry::new_unique(self.lua.lua(), ud_ptr as *mut _);
register(&mut registry);
self.lua.push_userdata_metatable(registry.into_raw())?;
let mt_ptr = ffi::lua_topointer(state, -1);
self.lua.register_userdata_metatable(mt_ptr, None);
// Write data to the pointer and attach metatable
#[cfg(not(feature = "luau"))]
std::ptr::write(ud_ptr, UserDataStorage::new_scoped(data));
ffi::lua_setmetatable(state, -2);
AnyUserData(self.lua.pop_ref())
};
self.seal_userdata::<T>(&ud);
Ok(ud)
}
/// Adds a destructor function to be run when the scope ends.
///
/// This functionality is useful for cleaning up any resources after the scope ends.
///
/// # Example
///
/// ```rust
/// # use mlua::{Error, Lua, Result};
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
/// let ud = lua.create_any_userdata(String::from("hello"))?;
/// lua.scope(|scope| {
/// scope.add_destructor(|| {
/// _ = ud.take::<String>();
/// });
/// // Run the code that uses `ud` here
/// Ok(())
/// })?;
/// assert!(matches!(ud.borrow::<String>(), Err(Error::UserDataDestructed)));
/// # Ok(())
/// # }
pub fn add_destructor(&'scope self, destructor: impl FnOnce() + 'env) {
self.user_destructors.0.borrow_mut().push(Box::new(destructor));
}
unsafe fn create_callback(&'scope self, f: ScopedCallback<'scope>) -> Result<Function> {
let f = mem::transmute::<ScopedCallback, Callback>(f);
let f = self.lua.create_callback(f)?;
@@ -233,23 +282,27 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
}
/// Shortens the lifetime of the userdata to the lifetime of the scope.
unsafe fn seal_userdata<T: 'static>(&self, ud: &AnyUserData) -> Result<()> {
let destructor: DestructorCallback = Box::new(|rawlua, vref| {
fn seal_userdata<T: 'env>(&self, ud: &AnyUserData) {
let destructor: DestructorCallback = Box::new(|rawlua, vref| unsafe {
let state = rawlua.state();
let _sg = StackGuard::new(state);
assert_stack(state, 2);
// Ensure that userdata is not destructed
if rawlua.push_userdata_ref(&vref).is_err() {
return vec![];
match rawlua.push_userdata_ref(&vref) {
Ok(Some(_)) => {}
Ok(None) => {
// Deregister metatable
let mt_ptr = get_metatable_ptr(state, -1);
rawlua.deregister_userdata_metatable(mt_ptr);
}
Err(_) => return vec![],
}
let data = take_userdata::<UserDataStorage<T>>(state);
vec![Box::new(move || drop(data))]
});
self.destructors.0.borrow_mut().push((ud.0.clone(), destructor));
Ok(())
}
}
@@ -271,3 +324,12 @@ impl Drop for Destructors<'_> {
}
}
}
impl Drop for UserDestructors<'_> {
fn drop(&mut self) {
let destructors = mem::take(&mut *self.0.borrow_mut());
for destructor in destructors {
destructor();
}
}
}
+61 -34
View File
@@ -1,5 +1,5 @@
use std::any::TypeId;
use std::cell::RefCell;
use std::cell::{BorrowError, BorrowMutError, RefCell};
use std::marker::PhantomData;
use std::ops::Deref;
use std::os::raw::c_int;
@@ -485,7 +485,7 @@ impl Lua {
let lua = self.lock();
unsafe {
if (*lua.extra.get()).sandboxed != enabled {
let state = lua.main_state;
let state = lua.main_state();
check_stack(state, 3)?;
protect_lua!(state, 0, 0, |state| {
if enabled {
@@ -562,10 +562,10 @@ impl Lua {
unsafe {
let state = lua.state();
ffi::lua_sethook(state, None, 0, 0);
match crate::util::get_main_state(lua.main_state) {
Some(main_state) if !ptr::eq(state, main_state) => {
match lua.main_state {
Some(main_state) if state != main_state.as_ptr() => {
// If main_state is different from state, remove hook from it too
ffi::lua_sethook(main_state, None, 0, 0);
ffi::lua_sethook(main_state.as_ptr(), None, 0, 0);
}
_ => {}
};
@@ -654,7 +654,7 @@ impl Lua {
let lua = self.lock();
unsafe {
(*lua.extra.get()).interrupt_callback = Some(Rc::new(callback));
(*ffi::lua_callbacks(lua.main_state)).interrupt = Some(interrupt_proc);
(*ffi::lua_callbacks(lua.main_state())).interrupt = Some(interrupt_proc);
}
}
@@ -667,7 +667,7 @@ impl Lua {
let lua = self.lock();
unsafe {
(*lua.extra.get()).interrupt_callback = None;
(*ffi::lua_callbacks(lua.main_state)).interrupt = None;
(*ffi::lua_callbacks(lua.main_state())).interrupt = None;
}
}
@@ -697,10 +697,9 @@ impl Lua {
}
let lua = self.lock();
let state = lua.main_state;
unsafe {
(*lua.extra.get()).warn_callback = Some(Box::new(callback));
ffi::lua_setwarnf(state, Some(warn_proc), lua.extra.get() as *mut c_void);
ffi::lua_setwarnf(lua.state(), Some(warn_proc), lua.extra.get() as *mut c_void);
}
}
@@ -715,7 +714,7 @@ impl Lua {
let lua = self.lock();
unsafe {
(*lua.extra.get()).warn_callback = None;
ffi::lua_setwarnf(lua.main_state, None, ptr::null_mut());
ffi::lua_setwarnf(lua.state(), None, ptr::null_mut());
}
}
@@ -767,13 +766,14 @@ impl Lua {
/// Returns the amount of memory (in bytes) currently used inside this Lua state.
pub fn used_memory(&self) -> usize {
let lua = self.lock();
let state = lua.main_state();
unsafe {
match MemoryState::get(lua.main_state) {
match MemoryState::get(state) {
mem_state if !mem_state.is_null() => (*mem_state).used_memory(),
_ => {
// Get data from the Lua GC
let used_kbytes = ffi::lua_gc(lua.main_state, ffi::LUA_GCCOUNT, 0);
let used_kbytes_rem = ffi::lua_gc(lua.main_state, ffi::LUA_GCCOUNTB, 0);
let used_kbytes = ffi::lua_gc(state, ffi::LUA_GCCOUNT, 0);
let used_kbytes_rem = ffi::lua_gc(state, ffi::LUA_GCCOUNTB, 0);
(used_kbytes as usize) * 1024 + (used_kbytes_rem as usize)
}
}
@@ -790,7 +790,7 @@ impl Lua {
pub fn set_memory_limit(&self, limit: usize) -> Result<usize> {
let lua = self.lock();
unsafe {
match MemoryState::get(lua.main_state) {
match MemoryState::get(lua.state()) {
mem_state if !mem_state.is_null() => Ok((*mem_state).set_memory_limit(limit)),
_ => Err(Error::MemoryControlNotAvailable),
}
@@ -803,19 +803,19 @@ impl Lua {
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
pub fn gc_is_running(&self) -> bool {
let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCISRUNNING, 0) != 0 }
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCISRUNNING, 0) != 0 }
}
/// Stop the Lua GC from running
pub fn gc_stop(&self) {
let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCSTOP, 0) };
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCSTOP, 0) };
}
/// Restarts the Lua GC if it is not running
pub fn gc_restart(&self) {
let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCRESTART, 0) };
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCRESTART, 0) };
}
/// Perform a full garbage-collection cycle.
@@ -824,9 +824,10 @@ impl Lua {
/// objects. Once to finish the current gc cycle, and once to start and finish the next cycle.
pub fn gc_collect(&self) -> Result<()> {
let lua = self.lock();
let state = lua.main_state();
unsafe {
check_stack(lua.main_state, 2)?;
protect_lua!(lua.main_state, 0, 0, fn(state) ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0))
check_stack(state, 2)?;
protect_lua!(state, 0, 0, fn(state) ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0))
}
}
@@ -843,9 +844,10 @@ impl Lua {
/// finished a collection cycle.
pub fn gc_step_kbytes(&self, kbytes: c_int) -> Result<bool> {
let lua = self.lock();
let state = lua.main_state();
unsafe {
check_stack(lua.main_state, 3)?;
protect_lua!(lua.main_state, 0, 0, |state| {
check_stack(state, 3)?;
protect_lua!(state, 0, 0, |state| {
ffi::lua_gc(state, ffi::LUA_GCSTEP, kbytes) != 0
})
}
@@ -861,11 +863,12 @@ impl Lua {
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5
pub fn gc_set_pause(&self, pause: c_int) -> c_int {
let lua = self.lock();
let state = lua.main_state();
unsafe {
#[cfg(not(feature = "luau"))]
return ffi::lua_gc(lua.main_state, ffi::LUA_GCSETPAUSE, pause);
return ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause);
#[cfg(feature = "luau")]
return ffi::lua_gc(lua.main_state, ffi::LUA_GCSETGOAL, pause);
return ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause);
}
}
@@ -877,7 +880,7 @@ impl Lua {
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5
pub fn gc_set_step_multiplier(&self, step_multiplier: c_int) -> c_int {
let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCSETSTEPMUL, step_multiplier) }
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCSETSTEPMUL, step_multiplier) }
}
/// Changes the collector to incremental mode with the given parameters.
@@ -888,7 +891,7 @@ impl Lua {
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5.1
pub fn gc_inc(&self, pause: c_int, step_multiplier: c_int, step_size: c_int) -> GCMode {
let lua = self.lock();
let state = lua.main_state;
let state = lua.main_state();
#[cfg(any(
feature = "lua53",
@@ -941,7 +944,7 @@ impl Lua {
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
pub fn gc_gen(&self, minor_multiplier: c_int, major_multiplier: c_int) -> GCMode {
let lua = self.lock();
let state = lua.main_state;
let state = lua.main_state();
let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCGEN, minor_multiplier, major_multiplier) };
match prev_mode {
ffi::LUA_GCGEN => GCMode::Generational,
@@ -1000,10 +1003,17 @@ impl Lua {
/// [`Chunk::exec`]: crate::Chunk::exec
#[track_caller]
pub fn load<'a>(&self, chunk: impl AsChunk<'a>) -> Chunk<'a> {
let caller = Location::caller();
self.load_with_location(chunk, Location::caller())
}
pub(crate) fn load_with_location<'a>(
&self,
chunk: impl AsChunk<'a>,
location: &'static Location<'static>,
) -> Chunk<'a> {
Chunk {
lua: self.weak(),
name: chunk.name().unwrap_or_else(|| caller.to_string()),
name: chunk.name().unwrap_or_else(|| location.to_string()),
env: chunk.environment(self),
mode: chunk.mode(),
source: chunk.source(),
@@ -1025,7 +1035,7 @@ impl Lua {
///
/// Requires `feature = "luau"`
///
/// [buffer]: https://luau-lang.org/library#buffer-library
/// [buffer]: https://luau.org/library#buffer-library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result<Buffer> {
@@ -1033,8 +1043,8 @@ impl Lua {
let state = lua.state();
unsafe {
if lua.unlikely_memory_error() {
crate::util::push_buffer(lua.ref_thread(), buf.as_ref(), false)?;
return Ok(Buffer(lua.pop_ref_thread()));
crate::util::push_buffer(state, buf.as_ref(), false)?;
return Ok(Buffer(lua.pop_ref()));
}
let _sg = StackGuard::new(state);
@@ -1303,7 +1313,7 @@ impl Lua {
/// This methods provides a way to add fields or methods to userdata objects of a type `T`.
pub fn register_userdata_type<T: 'static>(&self, f: impl FnOnce(&mut UserDataRegistry<T>)) -> Result<()> {
let type_id = TypeId::of::<T>();
let mut registry = UserDataRegistry::new(type_id);
let mut registry = UserDataRegistry::new(self, type_id);
f(&mut registry);
let lua = self.lock();
@@ -1313,8 +1323,8 @@ impl Lua {
ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, table_id);
}
// Register the type
lua.create_userdata_metatable(registry)?;
// Add to "pending" registration map
((*lua.extra.get()).pending_userdata_reg).insert(type_id, registry.into_raw());
}
Ok(())
}
@@ -1472,6 +1482,7 @@ impl Lua {
&self,
f: impl for<'scope> FnOnce(&'scope mut Scope<'scope, 'env>) -> Result<R>,
) -> Result<R> {
// TODO: Update to `&Scope` in next major release
f(&mut Scope::new(self.lock_arc()))
}
@@ -1854,6 +1865,14 @@ impl Lua {
extra.app_data.borrow(Some(guard))
}
/// Tries to get a reference to an application data object stored by [`Lua::set_app_data`] of
/// type `T`.
pub fn try_app_data_ref<T: 'static>(&self) -> StdResult<Option<AppDataRef<T>>, BorrowError> {
let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() };
extra.app_data.try_borrow(Some(guard))
}
/// Gets a mutable reference to an application data object stored by [`Lua::set_app_data`] of
/// type `T`.
///
@@ -1867,6 +1886,14 @@ impl Lua {
extra.app_data.borrow_mut(Some(guard))
}
/// Tries to get a mutable reference to an application data object stored by
/// [`Lua::set_app_data`] of type `T`.
pub fn try_app_data_mut<T: 'static>(&self) -> StdResult<Option<AppDataRefMut<T>>, BorrowMutError> {
let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() };
extra.app_data.try_borrow_mut(Some(guard))
}
/// Removes an application data of type `T`.
///
/// # Panics
+7 -2
View File
@@ -13,6 +13,7 @@ use crate::error::Result;
use crate::state::RawLua;
use crate::stdlib::StdLib;
use crate::types::{AppData, ReentrantMutex, XRc};
use crate::userdata::RawUserDataRegistry;
use crate::util::{get_internal_metatable, push_internal_userdata, TypeKey, WrappedFailure};
#[cfg(any(feature = "luau", doc))]
@@ -26,7 +27,7 @@ use super::{Lua, WeakLua};
// Unique key to store `ExtraData` in the registry
static EXTRA_REGISTRY_KEY: u8 = 0;
const WRAPPED_FAILURE_POOL_SIZE: usize = 64;
const WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY: usize = 64;
const REF_STACK_RESERVE: c_int = 1;
/// Data associated with the Lua state.
@@ -35,6 +36,7 @@ pub(crate) struct ExtraData {
pub(super) weak: MaybeUninit<WeakLua>,
pub(super) owned: bool,
pub(super) pending_userdata_reg: FxHashMap<TypeId, RawUserDataRegistry>,
pub(super) registered_userdata_t: FxHashMap<TypeId, c_int>,
pub(super) registered_userdata_mt: FxHashMap<*const c_void, Option<TypeId>>,
pub(super) last_checked_userdata_mt: (*const c_void, Option<TypeId>),
@@ -58,6 +60,7 @@ pub(crate) struct ExtraData {
// Pool of `WrappedFailure` enums in the ref thread (as userdata)
pub(super) wrapped_failure_pool: Vec<c_int>,
pub(super) wrapped_failure_top: usize,
// Pool of `Thread`s (coroutines) for async execution
#[cfg(feature = "async")]
pub(super) thread_pool: Vec<c_int>,
@@ -144,6 +147,7 @@ impl ExtraData {
lua: MaybeUninit::uninit(),
weak: MaybeUninit::uninit(),
owned,
pending_userdata_reg: FxHashMap::default(),
registered_userdata_t: FxHashMap::default(),
registered_userdata_mt: FxHashMap::default(),
last_checked_userdata_mt: (ptr::null(), None),
@@ -157,7 +161,8 @@ impl ExtraData {
ref_stack_size: ffi::LUA_MINSTACK - REF_STACK_RESERVE,
ref_stack_top: ffi::lua_gettop(ref_thread),
ref_free: Vec::new(),
wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_SIZE),
wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY),
wrapped_failure_top: 0,
#[cfg(feature = "async")]
thread_pool: Vec::new(),
wrapped_failure_mt_ptr,
+104 -83
View File
@@ -1,11 +1,12 @@
use std::any::TypeId;
use std::cell::{Cell, UnsafeCell};
use std::ffi::{CStr, CString};
use std::mem;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::resume_unwind;
use std::ptr::{self, NonNull};
use std::result::Result as StdResult;
use std::sync::Arc;
use std::{mem, ptr};
use crate::chunk::ChunkMode;
use crate::error::{Error, Result};
@@ -21,7 +22,9 @@ use crate::types::{
AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData,
MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc,
};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataRegistry, UserDataStorage};
use crate::userdata::{
AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, UserDataStorage,
};
use crate::util::{
assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state,
get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, init_userdata_metatable,
@@ -41,7 +44,6 @@ use {
crate::multi::MultiValue,
crate::traits::FromLuaMulti,
crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue},
std::ptr::NonNull,
std::task::{Context, Poll, Waker},
};
@@ -50,7 +52,7 @@ use {
pub struct RawLua {
// The state is dynamic and depends on context
pub(super) state: Cell<*mut ffi::lua_State>,
pub(super) main_state: *mut ffi::lua_State,
pub(super) main_state: Option<NonNull<ffi::lua_State>>,
pub(super) extra: XRc<UnsafeCell<ExtraData>>,
}
@@ -61,9 +63,9 @@ impl Drop for RawLua {
return;
}
let mem_state = MemoryState::get(self.main_state);
let mem_state = MemoryState::get(self.main_state());
ffi::lua_close(self.main_state);
ffi::lua_close(self.main_state());
// Deallocate `MemoryState`
if !mem_state.is_null() {
@@ -95,10 +97,11 @@ impl RawLua {
self.state.get()
}
#[cfg(feature = "luau")]
#[inline(always)]
pub(crate) fn main_state(&self) -> *mut ffi::lua_State {
self.main_state
.map(|state| state.as_ptr())
.unwrap_or_else(|| self.state())
}
#[inline(always)]
@@ -221,7 +224,8 @@ impl RawLua {
#[allow(clippy::arc_with_non_send_sync)]
let rawlua = XRc::new(ReentrantMutex::new(RawLua {
state: Cell::new(state),
main_state,
// Make sure that we don't store current state as main state (if it's not available)
main_state: get_main_state(state).and_then(NonNull::new),
extra: XRc::clone(&extra),
}));
(*extra.get()).set_lua(&rawlua);
@@ -263,7 +267,7 @@ impl RawLua {
));
}
let res = load_std_libs(self.main_state, libs);
let res = load_std_libs(self.main_state(), libs);
// If `package` library loaded into a safe lua state then disable C modules
let curr_libs = (*self.extra.get()).libs;
@@ -315,41 +319,60 @@ impl RawLua {
let state = self.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
check_stack(state, 3)?;
let mode_str = match mode {
let name = name.map(CStr::as_ptr).unwrap_or(ptr::null());
let mode = match mode {
Some(ChunkMode::Binary) => cstr!("b"),
Some(ChunkMode::Text) => cstr!("t"),
None => cstr!("bt"),
};
match ffi::luaL_loadbufferenv(
state,
source.as_ptr() as *const c_char,
source.len(),
name.map(|n| n.as_ptr()).unwrap_or_else(ptr::null),
mode_str,
match env {
Some(env) => {
self.push_ref(&env.0);
-1
}
_ => 0,
},
) {
ffi::LUA_OK => {
#[cfg(feature = "luau-jit")]
if (*self.extra.get()).enable_jit && ffi::luau_codegen_supported() != 0 {
ffi::luau_codegen_compile(state, -1);
}
Ok(Function(self.pop_ref()))
}
let status = if self.unlikely_memory_error() {
self.load_chunk_inner(state, name, env, mode, source)
} else {
// Luau and Lua 5.2 can trigger an exception during chunk loading
protect_lua!(state, 0, 1, |state| {
self.load_chunk_inner(state, name, env, mode, source)
})?
};
match status {
ffi::LUA_OK => Ok(Function(self.pop_ref())),
err => Err(pop_error(state, err)),
}
}
}
pub(crate) unsafe fn load_chunk_inner(
&self,
state: *mut ffi::lua_State,
name: *const c_char,
env: Option<&Table>,
mode: *const c_char,
source: &[u8],
) -> c_int {
let status = ffi::luaL_loadbufferenv(
state,
source.as_ptr() as *const c_char,
source.len(),
name,
mode,
match env {
Some(env) => {
self.push_ref(&env.0);
-1
}
_ => 0,
},
);
#[cfg(feature = "luau-jit")]
if status == ffi::LUA_OK {
if (*self.extra.get()).enable_jit && ffi::luau_codegen_supported() != 0 {
ffi::luau_codegen_compile(state, -1);
}
}
status
}
/// Sets a 'hook' function for a thread (coroutine).
#[cfg(not(feature = "luau"))]
pub(crate) unsafe fn set_thread_hook<F>(
@@ -409,8 +432,8 @@ impl RawLua {
pub(crate) unsafe fn create_string(&self, s: impl AsRef<[u8]>) -> Result<String> {
let state = self.state();
if self.unlikely_memory_error() {
push_string(self.ref_thread(), s.as_ref(), false)?;
return Ok(String(self.pop_ref_thread()));
push_string(state, s.as_ref(), false)?;
return Ok(String(self.pop_ref()));
}
let _sg = StackGuard::new(state);
@@ -421,12 +444,12 @@ impl RawLua {
/// See [`Lua::create_table_with_capacity`]
pub(crate) unsafe fn create_table_with_capacity(&self, narr: usize, nrec: usize) -> Result<Table> {
let state = self.state();
if self.unlikely_memory_error() {
push_table(self.ref_thread(), narr, nrec, false)?;
return Ok(Table(self.pop_ref_thread()));
push_table(state, narr, nrec, false)?;
return Ok(Table(self.pop_ref()));
}
let state = self.state();
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
push_table(state, narr, nrec, true)?;
@@ -711,6 +734,10 @@ impl RawLua {
pub(crate) unsafe fn drop_ref(&self, vref: &ValueRef) {
let ref_thread = self.ref_thread();
mlua_debug_assert!(
ffi::lua_gettop(ref_thread) >= vref.index,
"GC finalizer is not allowed in ref_thread"
);
ffi::lua_pushnil(ref_thread);
ffi::lua_replace(ref_thread, vref.index);
(*self.extra.get()).ref_free.push(vref.index);
@@ -734,7 +761,7 @@ impl RawLua {
}
// MemoryInfo is empty in module mode so we cannot predict memory limits
match MemoryState::get(self.main_state) {
match MemoryState::get(self.state()) {
mem_state if !mem_state.is_null() => (*mem_state).memory_limit() == 0,
_ => (*self.extra.get()).skip_memory_check, // Check the special flag (only for module mode)
}
@@ -752,10 +779,10 @@ impl RawLua {
}
// Create a new metatable from `UserData` definition
let mut registry = UserDataRegistry::new(type_id);
let mut registry = UserDataRegistry::new(self.lua(), type_id);
T::register(&mut registry);
self.create_userdata_metatable(registry)
self.create_userdata_metatable(registry.into_raw())
})
}
@@ -770,8 +797,11 @@ impl RawLua {
return Ok(table_id as Integer);
}
// Create an empty metatable
let registry = UserDataRegistry::<T>::new(type_id);
// Check if metatable creation is pending or create an empty metatable otherwise
let registry = match (*self.extra.get()).pending_userdata_reg.remove(&type_id) {
Some(registry) => registry,
None => UserDataRegistry::<T>::new(self.lua(), type_id).into_raw(),
};
self.create_userdata_metatable(registry)
})
}
@@ -808,12 +838,9 @@ impl RawLua {
Ok(AnyUserData(self.pop_ref()))
}
pub(crate) unsafe fn create_userdata_metatable<T>(
&self,
registry: UserDataRegistry<T>,
) -> Result<Integer> {
pub(crate) unsafe fn create_userdata_metatable(&self, registry: RawUserDataRegistry) -> Result<Integer> {
let state = self.state();
let type_id = registry.type_id();
let type_id = registry.type_id;
self.push_userdata_metatable(registry)?;
@@ -830,9 +857,9 @@ impl RawLua {
Ok(id as Integer)
}
pub(crate) unsafe fn push_userdata_metatable<T>(&self, mut registry: UserDataRegistry<T>) -> Result<()> {
pub(crate) unsafe fn push_userdata_metatable(&self, mut registry: RawUserDataRegistry) -> Result<()> {
let state = self.state();
let _sg = StackGuard::with_top(state, ffi::lua_gettop(state) + 1);
let mut stack_guard = StackGuard::new(state);
check_stack(state, 13)?;
// Prepare metatable, add meta methods first and then meta fields
@@ -850,21 +877,19 @@ impl RawLua {
rawset_field(state, -2, MetaMethod::validate(&k)?)?;
}
let mut has_name = false;
for (k, push_field) in registry.meta_fields {
for (k, v) in registry.meta_fields {
has_name = has_name || k == MetaMethod::Type;
push_field(self)?;
v?.push_into_stack(self)?;
rawset_field(state, -2, MetaMethod::validate(&k)?)?;
}
// Set `__name/__type` if not provided
if !has_name {
let type_name = short_type_name::<T>();
let type_name = registry.type_name;
push_string(state, type_name.as_bytes(), !self.unlikely_memory_error())?;
rawset_field(state, -2, MetaMethod::Type.name())?;
}
let metatable_index = ffi::lua_absindex(state, -1);
let mut extra_tables_count = 0;
let fields_nrec = registry.fields.len();
if fields_nrec > 0 {
// If `__index` is a table then update it in-place
@@ -876,8 +901,8 @@ impl RawLua {
ffi::lua_pop(state, 1);
push_table(state, 0, fields_nrec, true)?;
}
for (k, push_field) in mem::take(&mut registry.fields) {
push_field(self)?;
for (k, v) in mem::take(&mut registry.fields) {
v?.push_into_stack(self)?;
rawset_field(state, -2, &k)?;
}
rawset_field(state, metatable_index, "__index")?;
@@ -897,19 +922,18 @@ impl RawLua {
self.push(self.create_callback(m)?)?;
rawset_field(state, -2, &k)?;
}
for (k, push_field) in registry.fields {
for (k, v) in registry.fields {
unsafe extern "C-unwind" fn return_field(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1));
1
}
push_field(self)?;
v?.push_into_stack(self)?;
protect_lua!(state, 1, 1, fn(state) {
ffi::lua_pushcclosure(state, return_field, 1);
})?;
rawset_field(state, -2, &k)?;
}
field_getters_index = Some(ffi::lua_absindex(state, -1));
extra_tables_count += 1;
}
let mut field_setters_index = None;
@@ -921,7 +945,6 @@ impl RawLua {
rawset_field(state, -2, &k)?;
}
field_setters_index = Some(ffi::lua_absindex(state, -1));
extra_tables_count += 1;
}
let mut methods_index = None;
@@ -958,18 +981,12 @@ impl RawLua {
}
_ => {
methods_index = Some(ffi::lua_absindex(state, -1));
extra_tables_count += 1;
}
}
}
#[cfg(feature = "luau")]
let extra_init = None;
#[cfg(not(feature = "luau"))]
let extra_init: Option<fn(*mut ffi::lua_State) -> Result<()>> = Some(|state| {
ffi::lua_pushcfunction(state, crate::util::userdata_destructor::<UserDataStorage<T>>);
rawset_field(state, -2, "__gc")
});
ffi::lua_pushcfunction(state, registry.destructor);
rawset_field(state, metatable_index, "__gc")?;
init_userdata_metatable(
state,
@@ -977,11 +994,10 @@ impl RawLua {
field_getters_index,
field_setters_index,
methods_index,
extra_init,
)?;
// Pop extra tables to get metatable on top of the stack
ffi::lua_pop(state, extra_tables_count);
// Update stack guard to keep metatable after return
stack_guard.keep(1);
Ok(())
}
@@ -1100,7 +1116,7 @@ impl RawLua {
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
unsafe {
if !(*self.extra.get()).libs.contains(StdLib::COROUTINE) {
load_std_libs(self.main_state, StdLib::COROUTINE)?;
load_std_libs(self.main_state(), StdLib::COROUTINE)?;
(*self.extra.get()).libs |= StdLib::COROUTINE;
}
}
@@ -1345,6 +1361,12 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()>
ffi::lua_pop(state, 1);
}
#[cfg(feature = "luau")]
if libs.contains(StdLib::VECTOR) {
requiref(state, ffi::LUA_VECLIBNAME, ffi::luaopen_vector, 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);
@@ -1367,16 +1389,15 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()>
}
#[cfg(feature = "luajit")]
{
if libs.contains(StdLib::JIT) {
requiref(state, ffi::LUA_JITLIBNAME, ffi::luaopen_jit, 1)?;
ffi::lua_pop(state, 1);
}
if libs.contains(StdLib::JIT) {
requiref(state, ffi::LUA_JITLIBNAME, ffi::luaopen_jit, 1)?;
ffi::lua_pop(state, 1);
}
if libs.contains(StdLib::FFI) {
requiref(state, ffi::LUA_FFILIBNAME, ffi::luaopen_ffi, 1)?;
ffi::lua_pop(state, 1);
}
#[cfg(feature = "luajit")]
if libs.contains(StdLib::FFI) {
requiref(state, ffi::LUA_FFILIBNAME, ffi::luaopen_ffi, 1)?;
ffi::lua_pop(state, 1);
}
Ok(())
+23 -35
View File
@@ -7,8 +7,6 @@ use crate::error::{Error, Result};
use crate::state::{ExtraData, RawLua};
use crate::util::{self, get_internal_metatable, WrappedFailure};
const WRAPPED_FAILURE_POOL_SIZE: usize = 64;
pub(super) struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State);
impl<'a> StateGuard<'a> {
@@ -42,26 +40,27 @@ where
enum PreallocatedFailure {
New(*mut WrappedFailure),
Existing(i32),
Reserved,
}
impl PreallocatedFailure {
unsafe fn reserve(state: *mut ffi::lua_State, extra: *mut ExtraData) -> Self {
match (*extra).wrapped_failure_pool.pop() {
Some(index) => PreallocatedFailure::Existing(index),
None => {
// We need to check stack for Luau in case when callback is called from interrupt
// See https://github.com/Roblox/luau/issues/446 and mlua #142 and #153
#[cfg(feature = "luau")]
ffi::lua_rawcheckstack(state, 2);
// Place it to the beginning of the stack
let ud = WrappedFailure::new_userdata(state);
ffi::lua_insert(state, 1);
PreallocatedFailure::New(ud)
}
if (*extra).wrapped_failure_top > 0 {
(*extra).wrapped_failure_top -= 1;
return PreallocatedFailure::Reserved;
}
// We need to check stack for Luau in case when callback is called from interrupt
// See https://github.com/Roblox/luau/issues/446 and mlua #142 and #153
#[cfg(feature = "luau")]
ffi::lua_rawcheckstack(state, 2);
// Place it to the beginning of the stack
let ud = WrappedFailure::new_userdata(state);
ffi::lua_insert(state, 1);
PreallocatedFailure::New(ud)
}
#[cold]
unsafe fn r#use(&self, state: *mut ffi::lua_State, extra: *mut ExtraData) -> *mut WrappedFailure {
let ref_thread = (*extra).ref_thread;
match *self {
@@ -69,12 +68,12 @@ where
ffi::lua_settop(state, 1);
ud
}
PreallocatedFailure::Existing(index) => {
PreallocatedFailure::Reserved => {
let index = (*extra).wrapped_failure_pool.pop().unwrap();
ffi::lua_settop(state, 0);
#[cfg(feature = "luau")]
ffi::lua_rawcheckstack(state, 2);
ffi::lua_pushvalue(ref_thread, index);
ffi::lua_xmove(ref_thread, state, 1);
ffi::lua_xpush(ref_thread, state, index);
ffi::lua_pushnil(ref_thread);
ffi::lua_replace(ref_thread, index);
(*extra).ref_free.push(index);
@@ -87,24 +86,13 @@ where
let ref_thread = (*extra).ref_thread;
match self {
PreallocatedFailure::New(_) => {
if (*extra).wrapped_failure_pool.len() < WRAPPED_FAILURE_POOL_SIZE {
ffi::lua_rotate(state, 1, -1);
ffi::lua_xmove(state, ref_thread, 1);
let index = ref_stack_pop(extra);
(*extra).wrapped_failure_pool.push(index);
} else {
ffi::lua_remove(state, 1);
}
}
PreallocatedFailure::Existing(index) => {
if (*extra).wrapped_failure_pool.len() < WRAPPED_FAILURE_POOL_SIZE {
(*extra).wrapped_failure_pool.push(index);
} else {
ffi::lua_pushnil(ref_thread);
ffi::lua_replace(ref_thread, index);
(*extra).ref_free.push(index);
}
ffi::lua_rotate(state, 1, -1);
ffi::lua_xmove(state, ref_thread, 1);
let index = ref_stack_pop(extra);
(*extra).wrapped_failure_pool.push(index);
(*extra).wrapped_failure_top += 1;
}
PreallocatedFailure::Reserved => (*extra).wrapped_failure_top += 1,
}
}
}
+7 -2
View File
@@ -43,17 +43,22 @@ impl StdLib {
/// [`package`](https://www.lua.org/manual/5.4/manual.html#6.3) library
pub const PACKAGE: StdLib = StdLib(1 << 8);
/// [`buffer`](https://luau-lang.org/library#buffer-library) library
/// [`buffer`](https://luau.org/library#buffer-library) library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub const BUFFER: StdLib = StdLib(1 << 9);
/// [`vector`](https://luau.org/library#vector-library) library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub const VECTOR: StdLib = StdLib(1 << 10);
/// [`jit`](http://luajit.org/ext_jit.html) library
///
/// Requires `feature = "luajit"`
#[cfg(any(feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))]
pub const JIT: StdLib = StdLib(1 << 9);
pub const JIT: StdLib = StdLib(1 << 11);
/// (**unsafe**) [`ffi`](http://luajit.org/ext_ffi.html) library
///
+42
View File
@@ -7,7 +7,9 @@ use std::{cmp, fmt, slice, str};
use crate::error::{Error, Result};
use crate::state::Lua;
use crate::traits::IntoLua;
use crate::types::{LuaType, ValueRef};
use crate::value::Value;
#[cfg(feature = "serialize")]
use {
@@ -55,7 +57,11 @@ impl String {
///
/// Any non-Unicode sequences are replaced with [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD].
///
/// This method returns [`StdString`] instead of [`Cow<'_, str>`] because lifetime cannot be
/// bound to a weak Lua object.
///
/// [U+FFFD]: std::char::REPLACEMENT_CHARACTER
/// [`Cow<'_, str>`]: std::borrow::Cow
///
/// # Examples
///
@@ -74,6 +80,16 @@ impl String {
StdString::from_utf8_lossy(&self.as_bytes()).into_owned()
}
/// Returns an object that implements [`Display`] for safely printing a Lua [`String`] that may
/// contain non-Unicode data.
///
/// This may perform lossy conversion.
///
/// [`Display`]: fmt::Display
pub fn display(&self) -> impl fmt::Display + '_ {
Display(self)
}
/// Get the bytes that make up this string.
///
/// The returned slice will not contain the terminating nul byte, but will contain any nul
@@ -212,6 +228,15 @@ impl Serialize for String {
}
}
struct Display<'a>(&'a String);
impl fmt::Display for Display<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let bytes = self.0.as_bytes();
<bstr::BStr as fmt::Display>::fmt(bstr::BStr::new(&bytes), f)
}
}
/// A borrowed string (`&str`) that holds a strong reference to the Lua state.
pub struct BorrowedStr<'a>(&'a str, #[allow(unused)] Lua);
@@ -343,6 +368,23 @@ impl<'a> IntoIterator for BorrowedBytes<'a> {
}
}
struct WrappedString<T: AsRef<[u8]>>(T);
impl String {
/// Wraps bytes, returning an opaque type that implements [`IntoLua`] trait.
///
/// This function uses [`Lua::create_string`] under the hood.
pub fn wrap(data: impl AsRef<[u8]>) -> impl IntoLua {
WrappedString(data)
}
}
impl<T: AsRef<[u8]>> IntoLua for WrappedString<T> {
fn into_lua(self, lua: &Lua) -> Result<Value> {
lua.create_string(self.0).map(Value::String)
}
}
impl LuaType for String {
const TYPE_ID: c_int = ffi::LUA_TSTRING;
}
+50 -7
View File
@@ -581,6 +581,24 @@ impl Table {
unsafe { ffi::lua_getreadonly(ref_thread, self.0.index) != 0 }
}
/// Controls `safeenv` attribute on the table.
///
/// This a special flag that activates some performance optimizations for environment tables.
/// In particular, it controls:
/// - Optimization of import resolution (cache values of constant keys).
/// - Fast-path for built-in iteration with pairs/ipairs.
/// - Fast-path for some built-in functions (fastcall).
///
/// For `safeenv` environments, monkey patching or modifying values may not work as expected.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_safeenv(&self, enabled: bool) {
let lua = self.0.lua.lock();
unsafe { ffi::lua_setsafeenv(lua.ref_thread(), self.0.index, enabled as _) };
}
/// Converts this table to a generic C pointer.
///
/// Different tables will give different pointers.
@@ -693,7 +711,8 @@ impl Table {
}
/// Iterates over the sequence part of the table, invoking the given closure on each value.
pub(crate) fn for_each_value<V>(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()>
#[doc(hidden)]
pub fn for_each_value<V>(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()>
where
V: FromLua,
{
@@ -777,16 +796,40 @@ impl Table {
let mut pairs = self.pairs::<Value, Value>().flatten().collect::<Vec<_>>();
// Sort keys
pairs.sort_by(|(a, _), (b, _)| a.sort_cmp(b));
let is_sequence = (pairs.iter().enumerate())
.all(|(i, (k, _))| matches!(k, Value::Integer(n) if *n == (i + 1) as Integer));
if pairs.is_empty() {
return write!(fmt, "{{}}");
}
writeln!(fmt, "{{")?;
for (key, value) in pairs {
write!(fmt, "{}[", " ".repeat(ident + 2))?;
key.fmt_pretty(fmt, false, ident + 2, visited)?;
write!(fmt, "] = ")?;
value.fmt_pretty(fmt, true, ident + 2, visited)?;
writeln!(fmt, ",")?;
if is_sequence {
// Format as list
for (_, value) in pairs {
write!(fmt, "{}", " ".repeat(ident + 2))?;
value.fmt_pretty(fmt, true, ident + 2, visited)?;
writeln!(fmt, ",")?;
}
} else {
fn is_simple_key(key: &[u8]) -> bool {
key.iter().take(1).all(|c| c.is_ascii_alphabetic() || *c == b'_')
&& key.iter().all(|c| c.is_ascii_alphanumeric() || *c == b'_')
}
for (key, value) in pairs {
match key {
Value::String(key) if is_simple_key(&key.as_bytes()) => {
write!(fmt, "{}{}", " ".repeat(ident + 2), key.display())?;
write!(fmt, " = ")?;
}
_ => {
write!(fmt, "{}[", " ".repeat(ident + 2))?;
key.fmt_pretty(fmt, false, ident + 2, visited)?;
write!(fmt, "] = ")?;
}
}
value.fmt_pretty(fmt, true, ident + 2, visited)?;
writeln!(fmt, ",")?;
}
}
write!(fmt, "{}}}", " ".repeat(ident))
}
+54 -18
View File
@@ -1,5 +1,5 @@
use std::any::{Any, TypeId};
use std::cell::{Cell, Ref, RefCell, RefMut, UnsafeCell};
use std::cell::{BorrowError, BorrowMutError, Cell, Ref, RefCell, RefMut, UnsafeCell};
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::result::Result as StdResult;
@@ -41,30 +41,66 @@ impl AppData {
.and_then(|data| data.into_inner().downcast::<T>().ok().map(|data| *data)))
}
#[inline]
#[track_caller]
pub(crate) fn borrow<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRef<T>> {
let data = unsafe { &*self.container.get() }
.get(&TypeId::of::<T>())?
.borrow();
self.borrow.set(self.borrow.get() + 1);
Some(AppDataRef {
data: Ref::filter_map(data, |data| data.downcast_ref()).ok()?,
borrow: &self.borrow,
_guard: guard,
})
match self.try_borrow(guard) {
Ok(data) => data,
Err(err) => panic!("already mutably borrowed: {err:?}"),
}
}
pub(crate) fn try_borrow<T: 'static>(
&self,
guard: Option<LuaGuard>,
) -> Result<Option<AppDataRef<T>>, BorrowError> {
let data = unsafe { &*self.container.get() }
.get(&TypeId::of::<T>())
.map(|c| c.try_borrow())
.transpose()?
.and_then(|data| Ref::filter_map(data, |data| data.downcast_ref()).ok());
match data {
Some(data) => {
self.borrow.set(self.borrow.get() + 1);
Ok(Some(AppDataRef {
data,
borrow: &self.borrow,
_guard: guard,
}))
}
None => Ok(None),
}
}
#[inline]
#[track_caller]
pub(crate) fn borrow_mut<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRefMut<T>> {
match self.try_borrow_mut(guard) {
Ok(data) => data,
Err(err) => panic!("already borrowed: {err:?}"),
}
}
pub(crate) fn try_borrow_mut<T: 'static>(
&self,
guard: Option<LuaGuard>,
) -> Result<Option<AppDataRefMut<T>>, BorrowMutError> {
let data = unsafe { &*self.container.get() }
.get(&TypeId::of::<T>())?
.borrow_mut();
self.borrow.set(self.borrow.get() + 1);
Some(AppDataRefMut {
data: RefMut::filter_map(data, |data| data.downcast_mut()).ok()?,
borrow: &self.borrow,
_guard: guard,
})
.get(&TypeId::of::<T>())
.map(|c| c.try_borrow_mut())
.transpose()?
.and_then(|data| RefMut::filter_map(data, |data| data.downcast_mut()).ok());
match data {
Some(data) => {
self.borrow.set(self.borrow.get() + 1);
Ok(Some(AppDataRefMut {
data,
borrow: &self.borrow,
_guard: guard,
}))
}
None => Ok(None),
}
}
#[track_caller]
+1 -1
View File
@@ -53,7 +53,7 @@ mod inner {
pub(crate) struct ReentrantMutexGuard<'a, T>(&'a T);
impl<'a, T> Deref for ReentrantMutexGuard<'a, T> {
impl<T> Deref for ReentrantMutexGuard<'_, T> {
type Target = T;
#[inline(always)]
+1 -1
View File
@@ -4,7 +4,7 @@ use std::os::raw::{c_int, c_void};
use crate::state::{RawLua, WeakLua};
/// A reference to a Lua (complex) value stored in the Lua auxiliary thread.
pub(crate) struct ValueRef {
pub struct ValueRef {
pub(crate) lua: WeakLua,
pub(crate) index: c_int,
pub(crate) drop: bool,
+28 -2
View File
@@ -27,8 +27,8 @@ use {
// Re-export for convenience
pub(crate) use cell::UserDataStorage;
pub use cell::{UserDataRef, UserDataRefMut};
pub(crate) use registry::UserDataProxy;
pub use registry::UserDataRegistry;
pub(crate) use registry::{RawUserDataRegistry, UserDataProxy};
/// Kinds of metamethods that can be overridden.
///
@@ -696,6 +696,31 @@ impl AnyUserData {
}
}
/// Destroys this userdata.
///
/// This is similar to [`AnyUserData::take`], but it doesn't require a type.
///
/// This method works for non-scoped userdata only.
pub fn destroy(&self) -> Result<()> {
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
lua.push_userdata_ref(&self.0)?;
protect_lua!(state, 1, 1, fn(state) {
if ffi::luaL_callmeta(state, -1, cstr!("__gc")) == 0 {
ffi::lua_pushboolean(state, 0);
}
})?;
if ffi::lua_isboolean(state, -1) != 0 && ffi::lua_toboolean(state, -1) != 0 {
return Ok(());
}
Err(Error::UserDataBorrowMutError)
}
}
/// Sets an associated value to this [`AnyUserData`].
///
/// The value may be any Lua value whatsoever, and can be retrieved with [`user_value`].
@@ -1048,7 +1073,7 @@ impl Serialize for AnyUserData {
}
}
pub(crate) struct WrappedUserdata<F: FnOnce(&Lua) -> Result<AnyUserData>>(F);
struct WrappedUserdata<F: FnOnce(&Lua) -> Result<AnyUserData>>(F);
impl AnyUserData {
/// Wraps any Rust type, returning an opaque type that implements [`IntoLua`] trait.
@@ -1082,6 +1107,7 @@ mod cell;
mod lock;
mod object;
mod registry;
mod util;
#[cfg(test)]
mod assertions {
+47 -5
View File
@@ -1,5 +1,5 @@
use std::any::{type_name, TypeId};
use std::cell::{RefCell, UnsafeCell};
use std::cell::{Cell, RefCell, UnsafeCell};
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::os::raw::c_int;
@@ -16,6 +16,7 @@ use crate::util::get_userdata;
use crate::value::Value;
use super::lock::{RawLock, UserDataLock};
use super::util::is_sync;
#[cfg(all(feature = "serialize", not(feature = "send")))]
type DynSerialize = dyn erased_serde::Serialize;
@@ -98,6 +99,15 @@ impl<T> UserDataVariant<T> {
}
}
#[inline(always)]
fn borrow_count(&self) -> &Cell<usize> {
match self {
Self::Default(inner) => &inner.borrow_count,
#[cfg(feature = "serialize")]
Self::Serializable(inner) => &inner.borrow_count,
}
}
#[inline(always)]
fn as_ptr(&self) -> *mut T {
match self {
@@ -129,10 +139,13 @@ impl Serialize for UserDataStorage<()> {
/// A type that provides interior mutability for a userdata value (thread-safe).
pub(crate) struct UserDataCell<T> {
raw_lock: RawLock,
borrow_count: Cell<usize>,
value: UnsafeCell<T>,
}
#[cfg(feature = "send")]
unsafe impl<T: Send> Send for UserDataCell<T> {}
#[cfg(feature = "send")]
unsafe impl<T: Send> Sync for UserDataCell<T> {}
impl<T> UserDataCell<T> {
@@ -140,6 +153,7 @@ impl<T> UserDataCell<T> {
fn new(value: T) -> Self {
UserDataCell {
raw_lock: RawLock::INIT,
borrow_count: Cell::new(0),
value: UnsafeCell::new(value),
}
}
@@ -162,7 +176,11 @@ impl<T> Deref for UserDataRef<T> {
impl<T> Drop for UserDataRef<T> {
#[inline]
fn drop(&mut self) {
unsafe { self.0.raw_lock().unlock_shared() };
if !cfg!(feature = "send") || is_sync::<T>() {
unsafe { self.0.raw_lock().unlock_shared() };
} else {
unsafe { self.0.raw_lock().unlock_exclusive() };
}
}
}
@@ -183,7 +201,11 @@ impl<T> TryFrom<UserDataVariant<T>> for UserDataRef<T> {
#[inline]
fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
if !variant.raw_lock().try_lock_shared() {
if !cfg!(feature = "send") || is_sync::<T>() {
if !variant.raw_lock().try_lock_shared() {
return Err(Error::UserDataBorrowError);
}
} else if !variant.raw_lock().try_lock_exclusive() {
return Err(Error::UserDataBorrowError);
}
Ok(UserDataRef(variant))
@@ -280,7 +302,10 @@ pub(crate) struct UserDataBorrowRef<'a, T>(&'a UserDataVariant<T>);
impl<T> Drop for UserDataBorrowRef<'_, T> {
#[inline]
fn drop(&mut self) {
unsafe { self.0.raw_lock().unlock_shared() };
unsafe {
self.0.borrow_count().set(self.0.borrow_count().get() - 1);
self.0.raw_lock().unlock_shared();
}
}
}
@@ -299,9 +324,14 @@ impl<'a, T> TryFrom<&'a UserDataVariant<T>> for UserDataBorrowRef<'a, T> {
#[inline(always)]
fn try_from(variant: &'a UserDataVariant<T>) -> Result<Self> {
// We don't need to check for `T: Sync` because when this method is used (internally),
// Lua mutex is already locked.
// If non-`Sync` userdata is already borrowed by another thread (via `UserDataRef`), it will be
// exclusively locked.
if !variant.raw_lock().try_lock_shared() {
return Err(Error::UserDataBorrowError);
}
variant.borrow_count().set(variant.borrow_count().get() + 1);
Ok(UserDataBorrowRef(variant))
}
}
@@ -311,7 +341,10 @@ pub(crate) struct UserDataBorrowMut<'a, T>(&'a UserDataVariant<T>);
impl<T> Drop for UserDataBorrowMut<'_, T> {
#[inline]
fn drop(&mut self) {
unsafe { self.0.raw_lock().unlock_exclusive() };
unsafe {
self.0.borrow_count().set(self.0.borrow_count().get() - 1);
self.0.raw_lock().unlock_exclusive();
}
}
}
@@ -339,6 +372,7 @@ impl<'a, T> TryFrom<&'a UserDataVariant<T>> for UserDataBorrowMut<'a, T> {
if !variant.raw_lock().try_lock_exclusive() {
return Err(Error::UserDataBorrowMutError);
}
variant.borrow_count().set(variant.borrow_count().get() + 1);
Ok(UserDataBorrowMut(variant))
}
}
@@ -455,6 +489,14 @@ impl<T> UserDataStorage<T> {
Self::Scoped(ScopedUserDataVariant::Boxed(RefCell::new(data)))
}
#[inline(always)]
pub(crate) fn is_borrowed(&self) -> bool {
match self {
Self::Owned(variant) => variant.borrow_count().get() > 0,
Self::Scoped(_) => true,
}
}
#[inline]
pub(crate) fn try_borrow_scoped<R>(&self, f: impl FnOnce(&T) -> R) -> Result<R> {
match self {
+7 -7
View File
@@ -63,32 +63,32 @@ mod lock_impl {
#[cfg(feature = "send")]
mod lock_impl {
use parking_lot::lock_api::RawMutex;
use parking_lot::lock_api::RawRwLock;
pub(crate) type RawLock = parking_lot::RawMutex;
pub(crate) type RawLock = parking_lot::RawRwLock;
impl super::UserDataLock for RawLock {
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Self = <Self as parking_lot::lock_api::RawMutex>::INIT;
const INIT: Self = <Self as parking_lot::lock_api::RawRwLock>::INIT;
#[inline(always)]
fn try_lock_shared(&self) -> bool {
RawLock::try_lock(self)
RawRwLock::try_lock_shared(self)
}
#[inline(always)]
fn try_lock_exclusive(&self) -> bool {
RawLock::try_lock(self)
RawRwLock::try_lock_exclusive(self)
}
#[inline(always)]
unsafe fn unlock_shared(&self) {
RawLock::unlock(self)
RawRwLock::unlock_shared(self)
}
#[inline(always)]
unsafe fn unlock_exclusive(&self) {
RawLock::unlock(self)
RawRwLock::unlock_exclusive(self)
}
}
}
+342 -138
View File
@@ -7,7 +7,7 @@ use std::os::raw::c_void;
use std::string::String as StdString;
use crate::error::{Error, Result};
use crate::state::{Lua, RawLua};
use crate::state::{Lua, LuaGuard};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{Callback, MaybeSend};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataStorage};
@@ -21,21 +21,46 @@ use {
std::future::{self, Future},
};
type StaticFieldCallback = Box<dyn FnOnce(&RawLua) -> Result<()> + 'static>;
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
use std::rc::Rc;
#[cfg(feature = "userdata-wrappers")]
use std::sync::{Arc, Mutex, RwLock};
#[derive(Clone, Copy)]
pub(crate) enum UserDataTypeId {
enum UserDataTypeId {
Shared(TypeId),
Unique(usize),
Unique(*mut c_void),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Rc(TypeId),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
RcRefCell(TypeId),
#[cfg(feature = "userdata-wrappers")]
Arc(TypeId),
#[cfg(feature = "userdata-wrappers")]
ArcMutex(TypeId),
#[cfg(feature = "userdata-wrappers")]
ArcRwLock(TypeId),
#[cfg(feature = "userdata-wrappers")]
ArcParkingLotMutex(TypeId),
#[cfg(feature = "userdata-wrappers")]
ArcParkingLotRwLock(TypeId),
}
/// Handle to registry for userdata methods and metamethods.
pub struct UserDataRegistry<T> {
lua: LuaGuard,
raw: RawUserDataRegistry,
ud_type_id: UserDataTypeId,
_type: PhantomData<T>,
}
pub(crate) struct RawUserDataRegistry {
// Fields
pub(crate) fields: Vec<(String, StaticFieldCallback)>,
pub(crate) fields: Vec<(String, Result<Value>)>,
pub(crate) field_getters: Vec<(String, Callback)>,
pub(crate) field_setters: Vec<(String, Callback)>,
pub(crate) meta_fields: Vec<(String, StaticFieldCallback)>,
pub(crate) meta_fields: Vec<(String, Result<Value>)>,
// Methods
pub(crate) methods: Vec<(String, Callback)>,
@@ -45,52 +70,72 @@ pub struct UserDataRegistry<T> {
#[cfg(feature = "async")]
pub(crate) async_meta_methods: Vec<(String, AsyncCallback)>,
pub(crate) type_id: UserDataTypeId,
_type: PhantomData<T>,
pub(crate) destructor: ffi::lua_CFunction,
pub(crate) type_id: Option<TypeId>,
pub(crate) type_name: StdString,
}
impl<T> UserDataRegistry<T> {
impl UserDataTypeId {
#[inline]
pub(crate) fn new(type_id: TypeId) -> Self {
UserDataRegistry {
fields: Vec::new(),
field_getters: Vec::new(),
field_setters: Vec::new(),
meta_fields: Vec::new(),
methods: Vec::new(),
#[cfg(feature = "async")]
async_methods: Vec::new(),
meta_methods: Vec::new(),
#[cfg(feature = "async")]
async_meta_methods: Vec::new(),
type_id: UserDataTypeId::Shared(type_id),
_type: PhantomData,
}
}
#[inline]
pub(crate) fn new_unique(ud_ptr: *const c_void) -> Self {
UserDataRegistry {
fields: Vec::new(),
field_getters: Vec::new(),
field_setters: Vec::new(),
meta_fields: Vec::new(),
methods: Vec::new(),
#[cfg(feature = "async")]
async_methods: Vec::new(),
meta_methods: Vec::new(),
#[cfg(feature = "async")]
async_meta_methods: Vec::new(),
type_id: UserDataTypeId::Unique(ud_ptr as usize),
_type: PhantomData,
}
}
#[inline]
pub(crate) fn type_id(&self) -> Option<TypeId> {
match self.type_id {
pub(crate) fn type_id(self) -> Option<TypeId> {
match self {
UserDataTypeId::Shared(type_id) => Some(type_id),
UserDataTypeId::Unique(_) => None,
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
UserDataTypeId::Rc(type_id) => Some(type_id),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
UserDataTypeId::RcRefCell(type_id) => Some(type_id),
#[cfg(feature = "userdata-wrappers")]
UserDataTypeId::Arc(type_id) => Some(type_id),
#[cfg(feature = "userdata-wrappers")]
UserDataTypeId::ArcMutex(type_id) => Some(type_id),
#[cfg(feature = "userdata-wrappers")]
UserDataTypeId::ArcRwLock(type_id) => Some(type_id),
#[cfg(feature = "userdata-wrappers")]
UserDataTypeId::ArcParkingLotMutex(type_id) => Some(type_id),
#[cfg(feature = "userdata-wrappers")]
UserDataTypeId::ArcParkingLotRwLock(type_id) => Some(type_id),
}
}
}
#[cfg(feature = "send")]
unsafe impl Send for UserDataTypeId {}
impl<T> UserDataRegistry<T> {
#[inline(always)]
pub(crate) fn new(lua: &Lua, type_id: TypeId) -> Self {
Self::with_type_id(lua, UserDataTypeId::Shared(type_id))
}
#[inline(always)]
pub(crate) fn new_unique(lua: &Lua, ud_ptr: *mut c_void) -> Self {
Self::with_type_id(lua, UserDataTypeId::Unique(ud_ptr))
}
#[inline(always)]
fn with_type_id(lua: &Lua, ud_type_id: UserDataTypeId) -> Self {
let raw = RawUserDataRegistry {
fields: Vec::new(),
field_getters: Vec::new(),
field_setters: Vec::new(),
meta_fields: Vec::new(),
methods: Vec::new(),
#[cfg(feature = "async")]
async_methods: Vec::new(),
meta_methods: Vec::new(),
#[cfg(feature = "async")]
async_meta_methods: Vec::new(),
destructor: super::util::userdata_destructor::<T>,
type_id: ud_type_id.type_id(),
type_name: short_type_name::<T>(),
};
UserDataRegistry {
lua: lua.lock_arc(),
raw,
ud_type_id,
_type: PhantomData,
}
}
@@ -107,7 +152,7 @@ impl<T> UserDataRegistry<T> {
};
}
let target_type_id = self.type_id;
let target_type_id = self.ud_type_id;
Box::new(move |rawlua, nargs| unsafe {
if nargs == 0 {
let err = Error::from_lua_conversion("missing argument", "userdata", None);
@@ -120,28 +165,102 @@ impl<T> UserDataRegistry<T> {
let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua);
match target_type_id {
// This branch is for `'static` userdata that share type metatable
UserDataTypeId::Shared(target_type_id) => {
match try_self_arg!(rawlua.get_userdata_type_id::<T>(self_index)) {
Some(self_type_id) if self_type_id == target_type_id => {
let ud = get_userdata::<UserDataStorage<T>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
#[rustfmt::skip]
UserDataTypeId::Shared(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<T>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<T>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
UserDataTypeId::Unique(target_ptr) => {
match get_userdata::<UserDataStorage<T>>(state, self_index) {
ud if ud as usize == target_ptr => {
try_self_arg!((*ud).try_borrow_scoped(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
#[rustfmt::skip]
UserDataTypeId::Unique(target_ptr)
if get_userdata::<UserDataStorage<T>>(state, self_index) as *mut c_void == target_ptr =>
{
let ud = target_ptr as *mut UserDataStorage<T>;
try_self_arg!((*ud).try_borrow_scoped(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
#[rustfmt::skip]
UserDataTypeId::Rc(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Rc<T>>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Rc<T>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
#[rustfmt::skip]
UserDataTypeId::RcRefCell(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Rc<RefCell<T>>>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Rc<RefCell<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let ud = ud.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::Arc(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<T>>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Arc<T>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::ArcMutex(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<Mutex<T>>>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Arc<Mutex<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let ud = ud.try_lock().map_err(|_| Error::UserDataBorrowError)?;
method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::ArcRwLock(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<RwLock<T>>>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Arc<RwLock<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let ud = ud.try_read().map_err(|_| Error::UserDataBorrowError)?;
method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::ArcParkingLotMutex(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<parking_lot::Mutex<T>>>(self_index))
== Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Arc<parking_lot::Mutex<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let ud = ud.try_lock().ok_or(Error::UserDataBorrowError)?;
method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::ArcParkingLotRwLock(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<parking_lot::RwLock<T>>>(self_index))
== Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Arc<parking_lot::RwLock<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let ud = ud.try_read().ok_or(Error::UserDataBorrowError)?;
method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua)
}))
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
})
}
@@ -160,7 +279,7 @@ impl<T> UserDataRegistry<T> {
}
let method = RefCell::new(method);
let target_type_id = self.type_id;
let target_type_id = self.ud_type_id;
Box::new(move |rawlua, nargs| unsafe {
let mut method = method.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?;
if nargs == 0 {
@@ -174,28 +293,96 @@ impl<T> UserDataRegistry<T> {
let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua);
match target_type_id {
// This branch is for `'static` userdata that share type metatable
UserDataTypeId::Shared(target_type_id) => {
match try_self_arg!(rawlua.get_userdata_type_id::<T>(self_index)) {
Some(self_type_id) if self_type_id == target_type_id => {
let ud = get_userdata::<UserDataStorage<T>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped_mut(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
#[rustfmt::skip]
UserDataTypeId::Shared(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<T>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<T>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped_mut(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
UserDataTypeId::Unique(target_ptr) => {
match get_userdata::<UserDataStorage<T>>(state, self_index) {
ud if ud as usize == target_ptr => {
try_self_arg!((*ud).try_borrow_scoped_mut(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
#[rustfmt::skip]
UserDataTypeId::Unique(target_ptr)
if get_userdata::<UserDataStorage<T>>(state, self_index) as *mut c_void == target_ptr =>
{
let ud = target_ptr as *mut UserDataStorage<T>;
try_self_arg!((*ud).try_borrow_scoped_mut(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
#[rustfmt::skip]
UserDataTypeId::Rc(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Rc<T>>(self_index)) == Some(target_type_id) =>
{
Err(Error::UserDataBorrowMutError)
},
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
#[rustfmt::skip]
UserDataTypeId::RcRefCell(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Rc<RefCell<T>>>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Rc<RefCell<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let mut ud = ud.try_borrow_mut().map_err(|_| Error::UserDataBorrowMutError)?;
method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::Arc(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<T>>(self_index)) == Some(target_type_id) =>
{
Err(Error::UserDataBorrowMutError)
},
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::ArcMutex(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<Mutex<T>>>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Arc<Mutex<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let mut ud = ud.try_lock().map_err(|_| Error::UserDataBorrowMutError)?;
method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::ArcRwLock(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<RwLock<T>>>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Arc<RwLock<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let mut ud = ud.try_write().map_err(|_| Error::UserDataBorrowMutError)?;
method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::ArcParkingLotMutex(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<parking_lot::Mutex<T>>>(self_index))
== Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Arc<parking_lot::Mutex<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let mut ud = ud.try_lock().ok_or(Error::UserDataBorrowMutError)?;
method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::ArcParkingLotRwLock(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<parking_lot::RwLock<T>>>(self_index))
== Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Arc<parking_lot::RwLock<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let mut ud = ud.try_write().ok_or(Error::UserDataBorrowMutError)?;
method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua)
}))
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
})
}
@@ -346,6 +533,11 @@ impl<T> UserDataRegistry<T> {
}
value.into_lua(lua)
}
#[inline(always)]
pub(crate) fn into_raw(self) -> RawUserDataRegistry {
self.raw
}
}
// Returns function name for the type `T`, without the module path
@@ -359,10 +551,7 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
V: IntoLua + 'static,
{
let name = name.to_string();
self.fields.push((
name,
Box::new(move |rawlua| unsafe { value.push_into_stack(rawlua) }),
));
self.raw.fields.push((name, value.into_lua(self.lua.lua())));
}
fn add_field_method_get<M, R>(&mut self, name: impl ToString, method: M)
@@ -372,7 +561,7 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_method(&name, move |lua, data, ()| method(lua, data));
self.field_getters.push((name, callback));
self.raw.field_getters.push((name, callback));
}
fn add_field_method_set<M, A>(&mut self, name: impl ToString, method: M)
@@ -382,7 +571,7 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_method_mut(&name, method);
self.field_setters.push((name, callback));
self.raw.field_setters.push((name, callback));
}
fn add_field_function_get<F, R>(&mut self, name: impl ToString, function: F)
@@ -392,7 +581,7 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_function(&name, function);
self.field_getters.push((name, callback));
self.raw.field_getters.push((name, callback));
}
fn add_field_function_set<F, A>(&mut self, name: impl ToString, mut function: F)
@@ -402,20 +591,17 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_function_mut(&name, move |lua, (data, val)| function(lua, data, val));
self.field_setters.push((name, callback));
self.raw.field_setters.push((name, callback));
}
fn add_meta_field<V>(&mut self, name: impl ToString, value: V)
where
V: IntoLua + 'static,
{
let lua = self.lua.lua();
let name = name.to_string();
self.meta_fields.push((
name.clone(),
Box::new(move |rawlua| unsafe {
Self::check_meta_field(rawlua.lua(), &name, value)?.push_into_stack(rawlua)
}),
));
let field = Self::check_meta_field(lua, &name, value).and_then(|v| v.into_lua(lua));
self.raw.meta_fields.push((name, field));
}
fn add_meta_field_with<F, R>(&mut self, name: impl ToString, f: F)
@@ -423,14 +609,10 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
F: FnOnce(&Lua) -> Result<R> + 'static,
R: IntoLua,
{
let lua = self.lua.lua();
let name = name.to_string();
self.meta_fields.push((
name.clone(),
Box::new(move |rawlua| unsafe {
let lua = rawlua.lua();
Self::check_meta_field(lua, &name, f(lua)?)?.push_into_stack(rawlua)
}),
));
let field = f(lua).and_then(|v| Self::check_meta_field(lua, &name, v).and_then(|v| v.into_lua(lua)));
self.raw.meta_fields.push((name, field));
}
}
@@ -443,7 +625,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_method(&name, method);
self.methods.push((name, callback));
self.raw.methods.push((name, callback));
}
fn add_method_mut<M, A, R>(&mut self, name: impl ToString, method: M)
@@ -454,7 +636,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_method_mut(&name, method);
self.methods.push((name, callback));
self.raw.methods.push((name, callback));
}
#[cfg(feature = "async")]
@@ -468,7 +650,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_async_method(&name, method);
self.async_methods.push((name, callback));
self.raw.async_methods.push((name, callback));
}
#[cfg(feature = "async")]
@@ -482,7 +664,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_async_method_mut(&name, method);
self.async_methods.push((name, callback));
self.raw.async_methods.push((name, callback));
}
fn add_function<F, A, R>(&mut self, name: impl ToString, function: F)
@@ -493,7 +675,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_function(&name, function);
self.methods.push((name, callback));
self.raw.methods.push((name, callback));
}
fn add_function_mut<F, A, R>(&mut self, name: impl ToString, function: F)
@@ -504,7 +686,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_function_mut(&name, function);
self.methods.push((name, callback));
self.raw.methods.push((name, callback));
}
#[cfg(feature = "async")]
@@ -517,7 +699,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_async_function(&name, function);
self.async_methods.push((name, callback));
self.raw.async_methods.push((name, callback));
}
fn add_meta_method<M, A, R>(&mut self, name: impl ToString, method: M)
@@ -528,7 +710,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_method(&name, method);
self.meta_methods.push((name, callback));
self.raw.meta_methods.push((name, callback));
}
fn add_meta_method_mut<M, A, R>(&mut self, name: impl ToString, method: M)
@@ -539,7 +721,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_method_mut(&name, method);
self.meta_methods.push((name, callback));
self.raw.meta_methods.push((name, callback));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
@@ -553,7 +735,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_async_method(&name, method);
self.async_meta_methods.push((name, callback));
self.raw.async_meta_methods.push((name, callback));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
@@ -567,7 +749,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_async_method_mut(&name, method);
self.async_meta_methods.push((name, callback));
self.raw.async_meta_methods.push((name, callback));
}
fn add_meta_function<F, A, R>(&mut self, name: impl ToString, function: F)
@@ -578,7 +760,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_function(&name, function);
self.meta_methods.push((name, callback));
self.raw.meta_methods.push((name, callback));
}
fn add_meta_function_mut<F, A, R>(&mut self, name: impl ToString, function: F)
@@ -589,7 +771,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_function_mut(&name, function);
self.meta_methods.push((name, callback));
self.raw.meta_methods.push((name, callback));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
@@ -602,31 +784,32 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_async_function(&name, function);
self.async_meta_methods.push((name, callback));
self.raw.async_meta_methods.push((name, callback));
}
}
macro_rules! lua_userdata_impl {
($type:ty) => {
($type:ty => $type_variant:tt) => {
lua_userdata_impl!($type, UserDataTypeId::$type_variant(TypeId::of::<$type>()));
};
($type:ty, $type_id:expr) => {
impl<T: UserData + 'static> UserData for $type {
fn register(registry: &mut UserDataRegistry<Self>) {
let type_id = TypeId::of::<T>();
let mut orig_registry = UserDataRegistry::new(type_id);
let mut orig_registry = UserDataRegistry::with_type_id(registry.lua.lua(), $type_id);
T::register(&mut orig_registry);
// Copy all fields, methods, etc. from the original registry
registry.fields.extend(orig_registry.fields);
registry.field_getters.extend(orig_registry.field_getters);
registry.field_setters.extend(orig_registry.field_setters);
registry.meta_fields.extend(orig_registry.meta_fields);
registry.methods.extend(orig_registry.methods);
(registry.raw.fields).extend(orig_registry.raw.fields);
(registry.raw.field_getters).extend(orig_registry.raw.field_getters);
(registry.raw.field_setters).extend(orig_registry.raw.field_setters);
(registry.raw.meta_fields).extend(orig_registry.raw.meta_fields);
(registry.raw.methods).extend(orig_registry.raw.methods);
#[cfg(feature = "async")]
registry.async_methods.extend(orig_registry.async_methods);
registry.meta_methods.extend(orig_registry.meta_methods);
(registry.raw.async_methods).extend(orig_registry.raw.async_methods);
(registry.raw.meta_methods).extend(orig_registry.raw.meta_methods);
#[cfg(feature = "async")]
registry
.async_meta_methods
.extend(orig_registry.async_meta_methods);
(registry.raw.async_meta_methods).extend(orig_registry.raw.async_meta_methods);
}
}
};
@@ -635,4 +818,25 @@ macro_rules! lua_userdata_impl {
// A special proxy object for UserData
pub(crate) struct UserDataProxy<T>(pub(crate) PhantomData<T>);
lua_userdata_impl!(UserDataProxy<T>);
lua_userdata_impl!(UserDataProxy<T>, UserDataTypeId::Shared(TypeId::of::<T>()));
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
lua_userdata_impl!(Rc<T> => Rc);
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
lua_userdata_impl!(Rc<RefCell<T>> => RcRefCell);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(Arc<T> => Arc);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(Arc<Mutex<T>> => ArcMutex);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(Arc<RwLock<T>> => ArcRwLock);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(Arc<parking_lot::Mutex<T>> => ArcParkingLotMutex);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(Arc<parking_lot::RwLock<T>> => ArcParkingLotRwLock);
#[cfg(test)]
mod assertions {
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(super::RawUserDataRegistry: Send);
}
+46
View File
@@ -0,0 +1,46 @@
use std::cell::Cell;
use std::marker::PhantomData;
use std::os::raw::c_int;
use super::UserDataStorage;
use crate::util::{get_userdata, take_userdata};
// This is a trick to check if a type is `Sync` or not.
// It uses leaked specialization feature from stdlib.
struct IsSync<'a, T> {
is_sync: &'a Cell<bool>,
_marker: PhantomData<T>,
}
impl<T> Clone for IsSync<'_, T> {
fn clone(&self) -> Self {
self.is_sync.set(false);
IsSync {
is_sync: self.is_sync,
_marker: PhantomData,
}
}
}
impl<T: Sync> Copy for IsSync<'_, T> {}
pub(crate) fn is_sync<T>() -> bool {
let is_sync = Cell::new(true);
let _ = [IsSync::<T> {
is_sync: &is_sync,
_marker: PhantomData,
}]
.clone();
is_sync.get()
}
pub(super) unsafe extern "C-unwind" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
let ud = get_userdata::<UserDataStorage<T>>(state, -1);
if !(*ud).is_borrowed() {
take_userdata::<UserDataStorage<T>>(state);
ffi::lua_pushboolean(state, 1);
} else {
ffi::lua_pushboolean(state, 0);
}
1
}
+14 -3
View File
@@ -21,9 +21,6 @@ pub(crate) use userdata::{
pub(crate) use userdata::push_uninit_userdata;
pub(crate) use userdata::push_userdata;
#[cfg(not(feature = "luau"))]
pub(crate) use userdata::userdata_destructor;
// Checks that Lua has enough free stack space for future stack operations. On failure, this will
// panic with an internal error message.
#[inline]
@@ -66,6 +63,11 @@ impl StackGuard {
pub(crate) fn with_top(state: *mut ffi::lua_State, top: c_int) -> StackGuard {
StackGuard { state, top }
}
#[inline]
pub(crate) fn keep(&mut self, n: c_int) {
self.top += n;
}
}
impl Drop for StackGuard {
@@ -129,6 +131,15 @@ pub(crate) unsafe fn push_table(
}
}
// Uses 4 stack spaces, does not call checkstack.
pub(crate) unsafe fn rawget_field(state: *mut ffi::lua_State, table: c_int, field: &str) -> Result<c_int> {
ffi::lua_pushvalue(state, table);
protect_lua!(state, 1, 1, |state| {
ffi::lua_pushlstring(state, field.as_ptr() as *const c_char, field.len());
ffi::lua_rawget(state, -2)
})
}
// Uses 4 stack spaces, does not call checkstack.
pub(crate) unsafe fn rawset_field(state: *mut ffi::lua_State, table: c_int, field: &str) -> Result<()> {
ffi::lua_pushvalue(state, table);
+65 -84
View File
@@ -1,9 +1,8 @@
use std::ffi::CStr;
use std::os::raw::{c_int, c_void};
use std::{ptr, str};
use crate::error::Result;
use crate::util::{check_stack, get_metatable_ptr, push_string, push_table, rawset_field, TypeKey};
use crate::util::{check_stack, get_metatable_ptr, push_table, rawget_field, rawset_field, TypeKey};
// Pushes the userdata and attaches a metatable with __gc method.
// Internally uses 3 stack spaces, does not call checkstack.
@@ -152,16 +151,12 @@ pub(crate) unsafe fn init_userdata_metatable(
field_getters: Option<c_int>,
field_setters: Option<c_int>,
methods: Option<c_int>,
extra_init: Option<fn(*mut ffi::lua_State) -> Result<()>>,
) -> Result<()> {
ffi::lua_pushvalue(state, metatable);
if field_getters.is_some() || methods.is_some() {
// Push `__index` generator function
init_userdata_metatable_index(state)?;
push_string(state, b"__index", true)?;
let index_type = ffi::lua_rawget(state, -3);
let index_type = rawget_field(state, metatable, "__index")?;
match index_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
for &idx in &[field_getters, methods] {
@@ -175,39 +170,31 @@ pub(crate) unsafe fn init_userdata_metatable(
// Generate `__index`
protect_lua!(state, 4, 1, fn(state) ffi::lua_call(state, 3, 1))?;
}
_ => mlua_panic!("improper __index type {}", index_type),
_ => mlua_panic!("improper `__index` type: {}", index_type),
}
rawset_field(state, -2, "__index")?;
rawset_field(state, metatable, "__index")?;
}
if let Some(field_setters) = field_setters {
// Push `__newindex` generator function
init_userdata_metatable_newindex(state)?;
push_string(state, b"__newindex", true)?;
let newindex_type = ffi::lua_rawget(state, -3);
let newindex_type = rawget_field(state, metatable, "__newindex")?;
match newindex_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
ffi::lua_pushvalue(state, field_setters);
// Generate `__newindex`
protect_lua!(state, 3, 1, fn(state) ffi::lua_call(state, 2, 1))?;
}
_ => mlua_panic!("improper __newindex type {}", newindex_type),
_ => mlua_panic!("improper `__newindex` type: {}", newindex_type),
}
rawset_field(state, -2, "__newindex")?;
}
// Additional initialization
if let Some(extra_init) = extra_init {
extra_init(state)?;
rawset_field(state, metatable, "__newindex")?;
}
ffi::lua_pushboolean(state, 0);
rawset_field(state, -2, "__metatable")?;
ffi::lua_pop(state, 1);
rawset_field(state, metatable, "__metatable")?;
Ok(())
}
@@ -234,50 +221,47 @@ unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()
ffi::lua_pop(state, 1);
// Create and cache `__index` generator
let code = cstr!(
r#"
local error, isfunction, istable = ...
return function (__index, field_getters, methods)
-- Common case: has field getters and index is a table
if field_getters ~= nil and methods == nil and istable(__index) then
return function (self, key)
local field_getter = field_getters[key]
if field_getter ~= nil then
return field_getter(self)
end
return __index[key]
end
end
let code = cr#"
local error, isfunction, istable = ...
return function (__index, field_getters, methods)
-- Common case: has field getters and index is a table
if field_getters ~= nil and methods == nil and istable(__index) then
return function (self, key)
if field_getters ~= nil then
local field_getter = field_getters[key]
if field_getter ~= nil then
return field_getter(self)
end
end
if methods ~= nil then
local method = methods[key]
if method ~= nil then
return method
end
end
if isfunction(__index) then
return __index(self, key)
elseif __index == nil then
error("attempt to get an unknown field '"..key.."'")
else
return __index[key]
local field_getter = field_getters[key]
if field_getter ~= nil then
return field_getter(self)
end
return __index[key]
end
end
"#
);
let code_len = CStr::from_ptr(code).to_bytes().len();
return function (self, key)
if field_getters ~= nil then
local field_getter = field_getters[key]
if field_getter ~= nil then
return field_getter(self)
end
end
if methods ~= nil then
local method = methods[key]
if method ~= nil then
return method
end
end
if isfunction(__index) then
return __index(self, key)
elseif __index == nil then
error("attempt to get an unknown field '"..key.."'")
else
return __index[key]
end
end
end
"#;
protect_lua!(state, 0, 1, |state| {
let ret = ffi::luaL_loadbuffer(state, code, code_len, cstr!("__mlua_index"));
let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_index"));
if ret != ffi::LUA_OK {
ffi::lua_error(state);
}
@@ -305,33 +289,30 @@ unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result
ffi::lua_pop(state, 1);
// Create and cache `__newindex` generator
let code = cstr!(
r#"
local error, isfunction = ...
return function (__newindex, field_setters)
return function (self, key, value)
if field_setters ~= nil then
local field_setter = field_setters[key]
if field_setter ~= nil then
field_setter(self, value)
return
end
end
if isfunction(__newindex) then
__newindex(self, key, value)
elseif __newindex == nil then
error("attempt to set an unknown field '"..key.."'")
else
__newindex[key] = value
let code = cr#"
local error, isfunction = ...
return function (__newindex, field_setters)
return function (self, key, value)
if field_setters ~= nil then
local field_setter = field_setters[key]
if field_setter ~= nil then
field_setter(self, value)
return
end
end
if isfunction(__newindex) then
__newindex(self, key, value)
elseif __newindex == nil then
error("attempt to set an unknown field '"..key.."'")
else
__newindex[key] = value
end
end
"#
);
let code_len = CStr::from_ptr(code).to_bytes().len();
end
"#;
protect_lua!(state, 0, 1, |state| {
let ret = ffi::luaL_loadbuffer(state, code, code_len, cstr!("__mlua_newindex"));
let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_newindex"));
if ret != ffi::LUA_OK {
ffi::lua_error(state);
}
@@ -351,7 +332,7 @@ unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result
}
#[cfg(not(feature = "luau"))]
pub(crate) unsafe extern "C-unwind" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
unsafe extern "C-unwind" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
// It's probably NOT a good idea to catch Rust panics in finalizer
// Lua 5.4 ignores it, other versions generates `LUA_ERRGCMM` without calling message handler
take_userdata::<T>(state);
+7 -2
View File
@@ -67,8 +67,7 @@ pub enum Value {
/// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned.
Error(Box<Error>),
/// Any other value not known to mlua (eg. LuaJIT CData).
#[allow(private_interfaces)]
Other(ValueRef),
Other(#[doc(hidden)] ValueRef),
}
pub use self::Value::Nil;
@@ -570,6 +569,12 @@ impl Value {
}
}
impl Default for Value {
fn default() -> Self {
Self::Nil
}
}
impl fmt::Debug for Value {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if fmt.alternate() {
+11 -8
View File
@@ -1,20 +1,23 @@
[lua54_coverage]
features = "lua54,vendored,async,send,serialize,macros,anyhow"
[lua54]
features = "lua54,vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
[lua54_with_memory_limit_coverage]
features = "lua54,vendored,async,send,serialize,macros"
[lua54_non_send]
features = "lua54,vendored,async,serialize,macros,anyhow,userdata-wrappers"
[lua54_with_memory_limit]
features = "lua54,vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
rustflags = "--cfg force_memory_limit"
[lua51_coverage]
[lua51]
features = "lua51,vendored,async,send,serialize,macros"
[lua51_with_memory_limit_coverage]
[lua51_with_memory_limit]
features = "lua51,vendored,async,send,serialize,macros"
rustflags = "--cfg force_memory_limit"
[luau_coverage]
[luau]
features = "luau,async,send,serialize,macros"
[luau_with_memory_limit_coverage]
[luau_with_memory_limit]
features = "luau,async,send,serialize,macros"
rustflags = "--cfg force_memory_limit"
+18 -1
View File
@@ -1,6 +1,6 @@
use std::{fs, io};
use mlua::{Lua, Result};
use mlua::{Chunk, Lua, Result};
#[test]
fn test_chunk_path() -> Result<()> {
@@ -121,3 +121,20 @@ fn test_compiler() -> Result<()> {
Ok(())
}
#[test]
fn test_chunk_wrap() -> Result<()> {
let lua = Lua::new();
let f = Chunk::wrap("return 123");
lua.globals().set("f", f)?;
lua.load("assert(f() == 123)").exec().unwrap();
lua.globals().set("f2", Chunk::wrap("c()"))?;
assert!(
(lua.load("f2()").exec().err().unwrap().to_string()).contains(file!()),
"wrong chunk location"
);
Ok(())
}
+1 -1
View File
@@ -7,7 +7,7 @@ fn test_debug_format() -> Result<()> {
// Globals
let globals = lua.globals();
let dump = format!("{globals:#?}");
assert!(dump.starts_with("{\n [\"_G\"] = table:"));
assert!(dump.starts_with("{\n _G = table:"));
// TODO: Other cases
+12
View File
@@ -75,9 +75,21 @@ fn test_function_calls() -> Result<()> {
let output = output.lock().unwrap();
if cfg!(feature = "luajit") && lua.load("jit.version_num").eval::<i64>()? >= 20100 {
#[cfg(not(force_memory_limit))]
assert_eq!(*output, vec![(None, "main"), (Some("len".to_string()), "Lua")]);
#[cfg(force_memory_limit)]
assert_eq!(
*output,
vec![(None, "C"), (None, "main"), (Some("len".to_string()), "Lua")]
);
} else {
#[cfg(not(force_memory_limit))]
assert_eq!(*output, vec![(None, "main"), (Some("len".to_string()), "C")]);
#[cfg(force_memory_limit)]
assert_eq!(
*output,
vec![(None, "C"), (None, "main"), (Some("len".to_string()), "C")]
);
}
Ok(())
+28 -10
View File
@@ -97,17 +97,19 @@ fn test_require() -> Result<()> {
fn test_vectors() -> Result<()> {
let lua = Lua::new();
let v: Vector = lua.load("vector(1, 2, 3) + vector(3, 2, 1)").eval()?;
let v: Vector = lua
.load("vector.create(1, 2, 3) + vector.create(3, 2, 1)")
.eval()?;
assert_eq!(v, [4.0, 4.0, 4.0]);
// Test conversion into Rust array
let v: [f64; 3] = lua.load("vector(1, 2, 3)").eval()?;
let v: [f64; 3] = lua.load("vector.create(1, 2, 3)").eval()?;
assert!(v == [1.0, 2.0, 3.0]);
// Test vector methods
lua.load(
r#"
local v = vector(1, 2, 3)
local v = vector.create(1, 2, 3)
assert(v.x == 1)
assert(v.y == 2)
assert(v.z == 3)
@@ -118,7 +120,7 @@ fn test_vectors() -> Result<()> {
// Test vector methods (fastcall)
lua.load(
r#"
local v = vector(1, 2, 3)
local v = vector.create(1, 2, 3)
assert(v.x == 1)
assert(v.y == 2)
assert(v.z == 3)
@@ -135,17 +137,19 @@ fn test_vectors() -> Result<()> {
fn test_vectors() -> Result<()> {
let lua = Lua::new();
let v: Vector = lua.load("vector(1, 2, 3, 4) + vector(4, 3, 2, 1)").eval()?;
let v: Vector = lua
.load("vector.create(1, 2, 3, 4) + vector.create(4, 3, 2, 1)")
.eval()?;
assert_eq!(v, [5.0, 5.0, 5.0, 5.0]);
// Test conversion into Rust array
let v: [f64; 4] = lua.load("vector(1, 2, 3, 4)").eval()?;
let v: [f64; 4] = lua.load("vector.create(1, 2, 3, 4)").eval()?;
assert!(v == [1.0, 2.0, 3.0, 4.0]);
// Test vector methods
lua.load(
r#"
local v = vector(1, 2, 3, 4)
local v = vector.create(1, 2, 3, 4)
assert(v.x == 1)
assert(v.y == 2)
assert(v.z == 3)
@@ -157,7 +161,7 @@ fn test_vectors() -> Result<()> {
// Test vector methods (fastcall)
lua.load(
r#"
local v = vector(1, 2, 3, 4)
local v = vector.create(1, 2, 3, 4)
assert(v.x == 1)
assert(v.y == 2)
assert(v.z == 3)
@@ -180,10 +184,10 @@ fn test_vector_metatable() -> Result<()> {
r#"
{
__index = {
new = vector,
new = vector.create,
product = function(a, b)
return vector(a.x * b.x, a.y * b.y, a.z * b.z)
return vector.create(a.x * b.x, a.y * b.y, a.z * b.z)
end
}
}
@@ -278,6 +282,20 @@ fn test_sandbox() -> Result<()> {
Ok(())
}
#[test]
fn test_sandbox_safeenv() -> Result<()> {
let lua = Lua::new();
lua.sandbox(true)?;
lua.globals().set("state", lua.create_table()?)?;
lua.globals().set_safeenv(false);
lua.load("state.a = 123").exec()?;
let a: i32 = lua.load("state.a = 321; return state.a").eval()?;
assert_eq!(a, 321);
Ok(())
}
#[test]
fn test_sandbox_nolibs() -> Result<()> {
let lua = Lua::new_with(StdLib::NONE, LuaOptions::default()).unwrap();
+10
View File
@@ -31,6 +31,16 @@ fn test_memory_limit() -> Result<()> {
lua.set_memory_limit(0)?;
f.call::<()>(()).expect("should trigger no memory limit");
// Test memory limit during chunk loading
lua.set_memory_limit(1024)?;
match lua
.load("local t = {}; for i = 1,10000 do t[i] = i end")
.into_function()
{
Err(Error::MemoryError(_)) => {}
_ => panic!("did not trigger memory error"),
};
Ok(())
}
+26 -1
View File
@@ -1,4 +1,4 @@
use mlua::{Error, ExternalError, IntoLuaMulti, Lua, Result, String, Value};
use mlua::{Error, ExternalError, Integer, IntoLuaMulti, Lua, MultiValue, Result, String, Value, Variadic};
#[test]
fn test_result_conversions() -> Result<()> {
@@ -58,3 +58,28 @@ fn test_result_conversions() -> Result<()> {
Ok(())
}
#[test]
fn test_multivalue() {
let mut multi = MultiValue::with_capacity(3);
multi.push_back(Value::Integer(1));
multi.push_back(Value::Integer(2));
multi.push_front(Value::Integer(3));
assert_eq!(multi.iter().filter_map(|v| v.as_integer()).sum::<Integer>(), 6);
let vec = multi.into_vec();
assert_eq!(&vec, &[Value::Integer(3), Value::Integer(1), Value::Integer(2)]);
let _multi2 = MultiValue::from_vec(vec);
}
#[test]
fn test_variadic() {
let mut var = Variadic::with_capacity(3);
var.extend_from_slice(&[1, 2, 3]);
assert_eq!(var.iter().sum::<u32>(), 6);
let vec = Vec::<u32>::from(var);
assert_eq!(&vec, &[1, 2, 3]);
let var2 = Variadic::from(vec);
assert_eq!(var2.as_slice(), &[1, 2, 3]);
}
+75 -6
View File
@@ -1,6 +1,7 @@
use std::cell::Cell;
use std::rc::Rc;
use std::string::String as StdString;
use std::sync::Arc;
use mlua::{
AnyUserData, Error, Function, Lua, MetaMethod, ObjectLike, Result, String, UserData, UserDataFields,
@@ -66,6 +67,27 @@ fn test_scope_outer_lua_access() -> Result<()> {
Ok(())
}
#[test]
fn test_scope_capture_scope() -> Result<()> {
let lua = Lua::new();
let i = Cell::new(0);
lua.scope(|scope| {
let f = scope.create_function(|_, ()| {
scope.create_function(|_, n: u32| {
i.set(i.get() + n);
Ok(())
})
})?;
f.call::<Function>(())?.call::<()>(10)?;
Ok(())
})?;
assert_eq!(i.get(), 10);
Ok(())
}
#[test]
fn test_scope_userdata_fields() -> Result<()> {
struct MyUserData<'a>(&'a Cell<i64>);
@@ -388,15 +410,26 @@ fn test_scope_userdata_ref_mut() -> Result<()> {
fn test_scope_any_userdata() -> Result<()> {
let lua = Lua::new();
lua.register_userdata_type::<StdString>(|reg| {
reg.add_meta_method("__tostring", |_, data, ()| Ok(data.clone()));
})?;
fn register(reg: &mut UserDataRegistry<&mut StdString>) {
reg.add_method_mut("push", |_, this, s: String| {
this.push_str(&s.to_str()?);
Ok(())
});
reg.add_meta_method("__tostring", |_, data, ()| Ok((*data).clone()));
}
let data = StdString::from("foo");
let mut data = StdString::from("foo");
lua.scope(|scope| {
let ud = scope.create_any_userdata_ref(&data)?;
let ud = scope.create_any_userdata(&mut data, register)?;
lua.globals().set("ud", ud)?;
lua.load("assert(tostring(ud) == 'foo')").exec()
lua.load(
r#"
assert(tostring(ud) == "foo")
ud:push("bar")
assert(tostring(ud) == "foobar")
"#,
)
.exec()
})?;
// Check that userdata is destructed
@@ -463,6 +496,42 @@ fn test_scope_any_userdata_ref_mut() -> Result<()> {
Ok(())
}
#[test]
fn test_scope_destructors() -> Result<()> {
let lua = Lua::new();
lua.register_userdata_type::<Arc<StdString>>(|reg| {
reg.add_meta_method("__tostring", |_, data, ()| Ok(data.to_string()));
})?;
let arc_str = Arc::new(StdString::from("foo"));
let ud = lua.create_any_userdata(arc_str.clone())?;
lua.scope(|scope| {
scope.add_destructor(|| {
assert!(ud.destroy().is_ok());
});
Ok(())
})?;
assert_eq!(Arc::strong_count(&arc_str), 1);
// Try destructing the userdata while it's borrowed
let ud = lua.create_any_userdata(arc_str.clone())?;
ud.borrow_scoped::<Arc<StdString>, _>(|arc_str| {
assert_eq!(arc_str.as_str(), "foo");
lua.scope(|scope| {
scope.add_destructor(|| {
assert!(ud.destroy().is_err());
});
Ok(())
})
.unwrap();
assert_eq!(arc_str.as_str(), "foo");
})?;
Ok(())
}
fn modify_userdata(lua: &Lua, ud: &AnyUserData) -> Result<()> {
lua.load(
r#"
+59 -9
View File
@@ -4,25 +4,35 @@ use std::cell::UnsafeCell;
use std::marker::PhantomData;
use std::string::String as StdString;
use mlua::{AnyUserData, Error, Lua, Result, UserDataRef};
use mlua::{AnyUserData, Error, Lua, ObjectLike, Result, UserData, UserDataMethods, UserDataRef};
use static_assertions::{assert_impl_all, assert_not_impl_all};
#[test]
fn test_userdata_multithread_access() -> Result<()> {
fn test_userdata_multithread_access_send_only() -> Result<()> {
let lua = Lua::new();
// This type is `Send` but not `Sync`.
struct MyUserData(#[allow(unused)] StdString, PhantomData<UnsafeCell<()>>);
struct MyUserData(StdString, PhantomData<UnsafeCell<()>>);
assert_impl_all!(MyUserData: Send);
assert_not_impl_all!(MyUserData: Sync);
lua.globals().set(
"ud",
AnyUserData::wrap(MyUserData("hello".to_string(), PhantomData)),
)?;
impl UserData for MyUserData {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("method", |lua, this, ()| {
let ud = lua.globals().get::<AnyUserData>("ud")?;
assert_eq!(ud.call_method::<String>("method2", ())?, "method2");
Ok(this.0.clone())
});
methods.add_method("method2", |_, _, ()| Ok("method2"));
}
}
lua.globals()
.set("ud", MyUserData("hello".to_string(), PhantomData))?;
// We acquired the exclusive reference.
let _ud1 = lua.globals().get::<UserDataRef<MyUserData>>("ud")?;
let ud = lua.globals().get::<UserDataRef<MyUserData>>("ud")?;
std::thread::scope(|s| {
s.spawn(|| {
@@ -31,5 +41,45 @@ fn test_userdata_multithread_access() -> Result<()> {
});
});
drop(ud);
lua.load("ud:method()").exec().unwrap();
Ok(())
}
#[test]
fn test_userdata_multithread_access_sync() -> Result<()> {
let lua = Lua::new();
// This type is `Send` and `Sync`.
struct MyUserData(StdString);
assert_impl_all!(MyUserData: Send, Sync);
impl UserData for MyUserData {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("method", |lua, this, ()| {
let ud = lua.globals().get::<AnyUserData>("ud")?;
assert!(ud.call_method::<()>("method2", ()).is_ok());
Ok(this.0.clone())
});
methods.add_method("method2", |_, _, ()| Ok(()));
}
}
lua.globals().set("ud", MyUserData("hello".to_string()))?;
// We acquired the shared reference.
let _ud = lua.globals().get::<UserDataRef<MyUserData>>("ud")?;
std::thread::scope(|s| {
s.spawn(|| {
// Getting another shared reference for `Sync` type is allowed.
let _ = lua.globals().get::<UserDataRef<MyUserData>>("ud").unwrap();
});
});
lua.load("ud:method()").exec().unwrap();
Ok(())
}
+4 -14
View File
@@ -133,13 +133,7 @@ fn test_serialize_failure() -> Result<(), Box<dyn StdError>> {
fn test_serialize_vector() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
let globals = lua.globals();
globals.set(
"vector",
lua.create_function(|_, (x, y, z)| Ok(mlua::Vector::new(x, y, z)))?,
)?;
let val = lua.load("{_vector = vector(1, 2, 3)}").eval::<Value>()?;
let val = lua.load("{_vector = vector.create(1, 2, 3)}").eval::<Value>()?;
let json = serde_json::json!({
"_vector": [1.0, 2.0, 3.0],
});
@@ -156,13 +150,9 @@ fn test_serialize_vector() -> Result<(), Box<dyn StdError>> {
fn test_serialize_vector() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
let globals = lua.globals();
globals.set(
"vector",
lua.create_function(|_, (x, y, z, w)| Ok(mlua::Vector::new(x, y, z, w)))?,
)?;
let val = lua.load("{_vector = vector(1, 2, 3, 4)}").eval::<Value>()?;
let val = lua
.load("{_vector = vector.create(1, 2, 3, 4)}")
.eval::<Value>()?;
let json = serde_json::json!({
"_vector": [1.0, 2.0, 3.0, 4.0],
});
+31 -2
View File
@@ -96,8 +96,8 @@ fn test_string_fmt_debug() -> Result<()> {
assert_eq!(format!("{:?}", s.as_bytes()), "[104, 101, 108, 108, 111]");
// Invalid utf8
let s = lua.create_string(b"hello\0world\r\n\t\xF0\x90\x80")?;
assert_eq!(format!("{s:?}"), r#"b"hello\0world\r\n\t\xF0\x90\x80""#);
let s = lua.create_string(b"hello\0world\r\n\t\xf0\x90\x80")?;
assert_eq!(format!("{s:?}"), r#"b"hello\0world\r\n\t\xf0\x90\x80""#);
Ok(())
}
@@ -114,3 +114,32 @@ fn test_string_pointer() -> Result<()> {
Ok(())
}
#[test]
fn test_string_display() -> Result<()> {
let lua = Lua::new();
let s = lua.create_string("hello")?;
assert_eq!(format!("{}", s.display()), "hello");
// With invalid utf8
let s = lua.create_string(b"hello\0world\xFF")?;
assert_eq!(format!("{}", s.display()), "hello\0world");
Ok(())
}
#[test]
fn test_string_wrap() -> Result<()> {
let lua = Lua::new();
let s = String::wrap("hello, world");
lua.globals().set("s", s)?;
assert_eq!(lua.globals().get::<String>("s")?, "hello, world");
let s2 = String::wrap("hello, world (owned)".to_string());
lua.globals().set("s2", s2)?;
assert_eq!(lua.globals().get::<String>("s2")?, "hello, world (owned)");
Ok(())
}
+8 -1
View File
@@ -397,6 +397,7 @@ fn test_table_fmt() -> Result<()> {
.load(
r#"
local t = {1, 2, 3, a = 5, b = { 6 }}
t["special-<chars>"] = 10
t[9.2] = 9.2
t[1.99] = 1.99
t[true] = true
@@ -410,7 +411,13 @@ fn test_table_fmt() -> Result<()> {
// Pretty print
assert_eq!(
format!("{table:#?}"),
"{\n [false] = false,\n [true] = true,\n [1] = 1,\n [1.99] = 1.99,\n [2] = 2,\n [3] = 3,\n [9.2] = 9.2,\n [\"a\"] = 5,\n [\"b\"] = {\n [1] = 6,\n },\n}"
"{\n [false] = false,\n [true] = true,\n [1] = 1,\n [1.99] = 1.99,\n [2] = 2,\n [3] = 3,\n [9.2] = 9.2,\n a = 5,\n b = {\n 6,\n },\n [\"special-<chars>\"] = 10,\n}"
);
let table2 = lua.create_table_from([("1", "first"), ("2", "second")])?;
assert_eq!(
format!("{table2:#?}"),
"{\n [\"1\"] = \"first\",\n [\"2\"] = \"second\",\n}"
);
Ok(())
+20
View File
@@ -844,10 +844,12 @@ fn test_application_data() -> Result<()> {
assert_eq!(format!("{s:?}"), "\"test1\"");
// Borrowing immutably and mutably of the same type is not allowed
assert!(lua.try_app_data_mut::<&str>().is_err());
match catch_unwind(AssertUnwindSafe(|| lua.app_data_mut::<&str>().unwrap())) {
Ok(_) => panic!("expected panic"),
Err(_) => {}
}
assert!(lua.try_app_data_ref::<Vec<&str>>().is_err());
drop((s, v));
// Test that application data is accessible from anywhere
@@ -1375,3 +1377,21 @@ fn test_exec_raw() -> Result<()> {
Ok(())
}
#[test]
fn test_gc_drop_ref_thread() -> Result<()> {
let lua = Lua::new();
let t = lua.create_table()?;
lua.create_function(move |_, ()| {
_ = &t;
Ok(())
})?;
for _ in 0..10000 {
// GC will run eventually to collect the function and the table above
lua.create_table()?;
}
Ok(())
}
+186 -1
View File
@@ -376,7 +376,18 @@ fn test_userdata_take() -> Result<()> {
fn test_userdata_destroy() -> Result<()> {
struct MyUserdata(#[allow(unused)] Arc<()>);
impl UserData for MyUserdata {}
impl UserData for MyUserdata {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("try_destroy", |lua, _this, ()| {
let ud = lua.globals().get::<AnyUserData>("ud")?;
match ud.destroy() {
Err(Error::UserDataBorrowMutError) => {}
r => panic!("expected `UserDataBorrowMutError` error, got {:?}", r),
}
Ok(())
});
}
}
let rc = Arc::new(());
@@ -394,6 +405,23 @@ fn test_userdata_destroy() -> Result<()> {
assert_eq!(Arc::strong_count(&rc), 1);
let ud = lua.create_userdata(MyUserdata(rc.clone()))?;
assert_eq!(Arc::strong_count(&rc), 2);
let ud_ref = ud.borrow::<MyUserdata>()?;
// With active `UserDataRef` this methods only marks userdata as destructed
// without running destructor
ud.destroy()?;
assert_eq!(Arc::strong_count(&rc), 2);
drop(ud_ref);
assert_eq!(Arc::strong_count(&rc), 1);
// We cannot destroy (internally) borrowed userdata
let ud = lua.create_userdata(MyUserdata(rc.clone()))?;
lua.globals().set("ud", &ud)?;
lua.load("ud:try_destroy()").exec().unwrap();
ud.destroy()?;
assert_eq!(Arc::strong_count(&rc), 1);
Ok(())
}
@@ -881,3 +909,160 @@ fn test_nested_userdata_gc() -> Result<()> {
Ok(())
}
#[cfg(feature = "userdata-wrappers")]
#[test]
fn test_userdata_wrappers() -> Result<()> {
struct MyUserData(i64);
impl UserData for MyUserData {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field("static", "constant");
fields.add_field_method_get("data", |_, this| Ok(this.0));
fields.add_field_method_set("data", |_, this, val| {
this.0 = val;
Ok(())
})
}
}
let lua = Lua::new();
let globals = lua.globals();
// Rc<T>
#[cfg(not(feature = "send"))]
{
let ud = std::rc::Rc::new(MyUserData(1));
globals.set("rc_ud", ud.clone())?;
lua.load(
r#"
assert(rc_ud.static == "constant")
local ok, err = pcall(function() rc_ud.data = 2 end)
assert(
tostring(err):sub(1, 32) == "error mutably borrowing userdata",
"expected error mutably borrowing userdata, got " .. tostring(err)
)
assert(rc_ud.data == 1)
"#,
)
.exec()
.unwrap();
globals.set("rc_ud", Nil)?;
lua.gc_collect()?;
assert_eq!(std::rc::Rc::strong_count(&ud), 1);
}
// Rc<RefCell<T>>
#[cfg(not(feature = "send"))]
{
let ud = std::rc::Rc::new(std::cell::RefCell::new(MyUserData(2)));
globals.set("rc_refcell_ud", ud.clone())?;
lua.load(
r#"
assert(rc_refcell_ud.static == "constant")
rc_refcell_ud.data = rc_refcell_ud.data + 1
assert(rc_refcell_ud.data == 3)
"#,
)
.exec()?;
assert_eq!(ud.borrow().0, 3);
globals.set("rc_refcell_ud", Nil)?;
lua.gc_collect()?;
assert_eq!(std::rc::Rc::strong_count(&ud), 1);
}
// Arc<T>
{
let ud = Arc::new(MyUserData(3));
globals.set("arc_ud", ud.clone())?;
lua.load(
r#"
assert(arc_ud.static == "constant")
local ok, err = pcall(function() arc_ud.data = 10 end)
assert(
tostring(err):sub(1, 32) == "error mutably borrowing userdata",
"expected error mutably borrowing userdata, got " .. tostring(err)
)
assert(arc_ud.data == 3)
"#,
)
.exec()?;
globals.set("arc_ud", Nil)?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&ud), 1);
}
// Arc<Mutex<T>>
{
let ud = Arc::new(std::sync::Mutex::new(MyUserData(4)));
globals.set("arc_mutex_ud", ud.clone())?;
lua.load(
r#"
assert(arc_mutex_ud.static == "constant")
arc_mutex_ud.data = arc_mutex_ud.data + 1
assert(arc_mutex_ud.data == 5)
"#,
)
.exec()?;
assert_eq!(ud.lock().unwrap().0, 5);
globals.set("arc_mutex_ud", Nil)?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&ud), 1);
}
// Arc<RwLock<T>>
{
let ud = Arc::new(std::sync::RwLock::new(MyUserData(6)));
globals.set("arc_rwlock_ud", ud.clone())?;
lua.load(
r#"
assert(arc_rwlock_ud.static == "constant")
arc_rwlock_ud.data = arc_rwlock_ud.data + 1
assert(arc_rwlock_ud.data == 7)
"#,
)
.exec()?;
assert_eq!(ud.read().unwrap().0, 7);
globals.set("arc_rwlock_ud", Nil)?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&ud), 1);
}
// Arc<parking_lot::Mutex<T>>
{
let ud = Arc::new(parking_lot::Mutex::new(MyUserData(8)));
globals.set("arc_parking_lot_mutex_ud", ud.clone())?;
lua.load(
r#"
assert(arc_parking_lot_mutex_ud.static == "constant")
arc_parking_lot_mutex_ud.data = arc_parking_lot_mutex_ud.data + 1
assert(arc_parking_lot_mutex_ud.data == 9)
"#,
)
.exec()?;
assert_eq!(ud.lock().0, 9);
globals.set("arc_parking_lot_mutex_ud", Nil)?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&ud), 1);
}
// Arc<parking_lot::RwLock<T>>
{
let ud = Arc::new(parking_lot::RwLock::new(MyUserData(10)));
globals.set("arc_parking_lot_rwlock_ud", ud.clone())?;
lua.load(
r#"
assert(arc_parking_lot_rwlock_ud.static == "constant")
arc_parking_lot_rwlock_ud.data = arc_parking_lot_rwlock_ud.data + 1
assert(arc_parking_lot_rwlock_ud.data == 11)
"#,
)
.exec()?;
assert_eq!(ud.read().0, 11);
globals.set("arc_parking_lot_rwlock_ud", Nil)?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&ud), 1);
}
Ok(())
}
+22
View File
@@ -296,3 +296,25 @@ fn test_value_conversions() -> Result<()> {
Ok(())
}
#[test]
fn test_value_exhaustive_match() {
match Value::Nil {
Value::Nil => {}
Value::Boolean(_) => {}
Value::LightUserData(_) => {}
Value::Integer(_) => {}
Value::Number(_) => {}
#[cfg(feature = "luau")]
Value::Vector(_) => {}
Value::String(_) => {}
Value::Table(_) => {}
Value::Function(_) => {}
Value::Thread(_) => {}
Value::UserData(_) => {}
#[cfg(feature = "luau")]
Value::Buffer(_) => {}
Value::Error(_) => {}
Value::Other(_) => {}
}
}