Compare commits

...

120 Commits

Author SHA1 Message Date
Alex Orlenko 13ff0ca798 v0.11.3 2025-08-29 23:11:21 +01:00
Alex Orlenko 44f49e35d6 Update CHANGELOG 2025-08-29 00:18:06 +01:00
Alex Orlenko e1ee4058a6 Add new benchmark to measure complex userdata method calls 2025-08-28 23:56:03 +01:00
Alex Orlenko f06d0020ea Add test to emulate method through field 2025-08-28 23:50:18 +01:00
Alex Orlenko d399559d30 Add Lua::yield_with to allow yielding Rust async functions and exchange values between Lua coroutine and Rust.
This functionality is similar to `coroutine.yield` and `coroutine.resume` without C restrictions.
2025-08-28 18:41:24 +01:00
Alex Orlenko 30735d5ff1 Fix thread recovery when pushing a bad arg
We should not erase thread stack if a bad argument is pushed before resuming the thread.
2025-08-25 23:07:37 +01:00
Alex Orlenko 75c23e5853 Add lua_cpcall to Luau ffi (0.688+) 2025-08-25 12:54:17 +01:00
Alex Orlenko 347856b806 Do not try to yield at non-yielable points in Luau interrupt
In particular we cannot yeild across metamethod/C-call boundaries.
This behaviour matches with Lua 5.3+ yielding from hooks only at safe points.
Closes #632
2025-08-25 12:19:50 +01:00
Alex Orlenko 774a63bece Add Buffer::cursor() method
This can be useful for providing access to buffers through core IO traits.
2025-08-24 11:29:01 +01:00
Alex Orlenko c481c87eac Add Lua::create_buffer_with_capacity method
This allow creating a preallocated buffer with specified size initialized to zero.
2025-08-23 22:38:55 +01:00
Alex Orlenko 85b280a9d6 Update nightly Rust error message matching 2025-08-23 09:40:13 +01:00
Alex Orlenko db7b782d3c Remove lifetimes from short type names 2025-08-23 09:13:31 +01:00
Alex Orlenko 5f38445558 Fix warnings 2025-08-20 16:25:06 +01:00
Alex Orlenko df0a44d405 Make Lua reference values cheap to clone
Instead of locking the VM and making a copy on auxiliary thread, track number of references using Rust ref counter.
This should also help reducing number of used references (they are limited to to 1M usually) on auxiliary thread.
2025-08-20 12:05:37 +01:00
Alex Orlenko f0806a6d62 Lower fastpath table creation limit to 1 << 26
When Lua is configured without memory restrictions, we use fastpath for table creation (unprotected mode).
In generally it's safe as long as we `abort()` on allocation failure.
However some Lua versions have additional restrictions on table size that we need to adhere in mlua too.
Probably Luau has the lowest limits.
Fixes #627
2025-08-13 22:49:40 +01:00
Alex Orlenko 3516f4c6ca v0.11.2 2025-08-10 00:53:45 +01:00
Alex Orlenko ca73583714 Update CHANGELOG 2025-08-10 00:53:01 +01:00
Alex Orlenko 36560435f7 Add push_into_stack_multi fastpath to Variadic 2025-08-10 00:35:51 +01:00
Alex Orlenko 763c2b2564 Update repl example: don't print newline if no values returned 2025-08-10 00:20:20 +01:00
Alex Orlenko bafdb6138c Update dependencies 2025-08-10 00:19:54 +01:00
Alex Orlenko c9d6a610e1 mlua-sys: v0.8.3 2025-08-10 00:11:05 +01:00
Alex Orlenko bd63f63bc9 Use ascii lowercase for module aliases
This matches with Luau 0.686 changes
2025-08-09 19:14:31 +01:00
piz-ewing c035c23a15 fix: normalize_chunk_name handles Windows paths with drive letter (#623)
Co-authored-by: ewing <ewing@MacBook-Pro.local>
2025-08-04 22:34:36 +01:00
Alex Orlenko cb153a52b2 Make Luau registered aliases case-insensitive
Executing `require("@my_module")` or `require("@My_Module")` should give the same result and use case-insensitive name.
See #620 for details
2025-07-26 22:23:16 +01:00
Alex Orlenko b1c69d3005 Use to_bits comparison to check if a float value can be represented as an integer losslessly.
This allows to simplify the code while still maintaining "negative zeros" edge case.
Thanks @JasonHise for the suggestion.
2025-07-25 21:25:08 +01:00
Alex Orlenko 841bd332e4 Fix LuaJIT negative zero tests 2025-07-25 15:24:04 +01:00
Alex Orlenko 815d1bd7c9 Better handling negative zeros to match Lua 5.3+ behavior
In Lua 5.3+ the function `lua_isinteger` returns "false" for -0.0 numbers.
In earlier Lua versions we should follow the same behavior to avoid losing the sign when converting to Integer.
Close #618
2025-07-25 14:32:47 +01:00
Alex Orlenko 78331ceebe v0.11.1 2025-07-15 22:43:18 +01:00
Alex Orlenko f945a35cbd Execute metatable destructor in Table::set_metatable at the end of invocation
Before this change, destructor was executed shortly after pushing metatable to ref_thread.
2025-07-15 19:14:46 +01:00
Alex Orlenko 459edb6816 Always grow aux ref stack considering the reserve 2025-07-15 16:32:22 +01:00
Alex Orlenko 00328b0b64 Protect Lua::push_c_function for Lua <5.2 2025-07-15 16:11:31 +01:00
Alex Orlenko 928d94d255 v0.11.0 2025-07-14 15:33:02 +01:00
Alex Orlenko 583c35a172 Prepare for v0.11.0 2025-07-12 22:47:20 +01:00
Alex Orlenko 1791c599f4 mlua-sys: v0.8.2 2025-07-12 22:46:37 +01:00
Alex Orlenko 1e48817a64 Fix deregistering previously-registered userdata 2025-07-12 19:20:15 +01:00
Alex Orlenko 8d219503dd Opt-out from R: MaybeSend in AsyncThread<R> 2025-07-12 15:34:04 +01:00
Alex Orlenko 95367855c1 Return AsyncCallFuture<R> instead of opaque impl Future from ObjectLike trait. 2025-07-12 15:30:58 +01:00
Alex Orlenko 49389c4aa4 Wrap Function::coverage callback to RefCell (Luau) 2025-07-12 13:34:39 +01:00
Alex Orlenko 13dc2b5352 Don't release Lua lock prematurely when when accessing Buffer bytes (Luau) 2025-07-12 13:08:33 +01:00
Alex Orlenko 7afbf74128 Add MaybeSend bound to async methods on ObjectLike trait (sealed) 2025-07-12 12:45:36 +01:00
Alex Orlenko 06c3bd9d69 Fix serde README section (close #613) 2025-07-12 11:50:11 +01:00
Alex Orlenko 1ddaea60ce Bump luau-src to 0.15.4+luau682 2025-07-12 11:27:41 +01:00
Alex Orlenko a653d08768 Simplify Compiler::add_library_constant (combine lib and member) 2025-07-09 00:11:46 +01:00
Alex Orlenko 2b6b0144a1 Merge Compiler::set_vector_lib into set_vector_ctor 2025-07-08 23:55:45 +01:00
Alex Orlenko 4cfe0be945 Update CHANGELOG 2025-07-08 23:22:18 +01:00
Alex Orlenko a9a4814c3c Use StdString for consistency in chunk.rs 2025-07-08 22:25:23 +01:00
Alex Orlenko b1f73ec29d Update Luau Compiler methods to better control extra options:
- Add `add_mutable_global`
- Add `add_userdata_type`
- Replace `set_library_constants` with `add_library_constant`
- Add `add_disabled_builtin`
2025-07-08 22:22:44 +01:00
Alex Orlenko 61a2141151 Don't panic when fourth library searcher does not exists.
When disabling C modules, we remove the last two searchers (C & C all-in-one).
In Pluto the C searches may not exist by design, in this case check that 4th searcher is present before removing it.
Closes #530
2025-07-08 13:37:48 +01:00
Alex Orlenko dea38f27a5 Change !cfg!(..) to cfg!(not(..)) for better readability 2025-07-08 10:42:15 +01:00
Sculas 04aaa18dc8 feat: Allow external build scripts to link Lua libraries (#529)
Allow external build scripts to link Lua libraries
2025-07-08 10:30:26 +01:00
Alex Orlenko d8455c038a Update CHANGELOG 2025-07-08 10:12:03 +01:00
Alex Orlenko ca22ea3be7 Deprecate Debug::curr_line() in favour of Debug::current_line() that returns Option 2025-07-08 10:09:26 +01:00
Alex Orlenko cf05593d66 Fix Debug::is_tail_call 2025-07-08 10:05:37 +01:00
Alex Orlenko d011a1f851 Update CHANGELOG 2025-07-07 23:15:46 +01:00
Alex Orlenko 1ec4661bf9 mlua_derive: v0.11.0 2025-07-07 22:58:41 +01:00
Alex Orlenko d3b2999d2f Remove MaybeSend requirement from Require trait and add to Lua::create_require_function instead 2025-07-07 22:58:24 +01:00
Alex Orlenko 72f6536efb Check table requested capacity limits before enabling unprotected mode.
Lua tables have limits and can overflow, which must be captured in protected mode.
2025-07-07 22:57:30 +01:00
Alex Orlenko 646827a6bb Update Table::set_metatable
- Return Err (instead of panic) when trying to change readonly table (Luau)
- Slightly optimize performance
2025-07-06 11:37:02 +01:00
Alex Orlenko 1882931cd9 Optimize Table::metatable 2025-07-06 10:57:26 +01:00
Alex Orlenko c90cac5189 Add Lua::set_globals method to replace global environment.
Closes #611
2025-07-06 10:57:25 +01:00
Alex Orlenko c0d839d8d2 Make Thread::state pub (hidden) 2025-07-05 22:36:47 +01:00
Alex Orlenko 80471c6dad Optimize AnyUserData::metatable 2025-07-05 11:33:11 +01:00
Alex Orlenko 4b9d1cf271 Replace impl ToString with Into<StdString>
This is a more canonical way to accept any types of stirng but not arbitrary types that implement `Display`
2025-07-05 11:21:18 +01:00
Alex Orlenko 55c07f3b28 Some minor changes in Luau TextRequirer (comments, naming, etc) 2025-07-03 14:42:24 +01:00
Alex Orlenko ef4eabd327 Don't use Value::as_str() internally 2025-07-01 22:51:16 +01:00
Alex Orlenko a3302afdc1 Deprecate Value::as_str and Value::as_string_lossy
These methods don't follow Rust naming convention, see
https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv
2025-07-01 22:35:40 +01:00
Alex Orlenko dfb4e9a668 Fix LuaJIT stack inspection tests 2025-07-01 21:40:06 +01:00
Alex Orlenko 92db0f6d3a Fix Lua::inspect_stack callback proto 2025-06-30 23:28:04 +01:00
Alex Orlenko a3697ab1db Add Debug::function method to get function running at a given level.
Close #607
2025-06-30 23:25:36 +01:00
Alex Orlenko 052740db15 Save lua_State at the moment of constructing Debug instead of resolving it dynamically 2025-06-30 22:38:46 +01:00
Alex Orlenko faf547c154 Refactor Lua::inspect_stack and debug interface.
It was possible to cause a crash when getting a `Debug` instance and keeping it while deallocating the Lua stack frames.
2025-06-30 12:21:10 +01:00
Alex Orlenko 0de7cd1c7d Don't move or wrap ffi::lua_Debug struct when inspecting stack
This can cause a crash if `ffi::lua_Debug` changed between `lua_getstack` and `lua_getinfo` calls.
Fixes #610
2025-06-29 11:49:34 +01:00
Alex Orlenko 58953e5635 v0.11.0-beta.3 2025-06-23 23:23:07 +01:00
Alex Orlenko 6406de405d mlua-sys: v0.8.1 2025-06-23 23:22:53 +01:00
Alex Orlenko 727096dd3b Handle OOM error during luau_load (Luau >= 0.679) 2025-06-23 23:22:36 +01:00
Alex Orlenko 2445230759 Update CHANGELOG 2025-06-23 22:44:36 +01:00
Alex Orlenko 1cd2bdc808 Ignore test_async_require on windows 2025-06-23 16:37:46 +01:00
Alex Orlenko f8ed33a2aa Fix tests 2025-06-23 16:23:53 +01:00
Alex Orlenko 25955893e0 (Luau Require) Resolve Lua file path relative to the current directory
and unrelated to Rust source file location.
When a Lua file is required inside a Rust file (in a chunk), we should resolve the Lua file relative to the current directory,
instead of relative to the Rust chunk path.
The Rust file location is an internal information that does not exist when the compiled binary runs.
Fixes #605
2025-06-23 15:53:35 +01:00
Alex Orlenko 3f0c69b70b Fix logic to terminate futures on drop.
The underlying Lua thread must stay in yielded state rather than finished.
2025-06-17 15:25:21 +01:00
Alex Orlenko 9da98d42c7 Move ref_stack_pop into ExtraData method. 2025-06-16 22:25:26 +01:00
Alex Orlenko f539f60987 Fix Function::deep_clone() method (Luau).
The `lua_clonefunction` function can fail (and trigger GC) so we need to return Result instead of allowing longjmp
2025-06-16 21:41:29 +01:00
Alex Orlenko aa187e6663 Increase REF_STACK_RESERVE to 3 slots 2025-06-16 21:37:16 +01:00
Alex Orlenko ec10bf2a39 Revert 2e0e86dab2 (Update stack_value helper) 2025-06-16 21:33:47 +01:00
Alex Orlenko 3ea80b763d cargo fmt 2025-06-16 11:21:51 +01:00
Alex Orlenko 2e0e86dab2 Update stack_value helper.
It uses zero stack spaces in Luau, and ref thread for `WrappedError` check in ther Lua versions.
Close #597
2025-06-16 11:12:53 +01:00
Alex Orlenko 0ac7aebb27 Update Lua::sandbox doc 2025-06-13 15:51:31 +01:00
Alex Orlenko 634e5d4550 Reduce collectgarbage options in sandboxed mode
See https://luau.org/sandbox#library
2025-06-13 15:47:54 +01:00
Alex Orlenko 05d6c20520 One more mismatched-lifetime-syntaxes 2025-06-12 23:44:47 +01:00
Alex Orlenko 63e7cfd31b Satisfy mismatched-lifetime-syntaxes lint (nightly)
See rust-lang/rust#141787
2025-06-12 16:30:48 +01:00
Alex Orlenko 9c24c99cbe v0.11.0-beta.2 2025-06-12 14:12:46 +01:00
Alex Orlenko 62f84828f2 Open some doc(hidden) functionality 2025-06-12 13:42:26 +01:00
Alex Orlenko 7bc72be7d3 Use serde feature flag instead of serialize.
The old one is still supported.
2025-06-12 13:35:22 +01:00
Ron Tseytlin 2fbbbe4238 Fix minor grammar mistakes in README.md (#591) 2025-06-12 12:57:11 +01:00
Alex Orlenko f00208373e Bump lua-src 2025-06-12 00:17:29 +01:00
Alex Orlenko a2dc662a92 Add RawLua::create_table_from (internal) 2025-06-11 23:55:06 +01:00
Alex Orlenko caeac2e9a3 Add private app_data container for mlua internal use 2025-06-11 22:24:26 +01:00
Alex Orlenko 3d5261640d Fix doc warnings 2025-06-06 23:25:36 +01:00
Alex Orlenko b57a6239a6 mlua_derive: v0.11.0-beta.2 2025-06-06 23:20:01 +01:00
Alex Orlenko 031424f6ce Update dev dependencies 2025-06-06 23:17:58 +01:00
Alex Orlenko c4c9609ac6 mlua-sys: v0.8.0 2025-06-06 23:09:30 +01:00
Alex Orlenko 6fcd18e434 Update CHANGELOG 2025-06-06 16:03:59 +01:00
Alex Orlenko 39cac5699a Add unwinding support (returning an Error) to Luau Require implementation 2025-06-06 13:46:51 +01:00
Alex Orlenko e6e1ef014f Some cosmetic changes (Luau "require") 2025-06-05 23:11:14 +01:00
Alex Orlenko 942a443592 Bump Lua 5.4 to 5.4.8 2025-06-05 15:02:47 +01:00
Alex Orlenko 65e292dac4 More Luau require tests 2025-06-03 23:01:50 +01:00
Alex Orlenko 1a82f83644 Move parking_lot/send_guard from send to userdata-wrappers feature.
Related to #553
2025-05-30 23:20:06 +01:00
Alex Orlenko 38fbd08c72 Use luau0-src v0.15.0 (Luau 0.676) 2025-05-30 21:56:27 +01:00
Alex Orlenko b3854d2f1d Add '=' prefix to __mlua_bind name 2025-05-28 14:03:10 +01:00
Alex Orlenko 00a56b115b Add LuaBorrowedBytes/LuaBorrowedStr to prelude 2025-05-28 14:01:35 +01:00
Alex Orlenko 2fefaafaa6 Update "AnyUserData::take" to work on ref thread without need to push into stack. 2025-05-28 12:13:34 +01:00
Alex Orlenko 76a8f8cc71 Add __type to Error's userdata metatable.
Close #585
2025-05-27 01:31:54 +01:00
Alex Orlenko 13395e9c3d Sync mlua_derive with AsChunk trait 2025-05-27 00:54:24 +01:00
Alex Orlenko c61219dd93 Update Luau Require trait (sync with 0.674) 2025-05-27 00:12:45 +01:00
Alex Orlenko 2e7c654cfe Make AsChunk trait dyn-friendly 2025-05-26 23:29:59 +01:00
Alex Orlenko f36aaa5ce1 Add loadstring function to Luau
Closes #578
2025-05-15 11:10:42 +01:00
Alex Orlenko df38878278 Update __mlua_index/__mlua_newindex chunk names 2025-05-15 11:03:30 +01:00
Alex Orlenko c5c1fe3b85 Fix tests 2025-05-14 00:19:29 +01:00
Alex Orlenko 0cc4b15f6b Generate doc for lua_module macro using doc cfg instead of docsrs 2025-05-13 21:56:11 +01:00
Alex Orlenko f16aca687d Terminate underlying Rust future when AsyncThread is dropped.
Before this change, Lua GC was responsible to collect and destroy the future if
`AsyncThread` dropped in yielded state.
Now we will propagate "drop" event immediately so Lua GC need to only free the memory.
2025-05-09 12:48:40 +01:00
83 changed files with 2362 additions and 1306 deletions
+14 -14
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,userdata-wrappers"
cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send"
cargo build --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers"
cargo build --features "${{ matrix.lua }},vendored,async,serde,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,anyhow,userdata-wrappers"
run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serde,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,anyhow,userdata-wrappers"
run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serde,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,anyhow,userdata-wrappers"
run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers"
shell: bash
test:
@@ -123,14 +123,14 @@ jobs:
- name: Run ${{ matrix.lua }} tests
run: |
cargo test --features "${{ matrix.lua }},vendored"
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"
cargo test --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers"
cargo test --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers,send"
shell: bash
- name: Run compile tests (macos lua54)
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }}
run: |
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros" -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" --tests -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serde,macros" --tests -- --ignored
shell: bash
test_with_sanitizer:
@@ -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,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
cargo test --tests --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
cargo test --tests --features "${{ matrix.lua }},vendored,async,serde,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,anyhow,userdata-wrappers"
cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serde,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,anyhow,userdata-wrappers"
cargo test --tests --features "${{ matrix.lua }},vendored,async,serde,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,userdata-wrappers"
clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers"
+54
View File
@@ -1,3 +1,57 @@
## v0.11.3 (Aug 30, 2025)
- Add `Lua::yield_with` to use as `coroutine.yield` functional replacement in async functions for any Lua
- Do not try to yield at non-yielable points in Luau interrupt (#632)
- Add `Buffer::cursor` method (Luau)
- Add `Lua::create_buffer_with_capacity` method (Luau)
- Make Lua reference values cheap to clone (only increments ref count)
- Fix panic on large (>67M entries) table creation
## v0.11.2 (Aug 10, 2025)
- Faster stack push for `Variadic<T>`
- Fix handling Windows paths with drive letter in Luau require (#623)
- Make Luau registered aliases ascii case-insensitive (#620)
- Fix deserializing negative zeros `-0.0` (#618)
## v0.11.1 (Jul 15, 2025)
- Fixed bug exhausting Lua auxiliary stack and leaving it without reserve (#615)
- `Lua::push_c_function` now correctly handles OOM for Lua 5.1 and Luau
## v0.11.0 (Jul 14, 2025)
Changes since v0.11.0-beta.3
- Allow linking external Lua libraries in a build script (e.g. pluto) using `external` mlua-sys feature flag
- `Lua::inspect_stack` takes a callback with `&Debug` argument, instead of returning `Debug` directly
- Added `Debug::function` method to get function running at a given level
- `Debug::curr_line` is deprecated in favour of `Debug::current_line` that returns `Option<usize>`
- Added `Lua::set_globals` method to replace global environment
- `Table::set_metatable` now returns `Result<()>` (this operation can fail in sandboxed Luau mode)
- `impl ToString` replaced with `Into<StdString>` in `UserData` registration
- `Value::as_str` and `Value::as_string_lossy` methods are deprecated (as they are non-idiomatic)
- Bugfixes and improvements
## v0.11.0-beta.3 (Jun 23, 2025)
- Luau in sandboxed mode has reduced options in `collectgarbage` function (to follow the official doc)
- `Function::deep_clone` now returns `Result<Function>` as this operation can trigger memory errors
- Luau "Require" resolves included Lua files relative to the current directory (#605)
- Fixed bug when finalizing `AsyncThread` on drop (`call_async` methods family)
## v0.11.0-beta.2 (Jun 12, 2025)
- Lua 5.4 updated to 5.4.8
- Terminate Rust `Future` when `AsyncThread` is dropped (without relying on Lua GC)
- Added `loadstring` function to Luau
- Make `AsChunk` trait dyn-friendly
- Luau `Require` trait synced with Luau 0.674
- Luau `Require` trait methods now can return `Error` variant (in `NavigateError` enum)
- Added `__type` to `Error`'s userdata metatable (for `typeof` function)
- `parking_log/send_guard` is moved to `userdata-wrappers` feature flag
- New `serde` feature flag to replace `serialize` (the old one is still available)
## v0.11.0-beta.1 (May 7th, 2025)
- New "require-by-string" for Luau (with `Require` trait and async support)
+17 -14
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.11.0-beta.1" # remember to update mlua_derive
version = "0.11.3" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
rust-version = "1.79.0"
edition = "2021"
@@ -16,7 +16,7 @@ with async/await features and support of writing native Lua modules in Rust.
"""
[package.metadata.docs.rs]
features = ["lua54", "vendored", "async", "send", "serialize", "macros"]
features = ["lua54", "vendored", "async", "send", "serde", "macros"]
rustdoc-args = ["--cfg", "docsrs"]
[workspace]
@@ -36,17 +36,20 @@ luau = ["ffi/luau"]
luau-jit = ["luau", "ffi/luau-codegen"]
luau-vector4 = ["luau", "ffi/luau-vector4"]
vendored = ["ffi/vendored"]
module = ["dep:mlua_derive", "ffi/module"]
module = ["mlua_derive", "ffi/module"]
async = ["dep:futures-util"]
send = ["parking_lot/send_guard", "error-send"]
send = ["error-send"]
error-send = []
serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value", "bstr/serde"]
serde = ["dep:serde", "dep:erased-serde", "dep:serde-value", "bstr/serde"]
macros = ["mlua_derive/macros"]
anyhow = ["dep:anyhow", "error-send"]
userdata-wrappers = []
userdata-wrappers = ["parking_lot/send_guard"]
# deprecated features
serialize = ["serde"]
[dependencies]
mlua_derive = { version = "=0.11.0-beta.1", optional = true, path = "mlua_derive" }
mlua_derive = { version = "=0.11.0", optional = true, path = "mlua_derive" }
bstr = { version = "1.0", features = ["std"], default-features = false }
either = "1.0"
num-traits = { version = "0.2.14" }
@@ -59,7 +62,7 @@ parking_lot = { version = "0.12", features = ["arc_lock"] }
anyhow = { version = "1.0", optional = true }
rustversion = "1.0"
ffi = { package = "mlua-sys", version = "0.7.0", path = "mlua-sys" }
ffi = { package = "mlua-sys", version = "0.8.3", path = "mlua-sys" }
[dev-dependencies]
trybuild = "1.0"
@@ -75,8 +78,8 @@ tempfile = "3"
static_assertions = "1.0"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
criterion = { version = "0.5", features = ["async_tokio"] }
rustyline = "15.0"
criterion = { version = "0.7", features = ["async_tokio"] }
rustyline = "17.0"
tokio = { version = "1.0", features = ["full"] }
[lints.rust]
@@ -90,7 +93,7 @@ required-features = ["async"]
[[bench]]
name = "serde"
harness = false
required-features = ["serialize"]
required-features = ["serde"]
[[example]]
name = "async_http_client"
@@ -98,7 +101,7 @@ required-features = ["async", "macros"]
[[example]]
name = "async_http_reqwest"
required-features = ["async", "serialize", "macros"]
required-features = ["async", "serde", "macros"]
[[example]]
name = "async_http_server"
@@ -113,8 +116,8 @@ name = "guided_tour"
required-features = ["macros"]
[[example]]
name = "serialize"
required-features = ["serialize"]
name = "serde"
required-features = ["serde"]
[[example]]
name = "userdata"
+40 -46
View File
@@ -17,18 +17,12 @@
[Benchmarks]: https://github.com/khvzak/script-bench-rs
[FAQ]: FAQ.md
# The main branch is the development version of `mlua`. Please see the [v0.10](https://github.com/mlua-rs/mlua/tree/v0.10) branch for the stable versions of `mlua`.
`mlua` is a set of bindings to the [Lua](https://www.lua.org) programming language for Rust with a goal to provide a
_safe_ (as much as possible), high level, easy to use, practical and flexible API.
> **Note**
>
> See v0.10 [release notes](https://github.com/mlua-rs/mlua/blob/main/docs/release_notes/v0.10.md).
Started as an `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Luau] and allows writing native Lua modules in Rust as well as using Lua in a standalone mode.
`mlua` is bindings to [Lua](https://www.lua.org) programming language for Rust with a goal to provide
_safe_ (as far as it's possible), high level, easy to use, practical and flexible API.
Started as `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Luau] and allows to write native Lua modules in Rust as well as use Lua in a standalone mode.
`mlua` tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platform and cross-compilation to `aarch64` (other targets are also supported).
`mlua` is tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platforms and cross-compilation to `aarch64` (other targets are also supported).
WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua/Luau versions excluding JIT.
@@ -39,7 +33,7 @@ WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for a
### Feature flags
`mlua` uses feature flags to reduce the amount of dependencies, compiled code and allow to choose only required set of features.
`mlua` uses feature flags to reduce the amount of dependencies and compiled code, and allow to choose only required set of features.
Below is a list of the available feature flags. By default `mlua` does not enable any features.
* `lua54`: enable Lua [5.4] support
@@ -51,12 +45,12 @@ Below is a list of the available feature flags. By default `mlua` does not enabl
* `luau`: enable [Luau] support (auto vendored mode)
* `luau-jit`: enable [Luau] support with JIT backend.
* `luau-vector4`: enable [Luau] support with 4-dimensional vector.
* `vendored`: build static Lua(JIT) library from sources during `mlua` compilation using [lua-src] or [luajit-src] crates
* `vendored`: build static Lua(JIT) libraries from sources during `mlua` compilation using [lua-src] or [luajit-src]
* `module`: enable module mode (building loadable `cdylib` library for Lua)
* `async`: enable async/await support (any executor can be used, eg. [tokio] or [async-std])
* `send`: make `mlua::Lua: Send + Sync` (adds [`Send`] requirement to `mlua::Function` and `mlua::UserData`)
* `error-send`: make `mlua:Error: Send + Sync`
* `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework
* `serde`: add serialization and deserialization support to `mlua` types using [serde]
* `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`
@@ -78,7 +72,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl
`mlua` supports async/await for all Lua versions including Luau.
This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6) and require running [Thread](https://docs.rs/mlua/latest/mlua/struct.Thread.html) along with enabling `feature = "async"` in `Cargo.toml`.
This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6) and requires running [Thread](https://docs.rs/mlua/latest/mlua/struct.Thread.html) along with enabling `feature = "async"` in `Cargo.toml`.
**Examples**:
- [HTTP Client](examples/async_http_client.rs)
@@ -93,18 +87,18 @@ This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6
cargo run --example async_http_client --features=lua54,async,macros
# async http client (reqwest)
cargo run --example async_http_reqwest --features=lua54,async,macros,serialize
cargo run --example async_http_reqwest --features=lua54,async,macros,serde
# async http server
cargo run --example async_http_server --features=lua54,async,macros,send
curl -v http://localhost:3000
```
### Serialization (serde) support
### Serde support
With `serialize` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition `mlua` provides [`serde::Serialize`] trait implementation for it (including `UserData` support).
With the `serde` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition, `mlua` provides the [`serde::Serialize`] trait implementation for `mlua::Value` (including `UserData` support).
[Example](examples/serialize.rs)
[Example](examples/serde.rs)
[`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
[`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
@@ -114,28 +108,28 @@ With `serialize` feature flag enabled, `mlua` allows you to serialize/deserializ
You have to enable one of the features: `lua54`, `lua53`, `lua52`, `lua51`, `luajit(52)` or `luau`, according to the chosen Lua version.
By default `mlua` uses `pkg-config` tool to find lua includes and libraries for the chosen Lua version.
In most cases it works as desired, although sometimes could be more preferable to use a custom lua library.
To achieve this, mlua supports `LUA_LIB`, `LUA_LIB_NAME` and `LUA_LINK` environment variables.
By default `mlua` uses `pkg-config` to find Lua includes and libraries for the chosen Lua version.
In most cases it works as desired, although sometimes it may be preferable to use a custom Lua library.
To achieve this, mlua supports the `LUA_LIB`, `LUA_LIB_NAME` and `LUA_LINK` environment variables.
`LUA_LINK` is optional and may be `dylib` (a dynamic library) or `static` (a static library, `.a` archive).
An example how to use them:
An example of how to use them:
``` sh
my_project $ LUA_LIB=$HOME/tmp/lua-5.2.4/src LUA_LIB_NAME=lua LUA_LINK=static cargo build
```
`mlua` also supports vendored lua/luajit using the auxiliary crates [lua-src](https://crates.io/crates/lua-src) and
`mlua` also supports vendored Lua/LuaJIT using the auxiliary crates [lua-src](https://crates.io/crates/lua-src) and
[luajit-src](https://crates.io/crates/luajit-src).
Just enable the `vendored` feature and cargo will automatically build and link specified lua/luajit version. This is the easiest way to get started with `mlua`.
Just enable the `vendored` feature and cargo will automatically build and link the specified Lua/LuaJIT version. This is the easiest way to get started with `mlua`.
### Standalone mode
In a standalone mode `mlua` allows to add to your application scripting support with a gently configured Lua runtime to ensure safety and soundness.
In standalone mode, `mlua` allows adding scripting support to your application with a gently configured Lua runtime to ensure safety and soundness.
Add to `Cargo.toml` :
Add to `Cargo.toml`:
``` toml
[dependencies]
mlua = { version = "0.10", features = ["lua54", "vendored"] }
mlua = { version = "0.11", features = ["lua54", "vendored"] }
```
`main.rs`
@@ -159,21 +153,21 @@ fn main() -> LuaResult<()> {
```
### Module mode
In a module mode `mlua` allows to create a compiled Lua module that can be loaded from Lua code using [`require`](https://www.lua.org/manual/5.4/manual.html#pdf-require). In this case `mlua` uses an external Lua runtime which could lead to potential unsafety due to unpredictability of the Lua environment and usage of libraries such as [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10).
In module mode, `mlua` allows creating a compiled Lua module that can be loaded from Lua code using [`require`](https://www.lua.org/manual/5.4/manual.html#pdf-require). In this case `mlua` uses an external Lua runtime which could lead to potential unsafety due to the unpredictability of the Lua environment and usage of libraries such as [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10).
[Example](examples/module)
Add to `Cargo.toml` :
Add to `Cargo.toml`:
``` toml
[lib]
crate-type = ["cdylib"]
[dependencies]
mlua = { version = "0.10", features = ["lua54", "module"] }
mlua = { version = "0.11", features = ["lua54", "module"] }
```
`lib.rs` :
`lib.rs`:
``` rust
use mlua::prelude::*;
@@ -216,14 +210,14 @@ rustflags = [
```
On Linux you can build modules normally with `cargo build --release`.
On Windows the target module will be linked with `lua5x.dll` library (depending on your feature flags).
On Windows the target module will be linked with the `lua5x.dll` library (depending on your feature flags).
Your main application should provide this library.
Module builds don't require Lua lib or headers to be installed on the system.
Module builds don't require Lua binaries or headers to be installed on the system.
### Publishing to luarocks.org
There is a LuaRocks build backend for mlua modules [`luarocks-build-rust-mlua`].
There is a LuaRocks build backend for mlua modules: [`luarocks-build-rust-mlua`].
Modules written in Rust and published to luarocks:
- [`decasify`](https://github.com/alerque/decasify)
@@ -236,10 +230,10 @@ Modules written in Rust and published to luarocks:
## Safety
One of the `mlua` goals is to provide *safe* API between Rust and Lua.
Every place where the Lua C API may trigger an error longjmp in any way is protected by `lua_pcall`,
and the user of the library is protected from directly interacting with unsafe things like the Lua stack,
and there is overhead associated with this safety.
One of `mlua`'s goals is to provide a *safe* API between Rust and Lua.
Every place where the Lua C API may trigger an error longjmp is protected by `lua_pcall`,
and the user of the library is protected from directly interacting with unsafe things like the Lua stack.
There is overhead associated with this safety.
Unfortunately, `mlua` does not provide absolute safety even without using `unsafe` .
This library contains a huge amount of unsafe code. There are almost certainly bugs still lurking in this library!
@@ -247,8 +241,8 @@ It is surprisingly, fiendishly difficult to use the Lua C API without the potent
## Panic handling
`mlua` wraps panics that are generated inside Rust callbacks in a regular Lua error. Panics could be
resumed then by returning or propagating the Lua error to Rust code.
`mlua` wraps panics that are generated inside Rust callbacks in a regular Lua error. Panics can then be
resumed by returning or propagating the Lua error to Rust code.
For example:
``` rust
@@ -267,12 +261,12 @@ let _ = lua.load(r#"
unreachable!()
```
Optionally `mlua` can disable Rust panics catching in Lua via `pcall`/`xpcall` and automatically resume
Optionally, `mlua` can disable Rust panic catching in Lua via `pcall`/`xpcall` and automatically resume
them across the Lua API boundary. This is controlled via `LuaOptions` and done by wrapping the Lua `pcall`/`xpcall`
functions on a way to prevent catching errors that are wrapped Rust panics.
functions to prevent catching errors that are wrapped Rust panics.
`mlua` should also be panic safe in another way as well, which is that any `Lua` instances or handles
remains usable after a user generated panic, and such panics should not break internal invariants or
remain usable after a user generated panic, and such panics should not break internal invariants or
leak Lua stack space. This is mostly important to safely use `mlua` types in Drop impls, as you should not be
using panics for general error handling.
@@ -289,12 +283,12 @@ If you encounter them, a bug report would be very welcome:
## Sandboxing
Please check the [Luau Sandboxing] page if you are interested in running untrusted Lua scripts in controlled environment.
Please check the [Luau Sandboxing] page if you are interested in running untrusted Lua scripts in a controlled environment.
`mlua` provides `Lua::sandbox` method for enabling sandbox mode (Luau only).
`mlua` provides the `Lua::sandbox` method for enabling sandbox mode (Luau only).
[Luau Sandboxing]: https://luau.org/sandbox
## License
This project is licensed under the [MIT license](LICENSE)
This project is licensed under the [MIT license](LICENSE).
+51
View File
@@ -128,6 +128,22 @@ fn table_traversal_sequence(c: &mut Criterion) {
});
}
fn table_ref_clone(c: &mut Criterion) {
let lua = Lua::new();
let t = lua.create_table().unwrap();
c.bench_function("table [ref clone]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
let _t2 = t.clone();
},
BatchSize::SmallInput,
);
});
}
fn function_create(c: &mut Criterion) {
let lua = Lua::new();
@@ -350,6 +366,39 @@ fn userdata_call_method(c: &mut Criterion) {
});
}
// A userdata method call that goes through an implicit `__index` function
fn userdata_call_method_complex(c: &mut Criterion) {
struct UserData(u64);
impl LuaUserData for UserData {
fn register(registry: &mut LuaUserDataRegistry<Self>) {
registry.add_field_method_get("val", |_, this| Ok(this.0));
registry.add_method_mut("inc_by", |_, this, by: u64| {
this.0 += by;
Ok(this.0)
});
}
}
let lua = Lua::new();
let ud = lua.create_userdata(UserData(0)).unwrap();
let inc_by = lua
.load("function(ud, s) return ud:inc_by(s) end")
.eval::<LuaFunction>()
.unwrap();
c.bench_function("userdata [call method complex]", |b| {
b.iter_batched(
|| {
collect_gc_twice(&lua);
},
|_| {
inc_by.call::<()>((&ud, 1)).unwrap();
},
BatchSize::SmallInput,
);
});
}
fn userdata_async_call_method(c: &mut Criterion) {
struct UserData(i64);
impl LuaUserData for UserData {
@@ -399,6 +448,7 @@ criterion_group! {
table_traversal_pairs,
table_traversal_for_each,
table_traversal_sequence,
table_ref_clone,
function_create,
function_call_sum,
@@ -413,6 +463,7 @@ criterion_group! {
userdata_create,
userdata_call_index,
userdata_call_method,
userdata_call_method_complex,
userdata_async_call_method,
}
+10 -8
View File
@@ -20,14 +20,16 @@ fn main() {
match lua.load(&line).eval::<MultiValue>() {
Ok(values) => {
editor.add_history_entry(line).unwrap();
println!(
"{}",
values
.iter()
.map(|value| format!("{:#?}", value))
.collect::<Vec<_>>()
.join("\t")
);
if values.len() > 0 {
println!(
"{}",
values
.iter()
.map(|value| format!("{:#?}", value))
.collect::<Vec<_>>()
.join("\t")
);
}
break;
}
Err(Error::SyntaxError {
+4 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua-sys"
version = "0.7.0"
version = "0.8.3"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
rust-version = "1.71"
edition = "2021"
@@ -30,6 +30,7 @@ luau = ["luau0-src"]
luau-codegen = ["luau"]
luau-vector4 = ["luau"]
vendored = ["lua-src", "luajit-src"]
external = []
module = []
[dependencies]
@@ -38,9 +39,9 @@ module = []
cc = "1.0"
cfg-if = "1.0"
pkg-config = "0.3.17"
lua-src = { version = ">= 547.1.0, < 547.2.0", optional = true }
lua-src = { version = ">= 548.1.0, < 548.2.0", optional = true }
luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true }
luau0-src = { version = "0.14.2", optional = true }
luau0-src = { version = "0.15.6", optional = true }
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] }
+19 -12
View File
@@ -14,22 +14,29 @@ fn main() {
#[cfg(all(feature = "luau", feature = "module", windows))]
compile_error!("Luau does not support `module` mode on Windows");
#[cfg(all(feature = "module", feature = "vendored"))]
compile_error!("`vendored` and `module` features are mutually exclusive");
#[cfg(any(
all(feature = "vendored", any(feature = "external", feature = "module")),
all(feature = "external", any(feature = "vendored", feature = "module")),
all(feature = "module", any(feature = "vendored", feature = "external"))
))]
compile_error!("`vendored`, `external` and `module` features are mutually exclusive");
println!("cargo:rerun-if-changed=build");
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
if target_os == "windows" && cfg!(feature = "module") {
if !std::env::var("LUA_LIB_NAME").unwrap_or_default().is_empty() {
// Don't use raw-dylib linking
find::probe_lua();
return;
// Check if compilation and linking is handled by external crate
if cfg!(not(feature = "external")) {
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
if target_os == "windows" && cfg!(feature = "module") {
if !std::env::var("LUA_LIB_NAME").unwrap_or_default().is_empty() {
// Don't use raw-dylib linking
find::probe_lua();
return;
}
println!("cargo:rustc-cfg=raw_dylib");
}
println!("cargo:rustc-cfg=raw_dylib");
#[cfg(not(feature = "module"))]
find::probe_lua();
}
#[cfg(not(feature = "module"))]
find::probe_lua();
}
+2 -1
View File
@@ -186,7 +186,8 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int {
if lua_type(L, idx) == LUA_TNUMBER {
let n = lua_tonumber(L, idx);
let i = lua_tointeger(L, idx);
if (n - i as lua_Number).abs() < lua_Number::EPSILON {
// Lua 5.3+ returns "false" for `-0.0`
if n.to_bits() == (i as lua_Number).to_bits() {
return 1;
}
}
+2 -1
View File
@@ -51,7 +51,8 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int {
if lua_type(L, idx) == LUA_TNUMBER {
let n = lua_tonumber(L, idx);
let i = lua_tointeger(L, idx);
if (n - i as lua_Number).abs() < lua_Number::EPSILON {
// Lua 5.3+ returns "false" for `-0.0`
if n.to_bits() == (i as lua_Number).to_bits() {
return 1;
}
}
+14 -6
View File
@@ -120,7 +120,8 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int {
if lua_type(L, idx) == LUA_TNUMBER {
let n = lua_tonumber(L, idx);
let i = lua_tointeger(L, idx);
if (n - i as lua_Number).abs() < lua_Number::EPSILON {
// Lua 5.3+ returns "false" for `-0.0`
if n.to_bits() == (i as lua_Number).to_bits() {
return 1;
}
}
@@ -388,7 +389,7 @@ pub unsafe fn luaL_loadbufferenv(
}
}
if chunk_is_text {
let status = if chunk_is_text {
if env < 0 {
env -= 1;
}
@@ -397,14 +398,21 @@ pub unsafe fn luaL_loadbufferenv(
ptr::write(data_ud, data);
// By deferring the `free(data)` to the userdata destructor, we ensure that
// even if `luau_load` throws an error, the `data` is still released.
let ok = luau_load(L, name, data, size, env) == 0;
let status = luau_load(L, name, data, size, env);
lua_replace(L, -2); // replace data with the result
if !ok {
return LUA_ERRSYNTAX;
status
} else {
luau_load(L, name, data, size, env)
};
if status != 0 {
if lua_isstring(L, -1) != 0 && CStr::from_ptr(lua_tostring(L, -1)) == c"not enough memory" {
// A case for Luau >= 0.679
return LUA_ERRMEM;
}
} else if luau_load(L, name, data, size, env) != 0 {
return LUA_ERRSYNTAX;
}
LUA_OK
}
+1
View File
@@ -235,6 +235,7 @@ unsafe extern "C-unwind" {
) -> c_int;
pub fn lua_call(L: *mut lua_State, nargs: c_int, nresults: c_int);
pub fn lua_pcall(L: *mut lua_State, nargs: c_int, nresults: c_int, errfunc: c_int) -> c_int;
pub fn lua_cpcall(L: *mut lua_State, f: lua_CFunction, ud: *mut c_void) -> c_int;
//
// Coroutine functions
+49 -26
View File
@@ -26,11 +26,14 @@ pub enum luarequire_WriteResult {
#[repr(C)]
pub struct luarequire_Configuration {
// Returns whether requires are permitted from the given chunkname.
pub is_require_allowed:
unsafe extern "C" fn(L: *mut lua_State, ctx: *mut c_void, requirer_chunkname: *const c_char) -> bool,
pub is_require_allowed: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
requirer_chunkname: *const c_char,
) -> bool,
// Resets the internal state to point at the requirer module.
pub reset: unsafe extern "C" fn(
pub reset: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
requirer_chunkname: *const c_char,
@@ -39,26 +42,27 @@ pub struct luarequire_Configuration {
// Resets the internal state to point at an aliased module, given its exact path from a configuration
// file. This function is only called when an alias's path cannot be resolved relative to its
// configuration file.
pub jump_to_alias: unsafe extern "C" fn(
pub jump_to_alias: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
path: *const c_char,
) -> luarequire_NavigateResult,
// Navigates through the context by making mutations to the internal state.
pub to_parent: unsafe extern "C" fn(L: *mut lua_State, ctx: *mut c_void) -> luarequire_NavigateResult,
pub to_child: unsafe extern "C" fn(
pub to_parent:
unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> luarequire_NavigateResult,
pub to_child: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
name: *const c_char,
) -> luarequire_NavigateResult,
// Returns whether the context is currently pointing at a module.
pub is_module_present: unsafe extern "C" fn(L: *mut lua_State, ctx: *mut c_void) -> bool,
pub is_module_present: unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> bool,
// Provides the contents of the current module. This function is only called if is_module_present returns
// true.
pub get_contents: unsafe extern "C" fn(
// Provides a chunkname for the current module. This will be accessible through the debug library. This
// function is only called if is_module_present returns true.
pub get_chunkname: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
@@ -66,9 +70,9 @@ pub struct luarequire_Configuration {
size_out: *mut usize,
) -> luarequire_WriteResult,
// Provides a chunkname for the current module. This will be accessible through the debug library. This
// function is only called if is_module_present returns true.
pub get_chunkname: unsafe extern "C" fn(
// Provides a loadname that identifies the current module and is passed to load. This function
// is only called if is_module_present returns true.
pub get_loadname: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
@@ -78,7 +82,7 @@ pub struct luarequire_Configuration {
// Provides a cache key representing the current module. This function is only called if
// is_module_present returns true.
pub get_cache_key: unsafe extern "C" fn(
pub get_cache_key: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
@@ -89,17 +93,36 @@ pub struct luarequire_Configuration {
// Returns whether a configuration file is present in the current context.
// If not, require-by-string will call to_parent until either a configuration file is present or
// NAVIGATE_FAILURE is returned (at root).
pub is_config_present: unsafe extern "C" fn(L: *mut lua_State, ctx: *mut c_void) -> bool,
pub is_config_present: unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> bool,
// Provides the contents of the configuration file in the current context.
// This function is only called if is_config_present returns true.
pub get_config: unsafe extern "C" fn(
L: *mut lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> luarequire_WriteResult,
// Parses the configuration file in the current context for the given alias and returns its
// value or WRITE_FAILURE if not found. This function is only called if is_config_present
// returns true. If this function pointer is set, get_config must not be set. Opting in to this
// function pointer disables parsing configuration files internally and can be used for finer
// control over the configuration file parsing process.
pub get_alias: Option<
unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
alias: *const c_char,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> luarequire_WriteResult,
>,
// Provides the contents of the configuration file in the current context. This function is only called
// if is_config_present returns true. If this function pointer is set, get_alias must not be set. Opting
// in to this function pointer enables parsing configuration files internally.
pub get_config: Option<
unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> luarequire_WriteResult,
>,
// Executes the module and places the result on the stack. Returns the number of results placed on the
// stack.
@@ -110,12 +133,12 @@ pub struct luarequire_Configuration {
ctx: *mut c_void,
path: *const c_char,
chunkname: *const c_char,
contents: *const c_char,
loadname: *const c_char,
) -> c_int,
}
// Populates function pointers in the given luarequire_Configuration.
pub type luarequire_Configuration_init = unsafe extern "C" fn(config: *mut luarequire_Configuration);
pub type luarequire_Configuration_init = unsafe extern "C-unwind" fn(config: *mut luarequire_Configuration);
unsafe extern "C-unwind" {
// Initializes and pushes the require closure onto the stack without registration.
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua_derive"
version = "0.11.0-beta.1"
version = "0.11.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2021"
description = "Procedural macros for the mlua crate."
+4 -4
View File
@@ -120,7 +120,7 @@ pub fn chunk(input: TokenStream) -> TokenStream {
Some(ChunkMode::Text)
}
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed((#source).as_bytes()))
}
}
@@ -129,13 +129,13 @@ pub fn chunk(input: TokenStream) -> TokenStream {
let globals = lua.globals();
let env = lua.create_table()?;
let meta = lua.create_table()?;
meta.raw_set("__index", globals.clone())?;
meta.raw_set("__newindex", globals)?;
meta.raw_set("__index", &globals)?;
meta.raw_set("__newindex", &globals)?;
// Add captured variables
#(#caps)*
env.set_metatable(Some(meta));
env.set_metatable(Some(meta))?;
Ok(env)
};
+98 -17
View File
@@ -1,6 +1,9 @@
#[cfg(feature = "serialize")]
use std::io;
#[cfg(feature = "serde")]
use serde::ser::{Serialize, Serializer};
use crate::state::RawLua;
use crate::types::ValueRef;
/// A Luau buffer type.
@@ -16,16 +19,17 @@ pub struct Buffer(pub(crate) ValueRef);
impl Buffer {
/// Copies the buffer data into a new `Vec<u8>`.
pub fn to_vec(&self) -> Vec<u8> {
unsafe { self.as_slice().to_vec() }
let lua = self.0.lua.lock();
self.as_slice(&lua).to_vec()
}
/// Returns the length of the buffer.
pub fn len(&self) -> usize {
unsafe { self.as_slice().len() }
let lua = self.0.lua.lock();
self.as_slice(&lua).len()
}
/// Returns `true` if the buffer is empty.
#[doc(hidden)]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
@@ -35,7 +39,8 @@ impl Buffer {
/// Offset is 0-based.
#[track_caller]
pub fn read_bytes<const N: usize>(&self, offset: usize) -> [u8; N] {
let data = unsafe { self.as_slice() };
let lua = self.0.lua.lock();
let data = self.as_slice(&lua);
let mut bytes = [0u8; N];
bytes.copy_from_slice(&data[offset..offset + N]);
bytes
@@ -46,21 +51,36 @@ impl Buffer {
/// Offset is 0-based.
#[track_caller]
pub fn write_bytes(&self, offset: usize, bytes: &[u8]) {
let data = unsafe {
let (buf, size) = self.as_raw_parts();
std::slice::from_raw_parts_mut(buf, size)
};
let lua = self.0.lua.lock();
let data = self.as_slice_mut(&lua);
data[offset..offset + bytes.len()].copy_from_slice(bytes);
}
pub(crate) unsafe fn as_slice(&self) -> &[u8] {
let (buf, size) = self.as_raw_parts();
std::slice::from_raw_parts(buf, size)
/// Returns an adaptor implementing [`io::Read`], [`io::Write`] and [`io::Seek`] over the
/// buffer.
///
/// Buffer operations are infallible, none of the read/write functions will return a Err.
pub fn cursor(self) -> impl io::Read + io::Write + io::Seek {
BufferCursor(self, 0)
}
pub(crate) fn as_slice(&self, lua: &RawLua) -> &[u8] {
unsafe {
let (buf, size) = self.as_raw_parts(lua);
std::slice::from_raw_parts(buf, size)
}
}
#[allow(clippy::mut_from_ref)]
fn as_slice_mut(&self, lua: &RawLua) -> &mut [u8] {
unsafe {
let (buf, size) = self.as_raw_parts(lua);
std::slice::from_raw_parts_mut(buf, size)
}
}
#[cfg(feature = "luau")]
unsafe fn as_raw_parts(&self) -> (*mut u8, usize) {
let lua = self.0.lua.lock();
unsafe fn as_raw_parts(&self, lua: &RawLua) -> (*mut u8, usize) {
let mut size = 0usize;
let buf = ffi::lua_tobuffer(lua.ref_thread(), self.0.index, &mut size);
mlua_assert!(!buf.is_null(), "invalid Luau buffer");
@@ -68,15 +88,76 @@ impl Buffer {
}
#[cfg(not(feature = "luau"))]
unsafe fn as_raw_parts(&self) -> (*mut u8, usize) {
unsafe fn as_raw_parts(&self, lua: &RawLua) -> (*mut u8, usize) {
unreachable!()
}
}
#[cfg(feature = "serialize")]
struct BufferCursor(Buffer, usize);
impl io::Read for BufferCursor {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let lua = self.0 .0.lua.lock();
let data = self.0.as_slice(&lua);
if self.1 == data.len() {
return Ok(0);
}
let len = buf.len().min(data.len() - self.1);
buf[..len].copy_from_slice(&data[self.1..self.1 + len]);
self.1 += len;
Ok(len)
}
}
impl io::Write for BufferCursor {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let lua = self.0 .0.lua.lock();
let data = self.0.as_slice_mut(&lua);
if self.1 == data.len() {
return Ok(0);
}
let len = buf.len().min(data.len() - self.1);
data[self.1..self.1 + len].copy_from_slice(&buf[..len]);
self.1 += len;
Ok(len)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl io::Seek for BufferCursor {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
let lua = self.0 .0.lua.lock();
let data = self.0.as_slice(&lua);
let new_offset = match pos {
io::SeekFrom::Start(offset) => offset as i64,
io::SeekFrom::End(offset) => data.len() as i64 + offset,
io::SeekFrom::Current(offset) => self.1 as i64 + offset,
};
if new_offset < 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid seek to a negative position",
));
}
if new_offset as usize > data.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid seek to a position beyond the end of the buffer",
));
}
self.1 = new_offset as usize;
Ok(self.1 as u64)
}
}
#[cfg(feature = "serde")]
impl Serialize for Buffer {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
serializer.serialize_bytes(unsafe { self.as_slice() })
let lua = self.0.lua.lock();
serializer.serialize_bytes(self.as_slice(&lua))
}
}
+134 -60
View File
@@ -38,28 +38,28 @@ pub trait AsChunk {
}
/// Returns chunk data (can be text or binary)
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>>
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a;
}
impl AsChunk for &str {
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>>
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
Ok(Cow::Borrowed(self.as_ref()))
Ok(Cow::Borrowed(self.as_bytes()))
}
}
impl AsChunk for StdString {
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Owned(self.into_bytes()))
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Owned(self.clone().into_bytes()))
}
}
impl AsChunk for &StdString {
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>>
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
@@ -68,7 +68,7 @@ impl AsChunk for &StdString {
}
impl AsChunk for &[u8] {
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>>
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
@@ -77,13 +77,13 @@ impl AsChunk for &[u8] {
}
impl AsChunk for Vec<u8> {
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Owned(self))
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Owned(self.clone()))
}
}
impl AsChunk for &Vec<u8> {
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>>
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
@@ -96,7 +96,7 @@ impl AsChunk for &Path {
Some(format!("@{}", self.display()))
}
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
std::fs::read(self).map(Cow::Owned)
}
}
@@ -106,11 +106,32 @@ impl AsChunk for PathBuf {
Some(format!("@{}", self.display()))
}
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
std::fs::read(self).map(Cow::Owned)
}
}
impl<C: AsChunk + ?Sized> AsChunk for Box<C> {
fn name(&self) -> Option<StdString> {
(**self).name()
}
fn environment(&self, lua: &Lua) -> Result<Option<Table>> {
(**self).environment(lua)
}
fn mode(&self) -> Option<ChunkMode> {
(**self).mode()
}
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
(**self).source()
}
}
/// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks.
#[must_use = "`Chunk`s do nothing unless one of `exec`, `eval`, `call`, or `into_function` are called on them"]
pub struct Chunk<'a> {
@@ -139,18 +160,39 @@ pub enum CompileConstant {
Boolean(bool),
Number(crate::Number),
Vector(crate::Vector),
String(String),
String(StdString),
}
#[cfg(feature = "luau")]
impl From<&'static str> for CompileConstant {
fn from(s: &'static str) -> Self {
CompileConstant::String(s.to_string())
#[cfg(any(feature = "luau", doc))]
impl From<bool> for CompileConstant {
fn from(b: bool) -> Self {
CompileConstant::Boolean(b)
}
}
#[cfg(any(feature = "luau", doc))]
type LibraryMemberConstantMap = std::sync::Arc<HashMap<(String, String), CompileConstant>>;
impl From<crate::Number> for CompileConstant {
fn from(n: crate::Number) -> Self {
CompileConstant::Number(n)
}
}
#[cfg(any(feature = "luau", doc))]
impl From<crate::Vector> for CompileConstant {
fn from(v: crate::Vector) -> Self {
CompileConstant::Vector(v)
}
}
#[cfg(any(feature = "luau", doc))]
impl From<&str> for CompileConstant {
fn from(s: &str) -> Self {
CompileConstant::String(s.to_owned())
}
}
#[cfg(any(feature = "luau", doc))]
type LibraryMemberConstantMap = HashMap<(StdString, StdString), CompileConstant>;
/// Luau compiler
#[cfg(any(feature = "luau", doc))]
@@ -161,14 +203,14 @@ pub struct Compiler {
debug_level: u8,
type_info_level: u8,
coverage_level: u8,
vector_lib: Option<String>,
vector_ctor: Option<String>,
vector_type: Option<String>,
mutable_globals: Vec<String>,
userdata_types: Vec<String>,
libraries_with_known_members: Vec<String>,
vector_lib: Option<StdString>,
vector_ctor: Option<StdString>,
vector_type: Option<StdString>,
mutable_globals: Vec<StdString>,
userdata_types: Vec<StdString>,
libraries_with_known_members: Vec<StdString>,
library_constants: Option<LibraryMemberConstantMap>,
disabled_builtins: Vec<String>,
disabled_builtins: Vec<StdString>,
}
#[cfg(any(feature = "luau", doc))]
@@ -246,70 +288,102 @@ impl Compiler {
self
}
/// Sets alternative global builtin to construct vectors, in addition to default builtin
/// `vector.create`.
///
/// To set the library and method name, use the `lib.ctor` format.
#[doc(hidden)]
#[must_use]
pub fn set_vector_lib(mut self, lib: impl Into<String>) -> Self {
self.vector_lib = Some(lib.into());
pub fn set_vector_ctor(mut self, ctor: impl Into<StdString>) -> Self {
let ctor = ctor.into();
let lib_ctor = ctor.split_once('.');
self.vector_lib = lib_ctor.as_ref().map(|&(lib, _)| lib.to_owned());
self.vector_ctor = (lib_ctor.as_ref())
.map(|&(_, ctor)| ctor.to_owned())
.or(Some(ctor));
self
}
/// Sets alternative vector type name for type tables, in addition to default type `vector`.
#[doc(hidden)]
#[must_use]
pub fn set_vector_ctor(mut self, ctor: impl Into<String>) -> Self {
self.vector_ctor = Some(ctor.into());
self
}
#[doc(hidden)]
#[must_use]
pub fn set_vector_type(mut self, r#type: impl Into<String>) -> Self {
pub fn set_vector_type(mut self, r#type: impl Into<StdString>) -> Self {
self.vector_type = Some(r#type.into());
self
}
/// Adds a mutable global.
///
/// It disables the import optimization for fields accessed through it.
#[must_use]
pub fn add_mutable_global(mut self, global: impl Into<StdString>) -> Self {
self.mutable_globals.push(global.into());
self
}
/// Sets a list of globals that are mutable.
///
/// It disables the import optimization for fields accessed through these.
#[must_use]
pub fn set_mutable_globals<S: Into<String>>(mut self, globals: Vec<S>) -> Self {
pub fn set_mutable_globals<S: Into<StdString>>(mut self, globals: impl IntoIterator<Item = S>) -> Self {
self.mutable_globals = globals.into_iter().map(|s| s.into()).collect();
self
}
/// Adds a userdata type to the list that will be included in the type information.
#[must_use]
pub fn add_userdata_type(mut self, r#type: impl Into<StdString>) -> Self {
self.userdata_types.push(r#type.into());
self
}
/// Sets a list of userdata types that will be included in the type information.
#[must_use]
pub fn set_userdata_types<S: Into<String>>(mut self, types: Vec<S>) -> Self {
pub fn set_userdata_types<S: Into<StdString>>(mut self, types: impl IntoIterator<Item = S>) -> Self {
self.userdata_types = types.into_iter().map(|s| s.into()).collect();
self
}
/// Sets constants for known library members.
/// Adds a constant for a known library member.
///
/// The constants are used by the compiler to optimize the generated bytecode.
/// Optimization level must be at least 2 for this to have any effect.
///
/// The first element of the tuple is the library name,the second is the member name, and the
/// third is the constant value.
/// The `name` is a string in the format `lib.member`, where `lib` is the library name
/// and `member` is the member (constant) name.
#[must_use]
pub fn set_library_constants<L, M>(mut self, constants: Vec<(L, M, CompileConstant)>) -> Self
where
L: Into<String>,
M: Into<String>,
{
let map = constants
.into_iter()
.map(|(lib, member, cons)| ((lib.into(), member.into()), cons))
.collect::<HashMap<_, _>>();
self.library_constants = Some(std::sync::Arc::new(map));
self.libraries_with_known_members = (self.library_constants.clone())
.map(|map| map.keys().map(|(lib, _)| lib.clone()).collect())
.unwrap_or_default();
pub fn add_library_constant(
mut self,
name: impl AsRef<str>,
r#const: impl Into<CompileConstant>,
) -> Self {
let Some((lib, member)) = name.as_ref().split_once('.') else {
return self;
};
let (lib, member) = (lib.to_owned(), member.to_owned());
if !self.libraries_with_known_members.contains(&lib) {
self.libraries_with_known_members.push(lib.clone());
}
self.library_constants
.get_or_insert_with(HashMap::new)
.insert((lib, member), r#const.into());
self
}
/// Adds a builtin that should be disabled.
#[must_use]
pub fn add_disabled_builtin(mut self, builtin: impl Into<StdString>) -> Self {
self.disabled_builtins.push(builtin.into());
self
}
/// Sets a list of builtins that should be disabled.
#[must_use]
pub fn set_disabled_builtins<S: Into<String>>(mut self, builtins: Vec<S>) -> Self {
pub fn set_disabled_builtins<S: Into<StdString>>(
mut self,
builtins: impl IntoIterator<Item = S>,
) -> Self {
self.disabled_builtins = builtins.into_iter().map(|s| s.into()).collect();
self
}
@@ -416,7 +490,7 @@ impl Compiler {
if bytecode.first() == Some(&0) {
// The rest of the bytecode is the error message starting with `:`
// See https://github.com/luau-lang/luau/blob/0.640/Compiler/src/Compiler.cpp#L4336
let message = String::from_utf8_lossy(&bytecode[2..]).to_string();
let message = StdString::from_utf8_lossy(&bytecode[2..]).into_owned();
return Err(Error::SyntaxError {
incomplete_input: message.ends_with("<eof>"),
message,
@@ -439,7 +513,7 @@ impl Chunk<'_> {
/// - `@` - 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 {
pub fn set_name(mut self, name: impl Into<StdString>) -> Self {
self.name = name.into();
self
}
@@ -616,7 +690,7 @@ impl Chunk<'_> {
if let Ok(ref source) = self.source {
if self.detect_mode() == ChunkMode::Text {
let lua = self.lua.lock();
if let Some(cache) = lua.app_data_ref_unguarded::<ChunksCache>() {
if let Some(cache) = lua.priv_app_data_ref::<ChunksCache>() {
if let Some(data) = cache.0.get(source.as_ref()) {
self.source = Ok(Cow::Owned(data.clone()));
self.mode = Some(ChunkMode::Binary);
@@ -633,12 +707,12 @@ impl Chunk<'_> {
if let Ok(ref binary_source) = self.source {
if self.detect_mode() == ChunkMode::Binary {
let lua = self.lua.lock();
if let Some(mut cache) = lua.app_data_mut_unguarded::<ChunksCache>() {
if let Some(mut cache) = lua.priv_app_data_mut::<ChunksCache>() {
cache.0.insert(text_source, binary_source.to_vec());
} else {
let mut cache = ChunksCache(HashMap::new());
cache.0.insert(text_source, binary_source.to_vec());
let _ = lua.try_set_app_data(cache);
lua.set_priv_app_data(cache);
};
}
}
@@ -687,7 +761,7 @@ impl Chunk<'_> {
ChunkMode::Text
}
fn convert_name(name: String) -> Result<CString> {
fn convert_name(name: StdString) -> Result<CString> {
CString::new(name).map_err(|err| Error::runtime(format!("invalid name: {err}")))
}
+1 -1
View File
@@ -645,7 +645,7 @@ impl FromLua for BString {
match value {
Value::String(s) => Ok((*s.as_bytes()).into()),
#[cfg(feature = "luau")]
Value::Buffer(buf) => unsafe { Ok(buf.as_slice().into()) },
Value::Buffer(buf) => Ok(buf.to_vec().into()),
_ => Ok((*lua
.coerce_string(value)?
.ok_or_else(|| Error::FromLuaConversionError {
+85 -102
View File
@@ -1,66 +1,33 @@
use std::borrow::Cow;
use std::cell::UnsafeCell;
use std::ops::Deref;
#[cfg(not(feature = "luau"))]
use std::ops::{BitOr, BitOrAssign};
use std::os::raw::c_int;
use ffi::lua_Debug;
use ffi::{lua_Debug, lua_State};
use crate::function::Function;
use crate::state::RawLua;
use crate::types::ReentrantMutexGuard;
use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str};
use crate::util::{assert_stack, linenumber_to_usize, ptr_to_lossy_str, ptr_to_str, StackGuard};
/// Contains information about currently executing Lua code.
///
/// The `Debug` structure is provided as a parameter to the hook function set with
/// [`Lua::set_hook`]. You may call the methods on this structure to retrieve information about the
/// Lua code executing at the time that the hook function was called. Further information can be
/// found in the Lua [documentation].
/// You may call the methods on this structure to retrieve information about the Lua code executing
/// at the specific level. Further information can be found in the Lua [documentation].
///
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#lua_Debug
/// [`Lua::set_hook`]: crate::Lua::set_hook
pub struct Debug<'a> {
lua: EitherLua<'a>,
ar: ActivationRecord,
#[cfg(feature = "luau")]
state: *mut lua_State,
lua: &'a RawLua,
#[cfg_attr(not(feature = "luau"), allow(unused))]
level: c_int,
}
enum EitherLua<'a> {
Owned(ReentrantMutexGuard<'a, RawLua>),
#[cfg(not(feature = "luau"))]
Borrowed(&'a RawLua),
}
impl Deref for EitherLua<'_> {
type Target = RawLua;
fn deref(&self) -> &Self::Target {
match self {
EitherLua::Owned(guard) => guard,
#[cfg(not(feature = "luau"))]
EitherLua::Borrowed(lua) => lua,
}
}
ar: *mut lua_Debug,
}
impl<'a> Debug<'a> {
// We assume the lock is held when this function is called.
#[cfg(not(feature = "luau"))]
pub(crate) fn new(lua: &'a RawLua, ar: *mut lua_Debug) -> Self {
pub(crate) fn new(lua: &'a RawLua, level: c_int, ar: *mut lua_Debug) -> Self {
Debug {
lua: EitherLua::Borrowed(lua),
ar: ActivationRecord::Borrowed(ar),
}
}
pub(crate) fn new_owned(guard: ReentrantMutexGuard<'a, RawLua>, _level: c_int, ar: lua_Debug) -> Self {
Debug {
lua: EitherLua::Owned(guard),
ar: ActivationRecord::Owned(UnsafeCell::new(ar)),
#[cfg(feature = "luau")]
level: _level,
state: lua.state(),
lua,
ar,
level,
}
}
@@ -74,7 +41,7 @@ impl<'a> Debug<'a> {
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn event(&self) -> DebugEvent {
unsafe {
match (*self.ar.get()).event {
match (*self.ar).event {
ffi::LUA_HOOKCALL => DebugEvent::Call,
ffi::LUA_HOOKRET => DebugEvent::Ret,
ffi::LUA_HOOKTAILCALL => DebugEvent::TailCall,
@@ -85,24 +52,48 @@ impl<'a> Debug<'a> {
}
}
/// Corresponds to the `n` what mask.
pub fn names(&self) -> DebugNames {
/// Returns the function that is running at the given level.
///
/// Corresponds to the `f` "what" mask.
pub fn function(&self) -> Function {
unsafe {
let _sg = StackGuard::new(self.state);
assert_stack(self.state, 1);
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("f"), self.ar) != 0,
"lua_getinfo failed with `f`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.state, self.level, cstr!("f"), self.ar) != 0,
"lua_getinfo failed with `f`"
);
ffi::lua_xmove(self.state, self.lua.ref_thread(), 1);
Function(self.lua.pop_ref_thread())
}
}
/// Corresponds to the `n` "what" mask.
pub fn names(&self) -> DebugNames<'_> {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), cstr!("n"), self.ar.get()) != 0,
ffi::lua_getinfo(self.state, cstr!("n"), self.ar) != 0,
"lua_getinfo failed with `n`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("n"), self.ar.get()) != 0,
ffi::lua_getinfo(self.state, self.level, cstr!("n"), self.ar) != 0,
"lua_getinfo failed with `n`"
);
DebugNames {
name: ptr_to_lossy_str((*self.ar.get()).name),
name: ptr_to_lossy_str((*self.ar).name),
#[cfg(not(feature = "luau"))]
name_what: match ptr_to_str((*self.ar.get()).namewhat) {
name_what: match ptr_to_str((*self.ar).namewhat) {
Some("") => None,
val => val,
},
@@ -112,118 +103,110 @@ impl<'a> Debug<'a> {
}
}
/// Corresponds to the `S` what mask.
pub fn source(&self) -> DebugSource {
/// Corresponds to the `S` "what" mask.
pub fn source(&self) -> DebugSource<'_> {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), cstr!("S"), self.ar.get()) != 0,
ffi::lua_getinfo(self.state, cstr!("S"), self.ar) != 0,
"lua_getinfo failed with `S`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("s"), self.ar.get()) != 0,
ffi::lua_getinfo(self.state, self.level, cstr!("s"), self.ar) != 0,
"lua_getinfo failed with `s`"
);
DebugSource {
source: ptr_to_lossy_str((*self.ar.get()).source),
source: ptr_to_lossy_str((*self.ar).source),
#[cfg(not(feature = "luau"))]
short_src: ptr_to_lossy_str((*self.ar.get()).short_src.as_ptr()),
short_src: ptr_to_lossy_str((*self.ar).short_src.as_ptr()),
#[cfg(feature = "luau")]
short_src: ptr_to_lossy_str((*self.ar.get()).short_src),
line_defined: linenumber_to_usize((*self.ar.get()).linedefined),
short_src: ptr_to_lossy_str((*self.ar).short_src),
line_defined: linenumber_to_usize((*self.ar).linedefined),
#[cfg(not(feature = "luau"))]
last_line_defined: linenumber_to_usize((*self.ar.get()).lastlinedefined),
last_line_defined: linenumber_to_usize((*self.ar).lastlinedefined),
#[cfg(feature = "luau")]
last_line_defined: None,
what: ptr_to_str((*self.ar.get()).what).unwrap_or("main"),
what: ptr_to_str((*self.ar).what).unwrap_or("main"),
}
}
}
/// Corresponds to the `l` what mask. Returns the current line.
#[doc(hidden)]
#[deprecated(note = "Use `current_line` instead")]
pub fn curr_line(&self) -> i32 {
self.current_line().map(|n| n as i32).unwrap_or(-1)
}
/// Corresponds to the `l` "what" mask. Returns the current line.
pub fn current_line(&self) -> Option<usize> {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), cstr!("l"), self.ar.get()) != 0,
ffi::lua_getinfo(self.state, cstr!("l"), self.ar) != 0,
"lua_getinfo failed with `l`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("l"), self.ar.get()) != 0,
ffi::lua_getinfo(self.state, self.level, cstr!("l"), self.ar) != 0,
"lua_getinfo failed with `l`"
);
(*self.ar.get()).currentline
linenumber_to_usize((*self.ar).currentline)
}
}
/// Corresponds to the `t` what mask. Returns true if the hook is in a function tail call, false
/// otherwise.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
/// Corresponds to the `t` "what" mask. Returns true if the hook is in a function tail call,
/// false otherwise.
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52")))
)]
pub fn is_tail_call(&self) -> bool {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), cstr!("t"), self.ar.get()) != 0,
ffi::lua_getinfo(self.state, cstr!("t"), self.ar) != 0,
"lua_getinfo failed with `t`"
);
(*self.ar.get()).currentline != 0
(*self.ar).istailcall != 0
}
}
/// Corresponds to the `u` what mask.
/// Corresponds to the `u` "what" mask.
pub fn stack(&self) -> DebugStack {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), cstr!("u"), self.ar.get()) != 0,
ffi::lua_getinfo(self.state, cstr!("u"), self.ar) != 0,
"lua_getinfo failed with `u`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("au"), self.ar.get()) != 0,
ffi::lua_getinfo(self.state, self.level, cstr!("au"), self.ar) != 0,
"lua_getinfo failed with `au`"
);
#[cfg(not(feature = "luau"))]
let stack = DebugStack {
num_ups: (*self.ar.get()).nups as _,
num_ups: (*self.ar).nups as _,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
num_params: (*self.ar.get()).nparams as _,
num_params: (*self.ar).nparams as _,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
is_vararg: (*self.ar.get()).isvararg != 0,
is_vararg: (*self.ar).isvararg != 0,
};
#[cfg(feature = "luau")]
let stack = DebugStack {
num_ups: (*self.ar.get()).nupvals,
num_params: (*self.ar.get()).nparams,
is_vararg: (*self.ar.get()).isvararg != 0,
num_ups: (*self.ar).nupvals,
num_params: (*self.ar).nparams,
is_vararg: (*self.ar).isvararg != 0,
};
stack
}
}
}
enum ActivationRecord {
#[cfg(not(feature = "luau"))]
Borrowed(*mut lua_Debug),
Owned(UnsafeCell<lua_Debug>),
}
impl ActivationRecord {
#[inline]
fn get(&self) -> *mut lua_Debug {
match self {
#[cfg(not(feature = "luau"))]
ActivationRecord::Borrowed(x) => *x,
ActivationRecord::Owned(x) => x.get(),
}
}
}
/// Represents a specific event that triggered the hook.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DebugEvent {
@@ -382,7 +365,7 @@ impl HookTriggers {
}
#[cfg(not(feature = "luau"))]
impl BitOr for HookTriggers {
impl std::ops::BitOr for HookTriggers {
type Output = Self;
fn bitor(mut self, rhs: Self) -> Self::Output {
@@ -397,7 +380,7 @@ impl BitOr for HookTriggers {
}
#[cfg(not(feature = "luau"))]
impl BitOrAssign for HookTriggers {
impl std::ops::BitOrAssign for HookTriggers {
fn bitor_assign(&mut self, rhs: Self) {
*self = *self | rhs;
}
+8 -8
View File
@@ -183,12 +183,12 @@ pub enum Error {
/// and returned again.
PreviouslyResumedPanic,
/// Serialization error.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
SerializeError(StdString),
/// Deserialization error.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
DeserializeError(StdString),
/// A custom error.
///
@@ -309,11 +309,11 @@ impl fmt::Display for Error {
Error::PreviouslyResumedPanic => {
write!(fmt, "previously resumed panic returned again")
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
Error::SerializeError(err) => {
write!(fmt, "serialize error: {err}")
},
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
Error::DeserializeError(err) => {
write!(fmt, "deserialize error: {err}")
},
@@ -494,14 +494,14 @@ impl From<Utf8Error> for Error {
}
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
impl serde::ser::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self {
Self::SerializeError(msg.to_string())
}
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
impl serde::de::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self {
Self::DeserializeError(msg.to_string())
+35 -16
View File
@@ -253,7 +253,7 @@ impl Function {
"#,
)
.try_cache()
.set_name("__mlua_bind")
.set_name("=__mlua_bind")
.call((self, args_wrapper))
}
@@ -434,7 +434,7 @@ impl Function {
/// [`Compiler::set_coverage_level`]: crate::chunk::Compiler::set_coverage_level
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn coverage<F>(&self, mut func: F)
pub fn coverage<F>(&self, func: F)
where
F: FnMut(CoverageInfo),
{
@@ -454,13 +454,16 @@ impl Function {
} else {
None
};
let rust_callback = &mut *(data as *mut F);
rust_callback(CoverageInfo {
function,
line_defined,
depth,
hits: slice::from_raw_parts(hits, size).to_vec(),
});
let rust_callback = &*(data as *const RefCell<F>);
if let Ok(mut rust_callback) = rust_callback.try_borrow_mut() {
// Call the Rust callback with CoverageInfo
rust_callback(CoverageInfo {
function,
line_defined,
depth,
hits: slice::from_raw_parts(hits, size).to_vec(),
});
}
}
let lua = self.0.lua.lock();
@@ -470,7 +473,8 @@ impl Function {
assert_stack(state, 1);
lua.push_ref(&self.0);
let func_ptr = &mut func as *mut F as *mut c_void;
let func = RefCell::new(func);
let func_ptr = &func as *const RefCell<F> as *mut c_void;
ffi::lua_getcoverage(state, -1, func_ptr, callback::<F>);
}
}
@@ -492,16 +496,24 @@ impl Function {
/// This function returns shallow clone (same handle) for Rust/C functions.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn deep_clone(&self) -> Self {
pub fn deep_clone(&self) -> Result<Self> {
let lua = self.0.lua.lock();
let ref_thread = lua.ref_thread();
let state = lua.state();
unsafe {
if ffi::lua_iscfunction(ref_thread, self.0.index) != 0 {
return self.clone();
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
lua.push_ref(&self.0);
if ffi::lua_iscfunction(state, -1) != 0 {
return Ok(self.clone());
}
ffi::lua_clonefunction(ref_thread, self.0.index);
Function(lua.pop_ref_thread())
if lua.unlikely_memory_error() {
ffi::lua_clonefunction(state, -1);
} else {
protect_lua!(state, 1, 1, fn(state) ffi::lua_clonefunction(state, -1))?;
}
Ok(Function(lua.pop_ref()))
}
}
}
@@ -645,6 +657,13 @@ impl LuaType for Function {
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct AsyncCallFuture<R: FromLuaMulti>(Result<AsyncThread<R>>);
#[cfg(feature = "async")]
impl<R: FromLuaMulti> AsyncCallFuture<R> {
pub(crate) fn error(err: Error) -> Self {
AsyncCallFuture(Err(err))
}
}
#[cfg(feature = "async")]
impl<R: FromLuaMulti> Future for AsyncCallFuture<R> {
type Output = Result<R>;
+10 -10
View File
@@ -35,7 +35,7 @@
//! The [`Value`] enum and other types implement [`serde::Serialize`] trait to support serializing
//! Lua values into Rust values.
//!
//! Requires `feature = "serialize"`.
//! Requires `feature = "serde"`.
//!
//! # Async/await support
//!
@@ -75,9 +75,9 @@ mod macros;
mod buffer;
mod chunk;
mod conversion;
mod debug;
mod error;
mod function;
mod hook;
#[cfg(any(feature = "luau", doc))]
mod luau;
mod memory;
@@ -101,9 +101,9 @@ pub use bstr::BString;
pub use ffi::{self, lua_CFunction, lua_State};
pub use crate::chunk::{AsChunk, Chunk, ChunkMode};
pub use crate::debug::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack};
pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result};
pub use crate::function::{Function, FunctionInfo};
pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack};
pub use crate::multi::{MultiValue, Variadic};
pub use crate::scope::Scope;
pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua};
@@ -124,7 +124,7 @@ pub use crate::userdata::{
pub use crate::value::{Nil, Value};
#[cfg(not(feature = "luau"))]
pub use crate::hook::HookTriggers;
pub use crate::debug::HookTriggers;
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
@@ -132,7 +132,7 @@ pub use crate::{
buffer::Buffer,
chunk::{CompileConstant, Compiler},
function::CoverageInfo,
luau::{NavigateError, Require},
luau::{NavigateError, Require, TextRequirer},
vector::Vector,
};
@@ -140,12 +140,12 @@ pub use crate::{
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn};
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
#[doc(inline)]
pub use crate::serde::{de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt};
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub mod serde;
#[cfg(feature = "mlua_derive")]
@@ -217,7 +217,7 @@ pub use mlua_derive::FromLua;
///
/// You can register multiple entrypoints as required.
///
/// ```
/// ```ignore
/// use mlua::{Lua, Result, Table};
///
/// #[mlua::lua_module]
@@ -254,7 +254,7 @@ pub use mlua_derive::FromLua;
/// ...
/// }
/// ```
#[cfg(any(feature = "module", docsrs))]
#[cfg(all(feature = "mlua_derive", any(feature = "module", doc)))]
#[cfg_attr(docsrs, doc(cfg(feature = "module")))]
pub use mlua_derive::lua_module;
+30 -8
View File
@@ -1,11 +1,15 @@
use std::ffi::CStr;
use std::os::raw::c_int;
use std::ptr;
use crate::chunk::ChunkMode;
use crate::error::Result;
use crate::function::Function;
use crate::state::Lua;
use crate::state::{callback_error_ext, ExtraData, Lua};
use crate::traits::{FromLuaMulti, IntoLua};
use crate::types::MaybeSend;
pub use require::{NavigateError, Require};
pub use require::{NavigateError, Require, TextRequirer};
// Since Luau has some missing standard functions, we re-implement them here
@@ -14,7 +18,7 @@ impl Lua {
/// and load modules.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn create_require_function<R: Require + 'static>(&self, require: R) -> Result<Function> {
pub fn create_require_function<R: Require + MaybeSend + 'static>(&self, require: R) -> Result<Function> {
require::create_require_function(self, require)
}
@@ -22,6 +26,7 @@ impl Lua {
let globals = self.globals();
globals.raw_set("collectgarbage", self.create_c_function(lua_collectgarbage)?)?;
globals.raw_set("loadstring", self.create_c_function(lua_loadstring)?)?;
// Set `_VERSION` global to include version number
// The environment variable `LUAU_VERSION` set by the build script
@@ -41,16 +46,17 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
let option = ffi::luaL_optstring(state, 1, cstr!("collect"));
let option = CStr::from_ptr(option);
let arg = ffi::luaL_optinteger(state, 2, 0);
let is_sandboxed = (*ExtraData::get(state)).sandboxed;
match option.to_str() {
Ok("collect") => {
Ok("collect") if !is_sandboxed => {
ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0);
0
}
Ok("stop") => {
Ok("stop") if !is_sandboxed => {
ffi::lua_gc(state, ffi::LUA_GCSTOP, 0);
0
}
Ok("restart") => {
Ok("restart") if !is_sandboxed => {
ffi::lua_gc(state, ffi::LUA_GCRESTART, 0);
0
}
@@ -60,12 +66,12 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
ffi::lua_pushnumber(state, kbytes + kbytes_rem / 1024.0);
1
}
Ok("step") => {
Ok("step") if !is_sandboxed => {
let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg as _);
ffi::lua_pushboolean(state, res);
1
}
Ok("isrunning") => {
Ok("isrunning") if !is_sandboxed => {
let res = ffi::lua_gc(state, ffi::LUA_GCISRUNNING, 0);
ffi::lua_pushboolean(state, res);
1
@@ -74,4 +80,20 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
}
}
unsafe extern "C-unwind" fn lua_loadstring(state: *mut ffi::lua_State) -> c_int {
callback_error_ext(state, ptr::null_mut(), false, move |extra, nargs| {
let rawlua = (*extra).raw_lua();
let (chunk, chunk_name) =
<(String, Option<String>)>::from_stack_args(nargs, 1, Some("loadstring"), rawlua)?;
let chunk_name = chunk_name.as_deref().unwrap_or("=(loadstring)");
(rawlua.lua())
.load(chunk)
.set_name(chunk_name)
.set_mode(ChunkMode::Text)
.into_function()?
.push_into_stack(rawlua)?;
Ok(1)
})
}
mod require;
+298 -254
View File
@@ -2,97 +2,99 @@ use std::cell::RefCell;
use std::collections::VecDeque;
use std::ffi::CStr;
use std::io::Result as IoResult;
use std::ops::{Deref, DerefMut};
use std::os::raw::{c_char, c_int, c_void};
use std::path::{Component, Path, PathBuf};
use std::result::Result as StdResult;
use std::{env, fmt, fs, mem, ptr};
use crate::error::Result;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::{callback_error_ext, Lua};
use crate::table::Table;
use crate::types::MaybeSend;
/// An error that can occur during navigation in the Luau `require` system.
/// An error that can occur during navigation in the Luau `require-by-string` system.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[derive(Debug, Clone)]
pub enum NavigateError {
Ambiguous,
NotFound,
Other(Error),
}
#[cfg(feature = "luau")]
trait IntoNavigateResult {
fn into_nav_result(self) -> ffi::luarequire_NavigateResult;
fn into_nav_result(self) -> Result<ffi::luarequire_NavigateResult>;
}
#[cfg(feature = "luau")]
impl IntoNavigateResult for StdResult<(), NavigateError> {
fn into_nav_result(self) -> ffi::luarequire_NavigateResult {
fn into_nav_result(self) -> Result<ffi::luarequire_NavigateResult> {
match self {
Ok(()) => ffi::luarequire_NavigateResult::Success,
Err(NavigateError::Ambiguous) => ffi::luarequire_NavigateResult::Ambiguous,
Err(NavigateError::NotFound) => ffi::luarequire_NavigateResult::NotFound,
Ok(()) => Ok(ffi::luarequire_NavigateResult::Success),
Err(NavigateError::Ambiguous) => Ok(ffi::luarequire_NavigateResult::Ambiguous),
Err(NavigateError::NotFound) => Ok(ffi::luarequire_NavigateResult::NotFound),
Err(NavigateError::Other(err)) => Err(err),
}
}
}
impl From<Error> for NavigateError {
fn from(err: Error) -> Self {
NavigateError::Other(err)
}
}
#[cfg(feature = "luau")]
type WriteResult = ffi::luarequire_WriteResult;
/// A trait for handling modules loading and navigation in the Luau `require` system.
pub trait Require: MaybeSend {
/// A trait for handling modules loading and navigation in the Luau `require-by-string` system.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub trait Require {
/// Returns `true` if "require" is permitted for the given chunk name.
fn is_require_allowed(&self, chunk_name: &str) -> bool;
/// Resets the internal state to point at the requirer module.
fn reset(&self, chunk_name: &str) -> StdResult<(), NavigateError>;
fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError>;
/// Resets the internal state to point at an aliased module.
///
/// This function received an exact path from a configuration file.
/// It's only called when an alias's path cannot be resolved relative to its
/// configuration file.
fn jump_to_alias(&self, path: &str) -> StdResult<(), NavigateError>;
fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError>;
// Navigate to parent directory
fn to_parent(&self) -> StdResult<(), NavigateError>;
fn to_parent(&mut self) -> StdResult<(), NavigateError>;
/// Navigate to the given child directory.
fn to_child(&self, name: &str) -> StdResult<(), NavigateError>;
fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError>;
/// Returns whether the context is currently pointing at a module
fn is_module_present(&self) -> bool;
/// Returns the contents of the current module
///
/// This function is only called if `is_module_present` returns true.
fn contents(&self) -> IoResult<Vec<u8>>;
/// Returns a chunk name for the current module.
///
/// This function is only called if `is_module_present` returns true.
/// The chunk name is used to identify the module using the debug library.
fn chunk_name(&self) -> String;
fn has_module(&self) -> bool;
/// Provides a cache key representing the current module.
///
/// This function is only called if `is_module_present` returns true.
fn cache_key(&self) -> Vec<u8>;
/// This function is only called if `has_module` returns true.
fn cache_key(&self) -> String;
/// Returns whether a configuration file is present in the current context.
fn is_config_present(&self) -> bool;
/// Returns whether a configuration is present in the current context.
fn has_config(&self) -> bool;
/// Returns the contents of the configuration file in the current context.
///
/// This function is only called if `is_config_present` returns true.
/// This function is only called if `has_config` returns true.
fn config(&self) -> IoResult<Vec<u8>>;
/// Returns a loader that when called, loads the module and returns the result.
/// Returns a loader function for the current module, that when called, loads the module
/// and returns the result.
///
/// Loader can be sync or async.
fn loader(&self, lua: &Lua, path: &str, chunk_name: &str, content: &[u8]) -> Result<Function> {
let _ = path;
lua.load(content).set_name(chunk_name).into_function()
}
/// This function is only called if `has_module` returns true.
fn loader(&self, lua: &Lua) -> Result<Function>;
}
impl fmt::Debug for dyn Require {
@@ -101,21 +103,33 @@ impl fmt::Debug for dyn Require {
}
}
/// The standard implementation of Luau `require` navigation.
#[derive(Default)]
pub(super) struct TextRequirer {
abs_path: RefCell<PathBuf>,
rel_path: RefCell<PathBuf>,
module_path: RefCell<PathBuf>,
/// The standard implementation of Luau `require-by-string` navigation.
#[derive(Default, Debug)]
pub struct TextRequirer {
/// An absolute path to the current Luau module (not mapped to a physical file)
abs_path: PathBuf,
/// A relative path to the current Luau module (not mapped to a physical file)
rel_path: PathBuf,
/// A physical path to the current Luau module, which is a file or a directory with an
/// `init.lua(u)` file
resolved_path: Option<PathBuf>,
}
impl TextRequirer {
pub(super) fn new() -> Self {
/// The prefix used for chunk names in the require system.
/// Only chunk names starting with this prefix are allowed to be used in `require`.
const CHUNK_PREFIX: &str = "@";
/// The file extensions that are considered valid for Luau modules.
const FILE_EXTENSIONS: &[&str] = &["luau", "lua"];
/// Creates a new `TextRequirer` instance.
pub fn new() -> Self {
Self::default()
}
fn normalize_chunk_name(chunk_name: &str) -> &str {
if let Some((path, line)) = chunk_name.split_once(':') {
if let Some((path, line)) = chunk_name.rsplit_once(':') {
if line.parse::<u32>().is_ok() {
return path;
}
@@ -152,162 +166,192 @@ impl TextRequirer {
components.into_iter().collect()
}
fn find_module_path(path: &Path) -> StdResult<PathBuf, NavigateError> {
/// Resolve a Luau module path to a physical file or directory.
///
/// Empty directories without init files are considered valid as "intermediate" directories.
fn resolve_module(path: &Path) -> StdResult<Option<PathBuf>, NavigateError> {
let mut found_path = None;
let current_ext = (path.extension().and_then(|s| s.to_str()))
.map(|s| format!("{s}."))
.unwrap_or_default();
for ext in ["luau", "lua"] {
let candidate = path.with_extension(format!("{current_ext}{ext}"));
if candidate.is_file() {
if found_path.is_some() {
if path.components().next_back() != Some(Component::Normal("init".as_ref())) {
let current_ext = (path.extension().and_then(|s| s.to_str()))
.map(|s| format!("{s}."))
.unwrap_or_default();
for ext in Self::FILE_EXTENSIONS {
let candidate = path.with_extension(format!("{current_ext}{ext}"));
if candidate.is_file() && found_path.replace(candidate).is_some() {
return Err(NavigateError::Ambiguous);
}
found_path = Some(candidate);
}
}
if path.is_dir() {
if found_path.is_some() {
return Err(NavigateError::Ambiguous);
}
for component in ["init.luau", "init.lua"] {
for component in Self::FILE_EXTENSIONS.iter().map(|ext| format!("init.{ext}")) {
let candidate = path.join(component);
if candidate.is_file() {
if found_path.is_some() {
return Err(NavigateError::Ambiguous);
}
found_path = Some(candidate);
if candidate.is_file() && found_path.replace(candidate).is_some() {
return Err(NavigateError::Ambiguous);
}
}
if found_path.is_none() {
found_path = Some(PathBuf::new());
// Directories without init files are considered valid "intermediate" path
return Ok(None);
}
}
found_path.ok_or(NavigateError::NotFound)
Ok(Some(found_path.ok_or(NavigateError::NotFound)?))
}
}
impl Require for TextRequirer {
fn is_require_allowed(&self, chunk_name: &str) -> bool {
chunk_name.starts_with('@')
chunk_name.starts_with(Self::CHUNK_PREFIX)
}
fn reset(&self, chunk_name: &str) -> StdResult<(), NavigateError> {
if !chunk_name.starts_with('@') {
fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError> {
if !chunk_name.starts_with(Self::CHUNK_PREFIX) {
return Err(NavigateError::NotFound);
}
let chunk_name = &Self::normalize_chunk_name(chunk_name)[1..];
let path = Self::normalize_path(chunk_name.as_ref());
let chunk_name = Self::normalize_chunk_name(&chunk_name[1..]);
let chunk_path = Self::normalize_path(chunk_name.as_ref());
if path.extension() == Some("rs".as_ref()) {
let cwd = match env::current_dir() {
Ok(cwd) => cwd,
Err(_) => return Err(NavigateError::NotFound),
};
self.abs_path.replace(Self::normalize_path(&cwd.join(&path)));
self.rel_path.replace(path);
self.module_path.replace(PathBuf::new());
if chunk_path.extension() == Some("rs".as_ref()) {
// Special case for Rust source files, reset to the current directory
let chunk_filename = chunk_path.file_name().unwrap();
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
self.abs_path = Self::normalize_path(&cwd.join(chunk_filename));
self.rel_path = ([Component::CurDir, Component::Normal(chunk_filename)].into_iter()).collect();
self.resolved_path = None;
return Ok(());
}
if path.is_absolute() {
let module_path = Self::find_module_path(&path)?;
self.abs_path.replace(path.clone());
self.rel_path.replace(path);
self.module_path.replace(module_path);
if chunk_path.is_absolute() {
let resolved_path = Self::resolve_module(&chunk_path)?;
self.abs_path = chunk_path.clone();
self.rel_path = chunk_path;
self.resolved_path = resolved_path;
} else {
// Relative path
let cwd = match env::current_dir() {
Ok(cwd) => cwd,
Err(_) => return Err(NavigateError::NotFound),
};
let abs_path = cwd.join(&path);
let module_path = Self::find_module_path(&abs_path)?;
self.abs_path.replace(Self::normalize_path(&abs_path));
self.rel_path.replace(path);
self.module_path.replace(module_path);
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
let abs_path = Self::normalize_path(&cwd.join(&chunk_path));
let resolved_path = Self::resolve_module(&abs_path)?;
self.abs_path = abs_path;
self.rel_path = chunk_path;
self.resolved_path = resolved_path;
}
Ok(())
}
fn jump_to_alias(&self, path: &str) -> StdResult<(), NavigateError> {
fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> {
let path = Self::normalize_path(path.as_ref());
let module_path = Self::find_module_path(&path)?;
let resolved_path = Self::resolve_module(&path)?;
self.abs_path.replace(path.clone());
self.rel_path.replace(path);
self.module_path.replace(module_path);
self.abs_path = path.clone();
self.rel_path = path;
self.resolved_path = resolved_path;
Ok(())
}
fn to_parent(&self) -> StdResult<(), NavigateError> {
let mut abs_path = self.abs_path.borrow().clone();
fn to_parent(&mut self) -> StdResult<(), NavigateError> {
let mut abs_path = self.abs_path.clone();
if !abs_path.pop() {
// It's important to return `NotFound` if we reached the root, as it's a "recoverable" error if we
// cannot go beyond the root directory.
// Luau "require-by-string` has a special logic to search for config file to resolve aliases.
return Err(NavigateError::NotFound);
}
let mut rel_parent = self.rel_path.borrow().clone();
let mut rel_parent = self.rel_path.clone();
rel_parent.pop();
let module_path = Self::find_module_path(&abs_path)?;
let resolved_path = Self::resolve_module(&abs_path)?;
self.abs_path.replace(abs_path);
self.rel_path.replace(Self::normalize_path(&rel_parent));
self.module_path.replace(module_path);
self.abs_path = abs_path;
self.rel_path = Self::normalize_path(&rel_parent);
self.resolved_path = resolved_path;
Ok(())
}
fn to_child(&self, name: &str) -> StdResult<(), NavigateError> {
let abs_path = self.abs_path.borrow().join(name);
let rel_path = self.rel_path.borrow().join(name);
let module_path = Self::find_module_path(&abs_path)?;
fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> {
let abs_path = self.abs_path.join(name);
let rel_path = self.rel_path.join(name);
let resolved_path = Self::resolve_module(&abs_path)?;
self.abs_path.replace(abs_path);
self.rel_path.replace(rel_path);
self.module_path.replace(module_path);
self.abs_path = abs_path;
self.rel_path = rel_path;
self.resolved_path = resolved_path;
Ok(())
}
fn is_module_present(&self) -> bool {
self.module_path.borrow().is_file()
fn has_module(&self) -> bool {
(self.resolved_path.as_deref())
.map(Path::is_file)
.unwrap_or(false)
}
fn contents(&self) -> IoResult<Vec<u8>> {
fs::read(&*self.module_path.borrow())
fn cache_key(&self) -> String {
self.resolved_path.as_deref().unwrap().display().to_string()
}
fn chunk_name(&self) -> String {
format!("@{}", self.rel_path.borrow().display())
}
fn cache_key(&self) -> Vec<u8> {
self.module_path.borrow().display().to_string().into_bytes()
}
fn is_config_present(&self) -> bool {
self.abs_path.borrow().join(".luaurc").is_file()
fn has_config(&self) -> bool {
self.abs_path.is_dir() && self.abs_path.join(".luaurc").is_file()
}
fn config(&self) -> IoResult<Vec<u8>> {
fs::read(self.abs_path.borrow().join(".luaurc"))
fs::read(self.abs_path.join(".luaurc"))
}
fn loader(&self, lua: &Lua) -> Result<Function> {
let name = format!("@{}", self.rel_path.display());
lua.load(self.resolved_path.as_deref().unwrap())
.set_name(name)
.into_function()
}
}
struct Context(Box<dyn Require>);
impl Deref for Context {
type Target = dyn Require;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
impl DerefMut for Context {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *self.0
}
}
macro_rules! try_borrow {
($state:expr, $ctx:expr) => {
match (*($ctx as *const RefCell<Context>)).try_borrow() {
Ok(ctx) => ctx,
Err(_) => ffi::luaL_error($state, cstr!("require context is already borrowed")),
}
};
}
macro_rules! try_borrow_mut {
($state:expr, $ctx:expr) => {
match (*($ctx as *const RefCell<Context>)).try_borrow_mut() {
Ok(ctx) => ctx,
Err(_) => ffi::luaL_error($state, cstr!("require context is already borrowed")),
}
};
}
#[cfg(feature = "luau")]
pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configuration) {
pub(super) unsafe extern "C-unwind" fn init_config(config: *mut ffi::luarequire_Configuration) {
if config.is_null() {
return;
}
unsafe extern "C" fn is_require_allowed(
_state: *mut ffi::lua_State,
unsafe extern "C-unwind" fn is_require_allowed(
state: *mut ffi::lua_State,
ctx: *mut c_void,
requirer_chunkname: *const c_char,
) -> bool {
@@ -315,119 +359,123 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu
return false;
}
let this = &*(ctx as *const Box<dyn Require>);
let this = try_borrow!(state, ctx);
let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy();
this.is_require_allowed(&chunk_name)
}
unsafe extern "C" fn reset(
_state: *mut ffi::lua_State,
unsafe extern "C-unwind" fn reset(
state: *mut ffi::lua_State,
ctx: *mut c_void,
requirer_chunkname: *const c_char,
) -> ffi::luarequire_NavigateResult {
let this = &*(ctx as *const Box<dyn Require>);
let mut this = try_borrow_mut!(state, ctx);
let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy();
this.reset(&chunk_name).into_nav_result()
}
unsafe extern "C" fn jump_to_alias(
_state: *mut ffi::lua_State,
ctx: *mut c_void,
path: *const c_char,
) -> ffi::luarequire_NavigateResult {
let this = &*(ctx as *const Box<dyn Require>);
let path = CStr::from_ptr(path).to_string_lossy();
this.jump_to_alias(&path).into_nav_result()
}
unsafe extern "C" fn to_parent(
_state: *mut ffi::lua_State,
ctx: *mut c_void,
) -> ffi::luarequire_NavigateResult {
let this = &*(ctx as *const Box<dyn Require>);
this.to_parent().into_nav_result()
}
unsafe extern "C" fn to_child(
_state: *mut ffi::lua_State,
ctx: *mut c_void,
name: *const c_char,
) -> ffi::luarequire_NavigateResult {
let this = &*(ctx as *const Box<dyn Require>);
let name = CStr::from_ptr(name).to_string_lossy();
this.to_child(&name).into_nav_result()
}
unsafe extern "C" fn is_module_present(_state: *mut ffi::lua_State, ctx: *mut c_void) -> bool {
let this = &*(ctx as *const Box<dyn Require>);
this.is_module_present()
}
unsafe extern "C" fn get_contents(
state: *mut ffi::lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
let this = &*(ctx as *const Box<dyn Require>);
write_to_buffer(state, buffer, buffer_size, size_out, || this.contents())
}
unsafe extern "C" fn get_chunkname(
state: *mut ffi::lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
let this = &*(ctx as *const Box<dyn Require>);
write_to_buffer(state, buffer, buffer_size, size_out, || {
Ok(this.chunk_name().into_bytes())
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.reset(&chunk_name).into_nav_result()
})
}
unsafe extern "C" fn get_cache_key(
unsafe extern "C-unwind" fn jump_to_alias(
state: *mut ffi::lua_State,
ctx: *mut c_void,
path: *const c_char,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
let path = CStr::from_ptr(path).to_string_lossy();
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.jump_to_alias(&path).into_nav_result()
})
}
unsafe extern "C-unwind" fn to_parent(
state: *mut ffi::lua_State,
ctx: *mut c_void,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.to_parent().into_nav_result()
})
}
unsafe extern "C-unwind" fn to_child(
state: *mut ffi::lua_State,
ctx: *mut c_void,
name: *const c_char,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
let name = CStr::from_ptr(name).to_string_lossy();
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.to_child(&name).into_nav_result()
})
}
unsafe extern "C-unwind" fn is_module_present(state: *mut ffi::lua_State, ctx: *mut c_void) -> bool {
let this = try_borrow!(state, ctx);
this.has_module()
}
unsafe extern "C-unwind" fn get_chunkname(
_state: *mut ffi::lua_State,
_ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
write_to_buffer(buffer, buffer_size, size_out, &[])
}
unsafe extern "C-unwind" fn get_loadname(
_state: *mut ffi::lua_State,
_ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
write_to_buffer(buffer, buffer_size, size_out, &[])
}
unsafe extern "C-unwind" fn get_cache_key(
state: *mut ffi::lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
let this = &*(ctx as *const Box<dyn Require>);
write_to_buffer(state, buffer, buffer_size, size_out, || Ok(this.cache_key()))
let this = try_borrow!(state, ctx);
let cache_key = this.cache_key();
write_to_buffer(buffer, buffer_size, size_out, cache_key.as_bytes())
}
unsafe extern "C" fn is_config_present(_state: *mut ffi::lua_State, ctx: *mut c_void) -> bool {
let this = &*(ctx as *const Box<dyn Require>);
this.is_config_present()
unsafe extern "C-unwind" fn is_config_present(state: *mut ffi::lua_State, ctx: *mut c_void) -> bool {
let this = try_borrow!(state, ctx);
this.has_config()
}
unsafe extern "C" fn get_config(
unsafe extern "C-unwind" fn get_config(
state: *mut ffi::lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
let this = &*(ctx as *const Box<dyn Require>);
write_to_buffer(state, buffer, buffer_size, size_out, || this.config())
let this = try_borrow!(state, ctx);
let config = callback_error_ext(state, ptr::null_mut(), true, move |_, _| Ok(this.config()?));
write_to_buffer(buffer, buffer_size, size_out, &config)
}
unsafe extern "C-unwind" fn load(
state: *mut ffi::lua_State,
ctx: *mut c_void,
path: *const c_char,
chunk_name: *const c_char,
contents: *const c_char,
_path: *const c_char,
_chunkname: *const c_char,
_loadname: *const c_char,
) -> c_int {
let this = &*(ctx as *const Box<dyn Require>);
let path = CStr::from_ptr(path).to_string_lossy();
let chunk_name = CStr::from_ptr(chunk_name).to_string_lossy();
let contents = CStr::from_ptr(contents).to_bytes();
callback_error_ext(state, ptr::null_mut(), false, move |extra, _| {
let this = try_borrow!(state, ctx);
callback_error_ext(state, ptr::null_mut(), true, move |extra, _| {
let rawlua = (*extra).raw_lua();
rawlua.push(this.loader(rawlua.lua(), &path, &chunk_name, contents)?)?;
let loader = this.loader(rawlua.lua())?;
rawlua.push(loader)?;
Ok(1)
})
}
@@ -438,64 +486,41 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu
(*config).to_parent = to_parent;
(*config).to_child = to_child;
(*config).is_module_present = is_module_present;
(*config).get_contents = get_contents;
(*config).get_chunkname = get_chunkname;
(*config).get_loadname = get_loadname;
(*config).get_cache_key = get_cache_key;
(*config).is_config_present = is_config_present;
(*config).get_config = get_config;
(*config).get_alias = None;
(*config).get_config = Some(get_config);
(*config).load = load;
}
/// Helper function to write data to a buffer
#[cfg(feature = "luau")]
unsafe fn write_to_buffer(
state: *mut ffi::lua_State,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
data_fetcher: impl Fn() -> IoResult<Vec<u8>>,
data: &[u8],
) -> WriteResult {
struct DataCache(Option<Vec<u8>>);
// The initial buffer size can be too small, to avoid making a second data fetch call,
// we cache the content in the first call, and then re-use it.
let lua = Lua::get_or_init_from_ptr(state);
match lua.try_app_data_mut::<DataCache>() {
Ok(Some(mut data_cache)) => {
if let Some(data) = data_cache.0.take() {
mlua_assert!(data.len() <= buffer_size, "buffer is too small");
*size_out = data.len();
ptr::copy_nonoverlapping(data.as_ptr(), buffer as *mut _, data.len());
return WriteResult::Success;
}
}
Ok(None) => {
// Init the cache
_ = lua.try_set_app_data(DataCache(None));
}
Err(_) => {}
// the buffer must be null terminated as it's a c++ `std::string` data() buffer
let is_null_terminated = data.last() == Some(&0);
*size_out = data.len() + if is_null_terminated { 0 } else { 1 };
if *size_out > buffer_size {
return WriteResult::BufferTooSmall;
}
match data_fetcher() {
Ok(data) => {
*size_out = data.len();
if *size_out > buffer_size {
// Cache the data for the next call to avoid getting the contents again
if let Ok(Some(mut data_cache)) = lua.try_app_data_mut::<DataCache>() {
data_cache.0 = Some(data);
}
return WriteResult::BufferTooSmall;
}
ptr::copy_nonoverlapping(data.as_ptr(), buffer as *mut _, data.len());
WriteResult::Success
}
Err(_) => WriteResult::Failure,
ptr::copy_nonoverlapping(data.as_ptr(), buffer as *mut _, data.len());
if !is_null_terminated {
*buffer.add(data.len()) = 0;
}
WriteResult::Success
}
#[cfg(feature = "luau")]
pub fn create_require_function<R: Require + 'static>(lua: &Lua, require: R) -> Result<Function> {
pub(super) fn create_require_function<R: Require + MaybeSend + 'static>(
lua: &Lua,
require: R,
) -> Result<Function> {
unsafe extern "C-unwind" fn find_current_file(state: *mut ffi::lua_State) -> c_int {
let mut ar: ffi::lua_Debug = mem::zeroed();
for level in 2.. {
@@ -511,18 +536,20 @@ pub fn create_require_function<R: Require + 'static>(lua: &Lua, require: R) -> R
}
unsafe extern "C-unwind" fn get_cache_key(state: *mut ffi::lua_State) -> c_int {
let requirer = ffi::lua_touserdata(state, ffi::lua_upvalueindex(1)) as *const Box<dyn Require>;
let cache_key = (*requirer).cache_key();
let ctx = ffi::lua_touserdata(state, ffi::lua_upvalueindex(1));
let ctx = try_borrow!(state, ctx);
let cache_key = ctx.cache_key();
ffi::lua_pushlstring(state, cache_key.as_ptr() as *const _, cache_key.len());
1
}
let (get_cache_key, find_current_file, proxyrequire, registered_modules, loader_cache) = unsafe {
lua.exec_raw::<(Function, Function, Function, Table, Table)>((), move |state| {
let requirer_ptr = ffi::lua_newuserdata_t::<Box<dyn Require>>(state, Box::new(require));
let context = Context(Box::new(require));
let context_ptr = ffi::lua_newuserdata_t(state, RefCell::new(context));
ffi::lua_pushcclosured(state, get_cache_key, cstr!("get_cache_key"), 1);
ffi::lua_pushcfunctiond(state, find_current_file, cstr!("find_current_file"));
ffi::luarequire_pushproxyrequire(state, init_config, requirer_ptr as *mut _);
ffi::luarequire_pushproxyrequire(state, init_config, context_ptr as *mut _);
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_REGISTERED_MODULES_TABLE);
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("__MLUA_LOADER_CACHE"));
})
@@ -540,10 +567,26 @@ pub fn create_require_function<R: Require + 'static>(lua: &Lua, require: R) -> R
1
}
let (error, r#type) = unsafe {
lua.exec_raw::<(Function, Function)>((), move |state| {
unsafe extern "C-unwind" fn to_lowercase(state: *mut ffi::lua_State) -> c_int {
let s = ffi::luaL_checkstring(state, 1);
let s = CStr::from_ptr(s);
if !s.to_bytes().iter().any(|&c| c.is_ascii_uppercase()) {
// If the string does not contain any uppercase ASCII letters, return it as is
return 1;
}
callback_error_ext(state, ptr::null_mut(), true, |extra, _| {
let s = (s.to_bytes().iter())
.map(|&c| c.to_ascii_lowercase())
.collect::<bstr::BString>();
(*extra).raw_lua().push(s).map(|_| 1)
})
}
let (error, r#type, to_lowercase) = unsafe {
lua.exec_raw::<(Function, Function, Function)>((), move |state| {
ffi::lua_pushcfunctiond(state, error, cstr!("error"));
ffi::lua_pushcfunctiond(state, r#type, cstr!("type"));
ffi::lua_pushcfunctiond(state, to_lowercase, cstr!("to_lowercase"));
})
}?;
@@ -556,6 +599,7 @@ pub fn create_require_function<R: Require + 'static>(lua: &Lua, require: R) -> R
env.raw_set("LOADER_CACHE", loader_cache)?;
env.raw_set("error", error)?;
env.raw_set("type", r#type)?;
env.raw_set("to_lowercase", to_lowercase)?;
lua.load(
r#"
@@ -565,7 +609,7 @@ pub fn create_require_function<R: Require + 'static>(lua: &Lua, require: R) -> R
end
-- Check if the module (path) is explicitly registered
local maybe_result = REGISTERED_MODULES[path]
local maybe_result = REGISTERED_MODULES[to_lowercase(path)]
if maybe_result ~= nil then
return maybe_result
end
+9
View File
@@ -297,6 +297,15 @@ impl<T: IntoLua> IntoLuaMulti for Variadic<T> {
fn into_lua_multi(self, lua: &Lua) -> Result<MultiValue> {
MultiValue::from_lua_iter(lua, self)
}
unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result<c_int> {
let nresults = self.len() as i32;
check_stack(lua.state(), nresults + 1)?;
for value in self.0 {
value.push_into_stack(lua)?;
}
Ok(nresults)
}
}
impl<T: FromLua> FromLuaMulti for Variadic<T> {
+13 -11
View File
@@ -2,15 +2,16 @@
#[doc(no_inline)]
pub use crate::{
AnyUserData as LuaAnyUserData, Chunk as LuaChunk, Either as LuaEither, Error as LuaError,
ErrorContext as LuaErrorContext, ExternalError as LuaExternalError, ExternalResult as LuaExternalResult,
FromLua, FromLuaMulti, Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode,
Integer as LuaInteger, IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn,
LuaNativeFnMut, LuaOptions, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil,
Number as LuaNumber, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult,
StdLib as LuaStdLib, String as LuaString, Table as LuaTable, TablePairs as LuaTablePairs,
TableSequence as LuaTableSequence, Thread as LuaThread, ThreadStatus as LuaThreadStatus,
UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable,
AnyUserData as LuaAnyUserData, BorrowedBytes as LuaBorrowedBytes, BorrowedStr as LuaBorrowedStr,
Chunk as LuaChunk, Either as LuaEither, Error as LuaError, ErrorContext as LuaErrorContext,
ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti,
Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, Integer as LuaInteger,
IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions,
MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber,
ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib,
String as LuaString, Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence,
Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData,
UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable,
UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef,
UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue,
Variadic as LuaVariadic, VmState as LuaVmState, WeakLua,
@@ -24,14 +25,15 @@ pub use crate::HookTriggers as LuaHookTriggers;
#[doc(no_inline)]
pub use crate::{
CompileConstant as LuaCompileConstant, CoverageInfo as LuaCoverageInfo,
NavigateError as LuaNavigateError, Require as LuaRequire, Vector as LuaVector,
NavigateError as LuaNavigateError, Require as LuaRequire, TextRequirer as LuaTextRequirer,
Vector as LuaVector,
};
#[cfg(feature = "async")]
#[doc(no_inline)]
pub use crate::{AsyncThread as LuaAsyncThread, LuaNativeAsyncFn};
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
#[doc(no_inline)]
pub use crate::{
DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, SerializeOptions as LuaSerializeOptions,
+4 -10
View File
@@ -8,9 +8,7 @@ use crate::state::{Lua, LuaGuard, RawLua};
use crate::traits::{FromLuaMulti, IntoLuaMulti};
use crate::types::{Callback, CallbackUpvalue, ScopedCallback, ValueRef};
use crate::userdata::{AnyUserData, UserData, UserDataRegistry, UserDataStorage};
use crate::util::{
self, assert_stack, check_stack, get_metatable_ptr, get_userdata, take_userdata, StackGuard,
};
use crate::util::{self, check_stack, get_metatable_ptr, get_userdata, take_userdata, StackGuard};
/// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and
/// callbacks that are not required to be `Send` or `'static`.
@@ -284,22 +282,18 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
/// Shortens the lifetime of the userdata to the lifetime of the scope.
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
match rawlua.push_userdata_ref(&vref) {
match rawlua.get_userdata_ref_type_id(&vref) {
Ok(Some(_)) => {}
Ok(None) => {
// Deregister metatable
let mt_ptr = get_metatable_ptr(state, -1);
let mt_ptr = get_metatable_ptr(rawlua.ref_thread(), vref.index);
rawlua.deregister_userdata_metatable(mt_ptr);
}
Err(_) => return vec![],
}
let data = take_userdata::<UserDataStorage<T>>(state);
let data = take_userdata::<UserDataStorage<T>>(rawlua.ref_thread(), vref.index);
vec![Box::new(move || drop(data))]
});
self.destructors.0.borrow_mut().push((ud.0.clone(), destructor));
+4 -1
View File
@@ -165,7 +165,10 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
serde_userdata(ud, |value| value.deserialize_any(visitor))
}
#[cfg(feature = "luau")]
Value::Buffer(buf) => visitor.visit_bytes(unsafe { buf.as_slice() }),
Value::Buffer(buf) => {
let lua = buf.0.lua.lock();
visitor.visit_bytes(buf.as_slice(&lua))
}
Value::Function(_)
| Value::Thread(_)
| Value::UserData(_)
+1 -1
View File
@@ -13,7 +13,7 @@ use crate::util::check_stack;
use crate::value::Value;
/// Trait for serializing/deserializing Lua values using Serde.
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub trait LuaSerdeExt: Sealed {
/// A special value (lightuserdata) to encode/decode optional (none) values.
///
+3 -3
View File
@@ -256,7 +256,7 @@ impl<'a> ser::Serializer for Serializer<'a> {
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
let table = self.lua.create_table_with_capacity(len.unwrap_or(0), 0)?;
if self.options.set_array_metatable {
table.set_metatable(Some(self.lua.array_metatable()));
table.set_metatable(Some(self.lua.array_metatable()))?;
}
Ok(SerializeSeq::new(self.lua, table, self.options))
}
@@ -529,8 +529,8 @@ impl ser::SerializeStruct for SerializeStruct<'_> {
fn end(self) -> Result<Value> {
match self.inner {
Some(table @ Value::Table(_)) => Ok(table),
Some(value) if self.options.detect_serde_json_arbitrary_precision => {
let number_s = value.as_str().expect("not an arbitrary precision number");
Some(value @ Value::String(_)) if self.options.detect_serde_json_arbitrary_precision => {
let number_s = value.to_string()?;
if number_s.contains(['.', 'e', 'E']) {
if let Ok(number) = number_s.parse().map(Value::Number) {
return Ok(number);
+213 -73
View File
@@ -8,9 +8,9 @@ use std::result::Result as StdResult;
use std::{fmt, mem, ptr};
use crate::chunk::{AsChunk, Chunk};
use crate::debug::Debug;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::hook::Debug;
use crate::memory::MemoryState;
use crate::multi::MultiValue;
use crate::scope::Scope;
@@ -24,13 +24,11 @@ use crate::types::{
ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak,
};
use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataStorage};
use crate::util::{
assert_stack, check_stack, protect_lua_closure, push_string, push_table, rawset_field, StackGuard,
};
use crate::util::{assert_stack, check_stack, protect_lua_closure, push_string, rawset_field, StackGuard};
use crate::value::{Nil, Value};
#[cfg(not(feature = "luau"))]
use crate::{hook::HookTriggers, types::HookKind};
use crate::{debug::HookTriggers, types::HookKind};
#[cfg(any(feature = "luau", doc))]
use crate::{buffer::Buffer, chunk::Compiler};
@@ -39,9 +37,10 @@ use crate::{buffer::Buffer, chunk::Compiler};
use {
crate::types::LightUserData,
std::future::{self, Future},
std::task::Poll,
};
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
use serde::Serialize;
pub(crate) use extra::ExtraData;
@@ -360,6 +359,8 @@ impl Lua {
if cfg!(feature = "luau") && !modname.starts_with('@') {
return Err(Error::runtime("module name must begin with '@'"));
}
#[cfg(feature = "luau")]
let modname = modname.to_ascii_lowercase();
unsafe {
self.exec_raw::<()>(value, |state| {
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, LOADED_MODULES_KEY);
@@ -377,7 +378,7 @@ impl Lua {
///
/// This is similar to setting the [`package.preload[modname]`] field.
///
/// [`package.preload[modname]`]: https://www.lua.org/manual/5.4/manual.html#pdf-package.preload
/// [`package.preload[modname]`]: <https://www.lua.org/manual/5.4/manual.html#pdf-package.preload>
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn preload_module(&self, modname: &str, func: Function) -> Result<()> {
@@ -488,6 +489,7 @@ impl Lua {
/// - Set globals to read-only (and activates safeenv)
/// - Setup local environment table that performs writes locally and proxies reads to the global
/// environment.
/// - Allow only `count` mode in `collectgarbage` function.
///
/// # Examples
///
@@ -545,7 +547,7 @@ impl Lua {
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn set_global_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
where
F: Fn(&Lua, Debug) -> Result<VmState> + MaybeSend + 'static,
F: Fn(&Lua, &Debug) -> Result<VmState> + MaybeSend + 'static,
{
let lua = self.lock();
unsafe {
@@ -578,7 +580,7 @@ impl Lua {
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
/// lua.set_hook(HookTriggers::EVERY_LINE, |_lua, debug| {
/// println!("line {}", debug.curr_line());
/// println!("line {:?}", debug.current_line());
/// Ok(VmState::Continue)
/// });
///
@@ -595,7 +597,7 @@ impl Lua {
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
where
F: Fn(&Lua, Debug) -> Result<VmState> + MaybeSend + 'static,
F: Fn(&Lua, &Debug) -> Result<VmState> + MaybeSend + 'static,
{
let lua = self.lock();
unsafe { lua.set_thread_hook(lua.state(), HookKind::Thread(triggers, XRc::new(callback))) }
@@ -630,13 +632,13 @@ impl Lua {
///
/// Any Luau code is guaranteed to call this handler "eventually"
/// (in practice this can happen at any function call or at any loop iteration).
/// This is similar to `Lua::set_hook` but in more simplified form.
///
/// The provided interrupt function can error, and this error will be propagated through
/// the Luau code that was executing at the time the interrupt was triggered.
/// Also this can be used to implement continuous execution limits by instructing Luau VM to
/// yield by returning [`VmState::Yield`].
///
/// This is similar to [`Lua::set_hook`] but in more simplified form.
/// yield by returning [`VmState::Yield`]. The yield will happen only at yieldable points
/// of execution (not across metamethod/C-call boundaries).
///
/// # Example
///
@@ -694,7 +696,10 @@ impl Lua {
match result {
VmState::Continue => {}
VmState::Yield => {
ffi::lua_yield(state, 0);
// We can yield only at yieldable points, otherwise ignore and continue
if ffi::lua_isyieldable(state) != 0 {
ffi::lua_yield(state, 0);
}
}
}
}
@@ -870,18 +875,16 @@ impl Lua {
}
}
/// Gets information about the interpreter runtime stack.
/// Gets information about the interpreter runtime stack at a given level.
///
/// This function returns [`Debug`] structure that can be used to get information about the
/// function executing at a given level. Level `0` is the current running function, whereas
/// level `n+1` is the function that has called level `n` (except for tail calls, which do
/// not count in the stack).
///
/// [`Debug`]: crate::hook::Debug
pub fn inspect_stack(&self, level: usize) -> Option<Debug> {
/// This function calls callback `f`, passing the [`Debug`] structure that can be used to get
/// information about the function executing at a given level.
/// Level `0` is the current running function, whereas level `n+1` is the function that has
/// called level `n` (except for tail calls, which do not count in the stack).
pub fn inspect_stack<R>(&self, level: usize, f: impl FnOnce(&Debug) -> R) -> Option<R> {
let lua = self.lock();
unsafe {
let mut ar: ffi::lua_Debug = mem::zeroed();
let mut ar = mem::zeroed::<ffi::lua_Debug>();
let level = level as c_int;
#[cfg(not(feature = "luau"))]
if ffi::lua_getstack(lua.state(), level, &mut ar) == 0 {
@@ -891,7 +894,8 @@ impl Lua {
if ffi::lua_getinfo(lua.state(), level, cstr!(""), &mut ar) == 0 {
return None;
}
Some(Debug::new_owned(lua, level, ar))
Some(f(&Debug::new(&lua, level, &mut ar)))
}
}
@@ -1150,7 +1154,7 @@ impl Lua {
}
}
/// Create and return an interned Lua string.
/// Creates and returns an interned Lua string.
///
/// Lua strings can be arbitrary `[u8]` data including embedded nulls, so in addition to `&str`
/// and `&String`, you can also pass plain `&[u8]` here.
@@ -1159,27 +1163,32 @@ impl Lua {
unsafe { self.lock().create_string(s) }
}
/// Create and return a Luau [buffer] object from a byte slice of data.
/// Creates and returns a Luau [buffer] object from a byte slice of data.
///
/// [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> {
pub fn create_buffer(&self, data: impl AsRef<[u8]>) -> Result<Buffer> {
let lua = self.lock();
let state = lua.state();
let data = data.as_ref();
unsafe {
if lua.unlikely_memory_error() {
crate::util::push_buffer(state, buf.as_ref(), false)?;
return Ok(Buffer(lua.pop_ref()));
}
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
crate::util::push_buffer(state, buf.as_ref(), true)?;
Ok(Buffer(lua.pop_ref()))
let (ptr, buffer) = lua.create_buffer_with_capacity(data.len())?;
ptr.copy_from_nonoverlapping(data.as_ptr(), data.len());
Ok(buffer)
}
}
/// Creates and returns a Luau [buffer] object with the specified size.
///
/// Size limit is 1GB. All bytes will be initialized to zero.
///
/// [buffer]: https://luau.org/library#buffer-library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn create_buffer_with_capacity(&self, size: usize) -> Result<Buffer> {
unsafe { Ok(self.lock().create_buffer_with_capacity(size)?.1) }
}
/// Creates and returns a new empty table.
#[inline]
pub fn create_table(&self) -> Result<Table> {
@@ -1202,28 +1211,7 @@ impl Lua {
K: IntoLua,
V: IntoLua,
{
let lua = self.lock();
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 6)?;
let iter = iter.into_iter();
let lower_bound = iter.size_hint().0;
let protect = !lua.unlikely_memory_error();
push_table(state, 0, lower_bound, protect)?;
for (k, v) in iter {
lua.push(k)?;
lua.push(v)?;
if protect {
protect_lua!(state, 3, 1, fn(state) ffi::lua_rawset(state, -3))?;
} else {
ffi::lua_rawset(state, -3);
}
}
Ok(Table(lua.pop_ref()))
}
unsafe { self.lock().create_table_from(iter) }
}
/// Creates a table from an iterator of values, using `1..` as the keys.
@@ -1309,8 +1297,24 @@ impl Lua {
/// This function is unsafe because provides a way to execute unsafe C function.
pub unsafe fn create_c_function(&self, func: ffi::lua_CFunction) -> Result<Function> {
let lua = self.lock();
ffi::lua_pushcfunction(lua.ref_thread(), func);
Ok(Function(lua.pop_ref_thread()))
if cfg!(any(feature = "lua54", feature = "lua53", feature = "lua52")) {
ffi::lua_pushcfunction(lua.ref_thread(), func);
return Ok(Function(lua.pop_ref_thread()));
}
// Lua <5.2 requires memory allocation to push a C function
let state = lua.state();
{
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
if lua.unlikely_memory_error() {
ffi::lua_pushcfunction(state, func);
} else {
protect_lua!(state, 0, 1, |state| ffi::lua_pushcfunction(state, func))?;
}
Ok(Function(lua.pop_ref()))
}
}
/// Wraps a Rust async function or closure, creating a callable Lua function handle to it.
@@ -1390,8 +1394,8 @@ impl Lua {
}
/// Creates a Lua userdata object from a custom serializable userdata type.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
#[inline]
pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
@@ -1418,8 +1422,8 @@ impl Lua {
/// Creates a Lua userdata object from a custom serializable Rust type.
///
/// See [`Lua::create_any_userdata`] for more details.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
#[inline]
pub fn create_ser_any_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
@@ -1439,7 +1443,7 @@ impl Lua {
let lua = self.lock();
unsafe {
// Deregister the type if it already registered
if let Some(&table_id) = (*lua.extra.get()).registered_userdata_t.get(&type_id) {
if let Some(table_id) = (*lua.extra.get()).registered_userdata_t.remove(&type_id) {
ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, table_id);
}
@@ -1573,6 +1577,39 @@ impl Lua {
}
}
/// Sets the global environment.
///
/// This will replace the current global environment with the provided `globals` table.
///
/// For Lua 5.2+ the globals table is stored in the registry and shared between all threads.
/// For Lua 5.1 and Luau the globals table is stored in each thread.
///
/// Please note that any existing Lua functions have cached global environment and will not
/// see the changes made by this method.
/// To update the environment for existing Lua functions, use [`Function::set_environment`].
pub fn set_globals(&self, globals: Table) -> Result<()> {
let lua = self.lock();
let state = lua.state();
unsafe {
#[cfg(feature = "luau")]
if (*lua.extra.get()).sandboxed {
return Err(Error::runtime("cannot change globals in a sandboxed Lua state"));
}
let _sg = StackGuard::new(state);
check_stack(state, 1)?;
lua.push_ref(&globals.0);
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS);
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX);
}
Ok(())
}
/// Returns a handle to the active `Thread`.
///
/// For calls to `Lua` this will be the main Lua thread, for parameters given to a callback,
@@ -1978,7 +2015,7 @@ impl Lua {
/// Panics if the data object of type `T` is currently mutably borrowed. Multiple immutable
/// reads can be taken out at the same time.
#[track_caller]
pub fn app_data_ref<T: 'static>(&self) -> Option<AppDataRef<T>> {
pub fn app_data_ref<T: 'static>(&self) -> Option<AppDataRef<'_, T>> {
let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() };
extra.app_data.borrow(Some(guard))
@@ -1986,7 +2023,7 @@ impl Lua {
/// Tries to get a reference to an application data object stored by [`Lua::set_app_data`] of
/// type `T`.
pub fn try_app_data_ref<T: 'static>(&self) -> StdResult<Option<AppDataRef<T>>, BorrowError> {
pub fn try_app_data_ref<T: 'static>(&self) -> StdResult<Option<AppDataRef<'_, T>>, BorrowError> {
let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() };
extra.app_data.try_borrow(Some(guard))
@@ -1999,7 +2036,7 @@ impl Lua {
///
/// Panics if the data object of type `T` is currently borrowed.
#[track_caller]
pub fn app_data_mut<T: 'static>(&self) -> Option<AppDataRefMut<T>> {
pub fn app_data_mut<T: 'static>(&self) -> Option<AppDataRefMut<'_, T>> {
let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() };
extra.app_data.borrow_mut(Some(guard))
@@ -2007,7 +2044,7 @@ impl Lua {
/// Tries to get a mutable reference to an application data object stored by
/// [`Lua::set_app_data`] of type `T`.
pub fn try_app_data_mut<T: 'static>(&self) -> StdResult<Option<AppDataRefMut<T>>, BorrowMutError> {
pub fn try_app_data_mut<T: 'static>(&self) -> StdResult<Option<AppDataRefMut<'_, T>>, BorrowMutError> {
let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() };
extra.app_data.try_borrow_mut(Some(guard))
@@ -2036,6 +2073,108 @@ impl Lua {
LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut std::os::raw::c_void)
}
#[cfg(feature = "async")]
#[inline(always)]
pub(crate) fn poll_terminate() -> LightUserData {
static ASYNC_POLL_TERMINATE: u8 = 0;
LightUserData(&ASYNC_POLL_TERMINATE as *const u8 as *mut std::os::raw::c_void)
}
#[cfg(feature = "async")]
#[inline(always)]
pub(crate) fn poll_yield() -> LightUserData {
static ASYNC_POLL_YIELD: u8 = 0;
LightUserData(&ASYNC_POLL_YIELD as *const u8 as *mut std::os::raw::c_void)
}
/// Suspends the current async function, returning the provided arguments to caller.
///
/// This function is similar to [`coroutine.yield`] but allow yeilding Rust functions
/// and passing values to the caller.
/// Please note that you cannot cross [`Thread`] boundaries (e.g. calling `yield_with` on one
/// thread and resuming on another).
///
/// # Examples
///
/// Async iterator:
///
/// ```
/// # use mlua::{Lua, Result};
///
/// async fn generator(lua: Lua, _: ()) -> Result<()> {
/// for i in 0..10 {
/// lua.yield_with::<()>(i).await?;
/// }
/// Ok(())
/// }
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// lua.globals().set("generator", lua.create_async_function(generator)?)?;
///
/// lua.load(r#"
/// local n = 0
/// for i in coroutine.wrap(generator) do
/// n = n + i
/// end
/// assert(n == 45)
/// "#)
/// .exec()
/// }
/// ```
///
/// Exchange values on yield:
///
/// ```
/// # use mlua::{Lua, Result, Value};
///
/// async fn pingpong(lua: Lua, mut val: i32) -> Result<()> {
/// loop {
/// val = lua.yield_with::<i32>(val).await? + 1;
/// }
/// Ok(())
/// }
///
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
///
/// let co = lua.create_thread(lua.create_async_function(pingpong)?)?;
/// assert_eq!(co.resume::<i32>(1)?, 1);
/// assert_eq!(co.resume::<i32>(2)?, 3);
/// assert_eq!(co.resume::<i32>(3)?, 4);
///
/// # Ok(())
/// # }
/// ```
///
/// [`coroutine.yield`]: https://www.lua.org/manual/5.4/manual.html#pdf-coroutine.yield
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn yield_with<R: FromLuaMulti>(&self, args: impl IntoLuaMulti) -> Result<R> {
let mut args = Some(args.into_lua_multi(self)?);
future::poll_fn(move |_cx| match args.take() {
Some(args) => unsafe {
let lua = self.lock();
lua.push(Self::poll_yield())?; // yield marker
if args.len() <= 1 {
lua.push(args.front())?;
} else {
lua.push(lua.create_sequence_from(&args)?)?;
}
lua.push(args.len())?;
Poll::Pending
},
None => unsafe {
let lua = self.lock();
let state = lua.state();
let _sg = StackGuard::with_top(state, 0);
let nvals = ffi::lua_gettop(state);
Poll::Ready(R::from_stack_multi(nvals, &lua))
},
})
.await
}
/// Returns a weak reference to the Lua instance.
///
/// This is useful for creating a reference to the Lua instance that does not prevent it from
@@ -2045,7 +2184,6 @@ impl Lua {
WeakLua(XRc::downgrade(&self.raw))
}
// Luau version located in `luau/mod.rs`
#[cfg(not(feature = "luau"))]
fn disable_c_modules(&self) -> Result<()> {
let package: Table = self.globals().get("package")?;
@@ -2068,13 +2206,15 @@ impl Lua {
// The third and fourth searchers looks for a loader as a C library
searchers.raw_set(3, loader)?;
searchers.raw_remove(4)?;
if searchers.raw_len() >= 4 {
searchers.raw_remove(4)?;
}
Ok(())
}
#[inline(always)]
pub(crate) fn lock(&self) -> ReentrantMutexGuard<RawLua> {
pub(crate) fn lock(&self) -> ReentrantMutexGuard<'_, RawLua> {
let rawlua = self.raw.lock();
#[cfg(feature = "luau")]
if unsafe { (*rawlua.extra.get()).running_gc } {
+36 -6
View File
@@ -28,7 +28,7 @@ use super::{Lua, WeakLua};
static EXTRA_REGISTRY_KEY: u8 = 0;
const WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY: usize = 64;
const REF_STACK_RESERVE: c_int = 2;
const REF_STACK_RESERVE: c_int = 3;
/// Data associated with the Lua state.
pub(crate) struct ExtraData {
@@ -44,8 +44,9 @@ pub(crate) struct ExtraData {
// When Lua instance dropped, setting `None` would prevent collecting `RegistryKey`s
pub(super) registry_unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
// Container to store arbitrary data (extensions)
// Containers to store arbitrary data (extensions)
pub(super) app_data: AppData,
pub(super) app_data_priv: AppData,
pub(super) safe: bool,
pub(super) libs: StdLib,
@@ -63,7 +64,7 @@ pub(crate) struct ExtraData {
pub(super) wrapped_failure_top: usize,
// Pool of `Thread`s (coroutines) for async execution
#[cfg(feature = "async")]
pub(super) thread_pool: Vec<c_int>,
pub(super) thread_pool: Vec<crate::types::ValueRefIndex>,
// Address of `WrappedFailure` metatable
pub(super) wrapped_failure_mt_ptr: *const c_void,
@@ -75,7 +76,7 @@ pub(crate) struct ExtraData {
#[cfg(not(feature = "luau"))]
pub(super) hook_callback: Option<crate::types::HookCallback>,
#[cfg(not(feature = "luau"))]
pub(super) hook_triggers: crate::hook::HookTriggers,
pub(super) hook_triggers: crate::debug::HookTriggers,
#[cfg(feature = "lua54")]
pub(super) warn_callback: Option<crate::types::WarnCallback>,
#[cfg(feature = "luau")]
@@ -88,7 +89,7 @@ pub(crate) struct ExtraData {
#[cfg(feature = "luau")]
pub(crate) running_gc: bool,
#[cfg(feature = "luau")]
pub(super) sandboxed: bool,
pub(crate) sandboxed: bool,
#[cfg(feature = "luau")]
pub(super) compiler: Option<Compiler>,
#[cfg(feature = "luau-jit")]
@@ -159,6 +160,7 @@ impl ExtraData {
last_checked_userdata_mt: (ptr::null(), None),
registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))),
app_data: AppData::default(),
app_data_priv: AppData::default(),
safe: false,
libs: StdLib::NONE,
skip_memory_check: false,
@@ -210,7 +212,7 @@ impl ExtraData {
self.weak.write(WeakLua(XRc::downgrade(raw)));
}
pub(super) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
#[cfg(feature = "luau")]
if cfg!(not(feature = "module")) {
// In the main app we can use `lua_callbacks` to access ExtraData
@@ -257,4 +259,32 @@ impl ExtraData {
pub(super) unsafe fn weak(&self) -> &WeakLua {
self.weak.assume_init_ref()
}
/// Pops a reference from top of the auxiliary stack and move it to a first free slot.
pub(super) unsafe fn ref_stack_pop(&mut self) -> c_int {
if let Some(free) = self.ref_free.pop() {
ffi::lua_replace(self.ref_thread, free);
return free;
}
// Try to grow max stack size
if self.ref_stack_top >= self.ref_stack_size {
let mut inc = self.ref_stack_size; // Try to double stack size
while inc > 0 && ffi::lua_checkstack(self.ref_thread, inc + REF_STACK_RESERVE) == 0 {
inc /= 2;
}
if inc == 0 {
// Pop item on top of the stack to avoid stack leaking and successfully run destructors
// during unwinding.
ffi::lua_pop(self.ref_thread, 1);
let top = self.ref_stack_top;
// It is a user error to create too many references to exhaust the Lua max stack size
// for the ref thread.
panic!("cannot create a Lua reference, out of auxiliary stack space (used {top} slots)");
}
self.ref_stack_size += inc;
}
self.ref_stack_top += 1;
self.ref_stack_top
}
}
+113 -80
View File
@@ -5,14 +5,13 @@ 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 crate::chunk::ChunkMode;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::memory::{MemoryState, ALLOCATOR};
use crate::state::util::{callback_error_ext, ref_stack_pop};
use crate::state::util::callback_error_ext;
use crate::stdlib::StdLib;
use crate::string::String;
use crate::table::Table;
@@ -39,7 +38,7 @@ use super::{Lua, LuaOptions, WeakLua};
#[cfg(not(feature = "luau"))]
use crate::{
hook::Debug,
debug::Debug,
types::{HookCallback, HookKind, VmState},
};
@@ -209,7 +208,7 @@ impl RawLua {
}
// Init serde metatables
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
crate::serde::init_metatables(state)?;
Ok::<_, Error>(())
@@ -297,7 +296,7 @@ impl RawLua {
if is_safe {
let curr_libs = (*self.extra.get()).libs;
if (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) {
mlua_expect!(self.lua().disable_c_modules(), "Error during disabling C modules");
mlua_expect!(self.lua().disable_c_modules(), "Error disabling C modules");
}
}
#[cfg(feature = "luau")]
@@ -307,27 +306,27 @@ impl RawLua {
res
}
/// See [`Lua::try_set_app_data`]
/// Private version of [`Lua::try_set_app_data`]
#[inline]
pub(crate) fn try_set_app_data<T: MaybeSend + 'static>(&self, data: T) -> StdResult<Option<T>, T> {
pub(crate) fn set_priv_app_data<T: MaybeSend + 'static>(&self, data: T) -> Option<T> {
let extra = unsafe { &*self.extra.get() };
extra.app_data.try_insert(data)
extra.app_data_priv.insert(data)
}
/// See [`Lua::app_data_ref`]
/// Private version of [`Lua::app_data_ref`]
#[track_caller]
#[inline]
pub(crate) fn app_data_ref_unguarded<T: 'static>(&self) -> Option<AppDataRef<T>> {
pub(crate) fn priv_app_data_ref<T: 'static>(&self) -> Option<AppDataRef<'_, T>> {
let extra = unsafe { &*self.extra.get() };
extra.app_data.borrow(None)
extra.app_data_priv.borrow(None)
}
/// See [`Lua::app_data_mut`]
/// Private version of [`Lua::app_data_mut`]
#[track_caller]
#[inline]
pub(crate) fn app_data_mut_unguarded<T: 'static>(&self) -> Option<AppDataRefMut<T>> {
pub(crate) fn priv_app_data_mut<T: 'static>(&self) -> Option<AppDataRefMut<'_, T>> {
let extra = unsafe { &*self.extra.get() };
extra.app_data.borrow_mut(None)
extra.app_data_priv.borrow_mut(None)
}
/// See [`Lua::create_registry_value`]
@@ -436,8 +435,8 @@ impl RawLua {
match (*extra).hook_callback.clone() {
Some(hook_callback) => {
let rawlua = (*extra).raw_lua();
let debug = Debug::new(rawlua, ar);
hook_callback((*extra).lua(), debug)
let debug = Debug::new(rawlua, 0, ar);
hook_callback((*extra).lua(), &debug)
}
None => {
ffi::lua_sethook(state, None, 0, 0);
@@ -466,9 +465,9 @@ impl RawLua {
let status = callback_error_ext(state, ptr::null_mut(), false, |extra, _| {
let rawlua = (*extra).raw_lua();
let debug = Debug::new(rawlua, ar);
let debug = Debug::new(rawlua, 0, ar);
let hook_callback = (*hook_callback_ptr).clone();
hook_callback((*extra).lua(), debug)
hook_callback((*extra).lua(), &debug)
});
process_status(state, (*ar).event, status)
}
@@ -524,6 +523,20 @@ impl RawLua {
Ok(String(self.pop_ref()))
}
#[cfg(feature = "luau")]
pub(crate) unsafe fn create_buffer_with_capacity(&self, size: usize) -> Result<(*mut u8, crate::Buffer)> {
let state = self.state();
if self.unlikely_memory_error() {
let ptr = crate::util::push_buffer(state, size, false)?;
return Ok((ptr, crate::Buffer(self.pop_ref())));
}
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
let ptr = crate::util::push_buffer(state, size, true)?;
Ok((ptr, crate::Buffer(self.pop_ref())))
}
/// 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();
@@ -538,6 +551,34 @@ impl RawLua {
Ok(Table(self.pop_ref()))
}
/// See [`Lua::create_table_from`]
pub(crate) unsafe fn create_table_from<I, K, V>(&self, iter: I) -> Result<Table>
where
I: IntoIterator<Item = (K, V)>,
K: IntoLua,
V: IntoLua,
{
let state = self.state();
let _sg = StackGuard::new(state);
check_stack(state, 6)?;
let iter = iter.into_iter();
let lower_bound = iter.size_hint().0;
let protect = !self.unlikely_memory_error();
push_table(state, 0, lower_bound, protect)?;
for (k, v) in iter {
self.push(k)?;
self.push(v)?;
if protect {
protect_lua!(state, 3, 1, fn(state) ffi::lua_rawset(state, -3))?;
} else {
ffi::lua_rawset(state, -3);
}
}
Ok(Table(self.pop_ref()))
}
/// See [`Lua::create_sequence_from`]
pub(crate) unsafe fn create_sequence_from<T, I>(&self, iter: I) -> Result<Table>
where
@@ -597,7 +638,7 @@ impl RawLua {
#[cfg(feature = "async")]
pub(crate) unsafe fn create_recycled_thread(&self, func: &Function) -> Result<Thread> {
if let Some(index) = (*self.extra.get()).thread_pool.pop() {
let thread_state = ffi::lua_tothread(self.ref_thread(), index);
let thread_state = ffi::lua_tothread(self.ref_thread(), *index.0);
ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index);
#[cfg(feature = "luau")]
@@ -613,48 +654,14 @@ impl RawLua {
self.create_thread(func)
}
/// Resets thread (coroutine) and returns it to the pool for later use.
/// Returns the thread to the pool for later use.
#[cfg(feature = "async")]
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) {
let thread_state = thread.1;
let extra = &mut *self.extra.get();
if extra.thread_pool.len() == extra.thread_pool.capacity() {
#[cfg(feature = "lua54")]
if ffi::lua_status(thread_state) != ffi::LUA_OK {
// Close all to-be-closed variables without returning thread to the pool
#[cfg(not(feature = "vendored"))]
ffi::lua_resetthread(thread_state);
#[cfg(feature = "vendored")]
ffi::lua_closethread(thread_state, self.state());
if extra.thread_pool.len() < extra.thread_pool.capacity() {
if let Some(index) = thread.0.index_count.take() {
extra.thread_pool.push(index);
}
return;
}
let mut reset_ok = false;
if ffi::lua_status(thread_state) == ffi::LUA_OK {
if ffi::lua_gettop(thread_state) > 0 {
ffi::lua_settop(thread_state, 0);
}
reset_ok = true;
}
#[cfg(feature = "lua54")]
if !reset_ok {
#[cfg(not(feature = "vendored"))]
let status = ffi::lua_resetthread(thread_state);
#[cfg(feature = "vendored")]
let status = ffi::lua_closethread(thread_state, self.state());
reset_ok = status == ffi::LUA_OK;
}
#[cfg(feature = "luau")]
if !reset_ok {
ffi::lua_resetthread(thread_state);
reset_ok = true;
}
if reset_ok {
extra.thread_pool.push(thread.0.index);
thread.0.drop = false; // Prevent thread from being garbage collected
}
}
@@ -702,7 +709,7 @@ impl RawLua {
/// Pops a value from the Lua stack.
///
/// Uses 2 stack spaces, does not call `checkstack`.
/// Uses up to 1 stack spaces, does not call `checkstack`.
pub(crate) unsafe fn pop_value(&self) -> Value {
let value = self.stack_value(-1, None);
ffi::lua_pop(self.state(), 1);
@@ -711,7 +718,7 @@ impl RawLua {
/// Returns value at given stack index without popping it.
///
/// Uses 2 stack spaces, does not call checkstack.
/// Uses up to 1 stack spaces, does not call `checkstack`.
pub(crate) unsafe fn stack_value(&self, idx: c_int, type_hint: Option<c_int>) -> Value {
let state = self.state();
match type_hint.unwrap_or_else(|| ffi::lua_type(state, idx)) {
@@ -736,7 +743,7 @@ impl RawLua {
let n = ffi::lua_tonumber(state, idx);
match num_traits::cast(n) {
Some(i) if (n - (i as Number)).abs() < Number::EPSILON => Value::Integer(i),
Some(i) if n.to_bits() == (i as Number).to_bits() => Value::Integer(i),
_ => Value::Number(n),
}
}
@@ -824,21 +831,14 @@ impl RawLua {
#[inline]
pub(crate) unsafe fn pop_ref(&self) -> ValueRef {
ffi::lua_xmove(self.state(), self.ref_thread(), 1);
let index = ref_stack_pop(self.extra.get());
let index = (*self.extra.get()).ref_stack_pop();
ValueRef::new(self, index)
}
// Same as `pop_ref` but assumes the value is already on the reference thread
#[inline]
pub(crate) unsafe fn pop_ref_thread(&self) -> ValueRef {
let index = ref_stack_pop(self.extra.get());
ValueRef::new(self, index)
}
#[inline]
pub(crate) unsafe fn clone_ref(&self, vref: &ValueRef) -> ValueRef {
ffi::lua_pushvalue(self.ref_thread(), vref.index);
let index = ref_stack_pop(self.extra.get());
let index = (*self.extra.get()).ref_stack_pop();
ValueRef::new(self, index)
}
@@ -1244,7 +1244,7 @@ impl RawLua {
let rawlua = (*extra).raw_lua();
let func = &*(*upvalue).data;
let fut = func(rawlua, nargs);
let fut = Some(func(rawlua, nargs));
let extra = XRc::clone(&(*upvalue).extra);
let protect = !rawlua.unlikely_memory_error();
push_internal_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?;
@@ -1262,20 +1262,34 @@ impl RawLua {
unsafe extern "C-unwind" fn poll_future(state: *mut ffi::lua_State) -> c_int {
let upvalue = get_userdata::<AsyncPollUpvalue>(state, ffi::lua_upvalueindex(1));
callback_error_ext(state, (*upvalue).extra.get(), true, |extra, _| {
callback_error_ext(state, (*upvalue).extra.get(), true, |extra, nargs| {
// Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments)
// The lock must be already held as the future is polled
let rawlua = (*extra).raw_lua();
if nargs == 1 && ffi::lua_tolightuserdata(state, -1) == Lua::poll_terminate().0 {
// Destroy the future and terminate the Lua thread
(*upvalue).data.take();
ffi::lua_pushinteger(state, -1);
return Ok(1);
}
let fut = &mut (*upvalue).data;
let mut ctx = Context::from_waker(rawlua.waker());
match fut.as_mut().poll(&mut ctx) {
Poll::Pending => {
match fut.as_mut().map(|fut| fut.as_mut().poll(&mut ctx)) {
Some(Poll::Pending) => {
let fut_nvals = ffi::lua_gettop(state);
if fut_nvals >= 3 && ffi::lua_tolightuserdata(state, -3) == Lua::poll_yield().0 {
// We have some values to yield
ffi::lua_pushnil(state);
ffi::lua_replace(state, -4);
return Ok(3);
}
ffi::lua_pushnil(state);
ffi::lua_pushlightuserdata(state, Lua::poll_pending().0);
Ok(2)
}
Poll::Ready(nresults) => {
Some(Poll::Ready(nresults)) => {
match nresults? {
nresults if nresults < 3 => {
// Fast path for up to 2 results without creating a table
@@ -1293,6 +1307,7 @@ impl RawLua {
}
}
}
None => Err(Error::CallbackDestructed),
}
})
}
@@ -1338,8 +1353,9 @@ impl RawLua {
lua.load(
r#"
local poll = get_poll(...)
local nres, res, res2 = poll()
while true do
local nres, res, res2 = poll()
-- Poll::Ready branch, `nres` is the number of results
if nres ~= nil then
if nres == 0 then
return
@@ -1347,11 +1363,28 @@ impl RawLua {
return res
elseif nres == 2 then
return res, res2
elseif nres < 0 then
-- Negative `nres` means that the future is terminated
-- It must stay yielded and never be resumed again
yield()
else
return unpack(res, nres)
end
end
yield(res) -- `res` is a "pending" value
-- Poll::Pending branch
if res2 == nil then
-- `res` is a "pending" value
-- `yield` can return a signal to drop the future that we should propagate
-- to the poller
nres, res, res2 = poll(yield(res))
elseif res2 == 0 then
nres, res, res2 = poll(yield())
elseif res2 == 1 then
nres, res, res2 = poll(yield(res))
else
nres, res, res2 = poll(yield(unpack(res, res2)))
end
end
"#,
)
@@ -1363,14 +1396,14 @@ impl RawLua {
#[cfg(feature = "async")]
#[inline]
pub(crate) unsafe fn waker(&self) -> &Waker {
(*self.extra.get()).waker.as_ref()
pub(crate) fn waker(&self) -> &Waker {
unsafe { (*self.extra.get()).waker.as_ref() }
}
#[cfg(feature = "async")]
#[inline]
pub(crate) unsafe fn set_waker(&self, waker: NonNull<Waker>) -> NonNull<Waker> {
mem::replace(&mut (*self.extra.get()).waker, waker)
pub(crate) fn set_waker(&self, waker: NonNull<Waker>) -> NonNull<Waker> {
unsafe { mem::replace(&mut (*self.extra.get()).waker, waker) }
}
}
+1 -29
View File
@@ -89,7 +89,7 @@ where
PreallocatedFailure::New(_) => {
ffi::lua_rotate(state, 1, -1);
ffi::lua_xmove(state, ref_thread, 1);
let index = ref_stack_pop(extra);
let index = (*extra).ref_stack_pop();
(*extra).wrapped_failure_pool.push(index);
(*extra).wrapped_failure_top += 1;
}
@@ -150,31 +150,3 @@ where
}
}
}
pub(super) unsafe fn ref_stack_pop(extra: *mut ExtraData) -> c_int {
let extra = &mut *extra;
if let Some(free) = extra.ref_free.pop() {
ffi::lua_replace(extra.ref_thread, free);
return free;
}
// Try to grow max stack size
if extra.ref_stack_top >= extra.ref_stack_size {
let mut inc = extra.ref_stack_size; // Try to double stack size
while inc > 0 && ffi::lua_checkstack(extra.ref_thread, inc) == 0 {
inc /= 2;
}
if inc == 0 {
// Pop item on top of the stack to avoid stack leaking and successfully run destructors
// during unwinding.
ffi::lua_pop(extra.ref_thread, 1);
let top = extra.ref_stack_top;
// It is a user error to create enough references to exhaust the Lua max stack size for
// the ref thread.
panic!("cannot create a Lua reference, out of auxiliary stack space (used {top} slots)");
}
extra.ref_stack_size += inc;
}
extra.ref_stack_top += 1;
extra.ref_stack_top
}
+9 -9
View File
@@ -11,7 +11,7 @@ use crate::traits::IntoLua;
use crate::types::{LuaType, ValueRef};
use crate::value::Value;
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
use {
serde::ser::{Serialize, Serializer},
std::result::Result as StdResult,
@@ -43,7 +43,7 @@ impl String {
/// # }
/// ```
#[inline]
pub fn to_str(&self) -> Result<BorrowedStr> {
pub fn to_str(&self) -> Result<BorrowedStr<'_>> {
BorrowedStr::try_from(self)
}
@@ -86,7 +86,7 @@ impl String {
/// Get the bytes that make up this string.
///
/// The returned slice will not contain the terminating nul byte, but will contain any nul
/// The returned slice will not contain the terminating null byte, but will contain any null
/// bytes embedded into the Lua string.
///
/// # Examples
@@ -102,19 +102,19 @@ impl String {
/// # }
/// ```
#[inline]
pub fn as_bytes(&self) -> BorrowedBytes {
pub fn as_bytes(&self) -> BorrowedBytes<'_> {
BorrowedBytes::from(self)
}
/// Get the bytes that make up this string, including the trailing nul byte.
pub fn as_bytes_with_nul(&self) -> BorrowedBytes {
/// Get the bytes that make up this string, including the trailing null byte.
pub fn as_bytes_with_nul(&self) -> BorrowedBytes<'_> {
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self);
// Include the trailing nul byte (it's always present but excluded by default)
// Include the trailing null byte (it's always present but excluded by default)
let buf = unsafe { slice::from_raw_parts((*buf).as_ptr(), (*buf).len() + 1) };
BorrowedBytes { buf, borrow, _lua }
}
// Does not return the terminating nul byte
// Does not return the terminating null byte
unsafe fn to_slice(&self) -> (&[u8], Lua) {
let lua = self.0.lua.upgrade();
let slice = {
@@ -211,7 +211,7 @@ impl Hash for String {
}
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
impl Serialize for String {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
+30 -38
View File
@@ -13,9 +13,9 @@ use crate::util::{assert_stack, check_stack, get_metatable_ptr, StackGuard};
use crate::value::{Nil, Value};
#[cfg(feature = "async")]
use futures_util::future::{self, Either, Future};
use crate::function::AsyncCallFuture;
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
use {
rustc_hash::FxHashSet,
serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer},
@@ -211,7 +211,7 @@ impl Table {
///
/// let always_equals_mt = lua.create_table()?;
/// always_equals_mt.set("__eq", lua.create_function(|_, (_t1, _t2): (Table, Table)| Ok(true))?)?;
/// table2.set_metatable(Some(always_equals_mt));
/// table2.set_metatable(Some(always_equals_mt))?;
///
/// assert!(table1.equals(&table1.clone())?);
/// assert!(table1.equals(&table2)?);
@@ -487,16 +487,12 @@ impl Table {
/// [`getmetatable`]: https://www.lua.org/manual/5.4/manual.html#pdf-getmetatable
pub fn metatable(&self) -> Option<Table> {
let lua = self.0.lua.lock();
let state = lua.state();
let ref_thread = lua.ref_thread();
unsafe {
let _sg = StackGuard::new(state);
assert_stack(state, 2);
lua.push_ref(&self.0);
if ffi::lua_getmetatable(state, -1) == 0 {
if ffi::lua_getmetatable(ref_thread, self.0.index) == 0 {
None
} else {
Some(Table(lua.pop_ref()))
Some(Table(lua.pop_ref_thread()))
}
}
}
@@ -505,27 +501,23 @@ impl Table {
///
/// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does
/// nothing).
pub fn set_metatable(&self, metatable: Option<Table>) {
// Workaround to throw readonly error without returning Result
pub fn set_metatable(&self, metatable: Option<Table>) -> Result<()> {
#[cfg(feature = "luau")]
if self.is_readonly() {
panic!("attempt to modify a readonly table");
return Err(Error::runtime("attempt to modify a readonly table"));
}
let lua = self.0.lua.lock();
let state = lua.state();
let ref_thread = lua.ref_thread();
unsafe {
let _sg = StackGuard::new(state);
assert_stack(state, 2);
lua.push_ref(&self.0);
if let Some(metatable) = metatable {
lua.push_ref(&metatable.0);
if let Some(metatable) = &metatable {
ffi::lua_pushvalue(ref_thread, metatable.0.index);
} else {
ffi::lua_pushnil(state);
ffi::lua_pushnil(ref_thread);
}
ffi::lua_setmetatable(state, -2);
ffi::lua_setmetatable(ref_thread, self.0.index);
}
Ok(())
}
/// Returns true if the table has metatable attached.
@@ -613,7 +605,7 @@ impl Table {
/// ```
///
/// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next
pub fn pairs<K: FromLua, V: FromLua>(&self) -> TablePairs<K, V> {
pub fn pairs<K: FromLua, V: FromLua>(&self) -> TablePairs<'_, K, V> {
TablePairs {
guard: self.0.lua.lock(),
table: self,
@@ -678,7 +670,7 @@ impl Table {
/// # Ok(())
/// # }
/// ```
pub fn sequence_values<V: FromLua>(&self) -> TableSequence<V> {
pub fn sequence_values<V: FromLua>(&self) -> TableSequence<'_, V> {
TableSequence {
guard: self.0.lua.lock(),
table: self,
@@ -735,7 +727,7 @@ impl Table {
Ok(())
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
pub(crate) fn is_array(&self) -> bool {
let lua = self.0.lua.lock();
let state = lua.state();
@@ -892,16 +884,16 @@ impl ObjectLike for Table {
R: FromLuaMulti,
{
// Convert table to a function and call via pcall that respects the `__call` metamethod.
Function(self.0.copy()).call(args)
Function(self.0.clone()).call(args)
}
#[cfg(feature = "async")]
#[inline]
fn call_async<R>(&self, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where
R: FromLuaMulti,
{
Function(self.0.copy()).call_async(args)
Function(self.0.clone()).call_async(args)
}
#[inline]
@@ -913,7 +905,7 @@ impl ObjectLike for Table {
}
#[cfg(feature = "async")]
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where
R: FromLuaMulti,
{
@@ -933,35 +925,35 @@ impl ObjectLike for Table {
#[cfg(feature = "async")]
#[inline]
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where
R: FromLuaMulti,
{
match self.get(name) {
Ok(Value::Function(func)) => Either::Left(func.call_async(args)),
Ok(Value::Function(func)) => func.call_async(args),
Ok(val) => {
let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
Either::Right(future::ready(Err(Error::RuntimeError(msg))))
AsyncCallFuture::error(Error::RuntimeError(msg))
}
Err(err) => Either::Right(future::ready(Err(err))),
Err(err) => AsyncCallFuture::error(err),
}
}
#[inline]
fn to_string(&self) -> Result<StdString> {
Value::Table(Table(self.0.copy())).to_string()
Value::Table(Table(self.0.clone())).to_string()
}
}
/// A wrapped [`Table`] with customized serialization behavior.
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
pub(crate) struct SerializableTable<'a> {
table: &'a Table,
options: crate::serde::de::Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
impl Serialize for Table {
#[inline]
fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
@@ -969,7 +961,7 @@ impl Serialize for Table {
}
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
impl<'a> SerializableTable<'a> {
#[inline]
pub(crate) fn new(
@@ -985,7 +977,7 @@ impl<'a> SerializableTable<'a> {
}
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
impl Serialize for SerializableTable<'_> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
+68 -34
View File
@@ -10,7 +10,7 @@ use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard};
#[cfg(not(feature = "luau"))]
use crate::{
hook::{Debug, HookTriggers},
debug::{Debug, HookTriggers},
types::HookKind,
};
@@ -86,13 +86,15 @@ unsafe impl Sync for Thread {}
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct AsyncThread<R> {
thread: Thread,
ret: PhantomData<R>,
ret: PhantomData<fn() -> R>,
recycle: bool,
}
impl Thread {
/// Returns reference to the Lua state that this thread is associated with.
#[doc(hidden)]
#[inline(always)]
fn state(&self) -> *mut ffi::lua_State {
pub fn state(&self) -> *mut ffi::lua_State {
self.1
}
@@ -154,7 +156,6 @@ impl Thread {
let thread_state = self.state();
unsafe {
let _sg = StackGuard::new(state);
let _thread_sg = StackGuard::with_top(thread_state, 0);
let nargs = args.push_into_stack_multi(&lua)?;
if nargs > 0 {
@@ -163,6 +164,7 @@ impl Thread {
pushed_nargs += nargs;
}
let _thread_sg = StackGuard::with_top(thread_state, 0);
let (_, nresults) = self.resume_inner(&lua, pushed_nargs)?;
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
@@ -190,12 +192,12 @@ impl Thread {
let thread_state = self.state();
unsafe {
let _sg = StackGuard::new(state);
let _thread_sg = StackGuard::with_top(thread_state, 0);
check_stack(state, 1)?;
error.push_into_stack(&lua)?;
ffi::lua_xmove(state, thread_state, 1);
let _thread_sg = StackGuard::with_top(thread_state, 0);
let (_, nresults) = self.resume_inner(&lua, ffi::LUA_RESUMEERROR)?;
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
@@ -263,11 +265,13 @@ impl Thread {
/// You can have multiple hooks for different threads.
///
/// To remove a hook call [`Thread::remove_hook`].
///
/// [`Lua::set_hook`]: crate::Lua::set_hook
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
where
F: Fn(&crate::Lua, Debug) -> Result<crate::VmState> + crate::MaybeSend + 'static,
F: Fn(&crate::Lua, &Debug) -> Result<crate::VmState> + crate::MaybeSend + 'static,
{
let lua = self.0.lua.lock();
unsafe {
@@ -305,29 +309,10 @@ impl Thread {
pub fn reset(&self, func: Function) -> Result<()> {
let lua = self.0.lua.lock();
let thread_state = self.state();
match self.status_inner(&lua) {
ThreadStatusInner::Running => return Err(Error::runtime("cannot reset a running thread")),
// Any Lua can reuse new or finished thread
ThreadStatusInner::New(_) => unsafe { ffi::lua_settop(thread_state, 0) },
ThreadStatusInner::Finished => {}
#[cfg(not(any(feature = "lua54", feature = "luau")))]
_ => return Err(Error::runtime("cannot reset non-finished thread")),
#[cfg(any(feature = "lua54", feature = "luau"))]
_ => unsafe {
#[cfg(all(feature = "lua54", not(feature = "vendored")))]
let status = ffi::lua_resetthread(thread_state);
#[cfg(all(feature = "lua54", feature = "vendored"))]
let status = ffi::lua_closethread(thread_state, lua.state());
#[cfg(feature = "lua54")]
if status != ffi::LUA_OK {
return Err(pop_error(thread_state, status));
}
#[cfg(feature = "luau")]
ffi::lua_resetthread(thread_state);
},
}
unsafe {
let status = self.status_inner(&lua);
self.reset_inner(status)?;
// Push function to the top of the thread stack
ffi::lua_xpush(lua.ref_thread(), thread_state, func.0.index);
@@ -342,6 +327,42 @@ impl Thread {
}
}
unsafe fn reset_inner(&self, status: ThreadStatusInner) -> Result<()> {
match status {
ThreadStatusInner::New(_) => {
// The thread is new, so we can just set the top to 0
ffi::lua_settop(self.state(), 0);
Ok(())
}
ThreadStatusInner::Running => Err(Error::runtime("cannot reset a running thread")),
ThreadStatusInner::Finished => Ok(()),
#[cfg(not(any(feature = "lua54", feature = "luau")))]
ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => {
Err(Error::runtime("cannot reset non-finished thread"))
}
#[cfg(any(feature = "lua54", feature = "luau"))]
ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => {
let thread_state = self.state();
#[cfg(all(feature = "lua54", not(feature = "vendored")))]
let status = ffi::lua_resetthread(thread_state);
#[cfg(all(feature = "lua54", feature = "vendored"))]
let status = {
let lua = self.0.lua.lock();
ffi::lua_closethread(thread_state, lua.state())
};
#[cfg(feature = "lua54")]
if status != ffi::LUA_OK {
return Err(pop_error(thread_state, status));
}
#[cfg(feature = "luau")]
ffi::lua_resetthread(thread_state);
Ok(())
}
}
}
/// Converts [`Thread`] to an [`AsyncThread`] which implements [`Future`] and [`Stream`] traits.
///
/// Only resumable threads can be converted to [`AsyncThread`].
@@ -453,7 +474,6 @@ impl Thread {
/// ```
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[doc(hidden)]
pub fn sandbox(&self) -> Result<()> {
let lua = self.0.lua.lock();
let state = lua.state();
@@ -505,8 +525,22 @@ impl<R> Drop for AsyncThread<R> {
fn drop(&mut self) {
if self.recycle {
if let Some(lua) = self.thread.0.lua.try_lock() {
// For Lua 5.4 this also closes all pending to-be-closed variables
unsafe { lua.recycle_thread(&mut self.thread) };
unsafe {
let mut status = self.thread.status_inner(&lua);
if matches!(status, ThreadStatusInner::Yielded(0)) {
// The thread is dropped while yielded, resume it with the "terminate" signal
ffi::lua_pushlightuserdata(self.thread.1, crate::Lua::poll_terminate().0);
if let Ok((new_status, _)) = self.thread.resume_inner(&lua, 1) {
// `new_status` should always be `ThreadStatusInner::Yielded(0)`
status = new_status;
}
}
// For Lua 5.4 this also closes all pending to-be-closed variables
if self.thread.reset_inner(status).is_ok() {
lua.recycle_thread(&mut self.thread);
}
}
}
}
}
@@ -570,7 +604,7 @@ impl<R: FromLuaMulti> Future for AsyncThread<R> {
if status.is_yielded() {
if !(nresults == 1 && is_poll_pending(thread_state)) {
// Ignore value returned via yield()
// Ignore values returned via yield()
cx.waker().wake_by_ref();
}
return Poll::Pending;
@@ -601,7 +635,7 @@ struct WakerGuard<'lua, 'a> {
impl<'lua, 'a> WakerGuard<'lua, 'a> {
#[inline]
pub fn new(lua: &'lua RawLua, waker: &'a Waker) -> Result<WakerGuard<'lua, 'a>> {
let prev = unsafe { lua.set_waker(NonNull::from(waker)) };
let prev = lua.set_waker(NonNull::from(waker));
Ok(WakerGuard {
lua,
prev,
@@ -613,7 +647,7 @@ impl<'lua, 'a> WakerGuard<'lua, 'a> {
#[cfg(feature = "async")]
impl Drop for WakerGuard<'_, '_> {
fn drop(&mut self) {
unsafe { self.lua.set_waker(self.prev) };
self.lua.set_waker(self.prev);
}
}
+4 -4
View File
@@ -11,7 +11,7 @@ use crate::util::{check_stack, short_type_name};
use crate::value::Value;
#[cfg(feature = "async")]
use std::future::Future;
use {crate::function::AsyncCallFuture, std::future::Future};
/// Trait for types convertible to [`Value`].
pub trait IntoLua: Sized {
@@ -162,7 +162,7 @@ pub trait ObjectLike: Sealed {
/// arguments.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async<R>(&self, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where
R: FromLuaMulti;
@@ -178,7 +178,7 @@ pub trait ObjectLike: Sealed {
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where
R: FromLuaMulti;
@@ -196,7 +196,7 @@ pub trait ObjectLike: Sealed {
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where
R: FromLuaMulti;
+8 -5
View File
@@ -1,9 +1,9 @@
use std::cell::UnsafeCell;
use std::os::raw::{c_int, c_void};
use crate::error::Result;
#[cfg(not(feature = "luau"))]
use crate::hook::{Debug, HookTriggers};
use crate::debug::{Debug, HookTriggers};
use crate::error::Result;
use crate::state::{ExtraData, Lua, RawLua};
// Re-export mutex wrappers
@@ -20,6 +20,9 @@ pub use either::Either;
pub use registry_key::RegistryKey;
pub(crate) use value_ref::ValueRef;
#[cfg(feature = "async")]
pub(crate) use value_ref::ValueRefIndex;
/// Type of Lua integer numbers.
pub type Integer = ffi::lua_Integer;
/// Type of Lua floating point numbers.
@@ -61,7 +64,7 @@ pub(crate) type AsyncCallback =
pub(crate) type AsyncCallbackUpvalue = Upvalue<AsyncCallback>;
#[cfg(feature = "async")]
pub(crate) type AsyncPollUpvalue = Upvalue<BoxFuture<'static, Result<c_int>>>;
pub(crate) type AsyncPollUpvalue = Upvalue<Option<BoxFuture<'static, Result<c_int>>>>;
/// Type to set next Lua VM action after executing interrupt or hook function.
pub enum VmState {
@@ -79,10 +82,10 @@ pub(crate) enum HookKind {
}
#[cfg(all(feature = "send", not(feature = "luau")))]
pub(crate) type HookCallback = XRc<dyn Fn(&Lua, Debug) -> Result<VmState> + Send>;
pub(crate) type HookCallback = XRc<dyn Fn(&Lua, &Debug) -> Result<VmState> + Send>;
#[cfg(all(not(feature = "send"), not(feature = "luau")))]
pub(crate) type HookCallback = XRc<dyn Fn(&Lua, Debug) -> Result<VmState>>;
pub(crate) type HookCallback = XRc<dyn Fn(&Lua, &Debug) -> Result<VmState>>;
#[cfg(all(feature = "send", feature = "luau"))]
pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState> + Send>;
+4 -4
View File
@@ -43,7 +43,7 @@ impl AppData {
#[inline]
#[track_caller]
pub(crate) fn borrow<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRef<T>> {
pub(crate) fn borrow<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRef<'_, T>> {
match self.try_borrow(guard) {
Ok(data) => data,
Err(err) => panic!("already mutably borrowed: {err:?}"),
@@ -53,7 +53,7 @@ impl AppData {
pub(crate) fn try_borrow<T: 'static>(
&self,
guard: Option<LuaGuard>,
) -> Result<Option<AppDataRef<T>>, BorrowError> {
) -> Result<Option<AppDataRef<'_, T>>, BorrowError> {
let data = unsafe { &*self.container.get() }
.get(&TypeId::of::<T>())
.map(|c| c.try_borrow())
@@ -74,7 +74,7 @@ impl AppData {
#[inline]
#[track_caller]
pub(crate) fn borrow_mut<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRefMut<T>> {
pub(crate) fn borrow_mut<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRefMut<'_, T>> {
match self.try_borrow_mut(guard) {
Ok(data) => data,
Err(err) => panic!("already borrowed: {err:?}"),
@@ -84,7 +84,7 @@ impl AppData {
pub(crate) fn try_borrow_mut<T: 'static>(
&self,
guard: Option<LuaGuard>,
) -> Result<Option<AppDataRefMut<T>>, BorrowMutError> {
) -> Result<Option<AppDataRefMut<'_, T>>, BorrowMutError> {
let data = unsafe { &*self.container.get() }
.get(&TypeId::of::<T>())
.map(|c| c.try_borrow_mut())
+1 -1
View File
@@ -31,7 +31,7 @@ mod inner {
}
#[inline(always)]
pub(crate) fn lock(&self) -> ReentrantMutexGuard<T> {
pub(crate) fn lock(&self) -> ReentrantMutexGuard<'_, T> {
ReentrantMutexGuard(&self.0)
}
+28 -23
View File
@@ -1,22 +1,39 @@
use std::fmt;
use std::os::raw::{c_int, c_void};
use super::XRc;
use crate::state::{RawLua, WeakLua};
/// A reference to a Lua (complex) value stored in the Lua auxiliary thread.
#[derive(Clone)]
pub struct ValueRef {
pub(crate) lua: WeakLua,
// Keep index separate to avoid additional indirection when accessing it.
pub(crate) index: c_int,
pub(crate) drop: bool,
// If `index_count` is `None`, the value does not need to be destroyed.
pub(crate) index_count: Option<ValueRefIndex>,
}
/// A reference to a Lua value index in the auxiliary thread.
/// It's cheap to clone and can be used to track the number of references to a value.
#[derive(Clone)]
pub(crate) struct ValueRefIndex(pub(crate) XRc<c_int>);
impl From<c_int> for ValueRefIndex {
#[inline]
fn from(index: c_int) -> Self {
ValueRefIndex(XRc::new(index))
}
}
impl ValueRef {
#[inline]
pub(crate) fn new(lua: &RawLua, index: c_int) -> Self {
pub(crate) fn new(lua: &RawLua, index: impl Into<ValueRefIndex>) -> Self {
let index = index.into();
ValueRef {
lua: lua.weak().clone(),
index,
drop: true,
index: *index.0,
index_count: Some(index),
}
}
@@ -25,16 +42,6 @@ impl ValueRef {
let lua = self.lua.lock();
unsafe { ffi::lua_topointer(lua.ref_thread(), self.index) }
}
/// Returns a copy of the value, which is valid as long as the original value is held.
#[inline]
pub(crate) fn copy(&self) -> Self {
ValueRef {
lua: self.lua.clone(),
index: self.index,
drop: false,
}
}
}
impl fmt::Debug for ValueRef {
@@ -43,17 +50,15 @@ impl fmt::Debug for ValueRef {
}
}
impl Clone for ValueRef {
fn clone(&self) -> Self {
unsafe { self.lua.lock().clone_ref(self) }
}
}
impl Drop for ValueRef {
fn drop(&mut self) {
if self.drop {
if let Some(lua) = self.lua.try_lock() {
unsafe { lua.drop_ref(self) };
if let Some(ValueRefIndex(index)) = self.index_count.take() {
// It's guaranteed that the inner value returns exactly once.
// This means in particular that the value is not dropped.
if XRc::into_inner(index).is_some() {
if let Some(lua) = self.lua.try_lock() {
unsafe { lua.drop_ref(self) };
}
}
}
}
+50 -48
View File
@@ -18,7 +18,7 @@ use crate::value::Value;
#[cfg(feature = "async")]
use std::future::Future;
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
use {
serde::ser::{self, Serialize, Serializer},
std::result::Result as StdResult,
@@ -240,6 +240,13 @@ impl AsRef<str> for MetaMethod {
}
}
impl From<MetaMethod> for StdString {
#[inline]
fn from(method: MetaMethod) -> Self {
method.name().to_owned()
}
}
/// Method registry for [`UserData`] implementors.
pub trait UserDataMethods<T> {
/// Add a regular method which accepts a `&T` as the first parameter.
@@ -249,7 +256,7 @@ pub trait UserDataMethods<T> {
///
/// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will
/// be used as a fall-back if no regular method is found.
fn add_method<M, A, R>(&mut self, name: impl ToString, method: M)
fn add_method<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -260,7 +267,7 @@ pub trait UserDataMethods<T> {
/// Refer to [`add_method`] for more information about the implementation.
///
/// [`add_method`]: UserDataMethods::add_method
fn add_method_mut<M, A, R>(&mut self, name: impl ToString, method: M)
fn add_method_mut<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -273,7 +280,7 @@ pub trait UserDataMethods<T> {
/// [`add_method`]: UserDataMethods::add_method
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_method<M, A, MR, R>(&mut self, name: impl ToString, method: M)
fn add_async_method<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
@@ -288,7 +295,7 @@ pub trait UserDataMethods<T> {
/// [`add_method`]: UserDataMethods::add_method
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl ToString, method: M)
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
@@ -301,7 +308,7 @@ pub trait UserDataMethods<T> {
/// The first argument will be a [`AnyUserData`] of type `T` if the method is called with Lua
/// method syntax: `my_userdata:my_method(arg1, arg2)`, or it is passed in as the first
/// argument: `my_userdata.my_method(my_userdata, arg1, arg2)`.
fn add_function<F, A, R>(&mut self, name: impl ToString, function: F)
fn add_function<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -312,7 +319,7 @@ pub trait UserDataMethods<T> {
/// This is a version of [`add_function`] that accepts a `FnMut` argument.
///
/// [`add_function`]: UserDataMethods::add_function
fn add_function_mut<F, A, R>(&mut self, name: impl ToString, function: F)
fn add_function_mut<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -326,7 +333,7 @@ pub trait UserDataMethods<T> {
/// [`add_function`]: UserDataMethods::add_function
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_function<F, A, FR, R>(&mut self, name: impl ToString, function: F)
fn add_async_function<F, A, FR, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti,
@@ -341,7 +348,7 @@ pub trait UserDataMethods<T> {
/// side has a metatable. To prevent this, use [`add_meta_function`].
///
/// [`add_meta_function`]: UserDataMethods::add_meta_function
fn add_meta_method<M, A, R>(&mut self, name: impl ToString, method: M)
fn add_meta_method<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -355,7 +362,7 @@ pub trait UserDataMethods<T> {
/// side has a metatable. To prevent this, use [`add_meta_function`].
///
/// [`add_meta_function`]: UserDataMethods::add_meta_function
fn add_meta_method_mut<M, A, R>(&mut self, name: impl ToString, method: M)
fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -371,7 +378,7 @@ pub trait UserDataMethods<T> {
docsrs,
doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau")))))
)]
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl ToString, method: M)
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
@@ -387,7 +394,7 @@ pub trait UserDataMethods<T> {
/// [`add_meta_method_mut`]: UserDataMethods::add_meta_method_mut
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl ToString, method: M)
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
@@ -400,7 +407,7 @@ pub trait UserDataMethods<T> {
/// Metamethods for binary operators can be triggered if either the left or right argument to
/// the binary operator has a metatable, so the first argument here is not necessarily a
/// userdata of type `T`.
fn add_meta_function<F, A, R>(&mut self, name: impl ToString, function: F)
fn add_meta_function<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -411,7 +418,7 @@ pub trait UserDataMethods<T> {
/// This is a version of [`add_meta_function`] that accepts a `FnMut` argument.
///
/// [`add_meta_function`]: UserDataMethods::add_meta_function
fn add_meta_function_mut<F, A, R>(&mut self, name: impl ToString, function: F)
fn add_meta_function_mut<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -427,7 +434,7 @@ pub trait UserDataMethods<T> {
docsrs,
doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau")))))
)]
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl ToString, function: F)
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti,
@@ -446,7 +453,7 @@ pub trait UserDataFields<T> {
///
/// If `add_meta_method` is used to set the `__index` metamethod, it will
/// be used as a fall-back if no regular field or method are found.
fn add_field<V>(&mut self, name: impl ToString, value: V)
fn add_field<V>(&mut self, name: impl Into<StdString>, value: V)
where
V: IntoLua + 'static;
@@ -457,7 +464,7 @@ pub trait UserDataFields<T> {
///
/// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will
/// be used as a fall-back if no regular field or method are found.
fn add_field_method_get<M, R>(&mut self, name: impl ToString, method: M)
fn add_field_method_get<M, R>(&mut self, name: impl Into<StdString>, method: M)
where
M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static,
R: IntoLua;
@@ -470,21 +477,21 @@ pub trait UserDataFields<T> {
///
/// If `add_meta_method` is used to set the `__newindex` metamethod, the `__newindex` metamethod
/// will be used as a fall-back if no regular field is found.
fn add_field_method_set<M, A>(&mut self, name: impl ToString, method: M)
fn add_field_method_set<M, A>(&mut self, name: impl Into<StdString>, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
A: FromLua;
/// Add a regular field getter as a function which accepts a generic [`AnyUserData`] of type `T`
/// argument.
fn add_field_function_get<F, R>(&mut self, name: impl ToString, function: F)
fn add_field_function_get<F, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static,
R: IntoLua;
/// Add a regular field setter as a function which accepts a generic [`AnyUserData`] of type `T`
/// first argument.
fn add_field_function_set<F, A>(&mut self, name: impl ToString, function: F)
fn add_field_function_set<F, A>(&mut self, name: impl Into<StdString>, function: F)
where
F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
A: FromLua;
@@ -497,7 +504,7 @@ pub trait UserDataFields<T> {
///
/// `mlua` will trigger an error on an attempt to define a protected metamethod,
/// like `__gc` or `__metatable`.
fn add_meta_field<V>(&mut self, name: impl ToString, value: V)
fn add_meta_field<V>(&mut self, name: impl Into<StdString>, value: V)
where
V: IntoLua + 'static;
@@ -509,7 +516,7 @@ pub trait UserDataFields<T> {
///
/// `mlua` will trigger an error on an attempt to define a protected metamethod,
/// like `__gc` or `__metatable`.
fn add_meta_field_with<F, R>(&mut self, name: impl ToString, f: F)
fn add_meta_field_with<F, R>(&mut self, name: impl Into<StdString>, f: F)
where
F: FnOnce(&Lua) -> Result<R> + 'static,
R: IntoLua;
@@ -683,22 +690,16 @@ impl AnyUserData {
/// Keeps associated user values unchanged (they will be collected by Lua's GC).
pub fn take<T: 'static>(&self) -> Result<T> {
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
let type_id = lua.push_userdata_ref(&self.0)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
if (*get_userdata::<UserDataStorage<T>>(state, -1)).has_exclusive_access() {
take_userdata::<UserDataStorage<T>>(state).into_inner()
} else {
Err(Error::UserDataBorrowMutError)
}
match lua.get_userdata_ref_type_id(&self.0)? {
Some(type_id) if type_id == TypeId::of::<T>() => unsafe {
let ref_thread = lua.ref_thread();
if (*get_userdata::<UserDataStorage<T>>(ref_thread, self.0.index)).has_exclusive_access() {
take_userdata::<UserDataStorage<T>>(ref_thread, self.0.index).into_inner()
} else {
Err(Error::UserDataBorrowMutError)
}
_ => Err(Error::UserDataTypeMismatch),
}
},
_ => Err(Error::UserDataTypeMismatch),
}
}
@@ -887,16 +888,17 @@ impl AnyUserData {
self.raw_metatable().map(UserDataMetatable)
}
/// Returns a raw metatable of this [`AnyUserData`].
fn raw_metatable(&self) -> Result<Table> {
let lua = self.0.lua.lock();
let state = lua.state();
let ref_thread = lua.ref_thread();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
// Check that userdata is registered and not destructed
// All registered userdata types have a non-empty metatable
let _type_id = lua.get_userdata_ref_type_id(&self.0)?;
lua.push_userdata_ref(&self.0)?;
ffi::lua_getmetatable(state, -1); // Checked that non-empty on the previous call
Ok(Table(lua.pop_ref()))
ffi::lua_getmetatable(ref_thread, self.0.index);
Ok(Table(lua.pop_ref_thread()))
}
}
@@ -963,7 +965,7 @@ impl AnyUserData {
/// Returns `true` if this [`AnyUserData`] is serializable (e.g. was created using
/// [`Lua::create_ser_userdata`]).
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
pub(crate) fn is_serializable(&self) -> bool {
let lua = self.0.lua.lock();
let is_serializable = || unsafe {
@@ -1014,7 +1016,7 @@ impl UserDataMetatable {
/// The pairs are wrapped in a [`Result`], since they are lazily converted to `V` type.
///
/// [`Result`]: crate::Result
pub fn pairs<V: FromLua>(&self) -> UserDataMetatablePairs<V> {
pub fn pairs<V: FromLua>(&self) -> UserDataMetatablePairs<'_, V> {
UserDataMetatablePairs(self.0.pairs())
}
}
@@ -1047,7 +1049,7 @@ where
}
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
impl Serialize for AnyUserData {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
@@ -1078,8 +1080,8 @@ impl AnyUserData {
/// [`IntoLua`] trait.
///
/// This function uses [`Lua::create_ser_any_userdata`] under the hood.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn wrap_ser<T: Serialize + MaybeSend + 'static>(data: T) -> impl IntoLua {
WrappedUserdata(move |lua| lua.create_ser_any_userdata(data))
}
+12 -12
View File
@@ -1,6 +1,6 @@
use std::cell::{RefCell, UnsafeCell};
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
use serde::ser::{Serialize, Serializer};
use crate::error::{Error, Result};
@@ -9,10 +9,10 @@ use crate::types::XRc;
use super::lock::{RawLock, UserDataLock};
use super::r#ref::{UserDataRef, UserDataRefMut};
#[cfg(all(feature = "serialize", not(feature = "send")))]
#[cfg(all(feature = "serde", not(feature = "send")))]
type DynSerialize = dyn erased_serde::Serialize;
#[cfg(all(feature = "serialize", feature = "send"))]
#[cfg(all(feature = "serde", feature = "send"))]
type DynSerialize = dyn erased_serde::Serialize + Send;
pub(crate) enum UserDataStorage<T> {
@@ -24,7 +24,7 @@ pub(crate) enum UserDataStorage<T> {
// It's stored inside a Lua VM and protected by the outer `ReentrantMutex`.
pub(crate) enum UserDataVariant<T> {
Default(XRc<UserDataCell<T>>),
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
Serializable(XRc<UserDataCell<Box<DynSerialize>>>, bool), // bool is `is_sync`
}
@@ -33,7 +33,7 @@ impl<T> Clone for UserDataVariant<T> {
fn clone(&self) -> Self {
match self {
Self::Default(inner) => Self::Default(XRc::clone(inner)),
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
Self::Serializable(inner, is_sync) => Self::Serializable(XRc::clone(inner), *is_sync),
}
}
@@ -79,7 +79,7 @@ impl<T> UserDataVariant<T> {
}
Ok(match self {
Self::Default(inner) => XRc::into_inner(inner).unwrap().value.into_inner(),
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => unsafe {
let raw = Box::into_raw(XRc::into_inner(inner).unwrap().value.into_inner());
*Box::from_raw(raw as *mut T)
@@ -91,7 +91,7 @@ impl<T> UserDataVariant<T> {
fn strong_count(&self) -> usize {
match self {
Self::Default(inner) => XRc::strong_count(inner),
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => XRc::strong_count(inner),
}
}
@@ -100,7 +100,7 @@ impl<T> UserDataVariant<T> {
pub(super) fn raw_lock(&self) -> &RawLock {
match self {
Self::Default(inner) => &inner.raw_lock,
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => &inner.raw_lock,
}
}
@@ -109,13 +109,13 @@ impl<T> UserDataVariant<T> {
pub(super) fn as_ptr(&self) -> *mut T {
match self {
Self::Default(inner) => inner.value.get(),
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => unsafe { &mut **(inner.value.get() as *mut Box<T>) },
}
}
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
impl Serialize for UserDataStorage<()> {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
match self {
@@ -197,7 +197,7 @@ impl<T: 'static> UserDataStorage<T> {
Self::Scoped(ScopedUserDataVariant::RefMut(RefCell::new(data)))
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
#[inline(always)]
pub(crate) fn new_ser(data: T) -> Self
where
@@ -209,7 +209,7 @@ impl<T: 'static> UserDataStorage<T> {
Self::Owned(variant)
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
#[inline(always)]
pub(crate) fn is_serializable(&self) -> bool {
matches!(self, Self::Owned(UserDataVariant::Serializable(..)))
+12 -12
View File
@@ -8,21 +8,21 @@ use crate::value::Value;
use crate::Function;
#[cfg(feature = "async")]
use futures_util::future::{self, Either, Future};
use crate::function::AsyncCallFuture;
impl ObjectLike for AnyUserData {
#[inline]
fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
// `lua_gettable` method used under the hood can work with any Lua value
// that has `__index` metamethod
Table(self.0.copy()).get_protected(key)
Table(self.0.clone()).get_protected(key)
}
#[inline]
fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
// `lua_settable` method used under the hood can work with any Lua value
// that has `__newindex` metamethod
Table(self.0.copy()).set_protected(key, value)
Table(self.0.clone()).set_protected(key, value)
}
#[inline]
@@ -30,16 +30,16 @@ impl ObjectLike for AnyUserData {
where
R: FromLuaMulti,
{
Function(self.0.copy()).call(args)
Function(self.0.clone()).call(args)
}
#[cfg(feature = "async")]
#[inline]
fn call_async<R>(&self, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where
R: FromLuaMulti,
{
Function(self.0.copy()).call_async(args)
Function(self.0.clone()).call_async(args)
}
#[inline]
@@ -51,7 +51,7 @@ impl ObjectLike for AnyUserData {
}
#[cfg(feature = "async")]
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where
R: FromLuaMulti,
{
@@ -72,22 +72,22 @@ impl ObjectLike for AnyUserData {
}
#[cfg(feature = "async")]
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where
R: FromLuaMulti,
{
match self.get(name) {
Ok(Value::Function(func)) => Either::Left(func.call_async(args)),
Ok(Value::Function(func)) => func.call_async(args),
Ok(val) => {
let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
Either::Right(future::ready(Err(Error::RuntimeError(msg))))
AsyncCallFuture::error(Error::RuntimeError(msg))
}
Err(err) => Either::Right(future::ready(Err(err))),
Err(err) => AsyncCallFuture::error(err),
}
}
#[inline]
fn to_string(&self) -> Result<StdString> {
Value::UserData(AnyUserData(self.0.copy())).to_string()
Value::UserData(AnyUserData(self.0.clone())).to_string()
}
}
+1 -1
View File
@@ -63,7 +63,7 @@ impl<T> TryFrom<UserDataVariant<T>> for UserDataRef<T> {
#[inline]
fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
let guard = if !cfg!(feature = "send") || is_sync::<T>() {
let guard = if cfg!(not(feature = "send")) || is_sync::<T>() {
variant.raw_lock().try_lock_shared_guarded()
} else {
variant.raw_lock().try_lock_exclusive_guarded()
+42 -42
View File
@@ -365,101 +365,101 @@ fn get_function_name<T>(name: &str) -> StdString {
}
impl<T> UserDataFields<T> for UserDataRegistry<T> {
fn add_field<V>(&mut self, name: impl ToString, value: V)
fn add_field<V>(&mut self, name: impl Into<StdString>, value: V)
where
V: IntoLua + 'static,
{
let name = name.to_string();
let name = name.into();
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)
fn add_field_method_get<M, R>(&mut self, name: impl Into<StdString>, method: M)
where
M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static,
R: IntoLua,
{
let name = name.to_string();
let name = name.into();
let callback = self.box_method(&name, move |lua, data, ()| method(lua, data));
self.raw.field_getters.push((name, callback));
}
fn add_field_method_set<M, A>(&mut self, name: impl ToString, method: M)
fn add_field_method_set<M, A>(&mut self, name: impl Into<StdString>, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
A: FromLua,
{
let name = name.to_string();
let name = name.into();
let callback = self.box_method_mut(&name, method);
self.raw.field_setters.push((name, callback));
}
fn add_field_function_get<F, R>(&mut self, name: impl ToString, function: F)
fn add_field_function_get<F, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static,
R: IntoLua,
{
let name = name.to_string();
let name = name.into();
let callback = self.box_function(&name, function);
self.raw.field_getters.push((name, callback));
}
fn add_field_function_set<F, A>(&mut self, name: impl ToString, mut function: F)
fn add_field_function_set<F, A>(&mut self, name: impl Into<StdString>, mut function: F)
where
F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
A: FromLua,
{
let name = name.to_string();
let name = name.into();
let callback = self.box_function_mut(&name, move |lua, (data, val)| function(lua, data, val));
self.raw.field_setters.push((name, callback));
}
fn add_meta_field<V>(&mut self, name: impl ToString, value: V)
fn add_meta_field<V>(&mut self, name: impl Into<StdString>, value: V)
where
V: IntoLua + 'static,
{
let lua = self.lua.lua();
let name = name.to_string();
let name = name.into();
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)
fn add_meta_field_with<F, R>(&mut self, name: impl Into<StdString>, f: F)
where
F: FnOnce(&Lua) -> Result<R> + 'static,
R: IntoLua,
{
let lua = self.lua.lua();
let name = name.to_string();
let name = name.into();
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));
}
}
impl<T> UserDataMethods<T> for UserDataRegistry<T> {
fn add_method<M, A, R>(&mut self, name: impl ToString, method: M)
fn add_method<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = name.to_string();
let name = name.into();
let callback = self.box_method(&name, method);
self.raw.methods.push((name, callback));
}
fn add_method_mut<M, A, R>(&mut self, name: impl ToString, method: M)
fn add_method_mut<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = name.to_string();
let name = name.into();
let callback = self.box_method_mut(&name, method);
self.raw.methods.push((name, callback));
}
#[cfg(feature = "async")]
fn add_async_method<M, A, MR, R>(&mut self, name: impl ToString, method: M)
fn add_async_method<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
@@ -467,13 +467,13 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti,
{
let name = name.to_string();
let name = name.into();
let callback = self.box_async_method(&name, method);
self.raw.async_methods.push((name, callback));
}
#[cfg(feature = "async")]
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl ToString, method: M)
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
@@ -481,70 +481,70 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti,
{
let name = name.to_string();
let name = name.into();
let callback = self.box_async_method_mut(&name, method);
self.raw.async_methods.push((name, callback));
}
fn add_function<F, A, R>(&mut self, name: impl ToString, function: F)
fn add_function<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = name.to_string();
let name = name.into();
let callback = self.box_function(&name, function);
self.raw.methods.push((name, callback));
}
fn add_function_mut<F, A, R>(&mut self, name: impl ToString, function: F)
fn add_function_mut<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = name.to_string();
let name = name.into();
let callback = self.box_function_mut(&name, function);
self.raw.methods.push((name, callback));
}
#[cfg(feature = "async")]
fn add_async_function<F, A, FR, R>(&mut self, name: impl ToString, function: F)
fn add_async_function<F, A, FR, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti,
FR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti,
{
let name = name.to_string();
let name = name.into();
let callback = self.box_async_function(&name, function);
self.raw.async_methods.push((name, callback));
}
fn add_meta_method<M, A, R>(&mut self, name: impl ToString, method: M)
fn add_meta_method<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = name.to_string();
let name = name.into();
let callback = self.box_method(&name, method);
self.raw.meta_methods.push((name, callback));
}
fn add_meta_method_mut<M, A, R>(&mut self, name: impl ToString, method: M)
fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = name.to_string();
let name = name.into();
let callback = self.box_method_mut(&name, method);
self.raw.meta_methods.push((name, callback));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl ToString, method: M)
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
@@ -552,13 +552,13 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti,
{
let name = name.to_string();
let name = name.into();
let callback = self.box_async_method(&name, method);
self.raw.async_meta_methods.push((name, callback));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl ToString, method: M)
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
@@ -566,42 +566,42 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti,
{
let name = name.to_string();
let name = name.into();
let callback = self.box_async_method_mut(&name, method);
self.raw.async_meta_methods.push((name, callback));
}
fn add_meta_function<F, A, R>(&mut self, name: impl ToString, function: F)
fn add_meta_function<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = name.to_string();
let name = name.into();
let callback = self.box_function(&name, function);
self.raw.meta_methods.push((name, callback));
}
fn add_meta_function_mut<F, A, R>(&mut self, name: impl ToString, function: F)
fn add_meta_function_mut<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = name.to_string();
let name = name.into();
let callback = self.box_function_mut(&name, function);
self.raw.meta_methods.push((name, callback));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl ToString, function: F)
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti,
FR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti,
{
let name = name.to_string();
let name = name.into();
let callback = self.box_async_function(&name, function);
self.raw.async_meta_methods.push((name, callback));
}
+5 -4
View File
@@ -354,7 +354,7 @@ unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()
end
"#;
protect_lua!(state, 0, 1, |state| {
let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), 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);
}
@@ -405,7 +405,8 @@ unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result
end
"#;
protect_lua!(state, 0, 1, |state| {
let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_newindex"));
let code_len = code.count_bytes();
let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code_len, cstr!("=__mlua_newindex"));
if ret != ffi::LUA_OK {
ffi::lua_error(state);
}
@@ -454,9 +455,9 @@ pub(crate) unsafe extern "C" fn collect_userdata<T>(
// It checks if the userdata is safe to destroy and sets the "destroyed" metatable
// to prevent further GC collection.
pub(super) unsafe extern "C-unwind" fn destroy_userdata_storage<T>(state: *mut ffi::lua_State) -> c_int {
let ud = get_userdata::<UserDataStorage<T>>(state, -1);
let ud = get_userdata::<UserDataStorage<T>>(state, 1);
if (*ud).is_safe_to_destroy() {
take_userdata::<UserDataStorage<T>>(state);
take_userdata::<UserDataStorage<T>>(state, 1);
ffi::lua_pushboolean(state, 1);
} else {
ffi::lua_pushboolean(state, 0);
+5 -1
View File
@@ -349,7 +349,11 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<(
state,
Some(|state| {
ffi::lua_pushcfunction(state, error_tostring);
rawset_field(state, -2, "__tostring")
ffi::lua_setfield(state, -2, cstr!("__tostring"));
// This is mostly for Luau typeof() function
ffi::lua_pushstring(state, cstr!("error"));
ffi::lua_setfield(state, -2, cstr!("__type"));
}),
)?;
+7 -9
View File
@@ -88,7 +88,7 @@ impl Drop for StackGuard {
#[inline(always)]
pub(crate) unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: bool) -> Result<()> {
// Always use protected mode if the string is too long
if protect || s.len() > (1 << 30) {
if protect || s.len() >= const { 1 << 30 } {
protect_lua!(state, 0, 1, |state| {
ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len());
})
@@ -101,15 +101,13 @@ pub(crate) unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect:
// Uses 3 stack spaces (when protect), does not call checkstack.
#[cfg(feature = "luau")]
#[inline(always)]
pub(crate) unsafe fn push_buffer(state: *mut ffi::lua_State, b: &[u8], protect: bool) -> Result<()> {
let data = if protect {
protect_lua!(state, 0, 1, |state| ffi::lua_newbuffer(state, b.len()))?
pub(crate) unsafe fn push_buffer(state: *mut ffi::lua_State, size: usize, protect: bool) -> Result<*mut u8> {
let data = if protect || size > const { 1024 * 1024 * 1024 } {
protect_lua!(state, 0, 1, |state| ffi::lua_newbuffer(state, size))?
} else {
ffi::lua_newbuffer(state, b.len())
ffi::lua_newbuffer(state, size)
};
let buf = slice::from_raw_parts_mut(data as *mut u8, b.len());
buf.copy_from_slice(b);
Ok(())
Ok(data as *mut u8)
}
// Uses 3 stack spaces, does not call checkstack.
@@ -122,7 +120,7 @@ pub(crate) unsafe fn push_table(
) -> Result<()> {
let narr: c_int = narr.try_into().unwrap_or(c_int::MAX);
let nrec: c_int = nrec.try_into().unwrap_or(c_int::MAX);
if protect {
if protect || narr >= const { 1 << 26 } || nrec >= const { 1 << 26 } {
protect_lua!(state, 0, 1, |state| ffi::lua_createtable(state, narr, nrec))
} else {
ffi::lua_createtable(state, narr, nrec);
+18 -8
View File
@@ -1,6 +1,6 @@
//! Mostly copied from [bevy_utils]
//! Inspired by bevy's [disqualified]
//!
//! [bevy_utils]: https://github.com/bevyengine/bevy/blob/main/crates/bevy_utils/src/short_names.rs
//! [disqualified]: https://github.com/bevyengine/disqualified/blob/main/src/short_name.rs
use std::any::type_name;
@@ -23,8 +23,7 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
while index < end_of_string {
let rest_of_string = full_name.get(index..end_of_string).unwrap_or_default();
// Collapse everything up to the next special character,
// then skip over it
// Collapse everything up to the next special character, then skip over it
if let Some(special_character_index) =
rest_of_string.find(|c: char| [' ', '<', '>', '(', ')', '[', ']', ',', ';'].contains(&c))
{
@@ -32,11 +31,16 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
parsed_name += collapse_type_name(segment_to_collapse);
// Insert the special character
let special_character = &rest_of_string[special_character_index..=special_character_index];
parsed_name.push_str(special_character);
parsed_name += special_character;
// Remove lifetimes like <'_> or <'_, '_, ...>
if parsed_name.ends_with("<'_>") || parsed_name.ends_with("<'_, ") {
_ = parsed_name.split_off(parsed_name.len() - 4);
}
match special_character {
">" | ")" | "]" if rest_of_string[special_character_index + 1..].starts_with("::") => {
parsed_name.push_str("::");
parsed_name += "::";
// Move the index past the "::"
index += special_character_index + 3;
}
@@ -53,14 +57,18 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
}
#[inline(always)]
fn collapse_type_name(string: &str) -> &str {
string.rsplit("::").next().unwrap()
fn collapse_type_name(segment: &str) -> &str {
segment.rsplit("::").next().unwrap()
}
#[cfg(test)]
mod tests {
use super::short_type_name;
use std::collections::HashMap;
use std::marker::PhantomData;
struct MyData<'a, 'b>(PhantomData<&'a &'b ()>);
struct MyDataT<'a, T>(PhantomData<&'a T>);
#[test]
fn tests() {
@@ -73,5 +81,7 @@ mod tests {
"HashMap<String, Option<[i32; 3]>>"
);
assert_eq!(short_type_name::<dyn Fn(i32) -> i32>(), "dyn Fn(i32) -> i32");
assert_eq!(short_type_name::<MyDataT<&str>>(), "MyDataT<&str>");
assert_eq!(short_type_name::<(&MyData, [MyData])>(), "(MyData, [MyData])");
}
}
+22 -19
View File
@@ -47,7 +47,7 @@ pub(crate) unsafe fn get_internal_metatable<T: TypeKey>(state: *mut ffi::lua_Sta
// Uses 6 stack spaces and calls checkstack.
pub(crate) unsafe fn init_internal_metatable<T: TypeKey>(
state: *mut ffi::lua_State,
customize_fn: Option<fn(*mut ffi::lua_State) -> Result<()>>,
customize_fn: Option<fn(*mut ffi::lua_State)>,
) -> Result<()> {
check_stack(state, 6)?;
@@ -62,18 +62,18 @@ pub(crate) unsafe fn init_internal_metatable<T: TypeKey>(
ffi::lua_pushboolean(state, 0);
rawset_field(state, -2, "__metatable")?;
if let Some(f) = customize_fn {
f(state)?;
}
protect_lua!(state, 1, 0, |state| {
if let Some(f) = customize_fn {
f(state);
}
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, T::type_key());
})?;
Ok(())
}
// Uses 2 stack spaces, does not call checkstack
// Uses up to 1 stack space, does not call `checkstack`
pub(crate) unsafe fn get_internal_userdata<T: TypeKey>(
state: *mut ffi::lua_State,
index: c_int,
@@ -141,24 +141,27 @@ pub(crate) unsafe fn get_userdata<T>(state: *mut ffi::lua_State, index: c_int) -
ud
}
// Pops the userdata off of the top of the stack and returns it to rust, invalidating the lua
// userdata and gives it the special "destructed" userdata metatable. Userdata must not have been
// previously invalidated, and this method does not check for this.
// Uses 1 extra stack space and does not call checkstack.
pub(crate) unsafe fn take_userdata<T>(state: *mut ffi::lua_State) -> T {
// We set the metatable of userdata on __gc to a special table with no __gc method and with
// metamethods that trigger an error on access. We do this so that it will not be double
// dropped, and also so that it cannot be used or identified as any particular userdata type
// after the first call to __gc.
/// Unwraps `T` from the Lua userdata and invalidating it by setting the special "destructed"
/// metatable.
///
/// This method does not check that userdata is of type `T` and was not previously invalidated.
///
/// Uses 1 extra stack space, does not call checkstack.
pub(crate) unsafe fn take_userdata<T>(state: *mut ffi::lua_State, idx: c_int) -> T {
#[rustfmt::skip]
let idx = if idx < 0 { ffi::lua_absindex(state, idx) } else { idx };
// Update the metatable of this userdata to a special one with no `__gc` method and with
// metamethods that trigger an error on access.
// We do this so that it will not be double dropped or used after being dropped.
get_destructed_userdata_metatable(state);
ffi::lua_setmetatable(state, -2);
let ud = get_userdata::<T>(state, -1);
ffi::lua_setmetatable(state, idx);
let ud = get_userdata::<T>(state, idx);
// Update userdata tag to disable destructor and mark as destructed
#[cfg(feature = "luau")]
ffi::lua_setuserdatatag(state, -1, 1);
ffi::lua_setuserdatatag(state, idx, 1);
ffi::lua_pop(state, 1);
ptr::read(ud)
}
+18 -10
View File
@@ -15,7 +15,7 @@ use crate::types::{Integer, LightUserData, Number, ValueRef};
use crate::userdata::AnyUserData;
use crate::util::{check_stack, StackGuard};
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
use {
crate::table::SerializableTable,
rustc_hash::FxHashSet,
@@ -356,14 +356,22 @@ impl Value {
///
/// If the value is a Lua [`String`], try to convert it to [`BorrowedStr`] or return `None`
/// otherwise.
#[deprecated(
since = "0.11.0",
note = "This method does not follow Rust naming convention. Use `as_string().and_then(|s| s.to_str().ok())` instead."
)]
#[inline]
pub fn as_str(&self) -> Option<BorrowedStr> {
pub fn as_str(&self) -> Option<BorrowedStr<'_>> {
self.as_string().and_then(|s| s.to_str().ok())
}
/// Cast the value to [`StdString`].
///
/// If the value is a Lua [`String`], converts it to [`StdString`] or returns `None` otherwise.
#[deprecated(
since = "0.11.0",
note = "This method does not follow Rust naming convention. Use `as_string().map(|s| s.to_string_lossy())` instead."
)]
#[inline]
pub fn as_string_lossy(&self) -> Option<StdString> {
self.as_string().map(|s| s.to_string_lossy())
@@ -481,10 +489,10 @@ impl Value {
/// Wrap reference to this Value into [`SerializableValue`].
///
/// This allows customizing serialization behavior using serde.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
#[doc(hidden)]
pub fn to_serializable(&self) -> SerializableValue {
pub fn to_serializable(&self) -> SerializableValue<'_> {
SerializableValue::new(self, Default::default(), None)
}
@@ -630,8 +638,8 @@ impl PartialEq for Value {
}
/// A wrapped [`Value`] with customized serialization behavior.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub struct SerializableValue<'a> {
value: &'a Value,
options: crate::serde::de::Options,
@@ -639,7 +647,7 @@ pub struct SerializableValue<'a> {
visited: Option<Rc<RefCell<FxHashSet<*const c_void>>>>,
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
impl Serialize for Value {
#[inline]
fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
@@ -647,7 +655,7 @@ impl Serialize for Value {
}
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
impl<'a> SerializableValue<'a> {
#[inline]
pub(crate) fn new(
@@ -711,7 +719,7 @@ impl<'a> SerializableValue<'a> {
}
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
impl Serialize for SerializableValue<'_> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
+2 -3
View File
@@ -1,6 +1,6 @@
use std::fmt;
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
use serde::ser::{Serialize, SerializeTupleStruct, Serializer};
/// A Luau vector type.
@@ -38,7 +38,6 @@ impl Vector {
}
/// Creates a new vector with all components set to `0.0`.
#[doc(hidden)]
pub const fn zero() -> Self {
Self([0.0; Self::SIZE])
}
@@ -66,7 +65,7 @@ impl Vector {
}
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
impl Serialize for Vector {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
let mut ts = serializer.serialize_tuple_struct("Vector", Self::SIZE)?;
+7 -7
View File
@@ -1,23 +1,23 @@
[lua54]
features = "lua54,vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
features = "lua54,vendored,async,send,serde,macros,anyhow,userdata-wrappers"
[lua54_non_send]
features = "lua54,vendored,async,serialize,macros,anyhow,userdata-wrappers"
features = "lua54,vendored,async,serde,macros,anyhow,userdata-wrappers"
[lua54_with_memory_limit]
features = "lua54,vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
features = "lua54,vendored,async,send,serde,macros,anyhow,userdata-wrappers"
rustflags = "--cfg force_memory_limit"
[lua51]
features = "lua51,vendored,async,send,serialize,macros"
features = "lua51,vendored,async,send,serde,macros"
[lua51_with_memory_limit]
features = "lua51,vendored,async,send,serialize,macros"
features = "lua51,vendored,async,send,serde,macros"
rustflags = "--cfg force_memory_limit"
[luau]
features = "luau,async,send,serialize,macros"
features = "luau,async,send,serde,macros"
[luau_with_memory_limit]
features = "luau,async,send,serialize,macros"
features = "luau,async,send,serde,macros"
rustflags = "--cfg force_memory_limit"
+77 -3
View File
@@ -8,8 +8,8 @@ use futures_util::stream::TryStreamExt;
use tokio::sync::Mutex;
use mlua::{
Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, UserData,
UserDataMethods, Value,
Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, ThreadStatus, UserData,
UserDataMethods, UserDataRef, Value,
};
#[cfg(not(target_arch = "wasm32"))]
@@ -386,7 +386,7 @@ async fn test_async_table_object_like() -> Result<()> {
table.get::<i64>("val")
})?,
)?;
table.set_metatable(Some(metatable));
table.set_metatable(Some(metatable))?;
assert_eq!(table.call_async::<i64>(()).await.unwrap(), 15);
match table.call_async_method::<()>("non_existent", ()).await {
@@ -547,6 +547,7 @@ async fn test_async_thread_error() -> Result<()> {
#[tokio::test]
async fn test_async_terminate() -> Result<()> {
// Future captures `Lua` instance and dropped all together
let mutex = Arc::new(Mutex::new(0u32));
{
let lua = Lua::new();
@@ -565,6 +566,17 @@ async fn test_async_terminate() -> Result<()> {
}
assert!(mutex.try_lock().is_ok());
// Future is dropped, but `Lua` instance is still alive
let lua = Lua::new();
let func = lua.create_async_function(move |_, mutex: UserDataRef<Arc<Mutex<u32>>>| async move {
let _guard = mutex.lock().await;
sleep_ms(100).await;
Ok(())
})?;
let mutex2 = lua.create_any_userdata(mutex.clone())?;
let _ = tokio::time::timeout(Duration::from_millis(30), func.call_async::<()>(mutex2)).await;
assert!(mutex.try_lock().is_ok());
Ok(())
}
@@ -598,6 +610,36 @@ async fn test_async_task() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn test_async_task_abort() -> Result<()> {
let lua = Lua::new();
let sleep = lua.create_async_function(move |_lua, n: u64| async move {
sleep_ms(n).await;
Ok(())
})?;
lua.globals().set("sleep", sleep)?;
let local = tokio::task::LocalSet::new();
local
.run_until(async {
let lua2 = lua.clone();
let jh = tokio::task::spawn_local(async move {
lua2.load("sleep(200) result = 'done'")
.exec_async()
.await
.unwrap();
});
sleep_ms(100).await; // Wait for the task to start
jh.abort();
})
.await;
local.await;
assert_eq!(lua.globals().get::<Value>("result")?, Value::Nil);
Ok(())
}
#[tokio::test]
#[cfg(not(feature = "luau"))]
async fn test_async_hook() -> Result<()> {
@@ -625,3 +667,35 @@ async fn test_async_hook() -> Result<()> {
Ok(())
}
#[test]
fn test_async_yield_with() -> Result<()> {
let lua = Lua::new();
let func = lua.create_async_function(|lua, (mut a, mut b): (i32, i32)| async move {
let zero = lua.yield_with::<MultiValue>(()).await?;
assert!(zero.is_empty());
let one = lua.yield_with::<MultiValue>(a + b).await?;
assert_eq!(one.len(), 1);
for _ in 0..3 {
(a, b) = lua.yield_with((a + b, a * b)).await?;
}
Ok((0, 0))
})?;
let thread = lua.create_thread(func)?;
let zero = thread.resume::<MultiValue>((2, 3))?; // function arguments
assert!(zero.is_empty());
let one = thread.resume::<i32>(())?; // value of "zero" is passed here
assert_eq!(one, 5);
assert_eq!(thread.resume::<(i32, i32)>(1)?, (5, 6)); // value of "one" is passed here
assert_eq!(thread.resume::<(i32, i32)>((10, 11))?, (21, 110));
assert_eq!(thread.resume::<(i32, i32)>((11, 12))?, (23, 132));
assert_eq!(thread.resume::<(i32, i32)>((12, 13))?, (0, 0));
assert_eq!(thread.status(), ThreadStatus::Finished);
Ok(())
}
+68 -2
View File
@@ -1,5 +1,7 @@
#![cfg(feature = "luau")]
use std::io::{Read, Seek, SeekFrom, Write};
use mlua::{Lua, Result, Value};
#[test]
@@ -41,7 +43,7 @@ fn test_buffer() -> Result<()> {
}
#[test]
#[should_panic(expected = "range end index 14 out of range for slice of length 13")]
#[should_panic(expected = "out of range for slice of length 13")]
fn test_buffer_out_of_bounds_read() {
let lua = Lua::new();
let buf = lua.create_buffer(b"hello, world!").unwrap();
@@ -49,9 +51,73 @@ fn test_buffer_out_of_bounds_read() {
}
#[test]
#[should_panic(expected = "range end index 16 out of range for slice of length 13")]
#[should_panic(expected = "out of range for slice of length 13")]
fn test_buffer_out_of_bounds_write() {
let lua = Lua::new();
let buf = lua.create_buffer(b"hello, world!").unwrap();
buf.write_bytes(14, b"!!");
}
#[test]
fn create_large_buffer() {
let lua = Lua::new();
let err = lua.create_buffer_with_capacity(1_073_741_824 + 1).unwrap_err(); // 1GB
assert!(err.to_string().contains("memory allocation error"));
// Normal buffer is okay
let buf = lua.create_buffer_with_capacity(1024 * 1024).unwrap();
assert_eq!(buf.len(), 1024 * 1024);
}
#[test]
fn test_buffer_cursor() -> Result<()> {
let lua = Lua::new();
let mut cursor = lua.create_buffer(b"hello, world")?.cursor();
let mut data = Vec::new();
cursor.read_to_end(&mut data)?;
assert_eq!(data, b"hello, world");
// No more data to read
let mut one = [0u8; 1];
assert_eq!(cursor.read(&mut one)?, 0);
// Seek to start
cursor.seek(SeekFrom::Start(0))?;
cursor.read_exact(&mut one)?;
assert_eq!(one, [b'h']);
// Seek to end -5
cursor.seek(SeekFrom::End(-5))?;
let mut five = [0u8; 5];
cursor.read_exact(&mut five)?;
assert_eq!(&five, b"world");
// Seek to current -1
cursor.seek(SeekFrom::Current(-1))?;
cursor.read_exact(&mut one)?;
assert_eq!(one, [b'd']);
// Invalid seek
assert!(cursor.seek(SeekFrom::Current(-100)).is_err());
assert!(cursor.seek(SeekFrom::End(1)).is_err());
// Write data
let buf = lua.create_buffer_with_capacity(100)?;
cursor = buf.clone().cursor();
cursor.write_all(b"hello, ...")?;
cursor.seek(SeekFrom::Current(-3))?;
cursor.write_all(b"Rust!")?;
assert_eq!(&buf.read_bytes::<12>(0), b"hello, Rust!");
// Writing beyond the end of the buffer does nothing
cursor.seek(SeekFrom::End(0))?;
assert_eq!(cursor.write(b".")?, 0);
// Flush is no-op
cursor.flush()?;
Ok(())
}
+9 -12
View File
@@ -119,12 +119,11 @@ fn test_compiler() -> Result<()> {
.set_debug_level(2)
.set_type_info_level(1)
.set_coverage_level(2)
.set_vector_lib("vector")
.set_vector_ctor("new")
.set_vector_ctor("vector.new")
.set_vector_type("vector")
.set_mutable_globals(vec!["mutable_global"])
.set_userdata_types(vec!["MyUserdata"])
.set_disabled_builtins(vec!["tostring"]);
.set_mutable_globals(["mutable_global"])
.set_userdata_types(["MyUserdata"])
.set_disabled_builtins(["tostring"]);
assert!(compiler.compile("return tostring(vector.new(1, 2, 3))").is_ok());
@@ -142,16 +141,14 @@ fn test_compiler() -> Result<()> {
#[cfg(feature = "luau")]
#[test]
fn test_compiler_library_constants() {
use mlua::{CompileConstant, Compiler, Vector};
use mlua::{Compiler, Vector};
let compiler = Compiler::new()
.set_optimization_level(2)
.set_library_constants(vec![
("mylib", "const_bool", CompileConstant::Boolean(true)),
("mylib", "const_num", CompileConstant::Number(123.0)),
("mylib", "const_vec", CompileConstant::Vector(Vector::zero())),
("mylib", "const_str", "value1".into()),
]);
.add_library_constant("mylib.const_bool", true)
.add_library_constant("mylib.const_num", 123.0)
.add_library_constant("mylib.const_vec", Vector::zero())
.add_library_constant("mylib.const_str", "value1");
let lua = Lua::new();
lua.set_compiler(compiler);
+29 -51
View File
@@ -1,32 +1,28 @@
error[E0277]: the type `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
error[E0277]: the type `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> tests/compile/lua_norefunwindsafe.rs:7:18
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| |
| required by a bound introduced by this call
|
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::raw::RawLua>`, which is required by `{closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:20}: UnwindSafe`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-0.4.12/src/remutex.rs
= help: within `mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<*mut lua_State>`
note: required because it appears within the type `Cell<*mut lua_State>`
--> $RUST/core/src/cell.rs
|
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
| pub struct Cell<T: ?Sized> {
| ^^^^
note: required because it appears within the type `mlua::state::raw::RawLua`
--> src/state/raw.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>`
--> $RUST/core/src/marker.rs
| pub struct RawLua {
| ^^^^^^
note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>`
--> src/types/sync.rs
|
| pub struct PhantomData<T: ?Sized>;
| ^^^^^^^^^^^
note: required because it appears within the type `Arc<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Arc<
| ^^^
| pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^^^^^^^
= note: required for `Rc<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>` to implement `RefUnwindSafe`
note: required because it appears within the type `Lua`
--> src/state.rs
|
@@ -44,45 +40,27 @@ note: required by a bound in `std::panic::catch_unwind`
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ^^^^^^^^^^ required by this bound in `catch_unwind`
error[E0277]: the type `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
error[E0277]: the type `UnsafeCell<mlua::state::extra::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> tests/compile/lua_norefunwindsafe.rs:7:18
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::extra::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| |
| required by a bound introduced by this call
|
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<usize>`, which is required by `{closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:20}: UnwindSafe`
note: required because it appears within the type `Cell<usize>`
--> $RUST/core/src/cell.rs
= help: the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::extra::ExtraData>`
= note: required for `Rc<UnsafeCell<mlua::state::extra::ExtraData>>` to implement `RefUnwindSafe`
note: required because it appears within the type `mlua::state::raw::RawLua`
--> src/state/raw.rs
|
| pub struct Cell<T: ?Sized> {
| ^^^^
note: required because it appears within the type `lock_api::remutex::RawReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId>`
--> $CARGO/lock_api-0.4.12/src/remutex.rs
| pub struct RawLua {
| ^^^^^^
note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>`
--> src/types/sync.rs
|
| pub struct RawReentrantMutex<R, G> {
| ^^^^^^^^^^^^^^^^^
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-0.4.12/src/remutex.rs
|
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>`
--> $RUST/core/src/marker.rs
|
| pub struct PhantomData<T: ?Sized>;
| ^^^^^^^^^^^
note: required because it appears within the type `Arc<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Arc<
| ^^^
| pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^^^^^^^
= note: required for `Rc<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>` to implement `RefUnwindSafe`
note: required because it appears within the type `Lua`
--> src/state.rs
|
+1 -1
View File
@@ -8,7 +8,7 @@ error[E0277]: `Rc<Cell<i32>>` cannot be sent between threads safely
| | within this `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}`
| required by a bound introduced by this call
|
= help: within `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}`, the trait `Send` is not implemented for `Rc<Cell<i32>>`, which is required by `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}: MaybeSend`
= help: within `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}`, the trait `Send` is not implemented for `Rc<Cell<i32>>`
note: required because it's used within this closure
--> tests/compile/non_send.rs:11:25
|
+104 -47
View File
@@ -1,38 +1,38 @@
error[E0277]: the type `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
error[E0277]: the type `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> tests/compile/ref_nounwindsafe.rs:8:18
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| |
| required by a bound introduced by this call
|
= help: within `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::raw::RawLua>`, which is required by `{closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:25}: UnwindSafe`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-0.4.12/src/remutex.rs
= help: within `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<usize>`
note: required because it appears within the type `Cell<usize>`
--> $RUST/core/src/cell.rs
|
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
| pub struct Cell<T: ?Sized> {
| ^^^^
note: required because it appears within the type `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
= note: required for `NonNull<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::sync::Weak<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
| struct RcInner<T: ?Sized> {
| ^^^^^^^
= note: required for `NonNull<rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::rc::Weak<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| pub struct Weak<
| ^^^^
note: required because it appears within the type `mlua::state::WeakLua`
note: required because it appears within the type `WeakLua`
--> src/state.rs
|
| pub(crate) struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
| ^^^^^^^
note: required because it appears within the type `mlua::types::ValueRef`
--> src/types.rs
| pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
| ^^^^^^^
note: required because it appears within the type `mlua::types::value_ref::ValueRef`
--> src/types/value_ref.rs
|
| pub(crate) struct ValueRef {
| ^^^^^^^^
| pub struct ValueRef {
| ^^^^^^^^
note: required because it appears within the type `LuaTable`
--> src/table.rs
|
@@ -49,51 +49,108 @@ note: required by a bound in `std::panic::catch_unwind`
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ^^^^^^^^^^ required by this bound in `catch_unwind`
error[E0277]: the type `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
error[E0277]: the type `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> tests/compile/ref_nounwindsafe.rs:8:18
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| |
| required by a bound introduced by this call
|
= help: within `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<usize>`, which is required by `{closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:25}: UnwindSafe`
note: required because it appears within the type `Cell<usize>`
= help: within `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<*mut lua_State>`
note: required because it appears within the type `Cell<*mut lua_State>`
--> $RUST/core/src/cell.rs
|
| pub struct Cell<T: ?Sized> {
| ^^^^
note: required because it appears within the type `lock_api::remutex::RawReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId>`
--> $CARGO/lock_api-0.4.12/src/remutex.rs
note: required because it appears within the type `mlua::state::raw::RawLua`
--> src/state/raw.rs
|
| pub struct RawReentrantMutex<R, G> {
| ^^^^^^^^^^^^^^^^^
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-0.4.12/src/remutex.rs
| pub struct RawLua {
| ^^^^^^
note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>`
--> src/types/sync.rs
|
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
| pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^^^^^^^
note: required because it appears within the type `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
= note: required for `NonNull<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::sync::Weak<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
| struct RcInner<T: ?Sized> {
| ^^^^^^^
= note: required for `NonNull<rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::rc::Weak<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| pub struct Weak<
| ^^^^
note: required because it appears within the type `mlua::state::WeakLua`
note: required because it appears within the type `WeakLua`
--> src/state.rs
|
| pub(crate) struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
| ^^^^^^^
note: required because it appears within the type `mlua::types::ValueRef`
--> src/types.rs
| pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
| ^^^^^^^
note: required because it appears within the type `mlua::types::value_ref::ValueRef`
--> src/types/value_ref.rs
|
| pub(crate) struct ValueRef {
| ^^^^^^^^
| pub struct ValueRef {
| ^^^^^^^^
note: required because it appears within the type `LuaTable`
--> src/table.rs
|
| pub struct Table(pub(crate) ValueRef);
| ^^^^^
note: required because it's used within this closure
--> tests/compile/ref_nounwindsafe.rs:8:18
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ^^^^^^^
note: required by a bound in `std::panic::catch_unwind`
--> $RUST/std/src/panic.rs
|
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ^^^^^^^^^^ required by this bound in `catch_unwind`
error[E0277]: the type `UnsafeCell<mlua::state::extra::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> tests/compile/ref_nounwindsafe.rs:8:18
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::extra::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| |
| required by a bound introduced by this call
|
= help: the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::extra::ExtraData>`
= note: required for `Rc<UnsafeCell<mlua::state::extra::ExtraData>>` to implement `RefUnwindSafe`
note: required because it appears within the type `mlua::state::raw::RawLua`
--> src/state/raw.rs
|
| pub struct RawLua {
| ^^^^^^
note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>`
--> src/types/sync.rs
|
| pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^^^^^^^
note: required because it appears within the type `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| struct RcInner<T: ?Sized> {
| ^^^^^^^
= note: required for `NonNull<rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::rc::Weak<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| pub struct Weak<
| ^^^^
note: required because it appears within the type `WeakLua`
--> src/state.rs
|
| pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
| ^^^^^^^
note: required because it appears within the type `mlua::types::value_ref::ValueRef`
--> src/types/value_ref.rs
|
| pub struct ValueRef {
| ^^^^^^^^
note: required because it appears within the type `LuaTable`
--> src/table.rs
|
+1 -1
View File
@@ -2,7 +2,7 @@ error[E0373]: closure may outlive the current function, but it borrows `inner`,
--> tests/compile/scope_callback_capture.rs:7:43
|
5 | lua.scope(|scope| {
| ----- has type `&'1 mut mlua::scope::Scope<'1, '_>`
| ----- has type `&'1 mlua::Scope<'1, '_>`
6 | let mut inner: Option<Table> = None;
7 | let f = scope.create_function_mut(|_, t: Table| {
| ^^^^^^^^^^^^^ may outlive borrowed value `inner`
+1 -1
View File
@@ -2,7 +2,7 @@ error[E0373]: closure may outlive the current function, but it borrows `test.fie
--> tests/compile/scope_invariance.rs:13:39
|
9 | lua.scope(|scope| {
| ----- has type `&'1 mut mlua::scope::Scope<'1, '_>`
| ----- has type `&'1 mlua::Scope<'1, '_>`
...
13 | scope.create_function_mut(|_, ()| {
| ^^^^^^^ may outlive borrowed value `test.field`
+1 -1
View File
@@ -2,7 +2,7 @@ error[E0499]: cannot borrow `i` as mutable more than once at a time
--> tests/compile/scope_mutable_aliasing.rs:12:51
|
10 | lua.scope(|scope| {
| ----- has type `&mut mlua::scope::Scope<'_, '1>`
| ----- has type `&mlua::Scope<'_, '1>`
11 | let _a = scope.create_userdata(MyUserData(&mut i)).unwrap();
| -----------------------------------------
| | |
+1 -1
View File
@@ -2,7 +2,7 @@ error[E0597]: `ibad` does not live long enough
--> tests/compile/scope_userdata_borrow.rs:15:46
|
11 | lua.scope(|scope| {
| ----- has type `&mut mlua::scope::Scope<'_, '1>`
| ----- has type `&mlua::Scope<'_, '1>`
...
14 | let ibad = 42;
| ---- binding `ibad` declared here
+6 -6
View File
@@ -267,7 +267,7 @@ fn test_registry_value_into_lua() -> Result<()> {
let r = lua.create_registry_value(&s)?;
let value1 = lua.pack(&r)?;
let value2 = lua.pack(r)?;
assert_eq!(value1.as_str().as_deref(), Some("hello, world"));
assert_eq!(value1.to_string()?, "hello, world");
assert_eq!(value1.to_pointer(), value2.to_pointer());
// Push into stack
@@ -560,11 +560,11 @@ fn test_osstring_into_from_lua() -> Result<()> {
let v = lua.pack(s.as_os_str())?;
assert!(v.is_string());
assert_eq!(v.as_str().unwrap(), "hello, world");
assert_eq!(v.as_string().unwrap(), "hello, world");
let v = lua.pack(s)?;
assert!(v.is_string());
assert_eq!(v.as_str().unwrap(), "hello, world");
assert_eq!(v.as_string().unwrap(), "hello, world");
let s = lua.create_string("hello, world")?;
let bstr = lua.unpack::<OsString>(Value::String(s))?;
@@ -588,11 +588,11 @@ fn test_pathbuf_into_from_lua() -> Result<()> {
let v = lua.pack(pb.as_path())?;
assert!(v.is_string());
assert_eq!(v.as_str().unwrap(), pb_str);
assert_eq!(v.to_string().unwrap(), pb_str);
let v = lua.pack(pb.clone())?;
assert!(v.is_string());
assert_eq!(v.as_str().unwrap(), pb_str);
assert_eq!(v.to_string().unwrap(), pb_str);
let s = lua.create_string(pb_str)?;
let bstr = lua.unpack::<PathBuf>(Value::String(s))?;
@@ -724,7 +724,7 @@ fn test_char_into_lua() -> Result<()> {
let v = '🦀';
let v2 = v.into_lua(&lua)?;
assert_eq!(Some(v.to_string()), v2.as_string_lossy());
assert_eq!(*v2.as_string().unwrap(), v.to_string());
Ok(())
}
+2 -2
View File
@@ -306,7 +306,7 @@ fn test_function_deep_clone() -> Result<()> {
lua.globals().set("a", 1)?;
let func1 = lua.load("a += 1; return a").into_function()?;
let func2 = func1.deep_clone();
let func2 = func1.deep_clone()?;
assert_ne!(func1.to_pointer(), func2.to_pointer());
assert_eq!(func1.call::<i32>(())?, 2);
@@ -314,7 +314,7 @@ fn test_function_deep_clone() -> Result<()> {
// Check that for Rust functions deep_clone is just a clone
let rust_func = lua.create_function(|_, ()| Ok(42))?;
let rust_func2 = rust_func.deep_clone();
let rust_func2 = rust_func.deep_clone()?;
assert_eq!(rust_func.to_pointer(), rust_func2.to_pointer());
Ok(())
+2 -2
View File
@@ -24,7 +24,7 @@ fn test_line_counts() -> Result<()> {
let lua = Lua::new();
lua.set_hook(HookTriggers::EVERY_LINE, move |_lua, debug| {
assert_eq!(debug.event(), DebugEvent::Line);
hook_output.lock().unwrap().push(debug.curr_line());
hook_output.lock().unwrap().push(debug.current_line().unwrap());
Ok(VmState::Continue)
})?;
lua.load(
@@ -240,7 +240,7 @@ fn test_hook_threads() -> Result<()> {
let hook_output = output.clone();
co.set_hook(HookTriggers::EVERY_LINE, move |_lua, debug| {
assert_eq!(debug.event(), DebugEvent::Line);
hook_output.lock().unwrap().push(debug.curr_line());
hook_output.lock().unwrap().push(debug.current_line().unwrap());
Ok(VmState::Continue)
})?;
+62 -7
View File
@@ -3,11 +3,12 @@
use std::cell::Cell;
use std::fmt::Debug;
use std::os::raw::c_void;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};
use std::sync::Arc;
use mlua::{Compiler, Error, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, Vector, VmState};
use mlua::{
Compiler, Error, Function, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, Vector, VmState,
};
#[test]
fn test_version() -> Result<()> {
@@ -118,11 +119,13 @@ fn test_vector_metatable() -> Result<()> {
"#,
)
.eval::<Table>()?;
vector_mt.set_metatable(Some(vector_mt.clone()));
vector_mt.set_metatable(Some(vector_mt.clone()))?;
lua.set_type_metatable::<Vector>(Some(vector_mt.clone()));
lua.globals().set("Vector3", vector_mt)?;
let compiler = Compiler::new().set_vector_lib("Vector3").set_vector_ctor("new");
let compiler = Compiler::new()
.set_vector_ctor("Vector3.new")
.set_vector_type("Vector3");
// Test vector methods (fastcall)
lua.load(
@@ -165,9 +168,9 @@ fn test_readonly_table() -> Result<()> {
check_readonly_error(t.raw_pop::<Value>());
// Special case
match catch_unwind(AssertUnwindSafe(|| t.set_metatable(None))) {
Ok(_) => panic!("expected panic, got nothing"),
Err(_) => {}
match t.set_metatable(None) {
Err(Error::RuntimeError(e)) if e.contains("attempt to modify a readonly table") => {}
r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"),
}
Ok(())
@@ -194,6 +197,14 @@ fn test_sandbox() -> Result<()> {
co.sandbox()?;
assert_eq!(co.resume::<Option<i32>>(())?, Some(123));
// collectgarbage should be restricted in sandboxed mode
let collectgarbage = lua.globals().get::<Function>("collectgarbage")?;
for arg in ["collect", "stop", "restart", "step", "isrunning"] {
let err = collectgarbage.call::<()>(arg).err().unwrap().to_string();
assert!(err.contains("collectgarbage called with invalid option"));
}
assert!(collectgarbage.call::<u64>("count").unwrap() > 0);
lua.sandbox(false)?;
// Previously set variable `global` should be cleared now
@@ -203,6 +214,11 @@ fn test_sandbox() -> Result<()> {
let table = lua.globals().get::<Table>("table")?;
table.set("test", "test")?;
// collectgarbage should work now
for arg in ["collect", "stop", "restart", "count", "step", "isrunning"] {
collectgarbage.call::<()>(arg).unwrap();
}
Ok(())
}
@@ -314,6 +330,15 @@ fn test_interrupts() -> Result<()> {
assert_eq!(yield_count.load(Ordering::Relaxed), 7);
assert_eq!(co.status(), ThreadStatus::Finished);
// Test no yielding at non-yieldable points
yield_count.store(0, Ordering::Relaxed);
let co = lua.create_thread(lua.create_function(|lua, arg: Value| {
(lua.load("return (function(x) return x end)(...)")).call::<Value>(arg)
})?)?;
let res = co.resume::<String>("abc")?;
assert_eq!(res, "abc".to_string());
assert_eq!(yield_count.load(Ordering::Relaxed), 3);
//
// Test errors in interrupts
//
@@ -415,5 +440,35 @@ fn test_thread_events() -> Result<()> {
Ok(())
}
#[test]
fn test_loadstring() -> Result<()> {
let lua = Lua::new();
let f = lua.load(r#"loadstring("return 123")"#).eval::<Function>()?;
assert_eq!(f.call::<i32>(())?, 123);
let err = lua
.load(r#"loadstring("retur 123", "chunk")"#)
.exec()
.err()
.unwrap();
assert!(err.to_string().contains(
r#"syntax error: [string "chunk"]:1: Incomplete statement: expected assignment or a function call"#
));
Ok(())
}
#[test]
fn test_typeof_error() -> Result<()> {
let lua = Lua::new();
let err = Error::runtime("just a test error");
let res = lua.load("return typeof(...)").call::<String>(err)?;
assert_eq!(res, "error");
Ok(())
}
#[path = "luau/require.rs"]
mod require;
+122 -17
View File
@@ -1,9 +1,16 @@
use mlua::{IntoLua, Lua, Result, Value};
use std::io::Result as IoResult;
use std::result::Result as StdResult;
use mlua::{Error, IntoLua, Lua, MultiValue, NavigateError, Require, Result, TextRequirer, Value};
fn run_require(lua: &Lua, path: impl IntoLua) -> Result<Value> {
lua.load(r#"return require(...)"#).call(path)
}
fn run_require_pcall(lua: &Lua, path: impl IntoLua) -> Result<MultiValue> {
lua.load(r#"return pcall(require, ...)"#).call(path)
}
#[track_caller]
fn get_str(value: &Value, key: impl IntoLua) -> String {
value.as_table().unwrap().get::<String>(key).unwrap()
@@ -32,6 +39,65 @@ fn test_require_errors() {
assert!(res.is_err());
assert!((res.unwrap_err().to_string())
.contains("bad argument #1 to 'require' (string expected, got boolean)"));
// Require from loadstring
let res = lua
.load(r#"return loadstring("require('./a/relative/path')")()"#)
.eval::<Value>();
assert!(res.is_err());
assert!((res.unwrap_err().to_string()).contains("require is not supported in this context"));
// Test throwing mlua::Error
struct MyRequire(TextRequirer);
impl Require for MyRequire {
fn is_require_allowed(&self, chunk_name: &str) -> bool {
self.0.is_require_allowed(chunk_name)
}
fn reset(&mut self, _chunk_name: &str) -> StdResult<(), NavigateError> {
Err(Error::runtime("test error"))?
}
fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> {
self.0.jump_to_alias(path)
}
fn to_parent(&mut self) -> StdResult<(), NavigateError> {
self.0.to_parent()
}
fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> {
self.0.to_child(name)
}
fn has_module(&self) -> bool {
self.0.has_module()
}
fn cache_key(&self) -> String {
self.0.cache_key()
}
fn has_config(&self) -> bool {
self.0.has_config()
}
fn config(&self) -> IoResult<Vec<u8>> {
self.0.config()
}
fn loader(&self, lua: &Lua) -> Result<mlua::Function> {
self.0.loader(lua)
}
}
let require = lua
.create_require_function(MyRequire(TextRequirer::new()))
.unwrap();
lua.globals().set("require", require).unwrap();
let res = lua.load(r#"return require('./a/relative/path')"#).exec();
assert!((res.unwrap_err().to_string()).contains("test error"));
}
#[test]
@@ -39,44 +105,69 @@ fn test_require_without_config() {
let lua = Lua::new();
// RequireSimpleRelativePath
let res = run_require(&lua, "./require/without_config/dependency").unwrap();
let res = run_require(&lua, "./tests/luau/require/without_config/dependency").unwrap();
assert_eq!("result from dependency", get_str(&res, 1));
// RequireSimpleRelativePathWithinPcall
let res = run_require_pcall(&lua, "./tests/luau/require/without_config/dependency").unwrap();
assert!(res[0].as_boolean().unwrap());
assert_eq!("result from dependency", get_str(&res[1], 1));
// RequireRelativeToRequiringFile
let res = run_require(&lua, "./require/without_config/module").unwrap();
let res = run_require(&lua, "./tests/luau/require/without_config/module").unwrap();
assert_eq!("result from dependency", get_str(&res, 1));
assert_eq!("required into module", get_str(&res, 2));
// RequireLua
let res = run_require(&lua, "./require/without_config/lua_dependency").unwrap();
let res = run_require(&lua, "./tests/luau/require/without_config/lua_dependency").unwrap();
assert_eq!("result from lua_dependency", get_str(&res, 1));
// RequireInitLuau
let res = run_require(&lua, "./require/without_config/luau").unwrap();
let res = run_require(&lua, "./tests/luau/require/without_config/luau").unwrap();
assert_eq!("result from init.luau", get_str(&res, 1));
// RequireInitLua
let res = run_require(&lua, "./require/without_config/lua").unwrap();
let res = run_require(&lua, "./tests/luau/require/without_config/lua").unwrap();
assert_eq!("result from init.lua", get_str(&res, 1));
// RequireSubmoduleUsingSelf
let res = run_require(&lua, "./require/without_config/nested_module_requirer").unwrap();
// RequireSubmoduleUsingSelfIndirectly
let res = run_require(&lua, "./tests/luau/require/without_config/nested_module_requirer").unwrap();
assert_eq!("result from submodule", get_str(&res, 1));
// RequireSubmoduleUsingSelfDirectly
let res = run_require(&lua, "./tests/luau/require/without_config/nested").unwrap();
assert_eq!("result from submodule", get_str(&res, 1));
// CannotRequireInitLuauDirectly
let res = run_require(&lua, "./tests/luau/require/without_config/nested/init");
assert!(res.is_err());
assert!((res.unwrap_err().to_string()).contains("could not resolve child component \"init\""));
// RequireNestedInits
let res = run_require(&lua, "./tests/luau/require/without_config/nested_inits_requirer").unwrap();
assert_eq!("result from nested_inits/init", get_str(&res, 1));
assert_eq!("required into module", get_str(&res, 2));
// RequireWithFileAmbiguity
let res = run_require(&lua, "./require/without_config/ambiguous_file_requirer");
let res = run_require(
&lua,
"./tests/luau/require/without_config/ambiguous_file_requirer",
);
assert!(res.is_err());
assert!((res.unwrap_err().to_string())
.contains("could not resolve child component \"dependency\" (ambiguous)"));
// RequireWithDirectoryAmbiguity
let res = run_require(&lua, "./require/without_config/ambiguous_directory_requirer");
let res = run_require(
&lua,
"./tests/luau/require/without_config/ambiguous_directory_requirer",
);
assert!(res.is_err());
assert!((res.unwrap_err().to_string())
.contains("could not resolve child component \"dependency\" (ambiguous)"));
// CheckCachedResult
let res = run_require(&lua, "./require/without_config/validate_cache").unwrap();
let res = run_require(&lua, "./tests/luau/require/without_config/validate_cache").unwrap();
assert!(res.is_table());
}
@@ -85,15 +176,24 @@ fn test_require_with_config() {
let lua = Lua::new();
// RequirePathWithAlias
let res = run_require(&lua, "./require/with_config/src/alias_requirer").unwrap();
let res = run_require(&lua, "./tests/luau/require/with_config/src/alias_requirer").unwrap();
assert_eq!("result from dependency", get_str(&res, 1));
// RequirePathWithAlias (case-insensitive)
let res2 = run_require(&lua, "./tests/luau/require/with_config/src/alias_requirer_uc").unwrap();
assert_eq!("result from dependency", get_str(&res2, 1));
assert_eq!(res.to_pointer(), res2.to_pointer());
// RequirePathWithParentAlias
let res = run_require(&lua, "./require/with_config/src/parent_alias_requirer").unwrap();
let res = run_require(&lua, "./tests/luau/require/with_config/src/parent_alias_requirer").unwrap();
assert_eq!("result from other_dependency", get_str(&res, 1));
// RequirePathWithAliasPointingToDirectory
let res = run_require(&lua, "./require/with_config/src/directory_alias_requirer").unwrap();
let res = run_require(
&lua,
"./tests/luau/require/with_config/src/directory_alias_requirer",
)
.unwrap();
assert_eq!("result from subdirectory_dependency", get_str(&res, 1));
// RequireAliasThatDoesNotExist
@@ -107,7 +207,7 @@ fn test_require_with_config() {
assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias"));
}
#[cfg(feature = "async")]
#[cfg(all(feature = "async", not(windows)))]
#[tokio::test]
async fn test_async_require() -> Result<()> {
let lua = Lua::new();
@@ -130,14 +230,19 @@ async fn test_async_require() -> Result<()> {
Ok(())
})?,
)?;
lua.globals().set("tmp_dir", temp_dir.path().to_str().unwrap())?;
lua.globals().set(
"curr_dir_components",
std::env::current_dir().unwrap().components().count(),
)?;
lua.load(
r#"
local result = require("./async_chunk")
local path_to_root = string.rep("/..", curr_dir_components - 1)
local result = require(`.{path_to_root}{tmp_dir}/async_chunk`)
assert(result == "result_after_async_sleep")
"#,
)
.set_name(format!("@{}", temp_dir.path().join("require.rs").display()))
.exec_async()
.await
}
@@ -0,0 +1 @@
return require("@DeP")
@@ -0,0 +1,2 @@
local result = require("@self/init")
return result
@@ -0,0 +1 @@
return {"result from nested_inits/init"}
@@ -0,0 +1,3 @@
local result = require("./nested_inits")
result[#result+1] = "required into module"
return result
+2 -2
View File
@@ -43,12 +43,12 @@ fn test_result_conversions() -> Result<()> {
let multi_err1 = err1.into_lua_multi(&lua)?;
assert_eq!(multi_err1.len(), 2);
assert_eq!(multi_err1[0], Value::Nil);
assert_eq!(multi_err1[1].as_str().unwrap(), "failure1");
assert_eq!(multi_err1[1].as_string().unwrap(), "failure1");
let ok2 = Ok::<_, Error>("!");
let multi_ok2 = ok2.into_lua_multi(&lua)?;
assert_eq!(multi_ok2.len(), 1);
assert_eq!(multi_ok2[0].as_str().unwrap(), "!");
assert_eq!(multi_ok2[0].as_string().unwrap(), "!");
let err2 = Err::<String, _>("failure2".into_lua_err());
let multi_err2 = err2.into_lua_multi(&lua)?;
assert_eq!(multi_err2.len(), 2);
+4 -2
View File
@@ -382,7 +382,8 @@ fn test_scope_userdata_ref() -> Result<()> {
modify_userdata(&lua, &ud)?;
// We can only borrow userdata scoped
assert!((matches!(ud.borrow::<MyUserData>(), Err(Error::UserDataTypeMismatch))));
#[rustfmt::skip]
assert!(matches!(ud.borrow::<MyUserData>(), Err(Error::UserDataTypeMismatch)));
ud.borrow_scoped::<MyUserData, ()>(|ud_inst| {
assert_eq!(ud_inst.0.get(), 2);
})?;
@@ -419,7 +420,8 @@ fn test_scope_userdata_ref_mut() -> Result<()> {
let ud = scope.create_userdata_ref_mut(&mut data)?;
modify_userdata(&lua, &ud)?;
assert!((matches!(ud.borrow_mut::<MyUserData>(), Err(Error::UserDataTypeMismatch))));
#[rustfmt::skip]
assert!(matches!(ud.borrow_mut::<MyUserData>(), Err(Error::UserDataTypeMismatch)));
ud.borrow_mut_scoped::<MyUserData, ()>(|ud_inst| {
ud_inst.0 += 10;
})?;
+3 -3
View File
@@ -1,4 +1,4 @@
#![cfg(feature = "serialize")]
#![cfg(feature = "serde")]
use std::collections::HashMap;
use std::error::Error as StdError;
@@ -25,7 +25,7 @@ fn test_serialize() -> Result<(), Box<dyn StdError>> {
globals.set("null", lua.null())?;
let empty_array = lua.create_table()?;
empty_array.set_metatable(Some(lua.array_metatable()));
empty_array.set_metatable(Some(lua.array_metatable()))?;
globals.set("empty_array", empty_array)?;
let val = lua
@@ -173,7 +173,7 @@ fn test_serialize_sorted() -> LuaResult<()> {
globals.set("null", lua.null())?;
let empty_array = lua.create_table()?;
empty_array.set_metatable(Some(lua.array_metatable()));
empty_array.set_metatable(Some(lua.array_metatable()))?;
globals.set("empty_array", empty_array)?;
let value = lua
+11 -2
View File
@@ -61,6 +61,15 @@ fn test_table() -> Result<()> {
Ok(())
}
#[test]
#[cfg(target_os = "linux")] // Linux allow overcommiting the memory (relevant for CI)
fn test_table_with_large_capacity() {
let lua = Lua::new();
let t = lua.create_table_with_capacity(1 << 26, 1 << 26);
assert!(t.is_ok());
}
#[test]
fn test_table_push_pop() -> Result<()> {
let lua = Lua::new();
@@ -298,10 +307,10 @@ fn test_metatable() -> Result<()> {
let table = lua.create_table()?;
let metatable = lua.create_table()?;
metatable.set("__index", lua.create_function(|_, ()| Ok("index_value"))?)?;
table.set_metatable(Some(metatable));
table.set_metatable(Some(metatable))?;
assert_eq!(table.get::<String>("any_key")?, "index_value");
assert_eq!(table.raw_get::<Value>("any_key")?, Value::Nil);
table.set_metatable(None);
table.set_metatable(None)?;
assert_eq!(table.get::<Value>("any_key")?, Value::Nil);
Ok(())
+81 -8
View File
@@ -147,6 +147,31 @@ fn test_eval() -> Result<()> {
Ok(())
}
#[test]
fn test_replace_globals() -> Result<()> {
let lua = Lua::new();
let globals = lua.create_table()?;
globals.set("foo", "bar")?;
lua.set_globals(globals.clone())?;
let val = lua.load("return foo").eval::<StdString>()?;
assert_eq!(val, "bar");
// Updating globals in sandboxed Lua state is not allowed
#[cfg(feature = "luau")]
{
lua.sandbox(true)?;
match lua.set_globals(globals) {
Err(Error::RuntimeError(msg))
if msg.contains("cannot change globals in a sandboxed Lua state") => {}
r => panic!("expected RuntimeError(...) with a specific error message, got {r:?}"),
}
}
Ok(())
}
#[test]
fn test_load_mode() -> Result<()> {
let lua = unsafe { Lua::unsafe_new() };
@@ -577,6 +602,21 @@ fn test_num_conversion() -> Result<()> {
assert_eq!(lua.unpack::<i128>(lua.pack(1i128 << 64)?)?, 1i128 << 64);
// Negative zero
let negative_zero = lua.load("-0.0").eval::<f64>()?;
assert_eq!(negative_zero, 0.0);
// LuaJIT treats -0.0 as a positive zero
#[cfg(not(feature = "luajit"))]
assert!(negative_zero.is_sign_negative());
// In Lua <5.3 all numbers are floats
#[cfg(not(any(feature = "lua54", feature = "lua53", feature = "luajit")))]
{
let negative_zero = lua.load("-0").eval::<f64>()?;
assert_eq!(negative_zero, 0.0);
assert!(negative_zero.is_sign_negative());
}
Ok(())
}
@@ -1202,6 +1242,17 @@ fn test_register_module() -> Result<()> {
res.unwrap_err().to_string(),
"runtime error: module name must begin with '@'"
);
// Luau registered modules (aliases) are case-insensitive
let res = lua.register_module("@My_Module", &t);
assert!(res.is_ok());
lua.load(
r#"
local my_module = require("@MY_MODule")
assert(my_module.name == "my_module")
"#,
)
.exec()?;
}
Ok(())
@@ -1251,14 +1302,18 @@ fn test_inspect_stack() -> Result<()> {
let lua = Lua::new();
// Not inside any function
assert!(lua.inspect_stack(0).is_none());
assert!(lua.inspect_stack(0, |_| ()).is_none());
let logline = lua.create_function(|lua, msg: StdString| {
let debug = lua.inspect_stack(1).unwrap(); // caller
let source = debug.source().short_src;
let source = source.as_deref().unwrap_or("?");
let line = debug.curr_line();
Ok(format!("{}:{} {}", source, line, msg))
let r = lua
.inspect_stack(1, |debug| {
let source = debug.source().short_src;
let source = source.as_deref().unwrap_or("?");
let line = debug.current_line().unwrap();
format!("{}:{} {}", source, line, msg)
})
.unwrap();
Ok(r)
})?;
lua.globals().set("logline", logline)?;
@@ -1281,8 +1336,7 @@ fn test_inspect_stack() -> Result<()> {
.exec()?;
let stack_info = lua.create_function(|lua, ()| {
let debug = lua.inspect_stack(1).unwrap(); // caller
let stack_info = debug.stack();
let stack_info = lua.inspect_stack(1, |debug| debug.stack()).unwrap();
Ok(format!("{stack_info:?}"))
})?;
lua.globals().set("stack_info", stack_info)?;
@@ -1312,6 +1366,25 @@ fn test_inspect_stack() -> Result<()> {
)
.exec()?;
// Test retrieving currently running function
let running_function =
lua.create_function(|lua, ()| Ok(lua.inspect_stack(1, |debug| debug.function())))?;
lua.globals().set("running_function", running_function)?;
lua.load(
r#"
local function baz()
return running_function()
end
if jit == nil then
assert(baz() == baz)
else
-- luajit inline the "baz" function and returns the chunk itself
assert(baz() == running_function())
end
"#,
)
.exec()?;
Ok(())
}
+22 -1
View File
@@ -1,6 +1,6 @@
use std::panic::catch_unwind;
use mlua::{Error, Function, Lua, Result, Thread, ThreadStatus};
use mlua::{Error, Function, IntoLua, Lua, Result, Thread, ThreadStatus, Value};
#[test]
fn test_thread() -> Result<()> {
@@ -252,3 +252,24 @@ fn test_thread_resume_error() -> Result<()> {
Ok(())
}
#[test]
fn test_thread_resume_bad_arg() -> Result<()> {
let lua = Lua::new();
struct BadArg;
impl IntoLua for BadArg {
fn into_lua(self, _lua: &Lua) -> Result<Value> {
Err(Error::runtime("bad arg"))
}
}
let f = lua.create_thread(lua.create_function(|_, ()| Ok("okay"))?)?;
let res = f.resume::<()>((123, BadArg));
assert!(matches!(res, Err(Error::RuntimeError(msg)) if msg == "bad arg"));
let res = f.resume::<String>(()).unwrap();
assert_eq!(res, "okay");
Ok(())
}
+14 -4
View File
@@ -38,7 +38,7 @@ fn test_userdata() -> Result<()> {
#[test]
fn test_methods() -> Result<()> {
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
struct MyUserData(i64);
impl UserData for MyUserData {
@@ -81,7 +81,7 @@ fn test_methods() -> Result<()> {
check_methods(&lua, lua.create_userdata(MyUserData(42))?)?;
// Additionally check serializable userdata
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
check_methods(&lua, lua.create_ser_userdata(MyUserData(42))?)?;
Ok(())
@@ -306,7 +306,7 @@ fn test_userdata_take() -> Result<()> {
}
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
impl serde::Serialize for MyUserdata {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
@@ -364,7 +364,7 @@ fn test_userdata_take() -> Result<()> {
check_userdata_take(&lua, userdata, rc)?;
// Additionally check serializable userdata
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
{
let rc = Arc::new(18);
let userdata = lua.create_ser_userdata(MyUserdata(rc.clone()))?;
@@ -525,6 +525,11 @@ fn test_fields() -> Result<()> {
Ok(())
});
// Field that emulates method
fields.add_field_function_get("val_fget", |lua, ud| {
lua.create_function(move |_, ()| Ok(ud.borrow::<MyUserData>()?.0))
});
// Use userdata "uservalue" storage
fields.add_field_function_get("uval", |_, ud| ud.user_value::<Option<String>>());
fields.add_field_function_set("uval", |_, ud, s: Option<String>| ud.set_user_value(s));
@@ -537,6 +542,10 @@ fn test_fields() -> Result<()> {
})
})
}
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("dummy", |_, _, ()| Ok(()));
}
}
globals.set("ud", MyUserData(7))?;
@@ -546,6 +555,7 @@ fn test_fields() -> Result<()> {
assert(ud.val == 7)
ud.val = 10
assert(ud.val == 10)
assert(ud:val_fget() == 10)
assert(ud.uval == nil)
ud.uval = "hello"
+1 -10
View File
@@ -255,16 +255,7 @@ fn test_value_conversions() -> Result<()> {
Value::String(lua.create_string("hello")?).as_string().unwrap(),
"hello"
);
assert_eq!(
Value::String(lua.create_string("hello")?).as_str().unwrap(),
"hello"
);
assert_eq!(
Value::String(lua.create_string("hello")?)
.as_string_lossy()
.unwrap(),
"hello"
);
assert_eq!(Value::String(lua.create_string("hello")?).to_string()?, "hello");
assert!(Value::Table(lua.create_table()?).is_table());
assert!(Value::Table(lua.create_table()?).as_table().is_some());
assert!(Value::Function(lua.create_function(|_, ()| Ok(())).unwrap()).is_function());