Compare commits

...

137 Commits

Author SHA1 Message Date
Alex Orlenko 58953e5635 v0.11.0-beta.3 2025-06-23 23:23:07 +01:00
Alex Orlenko 6406de405d mlua-sys: v0.8.1 2025-06-23 23:22:53 +01:00
Alex Orlenko 727096dd3b Handle OOM error during luau_load (Luau >= 0.679) 2025-06-23 23:22:36 +01:00
Alex Orlenko 2445230759 Update CHANGELOG 2025-06-23 22:44:36 +01:00
Alex Orlenko 1cd2bdc808 Ignore test_async_require on windows 2025-06-23 16:37:46 +01:00
Alex Orlenko f8ed33a2aa Fix tests 2025-06-23 16:23:53 +01:00
Alex Orlenko 25955893e0 (Luau Require) Resolve Lua file path relative to the current directory
and unrelated to Rust source file location.
When a Lua file is required inside a Rust file (in a chunk), we should resolve the Lua file relative to the current directory,
instead of relative to the Rust chunk path.
The Rust file location is an internal information that does not exist when the compiled binary runs.
Fixes #605
2025-06-23 15:53:35 +01:00
Alex Orlenko 3f0c69b70b Fix logic to terminate futures on drop.
The underlying Lua thread must stay in yielded state rather than finished.
2025-06-17 15:25:21 +01:00
Alex Orlenko 9da98d42c7 Move ref_stack_pop into ExtraData method. 2025-06-16 22:25:26 +01:00
Alex Orlenko f539f60987 Fix Function::deep_clone() method (Luau).
The `lua_clonefunction` function can fail (and trigger GC) so we need to return Result instead of allowing longjmp
2025-06-16 21:41:29 +01:00
Alex Orlenko aa187e6663 Increase REF_STACK_RESERVE to 3 slots 2025-06-16 21:37:16 +01:00
Alex Orlenko ec10bf2a39 Revert 2e0e86dab2 (Update stack_value helper) 2025-06-16 21:33:47 +01:00
Alex Orlenko 3ea80b763d cargo fmt 2025-06-16 11:21:51 +01:00
Alex Orlenko 2e0e86dab2 Update stack_value helper.
It uses zero stack spaces in Luau, and ref thread for `WrappedError` check in ther Lua versions.
Close #597
2025-06-16 11:12:53 +01:00
Alex Orlenko 0ac7aebb27 Update Lua::sandbox doc 2025-06-13 15:51:31 +01:00
Alex Orlenko 634e5d4550 Reduce collectgarbage options in sandboxed mode
See https://luau.org/sandbox#library
2025-06-13 15:47:54 +01:00
Alex Orlenko 05d6c20520 One more mismatched-lifetime-syntaxes 2025-06-12 23:44:47 +01:00
Alex Orlenko 63e7cfd31b Satisfy mismatched-lifetime-syntaxes lint (nightly)
See rust-lang/rust#141787
2025-06-12 16:30:48 +01:00
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
Alex Orlenko 9b45663afd v0.11.0-beta.1 2025-05-06 23:25:26 +01:00
Alex Orlenko c0c802262e Add must_use to AsyncCallFuture 2025-05-06 22:48:48 +01:00
Alex Orlenko dda344bfc6 Rename _typos.toml to typos.toml 2025-05-06 22:42:19 +01:00
Alex Orlenko 93adfa42d1 Update doc comments 2025-05-06 22:36:23 +01:00
Alex Orlenko 5dc6c3214b Prepare for 2024 edition 2025-05-06 21:28:21 +01:00
Alex Orlenko da08f7210f Revert disabling "module" feature flag for Luau in mlua-sys
Dynamic linking should still be okay
2025-05-06 15:48:52 +01:00
Alex Orlenko 8b7a85076a mlua-sys: Prepare to 2024 edition 2025-05-06 15:47:14 +01:00
Alex Orlenko a4ff68a120 v0.10.4 2025-05-06 15:47:09 +01:00
Alex Orlenko 5454cfa5ed mlua-sys: v0.7.0 2025-05-06 15:46:35 +01:00
Alex Orlenko f9d9dc74a7 Update CHANGELOG 2025-05-06 15:46:29 +01:00
Alex Orlenko ed796fff51 Support async require loaders for Luau 2025-05-04 11:00:59 +01:00
Alex Orlenko 65e5be81ba Update __mlua_async_poll chunk name 2025-05-04 11:00:27 +01:00
Alex Orlenko 4fa8e645b7 Fix Luau "require" tests 2025-05-03 21:45:39 +01:00
Alex Orlenko 05a0abb8c2 Replace Requite::load with Require::loader 2025-05-03 18:00:02 +01:00
Alex Orlenko f0c0527c2b Update Lua* dependencies 2025-05-03 17:58:33 +01:00
Alex Orlenko 2490dfeb38 Silence clippy false positives 2025-05-01 13:18:40 +01:00
Alex Orlenko 4a3dbafb6e Update scoped userdata mismatch tests 2025-05-01 13:12:00 +01:00
Alex Orlenko 97ca3e2c08 Check that type passed to scoped userdata self argument is userdata.
If passed type is non-userdata we try to get a pointer (which will be null) that triggers an assertion.
Having a check also allow us to generate right error message.
Fixes #569
2025-05-01 13:09:23 +01:00
Alex Orlenko d6fb328660 Don't use Lua::get_or_init_from_ptr in Require::load wrapper 2025-04-28 10:07:11 +01:00
Alex Orlenko edd508ed1f Make StateGuard automatically enabled inside callback_error_ext.
Remove manual usage of `StateGuard` in other places.
Closes #567
2025-04-27 23:09:20 +01:00
Alex Orlenko a30a736291 Don't borrow mutably appdata container when working with require content cache 2025-04-27 00:38:30 +01:00
Alex Orlenko 8e1df48e81 Add encode_empty_tables_as_array serialize option.
This will change the behaviour of encoding empty Lua tables into array instead of map.
2025-04-26 17:53:56 +01:00
Alex Orlenko efd192b816 Optimize Table::is_empty 2025-04-26 15:37:12 +01:00
krakow10 5e1451a465 Fix Reversed Comments & Typo (#560)
* Fix reversed comments

* Fix typos
2025-04-26 13:49:50 +01:00
Alex Orlenko 64b1d152b9 Deprecate Lua::load_from_finction and replace it with Lua::register_module and Lua::preload_module instead. 2025-04-26 13:42:07 +01:00
Alex Orlenko a8a4aa8c93 Switch to "proxyrequire" function (follow up Luau 0.671) 2025-04-25 23:41:11 +01:00
Alex Orlenko 9fda2ecfcc mlua-sys: Update Luau to 0.671 2025-04-25 23:38:49 +01:00
Alex Orlenko 21fc924457 Add Lua::create_require_function method 2025-04-25 20:01:37 +01:00
Alex Orlenko 30d5e08a8a Add MaybeSend to Require trait 2025-04-25 15:51:33 +01:00
Alex Orlenko 560a30ca02 Support new Luau require system
This commit introduces the `Require` trait that be used to change `require` behaviour.
By default mlua implements behaviour same as `ReplRequirer` in the original Luau.
Unfortunately binary Luau modules are no longer supported by the new system.
2025-04-25 14:19:41 +01:00
Alex Orlenko 117f8377e2 mlua-sys: Add definitions for Luau require library (since 0.669) 2025-04-20 21:44:16 +02:00
Alex Orlenko 8173ef2fa8 Update README 2025-04-06 17:51:54 +01:00
Alex Orlenko 4e5677ae54 Update references to mlua repo 2025-04-06 15:37:38 +01:00
Alex Orlenko 49958f4827 Split Luau thread event callback to creation and collection callbacks.
We need this because they have different requirements:
- Thread creation callback can return Error or panic
- Thread collection callback runs during Luau GC cycle and cannot make any Lua calls or trigger panics.
2025-04-06 13:57:15 +01:00
Alex Orlenko 39b3af2ff2 Fix warnings when testing documentation 2025-04-05 23:44:25 +01:00
Alex Orlenko b805939320 Make Lua::weak() method and WeakLua struct public.
This can be useful to prevent circular dependencies between Rust and Lua
or check that Lua instance is still alive.
2025-04-05 23:40:05 +01:00
Alex Orlenko bd13300288 Update dependencies 2025-04-05 18:30:22 +01:00
Alex Orlenko 12e61e01f6 Restrict access to Luau VM from UserData destructors.
It's unsafe to make almost any Lua calls when userdata destructor is running.
This can cause recursive GC run and crash.
See https://github.com/luau-lang/luau/pull/510 for some details.
2025-04-05 10:31:39 +01:00
Alex Orlenko 53ff494ab9 Prepare for custom (Luau) userdata destructors.
We need to add logic later to prevent calling any Lua functions when UserData destructor is running.
2025-04-04 16:36:06 +01:00
Alex Orlenko 1cb081d20a Do not propagate collect_garbage flag when clonning Lua 2025-04-04 10:58:08 +01:00
Alex Orlenko c1a018a60d Add AnyUserData::type_id method 2025-03-31 22:41:42 +01:00
Alex Orlenko 4d2ff45a8b Update Lua::poll_pending doc (still hidden) 2025-03-31 22:26:47 +01:00
Alex Orlenko 72682198eb Bump Luau to 0.667 2025-03-30 11:12:14 +01:00
Alex Orlenko 87fa663ac1 Remove deprecated functions 2025-03-28 13:24:15 +00:00
Alex Orlenko 1f970824b4 Support user thread creation callback (Luau) 2025-03-28 11:51:35 +00:00
Alex Orlenko 8d864e2687 Reduce stack operations when creating userdata 2025-03-22 14:23:59 +00:00
Alex Orlenko b0140cdb9e Add Chunk::name(), Chunk::environment() and Chunk::mode() functions.
They can be used to retrieve existing chunk params.
2025-03-22 09:31:24 +00:00
Alex Orlenko 806817212e Rename Lua::init_from_ptr to Lua::get_or_init_from_ptr.
Return reference to `&Lua` with unbound lifetime (chosen by user).
2025-03-21 21:28:30 +00:00
Alex Orlenko 95c623e94d Use c string literal where appropriate 2025-03-21 15:57:15 +00:00
Alex Orlenko 0a1a1fe0b6 Move some userdata helpers from crate::util to crate::userdata::util 2025-03-21 15:13:53 +00:00
Alex Orlenko 4fe7d151a1 Refactor userdata-wrappers feature.
Support borrowing underlying data in `UserDataRef` and `UserDataRefMut`.
2025-03-21 14:19:38 +00:00
Alex Orlenko edbf1e9150 Change Lua library name constants from &str to *const c_char.
It makes easier to pass them to Lua API functions.
2025-03-14 23:10:14 +00:00
Alex Orlenko 69011a89d2 Revert "Add initial pluto support"
This reverts commit 0ed11e4134.
2025-03-14 22:59:06 +00:00
Alex Orlenko 0ed11e4134 Add initial pluto support
Pluto is a superset of Lua 5.4 with a focus on general-purpose programming.
`pluto` feature also enables `lua54` feature since they are compatible and share the same API.
2025-03-14 11:38:07 +00:00
Alex Orlenko 0ce599aff2 Update test case 2025-03-13 17:41:13 +00:00
Alex Orlenko 1ebb4b468a Support 52-bit integers for Luau
Simply to float conversion (it actually never fails or goes out of range)
2025-03-13 16:18:44 +00:00
Alex Orlenko 5ab97666d7 Fix clippy warnings 2025-03-07 22:34:50 +00:00
Alex Orlenko e706ae4fcb Remove Roblox from references to Luau
Closes #537
2025-02-24 19:03:36 +00:00
Alex Orlenko ae7cdcb934 Remove (internal) borrow counter and use instead "locked" flag and strong reference counter 2025-02-14 14:41:37 +00:00
Alex Orlenko dacddfa967 Add Variadic to prelude 2025-02-14 12:08:57 +00:00
Alex Orlenko a01b032c35 Add bstr/serde dependency if serialize feature flag is enabled 2025-02-11 13:39:21 +00:00
Alex Orlenko 18497f1528 Add library constants support for Luau compiler.
Add ability to specify compile-time constants for known library members.
2025-02-11 00:16:22 +00:00
Alex Orlenko 28e8f56989 Fix tests 2025-02-09 22:17:25 +00:00
Alex Orlenko c1f8abba9e Do not allow recursive warnings (Lua 5.4) 2025-02-09 15:08:52 +00:00
Alex Orlenko d1a587f49a Wrap hooks in refcounter instead of box, same as previously.
This allows to obtain independent clone of closure.
Ensure that stack is always clean when running thread hook.
2025-02-08 22:11:11 +00:00
Alex Orlenko 5bbd23ed1a Fix hook tests 2025-02-08 11:51:38 +00:00
Alex Orlenko 74b4601b48 Rework Lua hooks:
- Support global hooks inherited by new threads
- Support thread hooks, where each thread can have its own hook

This should also allow to enable hooks for async calls.
Related to #489 #347
2025-02-07 22:42:17 +00:00
Joel Natividad a89800b949 Typos config and ci (#523)
* typos configuration file
* typos CI
2025-02-05 19:02:48 +00:00
Alex Orlenko 2b6172ef38 Fix tests 2025-02-05 19:01:07 +00:00
Joel Natividad 20f7ce097d Fix typos (#522)
* fix various typos in the codebase

* fix typos in CHANGELOG.md
2025-02-05 16:16:20 +00:00
Andrew Farkas 6882e0434e Fix version number in changelog (#521) 2025-02-04 07:33:33 +00:00
Alex Orlenko fca38a6637 Imporove BorrowedStr/BorrowedBytes ergonomic.
Implement `FromLua` and `IntoLua` for these types to allow working with them directly.
2025-01-30 23:07:02 +00:00
Alex Orlenko cdd6a99136 Support Thread::resume_error call (Luau)
This method uses Luau-specific C API extension `lua_resumerror`
that allow to throw an error immediately when resuming a thead.
Closes #500 #513
2025-01-28 22:57:26 +00:00
Alex Orlenko 21d39a069d Pass &Scope instead of &mut Scope to Lua::scope closure.
It was a leftover from an experimental implementation that has been removed.
2025-01-28 18:23:18 +00:00
Alex Orlenko 58965c6255 Move lifetime from AsChunk<'a> to AsChunk::source 2025-01-28 18:17:29 +00:00
Alex Orlenko 8574682ffc Improve From/Into Lua char conversion 2025-01-28 10:41:10 +00:00
tk 47bc372096 impl FromLua/IntoLua for char (#516) 2025-01-28 10:10:28 +00:00
Alex Orlenko d376cb9403 Enable Thread::reset() for all Lua versions 2025-01-28 10:08:16 +00:00
Alex Orlenko ea5ecccf02 Change AsyncThread<A, R> to AsyncThread<R>.
Push arguments in `Thread::into_async()` to the thread during the call instead of first poll.
The pushed arguments will be automatically used on resume.
Fixes #508 and relates to #500.
2025-01-28 10:08:08 +00:00
Alex Orlenko cf71edc492 Update README (set main branch for development) 2025-01-28 10:07:41 +00:00
Alex Orlenko 9caf3542d9 v0.10.3 2025-01-27 21:33:51 +00:00
Alex Orlenko cb45db05fa Update README/CHANGELOG 2025-01-27 21:33:24 +00:00
Alex Orlenko d1cb2a9a96 mlua-sys: v0.6.7 2025-01-27 21:20:47 +00:00
Alex Orlenko aa3f6ba46c Fix prototype of new Luau compiler options and methods 2025-01-27 21:19:31 +00:00
Alex Orlenko cc57bed4c8 Update Luau to 0.657 2025-01-27 14:19:40 +00:00
Radiant b5d38ab2e3 Set Default for LuaValue to be nil. (#512) 2025-01-19 23:52:58 +00:00
Alex Orlenko cd4091f64d Allow exhaustive match on Value.
It was not possible because `ValueRef` variant in `Value::Other` was private.
Closes #502 and #503
2024-12-10 23:37:19 +00:00
Alex Orlenko 91e069a77e Optimize (and simplify) protected mode for Rust function calls 2024-12-07 00:04:12 +00:00
Alex Orlenko cacd3dc70f Add Table::set_safeenv method (Luau) 2024-12-04 10:40:49 +00:00
128 changed files with 5673 additions and 2714 deletions
+15 -15
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
@@ -194,7 +194,7 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest]
rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit, luau]
lua: [lua54, lua53, lua52, lua51, luajit]
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
@@ -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"
+17
View File
@@ -0,0 +1,17 @@
name: Typos Check
on:
pull_request:
workflow_dispatch:
jobs:
run:
name: Spell Check with Typos
runs-on: ubuntu-latest
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v4
- name: Check spelling
uses: crate-ci/typos@master
with:
config: ./typos.toml
+61 -4
View File
@@ -1,3 +1,60 @@
## 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)
- Added `Thread::resume_error` support for Luau
- 52 bit integers support for Luau (this is a breaking change)
- New features for Luau compiler (constants, disabled builtins, known members)
- `AsyncThread<A, R>` changed to `AsyncThread<R>` (`A` pushed to stack immediately)
- Lifetime `'a` moved from `AsChunk<'a>` to `AsChunk::source where Self: 'a`
- `Lua::scope` pass `&Scope` instead of `&mut Scope` to closure
- Added global hooks support (Lua 5.1+)
- Added per-thread hooks support (Lua 5.1+)
- `Lua::init_from_ptr` renamed to `Lua::get_or_init_from_ptr` and returns `&Lua`
- `Lua:load_from_function` is deprecated (this is `register_module` now)
- Added `Lua::register_module` and `Lua::preload_module`
## v0.10.4 (May 5th, 2025)
- Luau updated to 0.672
- New serde option `encode_empty_tables_as_array` to serialize empty tables as arrays
- Added `WeakLua` and `Lua::weak()` to create weak references to Lua state
- Trigger abort when Luau userdata destructors are panic (Luau GC does not support it)
- Added `AnyUserData::type_id()` method to get the type id of the userdata
- Added `Chunk::name()`, `Chunk::environment()` and `Chunk::mode()` functions
- Support borrowing underlying wrapped types for `UserDataRef` and `UserDataRefMut` (under `userdata-wrappers` feature)
- Added large (52bit) integers support for Luau
- Enable `serde` for `bstr` if `serialize` feature flag is enabled
- Recursive warnings (Lua 5.4) are no longer allowed
- Implemented `IntoLua`/`FromLua` for `BorrowedString` and `BorrowedBytes`
- Implemented `IntoLua`/`FromLua` for `char`
- Enable `Thread::reset()` for all Lua versions (limited support for 5.1-5.3)
- Bugfixes and improvements
## v0.10.3 (Jan 27th, 2025)
- Set `Default` for `Value` to be `Nil`
- Allow exhaustive match on `Value` (#502)
- Add `Table::set_safeenv` method (Luau)
## v0.10.2 (Dec 1st, 2024)
- Switch proc-macro-error to proc-macro-error2 (#493)
@@ -298,7 +355,7 @@ Other:
## v0.8.0
Changes since 0.7.4
- Roblox Luau support
- Luau support
- Removed C glue
- Added async support to `__index` and `__newindex` metamethods
- Added `Function::info()` to get information about functions (#149).
@@ -348,7 +405,7 @@ Breaking changes:
## v0.8.0-beta.1
- Roblox Luau support
- Luau support
- Refactored ffi module. C glue is no longer required
- Added async support to `__index` and `__newindex` metamethods
@@ -461,7 +518,7 @@ Breaking changes:
- [**Breaking**] Removed `AnyUserData::has_metamethod()`
- Added `Thread::reset()` for luajit/lua54 to recycle threads.
It's possible to attach a new function to a thread (coroutine).
- Added `chunk!` macro support to load chunks of Lua code using the Rust tokenizer and optinally capturing Rust variables.
- Added `chunk!` macro support to load chunks of Lua code using the Rust tokenizer and optionally capturing Rust variables.
- Improved error reporting (`Error`'s `__tostring` method formats full stacktraces). This is useful in the module mode.
## v0.6.0-beta.1
@@ -517,7 +574,7 @@ Breaking changes:
- Lua 5.4 support with `MetaMethod::Close`.
- `lua53` feature is disabled by default. Now preferred Lua version have to be chosen explicitly.
- Provide safety guaraness for Lua state, which means that potenially unsafe operations, like loading C modules (using `require` or `package.loadlib`) are disabled. Equalient for the previous `Lua::new()` function is `Lua::unsafe_new()`.
- Provide safety guarantees for Lua state, which means that potentially unsafe operations, like loading C modules (using `require` or `package.loadlib`) are disabled. Equivalent to the previous `Lua::new()` function is `Lua::unsafe_new()`.
- New `send` feature to require `Send`.
- New `module` feature, that disables linking to Lua Core Libraries. Required for modules.
- Don't allow `'callback` outlive `'lua` in `Lua::create_function()` to fix [the unsoundness](tests/compile/static_callback_args.rs).
+21 -20
View File
@@ -1,22 +1,22 @@
[package]
name = "mlua"
version = "0.10.2" # remember to update mlua_derive
version = "0.11.0-beta.3" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
rust-version = "1.79.0"
edition = "2021"
repository = "https://github.com/khvzak/mlua"
repository = "https://github.com/mlua-rs/mlua"
documentation = "https://docs.rs/mlua"
readme = "README.md"
keywords = ["lua", "luajit", "luau", "async", "scripting"]
categories = ["api-bindings", "asynchronous"]
license = "MIT"
description = """
High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox Luau
High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau
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]
@@ -32,21 +32,24 @@ lua52 = ["ffi/lua52"]
lua51 = ["ffi/lua51"]
luajit = ["ffi/luajit"]
luajit52 = ["luajit", "ffi/luajit52"]
luau = ["ffi/luau", "dep:libloading"]
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"]
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.10.1", optional = true, path = "mlua_derive" }
mlua_derive = { version = "=0.11.0-beta.2", optional = true, path = "mlua_derive" }
bstr = { version = "1.0", features = ["std"], default-features = false }
either = "1.0"
num-traits = { version = "0.2.14" }
@@ -57,11 +60,9 @@ erased-serde = { version = "0.4", optional = true }
serde-value = { version = "0.7", optional = true }
parking_lot = { version = "0.12", features = ["arc_lock"] }
anyhow = { version = "1.0", optional = true }
rustversion = "1.0"
ffi = { package = "mlua-sys", version = "0.6.6", path = "mlua-sys" }
[target.'cfg(unix)'.dependencies]
libloading = { version = "0.8", optional = true }
ffi = { package = "mlua-sys", version = "0.8.0", path = "mlua-sys" }
[dev-dependencies]
trybuild = "1.0"
@@ -77,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 = "14.0"
criterion = { version = "0.6", features = ["async_tokio"] }
rustyline = "16.0"
tokio = { version = "1.0", features = ["full"] }
[lints.rust]
@@ -92,7 +93,7 @@ required-features = ["async"]
[[bench]]
name = "serde"
harness = false
required-features = ["serialize"]
required-features = ["serde"]
[[example]]
name = "async_http_client"
@@ -100,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"
@@ -115,8 +116,8 @@ name = "guided_tour"
required-features = ["macros"]
[[example]]
name = "serialize"
required-features = ["serialize"]
name = "serde"
required-features = ["serde"]
[[example]]
name = "userdata"
+49 -47
View File
@@ -1,8 +1,8 @@
# mlua
[![Build Status]][github-actions] [![Latest Version]][crates.io] [![API Documentation]][docs.rs] [![Coverage Status]][codecov.io] ![MSRV]
[Build Status]: https://github.com/khvzak/mlua/workflows/CI/badge.svg
[github-actions]: https://github.com/khvzak/mlua/actions
[Build Status]: https://github.com/mlua-rs/mlua/workflows/CI/badge.svg
[github-actions]: https://github.com/mlua-rs/mlua/actions
[Latest Version]: https://img.shields.io/crates/v/mlua.svg
[crates.io]: https://crates.io/crates/mlua
[API Documentation]: https://docs.rs/mlua/badge.svg
@@ -17,27 +17,29 @@
[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`.
> **Note**
>
> See v0.10 [release notes](https://github.com/khvzak/mlua/blob/main/docs/release_notes/v0.10.md).
> See v0.10 [release notes](https://github.com/mlua-rs/mlua/blob/main/docs/release_notes/v0.10.md).
`mlua` is bindings to [Lua](https://www.lua.org) programming language for Rust with a goal to provide
_safe_ (as far as it's possible), high level, easy to use, practical and flexible API.
`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.
Started as `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Roblox Luau] and allows to write native Lua modules in Rust as well as use Lua in a standalone mode.
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` 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 versions excluding JIT.
WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua/Luau versions excluding JIT.
[GitHub Actions]: https://github.com/khvzak/mlua/actions
[Roblox Luau]: https://luau.org
[GitHub Actions]: https://github.com/mlua-rs/mlua/actions
[Luau]: https://luau.org
## Usage
### 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
@@ -49,12 +51,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`
@@ -64,9 +66,9 @@ Below is a list of the available feature flags. By default `mlua` does not enabl
[5.2]: https://www.lua.org/manual/5.2/manual.html
[5.1]: https://www.lua.org/manual/5.1/manual.html
[LuaJIT]: https://luajit.org/
[Luau]: https://github.com/Roblox/luau
[lua-src]: https://github.com/khvzak/lua-src-rs
[luajit-src]: https://github.com/khvzak/luajit-src-rs
[Luau]: https://github.com/luau-lang/luau
[lua-src]: https://github.com/mlua-rs/lua-src-rs
[luajit-src]: https://github.com/mlua-rs/luajit-src-rs
[tokio]: https://github.com/tokio-rs/tokio
[async-std]: https://github.com/async-rs/async-std
[`Send`]: https://doc.rust-lang.org/std/marker/trait.Send.html
@@ -76,7 +78,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)
@@ -91,7 +93,7 @@ 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
@@ -100,7 +102,7 @@ curl -v http://localhost:3000
### Serialization (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 it (including `UserData` support).
[Example](examples/serialize.rs)
@@ -112,28 +114,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.2", features = ["lua54", "vendored"] }
mlua = { version = "0.10", features = ["lua54", "vendored"] }
```
`main.rs`
@@ -157,21 +159,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.2", features = ["lua54", "module"] }
mlua = { version = "0.10", features = ["lua54", "module"] }
```
`lib.rs` :
`lib.rs`:
``` rust
use mlua::prelude::*;
@@ -214,14 +216,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)
@@ -234,10 +236,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!
@@ -245,8 +247,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
@@ -265,12 +267,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.
@@ -287,12 +289,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).
+1 -1
View File
@@ -3,7 +3,7 @@
The v0.10 version of mlua has goal to improve the user experience while keeping the same performance and safety guarantees.
This document highlights the most notable features. For a full list of changes, see the [CHANGELOG].
[CHANGELOG]: https://github.com/khvzak/mlua/blob/main/CHANGELOG.md
[CHANGELOG]: https://github.com/mlua-rs/mlua/blob/main/CHANGELOG.md
### New features
+2 -2
View File
@@ -3,7 +3,7 @@
The v0.9 version of mlua is a major release that includes a number of API changes and improvements. This release is a stepping stone towards the v1.0.
This document highlights the most important changes. For a full list of changes, see the [CHANGELOG].
[CHANGELOG]: https://github.com/khvzak/mlua/blob/main/CHANGELOG.md
[CHANGELOG]: https://github.com/mlua-rs/mlua/blob/main/CHANGELOG.md
### New features
@@ -304,7 +304,7 @@ assert_eq!(f.call::<_, mlua::String>(())?, "hello");
The new mlua version has a number of performance improvements. Please check the [benchmarks results] to see how mlua compares to rlua and rhai.
[benchmarks results]: https://github.com/khvzak/script-bench-rs
[benchmarks results]: https://github.com/mlua-rs/script-bench-rs
### Changes in `module` mode
+6 -6
View File
@@ -1,10 +1,10 @@
[package]
name = "mlua-sys"
version = "0.6.6"
version = "0.8.1"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
rust-version = "1.71"
edition = "2021"
repository = "https://github.com/khvzak/mlua"
repository = "https://github.com/mlua-rs/mlua"
documentation = "https://docs.rs/mlua-sys"
readme = "README.md"
categories = ["external-ffi-bindings"]
@@ -12,7 +12,7 @@ license = "MIT"
links = "lua"
build = "build/main.rs"
description = """
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox Luau
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau
"""
[package.metadata.docs.rs]
@@ -38,9 +38,9 @@ module = []
cc = "1.0"
cfg-if = "1.0"
pkg-config = "0.3.17"
lua-src = { version = ">= 547.0.0, < 547.1.0", optional = true }
luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true }
luau0-src = { version = "0.11.1", 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.15.0", optional = true }
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] }
+2 -2
View File
@@ -1,8 +1,8 @@
# mlua-sys
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox [Luau].
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and [Luau].
Intended to be consumed by the [mlua] crate.
[Luau]: https://github.com/Roblox/luau
[Luau]: https://github.com/luau-lang/luau
[mlua]: https://crates.io/crates/mlua
+3 -2
View File
@@ -1,7 +1,8 @@
//! Low level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox Luau.
//! Low level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau.
#![allow(non_camel_case_types, non_snake_case, dead_code)]
#![allow(non_camel_case_types, non_snake_case)]
#![allow(clippy::missing_safety_doc)]
#![allow(unsafe_op_in_unsafe_fn)]
#![doc(test(attr(deny(warnings))))]
#![cfg_attr(docsrs, feature(doc_cfg))]
+11 -11
View File
@@ -90,7 +90,7 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) ->
} else if compat53_findfield(L, objidx, level - 1) != 0 {
// try recursively
lua_remove(L, -2); // remove table (but keep name)
lua_pushliteral(L, ".");
lua_pushliteral(L, c".");
lua_insert(L, -2); // place '.' between the two names
lua_concat(L, 3);
return 1;
@@ -121,13 +121,13 @@ unsafe fn compat53_pushfuncname(L: *mut lua_State, ar: *mut lua_Debug) {
lua_pushfstring(L, cstr!("function '%s'"), (*ar).name);
} else if *(*ar).what == b'm' as c_char {
// main?
lua_pushliteral(L, "main chunk");
lua_pushliteral(L, c"main chunk");
} else if *(*ar).what == b'C' as c_char {
if compat53_pushglobalfuncname(L, ar) != 0 {
lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1));
lua_remove(L, -2); // remove name
} else {
lua_pushliteral(L, "?");
lua_pushliteral(L, c"?");
}
} else {
lua_pushfstring(
@@ -377,7 +377,7 @@ pub unsafe fn luaL_checkstack(L: *mut lua_State, sz: c_int, msg: *const c_char)
if !msg.is_null() {
luaL_error(L, cstr!("stack overflow (%s)"), msg);
} else {
lua_pushliteral(L, "stack overflow");
lua_pushliteral(L, c"stack overflow");
lua_error(L);
}
}
@@ -467,12 +467,12 @@ pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const
if !msg.is_null() {
lua_pushfstring(L, cstr!("%s\n"), msg);
}
lua_pushliteral(L, "stack traceback:");
lua_pushliteral(L, c"stack traceback:");
while lua_getstack(L1, level, &mut ar) != 0 {
level += 1;
if level == mark {
// too many levels?
lua_pushliteral(L, "\n\t..."); // add a '...'
lua_pushliteral(L, c"\n\t..."); // add a '...'
level = numlevels - COMPAT53_LEVELS2; // and skip to last ones
} else {
lua_getinfo(L1, cstr!("Slnt"), &mut ar);
@@ -480,7 +480,7 @@ pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const
if ar.currentline > 0 {
lua_pushfstring(L, cstr!("%d:"), ar.currentline);
}
lua_pushliteral(L, " in ");
lua_pushliteral(L, c" in ");
compat53_pushfuncname(L, &mut ar);
lua_concat(L, lua_gettop(L) - top);
}
@@ -493,16 +493,16 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize)
if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 {
match lua_type(L, idx) {
LUA_TNIL => {
lua_pushliteral(L, "nil");
lua_pushliteral(L, c"nil");
}
LUA_TSTRING | LUA_TNUMBER => {
lua_pushvalue(L, idx);
}
LUA_TBOOLEAN => {
if lua_toboolean(L, idx) == 0 {
lua_pushliteral(L, "false");
lua_pushliteral(L, c"false");
} else {
lua_pushliteral(L, "true");
lua_pushliteral(L, c"true");
}
}
t => {
@@ -548,7 +548,7 @@ pub unsafe fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_ch
pub unsafe fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int) {
luaL_checkstack(L, 3, cstr!("not enough stack slots available"));
luaL_getsubtable(L, LUA_REGISTRYINDEX, cstr!("_LOADED"));
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
if lua_getfield(L, -1, modname) == LUA_TNIL {
lua_pop(L, 1);
lua_pushcfunction(L, openf);
+5 -2
View File
@@ -8,6 +8,9 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State};
// Extra error code for 'luaL_load'
pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1;
// Key, in the registry, for table of loaded modules
pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED");
#[repr(C)]
pub struct luaL_Reg {
pub name: *const c_char,
@@ -15,7 +18,7 @@ pub struct luaL_Reg {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_register(L: *mut lua_State, libname: *const c_char, l: *const luaL_Reg);
#[link_name = "luaL_getmetafield"]
pub fn luaL_getmetafield_(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
@@ -58,7 +61,7 @@ pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int;
pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
+9 -10
View File
@@ -1,5 +1,6 @@
//! Contains definitions from `lua.h`.
use std::ffi::CStr;
use std::marker::{PhantomData, PhantomPinned};
use std::os::raw::{c_char, c_double, c_int, c_void};
use std::ptr;
@@ -83,13 +84,13 @@ pub type lua_Reader =
pub type lua_Writer =
unsafe extern "C-unwind" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int;
/// Type for memory-allocation functions
/// Type for memory-allocation functions (no unwinding)
#[rustfmt::skip]
pub type lua_Alloc =
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// State manipulation
//
@@ -219,7 +220,7 @@ pub const LUA_GCSETPAUSE: c_int = 6;
pub const LUA_GCSETSTEPMUL: c_int = 7;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
@@ -227,7 +228,7 @@ extern "C-unwind" {
// Miscellaneous functions
//
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
#[link_name = "lua_error"]
fn lua_error_(L: *mut lua_State) -> c_int;
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int;
@@ -312,10 +313,8 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int {
}
#[inline(always)]
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) {
use std::ffi::CString;
let c_str = CString::new(s).unwrap();
lua_pushlstring_(L, c_str.as_ptr(), c_str.as_bytes().len())
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
lua_pushstring_(L, s.as_ptr());
}
#[inline(always)]
@@ -371,7 +370,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug);
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
+13 -13
View File
@@ -1,27 +1,27 @@
//! Contains definitions from `lualib.h`.
use std::os::raw::c_int;
use std::os::raw::{c_char, c_int};
use super::lua::lua_State;
pub const LUA_COLIBNAME: &str = "coroutine";
pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_IOLIBNAME: &str = "io";
pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_LOADLIBNAME: &str = "package";
pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine");
pub const LUA_TABLIBNAME: *const c_char = cstr!("table");
pub const LUA_IOLIBNAME: *const c_char = cstr!("io");
pub const LUA_OSLIBNAME: *const c_char = cstr!("os");
pub const LUA_STRLIBNAME: *const c_char = cstr!("string");
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_LOADLIBNAME: *const c_char = cstr!("package");
#[cfg(feature = "luajit")]
pub const LUA_BITLIBNAME: &str = "bit";
pub const LUA_BITLIBNAME: *const c_char = cstr!("bit");
#[cfg(feature = "luajit")]
pub const LUA_JITLIBNAME: &str = "jit";
pub const LUA_JITLIBNAME: *const c_char = cstr!("jit");
#[cfg(feature = "luajit")]
pub const LUA_FFILIBNAME: &str = "ffi";
pub const LUA_FFILIBNAME: *const c_char = cstr!("ffi");
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
pub fn luaopen_io(L: *mut lua_State) -> c_int;
+4 -4
View File
@@ -199,16 +199,16 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize)
if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 {
match lua_type(L, idx) {
LUA_TNIL => {
lua_pushliteral(L, "nil");
lua_pushliteral(L, c"nil");
}
LUA_TSTRING | LUA_TNUMBER => {
lua_pushvalue(L, idx);
}
LUA_TBOOLEAN => {
if lua_toboolean(L, idx) == 0 {
lua_pushliteral(L, "false");
lua_pushliteral(L, c"false");
} else {
lua_pushliteral(L, "true");
lua_pushliteral(L, c"true");
}
}
t => {
@@ -232,7 +232,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize)
pub unsafe fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int) {
luaL_checkstack(L, 3, cstr!("not enough stack slots available"));
luaL_getsubtable(L, LUA_REGISTRYINDEX, cstr!("_LOADED"));
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
if lua_getfield(L, -1, modname) == LUA_TNIL {
lua_pop(L, 1);
lua_pushcfunction(L, openf);
+9 -3
View File
@@ -8,6 +8,12 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State, lua_Un
// Extra error code for 'luaL_load'
pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1;
// Key, in the registry, for table of loaded modules
pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED");
// Key, in the registry, for table of preloaded loaders
pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD");
#[repr(C)]
pub struct luaL_Reg {
pub name: *const c_char,
@@ -15,7 +21,7 @@ pub struct luaL_Reg {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number);
#[link_name = "luaL_getmetafield"]
@@ -63,7 +69,7 @@ pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int;
pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
@@ -76,7 +82,7 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
+12 -13
View File
@@ -1,5 +1,6 @@
//! Contains definitions from `lua.h`.
use std::ffi::CStr;
use std::marker::{PhantomData, PhantomPinned};
use std::os::raw::{c_char, c_double, c_int, c_uchar, c_uint, c_void};
use std::ptr;
@@ -88,13 +89,13 @@ pub type lua_Reader =
pub type lua_Writer =
unsafe extern "C-unwind" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int;
/// Type for memory-allocation functions
/// Type for memory-allocation functions (no unwinding)
#[rustfmt::skip]
pub type lua_Alloc =
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// State manipulation
//
@@ -160,14 +161,14 @@ pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_arith(L: *mut lua_State, op: c_int);
pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int;
pub fn lua_compare(L: *mut lua_State, idx1: c_int, idx2: c_int, op: c_int) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// Push functions (C -> stack)
//
@@ -256,7 +257,7 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// Coroutine functions
//
@@ -288,12 +289,12 @@ pub const LUA_GCGEN: c_int = 10;
pub const LUA_GCINC: c_int = 11;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// Miscellaneous functions
//
@@ -395,10 +396,8 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int {
}
#[inline(always)]
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) -> *const c_char {
use std::ffi::CString;
let c_str = CString::new(s).unwrap();
lua_pushlstring_(L, c_str.as_ptr(), c_str.as_bytes().len())
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
lua_pushstring(L, s.as_ptr());
}
#[inline(always)]
@@ -449,7 +448,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug);
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
+11 -11
View File
@@ -1,21 +1,21 @@
//! Contains definitions from `lualib.h`.
use std::os::raw::c_int;
use std::os::raw::{c_char, c_int};
use super::lua::lua_State;
pub const LUA_COLIBNAME: &str = "coroutine";
pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_IOLIBNAME: &str = "io";
pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_BITLIBNAME: &str = "bit32";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_LOADLIBNAME: &str = "package";
pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine");
pub const LUA_TABLIBNAME: *const c_char = cstr!("table");
pub const LUA_IOLIBNAME: *const c_char = cstr!("io");
pub const LUA_OSLIBNAME: *const c_char = cstr!("os");
pub const LUA_STRLIBNAME: *const c_char = cstr!("string");
pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32");
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_LOADLIBNAME: *const c_char = cstr!("package");
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
+5 -5
View File
@@ -9,10 +9,10 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State};
pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1;
// Key, in the registry, for table of loaded modules
pub const LUA_LOADED_TABLE: &str = "_LOADED";
pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED");
// Key, in the registry, for table of preloaded loaders
pub const LUA_PRELOAD_TABLE: &str = "_PRELOAD";
pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD");
#[repr(C)]
pub struct luaL_Reg {
@@ -21,7 +21,7 @@ pub struct luaL_Reg {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number, sz: usize);
pub fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
@@ -65,7 +65,7 @@ pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int;
pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
@@ -78,7 +78,7 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
+12 -13
View File
@@ -1,5 +1,6 @@
//! Contains definitions from `lua.h`.
use std::ffi::CStr;
use std::marker::{PhantomData, PhantomPinned};
use std::os::raw::{c_char, c_double, c_int, c_uchar, c_void};
use std::{mem, ptr};
@@ -95,13 +96,13 @@ pub type lua_Reader =
pub type lua_Writer =
unsafe extern "C-unwind" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int;
/// Type for memory-allocation functions
/// Type for memory-allocation functions (no unwinding)
#[rustfmt::skip]
pub type lua_Alloc =
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// State manipulation
//
@@ -171,14 +172,14 @@ pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_arith(L: *mut lua_State, op: c_int);
pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int;
pub fn lua_compare(L: *mut lua_State, idx1: c_int, idx2: c_int, op: c_int) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// Push functions (C -> stack)
//
@@ -264,7 +265,7 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// Coroutine functions
//
@@ -299,12 +300,12 @@ pub const LUA_GCSETSTEPMUL: c_int = 7;
pub const LUA_GCISRUNNING: c_int = 9;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// Miscellaneous functions
//
@@ -407,10 +408,8 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int {
}
#[inline(always)]
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) -> *const c_char {
use std::ffi::CString;
let c_str = CString::new(s).unwrap();
lua_pushlstring(L, c_str.as_ptr(), c_str.as_bytes().len())
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
lua_pushstring(L, s.as_ptr());
}
#[inline(always)]
@@ -478,7 +477,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug);
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
+12 -12
View File
@@ -1,22 +1,22 @@
//! Contains definitions from `lualib.h`.
use std::os::raw::c_int;
use std::os::raw::{c_char, c_int};
use super::lua::lua_State;
pub const LUA_COLIBNAME: &str = "coroutine";
pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_IOLIBNAME: &str = "io";
pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_UTF8LIBNAME: &str = "utf8";
pub const LUA_BITLIBNAME: &str = "bit32";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_LOADLIBNAME: &str = "package";
pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine");
pub const LUA_TABLIBNAME: *const c_char = cstr!("table");
pub const LUA_IOLIBNAME: *const c_char = cstr!("io");
pub const LUA_OSLIBNAME: *const c_char = cstr!("os");
pub const LUA_STRLIBNAME: *const c_char = cstr!("string");
pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8");
pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32");
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_LOADLIBNAME: *const c_char = cstr!("package");
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
+5 -5
View File
@@ -9,10 +9,10 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State};
pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1;
// Key, in the registry, for table of loaded modules
pub const LUA_LOADED_TABLE: &str = "_LOADED";
pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED");
// Key, in the registry, for table of preloaded loaders
pub const LUA_PRELOAD_TABLE: &str = "_PRELOAD";
pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD");
#[repr(C)]
pub struct luaL_Reg {
@@ -21,7 +21,7 @@ pub struct luaL_Reg {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number, sz: usize);
pub fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
@@ -64,7 +64,7 @@ pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int;
pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
@@ -77,7 +77,7 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
+13 -14
View File
@@ -1,5 +1,6 @@
//! Contains definitions from `lua.h`.
use std::ffi::CStr;
use std::marker::{PhantomData, PhantomPinned};
use std::os::raw::{c_char, c_double, c_int, c_uchar, c_ushort, c_void};
use std::{mem, ptr};
@@ -94,16 +95,16 @@ pub type lua_Reader =
pub type lua_Writer =
unsafe extern "C-unwind" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int;
/// Type for memory-allocation functions
/// Type for memory-allocation functions (no unwinding)
#[rustfmt::skip]
pub type lua_Alloc =
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
/// Type for warning functions
pub type lua_WarnFunction = unsafe extern "C-unwind" fn(ud: *mut c_void, msg: *const c_char, tocont: c_int);
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// State manipulation
//
@@ -185,14 +186,14 @@ pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_arith(L: *mut lua_State, op: c_int);
pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int;
pub fn lua_compare(L: *mut lua_State, idx1: c_int, idx2: c_int, op: c_int) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// Push functions (C -> stack)
//
@@ -278,7 +279,7 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// Coroutine functions
//
@@ -302,7 +303,7 @@ pub unsafe fn lua_yield(L: *mut lua_State, n: c_int) -> c_int {
// Warning-related functions
//
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_setwarnf(L: *mut lua_State, f: Option<lua_WarnFunction>, ud: *mut c_void);
pub fn lua_warning(L: *mut lua_State, msg: *const c_char, tocont: c_int);
}
@@ -323,12 +324,12 @@ pub const LUA_GCGEN: c_int = 10;
pub const LUA_GCINC: c_int = 11;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, ...) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// Miscellaneous functions
//
@@ -434,10 +435,8 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int {
}
#[inline(always)]
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) -> *const c_char {
use std::ffi::CString;
let c_str = CString::new(s).unwrap();
lua_pushlstring(L, c_str.as_ptr(), c_str.as_bytes().len())
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
lua_pushstring(L, s.as_ptr());
}
#[inline(always)]
@@ -520,7 +519,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug);
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
+11 -11
View File
@@ -1,21 +1,21 @@
//! Contains definitions from `lualib.h`.
use std::os::raw::c_int;
use std::os::raw::{c_char, c_int};
use super::lua::lua_State;
pub const LUA_COLIBNAME: &str = "coroutine";
pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_IOLIBNAME: &str = "io";
pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_UTF8LIBNAME: &str = "utf8";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_LOADLIBNAME: &str = "package";
pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine");
pub const LUA_TABLIBNAME: *const c_char = cstr!("table");
pub const LUA_IOLIBNAME: *const c_char = cstr!("io");
pub const LUA_OSLIBNAME: *const c_char = cstr!("os");
pub const LUA_STRLIBNAME: *const c_char = cstr!("string");
pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8");
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_LOADLIBNAME: *const c_char = cstr!("package");
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
+67 -20
View File
@@ -1,4 +1,4 @@
//! MLua compatibility layer for Roblox Luau.
//! MLua compatibility layer for Luau.
//!
//! Based on github.com/keplerproject/lua-compat-5.3
@@ -10,6 +10,8 @@ use super::lauxlib::*;
use super::lua::*;
use super::luacode::*;
pub const LUA_RESUMEERROR: c_int = -1;
unsafe fn compat53_reverse(L: *mut lua_State, mut a: c_int, mut b: c_int) {
while a < b {
lua_pushvalue(L, a);
@@ -41,7 +43,7 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) ->
} else if compat53_findfield(L, objidx, level - 1) != 0 {
// try recursively
lua_remove(L, -2); // remove table (but keep name)
lua_pushliteral(L, ".");
lua_pushliteral(L, c".");
lua_insert(L, -2); // place '.' between the two names
lua_concat(L, 3);
return 1;
@@ -75,7 +77,7 @@ unsafe fn compat53_pushfuncname(L: *mut lua_State, level: c_int, ar: *mut lua_De
lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1));
lua_remove(L, -2); // remove name
} else {
lua_pushliteral(L, "?");
lua_pushliteral(L, c"?");
}
}
@@ -125,6 +127,11 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int {
0
}
#[inline(always)]
pub unsafe fn lua_pushinteger(L: *mut lua_State, i: lua_Integer) {
lua_pushnumber(L, i as lua_Number);
}
#[inline(always)]
pub unsafe fn lua_tointeger(L: *mut lua_State, i: c_int) -> lua_Integer {
lua_tointegerx(L, i, ptr::null_mut())
@@ -176,6 +183,7 @@ pub unsafe fn lua_geti(L: *mut lua_State, mut idx: c_int, n: lua_Integer) -> c_i
#[inline(always)]
pub unsafe fn lua_rawgeti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_int {
let n = n.try_into().expect("cannot convert index from lua_Integer");
lua_rawgeti_(L, idx, n)
}
@@ -190,7 +198,7 @@ pub unsafe fn lua_rawgetp(L: *mut lua_State, idx: c_int, p: *const c_void) -> c_
pub unsafe fn lua_getuservalue(L: *mut lua_State, mut idx: c_int) -> c_int {
luaL_checkstack(L, 2, cstr!("not enough stack slots available"));
idx = lua_absindex(L, idx);
lua_pushliteral(L, "__mlua_uservalues");
lua_pushliteral(L, c"__mlua_uservalues");
if lua_rawget(L, LUA_REGISTRYINDEX) != LUA_TTABLE {
return LUA_TNIL;
}
@@ -211,6 +219,7 @@ pub unsafe fn lua_seti(L: *mut lua_State, mut idx: c_int, n: lua_Integer) {
#[inline(always)]
pub unsafe fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer) {
let n = n.try_into().expect("cannot convert index from lua_Integer");
lua_rawseti_(L, idx, n)
}
@@ -227,13 +236,13 @@ pub unsafe fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void) {
pub unsafe fn lua_setuservalue(L: *mut lua_State, mut idx: c_int) {
luaL_checkstack(L, 4, cstr!("not enough stack slots available"));
idx = lua_absindex(L, idx);
lua_pushliteral(L, "__mlua_uservalues");
lua_pushliteral(L, c"__mlua_uservalues");
lua_pushvalue(L, -1);
if lua_rawget(L, LUA_REGISTRYINDEX) != LUA_TTABLE {
lua_pop(L, 1);
lua_createtable(L, 0, 2); // main table
lua_createtable(L, 0, 1); // metatable
lua_pushliteral(L, "k");
lua_pushliteral(L, c"k");
lua_setfield(L, -2, cstr!("__mode"));
lua_setmetatable(L, -2);
lua_pushvalue(L, -2);
@@ -284,6 +293,19 @@ pub unsafe fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int, n
ret
}
#[inline(always)]
pub unsafe fn lua_resumex(L: *mut lua_State, from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int {
let ret = if narg == LUA_RESUMEERROR {
lua_resumeerror(L, from)
} else {
lua_resume_(L, from, narg)
};
if (ret == LUA_OK || ret == LUA_YIELD) && !(nres.is_null()) {
*nres = lua_gettop(L);
}
ret
}
//
// lauxlib ported functions
//
@@ -294,12 +316,30 @@ pub unsafe fn luaL_checkstack(L: *mut lua_State, sz: c_int, msg: *const c_char)
if !msg.is_null() {
luaL_error(L, cstr!("stack overflow (%s)"), msg);
} else {
lua_pushliteral(L, "stack overflow");
lua_pushliteral(L, c"stack overflow");
lua_error(L);
}
}
}
#[inline(always)]
pub unsafe fn luaL_checkinteger(L: *mut lua_State, narg: c_int) -> lua_Integer {
let mut isnum = 0;
let int = lua_tointegerx(L, narg, &mut isnum);
if isnum == 0 {
luaL_typeerror(L, narg, lua_typename(L, LUA_TNUMBER));
}
int
}
pub unsafe fn luaL_optinteger(L: *mut lua_State, narg: c_int, def: lua_Integer) -> lua_Integer {
if lua_isnoneornil(L, narg) != 0 {
def
} else {
luaL_checkinteger(L, narg)
}
}
#[inline(always)]
pub unsafe fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int {
if luaL_getmetafield_(L, obj, e) != 0 {
@@ -328,11 +368,11 @@ pub unsafe fn luaL_loadbufferenv(
mode: *const c_char,
mut env: c_int,
) -> c_int {
extern "C" {
unsafe extern "C" {
fn free(p: *mut c_void);
}
unsafe extern "C-unwind" fn data_dtor(data: *mut c_void) {
unsafe extern "C" fn data_dtor(_: *mut lua_State, data: *mut c_void) {
free(*(data as *mut *mut c_char) as *mut c_void);
}
@@ -348,7 +388,7 @@ pub unsafe fn luaL_loadbufferenv(
}
}
if chunk_is_text {
let status = if chunk_is_text {
if env < 0 {
env -= 1;
}
@@ -357,14 +397,21 @@ pub unsafe fn luaL_loadbufferenv(
ptr::write(data_ud, data);
// By deferring the `free(data)` to the userdata destructor, we ensure that
// even if `luau_load` throws an error, the `data` is still released.
let ok = luau_load(L, name, data, size, env) == 0;
let status = luau_load(L, name, data, size, env);
lua_replace(L, -2); // replace data with the result
if !ok {
return LUA_ERRSYNTAX;
status
} else {
luau_load(L, name, data, size, env)
};
if status != 0 {
if lua_isstring(L, -1) != 0 && CStr::from_ptr(lua_tostring(L, -1)) == c"not enough memory" {
// A case for Luau >= 0.679
return LUA_ERRMEM;
}
} else if luau_load(L, name, data, size, env) != 0 {
return LUA_ERRSYNTAX;
}
LUA_OK
}
@@ -415,11 +462,11 @@ pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const
if !msg.is_null() {
lua_pushfstring(L, cstr!("%s\n"), msg);
}
lua_pushliteral(L, "stack traceback:");
lua_pushliteral(L, c"stack traceback:");
while lua_getinfo(L1, level, cstr!(""), &mut ar) != 0 {
if level + 1 == mark {
// too many levels?
lua_pushliteral(L, "\n\t..."); // add a '...'
lua_pushliteral(L, c"\n\t..."); // add a '...'
level = numlevels - COMPAT53_LEVELS2; // and skip to last ones
} else {
lua_getinfo(L1, level, cstr!("sln"), &mut ar);
@@ -427,7 +474,7 @@ pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const
if ar.currentline > 0 {
lua_pushfstring(L, cstr!("%d:"), ar.currentline);
}
lua_pushliteral(L, " in ");
lua_pushliteral(L, c" in ");
compat53_pushfuncname(L, level, &mut ar);
lua_concat(L, lua_gettop(L) - top);
}
@@ -441,16 +488,16 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize)
if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 {
match lua_type(L, idx) {
LUA_TNIL => {
lua_pushliteral(L, "nil");
lua_pushliteral(L, c"nil");
}
LUA_TSTRING | LUA_TNUMBER => {
lua_pushvalue(L, idx);
}
LUA_TBOOLEAN => {
if lua_toboolean(L, idx) == 0 {
lua_pushliteral(L, "false");
lua_pushliteral(L, c"false");
} else {
lua_pushliteral(L, "true");
lua_pushliteral(L, c"true");
}
}
t => {
+16 -7
View File
@@ -3,7 +3,7 @@
use std::os::raw::{c_char, c_float, c_int, c_void};
use std::ptr;
use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State, lua_Unsigned, LUA_REGISTRYINDEX};
use super::lua::{self, lua_CFunction, lua_Number, lua_State, lua_Unsigned, LUA_REGISTRYINDEX};
#[repr(C)]
pub struct luaL_Reg {
@@ -11,7 +11,7 @@ pub struct luaL_Reg {
pub func: lua_CFunction,
}
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_register(L: *mut lua_State, libname: *const c_char, l: *const luaL_Reg);
#[link_name = "luaL_getmetafield"]
pub fn luaL_getmetafield_(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
@@ -33,8 +33,10 @@ extern "C-unwind" {
pub fn luaL_checkboolean(L: *mut lua_State, narg: c_int) -> c_int;
pub fn luaL_optboolean(L: *mut lua_State, narg: c_int, def: c_int) -> c_int;
pub fn luaL_checkinteger(L: *mut lua_State, narg: c_int) -> lua_Integer;
pub fn luaL_optinteger(L: *mut lua_State, narg: c_int, def: lua_Integer) -> lua_Integer;
#[link_name = "luaL_checkinteger"]
pub fn luaL_checkinteger_(L: *mut lua_State, narg: c_int) -> c_int;
#[link_name = "luaL_optinteger"]
pub fn luaL_optinteger_(L: *mut lua_State, narg: c_int, def: c_int) -> c_int;
pub fn luaL_checkunsigned(L: *mut lua_State, narg: c_int) -> lua_Unsigned;
pub fn luaL_optunsigned(L: *mut lua_State, narg: c_int, def: lua_Unsigned) -> lua_Unsigned;
@@ -69,10 +71,17 @@ extern "C-unwind" {
pub fn luaL_newstate() -> *mut lua_State;
// TODO: luaL_findtable
pub fn luaL_findtable(
L: *mut lua_State,
idx: c_int,
fname: *const c_char,
szhint: c_int,
) -> *const c_char;
pub fn luaL_typename(L: *mut lua_State, idx: c_int) -> *const c_char;
pub fn luaL_callyieldable(L: *mut lua_State, nargs: c_int, nresults: c_int) -> c_int;
// sandbox libraries and globals
#[link_name = "luaL_sandbox"]
pub fn luaL_sandbox_(L: *mut lua_State);
@@ -141,7 +150,7 @@ pub unsafe fn luaL_sandbox(L: *mut lua_State, enabled: c_int) {
}
// set all builtin metatables to read-only
lua_pushliteral(L, "");
lua_pushliteral(L, c"");
if lua_getmetatable(L, -1) != 0 {
lua_setreadonly(L, -1, enabled);
lua_pop(L, 2);
@@ -173,7 +182,7 @@ pub struct luaL_Strbuf {
// For compatibility
pub type luaL_Buffer = luaL_Strbuf;
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Strbuf);
pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Strbuf, size: usize) -> *mut c_char;
pub fn luaL_prepbuffsize(B: *mut luaL_Strbuf, size: usize) -> *mut c_char;
+36 -32
View File
@@ -1,5 +1,6 @@
//! Contains definitions from `lua.h`.
use std::ffi::CStr;
use std::marker::{PhantomData, PhantomPinned};
use std::os::raw::{c_char, c_double, c_float, c_int, c_uint, c_void};
use std::{mem, ptr};
@@ -11,10 +12,10 @@ pub const LUA_MULTRET: c_int = -1;
const LUAI_MAXCSTACK: c_int = 1000000;
// Number of valid Lua userdata tags
const LUA_UTAG_LIMIT: c_int = 128;
pub const LUA_UTAG_LIMIT: c_int = 128;
// Number of valid Lua lightuserdata tags
const LUA_LUTAG_LIMIT: c_int = 128;
pub const LUA_LUTAG_LIMIT: c_int = 128;
//
// Pseudo-indices
@@ -69,8 +70,11 @@ pub const LUA_MINSTACK: c_int = 20;
/// A Lua number, usually equivalent to `f64`.
pub type lua_Number = c_double;
/// A Lua integer, equivalent to `i32`.
pub type lua_Integer = c_int;
/// A Lua integer, usually equivalent to `i64`
#[cfg(target_pointer_width = "32")]
pub type lua_Integer = i32;
#[cfg(target_pointer_width = "64")]
pub type lua_Integer = i64;
/// A Lua unsigned integer, equivalent to `u32`.
pub type lua_Unsigned = c_uint;
@@ -79,20 +83,19 @@ pub type lua_Unsigned = c_uint;
pub type lua_CFunction = unsafe extern "C-unwind" fn(L: *mut lua_State) -> c_int;
pub type lua_Continuation = unsafe extern "C-unwind" fn(L: *mut lua_State, status: c_int) -> c_int;
/// Type for userdata destructor functions.
pub type lua_Udestructor = unsafe extern "C-unwind" fn(*mut c_void);
pub type lua_Destructor = unsafe extern "C-unwind" fn(L: *mut lua_State, *mut c_void);
/// Type for userdata destructor functions (no unwinding).
pub type lua_Destructor = unsafe extern "C" fn(L: *mut lua_State, *mut c_void);
/// Type for memory-allocation functions.
/// Type for memory-allocation functions (no unwinding).
pub type lua_Alloc =
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
/// Returns Luau release version (eg. `0.xxx`).
pub const fn luau_version() -> Option<&'static str> {
option_env!("LUAU_VERSION")
}
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// State manipulation
//
@@ -136,7 +139,7 @@ extern "C-unwind" {
pub fn lua_tonumberx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Number;
#[link_name = "lua_tointegerx"]
pub fn lua_tointegerx_(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Integer;
pub fn lua_tointegerx_(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> c_int;
pub fn lua_tounsignedx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Unsigned;
pub fn lua_tovector(L: *mut lua_State, idx: c_int) -> *const c_float;
pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int;
@@ -160,7 +163,8 @@ extern "C-unwind" {
//
pub fn lua_pushnil(L: *mut lua_State);
pub fn lua_pushnumber(L: *mut lua_State, n: lua_Number);
pub fn lua_pushinteger(L: *mut lua_State, n: lua_Integer);
#[link_name = "lua_pushinteger"]
pub fn lua_pushinteger_(L: *mut lua_State, n: c_int);
pub fn lua_pushunsigned(L: *mut lua_State, n: lua_Unsigned);
#[cfg(not(feature = "luau-vector4"))]
pub fn lua_pushvector(L: *mut lua_State, x: c_float, y: c_float, z: c_float);
@@ -186,7 +190,7 @@ extern "C-unwind" {
pub fn lua_pushlightuserdatatagged(L: *mut lua_State, p: *mut c_void, tag: c_int);
pub fn lua_newuserdatatagged(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void;
pub fn lua_newuserdatataggedwithmetatable(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void;
pub fn lua_newuserdatadtor(L: *mut lua_State, sz: usize, dtor: lua_Udestructor) -> *mut c_void;
pub fn lua_newuserdatadtor(L: *mut lua_State, sz: usize, dtor: lua_Destructor) -> *mut c_void;
pub fn lua_newbuffer(L: *mut lua_State, sz: usize) -> *mut c_void;
@@ -260,14 +264,14 @@ pub const LUA_GCSETGOAL: c_int = 7;
pub const LUA_GCSETSTEPMUL: c_int = 8;
pub const LUA_GCSETSTEPSIZE: c_int = 9;
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
//
// Memory statistics
//
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_setmemcat(L: *mut lua_State, category: c_int);
pub fn lua_totalbytes(L: *mut lua_State, category: c_int) -> usize;
}
@@ -275,7 +279,7 @@ extern "C-unwind" {
//
// Miscellaneous functions
//
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_error(L: *mut lua_State) -> !;
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_rawiter(L: *mut lua_State, idx: c_int, iter: c_int) -> c_int;
@@ -285,7 +289,7 @@ extern "C-unwind" {
pub fn lua_setuserdatatag(L: *mut lua_State, idx: c_int, tag: c_int);
pub fn lua_setuserdatadtor(L: *mut lua_State, tag: c_int, dtor: Option<lua_Destructor>);
pub fn lua_getuserdatadtor(L: *mut lua_State, tag: c_int) -> Option<lua_Destructor>;
pub fn lua_setuserdatametatable(L: *mut lua_State, tag: c_int, idx: c_int);
pub fn lua_setuserdatametatable(L: *mut lua_State, tag: c_int);
pub fn lua_getuserdatametatable(L: *mut lua_State, tag: c_int);
pub fn lua_setlightuserdataname(L: *mut lua_State, tag: c_int, name: *const c_char);
pub fn lua_getlightuserdataname(L: *mut lua_State, tag: c_int) -> *const c_char;
@@ -300,7 +304,7 @@ extern "C-unwind" {
pub const LUA_NOREF: c_int = -1;
pub const LUA_REFNIL: c_int = 0;
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_ref(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_unref(L: *mut lua_State, r#ref: c_int);
}
@@ -310,13 +314,13 @@ extern "C-unwind" {
//
#[inline(always)]
pub unsafe fn lua_tonumber(L: *mut lua_State, i: c_int) -> lua_Number {
lua_tonumberx(L, i, ptr::null_mut())
pub unsafe fn lua_tonumber(L: *mut lua_State, idx: c_int) -> lua_Number {
lua_tonumberx(L, idx, ptr::null_mut())
}
#[inline(always)]
pub unsafe fn lua_tointeger_(L: *mut lua_State, i: c_int) -> lua_Integer {
lua_tointegerx_(L, i, ptr::null_mut())
pub unsafe fn lua_tointeger_(L: *mut lua_State, idx: c_int) -> c_int {
lua_tointegerx_(L, idx, ptr::null_mut())
}
#[inline(always)]
@@ -340,12 +344,14 @@ pub unsafe fn lua_newuserdata(L: *mut lua_State, sz: usize) -> *mut c_void {
}
#[inline(always)]
pub unsafe fn lua_newuserdata_t<T>(L: *mut lua_State) -> *mut T {
unsafe extern "C-unwind" fn destructor<T>(ud: *mut c_void) {
pub unsafe fn lua_newuserdata_t<T>(L: *mut lua_State, data: T) -> *mut T {
unsafe extern "C" fn destructor<T>(_: *mut lua_State, ud: *mut c_void) {
ptr::drop_in_place(ud as *mut T);
}
lua_newuserdatadtor(L, mem::size_of::<T>(), destructor::<T>) as *mut T
let ud_ptr = lua_newuserdatadtor(L, const { mem::size_of::<T>() }, destructor::<T>) as *mut T;
ptr::write(ud_ptr, data);
ud_ptr
}
// TODO: lua_strlen
@@ -401,10 +407,8 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int {
}
#[inline(always)]
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) {
use std::ffi::CString;
let c_str = CString::new(s).unwrap();
lua_pushlstring_(L, c_str.as_ptr(), c_str.as_bytes().len())
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
lua_pushstring_(L, s.as_ptr());
}
#[inline(always)]
@@ -466,7 +470,7 @@ pub type lua_Coverage = unsafe extern "C-unwind" fn(
size: usize,
);
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_stackdepth(L: *mut lua_State) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, level: c_int, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getargument(L: *mut lua_State, level: c_int, n: c_int) -> c_int;
@@ -532,12 +536,12 @@ pub struct lua_Callbacks {
pub onallocate: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, osize: usize, nsize: usize)>,
}
extern "C" {
unsafe extern "C" {
pub fn lua_callbacks(L: *mut lua_State) -> *mut lua_Callbacks;
}
// Functions from customization lib
extern "C" {
unsafe extern "C" {
pub fn luau_setfflag(name: *const c_char, value: c_int) -> c_int;
pub fn lua_getmetatablepointer(L: *mut lua_State, idx: c_int) -> *const c_void;
}
+53 -2
View File
@@ -1,5 +1,6 @@
//! Contains definitions from `luacode.h`.
use std::marker::{PhantomData, PhantomPinned};
use std::os::raw::{c_char, c_int, c_void};
use std::{ptr, slice};
@@ -15,6 +16,10 @@ pub struct lua_CompileOptions {
pub vectorType: *const c_char,
pub mutableGlobals: *const *const c_char,
pub userdataTypes: *const *const c_char,
pub librariesWithKnownMembers: *const *const c_char,
pub libraryMemberTypeCallback: Option<lua_LibraryMemberTypeCallback>,
pub libraryMemberConstantCallback: Option<lua_LibraryMemberConstantCallback>,
pub disabledBuiltins: *const *const c_char,
}
impl Default for lua_CompileOptions {
@@ -29,11 +34,57 @@ impl Default for lua_CompileOptions {
vectorType: ptr::null(),
mutableGlobals: ptr::null(),
userdataTypes: ptr::null(),
librariesWithKnownMembers: ptr::null(),
libraryMemberTypeCallback: None,
libraryMemberConstantCallback: None,
disabledBuiltins: ptr::null(),
}
}
}
extern "C-unwind" {
#[repr(C)]
pub struct lua_CompileConstant {
_data: [u8; 0],
_marker: PhantomData<(*mut u8, PhantomPinned)>,
}
/// Type table tags
#[doc(hidden)]
#[repr(i32)]
#[non_exhaustive]
pub enum luau_BytecodeType {
Nil = 0,
Boolean,
Number,
String,
Table,
Function,
Thread,
UserData,
Vector,
Buffer,
Any = 15,
}
pub type lua_LibraryMemberTypeCallback =
unsafe extern "C-unwind" fn(library: *const c_char, member: *const c_char) -> c_int;
pub type lua_LibraryMemberConstantCallback = unsafe extern "C-unwind" fn(
library: *const c_char,
member: *const c_char,
constant: *mut lua_CompileConstant,
);
unsafe extern "C" {
pub fn luau_set_compile_constant_nil(cons: *mut lua_CompileConstant);
pub fn luau_set_compile_constant_boolean(cons: *mut lua_CompileConstant, b: c_int);
pub fn luau_set_compile_constant_number(cons: *mut lua_CompileConstant, n: f64);
pub fn luau_set_compile_constant_vector(cons: *mut lua_CompileConstant, x: f32, y: f32, z: f32, w: f32);
pub fn luau_set_compile_constant_string(cons: *mut lua_CompileConstant, s: *const c_char, l: usize);
}
unsafe extern "C-unwind" {
#[link_name = "luau_compile"]
pub fn luau_compile_(
source: *const c_char,
@@ -43,7 +94,7 @@ extern "C-unwind" {
) -> *mut c_char;
}
extern "C" {
unsafe extern "C" {
fn free(p: *mut c_void);
}
+1 -1
View File
@@ -4,7 +4,7 @@ use std::os::raw::c_int;
use super::lua::lua_State;
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luau_codegen_supported() -> c_int;
pub fn luau_codegen_create(state: *mut lua_State);
pub fn luau_codegen_compile(state: *mut lua_State, idx: c_int);
+12 -12
View File
@@ -1,21 +1,21 @@
//! Contains definitions from `lualib.h`.
use std::os::raw::c_int;
use std::os::raw::{c_char, c_int};
use super::lua::lua_State;
pub const LUA_COLIBNAME: &str = "coroutine";
pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_BITLIBNAME: &str = "bit32";
pub const LUA_BUFFERLIBNAME: &str = "buffer";
pub const LUA_UTF8LIBNAME: &str = "utf8";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_VECLIBNAME: &str = "vector";
pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine");
pub const LUA_TABLIBNAME: *const c_char = cstr!("table");
pub const LUA_OSLIBNAME: *const c_char = cstr!("os");
pub const LUA_STRLIBNAME: *const c_char = cstr!("string");
pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32");
pub const LUA_BUFFERLIBNAME: *const c_char = cstr!("buffer");
pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8");
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_VECLIBNAME: *const c_char = cstr!("vector");
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
+177
View File
@@ -0,0 +1,177 @@
//! Contains definitions from `Require.h`.
use std::os::raw::{c_char, c_int, c_void};
use super::lua::lua_State;
pub const LUA_REGISTERED_MODULES_TABLE: *const c_char = cstr!("_REGISTEREDMODULES");
#[repr(C)]
pub enum luarequire_NavigateResult {
Success,
Ambiguous,
NotFound,
}
// Functions returning WriteSuccess are expected to set their size_out argument
// to the number of bytes written to the buffer. If WriteBufferTooSmall is
// returned, size_out should be set to the required buffer size.
#[repr(C)]
pub enum luarequire_WriteResult {
Success,
BufferTooSmall,
Failure,
}
#[repr(C)]
pub struct luarequire_Configuration {
// Returns whether requires are permitted from the given chunkname.
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-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
requirer_chunkname: *const c_char,
) -> luarequire_NavigateResult,
// 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-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-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-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> bool,
// 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,
buffer_size: usize,
size_out: *mut usize,
) -> luarequire_WriteResult,
// 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,
buffer_size: usize,
size_out: *mut usize,
) -> luarequire_WriteResult,
// 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-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> luarequire_WriteResult,
// 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-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> bool,
// 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.
// Returning -1 directs the requiring thread to yield. In this case, this thread should be resumed with
// the module result pushed onto its stack.
pub load: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
path: *const c_char,
chunkname: *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-unwind" fn(config: *mut luarequire_Configuration);
unsafe extern "C-unwind" {
// Initializes and pushes the require closure onto the stack without registration.
pub fn luarequire_pushrequire(
L: *mut lua_State,
config_init: luarequire_Configuration_init,
ctx: *mut c_void,
) -> c_int;
// Initializes the require library and registers it globally.
pub fn luaopen_require(L: *mut lua_State, config_init: luarequire_Configuration_init, ctx: *mut c_void);
// Initializes and pushes a "proxyrequire" closure onto the stack.
//
// The closure takes two parameters: the string path to resolve and the chunkname of an existing
// module.
pub fn luarequire_pushproxyrequire(
L: *mut lua_State,
config_init: luarequire_Configuration_init,
ctx: *mut c_void,
) -> c_int;
// Registers an aliased require path to a result.
//
// After registration, the given result will always be immediately returned when the given path is
// required.
// Expects the path and table to be passed as arguments on the stack.
pub fn luarequire_registermodule(L: *mut lua_State) -> c_int;
// Clears the entry associated with the given cache key from the require cache.
// Expects the cache key to be passed as an argument on the stack.
pub fn luarequire_clearcacheentry(L: *mut lua_State) -> c_int;
// Clears all entries from the require cache.
pub fn luarequire_clearcache(L: *mut lua_State) -> c_int;
}
+2
View File
@@ -6,6 +6,7 @@ pub use lua::*;
pub use luacode::*;
pub use luacodegen::*;
pub use lualib::*;
pub use luarequire::*;
pub mod compat;
pub mod lauxlib;
@@ -13,3 +14,4 @@ pub mod lua;
pub mod luacode;
pub mod luacodegen;
pub mod lualib;
pub mod luarequire;
+3 -3
View File
@@ -1,10 +1,10 @@
[package]
name = "mlua_derive"
version = "0.10.1"
version = "0.11.0-beta.2"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2021"
description = "Procedural macros for the mlua crate."
repository = "https://github.com/khvzak/mlua"
repository = "https://github.com/mlua-rs/mlua"
keywords = ["lua", "mlua"]
license = "MIT"
@@ -19,6 +19,6 @@ quote = "1.0"
proc-macro2 = { version = "1.0", features = ["span-locations"] }
proc-macro-error2 = { version = "2.0.1", optional = true }
syn = { version = "2.0", features = ["full"] }
itertools = { version = "0.13", optional = true }
itertools = { version = "0.14", optional = true }
regex = { version = "1.4", optional = true }
once_cell = { version = "1.0", optional = true }
+2 -2
View File
@@ -103,7 +103,7 @@ pub fn chunk(input: TokenStream) -> TokenStream {
struct InnerChunk<F: FnOnce(&Lua) -> Result<Table>>(Cell<Option<F>>);
impl<F> AsChunk<'static> for InnerChunk<F>
impl<F> AsChunk for InnerChunk<F>
where
F: FnOnce(&Lua) -> Result<Table>,
{
@@ -120,7 +120,7 @@ pub fn chunk(input: TokenStream) -> TokenStream {
Some(ChunkMode::Text)
}
fn source(self) -> IoResult<Cow<'static, [u8]>> {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed((#source).as_bytes()))
}
}
+2 -3
View File
@@ -1,4 +1,4 @@
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
use serde::ser::{Serialize, Serializer};
use crate::types::ValueRef;
@@ -25,7 +25,6 @@ impl Buffer {
}
/// Returns `true` if the buffer is empty.
#[doc(hidden)]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
@@ -73,7 +72,7 @@ impl Buffer {
}
}
#[cfg(feature = "serialize")]
#[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() })
+213 -67
View File
@@ -2,7 +2,6 @@ use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::CString;
use std::io::Result as IoResult;
use std::marker::PhantomData;
use std::panic::Location;
use std::path::{Path, PathBuf};
use std::string::String as StdString;
@@ -17,7 +16,7 @@ use crate::value::Value;
/// Trait for types [loadable by Lua] and convertible to a [`Chunk`]
///
/// [loadable by Lua]: https://www.lua.org/manual/5.4/manual.html#3.3.2
pub trait AsChunk<'a> {
pub trait AsChunk {
/// Returns optional chunk name
///
/// See [`Chunk::set_name`] for possible name prefixes.
@@ -39,65 +38,100 @@ pub trait AsChunk<'a> {
}
/// Returns chunk data (can be text or binary)
fn source(self) -> IoResult<Cow<'a, [u8]>>;
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a;
}
impl<'a> AsChunk<'a> for &'a str {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self.as_ref()))
}
}
impl AsChunk<'static> for StdString {
fn source(self) -> IoResult<Cow<'static, [u8]>> {
Ok(Cow::Owned(self.into_bytes()))
}
}
impl<'a> AsChunk<'a> for &'a StdString {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
impl AsChunk for &str {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
Ok(Cow::Borrowed(self.as_bytes()))
}
}
impl<'a> AsChunk<'a> for &'a [u8] {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
impl AsChunk for StdString {
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]>>
where
Self: 'a,
{
Ok(Cow::Borrowed(self.as_bytes()))
}
}
impl AsChunk for &[u8] {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
Ok(Cow::Borrowed(self))
}
}
impl AsChunk<'static> for Vec<u8> {
fn source(self) -> IoResult<Cow<'static, [u8]>> {
Ok(Cow::Owned(self))
impl AsChunk for Vec<u8> {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Owned(self.clone()))
}
}
impl<'a> AsChunk<'a> for &'a Vec<u8> {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self.as_ref()))
impl AsChunk for &Vec<u8> {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
Ok(Cow::Borrowed(self))
}
}
impl AsChunk<'static> for &Path {
impl AsChunk for &Path {
fn name(&self) -> Option<StdString> {
Some(format!("@{}", self.display()))
}
fn source(self) -> IoResult<Cow<'static, [u8]>> {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
std::fs::read(self).map(Cow::Owned)
}
}
impl AsChunk<'static> for PathBuf {
impl AsChunk for PathBuf {
fn name(&self) -> Option<StdString> {
Some(format!("@{}", self.display()))
}
fn source(self) -> IoResult<Cow<'static, [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> {
@@ -117,6 +151,28 @@ pub enum ChunkMode {
Binary,
}
/// Represents a constant value that can be used by Luau compiler.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[derive(Clone, Debug)]
pub enum CompileConstant {
Nil,
Boolean(bool),
Number(crate::Number),
Vector(crate::Vector),
String(String),
}
#[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))]
type LibraryMemberConstantMap = std::sync::Arc<HashMap<(String, String), CompileConstant>>;
/// Luau compiler
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
@@ -131,6 +187,9 @@ pub struct Compiler {
vector_type: Option<String>,
mutable_globals: Vec<String>,
userdata_types: Vec<String>,
libraries_with_known_members: Vec<String>,
library_constants: Option<LibraryMemberConstantMap>,
disabled_builtins: Vec<String>,
}
#[cfg(any(feature = "luau", doc))]
@@ -155,6 +214,9 @@ impl Compiler {
vector_type: None,
mutable_globals: Vec::new(),
userdata_types: Vec::new(),
libraries_with_known_members: Vec::new(),
library_constants: None,
disabled_builtins: Vec::new(),
}
}
@@ -187,6 +249,7 @@ impl Compiler {
/// Possible values:
/// * 0 - generate for native modules (default)
/// * 1 - generate for all modules
#[must_use]
pub const fn set_type_info_level(mut self, level: u8) -> Self {
self.type_info_level = level;
self
@@ -229,15 +292,46 @@ impl Compiler {
///
/// It disables the import optimization for fields accessed through these.
#[must_use]
pub fn set_mutable_globals(mut self, globals: Vec<String>) -> Self {
self.mutable_globals = globals;
pub fn set_mutable_globals<S: Into<String>>(mut self, globals: Vec<S>) -> Self {
self.mutable_globals = globals.into_iter().map(|s| s.into()).collect();
self
}
/// Sets a list of userdata types that will be included in the type information.
#[must_use]
pub fn set_userdata_types(mut self, types: Vec<String>) -> Self {
self.userdata_types = types;
pub fn set_userdata_types<S: Into<String>>(mut self, types: Vec<S>) -> Self {
self.userdata_types = types.into_iter().map(|s| s.into()).collect();
self
}
/// Sets constants for known library members.
///
/// 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.
#[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();
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 {
self.disabled_builtins = builtins.into_iter().map(|s| s.into()).collect();
self
}
@@ -245,7 +339,9 @@ impl Compiler {
///
/// Returns [`Error::SyntaxError`] if the source code is invalid.
pub fn compile(&self, source: impl AsRef<[u8]>) -> Result<Vec<u8>> {
use std::os::raw::c_int;
use std::cell::RefCell;
use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
use std::ptr;
let vector_lib = self.vector_lib.clone();
@@ -277,6 +373,44 @@ impl Compiler {
vec2cstring_ptr!(mutable_globals, mutable_globals_ptr);
vec2cstring_ptr!(userdata_types, userdata_types_ptr);
vec2cstring_ptr!(libraries_with_known_members, libraries_with_known_members_ptr);
vec2cstring_ptr!(disabled_builtins, disabled_builtins_ptr);
thread_local! {
static LIBRARY_MEMBER_CONSTANT_MAP: RefCell<LibraryMemberConstantMap> = Default::default();
}
#[cfg(feature = "luau")]
unsafe extern "C-unwind" fn library_member_constant_callback(
library: *const c_char,
member: *const c_char,
constant: *mut ffi::lua_CompileConstant,
) {
let library = CStr::from_ptr(library).to_string_lossy();
let member = CStr::from_ptr(member).to_string_lossy();
LIBRARY_MEMBER_CONSTANT_MAP.with_borrow(|map| {
if let Some(cons) = map.get(&(library.to_string(), member.to_string())) {
match cons {
CompileConstant::Nil => ffi::luau_set_compile_constant_nil(constant),
CompileConstant::Boolean(b) => {
ffi::luau_set_compile_constant_boolean(constant, *b as c_int)
}
CompileConstant::Number(n) => ffi::luau_set_compile_constant_number(constant, *n),
CompileConstant::Vector(v) => {
#[cfg(not(feature = "luau-vector4"))]
ffi::luau_set_compile_constant_vector(constant, v.x(), v.y(), v.z(), 0.0);
#[cfg(feature = "luau-vector4")]
ffi::luau_set_compile_constant_vector(constant, v.x(), v.y(), v.z(), v.w());
}
CompileConstant::String(s) => ffi::luau_set_compile_constant_string(
constant,
s.as_ptr() as *const c_char,
s.len(),
),
}
}
})
}
let bytecode = unsafe {
let mut options = ffi::lua_CompileOptions::default();
@@ -289,6 +423,14 @@ impl Compiler {
options.vectorType = vector_type.map_or(ptr::null(), |s| s.as_ptr());
options.mutableGlobals = mutable_globals_ptr;
options.userdataTypes = userdata_types_ptr;
options.librariesWithKnownMembers = libraries_with_known_members_ptr;
if let Some(map) = self.library_constants.as_ref() {
if !self.libraries_with_known_members.is_empty() {
LIBRARY_MEMBER_CONSTANT_MAP.with_borrow_mut(|gmap| *gmap = map.clone());
options.libraryMemberConstantCallback = Some(library_member_constant_callback);
}
}
options.disabledBuiltins = disabled_builtins_ptr;
ffi::luau_compile(source.as_ref(), options)
};
@@ -307,6 +449,11 @@ impl Compiler {
}
impl Chunk<'_> {
/// Returns the name of this chunk.
pub fn name(&self) -> &str {
&self.name
}
/// Sets the name of this chunk, which results in more informative error traces.
///
/// Possible name prefixes:
@@ -318,6 +465,11 @@ impl Chunk<'_> {
self
}
/// Returns the environment of this chunk.
pub fn environment(&self) -> Option<&Table> {
self.env.as_ref().ok()?.as_ref()
}
/// Sets the environment of the loaded chunk to the given value.
///
/// In Lua >=5.2 main chunks always have exactly one upvalue, and this upvalue is used as the
@@ -334,6 +486,11 @@ impl Chunk<'_> {
self
}
/// Returns the mode (auto-detected by default) of this chunk.
pub fn mode(&self) -> ChunkMode {
self.detect_mode()
}
/// Sets whether the chunk is text or binary (autodetected by default).
///
/// Be aware, Lua does not check the consistency of the code inside binary chunks.
@@ -346,8 +503,6 @@ impl Chunk<'_> {
/// Sets or overwrites a Luau compiler used for this chunk.
///
/// See [`Compiler`] for details and possible options.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_compiler(mut self, compiler: Compiler) -> Self {
@@ -366,8 +521,6 @@ impl Chunk<'_> {
///
/// See [`exec`] for more details.
///
/// Requires `feature = "async"`
///
/// [`exec`]: Chunk::exec
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
@@ -398,8 +551,6 @@ impl Chunk<'_> {
///
/// See [`eval`] for more details.
///
/// Requires `feature = "async"`
///
/// [`eval`]: Chunk::eval
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
@@ -427,8 +578,6 @@ impl Chunk<'_> {
///
/// See [`call`] for more details.
///
/// Requires `feature = "async"`
///
/// [`call`]: Chunk::call
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
@@ -488,7 +637,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);
@@ -505,12 +654,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>() {
cache.0.insert(text_source, binary_source.as_ref().to_vec());
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.as_ref().to_vec());
let _ = lua.try_set_app_data(cache);
cache.0.insert(text_source, binary_source.to_vec());
lua.set_priv_app_data(cache);
};
}
}
@@ -543,21 +692,20 @@ impl Chunk<'_> {
}
fn detect_mode(&self) -> ChunkMode {
match (self.mode, &self.source) {
(Some(mode), _) => mode,
(None, Ok(source)) => {
#[cfg(not(feature = "luau"))]
if source.starts_with(ffi::LUA_SIGNATURE) {
return ChunkMode::Binary;
}
#[cfg(feature = "luau")]
if *source.first().unwrap_or(&u8::MAX) < b'\n' {
return ChunkMode::Binary;
}
ChunkMode::Text
}
(None, Err(_)) => ChunkMode::Text, // any value is fine
if let Some(mode) = self.mode {
return mode;
}
if let Ok(source) = &self.source {
#[cfg(not(feature = "luau"))]
if source.starts_with(ffi::LUA_SIGNATURE) {
return ChunkMode::Binary;
}
#[cfg(feature = "luau")]
if *source.first().unwrap_or(&u8::MAX) < b'\n' {
return ChunkMode::Binary;
}
}
ChunkMode::Text
}
fn convert_name(name: String) -> Result<CString> {
@@ -572,29 +720,27 @@ impl Chunk<'_> {
}
}
struct WrappedChunk<'a, T: AsChunk<'a>> {
struct WrappedChunk<T: AsChunk> {
chunk: T,
caller: &'static Location<'static>,
_marker: PhantomData<&'a T>,
}
impl<'a> Chunk<'a> {
impl Chunk<'_> {
/// Wraps a chunk of Lua code, returning an opaque type that implements [`IntoLua`] trait.
///
/// The resulted `IntoLua` implementation will convert the chunk into a Lua function without
/// executing it.
#[doc(hidden)]
#[track_caller]
pub fn wrap(chunk: impl AsChunk<'a> + 'a) -> impl IntoLua + 'a {
pub fn wrap(chunk: impl AsChunk) -> impl IntoLua {
WrappedChunk {
chunk,
caller: Location::caller(),
_marker: PhantomData,
}
}
}
impl<'a, T: AsChunk<'a>> IntoLua for WrappedChunk<'a, T> {
impl<T: AsChunk> IntoLua for WrappedChunk<T> {
fn into_lua(self, lua: &Lua) -> Result<Value> {
lua.load_with_location(self.chunk, self.caller)
.into_function()
+140 -33
View File
@@ -5,7 +5,7 @@ use std::hash::{BuildHasher, Hash};
use std::os::raw::c_int;
use std::path::{Path, PathBuf};
use std::string::String as StdString;
use std::{slice, str};
use std::{mem, slice, str};
use bstr::{BStr, BString, ByteSlice, ByteVec};
use num_traits::cast;
@@ -13,7 +13,7 @@ use num_traits::cast;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::{Lua, RawLua};
use crate::string::String;
use crate::string::{BorrowedBytes, BorrowedStr, String};
use crate::table::Table;
use crate::thread::Thread;
use crate::traits::{FromLua, IntoLua, ShortTypeName as _};
@@ -91,6 +91,94 @@ impl FromLua for String {
}
}
impl IntoLua for BorrowedStr<'_> {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.into_owned()))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
Ok(())
}
}
impl IntoLua for &BorrowedStr<'_> {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.clone().into_owned()))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
Ok(())
}
}
impl FromLua for BorrowedStr<'_> {
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let s = String::from_lua(value, lua)?;
let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?;
let buf = unsafe { mem::transmute::<&str, &'static str>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let s = String::from_stack(idx, lua)?;
let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?;
let buf = unsafe { mem::transmute::<&str, &'static str>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
}
}
impl IntoLua for BorrowedBytes<'_> {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.into_owned()))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
Ok(())
}
}
impl IntoLua for &BorrowedBytes<'_> {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.clone().into_owned()))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
Ok(())
}
}
impl FromLua for BorrowedBytes<'_> {
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let s = String::from_lua(value, lua)?;
let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s);
let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let s = String::from_stack(idx, lua)?;
let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s);
let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
}
}
impl IntoLua for Table {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
@@ -655,6 +743,51 @@ impl IntoLua for &Path {
}
}
impl IntoLua for char {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
let mut char_bytes = [0; 4];
self.encode_utf8(&mut char_bytes);
Ok(Value::String(lua.create_string(&char_bytes[..self.len_utf8()])?))
}
}
impl FromLua for char {
fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
let ty = value.type_name();
match value {
Value::Integer(i) => {
cast(i)
.and_then(char::from_u32)
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: "char".to_string(),
message: Some("integer out of range when converting to char".to_string()),
})
}
Value::String(s) => {
let str = s.to_str()?;
let mut str_iter = str.chars();
match (str_iter.next(), str_iter.next()) {
(Some(char), None) => Ok(char),
_ => Err(Error::FromLuaConversionError {
from: ty,
to: "char".to_string(),
message: Some(
"expected string to have exactly one char when converting to char".to_string(),
),
}),
}
}
_ => Err(Error::FromLuaConversionError {
from: ty,
to: Self::type_name(),
message: Some("expected string or integer".to_string()),
}),
}
}
}
#[inline]
unsafe fn push_bytes_into_stack<T>(this: T, lua: &RawLua) -> Result<()>
where
@@ -675,15 +808,9 @@ macro_rules! lua_convert_int {
impl IntoLua for $x {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
cast(self)
Ok(cast(self)
.map(Value::Integer)
.or_else(|| cast(self).map(Value::Number))
// This is impossible error because conversion to Number never fails
.ok_or_else(|| Error::ToLuaConversionError {
from: stringify!($x).to_string(),
to: "number",
message: Some("out of range".to_owned()),
})
.unwrap_or_else(|| Value::Number(self as ffi::lua_Number)))
}
#[inline]
@@ -766,13 +893,7 @@ macro_rules! lua_convert_float {
impl IntoLua for $x {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
cast(self)
.ok_or_else(|| Error::ToLuaConversionError {
from: stringify!($x).to_string(),
to: "number",
message: Some("out of range".to_string()),
})
.map(Value::Number)
Ok(Value::Number(self as _))
}
}
@@ -781,33 +902,19 @@ macro_rules! lua_convert_float {
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let ty = value.type_name();
lua.coerce_number(value)?
.map(|n| n as $x)
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: stringify!($x).to_string(),
message: Some("expected number or string coercible to number".to_string()),
})
.and_then(|n| {
cast(n).ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: stringify!($x).to_string(),
message: Some("number out of range".to_string()),
})
})
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let state = lua.state();
let type_id = ffi::lua_type(state, idx);
if type_id == ffi::LUA_TNUMBER {
let mut ok = 0;
let i = ffi::lua_tonumberx(state, idx, &mut ok);
if ok != 0 {
return cast(i).ok_or_else(|| Error::FromLuaConversionError {
from: "number",
to: stringify!($x).to_string(),
message: Some("out of range".to_owned()),
});
}
return Ok(ffi::lua_tonumber(state, idx) as _);
}
// Fallback to default
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
+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())
+53 -24
View File
@@ -14,9 +14,12 @@ use crate::value::Value;
#[cfg(feature = "async")]
use {
crate::thread::AsyncThread,
crate::traits::LuaNativeAsyncFn,
crate::types::AsyncCallback,
std::future::{self, Future},
std::pin::Pin,
std::task::{Context, Poll},
};
/// Handle to an internal Lua function.
@@ -128,9 +131,8 @@ impl Function {
/// Returns a future that, when polled, calls `self`, passing `args` as function arguments,
/// and drives the execution.
///
/// Internally it wraps the function to an [`AsyncThread`].
///
/// Requires `feature = "async"`
/// Internally it wraps the function to an [`AsyncThread`]. The returned type implements
/// `Future<Output = Result<R>>` and can be awaited.
///
/// # Examples
///
@@ -146,7 +148,7 @@ impl Function {
/// Ok(())
/// })?;
///
/// sleep.call_async(10).await?;
/// sleep.call_async::<()>(10).await?;
///
/// # Ok(())
/// # }
@@ -155,19 +157,18 @@ impl Function {
/// [`AsyncThread`]: crate::AsyncThread
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn call_async<R>(&self, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
pub fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where
R: FromLuaMulti,
{
let lua = self.0.lua.lock();
let thread_res = unsafe {
lua.create_recycled_thread(self).map(|th| {
let mut th = th.into_async(args);
AsyncCallFuture(unsafe {
lua.create_recycled_thread(self).and_then(|th| {
let mut th = th.into_async(args)?;
th.set_recyclable(true);
th
Ok(th)
})
};
async move { thread_res?.await }
})
}
/// Returns a function that, when called, calls `self`, passing `args` as the first set of
@@ -252,7 +253,7 @@ impl Function {
"#,
)
.try_cache()
.set_name("__mlua_bind")
.set_name("=__mlua_bind")
.call((self, args_wrapper))
}
@@ -280,7 +281,7 @@ impl Function {
// Traverse upvalues until we find the _ENV one
match ffi::lua_getupvalue(state, -1, i) {
s if s.is_null() => break,
s if std::ffi::CStr::from_ptr(s as _).to_bytes() == b"_ENV" => break,
s if std::ffi::CStr::from_ptr(s as _) == c"_ENV" => break,
_ => ffi::lua_pop(state, 1),
}
}
@@ -319,7 +320,7 @@ impl Function {
for i in 1..=255 {
match ffi::lua_getupvalue(state, -1, i) {
s if s.is_null() => return Ok(false),
s if std::ffi::CStr::from_ptr(s as _).to_bytes() == b"_ENV" => {
s if std::ffi::CStr::from_ptr(s as _) == c"_ENV" => {
ffi::lua_pop(state, 1);
// Create an anonymous function with the new environment
let f_with_env = lua
@@ -430,8 +431,6 @@ impl Function {
///
/// Recording of coverage information is controlled by [`Compiler::set_coverage_level`] option.
///
/// Requires `feature = "luau"`
///
/// [`Compiler::set_coverage_level`]: crate::chunk::Compiler::set_coverage_level
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
@@ -491,20 +490,26 @@ impl Function {
/// Copies the function prototype and all its upvalues to the
/// newly created function.
/// This function returns shallow clone (same handle) for Rust/C functions.
///
/// Requires `feature = "luau"`
#[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()))
}
}
}
@@ -644,6 +649,27 @@ impl LuaType for Function {
const TYPE_ID: c_int = ffi::LUA_TFUNCTION;
}
#[cfg(feature = "async")]
#[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> Future for AsyncCallFuture<R> {
type Output = Result<R>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// Safety: We're not moving any pinned data
let this = unsafe { self.get_unchecked_mut() };
match &mut this.0 {
Ok(thread) => {
let pinned_thread = unsafe { Pin::new_unchecked(thread) };
pinned_thread.poll(cx)
}
Err(err) => Poll::Ready(Err(err.clone())),
}
}
}
#[cfg(test)]
mod assertions {
use super::*;
@@ -652,4 +678,7 @@ mod assertions {
static_assertions::assert_not_impl_any!(Function: Send);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(Function: Send, Sync);
#[cfg(all(feature = "async", feature = "send"))]
static_assertions::assert_impl_all!(AsyncCallFuture<()>: Send);
}
+10 -6
View File
@@ -86,7 +86,7 @@ impl<'a> Debug<'a> {
}
/// Corresponds to the `n` what mask.
pub fn names(&self) -> DebugNames {
pub fn names(&self) -> DebugNames<'_> {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
@@ -113,7 +113,7 @@ impl<'a> Debug<'a> {
}
/// Corresponds to the `S` what mask.
pub fn source(&self) -> DebugSource {
pub fn source(&self) -> DebugSource<'_> {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
@@ -265,14 +265,18 @@ pub struct DebugStack {
/// Number of upvalues.
pub num_ups: u8,
/// Number of parameters.
///
/// Requires `feature = "lua54/lua53/lua52/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau")))
)]
pub num_params: u8,
/// Whether the function is a vararg function.
///
/// Requires `feature = "lua54/lua53/lua52/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau")))
)]
pub is_vararg: bool,
}
+17 -9
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
//!
@@ -66,6 +66,8 @@
// warnings at all.
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(not(send), allow(clippy::arc_with_non_send_sync))]
#![allow(clippy::ptr_eq)]
#![allow(unsafe_op_in_unsafe_fn)]
#[macro_use]
mod macros;
@@ -76,7 +78,7 @@ mod conversion;
mod error;
mod function;
mod hook;
#[cfg(feature = "luau")]
#[cfg(any(feature = "luau", doc))]
mod luau;
mod memory;
mod multi;
@@ -104,7 +106,7 @@ 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};
pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua};
pub use crate::stdlib::StdLib;
pub use crate::string::{BorrowedBytes, BorrowedStr, String};
pub use crate::table::{Table, TablePairs, TableSequence};
@@ -126,18 +128,24 @@ pub use crate::hook::HookTriggers;
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub use crate::{buffer::Buffer, chunk::Compiler, function::CoverageInfo, vector::Vector};
pub use crate::{
buffer::Buffer,
chunk::{CompileConstant, Compiler},
function::CoverageInfo,
luau::{NavigateError, Require, TextRequirer},
vector::Vector,
};
#[cfg(feature = "async")]
#[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")]
@@ -209,7 +217,7 @@ pub use mlua_derive::FromLua;
///
/// You can register multiple entrypoints as required.
///
/// ```
/// ```ignore
/// use mlua::{Lua, Result, Table};
///
/// #[mlua::lua_module]
@@ -246,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;
+42 -13
View File
@@ -1,16 +1,31 @@
use std::ffi::CStr;
use std::os::raw::c_int;
use std::ptr;
use crate::chunk::ChunkMode;
use crate::error::Result;
use crate::state::Lua;
use crate::function::Function;
use crate::state::{callback_error_ext, ExtraData, Lua};
use crate::traits::{FromLuaMulti, IntoLua};
pub use require::{NavigateError, Require, TextRequirer};
// Since Luau has some missing standard functions, we re-implement them here
impl Lua {
/// Create a custom Luau `require` function using provided [`Require`] implementation to find
/// 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> {
require::create_require_function(self, require)
}
pub(crate) unsafe fn configure_luau(&self) -> Result<()> {
let globals = self.globals();
globals.raw_set("collectgarbage", self.create_c_function(lua_collectgarbage)?)?;
globals.raw_set("loadstring", self.create_c_function(lua_loadstring)?)?;
// Set `_VERSION` global to include version number
// The environment variable `LUAU_VERSION` set by the build script
@@ -18,11 +33,10 @@ impl Lua {
globals.raw_set("_VERSION", format!("Luau {version}"))?;
}
Ok(())
}
// Enable default `require` implementation
let require = self.create_require_function(require::TextRequirer::new())?;
self.globals().raw_set("require", require)?;
pub(crate) fn disable_c_modules(&self) -> Result<()> {
package::disable_dylibs(self);
Ok(())
}
}
@@ -31,16 +45,17 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
let option = ffi::luaL_optstring(state, 1, cstr!("collect"));
let option = CStr::from_ptr(option);
let arg = ffi::luaL_optinteger(state, 2, 0);
let is_sandboxed = (*ExtraData::get(state)).sandboxed;
match option.to_str() {
Ok("collect") => {
Ok("collect") if !is_sandboxed => {
ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0);
0
}
Ok("stop") => {
Ok("stop") if !is_sandboxed => {
ffi::lua_gc(state, ffi::LUA_GCSTOP, 0);
0
}
Ok("restart") => {
Ok("restart") if !is_sandboxed => {
ffi::lua_gc(state, ffi::LUA_GCRESTART, 0);
0
}
@@ -50,12 +65,12 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
ffi::lua_pushnumber(state, kbytes + kbytes_rem / 1024.0);
1
}
Ok("step") => {
let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg);
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
@@ -64,6 +79,20 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
}
}
pub(crate) use package::register_package_module;
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 package;
mod require;
-271
View File
@@ -1,271 +0,0 @@
use std::ffi::CStr;
use std::fmt::Write;
use std::os::raw::c_int;
use std::path::{PathBuf, MAIN_SEPARATOR_STR};
use std::string::String as StdString;
use std::{env, fs};
use crate::chunk::ChunkMode;
use crate::error::Result;
use crate::state::Lua;
use crate::table::Table;
use crate::traits::IntoLua;
use crate::value::Value;
#[cfg(unix)]
use {libloading::Library, rustc_hash::FxHashMap};
//
// Luau package module
//
#[cfg(unix)]
const TARGET_MLUA_LUAU_ABI_VERSION: u32 = 2;
#[cfg(all(unix, feature = "module"))]
#[no_mangle]
#[used]
pub static MLUA_LUAU_ABI_VERSION: u32 = TARGET_MLUA_LUAU_ABI_VERSION;
// We keep reference to the loaded dylibs in application data
#[cfg(unix)]
struct LoadedDylibs(FxHashMap<PathBuf, Library>);
#[cfg(unix)]
impl std::ops::Deref for LoadedDylibs {
type Target = FxHashMap<PathBuf, Library>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(unix)]
impl std::ops::DerefMut for LoadedDylibs {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub(crate) fn register_package_module(lua: &Lua) -> Result<()> {
// Create the package table
let package = lua.create_table()?;
// Set `package.path`
let mut search_path = env::var("LUAU_PATH")
.or_else(|_| env::var("LUA_PATH"))
.unwrap_or_default();
if search_path.is_empty() {
search_path = "?.luau;?.lua".to_string();
}
package.raw_set("path", search_path)?;
// Set `package.cpath`
#[cfg(unix)]
{
let mut search_cpath = env::var("LUAU_CPATH")
.or_else(|_| env::var("LUA_CPATH"))
.unwrap_or_default();
if search_cpath.is_empty() {
if cfg!(any(target_os = "macos", target_os = "ios")) {
search_cpath = "?.dylib".to_string();
} else {
search_cpath = "?.so".to_string();
}
}
package.raw_set("cpath", search_cpath)?;
}
// Set `package.loaded` (table with a list of loaded modules)
let loaded = if let Ok(Some(loaded)) = lua.named_registry_value::<Option<Table>>("_LOADED") {
package.raw_set("loaded", &loaded)?;
loaded
} else {
let loaded = lua.create_table()?;
package.raw_set("loaded", &loaded)?;
lua.set_named_registry_value("_LOADED", &loaded)?;
loaded
};
// Set `package.loaders`
let loaders = lua.create_sequence_from([lua.create_function(lua_loader)?])?;
package.raw_set("loaders", &loaders)?;
#[cfg(unix)]
{
loaders.push(lua.create_function(dylib_loader)?)?;
lua.set_app_data(LoadedDylibs(FxHashMap::default()));
}
lua.set_named_registry_value("_LOADERS", loaders)?;
// Register the module and `require` function in globals
let globals = lua.globals();
globals.raw_set("package", &package)?;
loaded.raw_set("package", package)?;
globals.raw_set("require", unsafe { lua.create_c_function(lua_require)? })?;
Ok(())
}
#[allow(unused_variables)]
pub(crate) fn disable_dylibs(lua: &Lua) {
// Presence of `LoadedDylibs` in app data is used as a flag
// to check whether binary modules are enabled
#[cfg(unix)]
lua.remove_app_data::<LoadedDylibs>();
}
unsafe extern "C-unwind" fn lua_require(state: *mut ffi::lua_State) -> c_int {
ffi::lua_settop(state, 1);
let name = ffi::luaL_checkstring(state, 1);
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); // _LOADED is at index 2
if ffi::lua_rawgetfield(state, 2, name) != ffi::LUA_TNIL {
return 1; // module is already loaded
}
ffi::lua_pop(state, 1); // remove nil
// load the module
let err_buf = ffi::lua_newuserdata_t::<StdString>(state);
err_buf.write(StdString::new());
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADERS")); // _LOADERS is at index 3
for i in 1.. {
if ffi::lua_rawgeti(state, -1, i) == ffi::LUA_TNIL {
// no more loaders?
if (*err_buf).is_empty() {
ffi::luaL_error(state, cstr!("module '%s' not found"), name);
} else {
let bytes = (*err_buf).as_bytes();
let extra = ffi::lua_pushlstring(state, bytes.as_ptr() as *const _, bytes.len());
ffi::luaL_error(state, cstr!("module '%s' not found:%s"), name, extra);
}
}
ffi::lua_pushvalue(state, 1); // name arg
ffi::lua_call(state, 1, 2); // call loader
match ffi::lua_type(state, -2) {
ffi::LUA_TFUNCTION => break, // loader found
ffi::LUA_TSTRING => {
// error message
let msg = ffi::lua_tostring(state, -2);
let msg = CStr::from_ptr(msg).to_string_lossy();
_ = write!(&mut *err_buf, "\n\t{msg}");
}
_ => {}
}
ffi::lua_pop(state, 2); // remove both results
}
ffi::lua_pushvalue(state, 1); // name is 1st argument to module loader
ffi::lua_rotate(state, -2, 1); // loader data <-> name
// stack: ...; loader function; module name; loader data
ffi::lua_call(state, 2, 1);
// stack: ...; result from loader function
if ffi::lua_isnil(state, -1) != 0 {
ffi::lua_pop(state, 1);
ffi::lua_pushboolean(state, 1); // use true as result
}
ffi::lua_pushvalue(state, -1); // make copy of entrypoint result
ffi::lua_setfield(state, 2, name); /* _LOADED[name] = returned value */
1
}
/// Searches for the given `name` in the given `path`.
///
/// `path` is a string containing a sequence of templates separated by semicolons.
fn package_searchpath(name: &str, search_path: &str, try_prefix: bool) -> Option<PathBuf> {
let mut names = vec![name.replace('.', MAIN_SEPARATOR_STR)];
if try_prefix && name.contains('.') {
let prefix = name.split_once('.').map(|(prefix, _)| prefix).unwrap();
names.push(prefix.to_string());
}
for path in search_path.split(';') {
for name in &names {
let file_path = PathBuf::from(path.replace('?', name));
if let Ok(true) = fs::metadata(&file_path).map(|m| m.is_file()) {
return Some(file_path);
}
}
}
None
}
//
// Module loaders
//
/// Tries to load a lua (text) file
fn lua_loader(lua: &Lua, modname: StdString) -> Result<Value> {
let package = {
let loaded = lua.named_registry_value::<Table>("_LOADED")?;
loaded.raw_get::<Table>("package")
}?;
let search_path = package.get::<StdString>("path").unwrap_or_default();
if let Some(file_path) = package_searchpath(&modname, &search_path, false) {
match fs::read(&file_path) {
Ok(buf) => {
return lua
.load(buf)
.set_name(format!("={}", file_path.display()))
.set_mode(ChunkMode::Text)
.into_function()
.map(Value::Function);
}
Err(err) => {
return format!("cannot open '{}': {err}", file_path.display()).into_lua(lua);
}
}
}
Ok(Value::Nil)
}
/// Tries to load a dynamic library
#[cfg(unix)]
fn dylib_loader(lua: &Lua, modname: StdString) -> Result<Value> {
let package = {
let loaded = lua.named_registry_value::<Table>("_LOADED")?;
loaded.raw_get::<Table>("package")
}?;
let search_cpath = package.get::<StdString>("cpath").unwrap_or_default();
let find_symbol = |lib: &Library| unsafe {
if let Ok(entry) = lib.get::<ffi::lua_CFunction>(format!("luaopen_{modname}\0").as_bytes()) {
return lua.create_c_function(*entry).map(Value::Function);
}
// Try all in one mode
if let Ok(entry) =
lib.get::<ffi::lua_CFunction>(format!("luaopen_{}\0", modname.replace('.', "_")).as_bytes())
{
return lua.create_c_function(*entry).map(Value::Function);
}
"cannot find module entrypoint".into_lua(lua)
};
if let Some(file_path) = package_searchpath(&modname, &search_cpath, true) {
let file_path = file_path.canonicalize()?;
// Load the library and check for symbol
unsafe {
let mut loaded_dylibs = match lua.app_data_mut::<LoadedDylibs>() {
Some(loaded_dylibs) => loaded_dylibs,
None => return "dynamic libraries are disabled in safe mode".into_lua(lua),
};
// Check if it's already loaded
if let Some(lib) = loaded_dylibs.get(&file_path) {
return find_symbol(lib);
}
if let Ok(lib) = Library::new(&file_path) {
// Check version
let mod_version = lib.get::<*const u32>(b"MLUA_LUAU_ABI_VERSION");
let mod_version = mod_version.map(|v| **v).unwrap_or_default();
if mod_version != TARGET_MLUA_LUAU_ABI_VERSION {
let err = format!("wrong module ABI version (expected {TARGET_MLUA_LUAU_ABI_VERSION}, got {mod_version})");
return err.into_lua(lua);
}
let symbol = find_symbol(&lib);
loaded_dylibs.insert(file_path, lib);
return symbol;
}
}
}
Ok(Value::Nil)
}
+647
View File
@@ -0,0 +1,647 @@
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::{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.
#[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) -> Result<ffi::luarequire_NavigateResult>;
}
#[cfg(feature = "luau")]
impl IntoNavigateResult for StdResult<(), NavigateError> {
fn into_nav_result(self) -> Result<ffi::luarequire_NavigateResult> {
match self {
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.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub trait Require: MaybeSend {
/// 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(&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(&mut self, path: &str) -> StdResult<(), NavigateError>;
// Navigate to parent directory
fn to_parent(&mut self) -> StdResult<(), NavigateError>;
/// Navigate to the given child directory.
fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError>;
/// Returns whether the context is currently pointing at a module
fn has_module(&self) -> bool;
/// Provides a cache key representing the current module.
///
/// This function is only called if `has_module` returns true.
fn cache_key(&self) -> String;
/// 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 `has_config` returns true.
fn config(&self) -> IoResult<Vec<u8>>;
/// Returns a loader function for the current module, that when called, loads the module
/// and returns the result.
///
/// Loader can be sync or async.
/// This function is only called if `has_module` returns true.
fn loader(&self, lua: &Lua) -> Result<Function>;
}
impl fmt::Debug for dyn Require {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<dyn Require>")
}
}
/// The standard implementation of Luau `require` navigation.
#[doc(hidden)]
#[derive(Default, Debug)]
pub struct TextRequirer {
abs_path: PathBuf,
rel_path: PathBuf,
module_path: PathBuf,
}
impl TextRequirer {
/// 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 line.parse::<u32>().is_ok() {
return path;
}
}
chunk_name
}
// Normalizes the path by removing unnecessary components
fn normalize_path(path: &Path) -> PathBuf {
let mut components = VecDeque::new();
for comp in path.components() {
match comp {
Component::Prefix(..) | Component::RootDir => {
components.push_back(comp);
}
Component::CurDir => {}
Component::ParentDir => {
if matches!(components.back(), None | Some(Component::ParentDir)) {
components.push_back(Component::ParentDir);
} else if matches!(components.back(), Some(Component::Normal(..))) {
components.pop_back();
}
}
Component::Normal(..) => components.push_back(comp),
}
}
if matches!(components.front(), None | Some(Component::Normal(..))) {
components.push_front(Component::CurDir);
}
// Join the components back together
components.into_iter().collect()
}
fn find_module(path: &Path) -> StdResult<PathBuf, NavigateError> {
let mut found_path = None;
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 ["luau", "lua"] {
let candidate = path.with_extension(format!("{current_ext}{ext}"));
if candidate.is_file() && found_path.replace(candidate).is_some() {
return Err(NavigateError::Ambiguous);
}
}
}
if path.is_dir() {
for component in ["init.luau", "init.lua"] {
let candidate = path.join(component);
if candidate.is_file() && found_path.replace(candidate).is_some() {
return Err(NavigateError::Ambiguous);
}
}
if found_path.is_none() {
found_path = Some(PathBuf::new());
}
}
found_path.ok_or(NavigateError::NotFound)
}
}
impl Require for TextRequirer {
fn is_require_allowed(&self, chunk_name: &str) -> bool {
chunk_name.starts_with('@')
}
fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError> {
if !chunk_name.starts_with('@') {
return Err(NavigateError::NotFound);
}
let chunk_name = Self::normalize_chunk_name(&chunk_name[1..]);
let chunk_path = Self::normalize_path(chunk_name.as_ref());
if chunk_path.extension() == Some("rs".as_ref()) {
// Special case for Rust source files, reset to the current directory
let chunk_filename = chunk_path.file_name().unwrap();
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
self.abs_path = Self::normalize_path(&cwd.join(chunk_filename));
self.rel_path = ([Component::CurDir, Component::Normal(chunk_filename)].into_iter()).collect();
self.module_path = PathBuf::new();
return Ok(());
}
if chunk_path.is_absolute() {
let module_path = Self::find_module(&chunk_path)?;
self.abs_path = chunk_path.clone();
self.rel_path = chunk_path;
self.module_path = module_path;
} else {
// Relative path
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
let abs_path = Self::normalize_path(&cwd.join(&chunk_path));
let module_path = Self::find_module(&abs_path)?;
self.abs_path = abs_path;
self.rel_path = chunk_path;
self.module_path = module_path;
}
Ok(())
}
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)?;
self.abs_path = path.clone();
self.rel_path = path;
self.module_path = module_path;
Ok(())
}
fn to_parent(&mut self) -> StdResult<(), NavigateError> {
let mut abs_path = self.abs_path.clone();
if !abs_path.pop() {
return Err(NavigateError::NotFound);
}
let mut rel_parent = self.rel_path.clone();
rel_parent.pop();
let module_path = Self::find_module(&abs_path)?;
self.abs_path = abs_path;
self.rel_path = Self::normalize_path(&rel_parent);
self.module_path = module_path;
Ok(())
}
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 module_path = Self::find_module(&abs_path)?;
self.abs_path = abs_path;
self.rel_path = rel_path;
self.module_path = module_path;
Ok(())
}
fn has_module(&self) -> bool {
self.module_path.is_file()
}
fn cache_key(&self) -> String {
self.module_path.display().to_string()
}
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.join(".luaurc"))
}
fn loader(&self, lua: &Lua) -> Result<Function> {
let name = format!("@{}", self.rel_path.display());
lua.load(&*self.module_path).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-unwind" fn init_config(config: *mut ffi::luarequire_Configuration) {
if config.is_null() {
return;
}
unsafe extern "C-unwind" fn is_require_allowed(
state: *mut ffi::lua_State,
ctx: *mut c_void,
requirer_chunkname: *const c_char,
) -> bool {
if requirer_chunkname.is_null() {
return false;
}
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-unwind" fn reset(
state: *mut ffi::lua_State,
ctx: *mut c_void,
requirer_chunkname: *const c_char,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy();
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.reset(&chunk_name).into_nav_result()
})
}
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 = 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-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-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 = 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,
_chunkname: *const c_char,
_loadname: *const c_char,
) -> c_int {
let this = try_borrow!(state, ctx);
callback_error_ext(state, ptr::null_mut(), true, move |extra, _| {
let rawlua = (*extra).raw_lua();
let loader = this.loader(rawlua.lua())?;
rawlua.push(loader)?;
Ok(1)
})
}
(*config).is_require_allowed = is_require_allowed;
(*config).reset = reset;
(*config).jump_to_alias = jump_to_alias;
(*config).to_parent = to_parent;
(*config).to_child = to_child;
(*config).is_module_present = is_module_present;
(*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_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(
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
data: &[u8],
) -> WriteResult {
// 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;
}
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> {
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.. {
if ffi::lua_getinfo(state, level, cstr!("s"), &mut ar) == 0 {
ffi::luaL_error(state, cstr!("require is not supported in this context"));
}
if CStr::from_ptr(ar.what) != c"C" {
break;
}
}
ffi::lua_pushstring(state, ar.source);
1
}
unsafe extern "C-unwind" fn get_cache_key(state: *mut ffi::lua_State) -> c_int {
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 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, 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"));
})
}?;
unsafe extern "C-unwind" fn error(state: *mut ffi::lua_State) -> c_int {
ffi::luaL_where(state, 1);
ffi::lua_pushvalue(state, 1);
ffi::lua_concat(state, 2);
ffi::lua_error(state);
}
unsafe extern "C-unwind" fn r#type(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushstring(state, ffi::lua_typename(state, ffi::lua_type(state, 1)));
1
}
let (error, r#type) = unsafe {
lua.exec_raw::<(Function, Function)>((), move |state| {
ffi::lua_pushcfunctiond(state, error, cstr!("error"));
ffi::lua_pushcfunctiond(state, r#type, cstr!("type"));
})
}?;
// Prepare environment for the "require" function
let env = lua.create_table_with_capacity(0, 7)?;
env.raw_set("get_cache_key", get_cache_key)?;
env.raw_set("find_current_file", find_current_file)?;
env.raw_set("proxyrequire", proxyrequire)?;
env.raw_set("REGISTERED_MODULES", registered_modules)?;
env.raw_set("LOADER_CACHE", loader_cache)?;
env.raw_set("error", error)?;
env.raw_set("type", r#type)?;
lua.load(
r#"
local path = ...
if type(path) ~= "string" then
error("bad argument #1 to 'require' (string expected, got " .. type(path) .. ")")
end
-- Check if the module (path) is explicitly registered
local maybe_result = REGISTERED_MODULES[path]
if maybe_result ~= nil then
return maybe_result
end
local loader = proxyrequire(path, find_current_file())
local cache_key = get_cache_key()
-- Check if the loader result is already cached
local result = LOADER_CACHE[cache_key]
if result ~= nil then
return result
end
-- Call the loader function and cache the result
result = loader()
if result == nil then
result = true
end
LOADER_CACHE[cache_key] = result
return result
"#,
)
.try_cache()
.set_name("=__mlua_require")
.set_environment(env)
.into_function()
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::TextRequirer;
#[test]
fn test_path_normalize() {
for (input, expected) in [
// Basic formatting checks
("", "./"),
(".", "./"),
("a/relative/path", "./a/relative/path"),
// Paths containing extraneous '.' and '/' symbols
("./remove/extraneous/symbols/", "./remove/extraneous/symbols"),
("./remove/extraneous//symbols", "./remove/extraneous/symbols"),
("./remove/extraneous/symbols/.", "./remove/extraneous/symbols"),
("./remove/extraneous/./symbols", "./remove/extraneous/symbols"),
("../remove/extraneous/symbols/", "../remove/extraneous/symbols"),
("../remove/extraneous//symbols", "../remove/extraneous/symbols"),
("../remove/extraneous/symbols/.", "../remove/extraneous/symbols"),
("../remove/extraneous/./symbols", "../remove/extraneous/symbols"),
("/remove/extraneous/symbols/", "/remove/extraneous/symbols"),
("/remove/extraneous//symbols", "/remove/extraneous/symbols"),
("/remove/extraneous/symbols/.", "/remove/extraneous/symbols"),
("/remove/extraneous/./symbols", "/remove/extraneous/symbols"),
// Paths containing '..'
("./remove/me/..", "./remove"),
("./remove/me/../", "./remove"),
("../remove/me/..", "../remove"),
("../remove/me/../", "../remove"),
("/remove/me/..", "/remove"),
("/remove/me/../", "/remove"),
("./..", "../"),
("./../", "../"),
("../..", "../../"),
("../../", "../../"),
// '..' disappears if path is absolute and component is non-erasable
("/../", "/"),
] {
let path = TextRequirer::normalize_path(input.as_ref());
assert_eq!(
&path,
expected.as_ref() as &Path,
"wrong normalization for {input}"
);
}
}
}
+23 -6
View File
@@ -18,15 +18,32 @@ pub(crate) struct MemoryState {
}
impl MemoryState {
#[cfg(feature = "luau")]
#[inline]
pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
let mut mem_state = ptr::null_mut();
#[cfg(feature = "luau")]
{
ffi::lua_getallocf(state, &mut mem_state);
mlua_assert!(!mem_state.is_null(), "Luau state has no allocator userdata");
ffi::lua_getallocf(state, &mut mem_state);
mlua_assert!(!mem_state.is_null(), "Luau state has no allocator userdata");
mem_state as *mut MemoryState
}
#[cfg(not(feature = "luau"))]
#[rustversion::since(1.85)]
#[inline]
#[allow(clippy::incompatible_msrv)]
pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
let mut mem_state = ptr::null_mut();
if !ptr::fn_addr_eq(ffi::lua_getallocf(state, &mut mem_state), ALLOCATOR) {
mem_state = ptr::null_mut();
}
#[cfg(not(feature = "luau"))]
mem_state as *mut MemoryState
}
#[cfg(not(feature = "luau"))]
#[rustversion::before(1.85)]
#[inline]
pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
let mut mem_state = ptr::null_mut();
if ffi::lua_getallocf(state, &mut mem_state) != ALLOCATOR {
mem_state = ptr::null_mut();
}
@@ -80,7 +97,7 @@ impl MemoryState {
}
}
unsafe extern "C-unwind" fn allocator(
unsafe extern "C" fn allocator(
extra: *mut c_void,
ptr: *mut c_void,
osize: usize,
+3 -3
View File
@@ -126,8 +126,7 @@ impl MultiValue {
/// Creates a `MultiValue` container from vector of values.
///
/// This methods needs *O*(*n*) data movement if the circular buffer doesn't happen to be at the
/// beginning of the allocation.
/// This method works in *O*(1) time and does not allocate any additional memory.
#[inline]
pub fn from_vec(vec: Vec<Value>) -> MultiValue {
vec.into()
@@ -135,7 +134,8 @@ impl MultiValue {
/// Consumes the `MultiValue` and returns a vector of values.
///
/// This methods works in *O*(1) time and does not allocate any additional memory.
/// This method needs *O*(*n*) data movement if the circular buffer doesn't happen to be at the
/// beginning of the allocation.
#[inline]
pub fn into_vec(self) -> Vec<Value> {
self.into()
+16 -12
View File
@@ -2,18 +2,19 @@
#[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,
VmState as LuaVmState,
Variadic as LuaVariadic, VmState as LuaVmState, WeakLua,
};
#[cfg(not(feature = "luau"))]
@@ -22,13 +23,16 @@ pub use crate::HookTriggers as LuaHookTriggers;
#[cfg(feature = "luau")]
#[doc(no_inline)]
pub use crate::{CoverageInfo as LuaCoverageInfo, Vector as LuaVector};
pub use crate::{
CompileConstant as LuaCompileConstant, CoverageInfo as LuaCoverageInfo,
NavigateError as LuaNavigateError, Require as LuaRequire, 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,
+6 -12
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`.
@@ -168,7 +166,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
#[cfg(feature = "luau")]
let ud_ptr = {
let data = UserDataStorage::new_scoped(data);
util::push_userdata::<UserDataStorage<T>>(state, data, protect)?
util::push_userdata(state, data, protect)?
};
#[cfg(not(feature = "luau"))]
let ud_ptr = util::push_uninit_userdata::<UserDataStorage<T>>(state, protect)?;
@@ -216,7 +214,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
#[cfg(feature = "luau")]
let ud_ptr = {
let data = UserDataStorage::new_scoped(data);
util::push_userdata::<UserDataStorage<T>>(state, data, protect)?
util::push_userdata(state, data, protect)?
};
#[cfg(not(feature = "luau"))]
let ud_ptr = util::push_uninit_userdata::<UserDataStorage<T>>(state, protect)?;
@@ -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));
+18
View File
@@ -49,6 +49,11 @@ pub struct Options {
///
/// Default: **false**
pub sort_keys: bool,
/// If true, empty Lua tables will be encoded as array, instead of map.
///
/// Default: **false**
pub encode_empty_tables_as_array: bool,
}
impl Default for Options {
@@ -64,6 +69,7 @@ impl Options {
deny_unsupported_types: true,
deny_recursive_tables: true,
sort_keys: false,
encode_empty_tables_as_array: false,
}
}
@@ -93,6 +99,15 @@ impl Options {
self.sort_keys = enabled;
self
}
/// Sets [`encode_empty_tables_as_array`] option.
///
/// [`encode_empty_tables_as_array`]: #structfield.encode_empty_tables_as_array
#[must_use]
pub const fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self {
self.encode_empty_tables_as_array = enabled;
self
}
}
impl Deserializer {
@@ -141,6 +156,9 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
Err(_) => visitor.visit_bytes(&s.as_bytes()),
},
Value::Table(ref t) if t.raw_len() > 0 || t.is_array() => self.deserialize_seq(visitor),
Value::Table(ref t) if self.options.encode_empty_tables_as_array && t.is_empty() => {
self.deserialize_seq(visitor)
}
Value::Table(_) => self.deserialize_map(visitor),
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(),
Value::UserData(ud) if ud.is_serializable() => {
+1 -13
View File
@@ -13,12 +13,10 @@ 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.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
@@ -42,8 +40,6 @@ pub trait LuaSerdeExt: Sealed {
/// As result, encoded Array will contain only sequence part of the table, with the same length
/// as the `#` operator on that table.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
@@ -71,8 +67,6 @@ pub trait LuaSerdeExt: Sealed {
/// Converts `T` into a [`Value`] instance.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: crate::Value
///
/// # Example
@@ -104,8 +98,6 @@ pub trait LuaSerdeExt: Sealed {
/// Converts `T` into a [`Value`] instance with options.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
@@ -129,8 +121,6 @@ pub trait LuaSerdeExt: Sealed {
/// Deserializes a [`Value`] into any serde deserializable object.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
@@ -158,8 +148,6 @@ pub trait LuaSerdeExt: Sealed {
/// Deserializes a [`Value`] into any serde deserializable object with options.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
+316 -180
View File
@@ -2,7 +2,7 @@ use std::any::TypeId;
use std::cell::{BorrowError, BorrowMutError, RefCell};
use std::marker::PhantomData;
use std::ops::Deref;
use std::os::raw::c_int;
use std::os::raw::{c_char, c_int};
use std::panic::Location;
use std::result::Result as StdResult;
use std::{fmt, mem, ptr};
@@ -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;
use crate::{hook::HookTriggers, types::HookKind};
#[cfg(any(feature = "luau", doc))]
use crate::{buffer::Buffer, chunk::Compiler};
@@ -41,23 +39,25 @@ use {
std::future::{self, Future},
};
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
use serde::Serialize;
pub(crate) use extra::ExtraData;
pub use raw::RawLua;
use util::{callback_error_ext, StateGuard};
pub(crate) use util::callback_error_ext;
/// Top level Lua struct which represents an instance of Lua VM.
#[derive(Clone)]
pub struct Lua {
pub(self) raw: XRc<ReentrantMutex<RawLua>>,
// Controls whether garbage collection should be run on drop
pub(self) collect_garbage: bool,
}
/// Weak reference to Lua instance.
///
/// This can used to prevent circular references between Lua and Rust objects.
#[derive(Clone)]
pub(crate) struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
pub(crate) struct LuaGuard(ArcReentrantMutexGuard<RawLua>);
@@ -72,7 +72,6 @@ pub(crate) struct LuaGuard(ArcReentrantMutexGuard<RawLua>);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GCMode {
Incremental,
/// Requires `feature = "lua54"`
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
Generational,
@@ -98,9 +97,6 @@ pub struct LuaOptions {
/// Max size of thread (coroutine) object pool used to execute asynchronous functions.
///
/// It works on Lua 5.4 and Luau, where [`lua_resetthread`] function
/// is available and allows to reuse old coroutines after resetting their state.
///
/// Default: **0** (disabled)
///
/// [`lua_resetthread`]: https://www.lua.org/manual/5.4/manual.html#lua_resetthread
@@ -154,6 +150,16 @@ impl Drop for Lua {
}
}
impl Clone for Lua {
#[inline]
fn clone(&self) -> Self {
Lua {
raw: XRc::clone(&self.raw),
collect_garbage: false,
}
}
}
impl fmt::Debug for Lua {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Lua({:p})", self.lock().state())
@@ -215,6 +221,7 @@ impl Lua {
let lua = unsafe { Self::inner_new(libs, options) };
#[cfg(not(feature = "luau"))]
if libs.contains(StdLib::PACKAGE) {
mlua_expect!(lua.disable_c_modules(), "Error disabling C modules");
}
@@ -254,7 +261,7 @@ impl Lua {
/// Creates a new Lua state with required `libs` and `options`
unsafe fn inner_new(libs: StdLib, options: LuaOptions) -> Lua {
let lua = Lua {
raw: RawLua::new(libs, options),
raw: RawLua::new(libs, &options),
collect_garbage: true,
};
@@ -264,16 +271,23 @@ impl Lua {
lua
}
/// Constructs a new Lua instance from an existing raw state.
/// Returns or constructs Lua instance from a raw state.
///
/// Once called, a returned Lua state is cached in the registry and can be retrieved
/// Once initialized, the returned Lua instance is cached in the registry and can be retrieved
/// by calling this function again.
#[allow(clippy::missing_safety_doc)]
///
/// # Safety
/// The `Lua` must outlive the chosen lifetime `'a`.
#[inline]
pub unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> Lua {
Lua {
raw: RawLua::init_from_ptr(state, false),
collect_garbage: true,
pub unsafe fn get_or_init_from_ptr<'a>(state: *mut ffi::lua_State) -> &'a Lua {
debug_assert!(!state.is_null(), "Lua state is null");
match ExtraData::get(state) {
extra if !extra.is_null() => (*extra).lua(),
_ => {
// The `owned` flag is set to `false` as we don't own the Lua state.
RawLua::init_from_ptr(state, false);
(*ExtraData::get(state)).lua()
}
}
}
@@ -322,12 +336,6 @@ impl Lua {
R::from_stack_multi(nresults, &lua)
}
#[doc(hidden)]
#[deprecated(since = "0.10.0", note = "please use `load_std_libs` instead")]
pub fn load_from_std_lib(&self, libs: StdLib) -> Result<()> {
self.load_std_libs(libs)
}
/// Loads the specified subset of the standard libraries into an existing Lua state.
///
/// Use the [`StdLib`] flags to specify the libraries you want to load.
@@ -335,40 +343,78 @@ impl Lua {
unsafe { self.lock().load_std_libs(libs) }
}
/// Loads module `modname` into an existing Lua state using the specified entrypoint
/// function.
/// Registers module into an existing Lua state using the specified value.
///
/// Internally calls the Lua function `func` with the string `modname` as an argument,
/// sets the call result to `package.loaded[modname]` and returns copy of the result.
/// After registration, the given value will always be immediately returned when the
/// given module is [required].
///
/// If `package.loaded[modname]` value is not nil, returns copy of the value without
/// calling the function.
/// [required]: https://www.lua.org/manual/5.4/manual.html#pdf-require
pub fn register_module(&self, modname: &str, value: impl IntoLua) -> Result<()> {
#[cfg(not(feature = "luau"))]
const LOADED_MODULES_KEY: *const c_char = ffi::LUA_LOADED_TABLE;
#[cfg(feature = "luau")]
const LOADED_MODULES_KEY: *const c_char = ffi::LUA_REGISTERED_MODULES_TABLE;
if cfg!(feature = "luau") && !modname.starts_with('@') {
return Err(Error::runtime("module name must begin with '@'"));
}
unsafe {
self.exec_raw::<()>(value, |state| {
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, LOADED_MODULES_KEY);
ffi::lua_pushlstring(state, modname.as_ptr() as *const c_char, modname.len() as _);
ffi::lua_pushvalue(state, -3);
ffi::lua_rawset(state, -3);
})
}
}
/// Preloads module into an existing Lua state using the specified loader function.
///
/// If the function does not return a non-nil value then this method assigns true to
/// `package.loaded[modname]`.
/// When the module is required, the loader function will be called with module name as the
/// first argument.
///
/// Behavior is similar to Lua's [`require`] function.
/// This is similar to setting the [`package.preload[modname]`] field.
///
/// [`require`]: https://www.lua.org/manual/5.4/manual.html#pdf-require
pub fn load_from_function<T>(&self, modname: &str, func: Function) -> Result<T>
where
T: FromLua,
{
let lua = self.lock();
let state = lua.state();
/// [`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<()> {
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
let preload = unsafe {
self.exec_raw::<Option<Table>>((), |state| {
ffi::lua_getfield(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_PRELOAD_TABLE);
})?
};
#[cfg(any(feature = "lua51", feature = "luajit"))]
let preload = unsafe {
self.exec_raw::<Option<Table>>((), |state| {
if ffi::lua_getfield(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_LOADED_TABLE) != ffi::LUA_TNIL {
ffi::luaL_getsubtable(state, -1, ffi::LUA_LOADLIBNAME);
ffi::luaL_getsubtable(state, -1, cstr!("preload"));
ffi::lua_rotate(state, 1, 1);
}
})?
};
if let Some(preload) = preload {
preload.raw_set(modname, func)?;
}
Ok(())
}
#[doc(hidden)]
#[deprecated(since = "0.11.0", note = "Use `register_module` instead")]
#[cfg(not(feature = "luau"))]
#[cfg(not(tarpaulin_include))]
pub fn load_from_function<T: FromLua>(&self, modname: &str, func: Function) -> Result<T> {
let loaded = unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
protect_lua!(state, 0, 1, fn(state) {
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED"));
})?;
Table(lua.pop_ref())
self.exec_raw::<Table>((), |state| {
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_LOADED_TABLE);
})?
};
let modname = unsafe { lua.create_string(modname)? };
let value = match loaded.raw_get(&modname)? {
let value = match loaded.raw_get(modname)? {
Value::Nil => {
let result = match func.call(&modname)? {
let result = match func.call(modname)? {
Value::Nil => Value::Boolean(true),
res => res,
};
@@ -382,24 +428,14 @@ impl Lua {
/// Unloads module `modname`.
///
/// Removes module from the [`package.loaded`] table which allows to load it again.
/// It does not support unloading binary Lua modules since they are internally cached and can be
/// unloaded only by closing Lua state.
/// This method does not support unloading binary Lua modules since they are internally cached
/// and can be unloaded only by closing Lua state.
///
/// This is similar to calling [`Lua::register_module`] with `Nil` value.
///
/// [`package.loaded`]: https://www.lua.org/manual/5.4/manual.html#pdf-package.loaded
pub fn unload(&self, modname: &str) -> Result<()> {
let lua = self.lock();
let state = lua.state();
let loaded = unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
protect_lua!(state, 0, 1, fn(state) {
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED"));
})?;
Table(lua.pop_ref())
};
loaded.raw_set(modname, Nil)
pub fn unload_module(&self, modname: &str) -> Result<()> {
self.register_module(modname, Nil)
}
// Executes module entrypoint function, which returns only one Value.
@@ -413,15 +449,10 @@ impl Lua {
R: IntoLua,
{
// Make sure that Lua is initialized
let mut lua = Self::init_from_ptr(state);
lua.collect_garbage = false;
// `Lua` is no longer needed and must be dropped at this point to avoid memory leak
// in case of possible longjmp (lua_error) below
drop(lua);
let _ = Self::get_or_init_from_ptr(state);
callback_error_ext(state, ptr::null_mut(), move |extra, nargs| {
callback_error_ext(state, ptr::null_mut(), true, move |extra, nargs| {
let rawlua = (*extra).raw_lua();
let _guard = StateGuard::new(rawlua, state);
let args = A::from_stack_args(nargs, 1, None, rawlua)?;
func(rawlua.lua(), args)?.push_into_stack(rawlua)?;
Ok(1)
@@ -455,6 +486,7 @@ impl Lua {
/// - Set globals to read-only (and activates safeenv)
/// - Setup local environment table that performs writes locally and proxies reads to the global
/// environment.
/// - Allow only `count` mode in `collectgarbage` function.
///
/// # Examples
///
@@ -477,8 +509,6 @@ impl Lua {
/// # #[cfg(not(feature = "luau"))]
/// # fn main() {}
/// ```
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn sandbox(&self, enabled: bool) -> Result<()> {
@@ -504,6 +534,26 @@ impl Lua {
}
}
/// Sets or replaces a global hook function that will periodically be called as Lua code
/// executes.
///
/// All new threads created (by mlua) after this call will use the global hook function.
///
/// For more information see [`Lua::set_hook`].
#[cfg(not(feature = "luau"))]
#[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,
{
let lua = self.lock();
unsafe {
(*lua.extra.get()).hook_triggers = triggers;
(*lua.extra.get()).hook_callback = Some(XRc::new(callback));
lua.set_thread_hook(lua.state(), HookKind::Global)
}
}
/// Sets a hook function that will periodically be called as Lua code executes.
///
/// When exactly the hook function is called depends on the contents of the `triggers`
@@ -514,12 +564,10 @@ impl Lua {
/// limited form of execution limits by setting [`HookTriggers.every_nth_instruction`] and
/// erroring once an instruction limit has been reached.
///
/// This method sets a hook function for the current thread of this Lua instance.
/// This method sets a hook function for the *current* thread of this Lua instance.
/// If you want to set a hook function for another thread (coroutine), use
/// [`Thread::set_hook`] instead.
///
/// Please note you cannot have more than one hook function set at a time for this Lua instance.
///
/// # Example
///
/// Shows each line number of code being executed by the Lua interpreter.
@@ -544,15 +592,28 @@ impl Lua {
/// [`HookTriggers.every_nth_instruction`]: crate::HookTriggers::every_nth_instruction
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F)
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
where
F: Fn(&Lua, Debug) -> Result<VmState> + MaybeSend + 'static,
{
let lua = self.lock();
unsafe { lua.set_thread_hook(lua.state(), triggers, callback) };
unsafe { lua.set_thread_hook(lua.state(), HookKind::Thread(triggers, XRc::new(callback))) }
}
/// Removes any hook previously set by [`Lua::set_hook`] or [`Thread::set_hook`].
/// Removes a global hook previously set by [`Lua::set_global_hook`].
///
/// This function has no effect if a hook was not previously set.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn remove_global_hook(&self) {
let lua = self.lock();
unsafe {
(*lua.extra.get()).hook_callback = None;
(*lua.extra.get()).hook_triggers = HookTriggers::default();
}
}
/// Removes any hook from the current thread.
///
/// This function has no effect if a hook was not previously set.
#[cfg(not(feature = "luau"))]
@@ -560,17 +621,7 @@ impl Lua {
pub fn remove_hook(&self) {
let lua = self.lock();
unsafe {
let state = lua.state();
ffi::lua_sethook(state, None, 0, 0);
match lua.main_state {
Some(main_state) if state != main_state.as_ptr() => {
// If main_state is different from state, remove hook from it too
ffi::lua_sethook(main_state.as_ptr(), None, 0, 0);
}
_ => {}
};
(*lua.extra.get()).hook_callback = None;
(*lua.extra.get()).hook_thread = ptr::null_mut();
ffi::lua_sethook(lua.state(), None, 0, 0);
}
}
@@ -584,7 +635,7 @@ impl Lua {
/// 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.
/// This is similar to `Lua::set_hook` but in more simplified form.
///
/// # Example
///
@@ -612,7 +663,7 @@ impl Lua {
/// .into_function()?,
/// )?;
/// while co.status() == ThreadStatus::Resumable {
/// co.resume(())?;
/// co.resume::<()>(())?;
/// }
/// # Ok(())
/// # }
@@ -626,20 +677,17 @@ impl Lua {
where
F: Fn(&Lua) -> Result<VmState> + MaybeSend + 'static,
{
use std::rc::Rc;
unsafe extern "C-unwind" fn interrupt_proc(state: *mut ffi::lua_State, gc: c_int) {
if gc >= 0 {
// We don't support GC interrupts since they cannot survive Lua exceptions
return;
}
let result = callback_error_ext(state, ptr::null_mut(), move |extra, _| {
let result = callback_error_ext(state, ptr::null_mut(), false, move |extra, _| {
let interrupt_cb = (*extra).interrupt_callback.clone();
let interrupt_cb = mlua_expect!(interrupt_cb, "no interrupt callback set in interrupt_proc");
if Rc::strong_count(&interrupt_cb) > 2 {
if XRc::strong_count(&interrupt_cb) > 2 {
return Ok(VmState::Continue); // Don't allow recursion
}
let _guard = StateGuard::new((*extra).raw_lua(), state);
interrupt_cb((*extra).lua())
});
match result {
@@ -653,7 +701,7 @@ impl Lua {
// Set interrupt callback
let lua = self.lock();
unsafe {
(*lua.extra.get()).interrupt_callback = Some(Rc::new(callback));
(*lua.extra.get()).interrupt_callback = Some(XRc::new(callback));
(*ffi::lua_callbacks(lua.main_state())).interrupt = Some(interrupt_proc);
}
}
@@ -671,9 +719,95 @@ impl Lua {
}
}
/// Sets the warning function to be used by Lua to emit warnings.
/// Sets a thread creation callback that will be called when a thread is created.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_thread_creation_callback<F>(&self, callback: F)
where
F: Fn(&Lua, Thread) -> Result<()> + MaybeSend + 'static,
{
let lua = self.lock();
unsafe {
(*lua.extra.get()).thread_creation_callback = Some(XRc::new(callback));
(*ffi::lua_callbacks(lua.main_state())).userthread = Some(Self::userthread_proc);
}
}
/// Sets a thread collection callback that will be called when a thread is destroyed.
///
/// Requires `feature = "lua54"`
/// Luau GC does not support exceptions during collection, so the callback must be
/// non-panicking. If the callback panics, the program will be aborted.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_thread_collection_callback<F>(&self, callback: F)
where
F: Fn(crate::LightUserData) + MaybeSend + 'static,
{
let lua = self.lock();
unsafe {
(*lua.extra.get()).thread_collection_callback = Some(XRc::new(callback));
(*ffi::lua_callbacks(lua.main_state())).userthread = Some(Self::userthread_proc);
}
}
#[cfg(feature = "luau")]
unsafe extern "C-unwind" fn userthread_proc(parent: *mut ffi::lua_State, child: *mut ffi::lua_State) {
let extra = ExtraData::get(child);
if !parent.is_null() {
// Thread is created
let callback = match (*extra).thread_creation_callback {
Some(ref cb) => cb.clone(),
None => return,
};
if XRc::strong_count(&callback) > 2 {
return; // Don't allow recursion
}
ffi::lua_pushthread(child);
ffi::lua_xmove(child, (*extra).ref_thread, 1);
let value = Thread((*extra).raw_lua().pop_ref_thread(), child);
callback_error_ext(parent, extra, false, move |extra, _| {
callback((*extra).lua(), value)
})
} else {
// Thread is about to be collected
let callback = match (*extra).thread_collection_callback {
Some(ref cb) => cb.clone(),
None => return,
};
// We need to wrap the callback call in non-unwind function as it's not safe to unwind when
// Luau GC is running.
// This will trigger `abort()` if the callback panics.
unsafe extern "C" fn run_callback(
callback: *const crate::types::ThreadCollectionCallback,
value: *mut ffi::lua_State,
) {
(*callback)(crate::LightUserData(value as _));
}
(*extra).running_gc = true;
run_callback(&callback, child);
(*extra).running_gc = false;
}
}
/// Removes any thread creation or collection callbacks previously set by
/// [`Lua::set_thread_creation_callback`] or [`Lua::set_thread_collection_callback`].
///
/// This function has no effect if a thread callbacks were not previously set.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn remove_thread_callbacks(&self) {
let lua = self.lock();
unsafe {
let extra = lua.extra.get();
(*extra).thread_creation_callback = None;
(*extra).thread_collection_callback = None;
(*ffi::lua_callbacks(lua.main_state())).userthread = None;
}
}
/// Sets the warning function to be used by Lua to emit warnings.
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
pub fn set_warning_function<F>(&self, callback: F)
@@ -686,19 +820,20 @@ impl Lua {
unsafe extern "C-unwind" fn warn_proc(ud: *mut c_void, msg: *const c_char, tocont: c_int) {
let extra = ud as *mut ExtraData;
callback_error_ext((*extra).raw_lua().state(), extra, |extra, _| {
let cb = mlua_expect!(
(*extra).warn_callback.as_ref(),
"no warning callback set in warn_proc"
);
callback_error_ext((*extra).raw_lua().state(), extra, false, |extra, _| {
let warn_callback = (*extra).warn_callback.clone();
let warn_callback = mlua_expect!(warn_callback, "no warning callback set in warn_proc");
if XRc::strong_count(&warn_callback) > 2 {
return Ok(());
}
let msg = StdString::from_utf8_lossy(CStr::from_ptr(msg).to_bytes());
cb((*extra).lua(), &msg, tocont != 0)
warn_callback((*extra).lua(), &msg, tocont != 0)
});
}
let lua = self.lock();
unsafe {
(*lua.extra.get()).warn_callback = Some(Box::new(callback));
(*lua.extra.get()).warn_callback = Some(XRc::new(callback));
ffi::lua_setwarnf(lua.state(), Some(warn_proc), lua.extra.get() as *mut c_void);
}
}
@@ -706,8 +841,6 @@ impl Lua {
/// Removes warning function previously set by `set_warning_function`.
///
/// This function has no effect if a warning function was not previously set.
///
/// Requires `feature = "lua54"`
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
pub fn remove_warning_function(&self) {
@@ -722,8 +855,6 @@ impl Lua {
///
/// A message in a call with `incomplete` set to `true` should be continued in
/// another call to this function.
///
/// Requires `feature = "lua54"`
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
pub fn warning(&self, msg: impl AsRef<str>, incomplete: bool) {
@@ -746,7 +877,7 @@ impl Lua {
/// not count in the stack).
///
/// [`Debug`]: crate::hook::Debug
pub fn inspect_stack(&self, level: usize) -> Option<Debug> {
pub fn inspect_stack(&self, level: usize) -> Option<Debug<'_>> {
let lua = self.lock();
unsafe {
let mut ar: ffi::lua_Debug = mem::zeroed();
@@ -798,8 +929,6 @@ impl Lua {
}
/// Returns `true` if the garbage collector is currently running automatically.
///
/// Requires `feature = "lua54/lua53/lua52/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
pub fn gc_is_running(&self) -> bool {
let lua = self.lock();
@@ -937,8 +1066,6 @@ impl Lua {
/// Returns the previous mode. More information about the generational GC
/// can be found in the Lua 5.4 [documentation][lua_doc].
///
/// Requires `feature = "lua54"`
///
/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#2.5.2
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
@@ -959,8 +1086,6 @@ impl Lua {
/// including via `require` function.
///
/// See [`Compiler`] for details and possible options.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_compiler(&self, compiler: Compiler) {
@@ -1002,18 +1127,20 @@ impl Lua {
///
/// [`Chunk::exec`]: crate::Chunk::exec
#[track_caller]
pub fn load<'a>(&self, chunk: impl AsChunk<'a>) -> Chunk<'a> {
pub fn load<'a>(&self, chunk: impl AsChunk + 'a) -> Chunk<'a> {
self.load_with_location(chunk, Location::caller())
}
pub(crate) fn load_with_location<'a>(
&self,
chunk: impl AsChunk<'a>,
chunk: impl AsChunk + 'a,
location: &'static Location<'static>,
) -> Chunk<'a> {
Chunk {
lua: self.weak(),
name: chunk.name().unwrap_or_else(|| location.to_string()),
name: chunk
.name()
.unwrap_or_else(|| format!("@{}:{}", location.file(), location.line())),
env: chunk.environment(self),
mode: chunk.mode(),
source: chunk.source(),
@@ -1033,8 +1160,6 @@ impl Lua {
/// Create and return a Luau [buffer] object from a byte slice of data.
///
/// Requires `feature = "luau"`
///
/// [buffer]: https://luau.org/library#buffer-library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
@@ -1076,28 +1201,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.
@@ -1199,8 +1303,6 @@ impl Lua {
///
/// The family of `call_async()` functions takes care about creating [`Thread`].
///
/// Requires `feature = "async"`
///
/// # Examples
///
/// Non blocking sleep:
@@ -1266,10 +1368,8 @@ impl Lua {
}
/// Creates a Lua userdata object from a custom serializable userdata type.
///
/// Requires `feature = "serialize"`
#[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
@@ -1296,10 +1396,8 @@ impl Lua {
/// Creates a Lua userdata object from a custom serializable Rust type.
///
/// See [`Lua::create_any_userdata`] for more details.
///
/// Requires `feature = "serialize"`
#[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
@@ -1313,7 +1411,7 @@ impl Lua {
/// This methods provides a way to add fields or methods to userdata objects of a type `T`.
pub fn register_userdata_type<T: 'static>(&self, f: impl FnOnce(&mut UserDataRegistry<T>)) -> Result<()> {
let type_id = TypeId::of::<T>();
let mut registry = UserDataRegistry::new(self, type_id);
let mut registry = UserDataRegistry::new(self);
f(&mut registry);
let lua = self.lock();
@@ -1422,7 +1520,7 @@ impl Lua {
Err(_) => return,
},
ffi::LUA_TTHREAD => {
ffi::lua_newthread(state);
ffi::lua_pushthread(state);
}
#[cfg(feature = "luau")]
ffi::LUA_TBUFFER => {
@@ -1480,10 +1578,9 @@ impl Lua {
/// lifetimes only outlive the scope lifetime.
pub fn scope<'env, R>(
&self,
f: impl for<'scope> FnOnce(&'scope mut Scope<'scope, 'env>) -> Result<R>,
f: impl for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> Result<R>,
) -> Result<R> {
// TODO: Update to `&Scope` in next major release
f(&mut Scope::new(self.lock_arc()))
f(&Scope::new(self.lock_arc()))
}
/// Attempts to coerce a Lua value into a String in a manner consistent with Lua's internal
@@ -1792,8 +1889,8 @@ impl Lua {
let state = lua.state();
unsafe {
let mut unref_list = (*lua.extra.get()).registry_unref_list.lock();
let unref_list = mem::replace(&mut *unref_list, Some(Vec::new()));
for id in mlua_expect!(unref_list, "unref list not set") {
let unref_list = unref_list.replace(Vec::new());
for id in mlua_expect!(unref_list, "unref list is not set") {
ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, id);
}
}
@@ -1823,7 +1920,7 @@ impl Lua {
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// lua.set_app_data("hello");
/// lua.create_function(hello)?.call(())?;
/// lua.create_function(hello)?.call::<()>(())?;
/// let s = lua.app_data_ref::<&str>().unwrap();
/// assert_eq!(*s, "world");
/// Ok(())
@@ -1859,7 +1956,7 @@ impl Lua {
/// Panics if the data object of type `T` is currently mutably borrowed. Multiple immutable
/// reads can be taken out at the same time.
#[track_caller]
pub fn app_data_ref<T: 'static>(&self) -> Option<AppDataRef<T>> {
pub fn app_data_ref<T: 'static>(&self) -> Option<AppDataRef<'_, T>> {
let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() };
extra.app_data.borrow(Some(guard))
@@ -1867,7 +1964,7 @@ impl Lua {
/// Tries to get a reference to an application data object stored by [`Lua::set_app_data`] of
/// type `T`.
pub fn try_app_data_ref<T: 'static>(&self) -> StdResult<Option<AppDataRef<T>>, BorrowError> {
pub fn try_app_data_ref<T: 'static>(&self) -> StdResult<Option<AppDataRef<'_, T>>, BorrowError> {
let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() };
extra.app_data.try_borrow(Some(guard))
@@ -1880,7 +1977,7 @@ impl Lua {
///
/// Panics if the data object of type `T` is currently borrowed.
#[track_caller]
pub fn app_data_mut<T: 'static>(&self) -> Option<AppDataRefMut<T>> {
pub fn app_data_mut<T: 'static>(&self) -> Option<AppDataRefMut<'_, T>> {
let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() };
extra.app_data.borrow_mut(Some(guard))
@@ -1888,7 +1985,7 @@ impl Lua {
/// Tries to get a mutable reference to an application data object stored by
/// [`Lua::set_app_data`] of type `T`.
pub fn try_app_data_mut<T: 'static>(&self) -> StdResult<Option<AppDataRefMut<T>>, BorrowMutError> {
pub fn try_app_data_mut<T: 'static>(&self) -> StdResult<Option<AppDataRefMut<'_, T>>, BorrowMutError> {
let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() };
extra.app_data.try_borrow_mut(Some(guard))
@@ -1907,6 +2004,8 @@ impl Lua {
}
/// Returns an internal `Poll::Pending` constant used for executing async callbacks.
///
/// Every time when [`Future`] is Pending, Lua corotine is suspended with this constant.
#[cfg(feature = "async")]
#[doc(hidden)]
#[inline(always)]
@@ -1915,6 +2014,22 @@ 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)
}
/// 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
/// being deallocated.
#[inline(always)]
pub fn weak(&self) -> WeakLua {
WeakLua(XRc::downgrade(&self.raw))
}
// Luau version located in `luau/mod.rs`
#[cfg(not(feature = "luau"))]
fn disable_c_modules(&self) -> Result<()> {
@@ -1944,8 +2059,13 @@ impl Lua {
}
#[inline(always)]
pub(crate) fn lock(&self) -> ReentrantMutexGuard<RawLua> {
self.raw.lock()
pub(crate) fn lock(&self) -> ReentrantMutexGuard<'_, RawLua> {
let rawlua = self.raw.lock();
#[cfg(feature = "luau")]
if unsafe { (*rawlua.extra.get()).running_gc } {
panic!("Luau VM is suspended while GC is running");
}
rawlua
}
#[inline(always)]
@@ -1953,11 +2073,6 @@ impl Lua {
LuaGuard(self.raw.lock_arc())
}
#[inline(always)]
pub(crate) fn weak(&self) -> WeakLua {
WeakLua(XRc::downgrade(&self.raw))
}
/// Returns a handle to the unprotected Lua state without any synchronization.
///
/// This is useful where we know that the lock is already held by the caller.
@@ -1972,7 +2087,12 @@ impl WeakLua {
#[track_caller]
#[inline(always)]
pub(crate) fn lock(&self) -> LuaGuard {
LuaGuard::new(self.0.upgrade().expect("Lua instance is destroyed"))
let guard = LuaGuard::new(self.0.upgrade().expect("Lua instance is destroyed"));
#[cfg(feature = "luau")]
if unsafe { (*guard.extra.get()).running_gc } {
panic!("Luau VM is suspended while GC is running");
}
guard
}
#[inline(always)]
@@ -1980,14 +2100,30 @@ impl WeakLua {
Some(LuaGuard::new(self.0.upgrade()?))
}
/// Upgrades the weak Lua reference to a strong reference.
///
/// # Panics
///
/// Panics if the Lua instance is destroyed.
#[track_caller]
#[inline(always)]
pub(crate) fn upgrade(&self) -> Lua {
pub fn upgrade(&self) -> Lua {
Lua {
raw: self.0.upgrade().expect("Lua instance is destroyed"),
collect_garbage: false,
}
}
/// Tries to upgrade the weak Lua reference to a strong reference.
///
/// Returns `None` if the Lua instance is destroyed.
#[inline(always)]
pub fn try_upgrade(&self) -> Option<Lua> {
Some(Lua {
raw: self.0.upgrade()?,
collect_garbage: false,
})
}
}
impl PartialEq for WeakLua {
+53 -12
View File
@@ -27,8 +27,8 @@ use super::{Lua, WeakLua};
// Unique key to store `ExtraData` in the registry
static EXTRA_REGISTRY_KEY: u8 = 0;
const WRAPPED_FAILURE_POOL_SIZE: usize = 64;
const REF_STACK_RESERVE: c_int = 1;
const WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY: usize = 64;
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,
@@ -60,6 +61,7 @@ pub(crate) struct ExtraData {
// Pool of `WrappedFailure` enums in the ref thread (as userdata)
pub(super) wrapped_failure_pool: Vec<c_int>,
pub(super) wrapped_failure_top: usize,
// Pool of `Thread`s (coroutines) for async execution
#[cfg(feature = "async")]
pub(super) thread_pool: Vec<c_int>,
@@ -74,14 +76,20 @@ pub(crate) struct ExtraData {
#[cfg(not(feature = "luau"))]
pub(super) hook_callback: Option<crate::types::HookCallback>,
#[cfg(not(feature = "luau"))]
pub(super) hook_thread: *mut ffi::lua_State,
pub(super) hook_triggers: crate::hook::HookTriggers,
#[cfg(feature = "lua54")]
pub(super) warn_callback: Option<crate::types::WarnCallback>,
#[cfg(feature = "luau")]
pub(super) interrupt_callback: Option<crate::types::InterruptCallback>,
#[cfg(feature = "luau")]
pub(super) thread_creation_callback: Option<crate::types::ThreadCreationCallback>,
#[cfg(feature = "luau")]
pub(super) thread_collection_callback: Option<crate::types::ThreadCollectionCallback>,
#[cfg(feature = "luau")]
pub(super) sandboxed: bool,
pub(crate) running_gc: bool,
#[cfg(feature = "luau")]
pub(crate) sandboxed: bool,
#[cfg(feature = "luau")]
pub(super) compiler: Option<Compiler>,
#[cfg(feature = "luau-jit")]
@@ -152,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,
@@ -160,7 +169,8 @@ impl ExtraData {
ref_stack_size: ffi::LUA_MINSTACK - REF_STACK_RESERVE,
ref_stack_top: ffi::lua_gettop(ref_thread),
ref_free: Vec::new(),
wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_SIZE),
wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY),
wrapped_failure_top: 0,
#[cfg(feature = "async")]
thread_pool: Vec::new(),
wrapped_failure_mt_ptr,
@@ -169,17 +179,23 @@ impl ExtraData {
#[cfg(not(feature = "luau"))]
hook_callback: None,
#[cfg(not(feature = "luau"))]
hook_thread: ptr::null_mut(),
hook_triggers: Default::default(),
#[cfg(feature = "lua54")]
warn_callback: None,
#[cfg(feature = "luau")]
interrupt_callback: None,
#[cfg(feature = "luau")]
thread_creation_callback: None,
#[cfg(feature = "luau")]
thread_collection_callback: None,
#[cfg(feature = "luau")]
sandboxed: false,
#[cfg(feature = "luau")]
compiler: None,
#[cfg(feature = "luau-jit")]
enable_jit: true,
#[cfg(feature = "luau")]
running_gc: false,
}));
// Store it in the registry
@@ -193,13 +209,10 @@ impl ExtraData {
raw: XRc::clone(raw),
collect_garbage: false,
});
if self.owned {
XRc::decrement_strong_count(XRc::as_ptr(raw));
}
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
@@ -238,7 +251,7 @@ impl ExtraData {
}
#[inline(always)]
pub(super) unsafe fn raw_lua(&self) -> &RawLua {
pub(crate) unsafe fn raw_lua(&self) -> &RawLua {
&*self.lua.assume_init_ref().raw.data_ptr()
}
@@ -246,4 +259,32 @@ impl ExtraData {
pub(super) unsafe fn weak(&self) -> &WeakLua {
self.weak.assume_init_ref()
}
/// Pops a reference from top of the auxiliary stack and move it to a first free slot.
pub(super) unsafe fn ref_stack_pop(&mut self) -> c_int {
if let Some(free) = self.ref_free.pop() {
ffi::lua_replace(self.ref_thread, free);
return free;
}
// Try to grow max stack size
if self.ref_stack_top >= self.ref_stack_size {
let mut inc = self.ref_stack_size; // Try to double stack size
while inc > 0 && ffi::lua_checkstack(self.ref_thread, inc) == 0 {
inc /= 2;
}
if inc == 0 {
// Pop item on top of the stack to avoid stack leaking and successfully run destructors
// during unwinding.
ffi::lua_pop(self.ref_thread, 1);
let top = self.ref_stack_top;
// It is a user error to create too many references to exhaust the Lua max stack size
// for the ref thread.
panic!("cannot create a Lua reference, out of auxiliary stack space (used {top} slots)");
}
self.ref_stack_size += inc;
}
self.ref_stack_top += 1;
self.ref_stack_top
}
}
+228 -133
View File
@@ -1,18 +1,17 @@
use std::any::TypeId;
use std::cell::{Cell, UnsafeCell};
use std::ffi::{CStr, CString};
use std::ffi::CStr;
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, StateGuard};
use crate::state::util::callback_error_ext;
use crate::stdlib::StdLib;
use crate::string::String;
use crate::table::Table;
@@ -23,13 +22,14 @@ use crate::types::{
MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc,
};
use crate::userdata::{
AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, UserDataStorage,
init_userdata_metatable, AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry,
UserDataStorage,
};
use crate::util::{
assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state,
get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, init_userdata_metatable,
pop_error, push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall,
short_type_name, StackGuard, WrappedFailure,
get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, pop_error,
push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name,
StackGuard, WrappedFailure,
};
use crate::value::{Nil, Value};
@@ -37,7 +37,10 @@ use super::extra::ExtraData;
use super::{Lua, LuaOptions, WeakLua};
#[cfg(not(feature = "luau"))]
use crate::hook::{Debug, HookTriggers};
use crate::{
hook::Debug,
types::{HookCallback, HookKind, VmState},
};
#[cfg(feature = "async")]
use {
@@ -54,17 +57,25 @@ pub struct RawLua {
pub(super) state: Cell<*mut ffi::lua_State>,
pub(super) main_state: Option<NonNull<ffi::lua_State>>,
pub(super) extra: XRc<UnsafeCell<ExtraData>>,
owned: bool,
}
impl Drop for RawLua {
fn drop(&mut self) {
unsafe {
if !(*self.extra.get()).owned {
if !self.owned {
return;
}
let mem_state = MemoryState::get(self.main_state());
#[cfg(feature = "luau")]
{
// Reset any callbacks
(*ffi::lua_callbacks(self.main_state())).interrupt = None;
(*ffi::lua_callbacks(self.main_state())).userthread = None;
}
ffi::lua_close(self.main_state());
// Deallocate `MemoryState`
@@ -109,7 +120,7 @@ impl RawLua {
unsafe { (*self.extra.get()).ref_thread }
}
pub(super) unsafe fn new(libs: StdLib, options: LuaOptions) -> XRc<ReentrantMutex<Self>> {
pub(super) unsafe fn new(libs: StdLib, options: &LuaOptions) -> XRc<ReentrantMutex<Self>> {
let mem_state: *mut MemoryState = Box::into_raw(Box::default());
let mut state = ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void);
// If state is null then switch to Lua internal allocator
@@ -186,6 +197,8 @@ impl RawLua {
init_internal_metatable::<XRc<UnsafeCell<ExtraData>>>(state, None)?;
init_internal_metatable::<Callback>(state, None)?;
init_internal_metatable::<CallbackUpvalue>(state, None)?;
#[cfg(not(feature = "luau"))]
init_internal_metatable::<HookCallback>(state, None)?;
#[cfg(feature = "async")]
{
init_internal_metatable::<AsyncCallback>(state, None)?;
@@ -195,7 +208,7 @@ impl RawLua {
}
// Init serde metatables
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
crate::serde::init_metatables(state)?;
Ok::<_, Error>(())
@@ -227,8 +240,17 @@ impl RawLua {
// Make sure that we don't store current state as main state (if it's not available)
main_state: get_main_state(state).and_then(NonNull::new),
extra: XRc::clone(&extra),
owned,
}));
(*extra.get()).set_lua(&rawlua);
if owned {
// If Lua state is managed by us, then make internal `RawLua` reference "weak"
XRc::decrement_strong_count(XRc::as_ptr(&rawlua));
} else {
// If Lua state is not managed by us, then keep internal `RawLua` reference "strong"
// but `Extra` reference weak (it will be collected from registry at lua_close time)
XRc::decrement_strong_count(XRc::as_ptr(&extra));
}
rawlua
}
@@ -270,36 +292,41 @@ impl RawLua {
let res = load_std_libs(self.main_state(), libs);
// If `package` library loaded into a safe lua state then disable C modules
let curr_libs = (*self.extra.get()).libs;
if is_safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) {
mlua_expect!(self.lua().disable_c_modules(), "Error during disabling C modules");
#[cfg(not(feature = "luau"))]
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");
}
}
#[cfg(feature = "luau")]
let _ = is_safe;
unsafe { (*self.extra.get()).libs |= libs };
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`]
@@ -373,49 +400,29 @@ impl RawLua {
status
}
/// Sets a 'hook' function for a thread (coroutine).
/// Sets a hook for a thread (coroutine).
#[cfg(not(feature = "luau"))]
pub(crate) unsafe fn set_thread_hook<F>(
pub(crate) unsafe fn set_thread_hook(
&self,
state: *mut ffi::lua_State,
triggers: HookTriggers,
callback: F,
) where
F: Fn(&Lua, Debug) -> Result<crate::VmState> + MaybeSend + 'static,
{
use crate::types::VmState;
use std::rc::Rc;
thread_state: *mut ffi::lua_State,
hook: HookKind,
) -> Result<()> {
// Key to store hooks in the registry
const HOOKS_KEY: *const c_char = cstr!("__mlua_hooks");
unsafe extern "C-unwind" fn hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) {
let extra = ExtraData::get(state);
if (*extra).hook_thread != state {
// Hook was destined for a different thread, ignore
ffi::lua_sethook(state, None, 0, 0);
return;
}
let result = callback_error_ext(state, extra, move |extra, _| {
let hook_cb = (*extra).hook_callback.clone();
let hook_cb = mlua_expect!(hook_cb, "no hook callback set in hook_proc");
if Rc::strong_count(&hook_cb) > 2 {
return Ok(VmState::Continue); // Don't allow recursion
}
let rawlua = (*extra).raw_lua();
let _guard = StateGuard::new(rawlua, state);
let debug = Debug::new(rawlua, ar);
hook_cb((*extra).lua(), debug)
});
match result {
unsafe fn process_status(state: *mut ffi::lua_State, event: c_int, status: VmState) {
match status {
VmState::Continue => {}
VmState::Yield => {
// Only count and line events can yield
if (*ar).event == ffi::LUA_HOOKCOUNT || (*ar).event == ffi::LUA_HOOKLINE {
if event == ffi::LUA_HOOKCOUNT || event == ffi::LUA_HOOKLINE {
#[cfg(any(feature = "lua54", feature = "lua53"))]
if ffi::lua_isyieldable(state) != 0 {
ffi::lua_yield(state, 0);
}
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
{
ffi::lua_pushliteral(state, "attempt to yield from a hook");
ffi::lua_pushliteral(state, c"attempt to yield from a hook");
ffi::lua_error(state);
}
}
@@ -423,9 +430,83 @@ impl RawLua {
}
}
(*self.extra.get()).hook_callback = Some(Rc::new(callback));
(*self.extra.get()).hook_thread = state; // Mark for what thread the hook is set
ffi::lua_sethook(state, Some(hook_proc), triggers.mask(), triggers.count());
unsafe extern "C-unwind" fn global_hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) {
let status = callback_error_ext(state, ptr::null_mut(), false, move |extra, _| {
match (*extra).hook_callback.clone() {
Some(hook_callback) => {
let rawlua = (*extra).raw_lua();
let debug = Debug::new(rawlua, ar);
hook_callback((*extra).lua(), debug)
}
None => {
ffi::lua_sethook(state, None, 0, 0);
Ok(VmState::Continue)
}
}
});
process_status(state, (*ar).event, status);
}
unsafe extern "C-unwind" fn hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) {
let top = ffi::lua_gettop(state);
let mut hook_callback_ptr = ptr::null();
ffi::luaL_checkstack(state, 3, ptr::null());
if ffi::lua_getfield(state, ffi::LUA_REGISTRYINDEX, HOOKS_KEY) == ffi::LUA_TTABLE {
ffi::lua_pushthread(state);
if ffi::lua_rawget(state, -2) == ffi::LUA_TUSERDATA {
hook_callback_ptr = get_internal_userdata::<HookCallback>(state, -1, ptr::null());
}
}
ffi::lua_settop(state, top);
if hook_callback_ptr.is_null() {
ffi::lua_sethook(state, None, 0, 0);
return;
}
let status = callback_error_ext(state, ptr::null_mut(), false, |extra, _| {
let rawlua = (*extra).raw_lua();
let debug = Debug::new(rawlua, ar);
let hook_callback = (*hook_callback_ptr).clone();
hook_callback((*extra).lua(), debug)
});
process_status(state, (*ar).event, status)
}
let (triggers, callback) = match hook {
HookKind::Global if (*self.extra.get()).hook_callback.is_none() => {
return Ok(());
}
HookKind::Global => {
let triggers = (*self.extra.get()).hook_triggers;
let (mask, count) = (triggers.mask(), triggers.count());
ffi::lua_sethook(thread_state, Some(global_hook_proc), mask, count);
return Ok(());
}
HookKind::Thread(triggers, callback) => (triggers, callback),
};
// Hooks for threads stored in the registry (in a weak table)
let state = self.state();
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
protect_lua!(state, 0, 0, |state| {
if ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, HOOKS_KEY) == 0 {
// Table just created, initialize it
ffi::lua_pushliteral(state, c"k");
ffi::lua_setfield(state, -2, cstr!("__mode")); // hooktable.__mode = "k"
ffi::lua_pushvalue(state, -1);
ffi::lua_setmetatable(state, -2); // metatable(hooktable) = hooktable
}
ffi::lua_pushthread(thread_state);
ffi::lua_xmove(thread_state, state, 1); // key (thread)
let _ = push_internal_userdata(state, callback, false); // value (hook callback)
ffi::lua_rawset(state, -3); // hooktable[thread] = hook callback
})?;
ffi::lua_sethook(thread_state, Some(hook_proc), triggers.mask(), triggers.count());
Ok(())
}
/// See [`Lua::create_string`]
@@ -456,6 +537,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
@@ -492,11 +601,20 @@ impl RawLua {
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
let thread_state = if self.unlikely_memory_error() {
let protect = !self.unlikely_memory_error();
#[cfg(feature = "luau")]
let protect = protect || (*self.extra.get()).thread_creation_callback.is_some();
let thread_state = if !protect {
ffi::lua_newthread(state)
} else {
protect_lua!(state, 0, 1, |state| ffi::lua_newthread(state))?
};
// Inherit global hook if set
#[cfg(not(feature = "luau"))]
self.set_thread_hook(thread_state, HookKind::Global)?;
let thread = Thread(self.pop_ref(), thread_state);
ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index);
Ok(thread)
@@ -505,7 +623,6 @@ impl RawLua {
/// Wraps a Lua function into a new or recycled thread (coroutine).
#[cfg(feature = "async")]
pub(crate) unsafe fn create_recycled_thread(&self, func: &Function) -> Result<Thread> {
#[cfg(any(feature = "lua54", feature = "luau"))]
if let Some(index) = (*self.extra.get()).thread_pool.pop() {
let thread_state = ffi::lua_tothread(self.ref_thread(), index);
ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index);
@@ -523,29 +640,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")]
#[cfg(any(feature = "lua54", feature = "luau"))]
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) -> bool {
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) {
let extra = &mut *self.extra.get();
if extra.thread_pool.len() < extra.thread_pool.capacity() {
let thread_state = ffi::lua_tothread(extra.ref_thread, thread.0.index);
#[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, self.state());
#[cfg(feature = "lua54")]
if status != ffi::LUA_OK {
// Error object is on top, drop it
ffi::lua_settop(thread_state, 0);
}
#[cfg(feature = "luau")]
ffi::lua_resetthread(thread_state);
extra.thread_pool.push(thread.0.index);
thread.0.drop = false; // Prevent thread from being garbage collected
return true;
}
false
}
/// Pushes a value that implements `IntoLua` onto the Lua stack.
@@ -592,7 +694,7 @@ impl RawLua {
/// Pops a value from the Lua stack.
///
/// Uses 2 stack spaces, does not call `checkstack`.
/// Uses up to 1 stack spaces, does not call `checkstack`.
pub(crate) unsafe fn pop_value(&self) -> Value {
let value = self.stack_value(-1, None);
ffi::lua_pop(self.state(), 1);
@@ -601,7 +703,7 @@ impl RawLua {
/// Returns value at given stack index without popping it.
///
/// Uses 2 stack spaces, does not call checkstack.
/// Uses up to 1 stack spaces, does not call `checkstack`.
pub(crate) unsafe fn stack_value(&self, idx: c_int, type_hint: Option<c_int>) -> Value {
let state = self.state();
match type_hint.unwrap_or_else(|| ffi::lua_type(state, idx)) {
@@ -714,21 +816,21 @@ impl RawLua {
#[inline]
pub(crate) unsafe fn pop_ref(&self) -> ValueRef {
ffi::lua_xmove(self.state(), self.ref_thread(), 1);
let index = ref_stack_pop(self.extra.get());
let index = (*self.extra.get()).ref_stack_pop();
ValueRef::new(self, index)
}
// Same as `pop_ref` but assumes the value is already on the reference thread
#[inline]
pub(crate) unsafe fn pop_ref_thread(&self) -> ValueRef {
let index = ref_stack_pop(self.extra.get());
let index = (*self.extra.get()).ref_stack_pop();
ValueRef::new(self, index)
}
#[inline]
pub(crate) unsafe fn clone_ref(&self, vref: &ValueRef) -> ValueRef {
ffi::lua_pushvalue(self.ref_thread(), vref.index);
let index = ref_stack_pop(self.extra.get());
let index = (*self.extra.get()).ref_stack_pop();
ValueRef::new(self, index)
}
@@ -779,7 +881,7 @@ impl RawLua {
}
// Create a new metatable from `UserData` definition
let mut registry = UserDataRegistry::new(self.lua(), type_id);
let mut registry = UserDataRegistry::new(self.lua());
T::register(&mut registry);
self.create_userdata_metatable(registry.into_raw())
@@ -800,7 +902,7 @@ impl RawLua {
// Check if metatable creation is pending or create an empty metatable otherwise
let registry = match (*self.extra.get()).pending_userdata_reg.remove(&type_id) {
Some(registry) => registry,
None => UserDataRegistry::<T>::new(self.lua(), type_id).into_raw(),
None => UserDataRegistry::<T>::new(self.lua()).into_raw(),
};
self.create_userdata_metatable(registry)
})
@@ -815,12 +917,11 @@ impl RawLua {
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
// We push metatable first to ensure having correct metatable with `__gc` method
ffi::lua_pushnil(state);
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, get_metatable_id()?);
// We generate metatable first to make sure it *always* available when userdata pushed
let mt_id = get_metatable_id()?;
let protect = !self.unlikely_memory_error();
crate::util::push_userdata(state, data, protect)?;
ffi::lua_replace(state, -3);
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, mt_id);
ffi::lua_setmetatable(state, -2);
// Set empty environment for Lua 5.1
@@ -1018,17 +1119,22 @@ impl RawLua {
// Returns `TypeId` for the userdata ref, checking that it's registered and not destructed.
//
// Returns `None` if the userdata is registered but non-static.
pub(crate) unsafe fn get_userdata_ref_type_id(&self, vref: &ValueRef) -> Result<Option<TypeId>> {
self.get_userdata_type_id_inner(self.ref_thread(), vref.index)
#[inline(always)]
pub(crate) fn get_userdata_ref_type_id(&self, vref: &ValueRef) -> Result<Option<TypeId>> {
unsafe { self.get_userdata_type_id_inner(self.ref_thread(), vref.index) }
}
// Same as `get_userdata_ref_type_id` but assumes the userdata is already on the stack.
pub(crate) unsafe fn get_userdata_type_id<T>(&self, idx: c_int) -> Result<Option<TypeId>> {
match self.get_userdata_type_id_inner(self.state(), idx) {
pub(crate) unsafe fn get_userdata_type_id<T>(
&self,
state: *mut ffi::lua_State,
idx: c_int,
) -> Result<Option<TypeId>> {
match self.get_userdata_type_id_inner(state, idx) {
Ok(type_id) => Ok(type_id),
Err(Error::UserDataTypeMismatch) if ffi::lua_type(self.state(), idx) != ffi::LUA_TUSERDATA => {
Err(Error::UserDataTypeMismatch) if ffi::lua_type(state, idx) != ffi::LUA_TUSERDATA => {
// Report `FromLuaConversionError` instead
let idx_type_name = CStr::from_ptr(ffi::luaL_typename(self.state(), idx));
let idx_type_name = CStr::from_ptr(ffi::luaL_typename(state, idx));
let idx_type_name = idx_type_name.to_str().unwrap();
let message = format!("expected userdata of type '{}'", short_type_name::<T>());
Err(Error::from_lua_conversion(idx_type_name, "userdata", message))
@@ -1077,11 +1183,10 @@ impl RawLua {
pub(crate) fn create_callback(&self, func: Callback) -> Result<Function> {
unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int {
let upvalue = get_userdata::<CallbackUpvalue>(state, ffi::lua_upvalueindex(1));
callback_error_ext(state, (*upvalue).extra.get(), |extra, nargs| {
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 callback is executed
let rawlua = (*extra).raw_lua();
let _guard = StateGuard::new(rawlua, state);
match (*upvalue).data {
Some(ref func) => func(rawlua, nargs),
None => Err(Error::CallbackDestructed),
@@ -1125,15 +1230,13 @@ impl RawLua {
// Async functions cannot be scoped and therefore destroyed,
// so the first upvalue is always valid
let upvalue = get_userdata::<AsyncCallbackUpvalue>(state, ffi::lua_upvalueindex(1));
let extra = (*upvalue).extra.get();
callback_error_ext(state, extra, |extra, nargs| {
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 callback is executed
let rawlua = (*extra).raw_lua();
let _guard = StateGuard::new(rawlua, state);
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)?;
@@ -1151,21 +1254,27 @@ 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(), |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();
let _guard = StateGuard::new(rawlua, state);
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) => {
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
@@ -1183,6 +1292,7 @@ impl RawLua {
}
}
}
None => Err(Error::CallbackDestructed),
}
})
}
@@ -1228,8 +1338,8 @@ impl RawLua {
lua.load(
r#"
local poll = get_poll(...)
local nres, res, res2 = poll()
while true do
local nres, res, res2 = poll()
if nres ~= nil then
if nres == 0 then
return
@@ -1237,16 +1347,23 @@ 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
-- `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))
end
"#,
)
.try_cache()
.set_name("__mlua_async_poll")
.set_name("=__mlua_async_poll")
.set_environment(env)
.into_function()
}
@@ -1266,16 +1383,14 @@ impl RawLua {
// Uses 3 stack spaces
unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> {
#[inline(always)]
pub unsafe fn requiref(
unsafe fn requiref(
state: *mut ffi::lua_State,
modname: &str,
modname: *const c_char,
openf: ffi::lua_CFunction,
glb: c_int,
) -> Result<()> {
let modname = mlua_expect!(CString::new(modname), "modname contains nil byte");
protect_lua!(state, 0, 1, |state| {
ffi::luaL_requiref(state, modname.as_ptr() as *const c_char, openf, glb)
protect_lua!(state, 0, 0, |state| {
ffi::luaL_requiref(state, modname, openf, glb)
})
}
@@ -1306,36 +1421,30 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()>
{
if libs.contains(StdLib::COROUTINE) {
requiref(state, ffi::LUA_COLIBNAME, ffi::luaopen_coroutine, 1)?;
ffi::lua_pop(state, 1);
}
}
if libs.contains(StdLib::TABLE) {
requiref(state, ffi::LUA_TABLIBNAME, ffi::luaopen_table, 1)?;
ffi::lua_pop(state, 1);
}
#[cfg(not(feature = "luau"))]
if libs.contains(StdLib::IO) {
requiref(state, ffi::LUA_IOLIBNAME, ffi::luaopen_io, 1)?;
ffi::lua_pop(state, 1);
}
if libs.contains(StdLib::OS) {
requiref(state, ffi::LUA_OSLIBNAME, ffi::luaopen_os, 1)?;
ffi::lua_pop(state, 1);
}
if libs.contains(StdLib::STRING) {
requiref(state, ffi::LUA_STRLIBNAME, ffi::luaopen_string, 1)?;
ffi::lua_pop(state, 1);
}
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
{
if libs.contains(StdLib::UTF8) {
requiref(state, ffi::LUA_UTF8LIBNAME, ffi::luaopen_utf8, 1)?;
ffi::lua_pop(state, 1);
}
}
@@ -1343,7 +1452,6 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()>
{
if libs.contains(StdLib::BIT) {
requiref(state, ffi::LUA_BITLIBNAME, ffi::luaopen_bit32, 1)?;
ffi::lua_pop(state, 1);
}
}
@@ -1351,53 +1459,40 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()>
{
if libs.contains(StdLib::BIT) {
requiref(state, ffi::LUA_BITLIBNAME, ffi::luaopen_bit, 1)?;
ffi::lua_pop(state, 1);
}
}
#[cfg(feature = "luau")]
if libs.contains(StdLib::BUFFER) {
requiref(state, ffi::LUA_BUFFERLIBNAME, ffi::luaopen_buffer, 1)?;
ffi::lua_pop(state, 1);
}
#[cfg(feature = "luau")]
if libs.contains(StdLib::VECTOR) {
requiref(state, ffi::LUA_VECLIBNAME, ffi::luaopen_vector, 1)?;
ffi::lua_pop(state, 1);
}
if libs.contains(StdLib::MATH) {
requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?;
ffi::lua_pop(state, 1);
}
if libs.contains(StdLib::DEBUG) {
requiref(state, ffi::LUA_DBLIBNAME, ffi::luaopen_debug, 1)?;
ffi::lua_pop(state, 1);
}
#[cfg(not(feature = "luau"))]
if libs.contains(StdLib::PACKAGE) {
requiref(state, ffi::LUA_LOADLIBNAME, ffi::luaopen_package, 1)?;
ffi::lua_pop(state, 1);
}
#[cfg(feature = "luau")]
if libs.contains(StdLib::PACKAGE) {
let lua = (*ExtraData::get(state)).lua();
crate::luau::register_package_module(lua)?;
}
#[cfg(feature = "luajit")]
if libs.contains(StdLib::JIT) {
requiref(state, ffi::LUA_JITLIBNAME, ffi::luaopen_jit, 1)?;
ffi::lua_pop(state, 1);
}
#[cfg(feature = "luajit")]
if libs.contains(StdLib::FFI) {
requiref(state, ffi::LUA_FFILIBNAME, ffi::luaopen_ffi, 1)?;
ffi::lua_pop(state, 1);
}
Ok(())
+40 -68
View File
@@ -7,12 +7,10 @@ use crate::error::{Error, Result};
use crate::state::{ExtraData, RawLua};
use crate::util::{self, get_internal_metatable, WrappedFailure};
const WRAPPED_FAILURE_POOL_SIZE: usize = 64;
pub(super) struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State);
struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State);
impl<'a> StateGuard<'a> {
pub(super) fn new(inner: &'a RawLua, mut state: *mut ffi::lua_State) -> Self {
fn new(inner: &'a RawLua, mut state: *mut ffi::lua_State) -> Self {
state = inner.state.replace(state);
Self(inner, state)
}
@@ -25,10 +23,11 @@ impl Drop for StateGuard<'_> {
}
// An optimized version of `callback_error` that does not allocate `WrappedFailure` userdata
// and instead reuses unsed values from previous calls (or allocates new).
pub(super) unsafe fn callback_error_ext<F, R>(
// and instead reuses unused values from previous calls (or allocates new).
pub(crate) unsafe fn callback_error_ext<F, R>(
state: *mut ffi::lua_State,
mut extra: *mut ExtraData,
wrap_error: bool,
f: F,
) -> R
where
@@ -42,26 +41,27 @@ where
enum PreallocatedFailure {
New(*mut WrappedFailure),
Existing(i32),
Reserved,
}
impl PreallocatedFailure {
unsafe fn reserve(state: *mut ffi::lua_State, extra: *mut ExtraData) -> Self {
match (*extra).wrapped_failure_pool.pop() {
Some(index) => PreallocatedFailure::Existing(index),
None => {
// We need to check stack for Luau in case when callback is called from interrupt
// See https://github.com/Roblox/luau/issues/446 and mlua #142 and #153
#[cfg(feature = "luau")]
ffi::lua_rawcheckstack(state, 2);
// Place it to the beginning of the stack
let ud = WrappedFailure::new_userdata(state);
ffi::lua_insert(state, 1);
PreallocatedFailure::New(ud)
}
if (*extra).wrapped_failure_top > 0 {
(*extra).wrapped_failure_top -= 1;
return PreallocatedFailure::Reserved;
}
// We need to check stack for Luau in case when callback is called from interrupt
// See https://github.com/luau-lang/luau/issues/446 and mlua #142 and #153
#[cfg(feature = "luau")]
ffi::lua_rawcheckstack(state, 2);
// Place it to the beginning of the stack
let ud = WrappedFailure::new_userdata(state);
ffi::lua_insert(state, 1);
PreallocatedFailure::New(ud)
}
#[cold]
unsafe fn r#use(&self, state: *mut ffi::lua_State, extra: *mut ExtraData) -> *mut WrappedFailure {
let ref_thread = (*extra).ref_thread;
match *self {
@@ -69,12 +69,12 @@ where
ffi::lua_settop(state, 1);
ud
}
PreallocatedFailure::Existing(index) => {
PreallocatedFailure::Reserved => {
let index = (*extra).wrapped_failure_pool.pop().unwrap();
ffi::lua_settop(state, 0);
#[cfg(feature = "luau")]
ffi::lua_rawcheckstack(state, 2);
ffi::lua_pushvalue(ref_thread, index);
ffi::lua_xmove(ref_thread, state, 1);
ffi::lua_xpush(ref_thread, state, index);
ffi::lua_pushnil(ref_thread);
ffi::lua_replace(ref_thread, index);
(*extra).ref_free.push(index);
@@ -87,24 +87,13 @@ where
let ref_thread = (*extra).ref_thread;
match self {
PreallocatedFailure::New(_) => {
if (*extra).wrapped_failure_pool.len() < WRAPPED_FAILURE_POOL_SIZE {
ffi::lua_rotate(state, 1, -1);
ffi::lua_xmove(state, ref_thread, 1);
let index = ref_stack_pop(extra);
(*extra).wrapped_failure_pool.push(index);
} else {
ffi::lua_remove(state, 1);
}
}
PreallocatedFailure::Existing(index) => {
if (*extra).wrapped_failure_pool.len() < WRAPPED_FAILURE_POOL_SIZE {
(*extra).wrapped_failure_pool.push(index);
} else {
ffi::lua_pushnil(ref_thread);
ffi::lua_replace(ref_thread, index);
(*extra).ref_free.push(index);
}
ffi::lua_rotate(state, 1, -1);
ffi::lua_xmove(state, ref_thread, 1);
let index = (*extra).ref_stack_pop();
(*extra).wrapped_failure_pool.push(index);
(*extra).wrapped_failure_top += 1;
}
PreallocatedFailure::Reserved => (*extra).wrapped_failure_top += 1,
}
}
}
@@ -113,7 +102,11 @@ where
// to store a wrapped failure (error or panic) *before* we proceed.
let prealloc_failure = PreallocatedFailure::reserve(state, extra);
match catch_unwind(AssertUnwindSafe(|| f(extra, nargs))) {
match catch_unwind(AssertUnwindSafe(|| {
let rawlua = (*extra).raw_lua();
let _guard = StateGuard::new(rawlua, state);
f(extra, nargs)
})) {
Ok(Ok(r)) => {
// Return unused `WrappedFailure` to the pool
prealloc_failure.release(state, extra);
@@ -122,6 +115,13 @@ where
Ok(Err(err)) => {
let wrapped_error = prealloc_failure.r#use(state, extra);
if !wrap_error {
ptr::write(wrapped_error, WrappedFailure::Error(err));
get_internal_metatable::<WrappedFailure>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
}
// Build `CallbackError` with traceback
let traceback = if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 {
ffi::luaL_traceback(state, state, ptr::null(), 0);
@@ -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
}
+11 -10
View File
@@ -6,9 +6,11 @@ pub struct StdLib(u32);
impl StdLib {
/// [`coroutine`](https://www.lua.org/manual/5.4/manual.html#6.2) library
///
/// Requires `feature = "lua54/lua53/lua52/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau")))
)]
pub const COROUTINE: StdLib = StdLib(1);
/// [`table`](https://www.lua.org/manual/5.4/manual.html#6.6) library
@@ -26,21 +28,24 @@ impl StdLib {
pub const STRING: StdLib = StdLib(1 << 4);
/// [`utf8`](https://www.lua.org/manual/5.4/manual.html#6.5) library
///
/// Requires `feature = "lua54/lua53/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))))]
pub const UTF8: StdLib = StdLib(1 << 5);
/// [`bit`](https://www.lua.org/manual/5.2/manual.html#6.7) library
///
/// Requires `feature = "lua52/luajit/luau"`
#[cfg(any(feature = "lua52", feature = "luajit", feature = "luau", doc))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua52", feature = "luajit", feature = "luau")))
)]
pub const BIT: StdLib = StdLib(1 << 6);
/// [`math`](https://www.lua.org/manual/5.4/manual.html#6.7) library
pub const MATH: StdLib = StdLib(1 << 7);
/// [`package`](https://www.lua.org/manual/5.4/manual.html#6.3) library
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub const PACKAGE: StdLib = StdLib(1 << 8);
/// [`buffer`](https://luau.org/library#buffer-library) library
@@ -54,15 +59,11 @@ impl StdLib {
pub const VECTOR: StdLib = StdLib(1 << 10);
/// [`jit`](http://luajit.org/ext_jit.html) library
///
/// Requires `feature = "luajit"`
#[cfg(any(feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))]
pub const JIT: StdLib = StdLib(1 << 11);
/// (**unsafe**) [`ffi`](http://luajit.org/ext_ffi.html) library
///
/// Requires `feature = "luajit"`
#[cfg(any(feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))]
pub const FFI: StdLib = StdLib(1 << 30);
+68 -38
View File
@@ -1,4 +1,4 @@
use std::borrow::Borrow;
use std::borrow::{Borrow, Cow};
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::os::raw::{c_int, c_void};
@@ -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,14 +43,8 @@ impl String {
/// # }
/// ```
#[inline]
pub fn to_str(&self) -> Result<BorrowedStr> {
let BorrowedBytes(bytes, guard) = self.as_bytes();
let s = str::from_utf8(bytes).map_err(|e| Error::FromLuaConversionError {
from: "string",
to: "&str".to_string(),
message: Some(e.to_string()),
})?;
Ok(BorrowedStr(s, guard))
pub fn to_str(&self) -> Result<BorrowedStr<'_>> {
BorrowedStr::try_from(self)
}
/// Converts this string to a [`StdString`].
@@ -108,20 +102,22 @@ impl String {
/// # }
/// ```
#[inline]
pub fn as_bytes(&self) -> BorrowedBytes {
let (bytes, guard) = unsafe { self.to_slice() };
BorrowedBytes(&bytes[..bytes.len() - 1], guard)
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 {
let (bytes, guard) = unsafe { self.to_slice() };
BorrowedBytes(bytes, guard)
pub fn as_bytes_with_nul(&self) -> BorrowedBytes<'_> {
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self);
// Include the trailing nul byte (it's always present but excluded by default)
let buf = unsafe { slice::from_raw_parts((*buf).as_ptr(), (*buf).len() + 1) };
BorrowedBytes { buf, borrow, _lua }
}
// Does not return the terminating nul byte
unsafe fn to_slice(&self) -> (&[u8], Lua) {
let lua = self.0.lua.upgrade();
let slice = unsafe {
let slice = {
let rawlua = lua.lock();
let ref_thread = rawlua.ref_thread();
@@ -134,7 +130,7 @@ impl String {
// string type
let mut size = 0;
let data = ffi::lua_tolstring(ref_thread, self.0.index, &mut size);
slice::from_raw_parts(data as *const u8, size + 1)
slice::from_raw_parts(data as *const u8, size)
};
(slice, lua)
}
@@ -215,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
@@ -238,40 +234,45 @@ impl fmt::Display for Display<'_> {
}
/// A borrowed string (`&str`) that holds a strong reference to the Lua state.
pub struct BorrowedStr<'a>(&'a str, #[allow(unused)] Lua);
pub struct BorrowedStr<'a> {
// `buf` points to a readonly memory managed by Lua
pub(crate) buf: &'a str,
pub(crate) borrow: Cow<'a, String>,
pub(crate) _lua: Lua,
}
impl Deref for BorrowedStr<'_> {
type Target = str;
#[inline(always)]
fn deref(&self) -> &str {
self.0
self.buf
}
}
impl Borrow<str> for BorrowedStr<'_> {
#[inline(always)]
fn borrow(&self) -> &str {
self.0
self.buf
}
}
impl AsRef<str> for BorrowedStr<'_> {
#[inline(always)]
fn as_ref(&self) -> &str {
self.0
self.buf
}
}
impl fmt::Display for BorrowedStr<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
self.buf.fmt(f)
}
}
impl fmt::Debug for BorrowedStr<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
self.buf.fmt(f)
}
}
@@ -280,7 +281,7 @@ where
T: AsRef<str>,
{
fn eq(&self, other: &T) -> bool {
self.0 == other.as_ref()
self.buf == other.as_ref()
}
}
@@ -291,45 +292,65 @@ where
T: AsRef<str>,
{
fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
self.0.partial_cmp(other.as_ref())
self.buf.partial_cmp(other.as_ref())
}
}
impl Ord for BorrowedStr<'_> {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.0.cmp(other.0)
self.buf.cmp(other.buf)
}
}
impl<'a> TryFrom<&'a String> for BorrowedStr<'a> {
type Error = Error;
#[inline]
fn try_from(value: &'a String) -> Result<Self> {
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(value);
let buf = str::from_utf8(buf).map_err(|e| Error::FromLuaConversionError {
from: "string",
to: "&str".to_string(),
message: Some(e.to_string()),
})?;
Ok(Self { buf, borrow, _lua })
}
}
/// A borrowed byte slice (`&[u8]`) that holds a strong reference to the Lua state.
pub struct BorrowedBytes<'a>(&'a [u8], #[allow(unused)] Lua);
pub struct BorrowedBytes<'a> {
// `buf` points to a readonly memory managed by Lua
pub(crate) buf: &'a [u8],
pub(crate) borrow: Cow<'a, String>,
pub(crate) _lua: Lua,
}
impl Deref for BorrowedBytes<'_> {
type Target = [u8];
#[inline(always)]
fn deref(&self) -> &[u8] {
self.0
self.buf
}
}
impl Borrow<[u8]> for BorrowedBytes<'_> {
#[inline(always)]
fn borrow(&self) -> &[u8] {
self.0
self.buf
}
}
impl AsRef<[u8]> for BorrowedBytes<'_> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.0
self.buf
}
}
impl fmt::Debug for BorrowedBytes<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
self.buf.fmt(f)
}
}
@@ -338,7 +359,7 @@ where
T: AsRef<[u8]>,
{
fn eq(&self, other: &T) -> bool {
self.0 == other.as_ref()
self.buf == other.as_ref()
}
}
@@ -349,22 +370,31 @@ where
T: AsRef<[u8]>,
{
fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
self.0.partial_cmp(other.as_ref())
self.buf.partial_cmp(other.as_ref())
}
}
impl Ord for BorrowedBytes<'_> {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.0.cmp(other.0)
self.buf.cmp(other.buf)
}
}
impl<'a> IntoIterator for BorrowedBytes<'a> {
impl<'a> IntoIterator for &'a BorrowedBytes<'_> {
type Item = &'a u8;
type IntoIter = slice::Iter<'a, u8>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
self.iter()
}
}
impl<'a> From<&'a String> for BorrowedBytes<'a> {
#[inline]
fn from(value: &'a String) -> Self {
let (buf, _lua) = unsafe { value.to_slice() };
let borrow = Cow::Borrowed(value);
Self { buf, borrow, _lua }
}
}
+34 -36
View File
@@ -15,7 +15,7 @@ use crate::value::{Nil, Value};
#[cfg(feature = "async")]
use futures_util::future::{self, Either, Future};
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
use {
rustc_hash::FxHashSet,
serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer},
@@ -468,26 +468,16 @@ impl Table {
///
/// It checks both the array part and the hash part.
pub fn is_empty(&self) -> bool {
// Check array part
if self.raw_len() != 0 {
return false;
}
// Check hash part
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, 4);
lua.push_ref(&self.0);
ffi::lua_pushnil(state);
if ffi::lua_next(state, -2) != 0 {
return false;
ffi::lua_pushnil(ref_thread);
if ffi::lua_next(ref_thread, self.0.index) == 0 {
return true;
}
ffi::lua_pop(ref_thread, 2);
}
true
false
}
/// Returns a reference to the metatable of this table, or `None` if no metatable is set.
@@ -511,13 +501,6 @@ impl Table {
}
}
#[doc(hidden)]
#[deprecated(since = "0.10.0", note = "please use `metatable` instead")]
#[cfg(not(tarpaulin_include))]
pub fn get_metatable(&self) -> Option<Table> {
self.metatable()
}
/// Sets or removes the metatable of this table.
///
/// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does
@@ -554,8 +537,6 @@ impl Table {
}
/// Sets `readonly` attribute on the table.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_readonly(&self, enabled: bool) {
@@ -571,8 +552,6 @@ impl Table {
}
/// Returns `readonly` attribute of the table.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn is_readonly(&self) -> bool {
@@ -581,6 +560,22 @@ impl Table {
unsafe { ffi::lua_getreadonly(ref_thread, self.0.index) != 0 }
}
/// Controls `safeenv` attribute on the table.
///
/// This a special flag that activates some performance optimizations for environment tables.
/// In particular, it controls:
/// - Optimization of import resolution (cache values of constant keys).
/// - Fast-path for built-in iteration with pairs/ipairs.
/// - Fast-path for some built-in functions (fastcall).
///
/// For `safeenv` environments, monkey patching or modifying values may not work as expected.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_safeenv(&self, enabled: bool) {
let lua = self.0.lua.lock();
unsafe { ffi::lua_setsafeenv(lua.ref_thread(), self.0.index, enabled as _) };
}
/// Converts this table to a generic C pointer.
///
/// Different tables will give different pointers.
@@ -618,7 +613,7 @@ impl Table {
/// ```
///
/// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next
pub fn pairs<K: FromLua, V: FromLua>(&self) -> TablePairs<K, V> {
pub fn pairs<K: FromLua, V: FromLua>(&self) -> TablePairs<'_, K, V> {
TablePairs {
guard: self.0.lua.lock(),
table: self,
@@ -683,7 +678,7 @@ impl Table {
/// # Ok(())
/// # }
/// ```
pub fn sequence_values<V: FromLua>(&self) -> TableSequence<V> {
pub fn sequence_values<V: FromLua>(&self) -> TableSequence<'_, V> {
TableSequence {
guard: self.0.lua.lock(),
table: self,
@@ -740,7 +735,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();
@@ -959,14 +954,14 @@ impl ObjectLike for Table {
}
/// 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> {
@@ -974,7 +969,7 @@ impl Serialize for Table {
}
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
impl<'a> SerializableTable<'a> {
#[inline]
pub(crate) fn new(
@@ -990,7 +985,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
@@ -1012,7 +1007,10 @@ impl Serialize for SerializableTable<'_> {
// Array
let len = self.table.raw_len();
if len > 0 || self.table.is_array() {
if len > 0
|| self.table.is_array()
|| (self.options.encode_empty_tables_as_array && self.table.is_empty())
{
let mut seq = serializer.serialize_seq(Some(len))?;
let mut serialize_err = None;
let res = self.table.for_each_value::<Value>(|value| {
+233 -120
View File
@@ -2,8 +2,7 @@ use std::fmt;
use std::os::raw::{c_int, c_void};
use crate::error::{Error, Result};
#[allow(unused)]
use crate::state::Lua;
use crate::function::Function;
use crate::state::RawLua;
use crate::traits::{FromLuaMulti, IntoLuaMulti};
use crate::types::{LuaType, ValueRef};
@@ -12,7 +11,7 @@ use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard};
#[cfg(not(feature = "luau"))]
use crate::{
hook::{Debug, HookTriggers},
types::MaybeSend,
types::HookKind,
};
#[cfg(feature = "async")]
@@ -42,6 +41,33 @@ pub enum ThreadStatus {
Error,
}
/// Internal representation of a Lua thread status.
///
/// The number in `New` and `Yielded` variants is the number of arguments pushed
/// to the thread stack.
#[derive(Clone, Copy)]
enum ThreadStatusInner {
New(c_int),
Running,
Yielded(c_int),
Finished,
Error,
}
impl ThreadStatusInner {
#[cfg(feature = "async")]
#[inline(always)]
fn is_resumable(self) -> bool {
matches!(self, ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_))
}
#[cfg(feature = "async")]
#[inline(always)]
fn is_yielded(self) -> bool {
matches!(self, ThreadStatusInner::Yielded(_))
}
}
/// Handle to an internal Lua thread (coroutine).
#[derive(Clone)]
pub struct Thread(pub(crate) ValueRef, pub(crate) *mut ffi::lua_State);
@@ -53,16 +79,13 @@ unsafe impl Sync for Thread {}
/// Thread (coroutine) representation as an async [`Future`] or [`Stream`].
///
/// Requires `feature = "async"`
///
/// [`Future`]: std::future::Future
/// [`Stream`]: futures_util::stream::Stream
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct AsyncThread<A, R> {
pub struct AsyncThread<R> {
thread: Thread,
init_args: Option<A>,
ret: PhantomData<R>,
recycle: bool,
}
@@ -122,9 +145,10 @@ impl Thread {
R: FromLuaMulti,
{
let lua = self.0.lua.lock();
if self.status_inner(&lua) != ThreadStatus::Resumable {
return Err(Error::CoroutineUnresumable);
}
let mut pushed_nargs = match self.status_inner(&lua) {
ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
_ => return Err(Error::CoroutineUnresumable),
};
let state = lua.state();
let thread_state = self.state();
@@ -132,7 +156,47 @@ impl Thread {
let _sg = StackGuard::new(state);
let _thread_sg = StackGuard::with_top(thread_state, 0);
let nresults = self.resume_inner(&lua, args)?;
let nargs = args.push_into_stack_multi(&lua)?;
if nargs > 0 {
check_stack(thread_state, nargs)?;
ffi::lua_xmove(state, thread_state, nargs);
pushed_nargs += nargs;
}
let (_, nresults) = self.resume_inner(&lua, pushed_nargs)?;
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
R::from_stack_multi(nresults, &lua)
}
}
/// Resumes execution of this thread, immediately raising an error.
///
/// This is a Luau specific extension.
#[cfg(feature = "luau")]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn resume_error<R>(&self, error: impl crate::IntoLua) -> Result<R>
where
R: FromLuaMulti,
{
let lua = self.0.lua.lock();
match self.status_inner(&lua) {
ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_) => {}
_ => return Err(Error::CoroutineUnresumable),
};
let state = lua.state();
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 (_, nresults) = self.resume_inner(&lua, ffi::LUA_RESUMEERROR)?;
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
@@ -143,66 +207,86 @@ impl Thread {
/// Resumes execution of this thread.
///
/// It's similar to `resume()` but leaves `nresults` values on the thread stack.
unsafe fn resume_inner(&self, lua: &RawLua, args: impl IntoLuaMulti) -> Result<c_int> {
unsafe fn resume_inner(&self, lua: &RawLua, nargs: c_int) -> Result<(ThreadStatusInner, c_int)> {
let state = lua.state();
let thread_state = self.state();
let nargs = args.push_into_stack_multi(lua)?;
if nargs > 0 {
check_stack(thread_state, nargs)?;
ffi::lua_xmove(state, thread_state, nargs);
}
let mut nresults = 0;
#[cfg(not(feature = "luau"))]
let ret = ffi::lua_resume(thread_state, state, nargs, &mut nresults as *mut c_int);
if ret != ffi::LUA_OK && ret != ffi::LUA_YIELD {
if ret == ffi::LUA_ERRMEM {
#[cfg(feature = "luau")]
let ret = ffi::lua_resumex(thread_state, state, nargs, &mut nresults as *mut c_int);
match ret {
ffi::LUA_OK => Ok((ThreadStatusInner::Finished, nresults)),
ffi::LUA_YIELD => Ok((ThreadStatusInner::Yielded(0), nresults)),
ffi::LUA_ERRMEM => {
// Don't call error handler for memory errors
return Err(pop_error(thread_state, ret));
Err(pop_error(thread_state, ret))
}
_ => {
check_stack(state, 3)?;
protect_lua!(state, 0, 1, |state| error_traceback_thread(state, thread_state))?;
Err(pop_error(state, ret))
}
check_stack(state, 3)?;
protect_lua!(state, 0, 1, |state| error_traceback_thread(state, thread_state))?;
return Err(pop_error(state, ret));
}
Ok(nresults)
}
/// Gets the status of the thread.
pub fn status(&self) -> ThreadStatus {
self.status_inner(&self.0.lua.lock())
match self.status_inner(&self.0.lua.lock()) {
ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_) => ThreadStatus::Resumable,
ThreadStatusInner::Running => ThreadStatus::Running,
ThreadStatusInner::Finished => ThreadStatus::Finished,
ThreadStatusInner::Error => ThreadStatus::Error,
}
}
/// Gets the status of the thread (internal implementation).
pub(crate) fn status_inner(&self, lua: &RawLua) -> ThreadStatus {
fn status_inner(&self, lua: &RawLua) -> ThreadStatusInner {
let thread_state = self.state();
if thread_state == lua.state() {
// The thread is currently running
return ThreadStatus::Running;
return ThreadStatusInner::Running;
}
let status = unsafe { ffi::lua_status(thread_state) };
if status != ffi::LUA_OK && status != ffi::LUA_YIELD {
ThreadStatus::Error
} else if status == ffi::LUA_YIELD || unsafe { ffi::lua_gettop(thread_state) > 0 } {
ThreadStatus::Resumable
} else {
ThreadStatus::Finished
let top = unsafe { ffi::lua_gettop(thread_state) };
match status {
ffi::LUA_YIELD => ThreadStatusInner::Yielded(top),
ffi::LUA_OK if top > 0 => ThreadStatusInner::New(top - 1),
ffi::LUA_OK => ThreadStatusInner::Finished,
_ => ThreadStatusInner::Error,
}
}
/// Sets a hook function that will periodically be called as Lua code executes.
///
/// This function is similar or [`Lua::set_hook`] except that it sets for the thread.
/// To remove a hook call [`Lua::remove_hook`].
/// 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)
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
where
F: Fn(&Lua, Debug) -> Result<crate::VmState> + MaybeSend + 'static,
F: Fn(&crate::Lua, Debug) -> Result<crate::VmState> + crate::MaybeSend + 'static,
{
let lua = self.0.lua.lock();
unsafe {
lua.set_thread_hook(self.state(), triggers, callback);
lua.set_thread_hook(
self.state(),
HookKind::Thread(triggers, crate::types::XRc::new(callback)),
)
}
}
/// Removes any hook function from this thread.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn remove_hook(&self) {
let _lua = self.0.lua.lock();
unsafe {
ffi::lua_sethook(self.state(), None, 0, 0);
}
}
@@ -215,31 +299,17 @@ impl Thread {
/// In Luau: resets to the initial state of a newly created Lua thread.
/// Lua threads in arbitrary states (like yielded or errored) can be reset properly.
///
/// Other Lua versions can reset only new or finished threads.
///
/// Sets a Lua function for the thread afterwards.
///
/// Requires `feature = "lua54"` OR `feature = "luau"`.
///
/// [Lua 5.4]: https://www.lua.org/manual/5.4/manual.html#lua_closethread
#[cfg(any(feature = "lua54", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "luau"))))]
pub fn reset(&self, func: crate::function::Function) -> Result<()> {
pub fn reset(&self, func: Function) -> Result<()> {
let lua = self.0.lua.lock();
if self.status_inner(&lua) == ThreadStatus::Running {
return Err(Error::runtime("cannot reset a running thread"));
}
let thread_state = self.state();
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);
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);
@@ -255,9 +325,47 @@ 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.
///
/// `args` are passed as arguments to the thread function for first call.
/// Only resumable threads can be converted to [`AsyncThread`].
///
/// `args` are pushed to the thread stack and will be used when the thread is resumed.
/// The object calls [`resume`] while polling and also allow to run Rust futures
/// to completion using an executor.
///
@@ -265,8 +373,6 @@ impl Thread {
/// values whereas [`Future`] version discards that values and poll until the final
/// one (returned from the thread function).
///
/// Requires `feature = "async"`
///
/// [`Future`]: std::future::Future
/// [`Stream`]: futures_util::stream::Stream
/// [`resume`]: https://www.lua.org/manual/5.4/manual.html#lua_resume
@@ -290,7 +396,7 @@ impl Thread {
/// end)
/// "#).eval()?;
///
/// let mut stream = thread.into_async::<i64>(1);
/// let mut stream = thread.into_async::<i64>(1)?;
/// let mut sum = 0;
/// while let Some(n) = stream.try_next().await? {
/// sum += n;
@@ -303,15 +409,31 @@ impl Thread {
/// ```
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn into_async<R>(self, args: impl IntoLuaMulti) -> AsyncThread<impl IntoLuaMulti, R>
pub fn into_async<R>(self, args: impl IntoLuaMulti) -> Result<AsyncThread<R>>
where
R: FromLuaMulti,
{
AsyncThread {
thread: self,
init_args: Some(args),
ret: PhantomData,
recycle: false,
let lua = self.0.lua.lock();
if !self.status_inner(&lua).is_resumable() {
return Err(Error::CoroutineUnresumable);
}
let state = lua.state();
let thread_state = self.state();
unsafe {
let _sg = StackGuard::new(state);
let nargs = args.push_into_stack_multi(&lua)?;
if nargs > 0 {
check_stack(thread_state, nargs)?;
ffi::lua_xmove(state, thread_state, nargs);
}
Ok(AsyncThread {
thread: self,
ret: PhantomData,
recycle: false,
})
}
}
@@ -338,7 +460,7 @@ impl Thread {
/// Ok(())
/// })?)?;
/// thread.sandbox()?;
/// thread.resume(())?;
/// thread.resume::<()>(())?;
///
/// // The global environment should be unchanged
/// assert_eq!(lua.globals().get::<Option<u32>>("var")?, None);
@@ -348,11 +470,8 @@ impl Thread {
/// # #[cfg(not(feature = "luau"))]
/// # fn main() { }
/// ```
///
/// Requires `feature = "luau"`
#[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();
@@ -392,30 +511,33 @@ impl LuaType for Thread {
}
#[cfg(feature = "async")]
impl<A, R> AsyncThread<A, R> {
#[inline]
impl<R> AsyncThread<R> {
#[inline(always)]
pub(crate) fn set_recyclable(&mut self, recyclable: bool) {
self.recycle = recyclable;
}
}
#[cfg(feature = "async")]
#[cfg(any(feature = "lua54", feature = "luau"))]
impl<A, R> Drop for AsyncThread<A, R> {
impl<R> Drop for AsyncThread<R> {
fn drop(&mut self) {
if self.recycle {
if let Some(lua) = self.thread.0.lua.try_lock() {
unsafe {
// For Lua 5.4 this also closes all pending to-be-closed variables
if !lua.recycle_thread(&mut self.thread) {
#[cfg(feature = "lua54")]
if self.thread.status_inner(&lua) == ThreadStatus::Error {
#[cfg(not(feature = "vendored"))]
ffi::lua_resetthread(self.thread.state());
#[cfg(feature = "vendored")]
ffi::lua_closethread(self.thread.state(), lua.state());
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);
}
}
}
}
@@ -423,14 +545,15 @@ impl<A, R> Drop for AsyncThread<A, R> {
}
#[cfg(feature = "async")]
impl<A: IntoLuaMulti, R: FromLuaMulti> Stream for AsyncThread<A, R> {
impl<R: FromLuaMulti> Stream for AsyncThread<R> {
type Item = Result<R>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let lua = self.thread.0.lua.lock();
if self.thread.status_inner(&lua) != ThreadStatus::Resumable {
return Poll::Ready(None);
}
let nargs = match self.thread.status_inner(&lua) {
ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
_ => return Poll::Ready(None),
};
let state = lua.state();
let thread_state = self.thread.state();
@@ -439,36 +562,34 @@ impl<A: IntoLuaMulti, R: FromLuaMulti> Stream for AsyncThread<A, R> {
let _thread_sg = StackGuard::with_top(thread_state, 0);
let _wg = WakerGuard::new(&lua, cx.waker());
// This is safe as we are not moving the whole struct
let this = self.get_unchecked_mut();
let nresults = if let Some(args) = this.init_args.take() {
this.thread.resume_inner(&lua, args)?
} else {
this.thread.resume_inner(&lua, ())?
};
let (status, nresults) = (self.thread).resume_inner(&lua, nargs)?;
if nresults == 1 && is_poll_pending(thread_state) {
return Poll::Pending;
if status.is_yielded() {
if nresults == 1 && is_poll_pending(thread_state) {
return Poll::Pending;
}
// Continue polling
cx.waker().wake_by_ref();
}
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
cx.waker().wake_by_ref();
Poll::Ready(Some(R::from_stack_multi(nresults, &lua)))
}
}
}
#[cfg(feature = "async")]
impl<A: IntoLuaMulti, R: FromLuaMulti> Future for AsyncThread<A, R> {
impl<R: FromLuaMulti> Future for AsyncThread<R> {
type Output = Result<R>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let lua = self.thread.0.lua.lock();
if self.thread.status_inner(&lua) != ThreadStatus::Resumable {
return Poll::Ready(Err(Error::CoroutineUnresumable));
}
let nargs = match self.thread.status_inner(&lua) {
ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
_ => return Poll::Ready(Err(Error::CoroutineUnresumable)),
};
let state = lua.state();
let thread_state = self.thread.state();
@@ -477,21 +598,13 @@ impl<A: IntoLuaMulti, R: FromLuaMulti> Future for AsyncThread<A, R> {
let _thread_sg = StackGuard::with_top(thread_state, 0);
let _wg = WakerGuard::new(&lua, cx.waker());
// This is safe as we are not moving the whole struct
let this = self.get_unchecked_mut();
let nresults = if let Some(args) = this.init_args.take() {
this.thread.resume_inner(&lua, args)?
} else {
this.thread.resume_inner(&lua, ())?
};
let (status, nresults) = self.thread.resume_inner(&lua, nargs)?;
if nresults == 1 && is_poll_pending(thread_state) {
return Poll::Pending;
}
if ffi::lua_status(thread_state) == ffi::LUA_YIELD {
// Ignore value returned via yield()
cx.waker().wake_by_ref();
if status.is_yielded() {
if !(nresults == 1 && is_poll_pending(thread_state)) {
// Ignore value returned via yield()
cx.waker().wake_by_ref();
}
return Poll::Pending;
}
@@ -506,7 +619,7 @@ impl<A: IntoLuaMulti, R: FromLuaMulti> Future for AsyncThread<A, R> {
#[cfg(feature = "async")]
#[inline(always)]
unsafe fn is_poll_pending(state: *mut ffi::lua_State) -> bool {
ffi::lua_tolightuserdata(state, -1) == Lua::poll_pending().0
ffi::lua_tolightuserdata(state, -1) == crate::Lua::poll_pending().0
}
#[cfg(feature = "async")]
@@ -545,7 +658,7 @@ mod assertions {
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(Thread: Send, Sync);
#[cfg(all(feature = "async", not(feature = "send")))]
static_assertions::assert_not_impl_any!(AsyncThread<(), ()>: Send);
static_assertions::assert_not_impl_any!(AsyncThread<()>: Send);
#[cfg(all(feature = "async", feature = "send"))]
static_assertions::assert_impl_all!(AsyncThread<(), ()>: Send, Sync);
static_assertions::assert_impl_all!(AsyncThread<()>: Send, Sync);
}
-4
View File
@@ -175,8 +175,6 @@ pub trait ObjectLike: Sealed {
/// Gets the function associated to key `name` from the object and asynchronously calls it,
/// passing the object itself along with `args` as function arguments.
///
/// Requires `feature = "async"`
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
@@ -195,8 +193,6 @@ pub trait ObjectLike: Sealed {
/// Gets the function associated to key `name` from the object and asynchronously calls it,
/// passing `args` as function arguments.
///
/// Requires `feature = "async"`
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
+26 -9
View File
@@ -1,10 +1,9 @@
use std::cell::UnsafeCell;
use std::os::raw::{c_int, c_void};
use std::rc::Rc;
use crate::error::Result;
#[cfg(not(feature = "luau"))]
use crate::hook::Debug;
use crate::hook::{Debug, HookTriggers};
use crate::state::{ExtraData, Lua, RawLua};
// Re-export mutex wrappers
@@ -62,7 +61,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 {
@@ -73,23 +72,41 @@ pub enum VmState {
Yield,
}
#[cfg(not(feature = "luau"))]
pub(crate) enum HookKind {
Global,
Thread(HookTriggers, HookCallback),
}
#[cfg(all(feature = "send", not(feature = "luau")))]
pub(crate) type HookCallback = Rc<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 = Rc<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 = Rc<dyn Fn(&Lua) -> Result<VmState> + Send>;
pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState> + Send>;
#[cfg(all(not(feature = "send"), feature = "luau"))]
pub(crate) type InterruptCallback = Rc<dyn Fn(&Lua) -> Result<VmState>>;
pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState>>;
#[cfg(all(feature = "send", feature = "luau"))]
pub(crate) type ThreadCreationCallback = XRc<dyn Fn(&Lua, crate::Thread) -> Result<()> + Send>;
#[cfg(all(not(feature = "send"), feature = "luau"))]
pub(crate) type ThreadCreationCallback = XRc<dyn Fn(&Lua, crate::Thread) -> Result<()>>;
#[cfg(all(feature = "send", feature = "luau"))]
pub(crate) type ThreadCollectionCallback = XRc<dyn Fn(crate::LightUserData) + Send>;
#[cfg(all(not(feature = "send"), feature = "luau"))]
pub(crate) type ThreadCollectionCallback = XRc<dyn Fn(crate::LightUserData)>;
#[cfg(all(feature = "send", feature = "lua54"))]
pub(crate) type WarnCallback = Box<dyn Fn(&Lua, &str, bool) -> Result<()> + Send>;
pub(crate) type WarnCallback = XRc<dyn Fn(&Lua, &str, bool) -> Result<()> + Send>;
#[cfg(all(not(feature = "send"), feature = "lua54"))]
pub(crate) type WarnCallback = Box<dyn Fn(&Lua, &str, bool) -> Result<()>>;
pub(crate) type WarnCallback = XRc<dyn Fn(&Lua, &str, bool) -> Result<()>>;
/// A trait that adds `Send` requirement if `send` feature is enabled.
#[cfg(feature = "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)
}
+1 -1
View File
@@ -4,7 +4,7 @@ use std::os::raw::{c_int, c_void};
use crate::state::{RawLua, WeakLua};
/// A reference to a Lua (complex) value stored in the Lua auxiliary thread.
pub(crate) struct ValueRef {
pub struct ValueRef {
pub(crate) lua: WeakLua,
pub(crate) index: c_int,
pub(crate) drop: bool,
+66 -80
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,
@@ -26,9 +26,13 @@ use {
// Re-export for convenience
pub(crate) use cell::UserDataStorage;
pub use cell::{UserDataRef, UserDataRefMut};
pub use r#ref::{UserDataRef, UserDataRefMut};
pub use registry::UserDataRegistry;
pub(crate) use registry::{RawUserDataRegistry, UserDataProxy};
pub(crate) use util::{
borrow_userdata_scoped, borrow_userdata_scoped_mut, collect_userdata, init_userdata_metatable,
TypeIdHints,
};
/// Kinds of metamethods that can be overridden.
///
@@ -52,30 +56,32 @@ pub enum MetaMethod {
/// The unary minus (`-`) operator.
Unm,
/// The floor division (//) operator.
/// Requires `feature = "lua54/lua53/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))))]
IDiv,
/// The bitwise AND (&) operator.
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
BAnd,
/// The bitwise OR (|) operator.
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
BOr,
/// The bitwise XOR (binary ~) operator.
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
BXor,
/// The bitwise NOT (unary ~) operator.
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
BNot,
/// The bitwise left shift (<<) operator.
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
Shl,
/// The bitwise right shift (>>) operator.
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
Shr,
/// The string concatenation operator `..`.
Concat,
@@ -100,16 +106,16 @@ pub enum MetaMethod {
/// The `__pairs` metamethod.
///
/// This is not an operator, but it will be called by the built-in `pairs` function.
///
/// Requires `feature = "lua54/lua53/lua52"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52",))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52")))
)]
Pairs,
/// The `__ipairs` metamethod.
///
/// This is not an operator, but it will be called by the built-in [`ipairs`] function.
///
/// Requires `feature = "lua52"`
///
/// [`ipairs`]: https://www.lua.org/manual/5.2/manual.html#pdf-ipairs
#[cfg(any(feature = "lua52", feature = "luajit52", doc))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua52", feature = "luajit52"))))]
@@ -118,8 +124,6 @@ pub enum MetaMethod {
///
/// Executed before the iteration begins, and should return an iterator function like `next`
/// (or a custom one).
///
/// Requires `feature = "lua"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
Iter,
@@ -130,8 +134,6 @@ pub enum MetaMethod {
/// More information about to-be-closed variables can be found in the Lua 5.4
/// [documentation][lua_doc].
///
/// Requires `feature = "lua54"`
///
/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#3.3.8
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
@@ -217,9 +219,7 @@ impl MetaMethod {
pub(crate) const fn as_cstr(self) -> &'static CStr {
match self {
#[rustfmt::skip]
MetaMethod::Type => unsafe {
CStr::from_bytes_with_nul_unchecked(if cfg!(feature = "luau") { b"__type\0" } else { b"__name\0" })
},
MetaMethod::Type => if cfg!(feature = "luau") { c"__type" } else { c"__name" },
_ => unreachable!(),
}
}
@@ -270,8 +270,6 @@ pub trait UserDataMethods<T> {
///
/// Refer to [`add_method`] for more information about the implementation.
///
/// Requires `feature = "async"`
///
/// [`add_method`]: UserDataMethods::add_method
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
@@ -287,8 +285,6 @@ pub trait UserDataMethods<T> {
///
/// Refer to [`add_method`] for more information about the implementation.
///
/// Requires `feature = "async"`
///
/// [`add_method`]: UserDataMethods::add_method
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
@@ -327,8 +323,6 @@ pub trait UserDataMethods<T> {
///
/// This is an async version of [`add_function`].
///
/// Requires `feature = "async"`
///
/// [`add_function`]: UserDataMethods::add_function
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
@@ -371,11 +365,12 @@ pub trait UserDataMethods<T> {
///
/// This is an async version of [`add_meta_method`].
///
/// Requires `feature = "async"`
///
/// [`add_meta_method`]: UserDataMethods::add_meta_method
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[cfg_attr(
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)
where
T: 'static,
@@ -389,8 +384,6 @@ pub trait UserDataMethods<T> {
///
/// This is an async version of [`add_meta_method_mut`].
///
/// Requires `feature = "async"`
///
/// [`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")))]
@@ -428,11 +421,12 @@ pub trait UserDataMethods<T> {
///
/// This is an async version of [`add_meta_function`].
///
/// Requires `feature = "async"`
///
/// [`add_meta_function`]: UserDataMethods::add_meta_function
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[cfg_attr(
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)
where
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
@@ -622,7 +616,9 @@ impl AnyUserData {
/// Checks whether the type of this userdata is `T`.
#[inline]
pub fn is<T: 'static>(&self) -> bool {
self.inspect::<T, _, _>(|_| Ok(())).is_ok()
let type_id = self.type_id();
// We do not use wrapped types here, rather prefer to check the "real" type of the userdata
matches!(type_id, Some(type_id) if type_id == TypeId::of::<T>())
}
/// Borrow this userdata immutably if it is of type `T`.
@@ -637,7 +633,8 @@ impl AnyUserData {
/// [`DataTypeMismatch`]: crate::Error::UserDataTypeMismatch
#[inline]
pub fn borrow<T: 'static>(&self) -> Result<UserDataRef<T>> {
self.inspect(|ud| ud.try_borrow_owned())
let lua = self.0.lua.lock();
unsafe { UserDataRef::borrow_from_stack(&lua, lua.ref_thread(), self.0.index) }
}
/// Borrow this userdata immutably if it is of type `T`, passing the borrowed value
@@ -645,7 +642,10 @@ impl AnyUserData {
///
/// This method is the only way to borrow scoped userdata (created inside [`Lua::scope`]).
pub fn borrow_scoped<T: 'static, R>(&self, f: impl FnOnce(&T) -> R) -> Result<R> {
self.inspect(|ud| ud.try_borrow_scoped(|ud| f(ud)))
let lua = self.0.lua.lock();
let type_id = lua.get_userdata_ref_type_id(&self.0)?;
let type_hints = TypeIdHints::new::<T>();
unsafe { borrow_userdata_scoped(lua.ref_thread(), self.0.index, type_id, type_hints, f) }
}
/// Borrow this userdata mutably if it is of type `T`.
@@ -660,7 +660,8 @@ impl AnyUserData {
/// [`UserDataTypeMismatch`]: crate::Error::UserDataTypeMismatch
#[inline]
pub fn borrow_mut<T: 'static>(&self) -> Result<UserDataRefMut<T>> {
self.inspect(|ud| ud.try_borrow_owned_mut())
let lua = self.0.lua.lock();
unsafe { UserDataRefMut::borrow_from_stack(&lua, lua.ref_thread(), self.0.index) }
}
/// Borrow this userdata mutably if it is of type `T`, passing the borrowed value
@@ -668,7 +669,10 @@ impl AnyUserData {
///
/// This method is the only way to borrow scoped userdata (created inside [`Lua::scope`]).
pub fn borrow_mut_scoped<T: 'static, R>(&self, f: impl FnOnce(&mut T) -> R) -> Result<R> {
self.inspect(|ud| ud.try_borrow_scoped_mut(|ud| f(ud)))
let lua = self.0.lua.lock();
let type_id = lua.get_userdata_ref_type_id(&self.0)?;
let type_hints = TypeIdHints::new::<T>();
unsafe { borrow_userdata_scoped_mut(lua.ref_thread(), self.0.index, type_id, type_hints, f) }
}
/// Takes the value out of this userdata.
@@ -679,20 +683,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>() => {
// Try to borrow userdata exclusively
let _ = (*get_userdata::<UserDataStorage<T>>(state, -1)).try_borrow_mut()?;
take_userdata::<UserDataStorage<T>>(state).into_inner()
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),
}
}
@@ -881,12 +881,6 @@ impl AnyUserData {
self.raw_metatable().map(UserDataMetatable)
}
#[doc(hidden)]
#[deprecated(since = "0.10.0", note = "please use `metatable` instead")]
pub fn get_metatable(&self) -> Result<UserDataMetatable> {
self.metatable()
}
fn raw_metatable(&self) -> Result<Table> {
let lua = self.0.lua.lock();
let state = lua.state();
@@ -910,6 +904,15 @@ impl AnyUserData {
self.0.to_pointer()
}
/// Returns [`TypeId`] of this userdata if it is registered and `'static`.
///
/// This method is not available for scoped userdata.
#[inline]
pub fn type_id(&self) -> Option<TypeId> {
let lua = self.0.lua.lock();
lua.get_userdata_ref_type_id(&self.0).ok().flatten()
}
/// Returns a type name of this `UserData` (from a metatable field).
pub(crate) fn type_name(&self) -> Result<Option<StdString>> {
let lua = self.0.lua.lock();
@@ -954,7 +957,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 {
@@ -965,24 +968,6 @@ impl AnyUserData {
};
is_serializable().unwrap_or(false)
}
pub(crate) fn inspect<T, F, R>(&self, func: F) -> Result<R>
where
T: 'static,
F: FnOnce(&UserDataStorage<T>) -> Result<R>,
{
let lua = self.0.lua.lock();
unsafe {
let type_id = lua.get_userdata_ref_type_id(&self.0)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
let ud = get_userdata::<UserDataStorage<T>>(lua.ref_thread(), self.0.index);
func(&*ud)
}
_ => Err(Error::UserDataTypeMismatch),
}
}
}
}
/// Handle to a [`AnyUserData`] metatable.
@@ -1023,7 +1008,7 @@ impl UserDataMetatable {
/// The pairs are wrapped in a [`Result`], since they are lazily converted to `V` type.
///
/// [`Result`]: crate::Result
pub fn pairs<V: FromLua>(&self) -> UserDataMetatablePairs<V> {
pub fn pairs<V: FromLua>(&self) -> UserDataMetatablePairs<'_, V> {
UserDataMetatablePairs(self.0.pairs())
}
}
@@ -1056,7 +1041,7 @@ where
}
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
impl Serialize for AnyUserData {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
@@ -1087,8 +1072,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))
}
@@ -1106,6 +1091,7 @@ where
mod cell;
mod lock;
mod object;
mod r#ref;
mod registry;
mod util;
+82 -336
View File
@@ -1,27 +1,18 @@
use std::any::{type_name, TypeId};
use std::cell::{Cell, RefCell, UnsafeCell};
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::os::raw::c_int;
use std::cell::{RefCell, UnsafeCell};
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
use serde::ser::{Serialize, Serializer};
use crate::error::{Error, Result};
use crate::state::{Lua, RawLua};
use crate::traits::FromLua;
use crate::types::XRc;
use crate::userdata::AnyUserData;
use crate::util::get_userdata;
use crate::value::Value;
use super::lock::{RawLock, UserDataLock};
use super::util::is_sync;
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> {
@@ -33,8 +24,8 @@ 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")]
Serializable(XRc<UserDataCell<Box<DynSerialize>>>),
#[cfg(feature = "serde")]
Serializable(XRc<UserDataCell<Box<DynSerialize>>>, bool), // bool is `is_sync`
}
impl<T> Clone for UserDataVariant<T> {
@@ -42,17 +33,29 @@ impl<T> Clone for UserDataVariant<T> {
fn clone(&self) -> Self {
match self {
Self::Default(inner) => Self::Default(XRc::clone(inner)),
#[cfg(feature = "serialize")]
Self::Serializable(inner) => Self::Serializable(XRc::clone(inner)),
#[cfg(feature = "serde")]
Self::Serializable(inner, is_sync) => Self::Serializable(XRc::clone(inner), *is_sync),
}
}
}
impl<T> UserDataVariant<T> {
// Immutably borrows the wrapped value in-place.
#[inline(always)]
fn try_borrow(&self) -> Result<UserDataBorrowRef<T>> {
UserDataBorrowRef::try_from(self)
pub(super) fn try_borrow_scoped<R>(&self, f: impl FnOnce(&T) -> R) -> Result<R> {
// We don't need to check for `T: Sync` because when this method is used (internally),
// Lua mutex is already locked.
// If non-`Sync` userdata is already borrowed by another thread (via `UserDataRef`), it will be
// exclusively locked.
let _guard = (self.raw_lock().try_lock_shared_guarded()).map_err(|_| Error::UserDataBorrowError)?;
Ok(f(unsafe { &*self.as_ptr() }))
}
// Mutably borrows the wrapped value in-place.
#[inline(always)]
fn try_borrow_scoped_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> Result<R> {
let _guard =
(self.raw_lock().try_lock_exclusive_guarded()).map_err(|_| Error::UserDataBorrowMutError)?;
Ok(f(unsafe { &mut *self.as_ptr() }))
}
// Immutably borrows the wrapped value and returns an owned reference.
@@ -61,12 +64,6 @@ impl<T> UserDataVariant<T> {
UserDataRef::try_from(self.clone())
}
// Mutably borrows the wrapped value in-place.
#[inline(always)]
fn try_borrow_mut(&self) -> Result<UserDataBorrowMut<T>> {
UserDataBorrowMut::try_from(self)
}
// Mutably borrows the wrapped value and returns an owned reference.
#[inline(always)]
fn try_borrow_owned_mut(&self) -> Result<UserDataRefMut<T>> {
@@ -82,8 +79,8 @@ impl<T> UserDataVariant<T> {
}
Ok(match self {
Self::Default(inner) => XRc::into_inner(inner).unwrap().value.into_inner(),
#[cfg(feature = "serialize")]
Self::Serializable(inner) => unsafe {
#[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,45 +88,55 @@ impl<T> UserDataVariant<T> {
}
#[inline(always)]
fn raw_lock(&self) -> &RawLock {
fn strong_count(&self) -> usize {
match self {
Self::Default(inner) => XRc::strong_count(inner),
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => XRc::strong_count(inner),
}
}
#[inline(always)]
pub(super) fn raw_lock(&self) -> &RawLock {
match self {
Self::Default(inner) => &inner.raw_lock,
#[cfg(feature = "serialize")]
Self::Serializable(inner) => &inner.raw_lock,
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => &inner.raw_lock,
}
}
#[inline(always)]
fn borrow_count(&self) -> &Cell<usize> {
match self {
Self::Default(inner) => &inner.borrow_count,
#[cfg(feature = "serialize")]
Self::Serializable(inner) => &inner.borrow_count,
}
}
#[inline(always)]
fn as_ptr(&self) -> *mut T {
pub(super) fn as_ptr(&self) -> *mut T {
match self {
Self::Default(inner) => inner.value.get(),
#[cfg(feature = "serialize")]
Self::Serializable(inner) => unsafe { &mut **(inner.value.get() as *mut Box<T>) },
#[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 {
Self::Owned(UserDataVariant::Serializable(inner)) => unsafe {
// We need to borrow the inner value exclusively to serialize it.
Self::Owned(variant @ UserDataVariant::Serializable(inner, is_sync)) => unsafe {
#[cfg(feature = "send")]
let _guard = self.try_borrow_mut().map_err(serde::ser::Error::custom)?;
// No need to do this if the `send` feature is disabled.
if *is_sync {
let _guard = (variant.raw_lock().try_lock_shared_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
} else {
let _guard = (variant.raw_lock().try_lock_exclusive_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
}
#[cfg(not(feature = "send"))]
let _guard = self.try_borrow().map_err(serde::ser::Error::custom)?;
(*inner.value.get()).serialize(serializer)
{
let _ = is_sync;
let _guard = (variant.raw_lock().try_lock_shared_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
}
},
_ => Err(serde::ser::Error::custom("cannot serialize <userdata>")),
}
@@ -139,7 +146,6 @@ impl Serialize for UserDataStorage<()> {
/// A type that provides interior mutability for a userdata value (thread-safe).
pub(crate) struct UserDataCell<T> {
raw_lock: RawLock,
borrow_count: Cell<usize>,
value: UnsafeCell<T>,
}
@@ -153,242 +159,11 @@ impl<T> UserDataCell<T> {
fn new(value: T) -> Self {
UserDataCell {
raw_lock: RawLock::INIT,
borrow_count: Cell::new(0),
value: UnsafeCell::new(value),
}
}
}
/// A wrapper type for a userdata value that provides read access.
///
/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua.
pub struct UserDataRef<T>(UserDataVariant<T>);
impl<T> Deref for UserDataRef<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { &*self.0.as_ptr() }
}
}
impl<T> Drop for UserDataRef<T> {
#[inline]
fn drop(&mut self) {
if !cfg!(feature = "send") || is_sync::<T>() {
unsafe { self.0.raw_lock().unlock_shared() };
} else {
unsafe { self.0.raw_lock().unlock_exclusive() };
}
}
}
impl<T: fmt::Debug> fmt::Debug for UserDataRef<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for UserDataRef<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T> TryFrom<UserDataVariant<T>> for UserDataRef<T> {
type Error = Error;
#[inline]
fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
if !cfg!(feature = "send") || is_sync::<T>() {
if !variant.raw_lock().try_lock_shared() {
return Err(Error::UserDataBorrowError);
}
} else if !variant.raw_lock().try_lock_exclusive() {
return Err(Error::UserDataBorrowError);
}
Ok(UserDataRef(variant))
}
}
impl<T: 'static> FromLua for UserDataRef<T> {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
try_value_to_userdata::<T>(value)?.borrow()
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let type_id = lua.get_userdata_type_id::<T>(idx)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
(*get_userdata::<UserDataStorage<T>>(lua.state(), idx)).try_borrow_owned()
}
_ => Err(Error::UserDataTypeMismatch),
}
}
}
/// A wrapper type for a userdata value that provides read and write access.
///
/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua.
pub struct UserDataRefMut<T>(UserDataVariant<T>);
impl<T> Deref for UserDataRefMut<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { &*self.0.as_ptr() }
}
}
impl<T> DerefMut for UserDataRefMut<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.0.as_ptr() }
}
}
impl<T> Drop for UserDataRefMut<T> {
#[inline]
fn drop(&mut self) {
unsafe { self.0.raw_lock().unlock_exclusive() };
}
}
impl<T: fmt::Debug> fmt::Debug for UserDataRefMut<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for UserDataRefMut<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T> TryFrom<UserDataVariant<T>> for UserDataRefMut<T> {
type Error = Error;
#[inline]
fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
if !variant.raw_lock().try_lock_exclusive() {
return Err(Error::UserDataBorrowMutError);
}
Ok(UserDataRefMut(variant))
}
}
impl<T: 'static> FromLua for UserDataRefMut<T> {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
try_value_to_userdata::<T>(value)?.borrow_mut()
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let type_id = lua.get_userdata_type_id::<T>(idx)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
(*get_userdata::<UserDataStorage<T>>(lua.state(), idx)).try_borrow_owned_mut()
}
_ => Err(Error::UserDataTypeMismatch),
}
}
}
/// A type that provides read access to a userdata value (borrowing the value).
pub(crate) struct UserDataBorrowRef<'a, T>(&'a UserDataVariant<T>);
impl<T> Drop for UserDataBorrowRef<'_, T> {
#[inline]
fn drop(&mut self) {
unsafe {
self.0.borrow_count().set(self.0.borrow_count().get() - 1);
self.0.raw_lock().unlock_shared();
}
}
}
impl<T> Deref for UserDataBorrowRef<'_, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
// SAFETY: `UserDataBorrowRef` is only created with shared access to the value.
unsafe { &*self.0.as_ptr() }
}
}
impl<'a, T> TryFrom<&'a UserDataVariant<T>> for UserDataBorrowRef<'a, T> {
type Error = Error;
#[inline(always)]
fn try_from(variant: &'a UserDataVariant<T>) -> Result<Self> {
// We don't need to check for `T: Sync` because when this method is used (internally),
// Lua mutex is already locked.
// If non-`Sync` userdata is already borrowed by another thread (via `UserDataRef`), it will be
// exclusively locked.
if !variant.raw_lock().try_lock_shared() {
return Err(Error::UserDataBorrowError);
}
variant.borrow_count().set(variant.borrow_count().get() + 1);
Ok(UserDataBorrowRef(variant))
}
}
pub(crate) struct UserDataBorrowMut<'a, T>(&'a UserDataVariant<T>);
impl<T> Drop for UserDataBorrowMut<'_, T> {
#[inline]
fn drop(&mut self) {
unsafe {
self.0.borrow_count().set(self.0.borrow_count().get() - 1);
self.0.raw_lock().unlock_exclusive();
}
}
}
impl<T> Deref for UserDataBorrowMut<'_, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { &*self.0.as_ptr() }
}
}
impl<T> DerefMut for UserDataBorrowMut<'_, T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.0.as_ptr() }
}
}
impl<'a, T> TryFrom<&'a UserDataVariant<T>> for UserDataBorrowMut<'a, T> {
type Error = Error;
#[inline(always)]
fn try_from(variant: &'a UserDataVariant<T>) -> Result<Self> {
if !variant.raw_lock().try_lock_exclusive() {
return Err(Error::UserDataBorrowMutError);
}
variant.borrow_count().set(variant.borrow_count().get() + 1);
Ok(UserDataBorrowMut(variant))
}
}
#[inline]
fn try_value_to_userdata<T>(value: Value) -> Result<AnyUserData> {
match value {
Value::UserData(ud) => Ok(ud),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "userdata".to_string(),
message: Some(format!("expected userdata of type {}", type_name::<T>())),
}),
}
}
pub(crate) enum ScopedUserDataVariant<T> {
Ref(*const T),
RefMut(RefCell<*mut T>),
@@ -422,20 +197,22 @@ 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
T: Serialize + crate::types::MaybeSend,
{
let data = Box::new(data) as Box<DynSerialize>;
Self::Owned(UserDataVariant::Serializable(XRc::new(UserDataCell::new(data))))
let is_sync = super::util::is_sync::<T>();
let variant = UserDataVariant::Serializable(XRc::new(UserDataCell::new(data)), is_sync);
Self::Owned(variant)
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
#[inline(always)]
pub(crate) fn is_serializable(&self) -> bool {
matches!(self, Self::Owned(UserDataVariant::Serializable(_)))
matches!(self, Self::Owned(UserDataVariant::Serializable(..)))
}
// Immutably borrows the wrapped value and returns an owned reference.
@@ -447,23 +224,6 @@ impl<T: 'static> UserDataStorage<T> {
}
}
#[allow(unused)]
#[inline(always)]
pub(crate) fn try_borrow(&self) -> Result<UserDataBorrowRef<T>> {
match self {
Self::Owned(data) => data.try_borrow(),
Self::Scoped(_) => Err(Error::UserDataTypeMismatch),
}
}
#[inline(always)]
pub(crate) fn try_borrow_mut(&self) -> Result<UserDataBorrowMut<T>> {
match self {
Self::Owned(data) => data.try_borrow_mut(),
Self::Scoped(_) => Err(Error::UserDataTypeMismatch),
}
}
// Mutably borrows the wrapped value and returns an owned reference.
#[inline(always)]
pub(crate) fn try_borrow_owned_mut(&self) -> Result<UserDataRefMut<T>> {
@@ -489,18 +249,31 @@ impl<T> UserDataStorage<T> {
Self::Scoped(ScopedUserDataVariant::Boxed(RefCell::new(data)))
}
/// Returns `true` if it's safe to destroy the container.
///
/// It's safe to destroy the container if the reference count is greater than 1 or the lock is
/// not acquired.
#[inline(always)]
pub(crate) fn is_borrowed(&self) -> bool {
pub(crate) fn is_safe_to_destroy(&self) -> bool {
match self {
Self::Owned(variant) => variant.borrow_count().get() > 0,
Self::Scoped(_) => true,
Self::Owned(variant) => variant.strong_count() > 1 || !variant.raw_lock().is_locked(),
Self::Scoped(_) => false,
}
}
/// Returns `true` if the container has exclusive access to the value.
#[inline(always)]
pub(crate) fn has_exclusive_access(&self) -> bool {
match self {
Self::Owned(variant) => !variant.raw_lock().is_locked(),
Self::Scoped(_) => false,
}
}
#[inline]
pub(crate) fn try_borrow_scoped<R>(&self, f: impl FnOnce(&T) -> R) -> Result<R> {
match self {
Self::Owned(data) => Ok(f(&*data.try_borrow()?)),
Self::Owned(data) => data.try_borrow_scoped(f),
Self::Scoped(ScopedUserDataVariant::Ref(value)) => Ok(f(unsafe { &**value })),
Self::Scoped(ScopedUserDataVariant::RefMut(value) | ScopedUserDataVariant::Boxed(value)) => {
let t = value.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
@@ -512,7 +285,7 @@ impl<T> UserDataStorage<T> {
#[inline]
pub(crate) fn try_borrow_scoped_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> Result<R> {
match self {
Self::Owned(data) => Ok(f(&mut *data.try_borrow_mut()?)),
Self::Owned(data) => data.try_borrow_scoped_mut(f),
Self::Scoped(ScopedUserDataVariant::Ref(_)) => Err(Error::UserDataBorrowMutError),
Self::Scoped(ScopedUserDataVariant::RefMut(value) | ScopedUserDataVariant::Boxed(value)) => {
let mut t = value
@@ -523,30 +296,3 @@ impl<T> UserDataStorage<T> {
}
}
}
#[cfg(test)]
mod assertions {
use super::*;
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(UserDataRef<()>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_not_impl_all!(UserDataRef<std::rc::Rc<()>>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(UserDataRefMut<()>: Sync, Send);
#[cfg(feature = "send")]
static_assertions::assert_not_impl_all!(UserDataRefMut<std::rc::Rc<()>>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(UserDataBorrowRef<'_, ()>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(UserDataBorrowMut<'_, ()>: Send, Sync);
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_all!(UserDataRef<()>: Send, Sync);
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_all!(UserDataRefMut<()>: Send, Sync);
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_all!(UserDataBorrowRef<'_, ()>: Send, Sync);
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_all!(UserDataBorrowMut<'_, ()>: Send, Sync);
}
+50
View File
@@ -1,11 +1,51 @@
pub(crate) trait UserDataLock {
const INIT: Self;
fn is_locked(&self) -> bool;
fn try_lock_shared(&self) -> bool;
fn try_lock_exclusive(&self) -> bool;
unsafe fn unlock_shared(&self);
unsafe fn unlock_exclusive(&self);
fn try_lock_shared_guarded(&self) -> Result<LockGuard<'_, Self>, ()> {
if self.try_lock_shared() {
Ok(LockGuard {
lock: self,
exclusive: false,
})
} else {
Err(())
}
}
fn try_lock_exclusive_guarded(&self) -> Result<LockGuard<'_, Self>, ()> {
if self.try_lock_exclusive() {
Ok(LockGuard {
lock: self,
exclusive: true,
})
} else {
Err(())
}
}
}
pub(crate) struct LockGuard<'a, L: UserDataLock + ?Sized> {
lock: &'a L,
exclusive: bool,
}
impl<L: UserDataLock + ?Sized> Drop for LockGuard<'_, L> {
fn drop(&mut self) {
unsafe {
if self.exclusive {
self.lock.unlock_exclusive();
} else {
self.lock.unlock_shared();
}
}
}
}
pub(crate) use lock_impl::RawLock;
@@ -25,6 +65,11 @@ mod lock_impl {
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Self = Cell::new(UNUSED);
#[inline(always)]
fn is_locked(&self) -> bool {
self.get() != UNUSED
}
#[inline(always)]
fn try_lock_shared(&self) -> bool {
let flag = self.get().wrapping_add(1);
@@ -71,6 +116,11 @@ mod lock_impl {
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Self = <Self as parking_lot::lock_api::RawRwLock>::INIT;
#[inline(always)]
fn is_locked(&self) -> bool {
RawRwLock::is_locked(self)
}
#[inline(always)]
fn try_lock_shared(&self) -> bool {
RawRwLock::try_lock_shared(self)
+474
View File
@@ -0,0 +1,474 @@
use std::any::{type_name, TypeId};
use std::ops::{Deref, DerefMut};
use std::os::raw::c_int;
use std::{fmt, mem};
use crate::error::{Error, Result};
use crate::state::{Lua, RawLua};
use crate::traits::FromLua;
use crate::userdata::AnyUserData;
use crate::util::get_userdata;
use crate::value::Value;
use super::cell::{UserDataStorage, UserDataVariant};
use super::lock::{LockGuard, RawLock, UserDataLock};
use super::util::is_sync;
#[cfg(feature = "userdata-wrappers")]
use {
parking_lot::{
Mutex as MutexPL, MutexGuard as MutexGuardPL, RwLock as RwLockPL,
RwLockReadGuard as RwLockReadGuardPL, RwLockWriteGuard as RwLockWriteGuardPL,
},
std::sync::Arc,
};
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
use {
std::cell::{Ref, RefCell, RefMut},
std::rc::Rc,
};
/// A wrapper type for a userdata value that provides read access.
///
/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua.
pub struct UserDataRef<T: 'static> {
// It's important to drop the guard first, as it refers to the `inner` data.
_guard: LockGuard<'static, RawLock>,
inner: UserDataRefInner<T>,
}
impl<T> Deref for UserDataRef<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
&self.inner
}
}
impl<T: fmt::Debug> fmt::Debug for UserDataRef<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for UserDataRef<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T> TryFrom<UserDataVariant<T>> for UserDataRef<T> {
type Error = Error;
#[inline]
fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
let guard = if !cfg!(feature = "send") || is_sync::<T>() {
variant.raw_lock().try_lock_shared_guarded()
} else {
variant.raw_lock().try_lock_exclusive_guarded()
};
let guard = guard.map_err(|_| Error::UserDataBorrowError)?;
let guard = unsafe { mem::transmute::<LockGuard<_>, LockGuard<'static, _>>(guard) };
Ok(UserDataRef::from_parts(UserDataRefInner::Default(variant), guard))
}
}
impl<T: 'static> FromLua for UserDataRef<T> {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
try_value_to_userdata::<T>(value)?.borrow()
}
#[inline]
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
Self::borrow_from_stack(lua, lua.state(), idx)
}
}
impl<T: 'static> UserDataRef<T> {
#[inline(always)]
fn from_parts(inner: UserDataRefInner<T>, guard: LockGuard<'static, RawLock>) -> Self {
Self { _guard: guard, inner }
}
#[cfg(feature = "userdata-wrappers")]
fn remap<U>(
self,
f: impl FnOnce(UserDataVariant<T>) -> Result<UserDataRefInner<U>>,
) -> Result<UserDataRef<U>> {
match &self.inner {
UserDataRefInner::Default(variant) => {
let inner = f(variant.clone())?;
Ok(UserDataRef::from_parts(inner, self._guard))
}
_ => Err(Error::UserDataTypeMismatch),
}
}
pub(crate) unsafe fn borrow_from_stack(
lua: &RawLua,
state: *mut ffi::lua_State,
idx: c_int,
) -> Result<Self> {
let type_id = lua.get_userdata_type_id::<T>(state, idx)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
let ud = get_userdata::<UserDataStorage<T>>(state, idx);
(*ud).try_borrow_owned()
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == TypeId::of::<Rc<T>>() => {
let ud = get_userdata::<UserDataStorage<Rc<T>>>(state, idx);
((*ud).try_borrow_owned()).and_then(|ud| ud.transform_rc())
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud = get_userdata::<UserDataStorage<Rc<RefCell<T>>>>(state, idx);
((*ud).try_borrow_owned()).and_then(|ud| ud.transform_rc_refcell())
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == TypeId::of::<Arc<T>>() => {
let ud = get_userdata::<UserDataStorage<Arc<T>>>(state, idx);
((*ud).try_borrow_owned()).and_then(|ud| ud.transform_arc())
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == TypeId::of::<Arc<MutexPL<T>>>() => {
let ud = get_userdata::<UserDataStorage<Arc<MutexPL<T>>>>(state, idx);
((*ud).try_borrow_owned()).and_then(|ud| ud.transform_arc_mutex_pl())
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == TypeId::of::<Arc<RwLockPL<T>>>() => {
let ud = get_userdata::<UserDataStorage<Arc<RwLockPL<T>>>>(state, idx);
((*ud).try_borrow_owned()).and_then(|ud| ud.transform_arc_rwlock_pl())
}
_ => Err(Error::UserDataTypeMismatch),
}
}
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
impl<T> UserDataRef<Rc<T>> {
fn transform_rc(self) -> Result<UserDataRef<T>> {
self.remap(|variant| Ok(UserDataRefInner::Rc(variant)))
}
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
impl<T> UserDataRef<Rc<RefCell<T>>> {
fn transform_rc_refcell(self) -> Result<UserDataRef<T>> {
self.remap(|variant| unsafe {
let obj = &*variant.as_ptr();
let r#ref = obj.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
let borrow = std::mem::transmute::<Ref<T>, Ref<'static, T>>(r#ref);
Ok(UserDataRefInner::RcRefCell(borrow, variant))
})
}
}
#[cfg(feature = "userdata-wrappers")]
impl<T> UserDataRef<Arc<T>> {
fn transform_arc(self) -> Result<UserDataRef<T>> {
self.remap(|variant| Ok(UserDataRefInner::Arc(variant)))
}
}
#[cfg(feature = "userdata-wrappers")]
impl<T> UserDataRef<Arc<MutexPL<T>>> {
fn transform_arc_mutex_pl(self) -> Result<UserDataRef<T>> {
self.remap(|variant| unsafe {
let obj = &*variant.as_ptr();
let guard = obj.try_lock().ok_or(Error::UserDataBorrowError)?;
let borrow = std::mem::transmute::<MutexGuardPL<T>, MutexGuardPL<'static, T>>(guard);
Ok(UserDataRefInner::ArcMutexPL(borrow, variant))
})
}
}
#[cfg(feature = "userdata-wrappers")]
impl<T> UserDataRef<Arc<RwLockPL<T>>> {
fn transform_arc_rwlock_pl(self) -> Result<UserDataRef<T>> {
self.remap(|variant| unsafe {
let obj = &*variant.as_ptr();
let guard = obj.try_read().ok_or(Error::UserDataBorrowError)?;
let borrow = std::mem::transmute::<RwLockReadGuardPL<T>, RwLockReadGuardPL<'static, T>>(guard);
Ok(UserDataRefInner::ArcRwLockPL(borrow, variant))
})
}
}
#[allow(unused)]
enum UserDataRefInner<T: 'static> {
Default(UserDataVariant<T>),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Rc(UserDataVariant<Rc<T>>),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
RcRefCell(Ref<'static, T>, UserDataVariant<Rc<RefCell<T>>>),
#[cfg(feature = "userdata-wrappers")]
Arc(UserDataVariant<Arc<T>>),
#[cfg(feature = "userdata-wrappers")]
ArcMutexPL(MutexGuardPL<'static, T>, UserDataVariant<Arc<MutexPL<T>>>),
#[cfg(feature = "userdata-wrappers")]
ArcRwLockPL(RwLockReadGuardPL<'static, T>, UserDataVariant<Arc<RwLockPL<T>>>),
}
impl<T> Deref for UserDataRefInner<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
match self {
Self::Default(inner) => unsafe { &*inner.as_ptr() },
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Self::Rc(inner) => unsafe { &*Rc::as_ptr(&*inner.as_ptr()) },
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Self::RcRefCell(x, ..) => x,
#[cfg(feature = "userdata-wrappers")]
Self::Arc(inner) => unsafe { &*Arc::as_ptr(&*inner.as_ptr()) },
#[cfg(feature = "userdata-wrappers")]
Self::ArcMutexPL(x, ..) => x,
#[cfg(feature = "userdata-wrappers")]
Self::ArcRwLockPL(x, ..) => x,
}
}
}
/// A wrapper type for a userdata value that provides read and write access.
///
/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua.
pub struct UserDataRefMut<T: 'static> {
// It's important to drop the guard first, as it refers to the `inner` data.
_guard: LockGuard<'static, RawLock>,
inner: UserDataRefMutInner<T>,
}
impl<T> Deref for UserDataRefMut<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> DerefMut for UserDataRefMut<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl<T: fmt::Debug> fmt::Debug for UserDataRefMut<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for UserDataRefMut<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T> TryFrom<UserDataVariant<T>> for UserDataRefMut<T> {
type Error = Error;
#[inline]
fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
let guard = variant.raw_lock().try_lock_exclusive_guarded();
let guard = guard.map_err(|_| Error::UserDataBorrowMutError)?;
let guard = unsafe { mem::transmute::<LockGuard<_>, LockGuard<'static, _>>(guard) };
Ok(UserDataRefMut::from_parts(
UserDataRefMutInner::Default(variant),
guard,
))
}
}
impl<T: 'static> FromLua for UserDataRefMut<T> {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
try_value_to_userdata::<T>(value)?.borrow_mut()
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
Self::borrow_from_stack(lua, lua.state(), idx)
}
}
impl<T: 'static> UserDataRefMut<T> {
#[inline(always)]
fn from_parts(inner: UserDataRefMutInner<T>, guard: LockGuard<'static, RawLock>) -> Self {
Self { _guard: guard, inner }
}
#[cfg(feature = "userdata-wrappers")]
fn remap<U>(
self,
f: impl FnOnce(UserDataVariant<T>) -> Result<UserDataRefMutInner<U>>,
) -> Result<UserDataRefMut<U>> {
match &self.inner {
UserDataRefMutInner::Default(variant) => {
let inner = f(variant.clone())?;
Ok(UserDataRefMut::from_parts(inner, self._guard))
}
_ => Err(Error::UserDataTypeMismatch),
}
}
pub(crate) unsafe fn borrow_from_stack(
lua: &RawLua,
state: *mut ffi::lua_State,
idx: c_int,
) -> Result<Self> {
let type_id = lua.get_userdata_type_id::<T>(state, idx)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
let ud = get_userdata::<UserDataStorage<T>>(state, idx);
(*ud).try_borrow_owned_mut()
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == TypeId::of::<Rc<T>>() => Err(Error::UserDataBorrowMutError),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud = get_userdata::<UserDataStorage<Rc<RefCell<T>>>>(state, idx);
((*ud).try_borrow_owned_mut()).and_then(|ud| ud.transform_rc_refcell())
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == TypeId::of::<Arc<T>>() => Err(Error::UserDataBorrowMutError),
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == TypeId::of::<Arc<MutexPL<T>>>() => {
let ud = get_userdata::<UserDataStorage<Arc<MutexPL<T>>>>(state, idx);
((*ud).try_borrow_owned_mut()).and_then(|ud| ud.transform_arc_mutex_pl())
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == TypeId::of::<Arc<RwLockPL<T>>>() => {
let ud = get_userdata::<UserDataStorage<Arc<RwLockPL<T>>>>(state, idx);
((*ud).try_borrow_owned_mut()).and_then(|ud| ud.transform_arc_rwlock_pl())
}
_ => Err(Error::UserDataTypeMismatch),
}
}
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
impl<T> UserDataRefMut<Rc<RefCell<T>>> {
fn transform_rc_refcell(self) -> Result<UserDataRefMut<T>> {
self.remap(|variant| unsafe {
let obj = &*variant.as_ptr();
let refmut = obj.try_borrow_mut().map_err(|_| Error::UserDataBorrowMutError)?;
let borrow = std::mem::transmute::<RefMut<T>, RefMut<'static, T>>(refmut);
Ok(UserDataRefMutInner::RcRefCell(borrow, variant))
})
}
}
#[cfg(feature = "userdata-wrappers")]
impl<T> UserDataRefMut<Arc<MutexPL<T>>> {
fn transform_arc_mutex_pl(self) -> Result<UserDataRefMut<T>> {
self.remap(|variant| unsafe {
let obj = &*variant.as_ptr();
let guard = obj.try_lock().ok_or(Error::UserDataBorrowMutError)?;
let borrow = std::mem::transmute::<MutexGuardPL<T>, MutexGuardPL<'static, T>>(guard);
Ok(UserDataRefMutInner::ArcMutexPL(borrow, variant))
})
}
}
#[cfg(feature = "userdata-wrappers")]
impl<T> UserDataRefMut<Arc<RwLockPL<T>>> {
fn transform_arc_rwlock_pl(self) -> Result<UserDataRefMut<T>> {
self.remap(|variant| unsafe {
let obj = &*variant.as_ptr();
let guard = obj.try_write().ok_or(Error::UserDataBorrowMutError)?;
let borrow = std::mem::transmute::<RwLockWriteGuardPL<T>, RwLockWriteGuardPL<'static, T>>(guard);
Ok(UserDataRefMutInner::ArcRwLockPL(borrow, variant))
})
}
}
#[allow(unused)]
enum UserDataRefMutInner<T: 'static> {
Default(UserDataVariant<T>),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
RcRefCell(RefMut<'static, T>, UserDataVariant<Rc<RefCell<T>>>),
#[cfg(feature = "userdata-wrappers")]
ArcMutexPL(MutexGuardPL<'static, T>, UserDataVariant<Arc<MutexPL<T>>>),
#[cfg(feature = "userdata-wrappers")]
ArcRwLockPL(RwLockWriteGuardPL<'static, T>, UserDataVariant<Arc<RwLockPL<T>>>),
}
impl<T> Deref for UserDataRefMutInner<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
match self {
Self::Default(inner) => unsafe { &*inner.as_ptr() },
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Self::RcRefCell(x, ..) => x,
#[cfg(feature = "userdata-wrappers")]
Self::ArcMutexPL(x, ..) => x,
#[cfg(feature = "userdata-wrappers")]
Self::ArcRwLockPL(x, ..) => x,
}
}
}
impl<T> DerefMut for UserDataRefMutInner<T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
match self {
Self::Default(inner) => unsafe { &mut *inner.as_ptr() },
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Self::RcRefCell(x, ..) => x,
#[cfg(feature = "userdata-wrappers")]
Self::ArcMutexPL(x, ..) => x,
#[cfg(feature = "userdata-wrappers")]
Self::ArcRwLockPL(x, ..) => x,
}
}
}
#[inline]
fn try_value_to_userdata<T>(value: Value) -> Result<AnyUserData> {
match value {
Value::UserData(ud) => Ok(ud),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "userdata".to_string(),
message: Some(format!("expected userdata of type {}", type_name::<T>())),
}),
}
}
#[cfg(test)]
mod assertions {
use super::*;
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(UserDataRef<()>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_not_impl_all!(UserDataRef<std::rc::Rc<()>>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(UserDataRefMut<()>: Sync, Send);
#[cfg(feature = "send")]
static_assertions::assert_not_impl_all!(UserDataRefMut<std::rc::Rc<()>>: Send, Sync);
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_all!(UserDataRef<()>: Send, Sync);
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_all!(UserDataRefMut<()>: Send, Sync);
}
+55 -240
View File
@@ -10,8 +10,11 @@ use crate::error::{Error, Result};
use crate::state::{Lua, LuaGuard};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{Callback, MaybeSend};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataStorage};
use crate::util::{get_userdata, short_type_name};
use crate::userdata::{
borrow_userdata_scoped, borrow_userdata_scoped_mut, AnyUserData, MetaMethod, TypeIdHints, UserData,
UserDataFields, UserDataMethods, UserDataStorage,
};
use crate::util::short_type_name;
use crate::value::Value;
#[cfg(feature = "async")]
@@ -21,38 +24,18 @@ use {
std::future::{self, Future},
};
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
use std::rc::Rc;
#[cfg(feature = "userdata-wrappers")]
use std::sync::{Arc, Mutex, RwLock};
#[derive(Clone, Copy)]
enum UserDataTypeId {
Shared(TypeId),
enum UserDataType {
Shared(TypeIdHints),
Unique(*mut c_void),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Rc(TypeId),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
RcRefCell(TypeId),
#[cfg(feature = "userdata-wrappers")]
Arc(TypeId),
#[cfg(feature = "userdata-wrappers")]
ArcMutex(TypeId),
#[cfg(feature = "userdata-wrappers")]
ArcRwLock(TypeId),
#[cfg(feature = "userdata-wrappers")]
ArcParkingLotMutex(TypeId),
#[cfg(feature = "userdata-wrappers")]
ArcParkingLotRwLock(TypeId),
}
/// Handle to registry for userdata methods and metamethods.
pub struct UserDataRegistry<T> {
lua: LuaGuard,
raw: RawUserDataRegistry,
ud_type_id: UserDataTypeId,
_type: PhantomData<T>,
r#type: UserDataType,
_phantom: PhantomData<T>,
}
pub(crate) struct RawUserDataRegistry {
@@ -75,46 +58,34 @@ pub(crate) struct RawUserDataRegistry {
pub(crate) type_name: StdString,
}
impl UserDataTypeId {
impl UserDataType {
#[inline]
pub(crate) fn type_id(self) -> Option<TypeId> {
pub(crate) fn type_id(&self) -> Option<TypeId> {
match self {
UserDataTypeId::Shared(type_id) => Some(type_id),
UserDataTypeId::Unique(_) => None,
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
UserDataTypeId::Rc(type_id) => Some(type_id),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
UserDataTypeId::RcRefCell(type_id) => Some(type_id),
#[cfg(feature = "userdata-wrappers")]
UserDataTypeId::Arc(type_id) => Some(type_id),
#[cfg(feature = "userdata-wrappers")]
UserDataTypeId::ArcMutex(type_id) => Some(type_id),
#[cfg(feature = "userdata-wrappers")]
UserDataTypeId::ArcRwLock(type_id) => Some(type_id),
#[cfg(feature = "userdata-wrappers")]
UserDataTypeId::ArcParkingLotMutex(type_id) => Some(type_id),
#[cfg(feature = "userdata-wrappers")]
UserDataTypeId::ArcParkingLotRwLock(type_id) => Some(type_id),
UserDataType::Shared(hints) => Some(hints.type_id()),
UserDataType::Unique(_) => None,
}
}
}
#[cfg(feature = "send")]
unsafe impl Send for UserDataTypeId {}
unsafe impl Send for UserDataType {}
impl<T: 'static> UserDataRegistry<T> {
#[inline(always)]
pub(crate) fn new(lua: &Lua) -> Self {
Self::with_type(lua, UserDataType::Shared(TypeIdHints::new::<T>()))
}
}
impl<T> UserDataRegistry<T> {
#[inline(always)]
pub(crate) fn new(lua: &Lua, type_id: TypeId) -> Self {
Self::with_type_id(lua, UserDataTypeId::Shared(type_id))
}
#[inline(always)]
pub(crate) fn new_unique(lua: &Lua, ud_ptr: *mut c_void) -> Self {
Self::with_type_id(lua, UserDataTypeId::Unique(ud_ptr))
Self::with_type(lua, UserDataType::Unique(ud_ptr))
}
#[inline(always)]
fn with_type_id(lua: &Lua, ud_type_id: UserDataTypeId) -> Self {
fn with_type(lua: &Lua, r#type: UserDataType) -> Self {
let raw = RawUserDataRegistry {
fields: Vec::new(),
field_getters: Vec::new(),
@@ -126,16 +97,16 @@ impl<T> UserDataRegistry<T> {
meta_methods: Vec::new(),
#[cfg(feature = "async")]
async_meta_methods: Vec::new(),
destructor: super::util::userdata_destructor::<T>,
type_id: ud_type_id.type_id(),
destructor: super::util::destroy_userdata_storage::<T>,
type_id: r#type.type_id(),
type_name: short_type_name::<T>(),
};
UserDataRegistry {
lua: lua.lock_arc(),
raw,
ud_type_id,
_type: PhantomData,
r#type,
_phantom: PhantomData,
}
}
@@ -152,7 +123,7 @@ impl<T> UserDataRegistry<T> {
};
}
let target_type_id = self.ud_type_id;
let target_type = self.r#type;
Box::new(move |rawlua, nargs| unsafe {
if nargs == 0 {
let err = Error::from_lua_conversion("missing argument", "userdata", None);
@@ -164,103 +135,24 @@ impl<T> UserDataRegistry<T> {
// Self was at position 1, so we pass 2 here
let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua);
match target_type_id {
match target_type {
#[rustfmt::skip]
UserDataTypeId::Shared(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<T>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<T>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
UserDataType::Shared(type_hints) => {
let type_id = try_self_arg!(rawlua.get_userdata_type_id::<T>(state, self_index));
try_self_arg!(borrow_userdata_scoped(state, self_index, type_id, type_hints, |ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[rustfmt::skip]
UserDataTypeId::Unique(target_ptr)
if get_userdata::<UserDataStorage<T>>(state, self_index) as *mut c_void == target_ptr =>
{
UserDataType::Unique(target_ptr) if ffi::lua_touserdata(state, self_index) == target_ptr => {
let ud = target_ptr as *mut UserDataStorage<T>;
try_self_arg!((*ud).try_borrow_scoped(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
#[rustfmt::skip]
UserDataTypeId::Rc(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Rc<T>>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Rc<T>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
UserDataType::Unique(_) => {
try_self_arg!(rawlua.get_userdata_type_id::<T>(state, self_index));
Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch))
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
#[rustfmt::skip]
UserDataTypeId::RcRefCell(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Rc<RefCell<T>>>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Rc<RefCell<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let ud = ud.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::Arc(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<T>>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Arc<T>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::ArcMutex(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<Mutex<T>>>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Arc<Mutex<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let ud = ud.try_lock().map_err(|_| Error::UserDataBorrowError)?;
method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::ArcRwLock(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<RwLock<T>>>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Arc<RwLock<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let ud = ud.try_read().map_err(|_| Error::UserDataBorrowError)?;
method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::ArcParkingLotMutex(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<parking_lot::Mutex<T>>>(self_index))
== Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Arc<parking_lot::Mutex<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let ud = ud.try_lock().ok_or(Error::UserDataBorrowError)?;
method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::ArcParkingLotRwLock(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<parking_lot::RwLock<T>>>(self_index))
== Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Arc<parking_lot::RwLock<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let ud = ud.try_read().ok_or(Error::UserDataBorrowError)?;
method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua)
}))
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
})
}
@@ -279,7 +171,7 @@ impl<T> UserDataRegistry<T> {
}
let method = RefCell::new(method);
let target_type_id = self.ud_type_id;
let target_type = self.r#type;
Box::new(move |rawlua, nargs| unsafe {
let mut method = method.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?;
if nargs == 0 {
@@ -292,97 +184,24 @@ impl<T> UserDataRegistry<T> {
// Self was at position 1, so we pass 2 here
let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua);
match target_type_id {
match target_type {
#[rustfmt::skip]
UserDataTypeId::Shared(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<T>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<T>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped_mut(|ud| {
UserDataType::Shared(type_hints) => {
let type_id = try_self_arg!(rawlua.get_userdata_type_id::<T>(state, self_index));
try_self_arg!(borrow_userdata_scoped_mut(state, self_index, type_id, type_hints, |ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[rustfmt::skip]
UserDataTypeId::Unique(target_ptr)
if get_userdata::<UserDataStorage<T>>(state, self_index) as *mut c_void == target_ptr =>
{
UserDataType::Unique(target_ptr) if ffi::lua_touserdata(state, self_index) == target_ptr => {
let ud = target_ptr as *mut UserDataStorage<T>;
try_self_arg!((*ud).try_borrow_scoped_mut(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
#[rustfmt::skip]
UserDataTypeId::Rc(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Rc<T>>(self_index)) == Some(target_type_id) =>
{
Err(Error::UserDataBorrowMutError)
},
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
#[rustfmt::skip]
UserDataTypeId::RcRefCell(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Rc<RefCell<T>>>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Rc<RefCell<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let mut ud = ud.try_borrow_mut().map_err(|_| Error::UserDataBorrowMutError)?;
method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua)
}))
UserDataType::Unique(_) => {
try_self_arg!(rawlua.get_userdata_type_id::<T>(state, self_index));
Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch))
}
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::Arc(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<T>>(self_index)) == Some(target_type_id) =>
{
Err(Error::UserDataBorrowMutError)
},
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::ArcMutex(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<Mutex<T>>>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Arc<Mutex<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let mut ud = ud.try_lock().map_err(|_| Error::UserDataBorrowMutError)?;
method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::ArcRwLock(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<RwLock<T>>>(self_index)) == Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Arc<RwLock<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let mut ud = ud.try_write().map_err(|_| Error::UserDataBorrowMutError)?;
method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::ArcParkingLotMutex(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<parking_lot::Mutex<T>>>(self_index))
== Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Arc<parking_lot::Mutex<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let mut ud = ud.try_lock().ok_or(Error::UserDataBorrowMutError)?;
method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua)
}))
}
#[cfg(feature = "userdata-wrappers")]
#[rustfmt::skip]
UserDataTypeId::ArcParkingLotRwLock(target_type_id)
if try_self_arg!(rawlua.get_userdata_type_id::<Arc<parking_lot::RwLock<T>>>(self_index))
== Some(target_type_id) =>
{
let ud = get_userdata::<UserDataStorage<Arc<parking_lot::RwLock<T>>>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
let mut ud = ud.try_write().ok_or(Error::UserDataBorrowMutError)?;
method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua)
}))
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
})
}
@@ -789,14 +608,10 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
}
macro_rules! lua_userdata_impl {
($type:ty => $type_variant:tt) => {
lua_userdata_impl!($type, UserDataTypeId::$type_variant(TypeId::of::<$type>()));
};
($type:ty, $type_id:expr) => {
($type:ty) => {
impl<T: UserData + 'static> UserData for $type {
fn register(registry: &mut UserDataRegistry<Self>) {
let mut orig_registry = UserDataRegistry::with_type_id(registry.lua.lua(), $type_id);
let mut orig_registry = UserDataRegistry::new(registry.lua.lua());
T::register(&mut orig_registry);
// Copy all fields, methods, etc. from the original registry
@@ -818,22 +633,22 @@ macro_rules! lua_userdata_impl {
// A special proxy object for UserData
pub(crate) struct UserDataProxy<T>(pub(crate) PhantomData<T>);
lua_userdata_impl!(UserDataProxy<T>, UserDataTypeId::Shared(TypeId::of::<T>()));
lua_userdata_impl!(UserDataProxy<T>);
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
lua_userdata_impl!(Rc<T> => Rc);
lua_userdata_impl!(std::rc::Rc<T>);
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
lua_userdata_impl!(Rc<RefCell<T>> => RcRefCell);
lua_userdata_impl!(std::rc::Rc<std::cell::RefCell<T>>);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(Arc<T> => Arc);
lua_userdata_impl!(std::sync::Arc<T>);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(Arc<Mutex<T>> => ArcMutex);
lua_userdata_impl!(std::sync::Arc<std::sync::Mutex<T>>);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(Arc<RwLock<T>> => ArcRwLock);
lua_userdata_impl!(std::sync::Arc<std::sync::RwLock<T>>);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(Arc<parking_lot::Mutex<T>> => ArcParkingLotMutex);
lua_userdata_impl!(std::sync::Arc<parking_lot::Mutex<T>>);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(Arc<parking_lot::RwLock<T>> => ArcParkingLotRwLock);
lua_userdata_impl!(std::sync::Arc<parking_lot::RwLock<T>>);
#[cfg(test)]
mod assertions {
+428 -5
View File
@@ -1,9 +1,12 @@
use std::any::TypeId;
use std::cell::Cell;
use std::marker::PhantomData;
use std::os::raw::c_int;
use std::ptr;
use super::UserDataStorage;
use crate::util::{get_userdata, take_userdata};
use crate::error::{Error, Result};
use crate::util::{get_userdata, rawget_field, rawset_field, take_userdata};
// This is a trick to check if a type is `Sync` or not.
// It uses leaked specialization feature from stdlib.
@@ -34,13 +37,433 @@ pub(crate) fn is_sync<T>() -> bool {
is_sync.get()
}
pub(super) unsafe extern "C-unwind" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
let ud = get_userdata::<UserDataStorage<T>>(state, -1);
if !(*ud).is_borrowed() {
take_userdata::<UserDataStorage<T>>(state);
// Userdata type hints, used to match types of wrapped userdata
#[derive(Clone, Copy)]
pub(crate) struct TypeIdHints {
t: TypeId,
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
rc: TypeId,
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
rc_refcell: TypeId,
#[cfg(feature = "userdata-wrappers")]
arc: TypeId,
#[cfg(feature = "userdata-wrappers")]
arc_mutex: TypeId,
#[cfg(feature = "userdata-wrappers")]
arc_rwlock: TypeId,
#[cfg(feature = "userdata-wrappers")]
arc_pl_mutex: TypeId,
#[cfg(feature = "userdata-wrappers")]
arc_pl_rwlock: TypeId,
}
impl TypeIdHints {
pub(crate) fn new<T: 'static>() -> Self {
Self {
t: TypeId::of::<T>(),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
rc: TypeId::of::<std::rc::Rc<T>>(),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
rc_refcell: TypeId::of::<std::rc::Rc<std::cell::RefCell<T>>>(),
#[cfg(feature = "userdata-wrappers")]
arc: TypeId::of::<std::sync::Arc<T>>(),
#[cfg(feature = "userdata-wrappers")]
arc_mutex: TypeId::of::<std::sync::Arc<std::sync::Mutex<T>>>(),
#[cfg(feature = "userdata-wrappers")]
arc_rwlock: TypeId::of::<std::sync::Arc<std::sync::RwLock<T>>>(),
#[cfg(feature = "userdata-wrappers")]
arc_pl_mutex: TypeId::of::<std::sync::Arc<parking_lot::Mutex<T>>>(),
#[cfg(feature = "userdata-wrappers")]
arc_pl_rwlock: TypeId::of::<std::sync::Arc<parking_lot::RwLock<T>>>(),
}
}
#[inline(always)]
pub(crate) fn type_id(&self) -> TypeId {
self.t
}
}
pub(crate) unsafe fn borrow_userdata_scoped<T, R>(
state: *mut ffi::lua_State,
idx: c_int,
type_id: Option<TypeId>,
type_hints: TypeIdHints,
f: impl FnOnce(&T) -> R,
) -> Result<R> {
match type_id {
Some(type_id) if type_id == type_hints.t => {
let ud = get_userdata::<UserDataStorage<T>>(state, idx);
(*ud).try_borrow_scoped(|ud| f(ud))
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == type_hints.rc => {
let ud = get_userdata::<UserDataStorage<std::rc::Rc<T>>>(state, idx);
(*ud).try_borrow_scoped(|ud| f(ud))
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == type_hints.rc_refcell => {
let ud = get_userdata::<UserDataStorage<std::rc::Rc<std::cell::RefCell<T>>>>(state, idx);
(*ud).try_borrow_scoped(|ud| {
let ud = ud.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
Ok(f(&ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<T>>>(state, idx);
(*ud).try_borrow_scoped(|ud| f(ud))
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_mutex => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<std::sync::Mutex<T>>>>(state, idx);
(*ud).try_borrow_scoped(|ud| {
let ud = ud.try_lock().map_err(|_| Error::UserDataBorrowError)?;
Ok(f(&ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_rwlock => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<std::sync::RwLock<T>>>>(state, idx);
(*ud).try_borrow_scoped(|ud| {
let ud = ud.try_read().map_err(|_| Error::UserDataBorrowError)?;
Ok(f(&ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_pl_mutex => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<parking_lot::Mutex<T>>>>(state, idx);
(*ud).try_borrow_scoped(|ud| {
let ud = ud.try_lock().ok_or(Error::UserDataBorrowError)?;
Ok(f(&ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_pl_rwlock => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<parking_lot::RwLock<T>>>>(state, idx);
(*ud).try_borrow_scoped(|ud| {
let ud = ud.try_read().ok_or(Error::UserDataBorrowError)?;
Ok(f(&ud))
})?
}
_ => Err(Error::UserDataTypeMismatch),
}
}
pub(crate) unsafe fn borrow_userdata_scoped_mut<T, R>(
state: *mut ffi::lua_State,
idx: c_int,
type_id: Option<TypeId>,
type_hints: TypeIdHints,
f: impl FnOnce(&mut T) -> R,
) -> Result<R> {
match type_id {
Some(type_id) if type_id == type_hints.t => {
let ud = get_userdata::<UserDataStorage<T>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| f(ud))
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == type_hints.rc => {
let ud = get_userdata::<UserDataStorage<std::rc::Rc<T>>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| match std::rc::Rc::get_mut(ud) {
Some(ud) => Ok(f(ud)),
None => Err(Error::UserDataBorrowMutError),
})?
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == type_hints.rc_refcell => {
let ud = get_userdata::<UserDataStorage<std::rc::Rc<std::cell::RefCell<T>>>>(state, idx);
(*ud).try_borrow_scoped(|ud| {
let mut ud = ud.try_borrow_mut().map_err(|_| Error::UserDataBorrowMutError)?;
Ok(f(&mut ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<T>>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| match std::sync::Arc::get_mut(ud) {
Some(ud) => Ok(f(ud)),
None => Err(Error::UserDataBorrowMutError),
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_mutex => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<std::sync::Mutex<T>>>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| {
let mut ud = ud.try_lock().map_err(|_| Error::UserDataBorrowMutError)?;
Ok(f(&mut ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_rwlock => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<std::sync::RwLock<T>>>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| {
let mut ud = ud.try_write().map_err(|_| Error::UserDataBorrowMutError)?;
Ok(f(&mut ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_pl_mutex => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<parking_lot::Mutex<T>>>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| {
let mut ud = ud.try_lock().ok_or(Error::UserDataBorrowMutError)?;
Ok(f(&mut ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_pl_rwlock => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<parking_lot::RwLock<T>>>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| {
let mut ud = ud.try_write().ok_or(Error::UserDataBorrowMutError)?;
Ok(f(&mut ud))
})?
}
_ => Err(Error::UserDataTypeMismatch),
}
}
// Populates the given table with the appropriate members to be a userdata metatable for the given
// type. This function takes the given table at the `metatable` index, and adds an appropriate
// `__gc` member to it for the given type and a `__metatable` entry to protect the table from script
// access. The function also, if given a `field_getters` or `methods` tables, will create an
// `__index` metamethod (capturing previous one) to lookup in `field_getters` first, then `methods`
// and falling back to the captured `__index` if no matches found.
// The same is also applicable for `__newindex` metamethod and `field_setters` table.
// Internally uses 9 stack spaces and does not call checkstack.
pub(crate) unsafe fn init_userdata_metatable(
state: *mut ffi::lua_State,
metatable: c_int,
field_getters: Option<c_int>,
field_setters: Option<c_int>,
methods: Option<c_int>,
) -> Result<()> {
if field_getters.is_some() || methods.is_some() {
// Push `__index` generator function
init_userdata_metatable_index(state)?;
let index_type = rawget_field(state, metatable, "__index")?;
match index_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
for &idx in &[field_getters, methods] {
if let Some(idx) = idx {
ffi::lua_pushvalue(state, idx);
} else {
ffi::lua_pushnil(state);
}
}
// Generate `__index`
protect_lua!(state, 4, 1, fn(state) ffi::lua_call(state, 3, 1))?;
}
_ => mlua_panic!("improper `__index` type: {}", index_type),
}
rawset_field(state, metatable, "__index")?;
}
if let Some(field_setters) = field_setters {
// Push `__newindex` generator function
init_userdata_metatable_newindex(state)?;
let newindex_type = rawget_field(state, metatable, "__newindex")?;
match newindex_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
ffi::lua_pushvalue(state, field_setters);
// Generate `__newindex`
protect_lua!(state, 3, 1, fn(state) ffi::lua_call(state, 2, 1))?;
}
_ => mlua_panic!("improper `__newindex` type: {}", newindex_type),
}
rawset_field(state, metatable, "__newindex")?;
}
ffi::lua_pushboolean(state, 0);
rawset_field(state, metatable, "__metatable")?;
Ok(())
}
unsafe extern "C-unwind" fn lua_error_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_error(state);
}
unsafe extern "C-unwind" fn lua_isfunction_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushboolean(state, ffi::lua_isfunction(state, -1));
1
}
unsafe extern "C-unwind" fn lua_istable_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushboolean(state, ffi::lua_istable(state, -1));
1
}
unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()> {
let index_key = &USERDATA_METATABLE_INDEX as *const u8 as *const _;
if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, index_key) == ffi::LUA_TFUNCTION {
return Ok(());
}
ffi::lua_pop(state, 1);
// Create and cache `__index` generator
let code = cr#"
local error, isfunction, istable = ...
return function (__index, field_getters, methods)
-- Common case: has field getters and index is a table
if field_getters ~= nil and methods == nil and istable(__index) then
return function (self, key)
local field_getter = field_getters[key]
if field_getter ~= nil then
return field_getter(self)
end
return __index[key]
end
end
return function (self, key)
if field_getters ~= nil then
local field_getter = field_getters[key]
if field_getter ~= nil then
return field_getter(self)
end
end
if methods ~= nil then
local method = methods[key]
if method ~= nil then
return method
end
end
if isfunction(__index) then
return __index(self, key)
elseif __index == nil then
error("attempt to get an unknown field '"..key.."'")
else
return __index[key]
end
end
end
"#;
protect_lua!(state, 0, 1, |state| {
let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("=__mlua_index"));
if ret != ffi::LUA_OK {
ffi::lua_error(state);
}
ffi::lua_pushcfunction(state, lua_error_impl);
ffi::lua_pushcfunction(state, lua_isfunction_impl);
ffi::lua_pushcfunction(state, lua_istable_impl);
ffi::lua_call(state, 3, 1);
#[cfg(feature = "luau-jit")]
if ffi::luau_codegen_supported() != 0 {
ffi::luau_codegen_compile(state, -1);
}
// Store in the registry
ffi::lua_pushvalue(state, -1);
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, index_key);
})
}
unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result<()> {
let newindex_key = &USERDATA_METATABLE_NEWINDEX as *const u8 as *const _;
if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, newindex_key) == ffi::LUA_TFUNCTION {
return Ok(());
}
ffi::lua_pop(state, 1);
// Create and cache `__newindex` generator
let code = cr#"
local error, isfunction = ...
return function (__newindex, field_setters)
return function (self, key, value)
if field_setters ~= nil then
local field_setter = field_setters[key]
if field_setter ~= nil then
field_setter(self, value)
return
end
end
if isfunction(__newindex) then
__newindex(self, key, value)
elseif __newindex == nil then
error("attempt to set an unknown field '"..key.."'")
else
__newindex[key] = value
end
end
end
"#;
protect_lua!(state, 0, 1, |state| {
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);
}
ffi::lua_pushcfunction(state, lua_error_impl);
ffi::lua_pushcfunction(state, lua_isfunction_impl);
ffi::lua_call(state, 2, 1);
#[cfg(feature = "luau-jit")]
if ffi::luau_codegen_supported() != 0 {
ffi::luau_codegen_compile(state, -1);
}
// Store in the registry
ffi::lua_pushvalue(state, -1);
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, newindex_key);
})
}
// This method is called by Lua GC when it's time to collect the userdata.
//
// This method is usually used to collect internal userdata.
#[cfg(not(feature = "luau"))]
pub(crate) unsafe extern "C-unwind" fn collect_userdata<T>(state: *mut ffi::lua_State) -> c_int {
let ud = get_userdata::<T>(state, -1);
ptr::drop_in_place(ud);
0
}
// This method is called by Luau GC when it's time to collect the userdata.
#[cfg(feature = "luau")]
pub(crate) unsafe extern "C" fn collect_userdata<T>(
state: *mut ffi::lua_State,
ud: *mut std::os::raw::c_void,
) {
// Almost none Lua operations are allowed when destructor is running,
// so we need to set a flag to prevent calling any Lua functions
let extra = (*ffi::lua_callbacks(state)).userdata as *mut crate::state::ExtraData;
(*extra).running_gc = true;
// Luau does not support _any_ panics in destructors (they are declared as "C", NOT as "C-unwind"),
// so any panics will trigger `abort()`.
ptr::drop_in_place(ud as *mut T);
(*extra).running_gc = false;
}
// This method can be called by user or Lua GC to destroy the userdata.
// 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);
if (*ud).is_safe_to_destroy() {
take_userdata::<UserDataStorage<T>>(state, 1);
ffi::lua_pushboolean(state, 1);
} else {
ffi::lua_pushboolean(state, 0);
}
1
}
static USERDATA_METATABLE_INDEX: u8 = 0;
static USERDATA_METATABLE_NEWINDEX: u8 = 0;
+11 -17
View File
@@ -9,9 +9,8 @@ use std::sync::Arc;
use crate::error::{Error, Result};
use crate::memory::MemoryState;
use crate::util::{
check_stack, get_internal_metatable, get_internal_userdata, init_internal_metatable,
push_internal_userdata, push_string, push_table, rawset_field, to_string, TypeKey,
DESTRUCTED_USERDATA_METATABLE,
check_stack, get_internal_userdata, init_internal_metatable, push_internal_userdata, push_string,
push_table, rawset_field, to_string, TypeKey, DESTRUCTED_USERDATA_METATABLE,
};
static WRAPPED_FAILURE_TYPE_KEY: u8 = 0;
@@ -31,12 +30,8 @@ impl TypeKey for WrappedFailure {
impl WrappedFailure {
pub(crate) unsafe fn new_userdata(state: *mut ffi::lua_State) -> *mut Self {
#[cfg(feature = "luau")]
let ud = ffi::lua_newuserdata_t::<Self>(state);
#[cfg(not(feature = "luau"))]
let ud = ffi::lua_newuserdata(state, std::mem::size_of::<Self>()) as *mut Self;
ptr::write(ud, WrappedFailure::None);
ud
// Unprotected calls always return `Ok`
push_internal_userdata(state, WrappedFailure::None, false).unwrap()
}
}
@@ -90,16 +85,11 @@ where
let cause = Arc::new(err);
let wrapped_error = WrappedFailure::Error(Error::CallbackError { traceback, cause });
ptr::write(ud, wrapped_error);
get_internal_metatable::<WrappedFailure>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
}
Err(p) => {
ffi::lua_settop(state, 1);
ptr::write(ud, WrappedFailure::Panic(Some(p)));
get_internal_metatable::<WrappedFailure>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
}
}
@@ -262,7 +252,7 @@ where
pub(crate) unsafe extern "C-unwind" fn error_traceback(state: *mut ffi::lua_State) -> c_int {
// Luau calls error handler for memory allocation errors, skip it
// See https://github.com/Roblox/luau/issues/880
// See https://github.com/luau-lang/luau/issues/880
#[cfg(feature = "luau")]
if MemoryState::limit_reached(state) {
return 0;
@@ -325,7 +315,7 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<(
let _ = write!(&mut (*err_buf), "{error}");
Ok(err_buf)
}
Some(WrappedFailure::Panic(Some(ref panic))) => {
Some(WrappedFailure::Panic(Some(panic))) => {
let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key);
let err_buf = ffi::lua_touserdata(state, -1) as *mut String;
@@ -359,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"));
}),
)?;
+1 -2
View File
@@ -13,13 +13,12 @@ pub(crate) use short_names::short_type_name;
pub(crate) use types::TypeKey;
pub(crate) use userdata::{
get_destructed_userdata_metatable, get_internal_metatable, get_internal_userdata, get_userdata,
init_internal_metatable, init_userdata_metatable, push_internal_userdata, take_userdata,
init_internal_metatable, push_internal_userdata, push_userdata, take_userdata,
DESTRUCTED_USERDATA_METATABLE,
};
#[cfg(not(feature = "luau"))]
pub(crate) use userdata::push_uninit_userdata;
pub(crate) use userdata::push_userdata;
// Checks that Lua has enough free stack space for future stack operations. On failure, this will
// panic with an internal error message.
+15 -17
View File
@@ -10,73 +10,71 @@ pub(crate) trait TypeKey: Any {
fn type_key() -> *const c_void;
}
static STRING_TYPE_KEY: u8 = 0;
impl TypeKey for String {
#[inline(always)]
fn type_key() -> *const c_void {
static STRING_TYPE_KEY: u8 = 0;
&STRING_TYPE_KEY as *const u8 as *const c_void
}
}
static CALLBACK_TYPE_KEY: u8 = 0;
impl TypeKey for Callback {
#[inline(always)]
fn type_key() -> *const c_void {
static CALLBACK_TYPE_KEY: u8 = 0;
&CALLBACK_TYPE_KEY as *const u8 as *const c_void
}
}
static CALLBACK_UPVALUE_TYPE_KEY: u8 = 0;
impl TypeKey for CallbackUpvalue {
#[inline(always)]
fn type_key() -> *const c_void {
static CALLBACK_UPVALUE_TYPE_KEY: u8 = 0;
&CALLBACK_UPVALUE_TYPE_KEY as *const u8 as *const c_void
}
}
#[cfg(feature = "async")]
static ASYNC_CALLBACK_TYPE_KEY: u8 = 0;
#[cfg(not(feature = "luau"))]
impl TypeKey for crate::types::HookCallback {
#[inline(always)]
fn type_key() -> *const c_void {
static HOOK_CALLBACK_TYPE_KEY: u8 = 0;
&HOOK_CALLBACK_TYPE_KEY as *const u8 as *const c_void
}
}
#[cfg(feature = "async")]
impl TypeKey for AsyncCallback {
#[inline(always)]
fn type_key() -> *const c_void {
static ASYNC_CALLBACK_TYPE_KEY: u8 = 0;
&ASYNC_CALLBACK_TYPE_KEY as *const u8 as *const c_void
}
}
#[cfg(feature = "async")]
static ASYNC_CALLBACK_UPVALUE_TYPE_KEY: u8 = 0;
#[cfg(feature = "async")]
impl TypeKey for AsyncCallbackUpvalue {
#[inline(always)]
fn type_key() -> *const c_void {
static ASYNC_CALLBACK_UPVALUE_TYPE_KEY: u8 = 0;
&ASYNC_CALLBACK_UPVALUE_TYPE_KEY as *const u8 as *const c_void
}
}
#[cfg(feature = "async")]
static ASYNC_POLL_UPVALUE_TYPE_KEY: u8 = 0;
#[cfg(feature = "async")]
impl TypeKey for AsyncPollUpvalue {
#[inline(always)]
fn type_key() -> *const c_void {
static ASYNC_POLL_UPVALUE_TYPE_KEY: u8 = 0;
&ASYNC_POLL_UPVALUE_TYPE_KEY as *const u8 as *const c_void
}
}
#[cfg(feature = "async")]
static WAKER_TYPE_KEY: u8 = 0;
#[cfg(feature = "async")]
impl TypeKey for Option<std::task::Waker> {
#[inline(always)]
fn type_key() -> *const c_void {
static WAKER_TYPE_KEY: u8 = 0;
&WAKER_TYPE_KEY as *const u8 as *const c_void
}
}
+64 -235
View File
@@ -1,8 +1,9 @@
use std::os::raw::{c_int, c_void};
use std::{ptr, str};
use std::{mem, ptr};
use crate::error::Result;
use crate::util::{check_stack, get_metatable_ptr, push_table, rawget_field, rawset_field, TypeKey};
use crate::userdata::collect_userdata;
use crate::util::{check_stack, get_metatable_ptr, push_table, rawset_field, TypeKey};
// Pushes the userdata and attaches a metatable with __gc method.
// Internally uses 3 stack spaces, does not call checkstack.
@@ -10,11 +11,30 @@ pub(crate) unsafe fn push_internal_userdata<T: TypeKey>(
state: *mut ffi::lua_State,
t: T,
protect: bool,
) -> Result<()> {
push_userdata(state, t, protect)?;
) -> Result<*mut T> {
#[cfg(not(feature = "luau"))]
let ud_ptr = if protect {
protect_lua!(state, 0, 1, move |state| {
let ud_ptr = ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T;
ptr::write(ud_ptr, t);
ud_ptr
})?
} else {
let ud_ptr = ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T;
ptr::write(ud_ptr, t);
ud_ptr
};
#[cfg(feature = "luau")]
let ud_ptr = if protect {
protect_lua!(state, 0, 1, move |state| ffi::lua_newuserdata_t::<T>(state, t))?
} else {
ffi::lua_newuserdata_t::<T>(state, t)
};
get_internal_metatable::<T>(state);
ffi::lua_setmetatable(state, -2);
Ok(())
Ok(ud_ptr)
}
#[track_caller]
@@ -27,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)?;
@@ -35,25 +55,25 @@ pub(crate) unsafe fn init_internal_metatable<T: TypeKey>(
#[cfg(not(feature = "luau"))]
{
ffi::lua_pushcfunction(state, userdata_destructor::<T>);
ffi::lua_pushcfunction(state, collect_userdata::<T>);
rawset_field(state, -2, "__gc")?;
}
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,
@@ -81,24 +101,34 @@ pub(crate) unsafe fn get_internal_userdata<T: TypeKey>(
pub(crate) unsafe fn push_uninit_userdata<T>(state: *mut ffi::lua_State, protect: bool) -> Result<*mut T> {
if protect {
protect_lua!(state, 0, 1, |state| {
ffi::lua_newuserdata(state, std::mem::size_of::<T>()) as *mut T
ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T
})
} else {
Ok(ffi::lua_newuserdata(state, std::mem::size_of::<T>()) as *mut T)
Ok(ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T)
}
}
// Internally uses 3 stack spaces, does not call checkstack.
#[inline]
pub(crate) unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<*mut T> {
let size = const { mem::size_of::<T>() };
#[cfg(not(feature = "luau"))]
let ud_ptr = push_uninit_userdata(state, protect)?;
let ud_ptr = if protect {
protect_lua!(state, 0, 1, move |state| ffi::lua_newuserdata(state, size))?
} else {
ffi::lua_newuserdata(state, size)
} as *mut T;
#[cfg(feature = "luau")]
let ud_ptr = if protect {
protect_lua!(state, 0, 1, |state| { ffi::lua_newuserdata_t::<T>(state) })?
protect_lua!(state, 0, 1, |state| {
ffi::lua_newuserdatadtor(state, size, collect_userdata::<T>)
})?
} else {
ffi::lua_newuserdata_t::<T>(state)
};
ffi::lua_newuserdatadtor(state, size, collect_userdata::<T>)
} as *mut T;
ptr::write(ud_ptr, t);
Ok(ud_ptr)
}
@@ -111,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)
}
@@ -137,208 +170,4 @@ pub(crate) unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_Stat
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, key);
}
// Populates the given table with the appropriate members to be a userdata metatable for the given
// type. This function takes the given table at the `metatable` index, and adds an appropriate
// `__gc` member to it for the given type and a `__metatable` entry to protect the table from script
// access. The function also, if given a `field_getters` or `methods` tables, will create an
// `__index` metamethod (capturing previous one) to lookup in `field_getters` first, then `methods`
// and falling back to the captured `__index` if no matches found.
// The same is also applicable for `__newindex` metamethod and `field_setters` table.
// Internally uses 9 stack spaces and does not call checkstack.
pub(crate) unsafe fn init_userdata_metatable(
state: *mut ffi::lua_State,
metatable: c_int,
field_getters: Option<c_int>,
field_setters: Option<c_int>,
methods: Option<c_int>,
) -> Result<()> {
if field_getters.is_some() || methods.is_some() {
// Push `__index` generator function
init_userdata_metatable_index(state)?;
let index_type = rawget_field(state, metatable, "__index")?;
match index_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
for &idx in &[field_getters, methods] {
if let Some(idx) = idx {
ffi::lua_pushvalue(state, idx);
} else {
ffi::lua_pushnil(state);
}
}
// Generate `__index`
protect_lua!(state, 4, 1, fn(state) ffi::lua_call(state, 3, 1))?;
}
_ => mlua_panic!("improper `__index` type: {}", index_type),
}
rawset_field(state, metatable, "__index")?;
}
if let Some(field_setters) = field_setters {
// Push `__newindex` generator function
init_userdata_metatable_newindex(state)?;
let newindex_type = rawget_field(state, metatable, "__newindex")?;
match newindex_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
ffi::lua_pushvalue(state, field_setters);
// Generate `__newindex`
protect_lua!(state, 3, 1, fn(state) ffi::lua_call(state, 2, 1))?;
}
_ => mlua_panic!("improper `__newindex` type: {}", newindex_type),
}
rawset_field(state, metatable, "__newindex")?;
}
ffi::lua_pushboolean(state, 0);
rawset_field(state, metatable, "__metatable")?;
Ok(())
}
unsafe extern "C-unwind" fn lua_error_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_error(state);
}
unsafe extern "C-unwind" fn lua_isfunction_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushboolean(state, ffi::lua_isfunction(state, -1));
1
}
unsafe extern "C-unwind" fn lua_istable_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushboolean(state, ffi::lua_istable(state, -1));
1
}
unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()> {
let index_key = &USERDATA_METATABLE_INDEX as *const u8 as *const _;
if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, index_key) == ffi::LUA_TFUNCTION {
return Ok(());
}
ffi::lua_pop(state, 1);
// Create and cache `__index` generator
let code = cr#"
local error, isfunction, istable = ...
return function (__index, field_getters, methods)
-- Common case: has field getters and index is a table
if field_getters ~= nil and methods == nil and istable(__index) then
return function (self, key)
local field_getter = field_getters[key]
if field_getter ~= nil then
return field_getter(self)
end
return __index[key]
end
end
return function (self, key)
if field_getters ~= nil then
local field_getter = field_getters[key]
if field_getter ~= nil then
return field_getter(self)
end
end
if methods ~= nil then
local method = methods[key]
if method ~= nil then
return method
end
end
if isfunction(__index) then
return __index(self, key)
elseif __index == nil then
error("attempt to get an unknown field '"..key.."'")
else
return __index[key]
end
end
end
"#;
protect_lua!(state, 0, 1, |state| {
let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_index"));
if ret != ffi::LUA_OK {
ffi::lua_error(state);
}
ffi::lua_pushcfunction(state, lua_error_impl);
ffi::lua_pushcfunction(state, lua_isfunction_impl);
ffi::lua_pushcfunction(state, lua_istable_impl);
ffi::lua_call(state, 3, 1);
#[cfg(feature = "luau-jit")]
if ffi::luau_codegen_supported() != 0 {
ffi::luau_codegen_compile(state, -1);
}
// Store in the registry
ffi::lua_pushvalue(state, -1);
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, index_key);
})
}
unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result<()> {
let newindex_key = &USERDATA_METATABLE_NEWINDEX as *const u8 as *const _;
if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, newindex_key) == ffi::LUA_TFUNCTION {
return Ok(());
}
ffi::lua_pop(state, 1);
// Create and cache `__newindex` generator
let code = cr#"
local error, isfunction = ...
return function (__newindex, field_setters)
return function (self, key, value)
if field_setters ~= nil then
local field_setter = field_setters[key]
if field_setter ~= nil then
field_setter(self, value)
return
end
end
if isfunction(__newindex) then
__newindex(self, key, value)
elseif __newindex == nil then
error("attempt to set an unknown field '"..key.."'")
else
__newindex[key] = value
end
end
end
"#;
protect_lua!(state, 0, 1, |state| {
let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_newindex"));
if ret != ffi::LUA_OK {
ffi::lua_error(state);
}
ffi::lua_pushcfunction(state, lua_error_impl);
ffi::lua_pushcfunction(state, lua_isfunction_impl);
ffi::lua_call(state, 2, 1);
#[cfg(feature = "luau-jit")]
if ffi::luau_codegen_supported() != 0 {
ffi::luau_codegen_compile(state, -1);
}
// Store in the registry
ffi::lua_pushvalue(state, -1);
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, newindex_key);
})
}
#[cfg(not(feature = "luau"))]
unsafe extern "C-unwind" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
// It's probably NOT a good idea to catch Rust panics in finalizer
// Lua 5.4 ignores it, other versions generates `LUA_ERRGCMM` without calling message handler
take_userdata::<T>(state);
0
}
pub(crate) static DESTRUCTED_USERDATA_METATABLE: u8 = 0;
static USERDATA_METATABLE_INDEX: u8 = 0;
static USERDATA_METATABLE_NEWINDEX: u8 = 0;
+30 -13
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,
@@ -67,8 +67,7 @@ pub enum Value {
/// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned.
Error(Box<Error>),
/// Any other value not known to mlua (eg. LuaJIT CData).
#[allow(private_interfaces)]
Other(ValueRef),
Other(#[doc(hidden)] ValueRef),
}
pub use self::Value::Nil;
@@ -273,7 +272,10 @@ impl Value {
/// If the value is a Lua [`Integer`], try to convert it to `i64` or return `None` otherwise.
#[inline]
pub fn as_i64(&self) -> Option<i64> {
self.as_integer().map(i64::from)
#[cfg(target_pointer_width = "64")]
return self.as_integer();
#[cfg(not(target_pointer_width = "64"))]
return self.as_integer().map(i64::from);
}
/// Cast the value to `u64`.
@@ -355,7 +357,7 @@ impl Value {
/// If the value is a Lua [`String`], try to convert it to [`BorrowedStr`] or return `None`
/// otherwise.
#[inline]
pub fn as_str(&self) -> Option<BorrowedStr> {
pub fn as_str(&self) -> Option<BorrowedStr<'_>> {
self.as_string().and_then(|s| s.to_str().ok())
}
@@ -479,10 +481,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)
}
@@ -570,6 +572,12 @@ impl Value {
}
}
impl Default for Value {
fn default() -> Self {
Self::Nil
}
}
impl fmt::Debug for Value {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if fmt.alternate() {
@@ -622,8 +630,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,
@@ -631,7 +639,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> {
@@ -639,7 +647,7 @@ impl Serialize for Value {
}
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
impl<'a> SerializableValue<'a> {
#[inline]
pub(crate) fn new(
@@ -692,9 +700,18 @@ impl<'a> SerializableValue<'a> {
self.options.sort_keys = enabled;
self
}
/// If true, empty Lua tables will be encoded as array, instead of map.
///
/// Default: **false**
#[must_use]
pub const fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self {
self.options.encode_empty_tables_as_array = enabled;
self
}
}
#[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"
+104 -4
View File
@@ -9,7 +9,7 @@ use tokio::sync::Mutex;
use mlua::{
Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, UserData,
UserDataMethods, Value,
UserDataMethods, UserDataRef, Value,
};
#[cfg(not(target_arch = "wasm32"))]
@@ -273,7 +273,7 @@ async fn test_async_lua54_to_be_closed() -> Result<()> {
// Don't close by default when awaiting async threads
let co = lua.create_thread(f.clone())?;
let _ = co.clone().into_async::<()>(()).await;
let _ = co.clone().into_async::<()>(())?.await;
assert_eq!(globals.get::<usize>("close_count")?, 1);
let _ = co.reset(f);
assert_eq!(globals.get::<usize>("close_count")?, 2);
@@ -300,7 +300,7 @@ async fn test_async_thread_stream() -> Result<()> {
.eval()?,
)?;
let mut stream = thread.into_async::<i64>(1);
let mut stream = thread.into_async::<i64>(1)?;
let mut sum = 0;
while let Some(n) = stream.try_next().await? {
sum += n;
@@ -325,7 +325,7 @@ async fn test_async_thread() -> Result<()> {
}
})?;
let res: String = lua.create_thread(f)?.into_async(()).await?;
let res: String = lua.create_thread(f)?.into_async(())?.await?;
assert_eq!(res, "done");
@@ -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,5 +566,104 @@ 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(())
}
#[tokio::test]
async fn test_async_task() -> Result<()> {
let lua = Lua::new();
let delay = lua.create_function(|lua, (secs, f, args): (f32, Function, MultiValue)| {
let thread = lua.create_thread(f)?;
let thread2 = thread.clone().into_async::<()>(args)?;
tokio::task::spawn_local(async move {
tokio::time::sleep(Duration::from_secs_f32(secs)).await;
_ = thread2.await;
});
Ok(thread)
})?;
lua.globals().set("delay", delay)?;
let local = tokio::task::LocalSet::new();
local
.run_until(async {
_ = lua
.load("delay(0.1, function(msg) global_msg = msg end, 'done')")
.exec_async()
.await;
})
.await;
local.await;
assert_eq!(lua.globals().get::<String>("global_msg")?, "done");
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<()> {
use std::sync::atomic::{AtomicBool, Ordering};
let lua = Lua::new();
static HOOK_CALLED: AtomicBool = AtomicBool::new(false);
lua.set_global_hook(mlua::HookTriggers::new().every_line(), move |_, _| {
if !HOOK_CALLED.swap(true, Ordering::Relaxed) {
#[cfg(any(feature = "lu53", feature = "lua54"))]
return Ok(mlua::VmState::Yield);
}
Ok(mlua::VmState::Continue)
})?;
let sleep = lua.create_async_function(move |_lua, n: u64| async move {
sleep_ms(n).await;
Ok(())
})?;
lua.globals().set("sleep", sleep)?;
lua.load(r"sleep(100)").exec_async().await?;
assert!(HOOK_CALLED.load(Ordering::Relaxed));
Ok(())
}
+49 -6
View File
@@ -1,6 +1,24 @@
use std::{fs, io};
use mlua::{Chunk, Lua, Result};
use mlua::{Chunk, ChunkMode, Lua, Result};
#[test]
fn test_chunk_methods() -> Result<()> {
let lua = Lua::new();
#[cfg(unix)]
assert!(lua.load("return 123").name().starts_with("@tests/chunk.rs"));
let chunk2 = lua.load("return 123").set_name("@new_name");
assert_eq!(chunk2.name(), "@new_name");
let env = lua.create_table_from([("a", 987)])?;
let chunk3 = lua.load("return a").set_environment(env.clone());
assert_eq!(chunk3.environment().unwrap(), &env);
assert_eq!(chunk3.mode(), ChunkMode::Text);
assert_eq!(chunk3.call::<i32>(())?, 987);
Ok(())
}
#[test]
fn test_chunk_path() -> Result<()> {
@@ -96,8 +114,6 @@ fn test_chunk_macro() -> Result<()> {
#[cfg(feature = "luau")]
#[test]
fn test_compiler() -> Result<()> {
use std::vec;
let compiler = mlua::Compiler::new()
.set_optimization_level(2)
.set_debug_level(2)
@@ -106,10 +122,11 @@ fn test_compiler() -> Result<()> {
.set_vector_lib("vector")
.set_vector_ctor("new")
.set_vector_type("vector")
.set_mutable_globals(vec!["mutable_global".into()])
.set_userdata_types(vec!["MyUserdata".into()]);
.set_mutable_globals(vec!["mutable_global"])
.set_userdata_types(vec!["MyUserdata"])
.set_disabled_builtins(vec!["tostring"]);
assert!(compiler.compile("return vector.new(1, 2, 3)").is_ok());
assert!(compiler.compile("return tostring(vector.new(1, 2, 3))").is_ok());
// Error
match compiler.compile("%") {
@@ -122,6 +139,32 @@ fn test_compiler() -> Result<()> {
Ok(())
}
#[cfg(feature = "luau")]
#[test]
fn test_compiler_library_constants() {
use mlua::{CompileConstant, 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()),
]);
let lua = Lua::new();
lua.set_compiler(compiler);
let const_bool = lua.load("return mylib.const_bool").eval::<bool>().unwrap();
assert_eq!(const_bool, true);
let const_num = lua.load("return mylib.const_num").eval::<f64>().unwrap();
assert_eq!(const_num, 123.0);
let const_vec = lua.load("return mylib.const_vec").eval::<Vector>().unwrap();
assert_eq!(const_vec, Vector::zero());
let const_str = lua.load("return mylib.const_str").eval::<String>();
assert_eq!(const_str.unwrap(), "value1");
}
#[test]
fn test_chunk_wrap() -> Result<()> {
let lua = Lua::new();
+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
+96 -5
View File
@@ -1,13 +1,13 @@
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::ffi::{CStr, CString, OsString};
use std::ffi::{CString, OsString};
use std::path::PathBuf;
use bstr::BString;
use maplit::{btreemap, btreeset, hashmap, hashset};
use mlua::{
AnyUserData, Either, Error, Function, IntoLua, Lua, RegistryKey, Result, Table, Thread, UserDataRef,
Value,
AnyUserData, BorrowedBytes, BorrowedStr, Either, Error, Function, IntoLua, Lua, RegistryKey, Result,
Table, Thread, UserDataRef, Value,
};
#[test]
@@ -60,6 +60,66 @@ fn test_string_from_lua() -> Result<()> {
Ok(())
}
#[test]
fn test_borrowedstr_into_lua() -> Result<()> {
let lua = Lua::new();
// Direct conversion
let s = lua.create_string("hello, world!")?;
let bs = s.to_str()?;
let bs2 = (&bs).into_lua(&lua)?;
assert_eq!(bs2.as_string().unwrap(), "hello, world!");
// Push into stack
let table = lua.create_table()?;
table.set("bs", &bs)?;
assert_eq!(bs, table.get::<String>("bs")?);
Ok(())
}
#[test]
fn test_borrowedstr_from_lua() -> Result<()> {
let lua = Lua::new();
// From stack
let f = lua.create_function(|_, s: BorrowedStr| Ok(s))?;
let s = f.call::<String>("hello, world!")?;
assert_eq!(s, "hello, world!");
Ok(())
}
#[test]
fn test_borrowedbytes_into_lua() -> Result<()> {
let lua = Lua::new();
// Direct conversion
let s = lua.create_string("hello, world!")?;
let bb = s.as_bytes();
let bb2 = (&bb).into_lua(&lua)?;
assert_eq!(bb2.as_string().unwrap(), "hello, world!");
// Push into stack
let table = lua.create_table()?;
table.set("bb", &bb)?;
assert_eq!(bb, table.get::<String>("bb")?.as_bytes());
Ok(())
}
#[test]
fn test_borrowedbytes_from_lua() -> Result<()> {
let lua = Lua::new();
// From stack
let f = lua.create_function(|_, s: BorrowedBytes| Ok(s))?;
let s = f.call::<String>("hello, world!")?;
assert_eq!(s, "hello, world!");
Ok(())
}
#[test]
fn test_table_into_lua() -> Result<()> {
let lua = Lua::new();
@@ -390,8 +450,8 @@ fn test_conv_cstring() -> Result<()> {
let s2: CString = lua.globals().get("s")?;
assert_eq!(s, s2);
let cs = CStr::from_bytes_with_nul(b"hello\0").unwrap();
lua.globals().set("cs", cs)?;
let cs = c"hello";
lua.globals().set("cs", c"hello")?;
let cs2: CString = lua.globals().get("cs")?;
assert_eq!(cs, cs2.as_c_str());
@@ -657,3 +717,34 @@ fn test_either_from_lua() -> Result<()> {
Ok(())
}
#[test]
fn test_char_into_lua() -> Result<()> {
let lua = Lua::new();
let v = '🦀';
let v2 = v.into_lua(&lua)?;
assert_eq!(Some(v.to_string()), v2.as_string_lossy());
Ok(())
}
#[test]
fn test_char_from_lua() -> Result<()> {
let lua = Lua::new();
assert_eq!(lua.convert::<char>("A")?, 'A');
assert_eq!(lua.convert::<char>(65)?, 'A');
assert_eq!(lua.convert::<char>(128175)?, '💯');
assert!(lua
.convert::<char>(5456324)
.is_err_and(|e| e.to_string().contains("integer out of range")));
assert!(lua
.convert::<char>("hello")
.is_err_and(|e| e.to_string().contains("expected string to have exactly one char")));
assert!(lua
.convert::<char>(HashMap::<String, String>::new())
.is_err_and(|e| e.to_string().contains("expected string or integer")));
Ok(())
}
+3 -3
View File
@@ -214,7 +214,7 @@ fn test_function_dump() -> Result<()> {
#[cfg(feature = "luau")]
#[test]
fn test_finction_coverage() -> Result<()> {
fn test_function_coverage() -> Result<()> {
let lua = Lua::new();
lua.set_compiler(mlua::Compiler::default().set_coverage_level(1));
@@ -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(())
+45 -17
View File
@@ -1,6 +1,5 @@
#![cfg(not(feature = "luau"))]
use std::ops::Deref;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, Mutex};
@@ -27,7 +26,7 @@ fn test_line_counts() -> Result<()> {
assert_eq!(debug.event(), DebugEvent::Line);
hook_output.lock().unwrap().push(debug.curr_line());
Ok(VmState::Continue)
});
})?;
lua.load(
r#"
local x = 2 + 3
@@ -62,7 +61,7 @@ fn test_function_calls() -> Result<()> {
let name = names.name.map(|s| s.into_owned());
hook_output.lock().unwrap().push((name, source.what));
Ok(VmState::Continue)
});
})?;
lua.load(
r#"
@@ -101,17 +100,13 @@ fn test_error_within_hook() -> Result<()> {
lua.set_hook(HookTriggers::EVERY_LINE, |_lua, _debug| {
Err(Error::runtime("Something happened in there!"))
});
})?;
let err = lua.load("x = 1").exec().expect_err("panic didn't propagate");
match err {
Error::CallbackError { cause, .. } => match cause.deref() {
Error::RuntimeError(s) => assert_eq!(s, "Something happened in there!"),
_ => panic!("wrong callback error kind caught"),
},
_ => panic!("wrong error kind caught"),
};
Error::RuntimeError(msg) => assert_eq!(msg, "Something happened in there!"),
err => panic!("expected `RuntimeError` with a specific message, got {err:?}"),
}
Ok(())
}
@@ -135,7 +130,7 @@ fn test_limit_execution_instructions() -> Result<()> {
Ok(VmState::Continue)
}
},
);
)?;
lua.globals().set("x", Value::Integer(0))?;
let _ = lua
@@ -158,7 +153,7 @@ fn test_hook_removal() -> Result<()> {
lua.set_hook(HookTriggers::new().every_nth_instruction(1), |_lua, _debug| {
Err(Error::runtime("this hook should've been removed by this time"))
});
})?;
assert!(lua.load("local x = 1").exec().is_err());
lua.remove_hook();
@@ -205,10 +200,10 @@ fn test_hook_swap_within_hook() -> Result<()> {
});
Ok(VmState::Continue)
})
});
})?;
Ok(VmState::Continue)
})
});
})?;
TL_LUA.with(|tl| {
let tl = tl.borrow();
@@ -247,7 +242,7 @@ fn test_hook_threads() -> Result<()> {
assert_eq!(debug.event(), DebugEvent::Line);
hook_output.lock().unwrap().push(debug.curr_line());
Ok(VmState::Continue)
});
})?;
co.resume::<()>(())?;
lua.remove_hook();
@@ -277,7 +272,7 @@ fn test_hook_yield() -> Result<()> {
.into_function()?;
let co = lua.create_thread(func)?;
co.set_hook(HookTriggers::EVERY_LINE, move |_lua, _debug| Ok(VmState::Yield));
co.set_hook(HookTriggers::EVERY_LINE, move |_lua, _debug| Ok(VmState::Yield))?;
#[cfg(any(feature = "lua54", feature = "lua53"))]
{
@@ -297,3 +292,36 @@ fn test_hook_yield() -> Result<()> {
Ok(())
}
#[test]
fn test_global_hook() -> Result<()> {
let lua = Lua::new();
let counter = Arc::new(AtomicI64::new(0));
let hook_counter = counter.clone();
lua.set_global_hook(HookTriggers::EVERY_LINE, move |_lua, debug| {
assert_eq!(debug.event(), DebugEvent::Line);
hook_counter.fetch_add(1, Ordering::Relaxed);
Ok(VmState::Continue)
})?;
let thread = lua.create_thread(
lua.load(
r#"
local x = 2 + 3
local y = x * 63
coroutine.yield()
local z = string.len(x..", "..y)
"#,
)
.into_function()?,
)?;
thread.resume::<()>(()).unwrap();
lua.remove_global_hook();
thread.resume::<()>(()).unwrap();
assert_eq!(thread.status(), ThreadStatus::Finished);
assert_eq!(counter.load(Ordering::Relaxed), 3);
Ok(())
}
+149 -85
View File
@@ -1,12 +1,15 @@
#![cfg(feature = "luau")]
use std::cell::Cell;
use std::fmt::Debug;
use std::fs;
use std::os::raw::c_void;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::atomic::{AtomicU64, Ordering};
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<()> {
@@ -15,83 +18,6 @@ fn test_version() -> Result<()> {
Ok(())
}
#[test]
fn test_require() -> Result<()> {
// Ensure that require() is not available if package module is not loaded
let mut lua = Lua::new_with(StdLib::NONE, LuaOptions::default())?;
assert!(lua.globals().get::<Option<Value>>("require")?.is_none());
assert!(lua.globals().get::<Option<Value>>("package")?.is_none());
if cfg!(target_arch = "wasm32") {
// TODO: figure out why emscripten fails on file operations
// Also see https://github.com/rust-lang/rust/issues/119250
return Ok(());
}
lua = Lua::new();
// Check that require() can load stdlib modules (including `package`)
lua.load(
r#"
local math = require("math")
assert(math == _G.math, "math module does not match _G.math")
local package = require("package")
assert(package == _G.package, "package module does not match _G.package")
"#,
)
.exec()?;
let temp_dir = tempfile::tempdir().unwrap();
fs::write(
temp_dir.path().join("module.luau"),
r#"
counter = (counter or 0) + 1
return {
counter = counter,
error = function() error("test") end,
}
"#,
)?;
lua.globals()
.get::<Table>("package")?
.set("path", temp_dir.path().join("?.luau").to_string_lossy())?;
lua.load(
r#"
local module = require("module")
assert(module.counter == 1)
module = require("module")
assert(module.counter == 1)
local ok, err = pcall(module.error)
assert(not ok and string.find(err, "module.luau") ~= nil)
"#,
)
.exec()?;
// Require non-existent module
match lua.load("require('non-existent')").exec() {
Err(Error::RuntimeError(e)) if e.contains("module 'non-existent' not found") => {}
r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"),
}
// Require binary module in safe mode
lua.globals()
.get::<Table>("package")?
.set("cpath", temp_dir.path().join("?.so").to_string_lossy())?;
fs::write(temp_dir.path().join("dylib.so"), "")?;
match lua.load("require('dylib')").exec() {
Err(Error::RuntimeError(e)) if cfg!(unix) && e.contains("module 'dylib' not found") => {
assert!(e.contains("dynamic libraries are disabled in safe mode"))
}
Err(Error::RuntimeError(e)) if e.contains("module 'dylib' not found") => {}
r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"),
}
Ok(())
}
#[cfg(not(feature = "luau-vector4"))]
#[test]
fn test_vectors() -> Result<()> {
@@ -270,6 +196,14 @@ fn test_sandbox() -> Result<()> {
co.sandbox()?;
assert_eq!(co.resume::<Option<i32>>(())?, Some(123));
// collectgarbage should be restricted in sandboxed mode
let collectgarbage = lua.globals().get::<Function>("collectgarbage")?;
for arg in ["collect", "stop", "restart", "step", "isrunning"] {
let err = collectgarbage.call::<()>(arg).err().unwrap().to_string();
assert!(err.contains("collectgarbage called with invalid option"));
}
assert!(collectgarbage.call::<u64>("count").unwrap() > 0);
lua.sandbox(false)?;
// Previously set variable `global` should be cleared now
@@ -279,6 +213,25 @@ 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(())
}
#[test]
fn test_sandbox_safeenv() -> Result<()> {
let lua = Lua::new();
lua.sandbox(true)?;
lua.globals().set("state", lua.create_table()?)?;
lua.globals().set_safeenv(false);
lua.load("state.a = 123").exec()?;
let a: i32 = lua.load("state.a = 321; return state.a").eval()?;
assert_eq!(a, 321);
Ok(())
}
@@ -381,11 +334,8 @@ fn test_interrupts() -> Result<()> {
//
lua.set_interrupt(|_| Err(Error::runtime("error from interrupt")));
match f.call::<()>(()) {
Err(Error::CallbackError { cause, .. }) => match *cause {
Error::RuntimeError(ref m) if m == "error from interrupt" => {}
ref e => panic!("expected RuntimeError with a specific message, got {:?}", e),
},
r => panic!("expected CallbackError, got {:?}", r),
Err(Error::RuntimeError(ref msg)) => assert_eq!(msg, "error from interrupt"),
res => panic!("expected `RuntimeError` with a specific message, got {res:?}"),
}
lua.remove_interrupt();
@@ -398,3 +348,117 @@ fn test_fflags() {
// We cannot really on any particular feature flag to be present
assert!(Lua::set_fflag("UnknownFlag", true).is_err());
}
#[test]
fn test_thread_events() -> Result<()> {
let lua = Lua::new();
let count = Arc::new(AtomicU64::new(0));
let thread_data: Arc<(AtomicPtr<c_void>, AtomicBool)> = Arc::new(Default::default());
let (count2, thread_data2) = (count.clone(), thread_data.clone());
lua.set_thread_creation_callback(move |_, thread| {
count2.fetch_add(1, Ordering::Relaxed);
(thread_data2.0).store(thread.to_pointer() as *mut _, Ordering::Relaxed);
thread_data2.1.store(false, Ordering::Relaxed);
Ok(())
});
let (count3, thread_data3) = (count.clone(), thread_data.clone());
lua.set_thread_collection_callback(move |thread_ptr| {
count3.fetch_add(1, Ordering::Relaxed);
if thread_data3.0.load(Ordering::Relaxed) == thread_ptr.0 {
thread_data3.1.store(true, Ordering::Relaxed);
}
});
let t = lua.create_thread(lua.load("return 123").into_function()?)?;
assert_eq!(count.load(Ordering::Relaxed), 1);
let t_ptr = t.to_pointer();
assert_eq!(t_ptr, thread_data.0.load(Ordering::Relaxed));
assert!(!thread_data.1.load(Ordering::Relaxed));
// Thead will be destroyed after GC cycle
drop(t);
lua.gc_collect()?;
assert_eq!(count.load(Ordering::Relaxed), 2);
assert_eq!(t_ptr, thread_data.0.load(Ordering::Relaxed));
assert!(thread_data.1.load(Ordering::Relaxed));
// Check that recursion is not allowed
let count4 = count.clone();
lua.set_thread_creation_callback(move |lua, _value| {
count4.fetch_add(1, Ordering::Relaxed);
let _ = lua.create_thread(lua.load("return 123").into_function().unwrap())?;
Ok(())
});
let t = lua.create_thread(lua.load("return 123").into_function()?)?;
assert_eq!(count.load(Ordering::Relaxed), 3);
lua.remove_thread_callbacks();
drop(t);
lua.gc_collect()?;
assert_eq!(count.load(Ordering::Relaxed), 3);
// Test error inside callback
lua.set_thread_creation_callback(move |_, _| Err(Error::runtime("error when processing thread event")));
let result = lua.create_thread(lua.load("return 123").into_function()?);
assert!(result.is_err());
assert!(
matches!(result, Err(Error::RuntimeError(err)) if err.contains("error when processing thread event"))
);
// Test context switch when running Lua script
let count = Cell::new(0);
lua.set_thread_creation_callback(move |_, _| {
count.set(count.get() + 1);
if count.get() == 2 {
return Err(Error::runtime("thread limit exceeded"));
}
Ok(())
});
let result = lua
.load(
r#"
local co = coroutine.wrap(function() return coroutine.create(print) end)
co()
"#,
)
.exec();
assert!(result.is_err());
assert!(matches!(result, Err(Error::RuntimeError(err)) if err.contains("thread limit exceeded")));
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;
+243
View File
@@ -0,0 +1,243 @@
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()
}
#[test]
fn test_require_errors() {
let lua = Lua::new();
// RequireAbsolutePath
let res = run_require(&lua, "/an/absolute/path");
assert!(res.is_err());
assert!(
(res.unwrap_err().to_string()).contains("require path must start with a valid prefix: ./, ../, or @")
);
// RequireUnprefixedPath
let res = run_require(&lua, "an/unprefixed/path");
assert!(res.is_err());
assert!(
(res.unwrap_err().to_string()).contains("require path must start with a valid prefix: ./, ../, or @")
);
// Pass non-string to require
let res = run_require(&lua, true);
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]
fn test_require_without_config() {
let lua = Lua::new();
// RequireSimpleRelativePath
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, "./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, "./tests/luau/require/without_config/lua_dependency").unwrap();
assert_eq!("result from lua_dependency", get_str(&res, 1));
// RequireInitLuau
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, "./tests/luau/require/without_config/lua").unwrap();
assert_eq!("result from init.lua", get_str(&res, 1));
// 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,
"./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,
"./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, "./tests/luau/require/without_config/validate_cache").unwrap();
assert!(res.is_table());
}
#[test]
fn test_require_with_config() {
let lua = Lua::new();
// RequirePathWithAlias
let res = run_require(&lua, "./tests/luau/require/with_config/src/alias_requirer").unwrap();
assert_eq!("result from dependency", get_str(&res, 1));
// RequirePathWithParentAlias
let res = run_require(&lua, "./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,
"./tests/luau/require/with_config/src/directory_alias_requirer",
)
.unwrap();
assert_eq!("result from subdirectory_dependency", get_str(&res, 1));
// RequireAliasThatDoesNotExist
let res = run_require(&lua, "@this.alias.does.not.exist");
assert!(res.is_err());
assert!((res.unwrap_err().to_string()).contains("@this.alias.does.not.exist is not a valid alias"));
// IllegalAlias
let res = run_require(&lua, "@");
assert!(res.is_err());
assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias"));
}
#[cfg(all(feature = "async", not(windows)))]
#[tokio::test]
async fn test_async_require() -> Result<()> {
let lua = Lua::new();
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path().join("async_chunk.luau");
std::fs::write(
&temp_path,
r#"
sleep_ms(10)
return "result_after_async_sleep"
"#,
)
.unwrap();
lua.globals().set(
"sleep_ms",
lua.create_async_function(|_, ms: u64| async move {
tokio::time::sleep(std::time::Duration::from_millis(ms)).await;
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 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")
"#,
)
.exec_async()
.await
}
+6
View File
@@ -0,0 +1,6 @@
{
"aliases": {
"dep": "./this_should_be_overwritten_by_child_luaurc",
"otherdep": "./src/other_dependency"
}
}
@@ -0,0 +1,6 @@
{
"aliases": {
"dep": "./dependency",
"subdir": "./subdirectory"
}
}
@@ -0,0 +1 @@
return require("@dep")
@@ -0,0 +1 @@
return {"result from dependency"}
@@ -0,0 +1 @@
return(require("@subdir/subdirectory_dependency"))
@@ -0,0 +1 @@
return {"result from other_dependency"}
@@ -0,0 +1 @@
return require("@otherdep")
@@ -0,0 +1 @@
return {"result from subdirectory_dependency"}
@@ -0,0 +1 @@
return {"result from dependency"}

Some files were not shown because too many files have changed in this diff Show More